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
@@ -124,6 +124,7 @@ pub fn channel_sheared(
|
|||||||
/// A channel whose skew varies smoothly along x: `x' = x + alpha · y ·
|
/// A channel whose skew varies smoothly along x: `x' = x + alpha · y ·
|
||||||
/// sin(2π x / lx)` (zero at both ends, so it can be periodic), with the
|
/// sin(2π x / lx)` (zero at both ends, so it can be periodic), with the
|
||||||
/// across-channel spacing stretched by `stretch` toward the top wall.
|
/// across-channel spacing stretched by `stretch` toward the top wall.
|
||||||
|
/// Folds when `2π alpha ly / lx > 1`; keep `alpha` around 0.1.
|
||||||
pub fn channel_varying_skew(
|
pub fn channel_varying_skew(
|
||||||
lx: f64,
|
lx: f64,
|
||||||
ly: f64,
|
ly: f64,
|
||||||
|
|||||||
@@ -95,7 +95,12 @@ pub struct CurvilinearParameters {
|
|||||||
/// Projections per step (the first removes the divergence; the rest
|
/// Projections per step (the first removes the divergence; the rest
|
||||||
/// mop up inner-solver truncation).
|
/// mop up inner-solver truncation).
|
||||||
pub corrector_steps: usize,
|
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,
|
pub tolerance: f64,
|
||||||
/// Convection scheme.
|
/// Convection scheme.
|
||||||
pub convection: PatchConvection,
|
pub convection: PatchConvection,
|
||||||
@@ -111,7 +116,7 @@ impl Default for CurvilinearParameters {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
corrector_steps: 2,
|
corrector_steps: 2,
|
||||||
tolerance: 1e-8,
|
tolerance: 1e-4,
|
||||||
convection: PatchConvection::Upwind,
|
convection: PatchConvection::Upwind,
|
||||||
normal_diffusion: NormalDiffusion::Explicit,
|
normal_diffusion: NormalDiffusion::Explicit,
|
||||||
boundaries: PatchBoundaries::default(),
|
boundaries: PatchBoundaries::default(),
|
||||||
@@ -304,22 +309,24 @@ impl CurvilinearPisoSolver {
|
|||||||
}
|
}
|
||||||
let (_, matrix, anchor) = self.matrix.as_ref().expect("assembled");
|
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 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 iterations = 0;
|
||||||
let mut converged = true;
|
let mut converged = true;
|
||||||
let mut performed = 0;
|
let mut performed = 0;
|
||||||
let mut max_div = self.max_divergence(&field.flux);
|
let mut max_div = self.max_divergence(&field.flux);
|
||||||
for _ in 0..self.params.corrector_steps {
|
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);
|
let (pc, out) = self.solve_pressure_correction(matrix, *anchor, &field.flux, tolerance);
|
||||||
iterations += out.iterations;
|
iterations += out.iterations;
|
||||||
converged &= out.converged;
|
converged &= out.converged;
|
||||||
self.apply_correction(field, &pc, dt);
|
self.apply_correction(field, &pc, dt);
|
||||||
performed += 1;
|
performed += 1;
|
||||||
max_div = self.max_divergence(&field.flux);
|
max_div = self.max_divergence(&field.flux);
|
||||||
if max_div <= tolerance {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.time = t_new;
|
self.time = t_new;
|
||||||
Ok(CurvilinearResult {
|
Ok(CurvilinearResult {
|
||||||
|
|||||||
@@ -64,21 +64,22 @@ impl CurvilinearPisoSolver {
|
|||||||
let mut lu = ops.face_gradient_flux(mesh, f, &field.u, &un, bu);
|
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);
|
let mut lv = ops.face_gradient_flux(mesh, f, &field.v, &vn, bv);
|
||||||
if implicit_n && is_n && (side.is_none() || bval.is_some()) {
|
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 alpha = ops.face(f).alpha;
|
||||||
let (pu, pv) = match (face.owner, face.neigh) {
|
let (pu, pv) = match (face.owner, face.neigh) {
|
||||||
(Some(p), Some(q)) => {
|
(Some(p), Some(q)) => {
|
||||||
let other = if p == c { q } else { p };
|
let other = if p == c { q } else { p };
|
||||||
(
|
(
|
||||||
sign * alpha * (field.u[other] - field.u[c]),
|
alpha * (field.u[other] - field.u[c]),
|
||||||
sign * alpha * (field.v[other] - field.v[c]),
|
alpha * (field.v[other] - field.v[c]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let b = bval.expect("dirichlet");
|
let b = bval.expect("dirichlet");
|
||||||
(
|
(alpha * (b.0 - field.u[c]), alpha * (b.1 - field.v[c]))
|
||||||
sign * alpha * (b.0 - field.u[c]),
|
|
||||||
sign * alpha * (b.1 - field.v[c]),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
lu = sign * lu - pu;
|
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|`.
|
/// Largest cell mass imbalance `|Σ sign F_f|`.
|
||||||
pub(super) fn max_divergence(&self, flux: &[f64]) -> f64 {
|
pub(super) fn max_divergence(&self, flux: &[f64]) -> f64 {
|
||||||
let mesh = &self.mesh;
|
let mesh = &self.mesh;
|
||||||
|
|||||||
@@ -38,7 +38,9 @@
|
|||||||
|
|
||||||
use super::ale::{AleBoundaries, SideBoundary};
|
use super::ale::{AleBoundaries, SideBoundary};
|
||||||
use super::embedded_body::{EmbeddedBody, EmbeddedMask, FaceKind};
|
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::simple::ConvectionScheme;
|
||||||
use super::{FlowField, SolverResult};
|
use super::{FlowField, SolverResult};
|
||||||
use crate::{CfdConfig, CfdError, CfdResult};
|
use crate::{CfdConfig, CfdError, CfdResult};
|
||||||
@@ -61,6 +63,12 @@ pub struct EmbeddedParameters {
|
|||||||
/// [`PoissonSolverKind::Sor`]). Both solve the same system to the same
|
/// [`PoissonSolverKind::Sor`]). Both solve the same system to the same
|
||||||
/// true-residual stop; multigrid's cost is mesh-independent.
|
/// true-residual stop; multigrid's cost is mesh-independent.
|
||||||
pub poisson_solver: PoissonSolverKind,
|
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
|
/// 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
|
||||||
@@ -82,6 +90,7 @@ impl Default for EmbeddedParameters {
|
|||||||
tolerance: 1e-6,
|
tolerance: 1e-6,
|
||||||
boundaries: AleBoundaries::default(),
|
boundaries: AleBoundaries::default(),
|
||||||
poisson_solver: PoissonSolverKind::Sor,
|
poisson_solver: PoissonSolverKind::Sor,
|
||||||
|
poisson_precision: MgPrecision::F64,
|
||||||
convection_scheme: ConvectionScheme::Upwind,
|
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
|
/// 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
|
||||||
@@ -902,7 +917,10 @@ impl EmbeddedPisoSolver {
|
|||||||
let solution = solve_multigrid_pcg(
|
let solution = solve_multigrid_pcg(
|
||||||
&problem,
|
&problem,
|
||||||
&mut p_prime,
|
&mut p_prime,
|
||||||
&MultigridParameters::default(),
|
&MultigridParameters {
|
||||||
|
precision: self.parameters.poisson_precision,
|
||||||
|
..MultigridParameters::default()
|
||||||
|
},
|
||||||
inner_stop,
|
inner_stop,
|
||||||
anchor_cell,
|
anchor_cell,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -57,7 +57,9 @@ pub use flow_field::FlowField;
|
|||||||
pub use piso::{PisoParameters, PisoResult, PisoSolver};
|
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::{MultigridParameters, PoissonProblem, PoissonSolution, PoissonSolverKind};
|
pub use poisson::{
|
||||||
|
MgPrecision, MultigridParameters, PoissonProblem, PoissonSolution, PoissonSolverKind,
|
||||||
|
};
|
||||||
pub use polygon_sdf::PolygonSdf;
|
pub use polygon_sdf::PolygonSdf;
|
||||||
pub use simple::{ConvectionScheme, SimpleParameters, SimpleResult, SimpleSolver};
|
pub use simple::{ConvectionScheme, SimpleParameters, SimpleResult, SimpleSolver};
|
||||||
#[cfg(feature = "cuda")]
|
#[cfg(feature = "cuda")]
|
||||||
|
|||||||
@@ -37,7 +37,9 @@
|
|||||||
//! - Convective face fluxes fell back to the centre value at the sweep edges
|
//! - Convective face fluxes fell back to the centre value at the sweep edges
|
||||||
//! instead of using the prescribed boundary faces that exist there.
|
//! 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 super::{BoundaryConditions, FlowField, IncompressibleSolver, SolverResult};
|
||||||
use crate::{CfdConfig, CfdResult};
|
use crate::{CfdConfig, CfdResult};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@@ -59,6 +61,9 @@ pub struct PisoParameters {
|
|||||||
/// [`PoissonSolverKind::Sor`]). Both solve the same system to the same
|
/// [`PoissonSolverKind::Sor`]). Both solve the same system to the same
|
||||||
/// true-residual stop; multigrid's cost is mesh-independent.
|
/// true-residual stop; multigrid's cost is mesh-independent.
|
||||||
pub poisson_solver: PoissonSolverKind,
|
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 {
|
impl Default for PisoParameters {
|
||||||
@@ -68,6 +73,7 @@ impl Default for PisoParameters {
|
|||||||
time_step: 0.001,
|
time_step: 0.001,
|
||||||
tolerance: 1e-6,
|
tolerance: 1e-6,
|
||||||
poisson_solver: PoissonSolverKind::Sor,
|
poisson_solver: PoissonSolverKind::Sor,
|
||||||
|
poisson_precision: MgPrecision::F64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -398,7 +404,10 @@ impl PisoSolver {
|
|||||||
let solution = solve_multigrid_pcg(
|
let solution = solve_multigrid_pcg(
|
||||||
&problem,
|
&problem,
|
||||||
&mut p_prime,
|
&mut p_prime,
|
||||||
&MultigridParameters::default(),
|
&MultigridParameters {
|
||||||
|
precision: self.parameters.poisson_precision,
|
||||||
|
..MultigridParameters::default()
|
||||||
|
},
|
||||||
inner_stop,
|
inner_stop,
|
||||||
Some(nx + 1),
|
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.
|
/// Multigrid preconditioner parameters.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MultigridParameters {
|
pub struct MultigridParameters {
|
||||||
|
/// Precision of the V-cycle (see [`MgPrecision`]).
|
||||||
|
pub precision: MgPrecision,
|
||||||
/// 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
|
||||||
@@ -271,6 +289,7 @@ pub struct MultigridParameters {
|
|||||||
impl Default for MultigridParameters {
|
impl Default for MultigridParameters {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
precision: MgPrecision::F64,
|
||||||
smoother_sweeps: 2,
|
smoother_sweeps: 2,
|
||||||
coarsest_cells: 32,
|
coarsest_cells: 32,
|
||||||
max_iterations: 500,
|
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).
|
/// Hard cap on the number of levels (a 1×1 coarsest is reached long before).
|
||||||
const MAX_LEVELS: usize = 64;
|
const MAX_LEVELS: usize = 64;
|
||||||
|
|
||||||
/// One level of the hierarchy: the problem, its diagonal, the list of
|
/// The scalar the V-cycle runs in. `f64` reproduces the historical solver
|
||||||
/// active cells that carry an equation (row-major), the parent map into the
|
/// bit for bit (same operations in the same order on the same values);
|
||||||
/// next coarser level, and the V-cycle work vectors.
|
/// `f32` is the mixed-precision probe.
|
||||||
struct Level {
|
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: PoissonProblem,
|
||||||
/// `problem.active && ap > 0`: cells with an equation.
|
/// `problem.active && ap > 0`: cells with an equation.
|
||||||
active: Vec<bool>,
|
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.
|
/// Row-major indices of the active cells.
|
||||||
cells: Vec<usize>,
|
cells: Vec<usize>,
|
||||||
/// Fine index → coarse index (empty on the coarsest level).
|
/// Fine index → coarse index (empty on the coarsest level).
|
||||||
@@ -350,26 +432,26 @@ struct Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// V-cycle work vectors of one level.
|
/// V-cycle work vectors of one level.
|
||||||
struct Work {
|
struct Work<T: MgScalar> {
|
||||||
/// Right-hand side of the residual equation on this level.
|
/// Right-hand side of the residual equation on this level.
|
||||||
b: Vec<f64>,
|
b: Vec<T>,
|
||||||
/// Correction on this level.
|
/// Correction on this level.
|
||||||
x: Vec<f64>,
|
x: Vec<T>,
|
||||||
/// Residual.
|
/// Residual.
|
||||||
r: Vec<f64>,
|
r: Vec<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Work {
|
impl<T: MgScalar> Work<T> {
|
||||||
fn new(n: usize) -> Self {
|
fn new(n: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
b: vec![0.0; n],
|
b: vec![T::ZERO; n],
|
||||||
x: vec![0.0; n],
|
x: vec![T::ZERO; n],
|
||||||
r: vec![0.0; n],
|
r: vec![T::ZERO; n],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Level {
|
impl<T: MgScalar> Level<T> {
|
||||||
fn new(mut problem: PoissonProblem) -> Self {
|
fn new(mut problem: PoissonProblem) -> Self {
|
||||||
let (nx, ny) = (problem.nx, problem.ny);
|
let (nx, ny) = (problem.nx, problem.ny);
|
||||||
let n = nx * ny;
|
let n = nx * ny;
|
||||||
@@ -405,10 +487,15 @@ impl Level {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let cells: Vec<usize> = (0..n).filter(|&idx| active[idx]).collect();
|
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 {
|
Self {
|
||||||
|
ae: cast(&problem.ae),
|
||||||
|
aw: cast(&problem.aw),
|
||||||
|
an: cast(&problem.an),
|
||||||
|
as_: cast(&problem.as_),
|
||||||
|
ap: cast(&ap),
|
||||||
problem,
|
problem,
|
||||||
active,
|
active,
|
||||||
ap,
|
|
||||||
cells,
|
cells,
|
||||||
coarse_of: Vec::new(),
|
coarse_of: Vec::new(),
|
||||||
}
|
}
|
||||||
@@ -418,38 +505,38 @@ impl Level {
|
|||||||
/// read (their coefficients are the only non-zero ones after
|
/// read (their coefficients are the only non-zero ones after
|
||||||
/// sanitising).
|
/// sanitising).
|
||||||
#[inline]
|
#[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 nx = self.problem.nx;
|
||||||
let mut s = 0.0;
|
let mut s = T::ZERO;
|
||||||
let ae = self.problem.ae[idx];
|
let ae = self.ae[idx];
|
||||||
if ae != 0.0 {
|
if ae != T::ZERO {
|
||||||
s += ae * x[idx + 1];
|
s += ae * x[idx + 1];
|
||||||
}
|
}
|
||||||
let aw = self.problem.aw[idx];
|
let aw = self.aw[idx];
|
||||||
if aw != 0.0 {
|
if aw != T::ZERO {
|
||||||
s += aw * x[idx - 1];
|
s += aw * x[idx - 1];
|
||||||
}
|
}
|
||||||
let an = self.problem.an[idx];
|
let an = self.an[idx];
|
||||||
if an != 0.0 {
|
if an != T::ZERO {
|
||||||
s += an * x[idx + nx];
|
s += an * x[idx + nx];
|
||||||
}
|
}
|
||||||
let as_ = self.problem.as_[idx];
|
let as_ = self.as_[idx];
|
||||||
if as_ != 0.0 {
|
if as_ != T::ZERO {
|
||||||
s += as_ * x[idx - nx];
|
s += as_ * x[idx - nx];
|
||||||
}
|
}
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `y = A x` on the active cells.
|
/// `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 {
|
for &idx in &self.cells {
|
||||||
y[idx] = self.ap[idx] * x[idx] - self.neighbour_sum(x, idx);
|
y[idx] = self.ap[idx] * x[idx] - self.neighbour_sum(x, idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `r = b - A x` on the active cells; returns its L1 norm.
|
/// `r = b - A x` on the active cells; returns its L1 norm.
|
||||||
fn residual(&self, b: &[f64], x: &[f64], r: &mut [f64]) -> f64 {
|
fn residual(&self, b: &[T], x: &[T], r: &mut [T]) -> T {
|
||||||
let mut l1 = 0.0;
|
let mut l1 = T::ZERO;
|
||||||
for &idx in &self.cells {
|
for &idx in &self.cells {
|
||||||
let v = b[idx] - (self.ap[idx] * x[idx] - self.neighbour_sum(x, idx));
|
let v = b[idx] - (self.ap[idx] * x[idx] - self.neighbour_sum(x, idx));
|
||||||
r[idx] = v;
|
r[idx] = v;
|
||||||
@@ -459,7 +546,7 @@ impl Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// One symmetric Gauss–Seidel sweep (forward then backward) on `A x = b`.
|
/// 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 {
|
for &idx in &self.cells {
|
||||||
x[idx] = (b[idx] + self.neighbour_sum(x, idx)) / self.ap[idx];
|
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.
|
/// The multigrid hierarchy: level 0 is the fine problem.
|
||||||
pub(crate) struct Hierarchy {
|
pub(crate) struct Hierarchy<T: MgScalar = f64> {
|
||||||
levels: Vec<Level>,
|
levels: Vec<Level<T>>,
|
||||||
work: Vec<Work>,
|
work: Vec<Work<T>>,
|
||||||
sweeps: usize,
|
sweeps: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hierarchy {
|
impl<T: MgScalar> Hierarchy<T> {
|
||||||
/// Builds the hierarchy down to `coarsest_cells` active cells (or until
|
/// Builds the hierarchy down to `coarsest_cells` active cells (or until
|
||||||
/// coarsening stops reducing the count).
|
/// coarsening stops reducing the count).
|
||||||
pub(crate) fn build(problem: &PoissonProblem, params: &MultigridParameters) -> Self {
|
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 {
|
while levels.len() < MAX_LEVELS {
|
||||||
let fine = levels.last().expect("at least one level");
|
let fine = levels.last().expect("at least one level");
|
||||||
if fine.cells.len() <= params.coarsest_cells.max(1) {
|
if fine.cells.len() <= params.coarsest_cells.max(1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let (coarse, coarse_of) = fine.coarsen();
|
let (coarse, coarse_of) = fine.coarsen();
|
||||||
let coarse = Level::new(coarse);
|
let coarse = Level::<T>::new(coarse);
|
||||||
if coarse.cells.len() >= fine.cells.len() {
|
if coarse.cells.len() >= fine.cells.len() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -570,7 +657,7 @@ impl Hierarchy {
|
|||||||
let depth = self.levels.len();
|
let depth = self.levels.len();
|
||||||
let levels = &self.levels;
|
let levels = &self.levels;
|
||||||
for &idx in &levels[0].cells {
|
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.
|
// Down: smooth from zero, restrict the residual.
|
||||||
for l in 0..depth - 1 {
|
for l in 0..depth - 1 {
|
||||||
@@ -578,14 +665,14 @@ impl Hierarchy {
|
|||||||
let (head, tail) = self.work.split_at_mut(l + 1);
|
let (head, tail) = self.work.split_at_mut(l + 1);
|
||||||
let (wf, wc) = (&mut head[l], &mut tail[0]);
|
let (wf, wc) = (&mut head[l], &mut tail[0]);
|
||||||
for &idx in &fine.cells {
|
for &idx in &fine.cells {
|
||||||
wf.x[idx] = 0.0;
|
wf.x[idx] = T::ZERO;
|
||||||
}
|
}
|
||||||
for _ in 0..self.sweeps {
|
for _ in 0..self.sweeps {
|
||||||
fine.symmetric_gs(&wf.b, &mut wf.x);
|
fine.symmetric_gs(&wf.b, &mut wf.x);
|
||||||
}
|
}
|
||||||
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 {
|
||||||
wc.b[idx] = 0.0;
|
wc.b[idx] = T::ZERO;
|
||||||
}
|
}
|
||||||
for &idx in &fine.cells {
|
for &idx in &fine.cells {
|
||||||
let c = fine.coarse_of[idx];
|
let c = fine.coarse_of[idx];
|
||||||
@@ -601,7 +688,7 @@ impl Hierarchy {
|
|||||||
let bottom = &levels[depth - 1];
|
let bottom = &levels[depth - 1];
|
||||||
let wb = &mut self.work[depth - 1];
|
let wb = &mut self.work[depth - 1];
|
||||||
for &idx in &bottom.cells {
|
for &idx in &bottom.cells {
|
||||||
wb.x[idx] = 0.0;
|
wb.x[idx] = T::ZERO;
|
||||||
}
|
}
|
||||||
for _ in 0..COARSEST_SWEEPS {
|
for _ in 0..COARSEST_SWEEPS {
|
||||||
bottom.symmetric_gs(&wb.b, &mut wb.x);
|
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 (head, tail) = self.work.split_at_mut(l + 1);
|
||||||
let (wf, wc) = (&mut head[l], &tail[0]);
|
let (wf, wc) = (&mut head[l], &tail[0]);
|
||||||
for &idx in &fine.cells {
|
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 {
|
for _ in 0..self.sweeps {
|
||||||
fine.symmetric_gs(&wf.b, &mut wf.x);
|
fine.symmetric_gs(&wf.b, &mut wf.x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for &idx in &levels[0].cells {
|
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,
|
params: &MultigridParameters,
|
||||||
tolerance: f64,
|
tolerance: f64,
|
||||||
anchor: Option<usize>,
|
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 {
|
) -> PoissonSolution {
|
||||||
let n = problem.nx * problem.ny;
|
let n = problem.nx * problem.ny;
|
||||||
assert_eq!(p.len(), n, "p must have nx*ny entries");
|
assert_eq!(p.len(), n, "p must have nx*ny entries");
|
||||||
@@ -658,8 +762,9 @@ pub fn solve_multigrid_pcg(
|
|||||||
problem.validate()
|
problem.validate()
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut hier = Hierarchy::build(problem, params);
|
let mut hier = Hierarchy::<T>::build(problem, params);
|
||||||
let cells: Vec<usize> = hier.levels[0].cells.clone();
|
let fine = Level::<f64>::new(problem.clone());
|
||||||
|
let cells: Vec<usize> = fine.cells.clone();
|
||||||
let active_n = cells.len();
|
let active_n = cells.len();
|
||||||
if active_n == 0 {
|
if active_n == 0 {
|
||||||
return PoissonSolution {
|
return PoissonSolution {
|
||||||
@@ -710,9 +815,9 @@ pub fn solve_multigrid_pcg(
|
|||||||
let mut q = vec![0.0; n];
|
let mut q = vec![0.0; n];
|
||||||
|
|
||||||
let true_residual =
|
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| {
|
let finish = |p: &mut [f64], iterations: usize, residual: f64| {
|
||||||
// Level of each singular component: the anchor's component is
|
// Level of each singular component: the anchor's component is
|
||||||
// shifted so p[anchor] == 0, every other singular component to mean
|
// 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 {
|
if res < tolerance {
|
||||||
return finish(p, 0, res);
|
return finish(p, 0, res);
|
||||||
}
|
}
|
||||||
@@ -759,12 +864,12 @@ pub fn solve_multigrid_pcg(
|
|||||||
let mut last_true = res;
|
let mut last_true = res;
|
||||||
while iterations < params.max_iterations {
|
while iterations < params.max_iterations {
|
||||||
iterations += 1;
|
iterations += 1;
|
||||||
hier.levels[0].apply(&d, &mut q);
|
fine.apply(&d, &mut q);
|
||||||
let dq = dot(&d, &q);
|
let dq = dot(&d, &q);
|
||||||
if !dq.is_finite() || dq <= 0.0 || !rz.is_finite() || rz <= 0.0 {
|
if !dq.is_finite() || dq <= 0.0 || !rz.is_finite() || rz <= 0.0 {
|
||||||
// Breakdown (r = 0 to rounding, or a non-positive curvature
|
// Breakdown (r = 0 to rounding, or a non-positive curvature
|
||||||
// from rounding in the null space): stop on the true residual.
|
// 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);
|
return finish(p, iterations, res);
|
||||||
}
|
}
|
||||||
let alpha = rz / dq;
|
let alpha = rz / dq;
|
||||||
@@ -775,7 +880,7 @@ pub fn solve_multigrid_pcg(
|
|||||||
if l1(&r) < tolerance {
|
if l1(&r) < tolerance {
|
||||||
// The recurrence residual passed: confirm against the TRUE
|
// The recurrence residual passed: confirm against the TRUE
|
||||||
// residual, and resynchronise if rounding has let them drift.
|
// 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 {
|
if res < tolerance || res > 0.9 * last_true {
|
||||||
return finish(p, iterations, res);
|
return finish(p, iterations, res);
|
||||||
}
|
}
|
||||||
@@ -792,7 +897,7 @@ pub fn solve_multigrid_pcg(
|
|||||||
d[idx] = z[idx] + beta * d[idx];
|
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)
|
finish(p, iterations, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -446,7 +446,7 @@ fn galerkin_coarse_operator_matches_r_a_p() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pr.validate().expect("weighted problem is valid");
|
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());
|
assert!(hier.depth() >= 3, "depth {}", hier.depth());
|
||||||
let mut g = Lcg(5);
|
let mut g = Lcg(5);
|
||||||
for l in 0..hier.depth() - 1 {
|
for l in 0..hier.depth() - 1 {
|
||||||
@@ -570,7 +570,7 @@ fn v_cycle_preconditioner_is_symmetric() {
|
|||||||
),
|
),
|
||||||
("dirichlet", assemble(n, n, |_, _| true, [true; 4])),
|
("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 mut g = Lcg(11);
|
||||||
let cells = active_indices(&pr);
|
let cells = active_indices(&pr);
|
||||||
let mut x = vec![0.0; n * n];
|
let mut x = vec![0.0; n * n];
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ async fn march(
|
|||||||
.unwrap_or(d)
|
.unwrap_or(d)
|
||||||
};
|
};
|
||||||
let dt = env("RTX_CURV_DTFRAC", 0.4) * (h * h / (4.0 * nu)).min(h);
|
let dt = env("RTX_CURV_DTFRAC", 0.4) * (h * h / (4.0 * nu)).min(h);
|
||||||
let tol = env("RTX_CURV_TOL", 1e-10);
|
let tol = env("RTX_CURV_TOL", 1e-5);
|
||||||
let config = CfdConfig::new()
|
let config = CfdConfig::new()
|
||||||
.with_density(RHO)
|
.with_density(RHO)
|
||||||
.with_viscosity(MU)
|
.with_viscosity(MU)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ async fn steady(mesh: PatchMesh, diffusion: NormalDiffusion, dt_factor: f64) ->
|
|||||||
let dt = dt_factor * 0.4 * h * h / (4.0 * MU);
|
let dt = dt_factor * 0.4 * h * h / (4.0 * MU);
|
||||||
let config = CfdConfig::new().with_density(1.0).with_viscosity(MU);
|
let config = CfdConfig::new().with_density(1.0).with_viscosity(MU);
|
||||||
let params = CurvilinearParameters {
|
let params = CurvilinearParameters {
|
||||||
tolerance: 1e-11,
|
tolerance: 1e-6,
|
||||||
normal_diffusion: diffusion,
|
normal_diffusion: diffusion,
|
||||||
..CurvilinearParameters::default()
|
..CurvilinearParameters::default()
|
||||||
};
|
};
|
||||||
@@ -72,9 +72,20 @@ async fn steady(mesh: PatchMesh, diffusion: NormalDiffusion, dt_factor: f64) ->
|
|||||||
solver.initialize(&mut field, |_, _| (0.0, 0.0));
|
solver.initialize(&mut field, |_, _| (0.0, 0.0));
|
||||||
// Mass defect at the steady state, relative to the largest face flux
|
// Mass defect at the steady state, relative to the largest face flux
|
||||||
// (during the transient it is the pressure solver's residual).
|
// (during the transient it is the pressure solver's residual).
|
||||||
|
let env = |k: &str, d: f64| {
|
||||||
|
std::env::var(k)
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(d)
|
||||||
|
};
|
||||||
|
// 1e-9: the rounding floor of |du/dt| sits at ~2e-11 on the 32² channels
|
||||||
|
// (measured: 0 pressure iterations, divergence 1e-16) and the gates are
|
||||||
|
// at 1e-8.
|
||||||
|
let steady_tol = env("RTX_CURV_STEADY", 1e-9);
|
||||||
|
let trace = std::env::var("RTX_CURV_TRACE").is_ok();
|
||||||
let mut worst_mass = 0.0_f64;
|
let mut worst_mass = 0.0_f64;
|
||||||
let mut converged = false;
|
let mut converged = false;
|
||||||
for _ in 0..2_000_000 {
|
for step in 0..2_000_000 {
|
||||||
let before = field.u.clone();
|
let before = field.u.clone();
|
||||||
let r = solver.advance(&mut field, dt).await?;
|
let r = solver.advance(&mut field, dt).await?;
|
||||||
assert!(r.poisson_converged, "{r:?}");
|
assert!(r.poisson_converged, "{r:?}");
|
||||||
@@ -86,7 +97,16 @@ async fn steady(mesh: PatchMesh, diffusion: NormalDiffusion, dt_factor: f64) ->
|
|||||||
.zip(&before)
|
.zip(&before)
|
||||||
.map(|(a, b)| (a - b).abs())
|
.map(|(a, b)| (a - b).abs())
|
||||||
.fold(0.0, f64::max);
|
.fold(0.0, f64::max);
|
||||||
if change / dt < 1e-11 {
|
if trace && step % 50_000 == 0 {
|
||||||
|
println!(
|
||||||
|
" step {step} t={:.2} |du/dt| {:.3e} iters {} div {:.2e}",
|
||||||
|
solver.time(),
|
||||||
|
change / dt,
|
||||||
|
r.poisson_iterations,
|
||||||
|
r.max_divergence
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if change / dt < steady_tol {
|
||||||
converged = true;
|
converged = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -155,9 +175,15 @@ async fn cartesian_and_sheared_channels_hit_the_discrete_profile_exactly() -> Cf
|
|||||||
async fn varying_skew_channel_converges_to_the_parabola_at_second_order() -> CfdResult<()> {
|
async fn varying_skew_channel_converges_to_the_parabola_at_second_order() -> CfdResult<()> {
|
||||||
let mut errs = Vec::new();
|
let mut errs = Vec::new();
|
||||||
let mut vs = Vec::new();
|
let mut vs = Vec::new();
|
||||||
|
let only: Option<usize> = std::env::var("RTX_CURV_N")
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.parse().ok());
|
||||||
for n in [16usize, 32, 64] {
|
for n in [16usize, 32, 64] {
|
||||||
|
if only.is_some_and(|o| o != n) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let s = steady(
|
let s = steady(
|
||||||
channel_varying_skew(1.0, 1.0, n, n, 0.3, 2.0, true)?,
|
channel_varying_skew(1.0, 1.0, n, n, 0.1, 2.0, true)?,
|
||||||
NormalDiffusion::Explicit,
|
NormalDiffusion::Explicit,
|
||||||
1.0,
|
1.0,
|
||||||
)
|
)
|
||||||
@@ -179,6 +205,8 @@ async fn varying_skew_channel_converges_to_the_parabola_at_second_order() -> Cfd
|
|||||||
let o: Vec<f64> = errs.windows(2).map(|p| (p[0] / p[1]).log2()).collect();
|
let o: Vec<f64> = errs.windows(2).map(|p| (p[0] / p[1]).log2()).collect();
|
||||||
let ov: Vec<f64> = vs.windows(2).map(|p| (p[0] / p[1]).log2()).collect();
|
let ov: Vec<f64> = vs.windows(2).map(|p| (p[0] / p[1]).log2()).collect();
|
||||||
println!("varying skew orders u {o:?}, v {ov:?}");
|
println!("varying skew orders u {o:?}, v {ov:?}");
|
||||||
|
if only.is_none() {
|
||||||
assert!(o.iter().all(|&x| x >= 1.8), "orders {o:?}");
|
assert!(o.iter().all(|&x| x >= 1.8), "orders {o:?}");
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ async fn run(moving: bool, dt: f64, t_end: f64) -> CfdResult<Vec<Record>> {
|
|||||||
corrector_steps: 2,
|
corrector_steps: 2,
|
||||||
tolerance: 1e-8,
|
tolerance: 1e-8,
|
||||||
poisson_solver: PoissonSolverKind::Multigrid,
|
poisson_solver: PoissonSolverKind::Multigrid,
|
||||||
|
poisson_precision: rtx_cfd::solvers::incompressible::MgPrecision::F64,
|
||||||
..EmbeddedParameters::default()
|
..EmbeddedParameters::default()
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ fn solver(n: usize) -> CfdResult<EmbeddedPisoSolver> {
|
|||||||
corrector_steps: 2,
|
corrector_steps: 2,
|
||||||
tolerance: 1e-8,
|
tolerance: 1e-8,
|
||||||
poisson_solver: PoissonSolverKind::Multigrid,
|
poisson_solver: PoissonSolverKind::Multigrid,
|
||||||
|
poisson_precision: rtx_cfd::solvers::incompressible::MgPrecision::F64,
|
||||||
..EmbeddedParameters::default()
|
..EmbeddedParameters::default()
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -30,8 +30,8 @@
|
|||||||
|
|
||||||
use rtx_cfd::solvers::incompressible::{
|
use rtx_cfd::solvers::incompressible::{
|
||||||
AleBoundaries, BoundaryConditions, EmbeddedBody, EmbeddedParameters, EmbeddedPisoSolver,
|
AleBoundaries, BoundaryConditions, EmbeddedBody, EmbeddedParameters, EmbeddedPisoSolver,
|
||||||
FaceKind, FlowField, IncompressibleSolver, PisoParameters, PisoSolver, PoissonSolverKind,
|
FaceKind, FlowField, IncompressibleSolver, MgPrecision, PisoParameters, PisoSolver,
|
||||||
SideBoundary,
|
PoissonSolverKind, SideBoundary,
|
||||||
};
|
};
|
||||||
use rtx_cfd::{CfdConfig, CfdResult};
|
use rtx_cfd::{CfdConfig, CfdResult};
|
||||||
use std::f64::consts::PI;
|
use std::f64::consts::PI;
|
||||||
@@ -128,6 +128,7 @@ async fn piso_mms(n: usize, kind: PoissonSolverKind) -> CfdResult<SteadyMeasurem
|
|||||||
time_step: dt,
|
time_step: dt,
|
||||||
tolerance: 1e-8,
|
tolerance: 1e-8,
|
||||||
poisson_solver: kind,
|
poisson_solver: kind,
|
||||||
|
poisson_precision: rtx_cfd::solvers::incompressible::MgPrecision::F64,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
solver.set_momentum_source(source);
|
solver.set_momentum_source(source);
|
||||||
@@ -291,6 +292,7 @@ async fn taylor_green(n: usize, kind: PoissonSolverKind) -> CfdResult<TaylorGree
|
|||||||
time_step: dt,
|
time_step: dt,
|
||||||
tolerance: 1e-9,
|
tolerance: 1e-9,
|
||||||
poisson_solver: kind,
|
poisson_solver: kind,
|
||||||
|
poisson_precision: rtx_cfd::solvers::incompressible::MgPrecision::F64,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -418,6 +420,16 @@ fn mms_initial_field(n: usize) -> CfdResult<FlowField> {
|
|||||||
/// `tests/embedded_mms.rs::measure`, velocity error and divergence only,
|
/// `tests/embedded_mms.rs::measure`, velocity error and divergence only,
|
||||||
/// with the inner solver selectable.
|
/// with the inner solver selectable.
|
||||||
async fn embedded_mms(n: usize, kind: PoissonSolverKind) -> CfdResult<SteadyMeasurement> {
|
async fn embedded_mms(n: usize, kind: PoissonSolverKind) -> CfdResult<SteadyMeasurement> {
|
||||||
|
embedded_mms_with(n, kind, MgPrecision::F64).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `embedded_mms` with the multigrid V-cycle precision selectable (the M1
|
||||||
|
/// precision probe of `overset_metal_campaign.md` §3.2).
|
||||||
|
async fn embedded_mms_with(
|
||||||
|
n: usize,
|
||||||
|
kind: PoissonSolverKind,
|
||||||
|
precision: MgPrecision,
|
||||||
|
) -> CfdResult<SteadyMeasurement> {
|
||||||
let dx = 1.0 / n as f64;
|
let dx = 1.0 / n as f64;
|
||||||
let dt = mms_time_step(n);
|
let dt = mms_time_step(n);
|
||||||
let mut solver = EmbeddedPisoSolver::new(
|
let mut solver = EmbeddedPisoSolver::new(
|
||||||
@@ -426,6 +438,7 @@ async fn embedded_mms(n: usize, kind: PoissonSolverKind) -> CfdResult<SteadyMeas
|
|||||||
corrector_steps: 2,
|
corrector_steps: 2,
|
||||||
tolerance: 1e-8,
|
tolerance: 1e-8,
|
||||||
poisson_solver: kind,
|
poisson_solver: kind,
|
||||||
|
poisson_precision: precision,
|
||||||
..EmbeddedParameters::default()
|
..EmbeddedParameters::default()
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
@@ -526,6 +539,44 @@ async fn embedded_circle_steady_state_is_solver_independent() -> CfdResult<()> {
|
|||||||
/// solver assembles exactly the fixed-grid PISO's `PoissonProblem` (all
|
/// solver assembles exactly the fixed-grid PISO's `PoissonProblem` (all
|
||||||
/// cells active, anchor `(1, 1)`, no outlet), so the fields must still agree
|
/// cells active, anchor `(1, 1)`, no outlet), so the fields must still agree
|
||||||
/// to the bit.
|
/// to the bit.
|
||||||
|
/// M1 precision probe (`overset_metal_campaign.md` §3.2 M1, §5.2): with
|
||||||
|
/// the V-cycle in single precision inside the f64 conjugate gradient, the
|
||||||
|
/// embedded-circle steady state must be the same field to the projection
|
||||||
|
/// stop — the f64 CG owns the true residual, so the preconditioner's
|
||||||
|
/// precision may cost iterations, never accuracy. The measured gap is
|
||||||
|
/// printed; the assert is at the tolerance scale, not at f32 eps.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn embedded_circle_steady_state_survives_an_f32_vcycle() -> CfdResult<()> {
|
||||||
|
let n = 32;
|
||||||
|
let f64_arm = embedded_mms_with(n, PoissonSolverKind::Multigrid, MgPrecision::F64).await?;
|
||||||
|
let f32_arm = embedded_mms_with(n, PoissonSolverKind::Multigrid, MgPrecision::F32).await?;
|
||||||
|
println!(
|
||||||
|
"f32 V-cycle vs f64: L2 error {:.6e} vs {:.6e} (rel {:.2e}), max div {:.2e} vs {:.2e}, \
|
||||||
|
steps {} vs {}, wall {:.2} s vs {:.2} s",
|
||||||
|
f32_arm.l2_velocity,
|
||||||
|
f64_arm.l2_velocity,
|
||||||
|
rel(f32_arm.l2_velocity, f64_arm.l2_velocity),
|
||||||
|
f32_arm.max_div,
|
||||||
|
f64_arm.max_div,
|
||||||
|
f32_arm.steps,
|
||||||
|
f64_arm.steps,
|
||||||
|
f32_arm.seconds,
|
||||||
|
f64_arm.seconds
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
rel(f32_arm.l2_velocity, f64_arm.l2_velocity) < 1e-5,
|
||||||
|
"f32 V-cycle changed the steady error: {:.6e} vs {:.6e}",
|
||||||
|
f32_arm.l2_velocity,
|
||||||
|
f64_arm.l2_velocity
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
f32_arm.max_div < 1e-5,
|
||||||
|
"divergence with the f32 V-cycle {:.3e}",
|
||||||
|
f32_arm.max_div
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn without_a_body_the_embedded_solver_is_piso_to_the_bit_with_multigrid() -> CfdResult<()> {
|
async fn without_a_body_the_embedded_solver_is_piso_to_the_bit_with_multigrid() -> CfdResult<()> {
|
||||||
let n = 16;
|
let n = 16;
|
||||||
@@ -537,6 +588,7 @@ async fn without_a_body_the_embedded_solver_is_piso_to_the_bit_with_multigrid()
|
|||||||
time_step: dt,
|
time_step: dt,
|
||||||
tolerance: 1e-8,
|
tolerance: 1e-8,
|
||||||
poisson_solver: PoissonSolverKind::Multigrid,
|
poisson_solver: PoissonSolverKind::Multigrid,
|
||||||
|
poisson_precision: rtx_cfd::solvers::incompressible::MgPrecision::F64,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
piso.set_momentum_source(source);
|
piso.set_momentum_source(source);
|
||||||
@@ -547,6 +599,7 @@ async fn without_a_body_the_embedded_solver_is_piso_to_the_bit_with_multigrid()
|
|||||||
corrector_steps: 2,
|
corrector_steps: 2,
|
||||||
tolerance: 1e-8,
|
tolerance: 1e-8,
|
||||||
poisson_solver: PoissonSolverKind::Multigrid,
|
poisson_solver: PoissonSolverKind::Multigrid,
|
||||||
|
poisson_precision: rtx_cfd::solvers::incompressible::MgPrecision::F64,
|
||||||
..EmbeddedParameters::default()
|
..EmbeddedParameters::default()
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
@@ -611,6 +664,7 @@ async fn channel_with_circle(kind: PoissonSolverKind) -> CfdResult<(FlowField, u
|
|||||||
top: SideBoundary::Velocity,
|
top: SideBoundary::Velocity,
|
||||||
},
|
},
|
||||||
poisson_solver: kind,
|
poisson_solver: kind,
|
||||||
|
poisson_precision: rtx_cfd::solvers::incompressible::MgPrecision::F64,
|
||||||
..EmbeddedParameters::default()
|
..EmbeddedParameters::default()
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ async fn run_cfd1(ny: usize) -> CfdResult<Cfd1> {
|
|||||||
top: SideBoundary::Velocity,
|
top: SideBoundary::Velocity,
|
||||||
},
|
},
|
||||||
poisson_solver: PoissonSolverKind::Multigrid,
|
poisson_solver: PoissonSolverKind::Multigrid,
|
||||||
|
poisson_precision: rtx_cfd::solvers::incompressible::MgPrecision::F64,
|
||||||
..EmbeddedParameters::default()
|
..EmbeddedParameters::default()
|
||||||
};
|
};
|
||||||
let mut solver = EmbeddedPisoSolver::new(config, params)?;
|
let mut solver = EmbeddedPisoSolver::new(config, params)?;
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ impl Runner {
|
|||||||
top: SideBoundary::Velocity,
|
top: SideBoundary::Velocity,
|
||||||
},
|
},
|
||||||
poisson_solver: PoissonSolverKind::Multigrid,
|
poisson_solver: PoissonSolverKind::Multigrid,
|
||||||
|
poisson_precision: rtx_cfd::solvers::incompressible::MgPrecision::F64,
|
||||||
// Upwind's numerical viscosity (|u| h / 2 ~ 10x the physical nu
|
// Upwind's numerical viscosity (|u| h / 2 ~ 10x the physical nu
|
||||||
// on these grids) suppressed CFD3's vortex shedding entirely:
|
// on these grids) suppressed CFD3's vortex shedding entirely:
|
||||||
// the ny = 41 upwind run produced ONE lift zero-crossing in
|
// the ny = 41 upwind run produced ONE lift zero-crossing in
|
||||||
|
|||||||
@@ -122,6 +122,10 @@ pub struct MarchConfig {
|
|||||||
/// is repeated as 2, 4, 8, 16, 32 coupled substeps of dt/n — see
|
/// is repeated as 2, 4, 8, 16, 32 coupled substeps of dt/n — see
|
||||||
/// `rescue.rs` and omni-cortex `docs/coupling_rescue_campaign.md`.
|
/// `rescue.rs` and omni-cortex `docs/coupling_rescue_campaign.md`.
|
||||||
pub coupling_rescue: bool,
|
pub coupling_rescue: bool,
|
||||||
|
/// M1 precision probe (`RTX_{prefix}_POISSON_F32`, default off =
|
||||||
|
/// bit-identical): the pressure multigrid's V-cycle in single
|
||||||
|
/// precision inside the f64 CG (`overset_metal_campaign.md` §3.2 M1).
|
||||||
|
pub poisson_f32: bool,
|
||||||
/// Rung C (`RTX_{prefix}_CRESCUE_COARSE`, default 0 = off; needs
|
/// Rung C (`RTX_{prefix}_CRESCUE_COARSE`, default 0 = off; needs
|
||||||
/// `coupling_rescue`): on a trigger, instead of the substep ladder,
|
/// `coupling_rescue`): on a trigger, instead of the substep ladder,
|
||||||
/// reject the step and enter a coarse EPISODE of this many coupled
|
/// reject the step and enter a coarse EPISODE of this many coupled
|
||||||
@@ -208,6 +212,7 @@ impl MarchConfig {
|
|||||||
.and_then(|v| v.parse::<usize>().ok())
|
.and_then(|v| v.parse::<usize>().ok())
|
||||||
.unwrap_or(defaults.trace_from),
|
.unwrap_or(defaults.trace_from),
|
||||||
coupling_rescue: num("CRESCUE", f64::from(u8::from(defaults.coupling_rescue))) != 0.0,
|
coupling_rescue: num("CRESCUE", f64::from(u8::from(defaults.coupling_rescue))) != 0.0,
|
||||||
|
poisson_f32: num("POISSON_F32", f64::from(u8::from(defaults.poisson_f32))) != 0.0,
|
||||||
coarse_episode: num("CRESCUE_COARSE", defaults.coarse_episode as f64) as usize,
|
coarse_episode: num("CRESCUE_COARSE", defaults.coarse_episode as f64) as usize,
|
||||||
inc_trace: std::env::var(key("INCTRACE")).ok().or(defaults.inc_trace),
|
inc_trace: std::env::var(key("INCTRACE")).ok().or(defaults.inc_trace),
|
||||||
increment_factor: num("CRESCUE_INC", defaults.increment_factor),
|
increment_factor: num("CRESCUE_INC", defaults.increment_factor),
|
||||||
@@ -363,6 +368,7 @@ pub fn run_march(case: BenchmarkCase, config: &MarchConfig) -> MarchResult {
|
|||||||
trace_steps,
|
trace_steps,
|
||||||
trace_from,
|
trace_from,
|
||||||
coupling_rescue,
|
coupling_rescue,
|
||||||
|
poisson_f32,
|
||||||
coarse_episode,
|
coarse_episode,
|
||||||
ref inc_trace,
|
ref inc_trace,
|
||||||
increment_factor,
|
increment_factor,
|
||||||
@@ -374,6 +380,10 @@ pub fn run_march(case: BenchmarkCase, config: &MarchConfig) -> MarchResult {
|
|||||||
|
|
||||||
let (harness, mut solver, mut field) = Fsi2Harness::build_case(case, ny, flag_nx, smooth_in_h);
|
let (harness, mut solver, mut field) = Fsi2Harness::build_case(case, ny, flag_nx, smooth_in_h);
|
||||||
solver.set_mask_hysteresis(mask_hysteresis);
|
solver.set_mask_hysteresis(mask_hysteresis);
|
||||||
|
if poisson_f32 {
|
||||||
|
solver.set_poisson_precision(rtx_cfd::solvers::incompressible::MgPrecision::F32);
|
||||||
|
println!(" poisson V-cycle precision: F32 (M1 probe)");
|
||||||
|
}
|
||||||
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;
|
||||||
|
|||||||
@@ -438,6 +438,7 @@ impl Fsi2Harness {
|
|||||||
top: SideBoundary::Velocity,
|
top: SideBoundary::Velocity,
|
||||||
},
|
},
|
||||||
poisson_solver: PoissonSolverKind::Multigrid,
|
poisson_solver: PoissonSolverKind::Multigrid,
|
||||||
|
poisson_precision: rtx_cfd::solvers::incompressible::MgPrecision::F64,
|
||||||
// TVD, deliberately: FSI2 marches in time and needs the
|
// TVD, deliberately: FSI2 marches in time and needs the
|
||||||
// shedding physics upwind's numerical viscosity killed on
|
// shedding physics upwind's numerical viscosity killed on
|
||||||
// these grids (CFD3's finding). The limiter chatter that
|
// these grids (CFD3's finding). The limiter chatter that
|
||||||
|
|||||||
@@ -99,6 +99,13 @@ fn fsi2_interface_noise_floor() {
|
|||||||
_ => fsi2_harness::FSI2,
|
_ => fsi2_harness::FSI2,
|
||||||
};
|
};
|
||||||
let (mut harness, mut solver, mut field) = Fsi2Harness::build_case(case, ny, flag_nx, 0.0);
|
let (mut harness, mut solver, mut field) = Fsi2Harness::build_case(case, ny, flag_nx, 0.0);
|
||||||
|
// M1 precision probe (`RTX_FSI2_POISSON_F32=1`): the pressure
|
||||||
|
// multigrid's V-cycle in f32 inside the f64 CG. Printed so the arm can
|
||||||
|
// never pass vacuously.
|
||||||
|
if env_or("RTX_FSI2_POISSON_F32", 0.0) != 0.0 {
|
||||||
|
solver.set_poisson_precision(rtx_cfd::solvers::incompressible::MgPrecision::F32);
|
||||||
|
println!(" poisson V-cycle precision: F32 (M1 probe)");
|
||||||
|
}
|
||||||
let dt_fluid = harness.dt_fluid;
|
let dt_fluid = harness.dt_fluid;
|
||||||
|
|
||||||
// Rigid march to operating loads (the floor rides with the loads —
|
// Rigid march to operating loads (the floor rides with the loads —
|
||||||
|
|||||||
@@ -323,6 +323,7 @@ fn fsi1_coupled_cylinder_and_flag() {
|
|||||||
top: SideBoundary::Velocity,
|
top: SideBoundary::Velocity,
|
||||||
},
|
},
|
||||||
poisson_solver: PoissonSolverKind::Multigrid,
|
poisson_solver: PoissonSolverKind::Multigrid,
|
||||||
|
poisson_precision: rtx_cfd::solvers::incompressible::MgPrecision::F64,
|
||||||
// Upwind, deliberately: FSI1 is a steady FIXED-POINT problem, and
|
// Upwind, deliberately: FSI1 is a steady FIXED-POINT problem, and
|
||||||
// the TVD limiter's switching keeps the steady load chattering by
|
// the TVD limiter's switching keeps the steady load chattering by
|
||||||
// ~0.5% (a known property of limited schemes — they stall short of
|
// ~0.5% (a known property of limited schemes — they stall short of
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ fn fsi2_flapping_flag() {
|
|||||||
trace_steps: 0,
|
trace_steps: 0,
|
||||||
trace_from: usize::MAX,
|
trace_from: usize::MAX,
|
||||||
coupling_rescue: false,
|
coupling_rescue: false,
|
||||||
|
poisson_f32: false,
|
||||||
coarse_episode: 0,
|
coarse_episode: 0,
|
||||||
inc_trace: None,
|
inc_trace: None,
|
||||||
increment_factor: 0.0,
|
increment_factor: 0.0,
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ fn fsi3_added_mass_flag() {
|
|||||||
trace_steps: 0,
|
trace_steps: 0,
|
||||||
trace_from: usize::MAX,
|
trace_from: usize::MAX,
|
||||||
coupling_rescue: false,
|
coupling_rescue: false,
|
||||||
|
poisson_f32: false,
|
||||||
coarse_episode: 0,
|
coarse_episode: 0,
|
||||||
inc_trace: None,
|
inc_trace: None,
|
||||||
increment_factor: 0.0,
|
increment_factor: 0.0,
|
||||||
|
|||||||
Reference in New Issue
Block a user