rtx-cfd: CurvilinearPisoSolver::momentum_balance — the patch's own momentum balance on its solved cells in the scheme's fluxes (outward ρFu_f with the predictor's face value, Laplacian-form μ∇u·S on the solved/acceptor interface and the wall, the least-squares pressure volume sum vs the face-pressure integrals); flux_force, wall_force, pressure_defect δP; overset_cfd1 prints it and the acceptor band's mismatch, saves the patch flux, and RTX_OVERSET_CFD1_LOAD=dir runs the diagnostics offline on saved fields
Performance Benchmarks / Run Benchmarks (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
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (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 / 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
Performance Benchmarks / Run Benchmarks (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
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (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 / 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
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01X2GmJXeQ2njUecEKiJZ1G2
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
e31576d543
commit
0215c7d6a5
@@ -177,6 +177,40 @@ async fn run_cfd1(ny: usize, max_steps: usize) -> CfdResult<Cfd1> {
|
||||
)
|
||||
};
|
||||
|
||||
// `RTX_OVERSET_CFD1_LOAD=dir`: settled fields saved by a previous run
|
||||
// (`RTX_OVERSET_CFD1_SAVE`) replace the march — the diagnostics below
|
||||
// run offline in seconds instead of the 20–50 min settle.
|
||||
let loaded = match std::env::var("RTX_OVERSET_CFD1_LOAD") {
|
||||
Ok(dir) => {
|
||||
let tag = format!(
|
||||
"ny{ny}_{}",
|
||||
if std::env::var("RTX_OVERSET_CFD1_TVD").is_ok() {
|
||||
"tvd"
|
||||
} else {
|
||||
"upwind"
|
||||
}
|
||||
);
|
||||
let dir = std::path::Path::new(&dir);
|
||||
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")))
|
||||
.unwrap_or_else(|e| panic!("load patch {name}: {e}"));
|
||||
bytes
|
||||
.chunks_exact(8)
|
||||
.map(|c| f64::from_le_bytes(c.try_into().expect("8 bytes")))
|
||||
.collect()
|
||||
};
|
||||
field.patch.u = read("u");
|
||||
field.patch.v = read("v");
|
||||
field.patch.p = read("p");
|
||||
field.patch.flux = read("flux");
|
||||
assert_eq!(field.patch.u.len(), solver.patch().mesh().cell_count());
|
||||
assert_eq!(field.patch.flux.len(), solver.patch().mesh().faces().len());
|
||||
println!(" loaded settled fields {tag} from {}", dir.display());
|
||||
true
|
||||
}
|
||||
Err(_) => false,
|
||||
};
|
||||
let start = std::time::Instant::now();
|
||||
let flow_through = L / U_MEAN;
|
||||
let min_steps = (flow_through / dt).ceil() as usize;
|
||||
@@ -185,22 +219,23 @@ async fn run_cfd1(ny: usize, max_steps: usize) -> CfdResult<Cfd1> {
|
||||
let mut rounds_total = 0usize;
|
||||
let mut correctors_total = 0usize;
|
||||
let trace_first = std::env::var("RTX_OVERSET_CFD1_TRACE").is_ok();
|
||||
loop {
|
||||
let r = solver.advance(&mut field, dt).await?;
|
||||
steps += 1;
|
||||
let every: usize = std::env::var("RTX_OVERSET_CFD1_TRACE_EVERY")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(0);
|
||||
if (trace_first && steps <= 6) || (every > 0 && steps % every == 0) {
|
||||
let pmax = field
|
||||
.background
|
||||
.p
|
||||
.iter()
|
||||
.fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||
let ppmax = field.patch.p.iter().fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||
let upmax = field.patch.u.iter().fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||
println!(
|
||||
if !loaded {
|
||||
loop {
|
||||
let r = solver.advance(&mut field, dt).await?;
|
||||
steps += 1;
|
||||
let every: usize = std::env::var("RTX_OVERSET_CFD1_TRACE_EVERY")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(0);
|
||||
if (trace_first && steps <= 6) || (every > 0 && steps % every == 0) {
|
||||
let pmax = field
|
||||
.background
|
||||
.p
|
||||
.iter()
|
||||
.fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||
let ppmax = field.patch.p.iter().fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||
let upmax = field.patch.u.iter().fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||
println!(
|
||||
" step {steps}: rounds {:?} converged {} stalled {} bg res {:.2e} patch div {:.2e} patch iters {} conv {} | max|p| bg {pmax:.3e} patch {ppmax:.3e} max|u| patch {upmax:.3e} | defect bg {:.2e} patch {:.2e}",
|
||||
r.rounds,
|
||||
r.schwarz_converged,
|
||||
@@ -212,25 +247,26 @@ async fn run_cfd1(ny: usize, max_steps: usize) -> CfdResult<Cfd1> {
|
||||
r.background_mass_defect,
|
||||
r.patch_mass_defect
|
||||
);
|
||||
}
|
||||
if steps >= max_steps {
|
||||
break;
|
||||
}
|
||||
rounds_total += r.rounds.iter().sum::<usize>();
|
||||
correctors_total += r.rounds.len();
|
||||
if steps % 50 == 0 {
|
||||
let (fx, _) = cv_force(&field, &solver);
|
||||
history.push(fx);
|
||||
let load = solver
|
||||
.patch()
|
||||
.surface_force(&field.patch, PatchSide::Inner, solver.time());
|
||||
let umax = field
|
||||
.background
|
||||
.u
|
||||
.iter()
|
||||
.fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||
if steps % 500 == 0 || !umax.is_finite() {
|
||||
println!(
|
||||
}
|
||||
if steps >= max_steps {
|
||||
break;
|
||||
}
|
||||
rounds_total += r.rounds.iter().sum::<usize>();
|
||||
correctors_total += r.rounds.len();
|
||||
if steps % 50 == 0 {
|
||||
let (fx, _) = cv_force(&field, &solver);
|
||||
history.push(fx);
|
||||
let load =
|
||||
solver
|
||||
.patch()
|
||||
.surface_force(&field.patch, PatchSide::Inner, solver.time());
|
||||
let umax = field
|
||||
.background
|
||||
.u
|
||||
.iter()
|
||||
.fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||
if steps % 500 == 0 || !umax.is_finite() {
|
||||
println!(
|
||||
" ny = {ny}: step {steps} t = {:.2} s drag_cv {fx:.4} drag_wall {:.4} lift_wall {:.4} max|u| {umax:.3} rounds {:?} bg res {:.1e} patch div {:.1e} [{:.0} s]",
|
||||
solver.time(),
|
||||
load.total()[0],
|
||||
@@ -240,20 +276,21 @@ async fn run_cfd1(ny: usize, max_steps: usize) -> CfdResult<Cfd1> {
|
||||
r.patch_max_divergence,
|
||||
start.elapsed().as_secs_f64()
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
umax.is_finite(),
|
||||
"velocity became non-finite at step {steps}"
|
||||
);
|
||||
if steps >= min_steps && history.len() > 4 {
|
||||
let now = history[history.len() - 1];
|
||||
let then = history[history.len() - 5];
|
||||
if ((now - then) / now).abs() < 1e-4 {
|
||||
break;
|
||||
}
|
||||
assert!(
|
||||
umax.is_finite(),
|
||||
"velocity became non-finite at step {steps}"
|
||||
);
|
||||
if steps >= min_steps && history.len() > 4 {
|
||||
let now = history[history.len() - 1];
|
||||
let then = history[history.len() - 5];
|
||||
if ((now - then) / now).abs() < 1e-4 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(steps < 2_000_000, "CFD1 at ny = {ny} did not settle");
|
||||
}
|
||||
assert!(steps < 2_000_000, "CFD1 at ny = {ny} did not settle");
|
||||
}
|
||||
let seconds = start.elapsed().as_secs_f64();
|
||||
let load = solver
|
||||
@@ -373,6 +410,29 @@ async fn run_cfd1(ny: usize, max_steps: usize) -> CfdResult<Cfd1> {
|
||||
wall[0] - sf.0 - ring_sum,
|
||||
100.0 * (wall[0] - sf.0 - ring_sum) / wall[0],
|
||||
);
|
||||
// The patch's own momentum balance on its solved cells (scheme
|
||||
// fluxes; the unsteady term is omitted — settled state): the balance
|
||||
// residual is the gate; flux_force − wall_force = the least-squares
|
||||
// pressure's non-conservation δP; the acceptor band's mismatch is
|
||||
// then (box + ring Σr) − flux_force, all in N/m.
|
||||
let pb = solver.patch().momentum_balance(&field.patch, solver.time());
|
||||
let ff = pb.flux_force();
|
||||
let fw = pb.wall_force();
|
||||
let dp = pb.pressure_defect();
|
||||
let bal = pb.balance();
|
||||
let hole_flux = sf.0 + ring_sum;
|
||||
println!(
|
||||
" patch momentum balance ny = {ny} [N/m x / y; {} solved cells, {} interface faces, {} wall faces]: balance residual ({:+.3e}, {:+.3e}) | flux-form force through the interface ({:.4}, {:.4}) | wall force, scheme fluxes ({:.4}, {:.4}) | wall force, surface formula ({:.4}, {:.4}) | pressure defect δP = p_ls − p_face ({:+.4}, {:+.4}) [{:+.2}% of wall drag] | pieces: conv_acc ({:+.4}, {:+.4}) visc_acc ({:+.4}, {:+.4}) p_face_acc ({:+.4}, {:+.4}) visc_wall ({:+.4}, {:+.4}) p_face_wall ({:+.4}, {:+.4}) p_ls ({:+.4}, {:+.4}) | acceptor band: background hole flux {:.4} − patch interface {:.4} = {:+.4} ({:+.2}%)",
|
||||
pb.cells, pb.acc_faces, pb.wall_faces,
|
||||
bal[0], bal[1],
|
||||
ff[0], ff[1],
|
||||
fw[0], fw[1],
|
||||
wall[0], wall[1],
|
||||
dp[0], dp[1], 100.0 * dp[0] / wall[0],
|
||||
pb.conv_acc[0], pb.conv_acc[1], pb.visc_acc[0], pb.visc_acc[1], pb.p_face_acc[0], pb.p_face_acc[1],
|
||||
pb.visc_wall[0], pb.visc_wall[1], pb.p_face_wall[0], pb.p_face_wall[1], pb.p_ls[0], pb.p_ls[1],
|
||||
hole_flux, ff[0], hole_flux - ff[0], 100.0 * (hole_flux - ff[0]) / wall[0],
|
||||
);
|
||||
// `RTX_OVERSET_CFD1_SAVE=dir`: the settled fields, for offline
|
||||
// diagnostics without the march (background in `FlowField::save`'s
|
||||
// format; patch u, v, p as raw little-endian f64 vectors).
|
||||
@@ -392,6 +452,7 @@ async fn run_cfd1(ny: usize, max_steps: usize) -> CfdResult<Cfd1> {
|
||||
("u", &field.patch.u),
|
||||
("v", &field.patch.v),
|
||||
("p", &field.patch.p),
|
||||
("flux", &field.patch.flux),
|
||||
] {
|
||||
let bytes: Vec<u8> = vals.iter().flat_map(|x| x.to_le_bytes()).collect();
|
||||
std::fs::write(dir.join(format!("patch_{tag}_{name}.bin")), bytes).expect("save patch");
|
||||
|
||||
Reference in New Issue
Block a user