P5-3 instruments: per-region wall loads on saved instants (top / bottom / tip arc / fillets+cylinder — drag, lift, normal-traction mean/min/max; RTX_FSI2O_AUDIT_FACES, RTX_FSI2O_AUDIT_ONLY) and the patch-thickness knobs (RTX_FSI2O_PATCH_OFFSET / _PATCH_ROWS, RTX_OVERSET_PATCH_OFFSET / _PATCH_ROWS; save tags carry the offset) — the overlap band sat at a fixed number of cells (6 h) and moved inward in metres with refinement
CI / Build (macos-latest) (push) Failing after 9s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (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 (ubuntu-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (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 / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (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_0116sg1Qz1gMv9hdcKP1XUam
This commit is contained in:
Omar Sobh
2026-09-08 05:35:14 -07:00
co-authored by Claude Fable 5.1
parent 2c1143076e
commit 3e019d1491
3 changed files with 195 additions and 13 deletions
@@ -34,6 +34,37 @@ const FILLET: f64 = 0.5 * 0.41 / 41.0;
const PATCH_ROWS: usize = 12;
const PATCH_STRETCH: f64 = 4.0;
/// The patch's thickness in units of h (`RTX_FSI2O_PATCH_OFFSET`, 6).
/// At the default the overlap band sits a fixed NUMBER of cells from the
/// wall and moves inward in metres with refinement (ny 41 / 62 / 82 →
/// 60 / 40 / 30 mm); P5-3 holds it in metres across the ladder instead.
fn patch_offset_h() -> f64 {
std::env::var("RTX_FSI2O_PATCH_OFFSET")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(6.0)
}
/// The patch's across-rows (`RTX_FSI2O_PATCH_ROWS`, 12; scale it with the
/// offset to keep the wall spacing).
fn patch_rows() -> usize {
std::env::var("RTX_FSI2O_PATCH_ROWS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(PATCH_ROWS)
}
/// The save tag: `fsi2o_ny{ny}`, plus `_off{offset}` off the default
/// patch thickness so a thicker patch never loads the default's state.
fn save_tag(ny: usize) -> String {
let offset = patch_offset_h();
if (offset - 6.0).abs() < 1e-12 {
format!("fsi2o_ny{ny}")
} else {
format!("fsi2o_ny{ny}_off{offset}")
}
}
/// The deforming wall as the patch sees it: the wetted polygon (the
/// `Interface` walk, anchors included) and the velocity at each vertex.
#[derive(Debug, Clone, Default)]
@@ -221,8 +252,8 @@ impl OversetFluid {
FLAG_X1,
h,
FILLET,
6.0 * h,
PATCH_ROWS,
patch_offset_h() * h,
patch_rows(),
PATCH_STRETCH,
sweeps,
)?;
@@ -244,8 +275,8 @@ impl OversetFluid {
FLAG_X1,
h,
FILLET,
6.0 * h,
PATCH_ROWS,
patch_offset_h() * h,
patch_rows(),
PATCH_STRETCH,
20_000,
)?;
@@ -359,7 +390,7 @@ impl OversetFluid {
pub fn save(&self, dir: &str) -> CfdResult<()> {
let dir = std::path::Path::new(dir);
std::fs::create_dir_all(dir).expect("save dir");
let tag = format!("fsi2o_ny{}", self.ny);
let tag = save_tag(self.ny);
self.field
.background
.save(&dir.join(format!("bg_{tag}.bin")))?;
@@ -390,7 +421,7 @@ impl OversetFluid {
/// (the deformed mesh), its vectors, the time and the interface `d`
/// (`dir/inst_<tag>_<step>/`).
pub fn save_instant(&self, dir: &str, step: usize, d: &[f64], ddot: &[f64]) -> CfdResult<()> {
let tag = format!("fsi2o_ny{}", self.ny);
let tag = save_tag(self.ny);
let dir = std::path::Path::new(dir).join(format!("inst_{tag}_{step:06}"));
std::fs::create_dir_all(&dir).expect("instant dir");
self.field.background.save(&dir.join("bg.bin"))?;
@@ -496,6 +527,110 @@ impl OversetFluid {
Ok((fluid, d, t))
}
/// The wall load by REGION at the current state (P5-3): for the top
/// face, the bottom face, the tip arc, and the fillets + cylinder —
/// `(name, faces, length, drag, lift, mean t_n, min t_n, max t_n)`
/// with `t_n` the normal traction (≈ p at the wall), and the patch's
/// pressure level.
pub fn wall_regions(
&self,
d: &[f64],
) -> (
Vec<(&'static str, usize, f64, f64, f64, f64, f64, f64)>,
f64,
) {
let e = self.interface.edges(d);
let tip_mid = [
0.5 * (e.tip[0][0] + e.tip[e.tip.len() - 1][0]),
0.5 * (e.tip[0][1] + e.tip[e.tip.len() - 1][1]),
];
let nearest = |pts: &[[f64; 2]], q: [f64; 2]| -> f64 {
pts.iter()
.map(|p| (p[0] - q[0]).powi(2) + (p[1] - q[1]).powi(2))
.fold(f64::INFINITY, f64::min)
};
let mut acc: Vec<(&'static str, usize, f64, f64, f64, f64, f64, f64)> = vec![
(
"top",
0,
0.0,
0.0,
0.0,
0.0,
f64::INFINITY,
f64::NEG_INFINITY,
),
(
"bottom",
0,
0.0,
0.0,
0.0,
0.0,
f64::INFINITY,
f64::NEG_INFINITY,
),
(
"tip arc",
0,
0.0,
0.0,
0.0,
0.0,
f64::INFINITY,
f64::NEG_INFINITY,
),
(
"fillets+cyl",
0,
0.0,
0.0,
0.0,
0.0,
f64::INFINITY,
f64::NEG_INFINITY,
),
];
for (centre, normal, len, traction) in self.solver.patch().wall_tractions(
&self.field.patch,
PatchSide::Inner,
self.solver.time(),
) {
let on_cyl =
((centre[0] - CYL_CENTRE[0]).powi(2) + (centre[1] - CYL_CENTRE[1]).powi(2)).sqrt()
< CYL_R + 1.5 * FILLET;
let near_tip = ((centre[0] - tip_mid[0]).powi(2) + (centre[1] - tip_mid[1]).powi(2))
.sqrt()
< 2.5 * FLAG_T;
let k = if on_cyl {
3
} else if near_tip {
2
} else if nearest(&e.top, centre) <= nearest(&e.bottom, centre) {
0
} else {
1
};
let tn = traction[0] * normal[0] + traction[1] * normal[1];
let r = &mut acc[k];
r.1 += 1;
r.2 += len;
r.3 += traction[0] * len;
r.4 += traction[1] * len;
r.5 += tn * len;
r.6 = r.6.min(tn);
r.7 = r.7.max(tn);
}
for r in &mut acc {
if r.2 > 0.0 {
r.5 /= r.2;
}
}
let p = &self.field.patch.p;
let level = p.iter().sum::<f64>() / p.len().max(1) as f64;
(acc, level)
}
/// The solver-metric momentum chain at the current state (§5.11's
/// instrument on the moving patch): the solved-face pin, the ring, the
/// box in the solver's flux form, the patch's balance, the wall.
@@ -568,7 +703,7 @@ impl OversetFluid {
/// returns its time.
pub fn load(&mut self, dir: &str) -> CfdResult<f64> {
let dir = std::path::Path::new(dir);
let tag = format!("fsi2o_ny{}", self.ny);
let tag = save_tag(self.ny);
self.field.background = FlowField::load(&dir.join(format!("bg_{tag}.bin")))?;
let read = |name: &str| -> Vec<f64> {
let bytes = std::fs::read(dir.join(format!("patch_{tag}_{name}.bin")))
@@ -611,8 +746,8 @@ impl OversetFluid {
FLAG_X1,
self.h,
FILLET,
6.0 * self.h,
PATCH_ROWS,
patch_offset_h() * self.h,
patch_rows(),
PATCH_STRETCH,
if self.warm_sweeps > 0 && self.warm_base.is_some() {
self.warm_sweeps