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
Co-Authored-By: Claude Fable 5.1 <[email protected]>
138 lines
5.3 KiB
Rust
138 lines
5.3 KiB
Rust
//! 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))
|
||
}
|
||
}
|