P5 audit: the overset march saves the composite at every N-th committed step (RTX_FSI2O_SAVE_EVERY: background, the deformed patch's nodes, its vectors, d, ddot, time), and fsi2_overset_audit_of_saved_instants (RTX_FSI2O_AUDIT=dir) rebuilds the composite around each saved patch (build_case_with an initial mesh, so the overlap is the instant's) and prints the solver-metric chain — the CFD23 instrument on the moving patch
CI / Build CPU-Only (Explicit) (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 (macos-latest) (push) Canceled after 0s
CI / CI Success (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 / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (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 / Build CPU-Only (Explicit) (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 (macos-latest) (push) Canceled after 0s
CI / CI Success (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 / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (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
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_0116sg1Qz1gMv9hdcKP1XUam
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
3a68048934
commit
3c2add3f32
@@ -152,6 +152,19 @@ impl OversetFluid {
|
||||
flag_nx: usize,
|
||||
sweeps: usize,
|
||||
max_rounds: usize,
|
||||
) -> CfdResult<Self> {
|
||||
Self::build_case_with(case, ny, flag_nx, sweeps, max_rounds, None)
|
||||
}
|
||||
|
||||
/// [`Self::build_case`] with an initial patch mesh given (a saved
|
||||
/// instant's deformed patch: the overlap is built around it).
|
||||
pub fn build_case_with(
|
||||
case: BenchmarkCase,
|
||||
ny: usize,
|
||||
flag_nx: usize,
|
||||
sweeps: usize,
|
||||
max_rounds: usize,
|
||||
initial: Option<rtx_cfd::mesh::PatchMesh>,
|
||||
) -> CfdResult<Self> {
|
||||
let h = H / ny as f64;
|
||||
let nx = (L / h).round() as usize;
|
||||
@@ -220,7 +233,9 @@ impl OversetFluid {
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(0);
|
||||
let patch_mesh = if warm_sweeps > 0 {
|
||||
let patch_mesh = if let Some(m) = initial {
|
||||
m
|
||||
} else 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),
|
||||
@@ -362,6 +377,176 @@ impl OversetFluid {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Save the composite at an instant of the coupled march for the
|
||||
/// offline chain audit: the background, the patch's node coordinates
|
||||
/// (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 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"))?;
|
||||
let mesh = self.solver.patch().mesh();
|
||||
let (ns, nn) = (mesh.ns(), mesh.nn());
|
||||
let mut nodes: Vec<f64> = Vec::with_capacity(2 * (ns + 1) * (nn + 1));
|
||||
for k in 0..=nn {
|
||||
for i in 0..=ns {
|
||||
let p = mesh.node_xy(mesh.node(k, i));
|
||||
nodes.push(p[0]);
|
||||
nodes.push(p[1]);
|
||||
}
|
||||
}
|
||||
for (name, vals) in [
|
||||
("nodes", &nodes),
|
||||
("u", &self.field.patch.u),
|
||||
("v", &self.field.patch.v),
|
||||
("p", &self.field.patch.p),
|
||||
("flux", &self.field.patch.flux),
|
||||
("d", &d.to_vec()),
|
||||
("dd", &ddot.to_vec()),
|
||||
] {
|
||||
let bytes: Vec<u8> = vals.iter().flat_map(|x| x.to_le_bytes()).collect();
|
||||
std::fs::write(dir.join(format!("patch_{name}.bin")), bytes).expect("save");
|
||||
}
|
||||
std::fs::write(
|
||||
dir.join("meta.txt"),
|
||||
format!("t {:.17e}\nns {ns}\nnn {nn}\n", self.solver.time()),
|
||||
)
|
||||
.expect("meta");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rebuild the composite on a saved instant (`save_instant`) — the
|
||||
/// deformed patch from its node coordinates, the fields, the wall
|
||||
/// motion — and return it with the interface and the time; the chain
|
||||
/// then runs on it as on any settled state.
|
||||
pub fn from_instant(
|
||||
case: BenchmarkCase,
|
||||
ny: usize,
|
||||
flag_nx: usize,
|
||||
dir: &std::path::Path,
|
||||
) -> CfdResult<(Self, Vec<f64>, f64)> {
|
||||
let read = |name: &str| -> Vec<f64> {
|
||||
let bytes = std::fs::read(dir.join(format!("patch_{name}.bin")))
|
||||
.unwrap_or_else(|e| panic!("instant {name}: {e}"));
|
||||
bytes
|
||||
.chunks_exact(8)
|
||||
.map(|c| f64::from_le_bytes(c.try_into().expect("8 bytes")))
|
||||
.collect()
|
||||
};
|
||||
let meta = std::fs::read_to_string(dir.join("meta.txt")).expect("meta");
|
||||
let mut t = 0.0;
|
||||
let (mut ns, mut nn) = (0usize, 0usize);
|
||||
for line in meta.lines() {
|
||||
let mut it = line.split_whitespace();
|
||||
match (it.next(), it.next()) {
|
||||
(Some("t"), Some(v)) => t = v.parse().expect("t"),
|
||||
(Some("ns"), Some(v)) => ns = v.parse().expect("ns"),
|
||||
(Some("nn"), Some(v)) => nn = v.parse().expect("nn"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let nodes = read("nodes");
|
||||
let (xs, ys): (Vec<f64>, Vec<f64>) = nodes.chunks_exact(2).map(|c| (c[0], c[1])).unzip();
|
||||
let mesh = rtx_cfd::mesh::PatchMesh::from_nodes(ns, nn, xs, ys, Some([0.0, 0.0]))?;
|
||||
// The composite around the deformed patch: built from scratch so
|
||||
// the overlap is the instant's.
|
||||
let mut fluid = Self::build_case_with(case, ny, flag_nx, 100, 3, Some(mesh))?;
|
||||
let d = read("d");
|
||||
let dd = read("dd");
|
||||
{
|
||||
let mut w = fluid.shared.write().unwrap();
|
||||
w.polygon = fluid
|
||||
.interface
|
||||
.polygon(&d)
|
||||
.iter()
|
||||
.map(|&(x, y)| [x, y])
|
||||
.collect();
|
||||
w.velocity = fluid
|
||||
.interface
|
||||
.walk_velocities(&dd)
|
||||
.iter()
|
||||
.map(|&(u, v)| [u, v])
|
||||
.collect();
|
||||
}
|
||||
fluid.field.background = FlowField::load(&dir.join("bg.bin"))?;
|
||||
fluid.field.patch.u = read("u");
|
||||
fluid.field.patch.v = read("v");
|
||||
fluid.field.patch.p = read("p");
|
||||
fluid.field.patch.flux = read("flux");
|
||||
fluid.solver.set_time(t);
|
||||
fluid.last_d = d.clone();
|
||||
Ok((fluid, d, t))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn chain_line(&self) -> String {
|
||||
let dt = self.dt_fluid;
|
||||
let h = self.h;
|
||||
let (nx, ny) = (self.nx, self.ny);
|
||||
let cv = (
|
||||
(0.10 / h).round() as usize,
|
||||
(0.75 / h).round() as usize,
|
||||
(0.05 / h).round() as usize,
|
||||
(0.36 / h).round() as usize,
|
||||
);
|
||||
let _ = (nx, ny);
|
||||
let wall = self.measure_force();
|
||||
let mr = self.solver.momentum_residual(&self.field, dt);
|
||||
let ring = mr.fringe_fringe.fx + mr.fringe_hole.fx;
|
||||
let bx = self.solver.solver_metric_force(&self.field, dt, cv).0;
|
||||
let bx2 = self
|
||||
.solver
|
||||
.solver_metric_force(
|
||||
&self.field,
|
||||
dt,
|
||||
(
|
||||
(0.09 / h).round() as usize,
|
||||
(0.70 / h).round() as usize,
|
||||
(0.07 / h).round() as usize,
|
||||
(0.34 / h).round() as usize,
|
||||
),
|
||||
)
|
||||
.0;
|
||||
let pb = self
|
||||
.solver
|
||||
.patch()
|
||||
.momentum_balance(&self.field.patch, self.solver.time());
|
||||
let ff = pb.flux_force()[0];
|
||||
let fw = pb.wall_force()[0];
|
||||
format!(
|
||||
"solved far Σ|r| ({:.2e}, {:.2e}) {}/{} | near ring Σr ({:+.2e}, {:+.2e}) | box (solver flux form) {:.3} [2 boxes spread {:.1e}] → ring Σr {:+.3} ({} + {} faces of {} + {}) → hole flux {:.3} → band {:+.3} → patch interface {:.3} → interior {:+.3} (δP {:+.3}, balance residual {:+.3}) → wall, scheme fluxes {:.3} → wall formula {:+.3} → wall ({:.3}, {:.3}); total wall − box {:+.3} ({:+.2} %); reclassified so far {}",
|
||||
mr.solved_far.abs_x,
|
||||
mr.solved_far.abs_y,
|
||||
mr.solved_far.evaluated,
|
||||
mr.solved_far.total,
|
||||
mr.solved_near.fx,
|
||||
mr.solved_near.fy,
|
||||
bx,
|
||||
(bx - bx2).abs(),
|
||||
ring,
|
||||
mr.fringe_fringe.evaluated,
|
||||
mr.fringe_hole.evaluated,
|
||||
mr.fringe_fringe.total,
|
||||
mr.fringe_hole.total,
|
||||
bx + ring,
|
||||
ff - (bx + ring),
|
||||
ff,
|
||||
fw - ff,
|
||||
pb.pressure_defect()[0],
|
||||
pb.balance()[0],
|
||||
fw,
|
||||
wall.0 - fw,
|
||||
wall.0,
|
||||
wall.1,
|
||||
wall.0 - bx,
|
||||
100.0 * (wall.0 - bx) / wall.0,
|
||||
self.reclassified_total.get()
|
||||
)
|
||||
}
|
||||
|
||||
/// Load a state saved by [`Self::save`] (same ny, undeformed patch);
|
||||
/// returns its time.
|
||||
pub fn load(&mut self, dir: &str) -> CfdResult<f64> {
|
||||
|
||||
@@ -235,6 +235,10 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
|
||||
});
|
||||
let (t_fluid, t_structure) = (std::cell::Cell::new(0.0_f64), std::cell::Cell::new(0.0_f64));
|
||||
let prev_area = std::cell::Cell::new(fluid.borrow().shared.read().unwrap().area());
|
||||
let save_every: usize = std::env::var("RTX_FSI2O_SAVE_EVERY")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(0);
|
||||
let phase_start = std::time::Instant::now();
|
||||
|
||||
for step in 0..coupled_steps {
|
||||
@@ -364,6 +368,18 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
|
||||
committed_nodal = nodal;
|
||||
prev_area.set(fluid.borrow().shared.read().unwrap().area());
|
||||
fluid.borrow_mut().commit_base();
|
||||
// `RTX_FSI2O_SAVE_EVERY=N` (+ `RTX_FSI2O_SAVE`): the composite at
|
||||
// every N-th committed step, for the offline chain audit.
|
||||
if save_every > 0 && (step + 1) % save_every == 0 {
|
||||
if let Ok(dir) = std::env::var("RTX_FSI2O_SAVE") {
|
||||
let d_now = extract(&flag_state);
|
||||
let dd_now = extract_velocity(&flag_state);
|
||||
fluid
|
||||
.borrow()
|
||||
.save_instant(&dir, step + 1, &d_now, &dd_now)
|
||||
.expect("save instant");
|
||||
}
|
||||
}
|
||||
worst_conservation = worst_conservation.max(conservation);
|
||||
faces_used = faces;
|
||||
let ux = flag_state.displacement[a_dofs[0]];
|
||||
|
||||
Reference in New Issue
Block a user