overset: the stamp-energy instrument (P5-3's energy-source measurement) — OversetResult gains stamp_energy (½ρ(u_stamped² − u_before²)·dx·dy summed over the fringe faces after the flux balance on a reclassification step) and reclass_energy (the faces touching a cell whose class changed); the replay CSV logs both plus the reclassified-cell count per step — the comparator showed the plate loads per unit motion agree with ours to ~10 %, so the discrepancy is our limit-cycle amplitude, an h-growing energy source, and the hole boundary's reclassification is the candidate
CI / CI Success (push) Failing after 0s
CI / Build (macos-latest) (push) Failing after 16s
CI / Test (macos-latest) (push) Skipped
CI / Test (ubuntu-latest) (push) Skipped
CI / Python Bindings (maturin) (macos-latest) (push) Skipped
CI / Python Bindings (maturin) (ubuntu-latest) (push) Skipped
CI / WASM Build + Size Check (push) Skipped
CI / Distributed Training Tests (push) Skipped
Documentation / Build User Guide (push) Successful in 13s
CI / Format Check (push) Failing after 19s
CI / Clippy Check (push) Failing after 5s
Performance Benchmarks / Run Benchmarks (push) Failing after 6s
Documentation / Build API Documentation (push) Failing after 7s
CI / Build CPU-Only (Explicit) (push) Failing after 1m24s
CI / Build (ubuntu-latest) (push) Failing after 2m2s

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-12 17:32:05 -05:00
co-authored by Claude Fable 5.1
parent 796b8487ad
commit 914d369efe
2 changed files with 45 additions and 7 deletions
@@ -134,6 +134,14 @@ pub struct OversetResult {
pub reclassified_cells: usize, pub reclassified_cells: usize,
/// Background cells that jumped hole → active (patch moved > 1 cell). /// Background cells that jumped hole → active (patch moved > 1 cell).
pub fresh_cells: usize, pub fresh_cells: usize,
/// Kinetic energy the exchange wrote into the background's fringe faces
/// on this step's reclassification (`½ ρ (u_stamped² u_before²)` per
/// face times `dx·dy`, summed over the stamped `u` and `v` faces after
/// the fringe flux balance) [J/m]; 0 when the patch did not move.
pub stamp_energy: f64,
/// The part of `stamp_energy` on faces that touch a cell whose class
/// changed in this rebuild — the reclassification's own share.
pub reclass_energy: f64,
} }
/// Restorable state of the composite (the coupling re-runs a step). /// Restorable state of the composite (the coupling re-runs a step).
@@ -510,6 +518,7 @@ impl OversetPisoSolver {
// path to the bit. // path to the bit.
let mut reclassified = 0usize; let mut reclassified = 0usize;
let mut fresh = 0usize; let mut fresh = 0usize;
let (mut stamp_energy, mut reclass_energy) = (0.0_f64, 0.0_f64);
// P3b locating trace (print-only, `RTX_OVERSET_TRACE_SP`): the class // P3b locating trace (print-only, `RTX_OVERSET_TRACE_SP`): the class
// of every background cell before this step, kept only when a // of every background cell before this step, kept only when a
// reclassification happens. // reclassification happens.
@@ -581,11 +590,10 @@ impl OversetPisoSolver {
// instead of taking the patch's interpolation when they turn // instead of taking the patch's interpolation when they turn
// prescribed. // prescribed.
let h1 = std::env::var("RTX_OVERSET_H1").is_ok(); let h1 = std::env::var("RTX_OVERSET_H1").is_ok();
let u_before = field.background.u.clone();
let v_before = field.background.v.clone();
let (u_keep, v_keep) = if h1 { let (u_keep, v_keep) = if h1 {
( (Some(u_before.clone()), Some(v_before.clone()))
Some(field.background.u.clone()),
Some(field.background.v.clone()),
)
} else { } else {
(None, None) (None, None)
}; };
@@ -606,6 +614,31 @@ impl OversetPisoSolver {
} }
} }
self.balance_fringe(&mut field.background); self.balance_fringe(&mut field.background);
// The energy the stamp wrote (P5-3's energy-source instrument):
// every stamped face, and the faces touching a reclassified cell.
{
let rho = self.background.config().density;
let vol = dx * dy;
let changed = |jj: usize, ii: usize| {
jj < ny && ii < nx && old_map.class(jj, ii) != self.overlap.class(jj, ii)
};
for e in &self.overlap.fringe_u {
let (a, b) = (u_before[(e.j, e.i)], field.background.u[(e.j, e.i)]);
let de = 0.5 * rho * (b * b - a * a) * vol;
stamp_energy += de;
if changed(e.j, e.i) || (e.i > 0 && changed(e.j, e.i - 1)) {
reclass_energy += de;
}
}
for e in &self.overlap.fringe_v {
let (a, b) = (v_before[(e.j, e.i)], field.background.v[(e.j, e.i)]);
let de = 0.5 * rho * (b * b - a * a) * vol;
stamp_energy += de;
if changed(e.j, e.i) || (e.j > 0 && changed(e.j - 1, e.i)) {
reclass_energy += de;
}
}
}
// Knock-out H2 (`RTX_OVERSET_H2`): cells that turn active → fringe // Knock-out H2 (`RTX_OVERSET_H2`): cells that turn active → fringe
// keep the background's own pressure this step instead of the // keep the background's own pressure this step instead of the
// patch's stamped value (their p' is still Dirichlet from the // patch's stamped value (their p' is still Dirichlet from the
@@ -805,6 +838,8 @@ impl OversetPisoSolver {
overlap_flux_scale: scale, overlap_flux_scale: scale,
reclassified_cells: reclassified, reclassified_cells: reclassified,
fresh_cells: fresh, fresh_cells: fresh,
stamp_energy,
reclass_energy,
}) })
} }
} }
@@ -411,7 +411,7 @@ fn fsi2_overset_prescribed_motion() {
let mut f = std::fs::File::create(p).expect("csv"); let mut f = std::fs::File::create(p).expect("csv");
writeln!( writeln!(
f, f,
"t,uy_tip,drag,lift,power,wall_flux,area_rate,p_faces,p_normal,p_tangential,p_root,p_mid,p_tip,rounds_max,schwarz_ok,bg_residual,patch_div,defect_patch,defect_bg" "t,uy_tip,drag,lift,power,wall_flux,area_rate,p_faces,p_normal,p_tangential,p_root,p_mid,p_tip,rounds_max,schwarz_ok,bg_residual,patch_div,defect_patch,defect_bg,reclassified,e_stamp,e_reclass"
) )
.unwrap(); .unwrap();
f f
@@ -436,12 +436,15 @@ fn fsi2_overset_prescribed_motion() {
if let Err(e) = fluid.set_geometry(&d_r, &v_r) { if let Err(e) = fluid.set_geometry(&d_r, &v_r) {
panic!("set_geometry died at step {step}, t = {t_new:.4}: {e:?}"); panic!("set_geometry died at step {step}, t = {t_new:.4}: {e:?}");
} }
let (rounds_max, schwarz_ok, bg_res, patch_div) = match fluid.step() { let (rounds_max, schwarz_ok, bg_res, patch_div, reclassified, e_stamp, e_reclass) = match fluid.step() {
Ok(r) => ( Ok(r) => (
r.rounds.iter().copied().max().unwrap_or(0), r.rounds.iter().copied().max().unwrap_or(0),
r.schwarz_converged, r.schwarz_converged,
r.background_residual, r.background_residual,
r.patch_max_divergence, r.patch_max_divergence,
r.reclassified_cells,
r.stamp_energy,
r.reclass_energy,
), ),
Err(e) => panic!("fluid step died at step {step}, t = {t_new:.4}: {e:?}"), Err(e) => panic!("fluid step died at step {step}, t = {t_new:.4}: {e:?}"),
}; };
@@ -522,7 +525,7 @@ fn fsi2_overset_prescribed_motion() {
use std::io::Write as _; use std::io::Write as _;
writeln!( writeln!(
f, f,
"{t_new:.6},{uy_tip:.6e},{drag:.4},{lift:.4},{power:.6e},{wall_flux:.6e},{area_rate:.6e},{p_faces:.6e},{p_normal:.6e},{p_tangential:.6e},{:.6e},{:.6e},{:.6e},{rounds_max},{},{bg_res:.3e},{patch_div:.3e},{defect_patch:.3e},{defect_bg:.3e}", "{t_new:.6},{uy_tip:.6e},{drag:.4},{lift:.4},{power:.6e},{wall_flux:.6e},{area_rate:.6e},{p_faces:.6e},{p_normal:.6e},{p_tangential:.6e},{:.6e},{:.6e},{:.6e},{rounds_max},{},{bg_res:.3e},{patch_div:.3e},{defect_patch:.3e},{defect_bg:.3e},{reclassified},{e_stamp:.4e},{e_reclass:.4e}",
p_thirds[0], p_thirds[1], p_thirds[2], schwarz_ok as u8 p_thirds[0], p_thirds[1], p_thirds[2], schwarz_ok as u8
) )
.unwrap(); .unwrap();