Merge r8a-span-uniform-fsi (R8-a: the first coupled 3D FSI, span-uniform; default-off). Merge fixes: the load-route observers of R8-a (to_load_sink) and R8-c (sink(LoadKind, …)) both kept on the same summands (both inert by default); the harness's DeviceSdf gains plate: None (R8-c's new field). Merged-tree gate: slab ny 62 default / BAND_CHECK / all-host / uniform plate byte-identical to main's base; device suites 2/2, 3/3; the coupled harness reproduces R8-a's recorded slab march row for row (478 rows)
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Build (macos-latest) (push) Waiting to run
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / Clippy Check (push) Failing after 5s
CI / Build CPU-Only (Explicit) (push) Failing after 4s
CI / Format Check (push) Failing after 5s
CI / Build (ubuntu-latest) (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 6s
Performance Benchmarks / Run Benchmarks (push) Successful in 15s
Documentation / Build API Documentation (push) Failing after 17s
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Build (macos-latest) (push) Waiting to run
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / Clippy Check (push) Failing after 5s
CI / Build CPU-Only (Explicit) (push) Failing after 4s
CI / Format Check (push) Failing after 5s
CI / Build (ubuntu-latest) (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 6s
Performance Benchmarks / Run Benchmarks (push) Successful in 15s
Documentation / Build API Documentation (push) Failing after 17s
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -942,6 +942,16 @@ impl Mask {
|
||||
for c in 0..3 {
|
||||
v[c] = f.p[idx] * w[c];
|
||||
pressure[c] += v[c];
|
||||
super::exchange::to_load_sink(
|
||||
[
|
||||
(i as f64 + 0.5) * g.dx,
|
||||
(j as f64 + 0.5) * g.dy,
|
||||
(k as f64 + 0.5) * g.dz,
|
||||
],
|
||||
c,
|
||||
0,
|
||||
v[c],
|
||||
);
|
||||
}
|
||||
let x = [
|
||||
(i as f64 + 0.5) * g.dx,
|
||||
@@ -1000,6 +1010,7 @@ impl Mask {
|
||||
let mut fv = [0.0; 3];
|
||||
fv[c] = v;
|
||||
sink(LoadKind::Shear, x, fv);
|
||||
super::exchange::to_load_sink(x, c, 1, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,36 @@ pub(super) fn in_load_window(x: f64) -> bool {
|
||||
.is_none_or(|(x0, x1)| x >= x0 && x < x1)
|
||||
}
|
||||
|
||||
/// R8-a: a sink for the operator load route's CONTRIBUTIONS — each term
|
||||
/// the route sums (part 0 the cell's `p W`, 1 the face's wall shear, 2 and
|
||||
/// 3 the face's diffusive and convective exchange) is handed to it as
|
||||
/// (position, component, part, force on the body) while the totals are
|
||||
/// summed as before.
|
||||
/// A coupled harness distributes them onto its structure. Process-wide;
|
||||
/// `None` (the default) hands nothing and the routes' sums are unchanged.
|
||||
/// The gradient-weight term (the host prototype `pressure_centroid`) is
|
||||
/// not handed.
|
||||
pub type LoadSink = Box<dyn FnMut([f64; 3], usize, usize, f64) + Send>;
|
||||
|
||||
static LOAD_SINK: std::sync::Mutex<Option<LoadSink>> = std::sync::Mutex::new(None);
|
||||
static LOAD_SINK_ON: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
/// Install (or clear, with `None`) the load sink; returns the previous one.
|
||||
pub fn set_load_sink(sink: Option<LoadSink>) -> Option<LoadSink> {
|
||||
let mut guard = LOAD_SINK.lock().expect("load sink");
|
||||
LOAD_SINK_ON.store(sink.is_some(), std::sync::atomic::Ordering::SeqCst);
|
||||
std::mem::replace(&mut *guard, sink)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn to_load_sink(pos: [f64; 3], c: usize, part: usize, value: f64) {
|
||||
if LOAD_SINK_ON.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
if let Some(f) = LOAD_SINK.lock().expect("load sink").as_mut() {
|
||||
f(pos, c, part, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mask {
|
||||
/// The cut-cell load route: the force on the body from the operators
|
||||
/// themselves — `Σ_c p_c W_c` over the cells plus the implicit wall
|
||||
@@ -223,10 +253,12 @@ impl Mask {
|
||||
let v = -rho * m_plus * (u_face - u0);
|
||||
convective[c] -= v;
|
||||
sink(LoadKind::ExchangeConvective, xf, comp(c, -v));
|
||||
to_load_sink(xf, c, 3, -v);
|
||||
}
|
||||
let v = mu * cv.ap[d][1] * a_d * (un - u0) / solid_spacing(1.0);
|
||||
force[c] -= v;
|
||||
sink(LoadKind::ExchangeDiffusive, xf, comp(c, -v));
|
||||
to_load_sink(xf, c, 2, -v);
|
||||
}
|
||||
}
|
||||
// Minus side.
|
||||
@@ -245,11 +277,13 @@ impl Mask {
|
||||
let v = rho * m_minus * (u_face - u0);
|
||||
convective[c] -= v;
|
||||
sink(LoadKind::ExchangeConvective, xf, comp(c, -v));
|
||||
to_load_sink(xf, c, 3, -v);
|
||||
}
|
||||
let v =
|
||||
mu * cv.ap[d][0] * a_d * (ud - u0) / solid_spacing(-1.0);
|
||||
force[c] -= v;
|
||||
sink(LoadKind::ExchangeDiffusive, xf, comp(c, -v));
|
||||
to_load_sink(xf, c, 2, -v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ mod cut;
|
||||
mod geom;
|
||||
mod mask;
|
||||
mod poisson_setup;
|
||||
mod snapshot;
|
||||
|
||||
pub use snapshot::DeviceSnapshot;
|
||||
|
||||
use super::{Side, Solver, StepResult};
|
||||
use crate::solvers::incompressible::embedded3::field::Field;
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
//! R8-a: the step's snapshot and restore for a partitioned FSI loop — the
|
||||
//! coupled harness re-runs the same fluid step once per subiteration with
|
||||
//! a new candidate body position, so the device fields and the moving
|
||||
//! body's host state at the start of the step must come back exactly.
|
||||
//!
|
||||
//! Nothing here runs unless a caller asks for it: every existing path is
|
||||
//! untouched (byte-identical by construction).
|
||||
//!
|
||||
//! What a restore rebuilds instead of copying (the persistent R6 state
|
||||
//! whose band history would otherwise describe the rejected pass): the
|
||||
//! device geometry and classification (`geom`, `dmask`; the next step
|
||||
//! re-syncs them from the restored mask, as the first moving step of a run
|
||||
//! does), the predictor tables (`DeviceCut::build` at the restored time,
|
||||
//! the identity reference of `predictor_from`), the multigrid hierarchy
|
||||
//! and the projected-guess basis. The restored step is the same step up to
|
||||
//! the CG's initial guess (the Poisson solves' own tolerance).
|
||||
|
||||
use super::DeviceStep;
|
||||
use super::cut::{DeviceCut, Phase};
|
||||
use crate::solvers::incompressible::embedded3::poisson::device::runtime;
|
||||
use crate::solvers::incompressible::embedded3::wall::Mask;
|
||||
use cudarc::driver::CudaSlice;
|
||||
|
||||
/// The start-of-step state of a [`DeviceStep`] (device copies of the
|
||||
/// fields plus the moving body's host records).
|
||||
pub struct DeviceSnapshot {
|
||||
time: f64,
|
||||
fields: Vec<CudaSlice<f64>>,
|
||||
mask: Option<Mask>,
|
||||
vol_old: Vec<f64>,
|
||||
apertures_old: Option<[Vec<f64>; 3]>,
|
||||
wall_fluxes: Vec<f64>,
|
||||
last_ghost_correction: f64,
|
||||
mask_gen: u64,
|
||||
}
|
||||
|
||||
impl DeviceSnapshot {
|
||||
/// The solver time the snapshot was taken at.
|
||||
#[must_use]
|
||||
pub fn time(&self) -> f64 {
|
||||
self.time
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceStep {
|
||||
fn field_slots(&mut self) -> [&mut CudaSlice<f64>; 8] {
|
||||
[
|
||||
&mut self.u,
|
||||
&mut self.v,
|
||||
&mut self.w,
|
||||
&mut self.p,
|
||||
&mut self.p_prime,
|
||||
&mut self.u_old,
|
||||
&mut self.v_old,
|
||||
&mut self.w_old,
|
||||
]
|
||||
}
|
||||
|
||||
/// R8-a: the start-of-step state (device fields copied on the device,
|
||||
/// the host mask cloned). Take it between steps.
|
||||
pub fn snapshot(&mut self) -> DeviceSnapshot {
|
||||
let rt = runtime();
|
||||
let mut fields = Vec::with_capacity(8);
|
||||
for src in self.field_slots() {
|
||||
let mut dst = rt.stream.alloc_zeros::<f64>(src.len()).expect("alloc");
|
||||
rt.stream.memcpy_dtod(&*src, &mut dst).expect("snapshot");
|
||||
fields.push(dst);
|
||||
}
|
||||
rt.stream.synchronize().expect("sync");
|
||||
let s = &self.solver;
|
||||
let mask = s.mask.clone().map(|mut m| {
|
||||
// A clone is a host generation: its arrays must not return to
|
||||
// the device geometry's recycling pool under a live generation.
|
||||
if let Some(c) = m.cut.as_mut() {
|
||||
c.generation = 0;
|
||||
}
|
||||
m
|
||||
});
|
||||
DeviceSnapshot {
|
||||
time: s.time,
|
||||
fields,
|
||||
mask,
|
||||
vol_old: s.vol_old.clone(),
|
||||
apertures_old: s.apertures_old.clone(),
|
||||
wall_fluxes: s.wall_fluxes.clone(),
|
||||
last_ghost_correction: s.last_ghost_correction,
|
||||
mask_gen: s.mask_gen,
|
||||
}
|
||||
}
|
||||
|
||||
/// R8-a: back to `snap` (taken on this stepper). The body's functions
|
||||
/// must answer for the snapshot's time as they did when it was taken
|
||||
/// (the predictor tables are rebuilt from them).
|
||||
pub fn restore(&mut self, snap: &DeviceSnapshot) {
|
||||
let rt = runtime();
|
||||
for (dst, src) in self.field_slots().into_iter().zip(&snap.fields) {
|
||||
rt.stream.memcpy_dtod(src, dst).expect("restore");
|
||||
}
|
||||
rt.stream.synchronize().expect("sync");
|
||||
{
|
||||
let s = &mut self.solver;
|
||||
s.time = snap.time;
|
||||
s.mask = snap.mask.clone();
|
||||
s.vol_old = snap.vol_old.clone();
|
||||
s.apertures_old = snap.apertures_old.clone();
|
||||
s.apertures_old_gen = 0;
|
||||
s.wall_fluxes = snap.wall_fluxes.clone();
|
||||
s.last_ghost_correction = snap.last_ghost_correction;
|
||||
s.mask_gen = snap.mask_gen;
|
||||
s.pending_cut = None;
|
||||
s.pending_mask = None;
|
||||
s.mask_pool = None;
|
||||
s.geom_pool = Default::default();
|
||||
}
|
||||
self.geom = None;
|
||||
self.dmask = None;
|
||||
self.cg = None;
|
||||
self.steps_since_hierarchy = 0;
|
||||
self.guess.clear();
|
||||
if self.cut.is_some() {
|
||||
self.cut = DeviceCut::build(&self.solver, self.grid, Phase::Predictor, snap.time);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user