Files
rustytorch/crates/specialized/rtx-cfd/tests/poisson_redblack.rs
T
Omar SobhandClaude Fable 5.1 7f144e0005
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 / Format Check (push) Failing after 6s
Performance Benchmarks / Run Benchmarks (push) Failing after 7s
CI / Clippy Check (push) Failing after 7s
CI / Build (ubuntu-latest) (push) Failing after 7s
Documentation / Build User Guide (push) Successful in 7s
CI / Build CPU-Only (Explicit) (push) Failing after 1m19s
Documentation / Build API Documentation (push) Failing after 1m22s
PERF-2 P2: the red-black colour maps, the residual and the V-cycle's per-level maps on rayon threads (MultigridParameters::threads; RTX_THREADS in the harness; rtx_cfd::configure_threads) — each colour's values computed from the unchanged other colour into a scratch and written back, the L1 sum in the serial order: bit-identical to the serial red-black (pin: 4 right-hand sides at 4 threads); the coarsest level stays serial; no effect on the lexicographic regime
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YJPeT6WA2e7YvAnS875AHL
2026-09-15 23:59:29 -05:00

141 lines
5.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! PERF-2 (`docs/perf2_campaign.md`): the red-black symmetric GaussSeidel
//! 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, configure_threads,
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);
}
/// The threaded red-black V-cycle (colour maps, residual on threads) is the
/// serial red-black one bit for bit — the same per-cell arithmetic from the
/// same inputs, the sums in the same order.
#[test]
fn threaded_red_black_is_the_serial_red_black_bit_for_bit() {
let (nx, ny) = (96, 40);
configure_threads(4);
for k in 0..4u64 {
let prob = problem(nx, ny, 31 + k);
let serial = MultigridParameters {
smoother: MgSmoother::RedBlack,
threads: 1,
..MultigridParameters::default()
};
let threaded = MultigridParameters {
smoother: MgSmoother::RedBlack,
threads: 4,
..MultigridParameters::default()
};
let (mut a, mut b) = (vec![0.0; nx * ny], vec![0.0; nx * ny]);
let sa = solve_multigrid_pcg(&prob, &mut a, &serial, 1e-12, None);
let sb = solve_multigrid_pcg(&prob, &mut b, &threaded, 1e-12, None);
assert!(sa.converged && sb.converged);
assert_eq!(sa.iterations, sb.iterations, "rhs {k}");
assert!(
a.iter().zip(&b).all(|(x, y)| x.to_bits() == y.to_bits()),
"rhs {k}: threaded red-black differs from serial"
);
}
println!(" threaded red-black: 4 right-hand sides bit-identical to serial at 4 threads");
}