rtx-cfd: OverlapMap::region_force — momentum flux into a background region (control-volume face formula, one-sided next to holes); overset_cfd1 prints the four momentum routes (CV box, ring outer, hole boundary, wall) and their defects at the settled state
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01X2GmJXeQ2njUecEKiJZ1G2
This commit is contained in:
Omar Sobh
2026-09-06 09:08:01 -07:00
co-authored by Claude Fable 5.1
parent 5f780447de
commit 134ac03870
2 changed files with 178 additions and 1 deletions
@@ -509,6 +509,160 @@ impl OverlapMap {
/// Background-side overlap mass defect: `Σ_fringe |Σ_f sign F_f|`
/// (volume flux), the continuity the fringe cells do not enforce.
/// The force the background transmits INTO the region of cells whose
/// class satisfies `inside` — the sum over the region's boundary faces
/// of `sigma·n rho u (u·n)` with `n` pointing out of the region, the
/// control-volume formula of `EmbeddedMask::control_volume_force`
/// without the unsteady term (a settled-state diagnostic). Values are
/// taken from non-hole cells only: a face next to a hole cell uses the
/// one-sided stencil from its valid side, so the hole boundary itself
/// (`inside = Hole`) is evaluated from the fringe's stamped values.
///
/// Two regions make the P4 momentum-defect measurement: `Fringe |
/// Hole` (what the active region passes to the ring) and `Hole` (what
/// the ring passes on); their difference is the fringe ring's momentum
/// defect, and the hole boundary against the patch's wall force is the
/// patch region's.
pub fn region_force(
&self,
field: &FlowField,
rho: f64,
mu: f64,
inside: impl Fn(CellClass) -> bool,
) -> (f64, f64) {
let (nx, ny, dx, dy) = (self.nx, self.ny, self.dx, self.dy);
let valid = |j: isize, i: isize| -> bool {
j >= 0
&& i >= 0
&& (j as usize) < ny
&& (i as usize) < nx
&& self.class(j as usize, i as usize) != CellClass::Hole
};
let is_in = |j: isize, i: isize| -> bool {
j >= 0
&& i >= 0
&& (j as usize) < ny
&& (i as usize) < nx
&& inside(self.class(j as usize, i as usize))
};
// Face-located u is valid when either adjacent cell is; likewise v.
let uf_valid = |j: isize, i: isize| valid(j, i - 1) || valid(j, i);
let vf_valid = |j: isize, i: isize| valid(j - 1, i) || valid(j, i);
let u = |j: isize, i: isize| field.u[(j as usize, i as usize)];
let v = |j: isize, i: isize| field.v[(j as usize, i as usize)];
let p = |j: isize, i: isize| field.p[(j as usize, i as usize)];
// Cell-centred v and u (averages of the cell's two faces).
let v_c = |j: isize, i: isize| 0.5 * (v(j, i) + v(j + 1, i));
let u_c = |j: isize, i: isize| 0.5 * (u(j, i) + u(j, i + 1));
// Average of the valid members of a pair, or `None`.
let pair = |a: Option<f64>, b: Option<f64>| match (a, b) {
(Some(a), Some(b)) => Some(0.5 * (a + b)),
(Some(a), None) | (None, Some(a)) => Some(a),
(None, None) => None,
};
// Derivative across `x0 → x1 → x2` (spacing `h`): central when both
// ends are valid, one-sided otherwise, zero when nothing is.
let deriv = |m: Option<f64>, c: f64, pl: Option<f64>, h: f64| match (m, pl) {
(Some(m), Some(pl)) => (pl - m) / (2.0 * h),
(Some(m), None) => (c - m) / h,
(None, Some(pl)) => (pl - c) / h,
(None, None) => 0.0,
};
let (mut fx, mut fy) = (0.0, 0.0);
for jc in 0..ny as isize {
for ic in 0..nx as isize {
if !is_in(jc, ic) {
continue;
}
// Vertical faces: west (u face ic, n = x) and east (ic + 1, +x).
for (i, sign, nj, ni) in [(ic, -1.0, jc, ic - 1), (ic + 1, 1.0, jc, ic + 1)] {
if is_in(nj, ni) || nj < 0 || ni < 0 || ni >= nx as isize {
continue;
}
let j = jc;
let un = u(j, i);
let p_f = pair(
valid(j, i - 1).then(|| p(j, i - 1)),
valid(j, i).then(|| p(j, i)),
)
.unwrap_or(0.0);
let dudx = deriv(
uf_valid(j, i - 1).then(|| u(j, i - 1)),
un,
(i < nx as isize && uf_valid(j, i + 1)).then(|| u(j, i + 1)),
dx,
);
let dudy = deriv(
(j >= 1 && uf_valid(j - 1, i)).then(|| u(j - 1, i)),
un,
(j + 1 < ny as isize && uf_valid(j + 1, i)).then(|| u(j + 1, i)),
dy,
);
let vw = valid(j, i - 1).then(|| v_c(j, i - 1));
let ve = valid(j, i).then(|| v_c(j, i));
let dvdx = match (vw, ve) {
(Some(a), Some(b)) => (b - a) / dx,
(Some(a), None) => {
(a - if valid(j, i - 2) { v_c(j, i - 2) } else { a }) / dx
}
(None, Some(b)) => {
((if valid(j, i + 1) { v_c(j, i + 1) } else { b }) - b) / dx
}
(None, None) => 0.0,
};
let v_f = pair(vw, ve).unwrap_or(0.0);
let sxx = -p_f + 2.0 * mu * dudx;
let sxy = mu * (dudy + dvdx);
fx += sign * (sxx - rho * un * un) * dy;
fy += sign * (sxy - rho * v_f * un) * dy;
}
// Horizontal faces: south (v face jc, n = y) and north (jc + 1, +y).
for (j, sign, nj, ni) in [(jc, -1.0, jc - 1, ic), (jc + 1, 1.0, jc + 1, ic)] {
if is_in(nj, ni) || nj < 0 || nj >= ny as isize {
continue;
}
let i = ic;
let vn = v(j, i);
let p_f = pair(
valid(j - 1, i).then(|| p(j - 1, i)),
valid(j, i).then(|| p(j, i)),
)
.unwrap_or(0.0);
let dvdy = deriv(
vf_valid(j - 1, i).then(|| v(j - 1, i)),
vn,
(j < ny as isize && vf_valid(j + 1, i)).then(|| v(j + 1, i)),
dy,
);
let dvdx = deriv(
(i >= 1 && vf_valid(j, i - 1)).then(|| v(j, i - 1)),
vn,
(i + 1 < nx as isize && vf_valid(j, i + 1)).then(|| v(j, i + 1)),
dx,
);
let us = valid(j - 1, i).then(|| u_c(j - 1, i));
let un_ = valid(j, i).then(|| u_c(j, i));
let dudy = match (us, un_) {
(Some(a), Some(b)) => (b - a) / dy,
(Some(a), None) => {
(a - if valid(j - 2, i) { u_c(j - 2, i) } else { a }) / dy
}
(None, Some(b)) => {
((if valid(j + 1, i) { u_c(j + 1, i) } else { b }) - b) / dy
}
(None, None) => 0.0,
};
let u_f = pair(us, un_).unwrap_or(0.0);
let syy = -p_f + 2.0 * mu * dvdy;
let sxy = mu * (dudy + dvdx);
fx += sign * (sxy - rho * u_f * vn) * dx;
fy += sign * (syy - rho * vn * vn) * dx;
}
}
}
(fx, fy)
}
pub fn background_mass_defect(&self, field: &FlowField) -> f64 {
let (dx, dy) = (self.dx, self.dy);
self.fringe_cells