embedded3 item 10b: virtual merging of small cells in the projection (fraction < 0.1 → master = largest active face neighbour; off-stencil links on the fine Poisson level; merged rhs, anchor, mass residual and source scale); static sphere rows within 0.02 %, loads 9.4/10.4 %; stadium falsifier spikes 39–54× below the binary wall (circle 6–8×), energy per event 6–9× lower
Performance Benchmarks / Run Benchmarks (push) Failing after 4s
CI / Clippy Check (push) Failing after 3s
CI / Build (ubuntu-latest) (push) Failing after 3s
CI / Format Check (push) Failing after 4s
Documentation / Build User Guide (push) Successful in 6s
Documentation / Build API Documentation (push) Failing after 19s
CI / Build CPU-Only (Explicit) (push) Failing after 1m21s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Distributed Training Tests (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 / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Failing after 4s
CI / Clippy Check (push) Failing after 3s
CI / Build (ubuntu-latest) (push) Failing after 3s
CI / Format Check (push) Failing after 4s
Documentation / Build User Guide (push) Successful in 6s
Documentation / Build API Documentation (push) Failing after 19s
CI / Build CPU-Only (Explicit) (push) Failing after 1m21s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Distributed Training Tests (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 / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
5b1621e6ad
commit
0fa05f2056
+118
-6
@@ -80,13 +80,98 @@ impl Solver {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.merge_small_cells(&mut problem);
|
||||
problem
|
||||
}
|
||||
|
||||
/// Virtual merging (item 10b): every small cell's row is folded into
|
||||
/// its master's — the small cell becomes inactive, its faces to other
|
||||
/// cells become links from the master (to the neighbour, or to that
|
||||
/// neighbour's master), its Dirichlet contribution moves to the
|
||||
/// master; the face between the two is internal to the merged cell.
|
||||
fn merge_small_cells(&self, problem: &mut Problem) {
|
||||
let Some(mask) = self.mask.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if mask.merged_cells() == 0 {
|
||||
return;
|
||||
}
|
||||
let g = mask.grid();
|
||||
let (nx, ny, nz) = (g.nx, g.ny, g.nz);
|
||||
let target = |idx: usize| mask.master(idx).unwrap_or(idx);
|
||||
for k in 0..nz {
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
let s = g.cell(k, j, i);
|
||||
let Some(m) = mask.master(s) else { continue };
|
||||
if !problem.active[s] {
|
||||
continue;
|
||||
}
|
||||
problem.active[s] = false;
|
||||
// (neighbour, coefficient on s, the mirror coefficient on the neighbour)
|
||||
let mut faces: Vec<(usize, f64)> = Vec::with_capacity(6);
|
||||
if i + 1 < nx {
|
||||
faces.push((s + 1, problem.ae[s]));
|
||||
problem.aw[s + 1] = 0.0;
|
||||
}
|
||||
if i > 0 {
|
||||
faces.push((s - 1, problem.aw[s]));
|
||||
problem.ae[s - 1] = 0.0;
|
||||
}
|
||||
if j + 1 < ny {
|
||||
faces.push((s + nx, problem.an[s]));
|
||||
problem.as_[s + nx] = 0.0;
|
||||
}
|
||||
if j > 0 {
|
||||
faces.push((s - nx, problem.as_[s]));
|
||||
problem.an[s - nx] = 0.0;
|
||||
}
|
||||
if let Some(t) = problem.top(s, k) {
|
||||
faces.push((t, problem.at[s]));
|
||||
problem.ab[t] = 0.0;
|
||||
}
|
||||
if let Some(b) = problem.bottom(s, k) {
|
||||
faces.push((b, problem.ab[s]));
|
||||
problem.at[b] = 0.0;
|
||||
}
|
||||
problem.ae[s] = 0.0;
|
||||
problem.aw[s] = 0.0;
|
||||
problem.an[s] = 0.0;
|
||||
problem.as_[s] = 0.0;
|
||||
problem.at[s] = 0.0;
|
||||
problem.ab[s] = 0.0;
|
||||
for (n, c) in faces {
|
||||
let t = target(n);
|
||||
if c > 0.0 && t != m {
|
||||
problem.links.push((m.min(t), m.max(t), c));
|
||||
}
|
||||
}
|
||||
problem.extra_diag[m] += problem.extra_diag[s];
|
||||
problem.extra_diag[s] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fold the small cells' right-hand sides into their masters'.
|
||||
fn merge_rhs(&self, problem: &mut Problem) {
|
||||
let Some(mask) = self.mask.as_ref() else {
|
||||
return;
|
||||
};
|
||||
for s in 0..problem.rhs.len() {
|
||||
if let Some(m) = mask.master(s) {
|
||||
let v = problem.rhs[s];
|
||||
problem.rhs[m] += v;
|
||||
problem.rhs[s] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The operator with `field.sp` as the right-hand side.
|
||||
pub(crate) fn poisson_problem(&self, field: &Field, dt: f64) -> Problem {
|
||||
let mut problem = self.poisson_operator(field.grid, dt);
|
||||
problem.rhs.copy_from_slice(&field.sp);
|
||||
self.merge_rhs(&mut problem);
|
||||
problem
|
||||
}
|
||||
|
||||
@@ -94,9 +179,10 @@ impl Solver {
|
||||
/// the 2D `(1, 1)` at `k = 0`), or `None` with an outlet.
|
||||
pub(crate) fn anchor_cell(&self, g: Grid) -> Option<usize> {
|
||||
(!self.params.boundaries.any_outlet()).then(|| {
|
||||
self.mask
|
||||
.as_ref()
|
||||
.map_or(g.cell(0, 1, 1), super::super::wall::Mask::anchor)
|
||||
self.mask.as_ref().map_or(g.cell(0, 1, 1), |m| {
|
||||
let a = m.anchor();
|
||||
m.master(a).unwrap_or(a)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -274,8 +360,20 @@ impl Solver {
|
||||
}
|
||||
}
|
||||
}
|
||||
let inner_stop = self.inner_stop(g, source_scale);
|
||||
let problem = self.poisson_problem(field, dt);
|
||||
// The source scale reads the merged right-hand side (a merged small
|
||||
// cell's own divergence is not zero, its pair's is); identical to
|
||||
// the per-cell sum when nothing is merged.
|
||||
if self.mask.as_ref().is_some_and(|m| m.merged_cells() > 0) {
|
||||
source_scale = problem
|
||||
.rhs
|
||||
.iter()
|
||||
.zip(&problem.active)
|
||||
.filter(|&(_, &a)| a)
|
||||
.map(|(r, _)| r.abs())
|
||||
.sum();
|
||||
}
|
||||
let inner_stop = self.inner_stop(g, source_scale);
|
||||
let mut p_prime = vec![0.0; g.cells()];
|
||||
if warm_start {
|
||||
for k in 0..nz {
|
||||
@@ -310,6 +408,13 @@ impl Solver {
|
||||
c0 + 1,
|
||||
k0 + solution.iterations as u64,
|
||||
);
|
||||
if let Some(mask) = self.mask.as_ref() {
|
||||
for s in 0..p_prime.len() {
|
||||
if let Some(m) = mask.master(s) {
|
||||
p_prime[s] = p_prime[m];
|
||||
}
|
||||
}
|
||||
}
|
||||
field.p_prime.copy_from_slice(&p_prime);
|
||||
solution
|
||||
}
|
||||
@@ -401,7 +506,7 @@ impl Solver {
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut mass_imbalance = 0.0;
|
||||
let mut cell_flux = vec![0.0; g.cells()];
|
||||
for k in 0..nz {
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
@@ -422,10 +527,17 @@ impl Solver {
|
||||
if cut {
|
||||
divergence_flux += rho * self.wall_flux(idx);
|
||||
}
|
||||
mass_imbalance += divergence_flux.abs();
|
||||
// A merged small cell's flux counts with its master's.
|
||||
let owner = self
|
||||
.mask
|
||||
.as_ref()
|
||||
.and_then(|m| m.master(idx))
|
||||
.unwrap_or(idx);
|
||||
cell_flux[owner] += divergence_flux;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mass_imbalance: f64 = cell_flux.iter().map(|f| f.abs()).sum();
|
||||
let reference_flux = self.reference_flux(g);
|
||||
if reference_flux > 0.0 {
|
||||
mass_imbalance / reference_flux
|
||||
|
||||
Reference in New Issue
Block a user