rtx-cfd/rtx-fsi: overset A-P0 GATED + M1 precision probe — curvilinear collocated PISO: relative-reduction pressure stop (the absolute stop floored |du/dt| at 2e-4 on 64²), line-implicit-n sign fix, adjustPhi; gates: Cartesian reduction 1.37–1.40x the staggered error at orders 0.83/0.90; skewed stretched periodic annulus Stokes orders 2.30/2.06 (explicit and line-implicit), upwind 1.08/0.80; Poiseuille exact to 1e-9 on Cartesian and affine-sheared periodic channels (both diffusion variants), varying-skew channel order 2.02 (v 1.9), cell mass 1e-14; divergence ≤ 1e-11 relative every step; snapshot/restore bit-identical. M1: poisson.rs multigrid hierarchy generic over MgScalar (f32/f64), f64 CG keeps its own fine level; MgPrecision on MultigridParameters/EmbeddedParameters/PisoParameters, set_poisson_precision, harness RTX_FSI2_POISSON_F32 (march + noise probe, printed marker); f64 arm bit-identical in vivo (FSI2 default line-for-line with 08-31), f32 arm holds the noise floor and stall pins and the FSI2 band; poisson_equivalence f32 arm
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (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
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (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_01X2GmJXeQ2njUecEKiJZ1G2
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
52da75a3a9
commit
c63d79c300
@@ -95,7 +95,12 @@ pub struct CurvilinearParameters {
|
||||
/// Projections per step (the first removes the divergence; the rest
|
||||
/// mop up inner-solver truncation).
|
||||
pub corrector_steps: usize,
|
||||
/// Pressure-correction stop, relative to the step's flux scale.
|
||||
/// Pressure-correction stop: each projection reduces the L1 cell mass
|
||||
/// imbalance by this factor (plus a rounding floor of 1e-15 × Σ|F|).
|
||||
/// Relative to the incoming divergence, not to the flux scale, so the
|
||||
/// stop tightens as the flow settles — an absolute stop leaves a
|
||||
/// velocity-noise floor that grows with the grid (measured: |du/dt|
|
||||
/// floored at 2e-4 on the 64² Cartesian MMS with `1e-10 × Σ|F|`).
|
||||
pub tolerance: f64,
|
||||
/// Convection scheme.
|
||||
pub convection: PatchConvection,
|
||||
@@ -111,7 +116,7 @@ impl Default for CurvilinearParameters {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
corrector_steps: 2,
|
||||
tolerance: 1e-8,
|
||||
tolerance: 1e-4,
|
||||
convection: PatchConvection::Upwind,
|
||||
normal_diffusion: NormalDiffusion::Explicit,
|
||||
boundaries: PatchBoundaries::default(),
|
||||
@@ -304,22 +309,24 @@ impl CurvilinearPisoSolver {
|
||||
}
|
||||
let (_, matrix, anchor) = self.matrix.as_ref().expect("assembled");
|
||||
let flux_scale: f64 = field.flux.iter().map(|f| f.abs()).sum::<f64>().max(1e-300);
|
||||
let tolerance = self.params.tolerance * flux_scale;
|
||||
let floor = 1e-15 * flux_scale;
|
||||
|
||||
let mut iterations = 0;
|
||||
let mut converged = true;
|
||||
let mut performed = 0;
|
||||
let mut max_div = self.max_divergence(&field.flux);
|
||||
for _ in 0..self.params.corrector_steps {
|
||||
let incoming = self.divergence_l1(&field.flux);
|
||||
if incoming <= floor {
|
||||
break;
|
||||
}
|
||||
let tolerance = self.params.tolerance * incoming + floor;
|
||||
let (pc, out) = self.solve_pressure_correction(matrix, *anchor, &field.flux, tolerance);
|
||||
iterations += out.iterations;
|
||||
converged &= out.converged;
|
||||
self.apply_correction(field, &pc, dt);
|
||||
performed += 1;
|
||||
max_div = self.max_divergence(&field.flux);
|
||||
if max_div <= tolerance {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.time = t_new;
|
||||
Ok(CurvilinearResult {
|
||||
|
||||
@@ -64,21 +64,22 @@ impl CurvilinearPisoSolver {
|
||||
let mut lu = ops.face_gradient_flux(mesh, f, &field.u, &un, bu);
|
||||
let mut lv = ops.face_gradient_flux(mesh, f, &field.v, &vn, bv);
|
||||
if implicit_n && is_n && (side.is_none() || bval.is_some()) {
|
||||
// The orthogonal part, `alpha (u_other − u_c)` into the
|
||||
// cell whichever side owns the face, goes implicit: take
|
||||
// it out of the explicit flux here, put it back in
|
||||
// `solve_lines`.
|
||||
let alpha = ops.face(f).alpha;
|
||||
let (pu, pv) = match (face.owner, face.neigh) {
|
||||
(Some(p), Some(q)) => {
|
||||
let other = if p == c { q } else { p };
|
||||
(
|
||||
sign * alpha * (field.u[other] - field.u[c]),
|
||||
sign * alpha * (field.v[other] - field.v[c]),
|
||||
alpha * (field.u[other] - field.u[c]),
|
||||
alpha * (field.v[other] - field.v[c]),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
let b = bval.expect("dirichlet");
|
||||
(
|
||||
sign * alpha * (b.0 - field.u[c]),
|
||||
sign * alpha * (b.1 - field.v[c]),
|
||||
)
|
||||
(alpha * (b.0 - field.u[c]), alpha * (b.1 - field.v[c]))
|
||||
}
|
||||
};
|
||||
lu = sign * lu - pu;
|
||||
|
||||
@@ -207,6 +207,20 @@ impl CurvilinearPisoSolver {
|
||||
}
|
||||
}
|
||||
|
||||
/// Total cell mass imbalance `Σ_c |Σ_f sign F_f|`.
|
||||
pub(super) fn divergence_l1(&self, flux: &[f64]) -> f64 {
|
||||
let mesh = &self.mesh;
|
||||
(0..mesh.cell_count())
|
||||
.map(|c| {
|
||||
mesh.cell_faces(c)
|
||||
.iter()
|
||||
.map(|&(f, sign)| sign * flux[f])
|
||||
.sum::<f64>()
|
||||
.abs()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
/// Largest cell mass imbalance `|Σ sign F_f|`.
|
||||
pub(super) fn max_divergence(&self, flux: &[f64]) -> f64 {
|
||||
let mesh = &self.mesh;
|
||||
|
||||
@@ -38,7 +38,9 @@
|
||||
|
||||
use super::ale::{AleBoundaries, SideBoundary};
|
||||
use super::embedded_body::{EmbeddedBody, EmbeddedMask, FaceKind};
|
||||
use super::poisson::{MultigridParameters, PoissonProblem, PoissonSolverKind, solve_multigrid_pcg};
|
||||
use super::poisson::{
|
||||
MgPrecision, MultigridParameters, PoissonProblem, PoissonSolverKind, solve_multigrid_pcg,
|
||||
};
|
||||
use super::simple::ConvectionScheme;
|
||||
use super::{FlowField, SolverResult};
|
||||
use crate::{CfdConfig, CfdError, CfdResult};
|
||||
@@ -61,6 +63,12 @@ pub struct EmbeddedParameters {
|
||||
/// [`PoissonSolverKind::Sor`]). Both solve the same system to the same
|
||||
/// true-residual stop; multigrid's cost is mesh-independent.
|
||||
pub poisson_solver: PoissonSolverKind,
|
||||
/// Precision of the multigrid V-cycle (default [`MgPrecision::F64`] =
|
||||
/// bit-identical). `F32` is the M1 precision probe of
|
||||
/// `overset_metal_campaign.md` §3.2 / §5.2: the CG, its true residual
|
||||
/// and the stop stay f64; only the preconditioner runs in single
|
||||
/// precision. No effect with [`PoissonSolverKind::Sor`].
|
||||
pub poisson_precision: MgPrecision,
|
||||
/// Convective face values in the explicit predictor (default
|
||||
/// [`ConvectionScheme::Upwind`], which is bit-identical to the fixed-grid
|
||||
/// PISO). The TVD schemes add SIMPLE's limited correction to each
|
||||
@@ -82,6 +90,7 @@ impl Default for EmbeddedParameters {
|
||||
tolerance: 1e-6,
|
||||
boundaries: AleBoundaries::default(),
|
||||
poisson_solver: PoissonSolverKind::Sor,
|
||||
poisson_precision: MgPrecision::F64,
|
||||
convection_scheme: ConvectionScheme::Upwind,
|
||||
}
|
||||
}
|
||||
@@ -174,6 +183,12 @@ impl EmbeddedPisoSolver {
|
||||
})
|
||||
}
|
||||
|
||||
/// Precision of the multigrid V-cycle (see
|
||||
/// [`EmbeddedParameters::poisson_precision`]); the M1 probe knob.
|
||||
pub fn set_poisson_precision(&mut self, precision: MgPrecision) {
|
||||
self.parameters.poisson_precision = precision;
|
||||
}
|
||||
|
||||
/// Mask hysteresis for the moving-body rebuild, as a fraction of the
|
||||
/// min cell size (default 0, exactly the plain rebuild). With a band,
|
||||
/// a cell within `band * h_min` of the surface keeps the
|
||||
@@ -902,7 +917,10 @@ impl EmbeddedPisoSolver {
|
||||
let solution = solve_multigrid_pcg(
|
||||
&problem,
|
||||
&mut p_prime,
|
||||
&MultigridParameters::default(),
|
||||
&MultigridParameters {
|
||||
precision: self.parameters.poisson_precision,
|
||||
..MultigridParameters::default()
|
||||
},
|
||||
inner_stop,
|
||||
anchor_cell,
|
||||
);
|
||||
|
||||
@@ -57,7 +57,9 @@ pub use flow_field::FlowField;
|
||||
pub use piso::{PisoParameters, PisoResult, PisoSolver};
|
||||
#[cfg(feature = "cuda")]
|
||||
pub use piso_gpu::PisoGpuSolver;
|
||||
pub use poisson::{MultigridParameters, PoissonProblem, PoissonSolution, PoissonSolverKind};
|
||||
pub use poisson::{
|
||||
MgPrecision, MultigridParameters, PoissonProblem, PoissonSolution, PoissonSolverKind,
|
||||
};
|
||||
pub use polygon_sdf::PolygonSdf;
|
||||
pub use simple::{ConvectionScheme, SimpleParameters, SimpleResult, SimpleSolver};
|
||||
#[cfg(feature = "cuda")]
|
||||
|
||||
@@ -37,7 +37,9 @@
|
||||
//! - Convective face fluxes fell back to the centre value at the sweep edges
|
||||
//! instead of using the prescribed boundary faces that exist there.
|
||||
|
||||
use super::poisson::{MultigridParameters, PoissonProblem, PoissonSolverKind, solve_multigrid_pcg};
|
||||
use super::poisson::{
|
||||
MgPrecision, MultigridParameters, PoissonProblem, PoissonSolverKind, solve_multigrid_pcg,
|
||||
};
|
||||
use super::{BoundaryConditions, FlowField, IncompressibleSolver, SolverResult};
|
||||
use crate::{CfdConfig, CfdResult};
|
||||
use async_trait::async_trait;
|
||||
@@ -59,6 +61,9 @@ pub struct PisoParameters {
|
||||
/// [`PoissonSolverKind::Sor`]). Both solve the same system to the same
|
||||
/// true-residual stop; multigrid's cost is mesh-independent.
|
||||
pub poisson_solver: PoissonSolverKind,
|
||||
/// Precision of the multigrid V-cycle (default [`MgPrecision::F64`] =
|
||||
/// bit-identical; `F32` = the M1 precision probe, no effect with SOR).
|
||||
pub poisson_precision: MgPrecision,
|
||||
}
|
||||
|
||||
impl Default for PisoParameters {
|
||||
@@ -68,6 +73,7 @@ impl Default for PisoParameters {
|
||||
time_step: 0.001,
|
||||
tolerance: 1e-6,
|
||||
poisson_solver: PoissonSolverKind::Sor,
|
||||
poisson_precision: MgPrecision::F64,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -398,7 +404,10 @@ impl PisoSolver {
|
||||
let solution = solve_multigrid_pcg(
|
||||
&problem,
|
||||
&mut p_prime,
|
||||
&MultigridParameters::default(),
|
||||
&MultigridParameters {
|
||||
precision: self.parameters.poisson_precision,
|
||||
..MultigridParameters::default()
|
||||
},
|
||||
inner_stop,
|
||||
Some(nx + 1),
|
||||
);
|
||||
|
||||
@@ -252,9 +252,27 @@ impl PoissonProblem {
|
||||
}
|
||||
}
|
||||
|
||||
/// Arithmetic precision of the multigrid PRECONDITIONER (the conjugate
|
||||
/// gradient around it, its true residual, the mean projections and the
|
||||
/// stopping rule stay `f64`). `F32` is the registered mixed-precision
|
||||
/// design of `overset_metal_campaign.md` §3.2 M1 / §5.2: it decides
|
||||
/// whether an fp32-only device (Apple Metal) can carry the pressure solve
|
||||
/// of a coupled march. Default `F64` = bit-identical to the historical
|
||||
/// solver.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum MgPrecision {
|
||||
/// Double precision throughout (the default; bit-identical).
|
||||
#[default]
|
||||
F64,
|
||||
/// Single-precision V-cycle inside the double-precision CG.
|
||||
F32,
|
||||
}
|
||||
|
||||
/// Multigrid preconditioner parameters.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MultigridParameters {
|
||||
/// Precision of the V-cycle (see [`MgPrecision`]).
|
||||
pub precision: MgPrecision,
|
||||
/// 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
|
||||
/// purpose: unequal pre/post counts make the V-cycle non-symmetric and
|
||||
@@ -271,6 +289,7 @@ pub struct MultigridParameters {
|
||||
impl Default for MultigridParameters {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
precision: MgPrecision::F64,
|
||||
smoother_sweeps: 2,
|
||||
coarsest_cells: 32,
|
||||
max_iterations: 500,
|
||||
@@ -335,14 +354,77 @@ const COARSE_CORRECTION: f64 = 2.0;
|
||||
/// Hard cap on the number of levels (a 1×1 coarsest is reached long before).
|
||||
const MAX_LEVELS: usize = 64;
|
||||
|
||||
/// One level of the hierarchy: the problem, its diagonal, the list of
|
||||
/// active cells that carry an equation (row-major), the parent map into the
|
||||
/// next coarser level, and the V-cycle work vectors.
|
||||
struct Level {
|
||||
/// The scalar the V-cycle runs in. `f64` reproduces the historical solver
|
||||
/// bit for bit (same operations in the same order on the same values);
|
||||
/// `f32` is the mixed-precision probe.
|
||||
pub trait MgScalar:
|
||||
Copy
|
||||
+ PartialEq
|
||||
+ PartialOrd
|
||||
+ std::ops::Add<Output = Self>
|
||||
+ std::ops::Sub<Output = Self>
|
||||
+ std::ops::Mul<Output = Self>
|
||||
+ std::ops::Div<Output = Self>
|
||||
+ std::ops::AddAssign
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static
|
||||
{
|
||||
/// Zero.
|
||||
const ZERO: Self;
|
||||
/// From double.
|
||||
fn from_f64(v: f64) -> Self;
|
||||
/// To double.
|
||||
fn to_f64(self) -> f64;
|
||||
/// Absolute value.
|
||||
fn abs(self) -> Self;
|
||||
}
|
||||
|
||||
impl MgScalar for f64 {
|
||||
const ZERO: Self = 0.0;
|
||||
#[inline]
|
||||
fn from_f64(v: f64) -> Self {
|
||||
v
|
||||
}
|
||||
#[inline]
|
||||
fn to_f64(self) -> f64 {
|
||||
self
|
||||
}
|
||||
#[inline]
|
||||
fn abs(self) -> Self {
|
||||
f64::abs(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl MgScalar for f32 {
|
||||
const ZERO: Self = 0.0;
|
||||
#[inline]
|
||||
fn from_f64(v: f64) -> Self {
|
||||
v as f32
|
||||
}
|
||||
#[inline]
|
||||
fn to_f64(self) -> f64 {
|
||||
f64::from(self)
|
||||
}
|
||||
#[inline]
|
||||
fn abs(self) -> Self {
|
||||
f32::abs(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// One level of the hierarchy: the problem (kept in `f64` for coarsening
|
||||
/// and inspection), its coefficients and diagonal in the V-cycle scalar,
|
||||
/// the list of active cells that carry an equation (row-major), and the
|
||||
/// parent map into the next coarser level.
|
||||
struct Level<T: MgScalar> {
|
||||
problem: PoissonProblem,
|
||||
/// `problem.active && ap > 0`: cells with an equation.
|
||||
active: Vec<bool>,
|
||||
ap: Vec<f64>,
|
||||
ae: Vec<T>,
|
||||
aw: Vec<T>,
|
||||
an: Vec<T>,
|
||||
as_: Vec<T>,
|
||||
ap: Vec<T>,
|
||||
/// Row-major indices of the active cells.
|
||||
cells: Vec<usize>,
|
||||
/// Fine index → coarse index (empty on the coarsest level).
|
||||
@@ -350,26 +432,26 @@ struct Level {
|
||||
}
|
||||
|
||||
/// V-cycle work vectors of one level.
|
||||
struct Work {
|
||||
struct Work<T: MgScalar> {
|
||||
/// Right-hand side of the residual equation on this level.
|
||||
b: Vec<f64>,
|
||||
b: Vec<T>,
|
||||
/// Correction on this level.
|
||||
x: Vec<f64>,
|
||||
x: Vec<T>,
|
||||
/// Residual.
|
||||
r: Vec<f64>,
|
||||
r: Vec<T>,
|
||||
}
|
||||
|
||||
impl Work {
|
||||
impl<T: MgScalar> Work<T> {
|
||||
fn new(n: usize) -> Self {
|
||||
Self {
|
||||
b: vec![0.0; n],
|
||||
x: vec![0.0; n],
|
||||
r: vec![0.0; n],
|
||||
b: vec![T::ZERO; n],
|
||||
x: vec![T::ZERO; n],
|
||||
r: vec![T::ZERO; n],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Level {
|
||||
impl<T: MgScalar> Level<T> {
|
||||
fn new(mut problem: PoissonProblem) -> Self {
|
||||
let (nx, ny) = (problem.nx, problem.ny);
|
||||
let n = nx * ny;
|
||||
@@ -405,10 +487,15 @@ impl Level {
|
||||
}
|
||||
}
|
||||
let cells: Vec<usize> = (0..n).filter(|&idx| active[idx]).collect();
|
||||
let cast = |v: &[f64]| v.iter().map(|&x| T::from_f64(x)).collect::<Vec<T>>();
|
||||
Self {
|
||||
ae: cast(&problem.ae),
|
||||
aw: cast(&problem.aw),
|
||||
an: cast(&problem.an),
|
||||
as_: cast(&problem.as_),
|
||||
ap: cast(&ap),
|
||||
problem,
|
||||
active,
|
||||
ap,
|
||||
cells,
|
||||
coarse_of: Vec::new(),
|
||||
}
|
||||
@@ -418,38 +505,38 @@ impl Level {
|
||||
/// read (their coefficients are the only non-zero ones after
|
||||
/// sanitising).
|
||||
#[inline]
|
||||
fn neighbour_sum(&self, x: &[f64], idx: usize) -> f64 {
|
||||
fn neighbour_sum(&self, x: &[T], idx: usize) -> T {
|
||||
let nx = self.problem.nx;
|
||||
let mut s = 0.0;
|
||||
let ae = self.problem.ae[idx];
|
||||
if ae != 0.0 {
|
||||
let mut s = T::ZERO;
|
||||
let ae = self.ae[idx];
|
||||
if ae != T::ZERO {
|
||||
s += ae * x[idx + 1];
|
||||
}
|
||||
let aw = self.problem.aw[idx];
|
||||
if aw != 0.0 {
|
||||
let aw = self.aw[idx];
|
||||
if aw != T::ZERO {
|
||||
s += aw * x[idx - 1];
|
||||
}
|
||||
let an = self.problem.an[idx];
|
||||
if an != 0.0 {
|
||||
let an = self.an[idx];
|
||||
if an != T::ZERO {
|
||||
s += an * x[idx + nx];
|
||||
}
|
||||
let as_ = self.problem.as_[idx];
|
||||
if as_ != 0.0 {
|
||||
let as_ = self.as_[idx];
|
||||
if as_ != T::ZERO {
|
||||
s += as_ * x[idx - nx];
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// `y = A x` on the active cells.
|
||||
fn apply(&self, x: &[f64], y: &mut [f64]) {
|
||||
fn apply(&self, x: &[T], y: &mut [T]) {
|
||||
for &idx in &self.cells {
|
||||
y[idx] = self.ap[idx] * x[idx] - self.neighbour_sum(x, idx);
|
||||
}
|
||||
}
|
||||
|
||||
/// `r = b - A x` on the active cells; returns its L1 norm.
|
||||
fn residual(&self, b: &[f64], x: &[f64], r: &mut [f64]) -> f64 {
|
||||
let mut l1 = 0.0;
|
||||
fn residual(&self, b: &[T], x: &[T], r: &mut [T]) -> T {
|
||||
let mut l1 = T::ZERO;
|
||||
for &idx in &self.cells {
|
||||
let v = b[idx] - (self.ap[idx] * x[idx] - self.neighbour_sum(x, idx));
|
||||
r[idx] = v;
|
||||
@@ -459,7 +546,7 @@ impl Level {
|
||||
}
|
||||
|
||||
/// One symmetric Gauss–Seidel sweep (forward then backward) on `A x = b`.
|
||||
fn symmetric_gs(&self, b: &[f64], x: &mut [f64]) {
|
||||
fn symmetric_gs(&self, b: &[T], x: &mut [T]) {
|
||||
for &idx in &self.cells {
|
||||
x[idx] = (b[idx] + self.neighbour_sum(x, idx)) / self.ap[idx];
|
||||
}
|
||||
@@ -506,24 +593,24 @@ impl Level {
|
||||
}
|
||||
|
||||
/// The multigrid hierarchy: level 0 is the fine problem.
|
||||
pub(crate) struct Hierarchy {
|
||||
levels: Vec<Level>,
|
||||
work: Vec<Work>,
|
||||
pub(crate) struct Hierarchy<T: MgScalar = f64> {
|
||||
levels: Vec<Level<T>>,
|
||||
work: Vec<Work<T>>,
|
||||
sweeps: usize,
|
||||
}
|
||||
|
||||
impl Hierarchy {
|
||||
impl<T: MgScalar> Hierarchy<T> {
|
||||
/// Builds the hierarchy down to `coarsest_cells` active cells (or until
|
||||
/// coarsening stops reducing the count).
|
||||
pub(crate) fn build(problem: &PoissonProblem, params: &MultigridParameters) -> Self {
|
||||
let mut levels = vec![Level::new(problem.clone())];
|
||||
let mut levels = vec![Level::<T>::new(problem.clone())];
|
||||
while levels.len() < MAX_LEVELS {
|
||||
let fine = levels.last().expect("at least one level");
|
||||
if fine.cells.len() <= params.coarsest_cells.max(1) {
|
||||
break;
|
||||
}
|
||||
let (coarse, coarse_of) = fine.coarsen();
|
||||
let coarse = Level::new(coarse);
|
||||
let coarse = Level::<T>::new(coarse);
|
||||
if coarse.cells.len() >= fine.cells.len() {
|
||||
break;
|
||||
}
|
||||
@@ -570,7 +657,7 @@ impl Hierarchy {
|
||||
let depth = self.levels.len();
|
||||
let levels = &self.levels;
|
||||
for &idx in &levels[0].cells {
|
||||
self.work[0].b[idx] = r[idx];
|
||||
self.work[0].b[idx] = T::from_f64(r[idx]);
|
||||
}
|
||||
// Down: smooth from zero, restrict the residual.
|
||||
for l in 0..depth - 1 {
|
||||
@@ -578,14 +665,14 @@ impl Hierarchy {
|
||||
let (head, tail) = self.work.split_at_mut(l + 1);
|
||||
let (wf, wc) = (&mut head[l], &mut tail[0]);
|
||||
for &idx in &fine.cells {
|
||||
wf.x[idx] = 0.0;
|
||||
wf.x[idx] = T::ZERO;
|
||||
}
|
||||
for _ in 0..self.sweeps {
|
||||
fine.symmetric_gs(&wf.b, &mut wf.x);
|
||||
}
|
||||
fine.residual(&wf.b, &wf.x, &mut wf.r);
|
||||
for &idx in &coarse.cells {
|
||||
wc.b[idx] = 0.0;
|
||||
wc.b[idx] = T::ZERO;
|
||||
}
|
||||
for &idx in &fine.cells {
|
||||
let c = fine.coarse_of[idx];
|
||||
@@ -601,7 +688,7 @@ impl Hierarchy {
|
||||
let bottom = &levels[depth - 1];
|
||||
let wb = &mut self.work[depth - 1];
|
||||
for &idx in &bottom.cells {
|
||||
wb.x[idx] = 0.0;
|
||||
wb.x[idx] = T::ZERO;
|
||||
}
|
||||
for _ in 0..COARSEST_SWEEPS {
|
||||
bottom.symmetric_gs(&wb.b, &mut wb.x);
|
||||
@@ -613,14 +700,14 @@ impl Hierarchy {
|
||||
let (head, tail) = self.work.split_at_mut(l + 1);
|
||||
let (wf, wc) = (&mut head[l], &tail[0]);
|
||||
for &idx in &fine.cells {
|
||||
wf.x[idx] += 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 {
|
||||
fine.symmetric_gs(&wf.b, &mut wf.x);
|
||||
}
|
||||
}
|
||||
for &idx in &levels[0].cells {
|
||||
z[idx] = self.work[0].x[idx];
|
||||
z[idx] = self.work[0].x[idx].to_f64();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -649,6 +736,23 @@ pub fn solve_multigrid_pcg(
|
||||
params: &MultigridParameters,
|
||||
tolerance: f64,
|
||||
anchor: Option<usize>,
|
||||
) -> PoissonSolution {
|
||||
match params.precision {
|
||||
MgPrecision::F64 => solve_pcg_with::<f64>(problem, p, params, tolerance, anchor),
|
||||
MgPrecision::F32 => solve_pcg_with::<f32>(problem, p, params, tolerance, anchor),
|
||||
}
|
||||
}
|
||||
|
||||
/// The CG driver: `f64` throughout, with the V-cycle in `T`. The fine
|
||||
/// level used for the CG's own products and true residual is a separate
|
||||
/// `f64` level, so the precision of the preconditioner never enters the
|
||||
/// stopping rule or the reported residual.
|
||||
fn solve_pcg_with<T: MgScalar>(
|
||||
problem: &PoissonProblem,
|
||||
p: &mut [f64],
|
||||
params: &MultigridParameters,
|
||||
tolerance: f64,
|
||||
anchor: Option<usize>,
|
||||
) -> PoissonSolution {
|
||||
let n = problem.nx * problem.ny;
|
||||
assert_eq!(p.len(), n, "p must have nx*ny entries");
|
||||
@@ -658,8 +762,9 @@ pub fn solve_multigrid_pcg(
|
||||
problem.validate()
|
||||
);
|
||||
|
||||
let mut hier = Hierarchy::build(problem, params);
|
||||
let cells: Vec<usize> = hier.levels[0].cells.clone();
|
||||
let mut hier = Hierarchy::<T>::build(problem, params);
|
||||
let fine = Level::<f64>::new(problem.clone());
|
||||
let cells: Vec<usize> = fine.cells.clone();
|
||||
let active_n = cells.len();
|
||||
if active_n == 0 {
|
||||
return PoissonSolution {
|
||||
@@ -710,9 +815,9 @@ pub fn solve_multigrid_pcg(
|
||||
let mut q = vec![0.0; n];
|
||||
|
||||
let true_residual =
|
||||
|p: &[f64], r: &mut [f64], hier: &Hierarchy| -> f64 { hier.levels[0].residual(&b, p, r) };
|
||||
|p: &[f64], r: &mut [f64], fine: &Level<f64>| -> f64 { fine.residual(&b, p, r) };
|
||||
|
||||
let anchor = anchor.filter(|&a| a < n && hier.levels[0].active[a]);
|
||||
let anchor = anchor.filter(|&a| a < n && fine.active[a]);
|
||||
let finish = |p: &mut [f64], iterations: usize, residual: f64| {
|
||||
// Level of each singular component: the anchor's component is
|
||||
// shifted so p[anchor] == 0, every other singular component to mean
|
||||
@@ -737,7 +842,7 @@ pub fn solve_multigrid_pcg(
|
||||
}
|
||||
};
|
||||
|
||||
let mut res = true_residual(p, &mut r, &hier);
|
||||
let mut res = true_residual(p, &mut r, &fine);
|
||||
if res < tolerance {
|
||||
return finish(p, 0, res);
|
||||
}
|
||||
@@ -759,12 +864,12 @@ pub fn solve_multigrid_pcg(
|
||||
let mut last_true = res;
|
||||
while iterations < params.max_iterations {
|
||||
iterations += 1;
|
||||
hier.levels[0].apply(&d, &mut q);
|
||||
fine.apply(&d, &mut q);
|
||||
let dq = dot(&d, &q);
|
||||
if !dq.is_finite() || dq <= 0.0 || !rz.is_finite() || rz <= 0.0 {
|
||||
// Breakdown (r = 0 to rounding, or a non-positive curvature
|
||||
// from rounding in the null space): stop on the true residual.
|
||||
res = true_residual(p, &mut r, &hier);
|
||||
res = true_residual(p, &mut r, &fine);
|
||||
return finish(p, iterations, res);
|
||||
}
|
||||
let alpha = rz / dq;
|
||||
@@ -775,7 +880,7 @@ pub fn solve_multigrid_pcg(
|
||||
if l1(&r) < tolerance {
|
||||
// The recurrence residual passed: confirm against the TRUE
|
||||
// residual, and resynchronise if rounding has let them drift.
|
||||
res = true_residual(p, &mut r, &hier);
|
||||
res = true_residual(p, &mut r, &fine);
|
||||
if res < tolerance || res > 0.9 * last_true {
|
||||
return finish(p, iterations, res);
|
||||
}
|
||||
@@ -792,7 +897,7 @@ pub fn solve_multigrid_pcg(
|
||||
d[idx] = z[idx] + beta * d[idx];
|
||||
}
|
||||
}
|
||||
res = true_residual(p, &mut r, &hier);
|
||||
res = true_residual(p, &mut r, &fine);
|
||||
finish(p, iterations, res)
|
||||
}
|
||||
|
||||
|
||||
@@ -446,7 +446,7 @@ fn galerkin_coarse_operator_matches_r_a_p() {
|
||||
}
|
||||
}
|
||||
pr.validate().expect("weighted problem is valid");
|
||||
let hier = Hierarchy::build(&pr, &MultigridParameters::default());
|
||||
let hier = Hierarchy::<f64>::build(&pr, &MultigridParameters::default());
|
||||
assert!(hier.depth() >= 3, "depth {}", hier.depth());
|
||||
let mut g = Lcg(5);
|
||||
for l in 0..hier.depth() - 1 {
|
||||
@@ -570,7 +570,7 @@ fn v_cycle_preconditioner_is_symmetric() {
|
||||
),
|
||||
("dirichlet", assemble(n, n, |_, _| true, [true; 4])),
|
||||
] {
|
||||
let mut hier = Hierarchy::build(&pr, ¶ms);
|
||||
let mut hier = Hierarchy::<f64>::build(&pr, ¶ms);
|
||||
let mut g = Lcg(11);
|
||||
let cells = active_indices(&pr);
|
||||
let mut x = vec![0.0; n * n];
|
||||
|
||||
Reference in New Issue
Block a user