P5-1 instruments: the overset fluid saves/loads its rigid-phase state (RTX_FSI2O_SAVE / _LOAD — a coupling experiment then costs seconds, not the 12-minute rigid march), dumps the interface edges when the composite refuses a patch (RTX_FSI2O_DUMP_DIR, at the advance where the overlap is rebuilt), the release time is the fluid's clock; patch_cylinder_flag_deformed: cells inside the flag are holes at small bends and both sweep counts (the cantilever shape does not reproduce the P5-1 death — the CSVs show a coupling runaway), and a dumped-edges reproduction test
Documentation / Build User Guide (push) Canceled after 0s
Documentation / Build API Documentation (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 / 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
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 User Guide (push) Canceled after 0s
Documentation / Build API Documentation (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 / 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
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (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
d74571e21a
commit
2ed36c3a8c
@@ -94,6 +94,8 @@ pub struct OversetFluid {
|
||||
pub fresh_total: Cell<usize>,
|
||||
pub rounds_total: Cell<usize>,
|
||||
pub correctors_total: Cell<usize>,
|
||||
/// The interface of the last `set_geometry`.
|
||||
pub last_d: Vec<f64>,
|
||||
}
|
||||
|
||||
impl OversetFluid {
|
||||
@@ -236,9 +238,73 @@ impl OversetFluid {
|
||||
fresh_total: Cell::new(0),
|
||||
rounds_total: Cell::new(0),
|
||||
correctors_total: Cell::new(0),
|
||||
last_d: zero_d,
|
||||
})
|
||||
}
|
||||
|
||||
/// Save the fluid state (`RTX_FSI2O_SAVE=dir`, tag `fsi2o_ny{ny}`):
|
||||
/// the background as `FlowField::save`, the patch vectors raw, the
|
||||
/// time.
|
||||
pub fn save(&self, dir: &str) -> CfdResult<()> {
|
||||
let dir = std::path::Path::new(dir);
|
||||
std::fs::create_dir_all(dir).expect("save dir");
|
||||
let tag = format!("fsi2o_ny{}", self.ny);
|
||||
self.field
|
||||
.background
|
||||
.save(&dir.join(format!("bg_{tag}.bin")))?;
|
||||
for (name, vals) in [
|
||||
("u", &self.field.patch.u),
|
||||
("v", &self.field.patch.v),
|
||||
("p", &self.field.patch.p),
|
||||
("flux", &self.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");
|
||||
}
|
||||
std::fs::write(
|
||||
dir.join(format!("time_{tag}.txt")),
|
||||
format!("{:.17e}", self.solver.time()),
|
||||
)
|
||||
.expect("save time");
|
||||
println!(
|
||||
" saved the fluid state {tag} at t = {:.4} to {}",
|
||||
self.solver.time(),
|
||||
dir.display()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load a state saved by [`Self::save`] (same ny, undeformed patch);
|
||||
/// returns its time.
|
||||
pub fn load(&mut self, dir: &str) -> CfdResult<f64> {
|
||||
let dir = std::path::Path::new(dir);
|
||||
let tag = format!("fsi2o_ny{}", self.ny);
|
||||
self.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()
|
||||
};
|
||||
self.field.patch.u = read("u");
|
||||
self.field.patch.v = read("v");
|
||||
self.field.patch.p = read("p");
|
||||
self.field.patch.flux = read("flux");
|
||||
let t: f64 = std::fs::read_to_string(dir.join(format!("time_{tag}.txt")))
|
||||
.expect("time")
|
||||
.trim()
|
||||
.parse()
|
||||
.expect("time value");
|
||||
self.solver.set_time(t);
|
||||
println!(
|
||||
" loaded the fluid state {tag} at t = {t:.4} from {}",
|
||||
dir.display()
|
||||
);
|
||||
Ok(t)
|
||||
}
|
||||
|
||||
/// The patch around the interface `d`.
|
||||
pub fn patch_for(&self, d: &[f64]) -> CfdResult<rtx_cfd::mesh::PatchMesh> {
|
||||
let start = std::time::Instant::now();
|
||||
@@ -278,13 +344,51 @@ impl OversetFluid {
|
||||
.map(|&(u, v)| [u, v])
|
||||
.collect();
|
||||
}
|
||||
let mesh = self.patch_for(d)?;
|
||||
let mesh = match self.patch_for(d) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
self.dump_edges(d, &format!("generator refused ({e:?})"));
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
self.last_d = d.to_vec();
|
||||
self.solver.set_patch_mesh(mesh)
|
||||
}
|
||||
|
||||
/// The last geometry, for the offline reproduction
|
||||
/// (`patch_cylinder_flag_deformed.rs`, `RTX_CF_EDGES_FILE`): one edge
|
||||
/// per block, `x y` per line, when `RTX_FSI2O_DUMP_DIR` is set.
|
||||
fn dump_edges(&self, d: &[f64], why: &str) {
|
||||
let Ok(dir) = std::env::var("RTX_FSI2O_DUMP_DIR") else {
|
||||
return;
|
||||
};
|
||||
let e = self.interface.edges(d);
|
||||
let mut out = String::new();
|
||||
for (name, pts) in [("bottom", &e.bottom), ("tip", &e.tip), ("top", &e.top)] {
|
||||
out.push_str(&format!("# {name} {}\n", pts.len()));
|
||||
for p in pts {
|
||||
out.push_str(&format!("{:.17e} {:.17e}\n", p[0], p[1]));
|
||||
}
|
||||
}
|
||||
let path = std::path::Path::new(&dir).join("p5_death_edges.txt");
|
||||
std::fs::write(&path, out).expect("dump edges");
|
||||
println!(" {why}; edges dumped to {}", path.display());
|
||||
}
|
||||
|
||||
/// One fluid step at the current wall.
|
||||
pub fn step(&mut self) -> CfdResult<OversetResult> {
|
||||
let r = futures::executor::block_on(self.solver.advance(&mut self.field, self.dt_fluid))?;
|
||||
let r = match futures::executor::block_on(
|
||||
self.solver.advance(&mut self.field, self.dt_fluid),
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
// The overlap is rebuilt inside `advance`: a refused patch
|
||||
// surfaces here, with the last geometry.
|
||||
let d = self.last_d.clone();
|
||||
self.dump_edges(&d, &format!("advance refused ({e:?})"));
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
self.reclassified_total
|
||||
.set(self.reclassified_total.get() + r.reclassified_cells);
|
||||
self.fresh_total.set(self.fresh_total.get() + r.fresh_cells);
|
||||
|
||||
@@ -98,12 +98,27 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
|
||||
let dt = dt_fluid * cfg.subcycle as f64;
|
||||
let zero_d = vec![0.0; 2 * fluid.interface.wetted.len()];
|
||||
|
||||
// Phase 1: rigid flag to t_release.
|
||||
// Phase 1: rigid flag to t_release (`RTX_FSI2O_LOAD=dir` replaces the
|
||||
// march with the saved state; `RTX_FSI2O_SAVE=dir` saves it).
|
||||
let start = std::time::Instant::now();
|
||||
let rigid_steps = (cfg.t_release / dt_fluid).round() as usize;
|
||||
for _ in 0..rigid_steps {
|
||||
fluid.step().expect("rigid fluid step");
|
||||
if let Ok(dir) = std::env::var("RTX_FSI2O_LOAD") {
|
||||
let t = fluid.load(&dir).expect("load");
|
||||
assert!(
|
||||
(t - cfg.t_release).abs() < dt_fluid,
|
||||
"the saved state is at t = {t}, not t_release = {}",
|
||||
cfg.t_release
|
||||
);
|
||||
} else {
|
||||
for _ in 0..rigid_steps {
|
||||
fluid.step().expect("rigid fluid step");
|
||||
}
|
||||
if let Ok(dir) = std::env::var("RTX_FSI2O_SAVE") {
|
||||
fluid.save(&dir).expect("save");
|
||||
}
|
||||
}
|
||||
// The release time is the fluid's clock (the rigid step count rounds).
|
||||
let t_release = fluid.time();
|
||||
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}",
|
||||
@@ -280,7 +295,7 @@ pub fn run_march_overset(case: BenchmarkCase, config: &OversetMarchConfig) -> Ov
|
||||
outcome = iqn_ref.solve(&d_predicted, pass);
|
||||
}
|
||||
}
|
||||
let t_now = cfg.t_release + (step + 1) as f64 * dt;
|
||||
let t_now = t_release + (step + 1) as f64 * dt;
|
||||
match outcome {
|
||||
Ok(c) => {
|
||||
total_subiterations += c.iterations;
|
||||
|
||||
Reference in New Issue
Block a user