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
@@ -135,6 +135,10 @@ pub struct OversetFluid {
pub correctors_total: Cell<usize>,
/// The interface of the last `set_geometry`.
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
/// fringe) and the patch's max divergence.
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_R,
FLAG_T,
@@ -209,6 +213,37 @@ impl OversetFluid {
PATCH_STRETCH,
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
// line-implicit) beside the background's combined criterion.
let mut hs = f64::INFINITY;
@@ -290,6 +325,8 @@ impl OversetFluid {
correctors_total: Cell::new(0),
last_d: zero_d,
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`.
pub fn patch_for(&self, d: &[f64]) -> CfdResult<rtx_cfd::mesh::PatchMesh> {
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_R,
FLAG_T,
@@ -370,7 +412,11 @@ impl OversetFluid {
6.0 * self.h,
PATCH_ROWS,
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_seconds
@@ -576,15 +622,14 @@ impl OversetFluid {
/// change at every face.
pub fn wall_roughness(&self) -> (f64, usize, f64, f64) {
let mut tn: Vec<f64> = Vec::new();
for (centre, normal, _, traction) in
self.solver
.patch()
.wall_tractions(&self.field.patch, PatchSide::Inner, self.solver.time())
{
let on_cylinder = ((centre[0] - CYL_CENTRE[0]).powi(2)
+ (centre[1] - CYL_CENTRE[1]).powi(2))
.sqrt()
< CYL_R + 1e-9;
for (centre, normal, _, traction) in self.solver.patch().wall_tractions(
&self.field.patch,
PatchSide::Inner,
self.solver.time(),
) {
let on_cylinder =
((centre[0] - CYL_CENTRE[0]).powi(2) + (centre[1] - CYL_CENTRE[1]).powi(2)).sqrt()
< CYL_R + 1e-9;
if !on_cylinder {
tn.push(traction[0] * normal[0] + traction[1] * normal[1]);
}
@@ -602,7 +647,12 @@ impl OversetFluid {
prev_diff = d;
}
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
@@ -634,6 +684,14 @@ impl OversetFluid {
(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) {
(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).
let t_release = fluid.time();
fluid.commit_base();
let (rigid_drag, rigid_lift) = fluid.measure_force();
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}",
@@ -286,8 +287,7 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
}
let (pd, bd, div) = fl.last_defects.get();
let (rough, flips, tn_max, tn_mean) = fl.wall_roughness();
let (level, wall_flux, wall_area, poly_flux, poly_area) =
fl.level_and_wall_flux();
let (level, wall_flux, wall_area, poly_flux, poly_area) = fl.level_and_wall_flux();
let area_rate = (poly_area - prev_area.get()) / dt;
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",
@@ -363,6 +363,7 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
flag_state = new_state;
committed_nodal = nodal;
prev_area.set(fluid.borrow().shared.read().unwrap().area());
fluid.borrow_mut().commit_base();
worst_conservation = worst_conservation.max(conservation);
faces_used = faces;
let ux = flag_state.displacement[a_dofs[0]];