PERF-2: red-black symmetric Gauss–Seidel smoother behind a knob (MgSmoother::RedBlack; default lexicographic = the recorded regime, bit-identical) — red/black cell lists per level, the symmetric pair red,black,black,red, the operator cache keyed on the smoother; EmbeddedParameters::poisson_smoother, harness knobs RTX_FSI2O_MG_RB (overset) and RTX_FSI2_MG_RB (embedded, the noise probe); pin: solves the masked problem to the same stop, agrees with lexicographic to 2e-12, cached = uncached bit for bit
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-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 / Build CPU-Only (Explicit) (push) Failing after 5s
Documentation / Build API Documentation (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 5s
CI / Format Check (push) Failing after 16s
CI / Build (ubuntu-latest) (push) Failing after 2m18s
CI / Clippy Check (push) Failing after 2m38s
Performance Benchmarks / Run Benchmarks (push) Successful in 4m13s
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (ubuntu-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 / Build CPU-Only (Explicit) (push) Failing after 5s
Documentation / Build API Documentation (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 5s
CI / Format Check (push) Failing after 16s
CI / Build (ubuntu-latest) (push) Failing after 2m18s
CI / Clippy Check (push) Failing after 2m38s
Performance Benchmarks / Run Benchmarks (push) Successful in 4m13s
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01YJPeT6WA2e7YvAnS875AHL
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
26904e37d8
commit
cb5771fa71
@@ -71,6 +71,9 @@ pub struct EmbeddedParameters {
|
|||||||
/// and the stop stay f64; only the preconditioner runs in single
|
/// and the stop stay f64; only the preconditioner runs in single
|
||||||
/// precision. No effect with [`PoissonSolverKind::Sor`].
|
/// precision. No effect with [`PoissonSolverKind::Sor`].
|
||||||
pub poisson_precision: MgPrecision,
|
pub poisson_precision: MgPrecision,
|
||||||
|
/// The multigrid smoother ordering (PERF-2; default lexicographic, the
|
||||||
|
/// recorded regime).
|
||||||
|
pub poisson_smoother: super::poisson::MgSmoother,
|
||||||
/// Convective face values in the explicit predictor (default
|
/// Convective face values in the explicit predictor (default
|
||||||
/// [`ConvectionScheme::Upwind`], which is bit-identical to the fixed-grid
|
/// [`ConvectionScheme::Upwind`], which is bit-identical to the fixed-grid
|
||||||
/// PISO). The TVD schemes add SIMPLE's limited correction to each
|
/// PISO). The TVD schemes add SIMPLE's limited correction to each
|
||||||
@@ -93,6 +96,7 @@ impl Default for EmbeddedParameters {
|
|||||||
boundaries: AleBoundaries::default(),
|
boundaries: AleBoundaries::default(),
|
||||||
poisson_solver: PoissonSolverKind::Sor,
|
poisson_solver: PoissonSolverKind::Sor,
|
||||||
poisson_precision: MgPrecision::F64,
|
poisson_precision: MgPrecision::F64,
|
||||||
|
poisson_smoother: super::poisson::MgSmoother::Lexicographic,
|
||||||
convection_scheme: ConvectionScheme::Upwind,
|
convection_scheme: ConvectionScheme::Upwind,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,6 +219,12 @@ impl EmbeddedPisoSolver {
|
|||||||
self.parameters.poisson_precision = precision;
|
self.parameters.poisson_precision = precision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The multigrid smoother ordering (PERF-2's red-black knob); the
|
||||||
|
/// cached operator is rebuilt on the next solve.
|
||||||
|
pub fn set_poisson_smoother(&mut self, smoother: super::poisson::MgSmoother) {
|
||||||
|
self.parameters.poisson_smoother = smoother;
|
||||||
|
}
|
||||||
|
|
||||||
/// Mask hysteresis for the moving-body rebuild, as a fraction of the
|
/// Mask hysteresis for the moving-body rebuild, as a fraction of the
|
||||||
/// min cell size (default 0, exactly the plain rebuild). With a band,
|
/// min cell size (default 0, exactly the plain rebuild). With a band,
|
||||||
/// a cell within `band * h_min` of the surface keeps the
|
/// a cell within `band * h_min` of the surface keeps the
|
||||||
|
|||||||
@@ -299,6 +299,7 @@ impl EmbeddedPisoSolver {
|
|||||||
&mut p_prime,
|
&mut p_prime,
|
||||||
&MultigridParameters {
|
&MultigridParameters {
|
||||||
precision: self.parameters.poisson_precision,
|
precision: self.parameters.poisson_precision,
|
||||||
|
smoother: self.parameters.poisson_smoother,
|
||||||
..MultigridParameters::default()
|
..MultigridParameters::default()
|
||||||
},
|
},
|
||||||
inner_stop,
|
inner_stop,
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ pub use piso::{PisoParameters, PisoResult, PisoSolver};
|
|||||||
#[cfg(feature = "cuda")]
|
#[cfg(feature = "cuda")]
|
||||||
pub use piso_gpu::PisoGpuSolver;
|
pub use piso_gpu::PisoGpuSolver;
|
||||||
pub use poisson::{
|
pub use poisson::{
|
||||||
MgPrecision, MultigridParameters, PcgCache, PoissonProblem, PoissonSolution, PoissonSolverKind,
|
MgPrecision, MgSmoother, MultigridParameters, PcgCache, PoissonProblem, PoissonSolution,
|
||||||
solve_multigrid_pcg, solve_multigrid_pcg_cached,
|
PoissonSolverKind, solve_multigrid_pcg, solve_multigrid_pcg_cached,
|
||||||
};
|
};
|
||||||
pub use polygon_sdf::PolygonSdf;
|
pub use polygon_sdf::PolygonSdf;
|
||||||
pub use simple::{ConvectionScheme, SimpleParameters, SimpleResult, SimpleSolver};
|
pub use simple::{ConvectionScheme, SimpleParameters, SimpleResult, SimpleSolver};
|
||||||
|
|||||||
@@ -268,11 +268,27 @@ pub enum MgPrecision {
|
|||||||
F32,
|
F32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The V-cycle's smoother ordering (PERF-2, `docs/perf2_campaign.md`).
|
||||||
|
/// `Lexicographic` is the recorded regime (row-major symmetric
|
||||||
|
/// Gauss–Seidel, a dependency chain through the division per cell);
|
||||||
|
/// `RedBlack` updates the two colours of the five-point stencil in turn —
|
||||||
|
/// each colour a map of independent cells (threads, vectors, the GPU) —
|
||||||
|
/// and is a different preconditioner, gated by the noise probe and the
|
||||||
|
/// anchor's band, never bit-identical.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub enum MgSmoother {
|
||||||
|
#[default]
|
||||||
|
Lexicographic,
|
||||||
|
RedBlack,
|
||||||
|
}
|
||||||
|
|
||||||
/// Multigrid preconditioner parameters.
|
/// Multigrid preconditioner parameters.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MultigridParameters {
|
pub struct MultigridParameters {
|
||||||
/// Precision of the V-cycle (see [`MgPrecision`]).
|
/// Precision of the V-cycle (see [`MgPrecision`]).
|
||||||
pub precision: MgPrecision,
|
pub precision: MgPrecision,
|
||||||
|
/// Smoother ordering (see [`MgSmoother`]).
|
||||||
|
pub smoother: MgSmoother,
|
||||||
/// Symmetric Gauss–Seidel sweeps before AND after the coarse correction
|
/// Symmetric Gauss–Seidel sweeps before AND after the coarse correction
|
||||||
/// (default 2; a value of 0 is treated as 1). One count for both on
|
/// (default 2; a value of 0 is treated as 1). One count for both on
|
||||||
/// purpose: unequal pre/post counts make the V-cycle non-symmetric and
|
/// purpose: unequal pre/post counts make the V-cycle non-symmetric and
|
||||||
@@ -290,6 +306,7 @@ impl Default for MultigridParameters {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
precision: MgPrecision::F64,
|
precision: MgPrecision::F64,
|
||||||
|
smoother: MgSmoother::Lexicographic,
|
||||||
smoother_sweeps: 2,
|
smoother_sweeps: 2,
|
||||||
coarsest_cells: 32,
|
coarsest_cells: 32,
|
||||||
max_iterations: 500,
|
max_iterations: 500,
|
||||||
@@ -431,6 +448,10 @@ struct Level<T: MgScalar> {
|
|||||||
ap: Vec<T>,
|
ap: Vec<T>,
|
||||||
/// Row-major indices of the active cells.
|
/// Row-major indices of the active cells.
|
||||||
cells: Vec<usize>,
|
cells: Vec<usize>,
|
||||||
|
/// The active cells with `i + j` even / odd, each row-major (the
|
||||||
|
/// red-black smoother's two independent maps on the five-point stencil).
|
||||||
|
red: Vec<usize>,
|
||||||
|
black: Vec<usize>,
|
||||||
/// Fine index → coarse index (empty on the coarsest level).
|
/// Fine index → coarse index (empty on the coarsest level).
|
||||||
coarse_of: Vec<usize>,
|
coarse_of: Vec<usize>,
|
||||||
}
|
}
|
||||||
@@ -491,6 +512,17 @@ impl<T: MgScalar> Level<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let cells: Vec<usize> = (0..n).filter(|&idx| active[idx]).collect();
|
let cells: Vec<usize> = (0..n).filter(|&idx| active[idx]).collect();
|
||||||
|
let parity = |idx: usize| (idx % nx + idx / nx) % 2;
|
||||||
|
let red: Vec<usize> = cells
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|&idx| parity(idx) == 0)
|
||||||
|
.collect();
|
||||||
|
let black: Vec<usize> = cells
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|&idx| parity(idx) == 1)
|
||||||
|
.collect();
|
||||||
let cast = |v: &[f64]| v.iter().map(|&x| T::from_f64(x)).collect::<Vec<T>>();
|
let cast = |v: &[f64]| v.iter().map(|&x| T::from_f64(x)).collect::<Vec<T>>();
|
||||||
Self {
|
Self {
|
||||||
ae: cast(&problem.ae),
|
ae: cast(&problem.ae),
|
||||||
@@ -501,6 +533,8 @@ impl<T: MgScalar> Level<T> {
|
|||||||
problem,
|
problem,
|
||||||
active,
|
active,
|
||||||
cells,
|
cells,
|
||||||
|
red,
|
||||||
|
black,
|
||||||
coarse_of: Vec::new(),
|
coarse_of: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -559,6 +593,33 @@ impl<T: MgScalar> Level<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One symmetric red-black Gauss–Seidel sweep: red, black, black, red —
|
||||||
|
/// within a colour every cell reads only the other colour, so each
|
||||||
|
/// half-sweep is a map (symmetric as a preconditioner, like the
|
||||||
|
/// lexicographic pair).
|
||||||
|
fn symmetric_gs_rb(&self, b: &[T], x: &mut [T]) {
|
||||||
|
for &idx in &self.red {
|
||||||
|
x[idx] = (b[idx] + self.neighbour_sum(x, idx)) / self.ap[idx];
|
||||||
|
}
|
||||||
|
for &idx in &self.black {
|
||||||
|
x[idx] = (b[idx] + self.neighbour_sum(x, idx)) / self.ap[idx];
|
||||||
|
}
|
||||||
|
for &idx in &self.black {
|
||||||
|
x[idx] = (b[idx] + self.neighbour_sum(x, idx)) / self.ap[idx];
|
||||||
|
}
|
||||||
|
for &idx in &self.red {
|
||||||
|
x[idx] = (b[idx] + self.neighbour_sum(x, idx)) / self.ap[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One symmetric sweep in the chosen ordering.
|
||||||
|
fn smooth(&self, b: &[T], x: &mut [T], smoother: MgSmoother) {
|
||||||
|
match smoother {
|
||||||
|
MgSmoother::Lexicographic => self.symmetric_gs(b, x),
|
||||||
|
MgSmoother::RedBlack => self.symmetric_gs_rb(b, x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Galerkin coarsening: coarse face coefficient = sum of the fine
|
/// Galerkin coarsening: coarse face coefficient = sum of the fine
|
||||||
/// coefficients across that coarse face, coarse `extra_diag` = sum of
|
/// coefficients across that coarse face, coarse `extra_diag` = sum of
|
||||||
/// the children's. Returns the coarse problem and the parent map.
|
/// the children's. Returns the coarse problem and the parent map.
|
||||||
@@ -601,6 +662,7 @@ pub(crate) struct Hierarchy<T: MgScalar = f64> {
|
|||||||
levels: Vec<Level<T>>,
|
levels: Vec<Level<T>>,
|
||||||
work: Vec<Work<T>>,
|
work: Vec<Work<T>>,
|
||||||
sweeps: usize,
|
sweeps: usize,
|
||||||
|
smoother: MgSmoother,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: MgScalar> Hierarchy<T> {
|
impl<T: MgScalar> Hierarchy<T> {
|
||||||
@@ -630,6 +692,7 @@ impl<T: MgScalar> Hierarchy<T> {
|
|||||||
levels,
|
levels,
|
||||||
work,
|
work,
|
||||||
sweeps: params.smoother_sweeps.max(1),
|
sweeps: params.smoother_sweeps.max(1),
|
||||||
|
smoother: params.smoother,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,7 +735,7 @@ impl<T: MgScalar> Hierarchy<T> {
|
|||||||
wf.x[idx] = T::ZERO;
|
wf.x[idx] = T::ZERO;
|
||||||
}
|
}
|
||||||
for _ in 0..self.sweeps {
|
for _ in 0..self.sweeps {
|
||||||
fine.symmetric_gs(&wf.b, &mut wf.x);
|
fine.smooth(&wf.b, &mut wf.x, self.smoother);
|
||||||
}
|
}
|
||||||
fine.residual(&wf.b, &wf.x, &mut wf.r);
|
fine.residual(&wf.b, &wf.x, &mut wf.r);
|
||||||
for &idx in &coarse.cells {
|
for &idx in &coarse.cells {
|
||||||
@@ -695,7 +758,7 @@ impl<T: MgScalar> Hierarchy<T> {
|
|||||||
wb.x[idx] = T::ZERO;
|
wb.x[idx] = T::ZERO;
|
||||||
}
|
}
|
||||||
for _ in 0..COARSEST_SWEEPS {
|
for _ in 0..COARSEST_SWEEPS {
|
||||||
bottom.symmetric_gs(&wb.b, &mut wb.x);
|
bottom.smooth(&wb.b, &mut wb.x, self.smoother);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Up: prolongate, smooth.
|
// Up: prolongate, smooth.
|
||||||
@@ -707,7 +770,7 @@ impl<T: MgScalar> Hierarchy<T> {
|
|||||||
wf.x[idx] += T::from_f64(COARSE_CORRECTION) * wc.x[fine.coarse_of[idx]];
|
wf.x[idx] += T::from_f64(COARSE_CORRECTION) * wc.x[fine.coarse_of[idx]];
|
||||||
}
|
}
|
||||||
for _ in 0..self.sweeps {
|
for _ in 0..self.sweeps {
|
||||||
fine.symmetric_gs(&wf.b, &mut wf.x);
|
fine.smooth(&wf.b, &mut wf.x, self.smoother);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for &idx in &levels[0].cells {
|
for &idx in &levels[0].cells {
|
||||||
@@ -762,6 +825,7 @@ struct OperatorKey {
|
|||||||
coefficients: Vec<u64>,
|
coefficients: Vec<u64>,
|
||||||
smoother_sweeps: usize,
|
smoother_sweeps: usize,
|
||||||
coarsest_cells: usize,
|
coarsest_cells: usize,
|
||||||
|
smoother: MgSmoother,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OperatorKey {
|
impl OperatorKey {
|
||||||
@@ -782,6 +846,7 @@ impl OperatorKey {
|
|||||||
coefficients,
|
coefficients,
|
||||||
smoother_sweeps: params.smoother_sweeps,
|
smoother_sweeps: params.smoother_sweeps,
|
||||||
coarsest_cells: params.coarsest_cells,
|
coarsest_cells: params.coarsest_cells,
|
||||||
|
smoother: params.smoother,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,6 +855,7 @@ impl OperatorKey {
|
|||||||
&& self.ny == problem.ny
|
&& self.ny == problem.ny
|
||||||
&& self.smoother_sweeps == params.smoother_sweeps
|
&& self.smoother_sweeps == params.smoother_sweeps
|
||||||
&& self.coarsest_cells == params.coarsest_cells
|
&& self.coarsest_cells == params.coarsest_cells
|
||||||
|
&& self.smoother == params.smoother
|
||||||
&& self.active == problem.active
|
&& self.active == problem.active
|
||||||
&& self.coefficients.iter().copied().eq(problem
|
&& self.coefficients.iter().copied().eq(problem
|
||||||
.ae
|
.ae
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
//! PERF-2 (`docs/perf2_campaign.md`): the red-black symmetric Gauss–Seidel
|
||||||
|
//! smoother is a different preconditioner, not a bit-identical one: the pin
|
||||||
|
//! is that it solves the same masked problem to the same stop, that its
|
||||||
|
//! solution agrees with the lexicographic one to the solver's tolerance,
|
||||||
|
//! and that the cached red-black solve is bit-identical to the uncached
|
||||||
|
//! red-black solve (the cache keys on the smoother).
|
||||||
|
|
||||||
|
use rtx_cfd::solvers::incompressible::{
|
||||||
|
MgSmoother, MultigridParameters, PcgCache, PoissonProblem, solve_multigrid_pcg,
|
||||||
|
solve_multigrid_pcg_cached,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn problem(nx: usize, ny: usize, seed: u64) -> PoissonProblem {
|
||||||
|
let mut p = PoissonProblem::new(nx, ny);
|
||||||
|
let (dx, dy, dt) = (1.0 / nx as f64, 0.41 / ny as f64, 1e-3);
|
||||||
|
let (ae, an) = (dt * dy / dx, dt * dx / dy);
|
||||||
|
let hole = |i: usize, j: usize| {
|
||||||
|
let (x, y) = ((i as f64 + 0.5) * dx, (j as f64 + 0.5) * dy);
|
||||||
|
(x - 0.2).powi(2) + (y - 0.2).powi(2) < 0.05 * 0.05
|
||||||
|
};
|
||||||
|
for j in 0..ny {
|
||||||
|
for i in 0..nx {
|
||||||
|
let idx = j * nx + i;
|
||||||
|
if hole(i, j) {
|
||||||
|
p.active[idx] = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if i + 1 < nx && !hole(i + 1, j) {
|
||||||
|
p.ae[idx] = ae;
|
||||||
|
}
|
||||||
|
if i > 0 && !hole(i - 1, j) {
|
||||||
|
p.aw[idx] = ae;
|
||||||
|
}
|
||||||
|
if j + 1 < ny && !hole(i, j + 1) {
|
||||||
|
p.an[idx] = an;
|
||||||
|
}
|
||||||
|
if j > 0 && !hole(i, j - 1) {
|
||||||
|
p.as_[idx] = an;
|
||||||
|
}
|
||||||
|
if i + 1 == nx {
|
||||||
|
p.extra_diag[idx] = 2.0 * ae;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut state = seed | 1;
|
||||||
|
for idx in 0..nx * ny {
|
||||||
|
state ^= state << 13;
|
||||||
|
state ^= state >> 7;
|
||||||
|
state ^= state << 17;
|
||||||
|
p.rhs[idx] = if p.active[idx] {
|
||||||
|
1e-6 * ((state >> 11) as f64 / (1u64 << 53) as f64 - 0.5)
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn red_black_solves_the_masked_problem_and_caches_exactly() {
|
||||||
|
let (nx, ny) = (96, 40);
|
||||||
|
let prob = problem(nx, ny, 5);
|
||||||
|
let tol = 1e-12;
|
||||||
|
let lex = MultigridParameters::default();
|
||||||
|
let rb = MultigridParameters {
|
||||||
|
smoother: MgSmoother::RedBlack,
|
||||||
|
..MultigridParameters::default()
|
||||||
|
};
|
||||||
|
let (mut p_lex, mut p_rb) = (vec![0.0; nx * ny], vec![0.0; nx * ny]);
|
||||||
|
let s_lex = solve_multigrid_pcg(&prob, &mut p_lex, &lex, tol, None);
|
||||||
|
let s_rb = solve_multigrid_pcg(&prob, &mut p_rb, &rb, tol, None);
|
||||||
|
assert!(s_lex.converged && s_rb.converged);
|
||||||
|
assert!(
|
||||||
|
prob.residual_l1(&p_rb) < tol,
|
||||||
|
"red-black residual {:.3e}",
|
||||||
|
prob.residual_l1(&p_rb)
|
||||||
|
);
|
||||||
|
let scale = p_lex.iter().fold(0.0_f64, |m, v| m.max(v.abs()));
|
||||||
|
let diff = p_lex
|
||||||
|
.iter()
|
||||||
|
.zip(&p_rb)
|
||||||
|
.fold(0.0_f64, |m, (a, b)| m.max((a - b).abs()));
|
||||||
|
println!(
|
||||||
|
" lexicographic {} iterations vs red-black {} iterations; solutions differ by {:.3e} on a scale of {:.3e}",
|
||||||
|
s_lex.iterations, s_rb.iterations, diff, scale
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
diff < 1e-6 * scale,
|
||||||
|
"red-black and lexicographic disagree: {diff:.3e} of {scale:.3e}"
|
||||||
|
);
|
||||||
|
// The cached red-black solve is the uncached one, bit for bit.
|
||||||
|
let mut cache = PcgCache::default();
|
||||||
|
for k in 0..3u64 {
|
||||||
|
let mut q = prob.clone();
|
||||||
|
q.rhs = problem(nx, ny, 20 + k).rhs;
|
||||||
|
let (mut a, mut b) = (vec![0.0; nx * ny], vec![0.0; nx * ny]);
|
||||||
|
let sa = solve_multigrid_pcg(&q, &mut a, &rb, tol, None);
|
||||||
|
let sb = solve_multigrid_pcg_cached(&q, &mut b, &rb, tol, None, &mut cache);
|
||||||
|
assert!(a.iter().zip(&b).all(|(x, y)| x.to_bits() == y.to_bits()));
|
||||||
|
assert_eq!(sa.iterations, sb.iterations);
|
||||||
|
}
|
||||||
|
// Switching the smoother is a cache miss (the key carries it), still exact.
|
||||||
|
let (mut a, mut b) = (vec![0.0; nx * ny], vec![0.0; nx * ny]);
|
||||||
|
let sa = solve_multigrid_pcg(&prob, &mut a, &lex, tol, None);
|
||||||
|
let sb = solve_multigrid_pcg_cached(&prob, &mut b, &lex, tol, None, &mut cache);
|
||||||
|
assert!(a.iter().zip(&b).all(|(x, y)| x.to_bits() == y.to_bits()));
|
||||||
|
assert_eq!(sa.iterations, sb.iterations);
|
||||||
|
}
|
||||||
@@ -384,6 +384,14 @@ pub fn run_march(case: BenchmarkCase, config: &MarchConfig) -> MarchResult {
|
|||||||
solver.set_poisson_precision(rtx_cfd::solvers::incompressible::MgPrecision::F32);
|
solver.set_poisson_precision(rtx_cfd::solvers::incompressible::MgPrecision::F32);
|
||||||
println!(" poisson V-cycle precision: F32 (M1 probe)");
|
println!(" poisson V-cycle precision: F32 (M1 probe)");
|
||||||
}
|
}
|
||||||
|
// PERF-2: `RTX_FSI2_MG_RB=1` — the red-black smoother on the embedded
|
||||||
|
// solver (the noise probe's regime gate).
|
||||||
|
if std::env::var("RTX_FSI2_MG_RB").is_ok_and(|v| v == "1") {
|
||||||
|
solver.set_poisson_smoother(rtx_cfd::solvers::incompressible::MgSmoother::RedBlack);
|
||||||
|
println!(
|
||||||
|
" multigrid smoother: RED-BLACK symmetric Gauss–Seidel (PERF-2 regime, RTX_FSI2_MG_RB)"
|
||||||
|
);
|
||||||
|
}
|
||||||
let dt_fluid = harness.dt_fluid;
|
let dt_fluid = harness.dt_fluid;
|
||||||
let dt = dt_fluid * subcycle as f64;
|
let dt = dt_fluid * subcycle as f64;
|
||||||
let interface = &harness.interface;
|
let interface = &harness.interface;
|
||||||
|
|||||||
@@ -300,6 +300,16 @@ impl OversetFluid {
|
|||||||
},
|
},
|
||||||
poisson_solver: PoissonSolverKind::Multigrid,
|
poisson_solver: PoissonSolverKind::Multigrid,
|
||||||
poisson_precision: MgPrecision::F64,
|
poisson_precision: MgPrecision::F64,
|
||||||
|
// PERF-2: `RTX_FSI2O_MG_RB=1` selects the red-black smoother
|
||||||
|
// (a regime change, band-gated; default = the recorded regime).
|
||||||
|
poisson_smoother: if std::env::var("RTX_FSI2O_MG_RB").is_ok_and(|v| v == "1") {
|
||||||
|
println!(
|
||||||
|
" multigrid smoother: RED-BLACK symmetric Gauss–Seidel (PERF-2 regime, RTX_FSI2O_MG_RB)"
|
||||||
|
);
|
||||||
|
rtx_cfd::solvers::incompressible::MgSmoother::RedBlack
|
||||||
|
} else {
|
||||||
|
rtx_cfd::solvers::incompressible::MgSmoother::Lexicographic
|
||||||
|
},
|
||||||
convection_scheme: bg_convection(),
|
convection_scheme: bg_convection(),
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user