rtx-cfd: cylinder_flag_patch_deformed_from — the warm-started regeneration (P5's cost lever): the previous patch's interior and its outer ring projected onto the new offset polygon as the start, and an IDEMPOTENT build (one Winslow sweep alternating with the ray re-spacing; the P4-0 one-shot is not a fixed point — measured as the interior moving 0.05 h per build at rest), so a base converged as that map's fixed point stays put (9e-11 h at rest) and each build tracks the outline (interior/wall motion 0.99–1.14, worst angle 74.7°, wall row 0.37 h) 4.5× faster than the cold build; the rigid generator untouched (pins pass); fsi2 overset harness: RTX_FSI2O_WARM_SWEEPS with the converged base as the initial patch and the committed step's mesh as each step's warm start
Documentation / Build User Guide (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (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
Documentation / Build API Documentation (push) Canceled after 0s
CI / CI Success (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
Performance Benchmarks / Run Benchmarks (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-07 09:10:05 -07:00
co-authored by Claude Fable 5.1
parent 62f1d4bf9b
commit 3a68048934
4 changed files with 326 additions and 33 deletions
+125 -18
View File
@@ -589,7 +589,16 @@ pub fn cylinder_flag_patch(
// 1.74 → 1.31 with fillet = h/2 on the ny = 41/62/82 ladder). // 1.74 → 1.31 with fillet = h/2 on the ny = 41/62/82 ladder).
let inner = cylinder_flag_outline(centre, r, t, x_tip, fillet, 3, 16, h, 1.15); let inner = cylinder_flag_outline(centre, r, t, x_tip, fillet, 3, 16, h, 1.15);
let hull_src = hull_source(centre, r, [x_tip - t, centre[1]], t, [1.0, 0.0]); let hull_src = hull_source(centre, r, [x_tip - t, centre[1]], t, [1.0, 0.0]);
o_grid_from_outline(inner, hull_src, h, offset, nn, stretch, winslow_sweeps) o_grid_from_outline(
inner,
hull_src,
h,
offset,
nn,
stretch,
winslow_sweeps,
None,
)
} }
/// The FEA flag's wetted edges, deformed: `bottom` from the root to the /// The FEA flag's wetted edges, deformed: `bottom` from the root to the
@@ -622,11 +631,62 @@ pub fn cylinder_flag_patch_deformed(
nn: usize, nn: usize,
stretch: f64, stretch: f64,
winslow_sweeps: usize, winslow_sweeps: usize,
) -> CfdResult<(PatchMesh, (usize, f64))> {
cylinder_flag_patch_deformed_from(
None,
centre,
r,
t,
edges,
x_tip_ref,
h,
fillet,
offset,
nn,
stretch,
winslow_sweeps,
)
}
/// [`cylinder_flag_patch_deformed`] warm-started from `prev` (the same
/// topology: its interior rows are the Winslow start instead of the
/// transfinite one, so a few sweeps suffice when the outline moved a
/// little — P5's per-pass regeneration). `None` is the cold build.
#[allow(clippy::too_many_arguments)]
pub fn cylinder_flag_patch_deformed_from(
prev: Option<&PatchMesh>,
centre: [f64; 2],
r: f64,
t: f64,
edges: &FlagEdges,
x_tip_ref: f64,
h: f64,
fillet: f64,
offset: f64,
nn: usize,
stretch: f64,
winslow_sweeps: usize,
) -> CfdResult<(PatchMesh, (usize, f64))> { ) -> CfdResult<(PatchMesh, (usize, f64))> {
let (inner, tip) = let (inner, tip) =
cylinder_flag_outline_deformed(centre, r, t, edges, x_tip_ref, fillet, 3, 16, h, 1.15); cylinder_flag_outline_deformed(centre, r, t, edges, x_tip_ref, fillet, 3, 16, h, 1.15);
let hull_src = hull_source(centre, r, tip.centre, tip.radius, tip.axis); let hull_src = hull_source(centre, r, tip.centre, tip.radius, tip.axis);
o_grid_from_outline(inner, hull_src, h, offset, nn, stretch, winslow_sweeps) let start = prev.and_then(|m| {
(m.ns() == inner.len() && m.nn() == nn).then(|| {
(0..=nn)
.map(|k| (0..=m.ns()).map(|i| m.node_xy(m.node(k, i))).collect())
.collect::<Vec<Vec<[f64; 2]>>>()
})
});
o_grid_from_outline(
inner,
hull_src,
h,
offset,
nn,
stretch,
winslow_sweeps,
start,
)
} }
/// The deformed flag's rounded tip: the semicircle's centre, radius and /// The deformed flag's rounded tip: the semicircle's centre, radius and
@@ -857,6 +917,7 @@ fn hull_source(
/// The O-grid body shared by the rigid and the deformed generators: the /// The O-grid body shared by the rigid and the deformed generators: the
/// inner ring (counter-clockwise in, reversed to clockwise), the outer /// inner ring (counter-clockwise in, reversed to clockwise), the outer
/// ring on the hull offset, transfinite start, Winslow, re-spacing. /// ring on the hull offset, transfinite start, Winslow, re-spacing.
#[allow(clippy::too_many_arguments)]
fn o_grid_from_outline( fn o_grid_from_outline(
mut inner: Vec<[f64; 2]>, mut inner: Vec<[f64; 2]>,
hull_src: Vec<[f64; 2]>, hull_src: Vec<[f64; 2]>,
@@ -865,6 +926,7 @@ fn o_grid_from_outline(
nn: usize, nn: usize,
stretch: f64, stretch: f64,
winslow_sweeps: usize, winslow_sweeps: usize,
start: Option<Vec<Vec<[f64; 2]>>>,
) -> CfdResult<(PatchMesh, (usize, f64))> { ) -> CfdResult<(PatchMesh, (usize, f64))> {
inner.reverse(); // clockwise inner.reverse(); // clockwise
let ns = inner.len(); let ns = inner.len();
@@ -892,23 +954,68 @@ fn o_grid_from_outline(
.collect(); .collect();
let mut inner_closed = inner.clone(); let mut inner_closed = inner.clone();
inner_closed.push(inner[0]); inner_closed.push(inner[0]);
// Transfinite start. // Transfinite start, or the previous mesh's interior with the new
// inner and outer rings (the warm start).
let eta = stretched_fractions(nn, stretch); let eta = stretched_fractions(nn, stretch);
let mut x: Vec<Vec<[f64; 2]>> = eta let warm = start.is_some();
.iter() let mut x: Vec<Vec<[f64; 2]>> = match start {
.map(|&e| { Some(mut rows) => {
(0..=ns) rows[0] = inner_closed.clone();
.map(|i| { // The outer ring warm-starts too: the previous ring's nodes
[ // projected onto the new offset polygon keep the sliding
(1.0 - e) * inner_closed[i][0] + e * outer[i][0], // rows' converged distribution (the crude normal push would
(1.0 - e) * inner_closed[i][1] + e * outer[i][1], // undo it every build — measured as the interior moving
] // 0.05 h with the wall at rest).
}) rows[nn] = rows[nn]
.collect() .iter()
}) .map(|&p| nearest_on_polyline(&outer_poly, p))
.collect(); .collect();
let report = winslow_smooth(&mut x, ns, 1e-10 * h, winslow_sweeps, Some(&outer_poly)); rows
respace_rays(&mut x, ns, &eta); }
None => eta
.iter()
.map(|&e| {
(0..=ns)
.map(|i| {
[
(1.0 - e) * inner_closed[i][0] + e * outer[i][0],
(1.0 - e) * inner_closed[i][1] + e * outer[i][1],
]
})
.collect()
})
.collect(),
};
// The cold build: Winslow, then the ray re-spacing (the P4-0 one-shot).
// The warm build must be IDEMPOTENT at rest, and the one-shot is not
// (the re-spaced mesh is not the smoother's fixed point: measured as
// the interior moving 0.05 h per build with the wall at rest), so it
// alternates one sweep with a re-spacing — the base converged as a
// fixed point of that map stays put, and a moving outline is tracked.
let report = if warm {
let mut last = f64::INFINITY;
let mut done = 0;
for k in 0..winslow_sweeps {
let before = x.clone();
winslow_smooth(&mut x, ns, 0.0, 1, Some(&outer_poly));
respace_rays(&mut x, ns, &eta);
last = x
.iter()
.zip(&before)
.flat_map(|(r, b)| r.iter().zip(b))
.map(|(p, q)| ((p[0] - q[0]).powi(2) + (p[1] - q[1]).powi(2)).sqrt())
.fold(0.0, f64::max);
done = k + 1;
if last < 1e-10 * h {
break;
}
}
(done, last)
} else {
let report = winslow_smooth(&mut x, ns, 1e-10 * h, winslow_sweeps, Some(&outer_poly));
respace_rays(&mut x, ns, &eta);
report
};
let mut xs = Vec::with_capacity((nn + 1) * (ns + 1)); let mut xs = Vec::with_capacity((nn + 1) * (ns + 1));
let mut ys = Vec::with_capacity(xs.capacity()); let mut ys = Vec::with_capacity(xs.capacity());
for row in &x { for row in &x {
@@ -417,3 +417,130 @@ fn dumped_edges_reproduce_the_classification() -> CfdResult<()> {
} }
Ok(()) Ok(())
} }
/// The warm-started regeneration (P5's cost lever): along a sequence of
/// small bends, each patch built from the previous one's interior with a
/// few sweeps must pass the mesh gates, move its interior no more than a
/// few times the outline's motion (continuity — the ALE mesh velocity),
/// and stay within a cell of the cold build (neither is the smoother's
/// fixed point: the cold 500-sweep build still moves 1e-5 per sweep, so
/// the two drift apart by the smoother's own path, recorded not gated).
#[test]
fn warm_started_regeneration_tracks_the_cold_build() -> CfdResult<()> {
use rtx_cfd::mesh::patch_gen::cylinder_flag_patch_deformed_from;
let h = H / 41.0;
// The base: the smoother converged once (5 000 sweeps at rest), so the
// chain starts at its fixed point and each build tracks the outline
// only — from a 100-sweep base the first builds moved the interior
// 6.7× the wall (the smoother's own drift, 1e-2 h per 20 sweeps).
let t_base = std::time::Instant::now();
let (cold0, _) = deformed(0.0, h)?;
let (base, (bs, bl)) = cylinder_flag_patch_deformed_from(
Some(&cold0),
CENTRE,
R,
T,
&edges(0.0, 35, 2),
X1,
h,
FILLET,
6.0 * h,
12,
4.0,
5000,
)?;
println!(
" base: {bs} sweeps (last move {bl:.1e}) in {:.2} s",
t_base.elapsed().as_secs_f64()
);
let mut prev: Option<PatchMesh> = Some(base);
let (mut worst_gap, mut worst_ratio, mut t_warm, mut t_cold) = (0.0_f64, 0.0_f64, 0.0, 0.0);
for k in 0..=20 {
// A ramp to 40 mm in steps of 2 mm, then back — the flap's scale.
let a = 0.002 * if k <= 10 { k as f64 } else { (20 - k) as f64 };
let e = edges(a, 35, 2);
let t0 = std::time::Instant::now();
let (cold, _) =
cylinder_flag_patch_deformed(CENTRE, R, T, &e, X1, h, FILLET, 6.0 * h, 12, 4.0, 100)?;
t_cold += t0.elapsed().as_secs_f64();
let t1 = std::time::Instant::now();
let (warm, (sweeps, last)) = cylinder_flag_patch_deformed_from(
prev.as_ref(),
CENTRE,
R,
T,
&e,
X1,
h,
FILLET,
6.0 * h,
12,
4.0,
20,
)?;
t_warm += t1.elapsed().as_secs_f64();
warm.validate(80.0)
.unwrap_or_else(|m| panic!("warm k = {k}: {m}"));
let (min_s, wall, worst) = quality(&warm);
assert!(
min_s > 0.19 * h && wall < 0.5 * h && worst < 80.0,
"k = {k}: quality"
);
let mut gap = 0.0_f64;
for c in 0..cold.cell_count() {
let (p, q) = (cold.centre(c), warm.centre(c));
gap = gap.max(((p[0] - q[0]).powi(2) + (p[1] - q[1]).powi(2)).sqrt());
}
worst_gap = worst_gap.max(gap);
// Continuity: the interior's motion against the wall's.
if let Some(pm) = &prev {
let (mut wall_move, mut interior_move) = (0.0_f64, 0.0_f64);
for i in 0..=warm.ns() {
let (p, q) = (pm.node_xy(pm.node(0, i)), warm.node_xy(warm.node(0, i)));
wall_move = wall_move.max(((p[0] - q[0]).powi(2) + (p[1] - q[1]).powi(2)).sqrt());
for kk in 1..warm.nn() {
let (p, q) = (pm.node_xy(pm.node(kk, i)), warm.node_xy(warm.node(kk, i)));
interior_move =
interior_move.max(((p[0] - q[0]).powi(2) + (p[1] - q[1]).powi(2)).sqrt());
}
}
// With the wall at rest the interior must not move either
// (gated in cells); the ratio is read only when the wall moves.
let ratio = if wall_move > 1e-6 * h {
interior_move / wall_move
} else {
assert!(
interior_move < 1e-3 * h,
"k = {k}: the wall is at rest, the interior moved {:.2e} h",
interior_move / h
);
0.0
};
worst_ratio = worst_ratio.max(ratio);
if k % 5 == 0 {
println!(
" k = {k:2} a = {a:+.3}: warm {sweeps} sweeps (last move {last:.1e}): wall moved {:.2e} h, interior {:.2e} h ({ratio:.2}×); gap to cold-100 {:.2e} h; quality {:.3} h / {:.3} h / {worst:.1}°",
wall_move / h,
interior_move / h,
gap / h,
min_s / h,
wall / h
);
}
}
prev = Some(warm);
}
println!(
" warm-started regeneration: worst gap to cold-100 {:.2e} h, worst interior/wall motion ratio {worst_ratio:.2}, {:.3} s warm vs {:.3} s cold-100 ({:.1}×) over 21 builds",
worst_gap / h,
t_warm,
t_cold,
t_cold / t_warm.max(1e-9)
);
assert!(
worst_ratio < 3.0,
"interior moves {worst_ratio:.2}× the wall"
);
// The gap to the cold-100 build is the smoother's path, recorded.
Ok(())
}
@@ -135,6 +135,10 @@ pub struct OversetFluid {
pub correctors_total: Cell<usize>, pub correctors_total: Cell<usize>,
/// The interface of the last `set_geometry`. /// The interface of the last `set_geometry`.
pub last_d: Vec<f64>, pub last_d: Vec<f64>,
/// The committed step's patch, the warm start of every regeneration
/// in the next step (`RTX_FSI2O_WARM_SWEEPS`); `None` = cold builds.
pub warm_base: Option<rtx_cfd::mesh::PatchMesh>,
pub warm_sweeps: usize,
/// The last fluid step's mass defects (patch acceptor ring, background /// The last fluid step's mass defects (patch acceptor ring, background
/// fringe) and the patch's max divergence. /// fringe) and the patch's max divergence.
pub last_defects: Cell<(f64, f64, f64)>, pub last_defects: Cell<(f64, f64, f64)>,
@@ -196,7 +200,7 @@ impl OversetFluid {
} }
}); });
let (patch_mesh, _) = cylinder_flag_patch_deformed( let (cold_mesh, _) = cylinder_flag_patch_deformed(
CYL_CENTRE, CYL_CENTRE,
CYL_R, CYL_R,
FLAG_T, FLAG_T,
@@ -209,6 +213,37 @@ impl OversetFluid {
PATCH_STRETCH, PATCH_STRETCH,
sweeps, sweeps,
)?; )?;
// With the warm-started regeneration (`RTX_FSI2O_WARM_SWEEPS`), the
// initial patch is the converged fixed point of the warm build's
// sweep + re-spacing map, so the chain starts where it stays.
let warm_sweeps: usize = std::env::var("RTX_FSI2O_WARM_SWEEPS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(0);
let patch_mesh = if warm_sweeps > 0 {
let t0 = std::time::Instant::now();
let (m, (it, last)) = rtx_cfd::mesh::patch_gen::cylinder_flag_patch_deformed_from(
Some(&cold_mesh),
CYL_CENTRE,
CYL_R,
FLAG_T,
&interface.edges(&zero_d),
FLAG_X1,
h,
FILLET,
6.0 * h,
PATCH_ROWS,
PATCH_STRETCH,
20_000,
)?;
println!(
" warm base: {it} sweep+respace iterations (last move {last:.1e}) in {:.1} s; {warm_sweeps} per regeneration",
t0.elapsed().as_secs_f64()
);
m
} else {
cold_mesh
};
// The patch's along-body explicit limit (its wall row is // The patch's along-body explicit limit (its wall row is
// line-implicit) beside the background's combined criterion. // line-implicit) beside the background's combined criterion.
let mut hs = f64::INFINITY; let mut hs = f64::INFINITY;
@@ -290,6 +325,8 @@ impl OversetFluid {
correctors_total: Cell::new(0), correctors_total: Cell::new(0),
last_d: zero_d, last_d: zero_d,
last_defects: Cell::new((0.0, 0.0, 0.0)), last_defects: Cell::new((0.0, 0.0, 0.0)),
warm_base: None,
warm_sweeps,
}) })
} }
@@ -359,7 +396,12 @@ impl OversetFluid {
/// The patch around the interface `d`. /// The patch around the interface `d`.
pub fn patch_for(&self, d: &[f64]) -> CfdResult<rtx_cfd::mesh::PatchMesh> { pub fn patch_for(&self, d: &[f64]) -> CfdResult<rtx_cfd::mesh::PatchMesh> {
let start = std::time::Instant::now(); let start = std::time::Instant::now();
let (mesh, _) = cylinder_flag_patch_deformed( let (mesh, _) = rtx_cfd::mesh::patch_gen::cylinder_flag_patch_deformed_from(
if self.warm_sweeps > 0 {
self.warm_base.as_ref()
} else {
None
},
CYL_CENTRE, CYL_CENTRE,
CYL_R, CYL_R,
FLAG_T, FLAG_T,
@@ -370,7 +412,11 @@ impl OversetFluid {
6.0 * self.h, 6.0 * self.h,
PATCH_ROWS, PATCH_ROWS,
PATCH_STRETCH, PATCH_STRETCH,
self.sweeps, if self.warm_sweeps > 0 && self.warm_base.is_some() {
self.warm_sweeps
} else {
self.sweeps
},
)?; )?;
self.regen_count.set(self.regen_count.get() + 1); self.regen_count.set(self.regen_count.get() + 1);
self.regen_seconds self.regen_seconds
@@ -576,15 +622,14 @@ impl OversetFluid {
/// change at every face. /// change at every face.
pub fn wall_roughness(&self) -> (f64, usize, f64, f64) { pub fn wall_roughness(&self) -> (f64, usize, f64, f64) {
let mut tn: Vec<f64> = Vec::new(); let mut tn: Vec<f64> = Vec::new();
for (centre, normal, _, traction) in for (centre, normal, _, traction) in self.solver.patch().wall_tractions(
self.solver &self.field.patch,
.patch() PatchSide::Inner,
.wall_tractions(&self.field.patch, PatchSide::Inner, self.solver.time()) self.solver.time(),
{ ) {
let on_cylinder = ((centre[0] - CYL_CENTRE[0]).powi(2) let on_cylinder =
+ (centre[1] - CYL_CENTRE[1]).powi(2)) ((centre[0] - CYL_CENTRE[0]).powi(2) + (centre[1] - CYL_CENTRE[1]).powi(2)).sqrt()
.sqrt() < CYL_R + 1e-9;
< CYL_R + 1e-9;
if !on_cylinder { if !on_cylinder {
tn.push(traction[0] * normal[0] + traction[1] * normal[1]); tn.push(traction[0] * normal[0] + traction[1] * normal[1]);
} }
@@ -602,7 +647,12 @@ impl OversetFluid {
prev_diff = d; prev_diff = d;
} }
let max = tn.iter().fold(0.0_f64, |m, v| m.max(v.abs())); let max = tn.iter().fold(0.0_f64, |m, v| m.max(v.abs()));
(jumps / total.max(1e-300), flips, max, total / tn.len().max(1) as f64) (
jumps / total.max(1e-300),
flips,
max,
total / tn.len().max(1) as f64,
)
} }
/// The patch's pressure level (mean over its cells) and the wall's /// The patch's pressure level (mean over its cells) and the wall's
@@ -634,6 +684,14 @@ impl OversetFluid {
(level, flux, area, poly_flux, poly_area) (level, flux, area, poly_flux, poly_area)
} }
/// The current patch becomes the warm start of the next step's
/// regenerations (called after a committed step).
pub fn commit_base(&mut self) {
if self.warm_sweeps > 0 {
self.warm_base = Some(self.solver.patch().mesh().clone());
}
}
pub fn snapshot(&self) -> (OversetSolverState, OversetField) { pub fn snapshot(&self) -> (OversetSolverState, OversetField) {
(self.solver.snapshot(), self.field.clone()) (self.solver.snapshot(), self.field.clone())
} }
@@ -119,6 +119,7 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
} }
// The release time is the fluid's clock (the rigid step count rounds). // The release time is the fluid's clock (the rigid step count rounds).
let t_release = fluid.time(); let t_release = fluid.time();
fluid.commit_base();
let (rigid_drag, rigid_lift) = fluid.measure_force(); let (rigid_drag, rigid_lift) = fluid.measure_force();
println!( println!(
" {} OVERSET rigid phase: {rigid_steps} steps (dt {dt_fluid:.3e}) to t = {:.2} s in {:.0} s wall; wall drag {rigid_drag:.2} (rigid-flag reference {:.1}; the overset's own CFD2 at ny = 41: 137.8), lift {rigid_lift:.2}; rounds mean {:.2}", " {} OVERSET rigid phase: {rigid_steps} steps (dt {dt_fluid:.3e}) to t = {:.2} s in {:.0} s wall; wall drag {rigid_drag:.2} (rigid-flag reference {:.1}; the overset's own CFD2 at ny = 41: 137.8), lift {rigid_lift:.2}; rounds mean {:.2}",
@@ -286,8 +287,7 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
} }
let (pd, bd, div) = fl.last_defects.get(); let (pd, bd, div) = fl.last_defects.get();
let (rough, flips, tn_max, tn_mean) = fl.wall_roughness(); let (rough, flips, tn_max, tn_mean) = fl.wall_roughness();
let (level, wall_flux, wall_area, poly_flux, poly_area) = let (level, wall_flux, wall_area, poly_flux, poly_area) = fl.level_and_wall_flux();
fl.level_and_wall_flux();
let area_rate = (poly_area - prev_area.get()) / dt; let area_rate = (poly_area - prev_area.get()) / dt;
println!( println!(
" step {step} pass: |d_new d_candidate| = {residual:.3e}, |d_new| = {:.3e}, nodal Σ|F| {load:.2} ΣF ({fx:+.2}, {fy:+.2}), power {power:+.3e} W/m, wall t_n roughness {rough:.2} ({flips} flips, max {tn_max:.1} mean {tn_mean:.1} Pa), patch p level {level:+.1} Pa, wall net flux {wall_flux:+.3e} m²/s over {wall_area:.3} m (polygon {poly_flux:+.3e}; flag area rate {area_rate:+.3e}), patch mass defect {pd:.2e} bg {bd:.2e} div {div:.1e}, {faces} faces", " step {step} pass: |d_new d_candidate| = {residual:.3e}, |d_new| = {:.3e}, nodal Σ|F| {load:.2} ΣF ({fx:+.2}, {fy:+.2}), power {power:+.3e} W/m, wall t_n roughness {rough:.2} ({flips} flips, max {tn_max:.1} mean {tn_mean:.1} Pa), patch p level {level:+.1} Pa, wall net flux {wall_flux:+.3e} m²/s over {wall_area:.3} m (polygon {poly_flux:+.3e}; flag area rate {area_rate:+.3e}), patch mass defect {pd:.2e} bg {bd:.2e} div {div:.1e}, {faces} faces",
@@ -363,6 +363,7 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
flag_state = new_state; flag_state = new_state;
committed_nodal = nodal; committed_nodal = nodal;
prev_area.set(fluid.borrow().shared.read().unwrap().area()); prev_area.set(fluid.borrow().shared.read().unwrap().area());
fluid.borrow_mut().commit_base();
worst_conservation = worst_conservation.max(conservation); worst_conservation = worst_conservation.max(conservation);
faces_used = faces; faces_used = faces;
let ux = flag_state.displacement[a_dofs[0]]; let ux = flag_state.displacement[a_dofs[0]];