embedded3 S2-8 instrument: the load routes in PARTS per body — exchange split into diffusive / convective (cut_wall_exchange_parts), a diagnostic x window on every load route (exchange::set_load_window), the flag test as a 2D periodic slab (RTX_E3_FLAG_NZ) with an amplitude knob (RTX_E3_FLAG_AMP, 0 = frozen) and a PARTS summary; cut_wall_force / _per_span moved to exchange.rs (cutwall.rs line cap)
CI / Build (macos-latest) (push) Waiting to run
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 (ubuntu-latest) (push) Failing after 4s
Documentation / Build API Documentation (push) Failing after 4s
CI / Build CPU-Only (Explicit) (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 4s
CI / Format Check (push) Failing after 17s
CI / Clippy Check (push) Failing after 38s
Performance Benchmarks / Run Benchmarks (push) Successful in 1m47s

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
Omar Sobh
2026-09-19 08:53:40 -05:00
co-authored by Claude Fable 5.1
parent 3f0bd0650a
commit 5e1b3e0731
4 changed files with 169 additions and 55 deletions
@@ -16,6 +16,7 @@
use super::Grid;
use super::body::Body;
use super::cut::CutGeometry;
use super::exchange::in_load_window;
use super::field::Field;
use super::step::{Boundaries, Side};
use super::wall::{FaceKind, Mask};
@@ -589,37 +590,6 @@ impl Mask {
flux
}
/// The cut-cell load route: the force on the body from the operators
/// themselves — `Σ_c p_c W_c` over the cells plus the implicit wall
/// shear `Σ_f μ A_w (u_f U_b)/d_f` over the unknown faces. `None`
/// without a cut geometry.
pub fn cut_wall_force(&self, body: &Body, f: &Field, mu: f64, t: f64) -> Option<[f64; 3]> {
let (p, s) = self.cut_wall_force_parts(body, f, mu, t)?;
let x = self.cut_wall_exchange_force(body, f, mu, self.density, t, None)?;
Some([p[0] + s[0] + x[0], p[1] + s[1] + x[1], p[2] + s[2] + x[2]])
}
/// The cut-cell load route restricted to the cells (and faces) of the
/// planes `k0..k1`, divided by the slab's thickness: the load per unit
/// span on a body's mid-section.
pub fn cut_wall_force_per_span(
&self,
body: &Body,
f: &Field,
mu: f64,
t: f64,
(k0, k1): (usize, usize),
) -> Option<[f64; 3]> {
let (p, s) = self.cut_wall_force_parts_in(body, f, mu, t, Some((k0, k1)))?;
let x = self.cut_wall_exchange_force(body, f, mu, self.density, t, Some((k0, k1)))?;
let lz = (k1 - k0) as f64 * self.grid.dz;
Some([
(p[0] + s[0] + x[0]) / lz,
(p[1] + s[1] + x[1]) / lz,
(p[2] + s[2] + x[2]) / lz,
])
}
/// The cut-cell load route split into its pressure and shear parts.
pub fn cut_wall_force_parts(
&self,
@@ -631,7 +601,7 @@ impl Mask {
self.cut_wall_force_parts_in(body, f, mu, t, None)
}
fn cut_wall_force_parts_in(
pub(super) fn cut_wall_force_parts_in(
&self,
body: &Body,
f: &Field,
@@ -646,8 +616,9 @@ impl Mask {
let mut pressure = [0.0; 3];
let mut force = [0.0; 3];
for (idx, w) in cut.wall.iter().enumerate() {
let k = g.kji(idx).0;
if self.cell_fluid[idx] && k >= k0 && k < k1 {
let (k, _, i) = g.kji(idx);
if self.cell_fluid[idx] && k >= k0 && k < k1 && in_load_window((i as f64 + 0.5) * g.dx)
{
for c in 0..3 {
pressure[c] += f.p[idx] * w[c];
}
@@ -676,7 +647,7 @@ impl Mask {
1 => self.v_kind[idx],
_ => self.w_kind[idx],
};
if kind != FaceKind::Fluid {
if kind != FaceKind::Fluid || !in_load_window(lat.face_position(c, p)[0]) {
continue;
}
let cv = self.cv_geometry(c, p);
@@ -13,7 +13,55 @@ use super::field::Field;
use super::wall::{FaceKind, Mask};
use crate::solvers::incompressible::ConvectionScheme;
/// DIAGNOSTIC: an x window on every load route (`set_load_window`): cells and
/// faces outside `[x0, x1)` are skipped — the cylinder and the flag read
/// apart. Process-wide; `None` (the default) reads the whole body.
static LOAD_WINDOW: std::sync::Mutex<Option<(f64, f64)>> = std::sync::Mutex::new(None);
/// Set or clear the diagnostic x window of the load routes.
pub fn set_load_window(window: Option<(f64, f64)>) {
*LOAD_WINDOW.lock().expect("load window") = window;
}
pub(super) fn in_load_window(x: f64) -> bool {
LOAD_WINDOW
.lock()
.expect("load window")
.is_none_or(|(x0, x1)| x >= x0 && x < x1)
}
impl Mask {
/// The cut-cell load route: the force on the body from the operators
/// themselves — `Σ_c p_c W_c` over the cells plus the implicit wall
/// shear `Σ_f μ A_w (u_f U_b)/d_f` over the unknown faces. `None`
/// without a cut geometry.
pub fn cut_wall_force(&self, body: &Body, f: &Field, mu: f64, t: f64) -> Option<[f64; 3]> {
let (p, s) = self.cut_wall_force_parts(body, f, mu, t)?;
let x = self.cut_wall_exchange_force(body, f, mu, self.density, t, None)?;
Some([p[0] + s[0] + x[0], p[1] + s[1] + x[1], p[2] + s[2] + x[2]])
}
/// The cut-cell load route restricted to the cells (and faces) of the
/// planes `k0..k1`, divided by the slab's thickness: the load per unit
/// span on a body's mid-section.
pub fn cut_wall_force_per_span(
&self,
body: &Body,
f: &Field,
mu: f64,
t: f64,
(k0, k1): (usize, usize),
) -> Option<[f64; 3]> {
let (p, s) = self.cut_wall_force_parts_in(body, f, mu, t, Some((k0, k1)))?;
let x = self.cut_wall_exchange_force(body, f, mu, self.density, t, Some((k0, k1)))?;
let lz = (k1 - k0) as f64 * self.grid.dz;
Some([
(p[0] + s[0] + x[0]) / lz,
(p[1] + s[1] + x[1]) / lz,
(p[2] + s[2] + x[2]) / lz,
])
}
/// The momentum the fluid's face control volumes exchange with the
/// prescribed faces beside them, as a force on the body (the negative
/// of the force on the fluid), over the z planes `planes` (all when
@@ -27,6 +75,23 @@ impl Mask {
t: f64,
planes: Option<(usize, usize)>,
) -> Option<[f64; 3]> {
let (d, c) = self.cut_wall_exchange_parts(body, f, mu, rho, t, planes)?;
Some([d[0] + c[0], d[1] + c[1], d[2] + c[2]])
}
/// The exchange split into its DIFFUSIVE and CONVECTIVE parts (forces on
/// the body). On a wall at rest the convective part is the scheme's
/// flux correction only; on a moving wall it carries `ρ m (u_face u_f)`
/// with `m` the wall's own swept flux — O(v_wall h / ν) times the shear.
pub fn cut_wall_exchange_parts(
&self,
body: &Body,
f: &Field,
mu: f64,
rho: f64,
t: f64,
planes: Option<(usize, usize)>,
) -> Option<([f64; 3], [f64; 3])> {
let _ = body;
let _ = t;
self.cut.as_ref()?;
@@ -55,6 +120,7 @@ impl Mask {
|a: [i64; 3], b: [i64; 3], s: i64| [a[0] + s * b[0], a[1] + s * b[1], a[2] + s * b[2]];
let upwind = |m: f64, up: f64, dn: f64| if m >= 0.0 { up } else { dn };
let mut force = [0.0; 3];
let mut convective = [0.0; 3];
for c in 0..3 {
let (ir, jr, kr) = match c {
0 => (1..nx, 0..ny, k0..k1),
@@ -67,7 +133,9 @@ impl Mask {
for i in ir.clone() {
let p = [i as i64, j as i64, k as i64];
let idx = lat.face(c, p).expect("face");
if kind(c, idx) != FaceKind::Fluid {
if kind(c, idx) != FaceKind::Fluid
|| !in_load_window(lat.face_position(c, p)[0])
{
continue;
}
let cv = self.cv_geometry(c, p);
@@ -123,9 +191,9 @@ impl Mask {
scheme.face_correction(up2, un, u0)
};
let u_face = upwind(m_plus, u0, un) + delta;
let on_fluid = -rho * m_plus * (u_face - u0)
+ mu * cv.ap[d][1] * a_d * (un - u0) / solid_spacing(1.0);
force[c] -= on_fluid;
convective[c] -= -rho * m_plus * (u_face - u0);
force[c] -=
mu * cv.ap[d][1] * a_d * (un - u0) / solid_spacing(1.0);
}
}
// Minus side.
@@ -140,9 +208,9 @@ impl Mask {
scheme.face_correction(up1, u0, ud)
};
let u_face = upwind(m_minus, ud, u0) + delta;
let on_fluid = rho * m_minus * (u_face - u0)
+ mu * cv.ap[d][0] * a_d * (ud - u0) / solid_spacing(-1.0);
force[c] -= on_fluid;
convective[c] -= rho * m_minus * (u_face - u0);
force[c] -=
mu * cv.ap[d][0] * a_d * (ud - u0) / solid_spacing(-1.0);
}
}
}
@@ -150,7 +218,7 @@ impl Mask {
}
}
}
Some(force)
Some((force, convective))
}
/// The closure lag of a moving body's pressure correction: the
@@ -52,7 +52,7 @@ impl Mask {
continue;
}
let (k, j, i) = g.kji(idx);
if k < k0 || k >= k1 {
if k < k0 || k >= k1 || !super::exchange::in_load_window((i as f64 + 0.5) * g.dx) {
continue;
}
let xc = [