Files
rustytorch/crates/specialized/rtx-cfd/src/solvers/incompressible/embedded3/closure.rs
T
Omar SobhandClaude Fable 5.1 99e4a7214b
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Build (macos-latest) (push) Waiting to run
CI / Format Check (push) Failing after 4s
CI / Clippy Check (push) Failing after 4s
Performance Benchmarks / Run Benchmarks (push) Failing after 5s
CI / Build (ubuntu-latest) (push) Failing after 4s
Documentation / Build User Guide (push) Successful in 7s
CI / Build CPU-Only (Explicit) (push) Failing after 54s
Documentation / Build API Documentation (push) Failing after 1m0s
embedded3 S2-5: centroid diffusion default ON (RTX_E3_DIFFUSION_CENTROID=0 restores the records); flat_wall_position_is_second_order gate; closure.rs split (cutwall.rs under 700)
Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-18 12:37:56 -05:00

138 lines
5.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! The cut wall's closures beyond the first form (S2-4, S2-5): the
//! open-part centroid shifts of the cut faces and the spacing they give
//! the cross-direction diffusion (the default since S2-5), the exchange
//! distance toward solid neighbours, and the quadratic wall gradient.
use super::cutwall::CvGeometry;
use super::wall::Mask;
impl Mask {
/// The shift of a face's open-part centroid from the face centre:
/// `½h(1 α)` along the wall normal's in-plane part, away from the
/// body (zero for a full face or without the centroid diffusion). Read
/// from the tables of [`Self::compute_face_shifts`].
pub(super) fn face_shift(&self, c: usize, p: [i64; 3]) -> [f64; 3] {
let (Some(t), Some(f)) = (self.face_shifts.as_ref(), self.lattice().face(c, p)) else {
return [0.0; 3];
};
[t[c][3 * f], t[c][3 * f + 1], t[c][3 * f + 2]]
}
/// The per-face shift tables (three components interleaved).
#[must_use]
pub fn face_shift_tables(&self) -> Option<&[Vec<f64>; 3]> {
self.face_shifts.as_ref()
}
/// Build the open-part centroid shifts of every cut face (S2-5).
pub fn compute_face_shifts(&mut self) {
let g = self.grid;
let h = [g.dx, g.dy, g.dz];
let lat = self.lattice();
let sizes = [g.n_ufaces(), g.n_vfaces(), g.n_wfaces()];
let mut tables = [
vec![0.0; 3 * sizes[0]],
vec![0.0; 3 * sizes[1]],
vec![0.0; 3 * sizes[2]],
];
for c in 0..3 {
let (ni, nj, nk) = (
g.nx + usize::from(c == 0),
g.ny + usize::from(c == 1),
g.nz + usize::from(c == 2),
);
for k in 0..nk {
for j in 0..nj {
for i in 0..ni {
let p = [i as i64, j as i64, k as i64];
let Some(f) = lat.face(c, p) else { continue };
let Some(alpha) = self.aperture(c, p) else {
continue;
};
if alpha <= 0.0 || alpha >= 1.0 {
continue;
}
// Interior faces only (a control volume needs both cells).
let on_side = p[c] == 0 || p[c] as usize == [g.nx, g.ny, g.nz][c];
if on_side && !(c == 2 && self.periodic_z) {
continue;
}
let cv = self.cv_geometry(c, p);
let mut n = cv.wall;
n[c] = 0.0;
let a = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
if a == 0.0 {
continue;
}
for d in 0..3 {
// `wall` points into the body: the open part lies the other way.
tables[c][3 * f + d] = -0.5 * h[d] * (1.0 - alpha) * n[d] / a;
}
}
}
}
}
self.face_shifts = Some(tables);
}
/// The distance over which a fluid face exchanges momentum with a solid
/// neighbour face along `d`: the full spacing, or (S2-5) the axis
/// distance from the open part's centroid to the wall, `min(h, d_f/|n_d|)`.
pub(super) fn exchange_delta(&self, cv: &CvGeometry, d: usize) -> f64 {
let h = [self.grid.dx, self.grid.dy, self.grid.dz][d];
if !self.wall_exchange_axis {
return h;
}
let a_w =
(cv.wall[0] * cv.wall[0] + cv.wall[1] * cv.wall[1] + cv.wall[2] * cv.wall[2]).sqrt();
if a_w == 0.0 {
return h;
}
let n_d = cv.wall[d].abs() / a_w;
if n_d < 1e-12 {
return h;
}
(cv.distance / n_d).min(h)
}
/// The wall-gradient coefficients of the unknown face of component
/// `c` at `p` with control volume `cv`: `u'(0) = c_1 (u_f U_b) + c_2
/// (u_n U_b)` with `u_n` the face returned (one lattice step away
/// from the body along the wall normal's dominant axis). Order 1, or
/// no open neighbour: `(1/d_f, 0, None)`.
pub(super) fn wall_gradient(
&self,
c: usize,
p: [i64; 3],
cv: &CvGeometry,
) -> (f64, f64, Option<usize>) {
let linear = (1.0 / cv.distance, 0.0, None);
if self.wall_order < 2 {
return linear;
}
let a_w =
(cv.wall[0] * cv.wall[0] + cv.wall[1] * cv.wall[1] + cv.wall[2] * cv.wall[2]).sqrt();
if a_w == 0.0 {
return linear;
}
let n = [cv.wall[0] / a_w, cv.wall[1] / a_w, cv.wall[2] / a_w];
let mut d = 0;
for k in 1..3 {
if n[k].abs() > n[d].abs() {
d = k;
}
}
// `n` points into the body: step the other way.
let mut q = p;
q[d] -= if n[d] > 0.0 { 1 } else { -1 };
let open = self.aperture(c, q).is_some_and(|a| a > 0.0);
if !open {
return linear;
}
let f = self.lattice().face(c, q).expect("open face");
let h = [self.grid.dx, self.grid.dy, self.grid.dz];
let d1 = cv.distance;
let d2 = d1 + h[d] * n[d].abs();
(d2 / (d1 * (d2 - d1)), -d1 / (d2 * (d2 - d1)), Some(f))
}
}