rtx-cfd: overset A-P2 — the patch overlaps the background (OversetPisoSolver), gated S1–S5
CI / Test (macos-latest) (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (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 (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
Documentation / Build User Guide (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
Documentation / Build API Documentation (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (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 (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
Documentation / Build User Guide (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
Documentation / Build API Documentation (push) Canceled after 0s
Background = the embedded solver with a mask from the overlap classification
(embedded/{mod,projection}.rs: module split, projection's solve/apply halves,
set_overlap, fringe p' Dirichlet by elimination into extra_diag/rhs, anchor
dropped, set_inner_stop_factor, phase API begin_step/solve_correction/
apply_correction/end_step; advance rebuilt on the phases — every suite digit-
identical, FSI2 default line-for-line). Patch = the curvilinear solver with an
acceptor ring (set_side_velocity; set_acceptor_ring/stamp_acceptors/
set_acceptor_correction; acceptor Dirichlet by elimination into
PressureSystem.links so the BiCGSTAB stop stays in flux units — identity rows
measured unconverged at 2431 iterations; same phase API). overset/overlap.rs:
OverlapMap — hole/fringe/active from the patch's own indices (hole = body or
k <= nn-1-overlap_rows, DEFAULT_OVERLAP_ROWS = 4 from the 2.9 h depth budget),
dual-quad inverse-bilinear donors patch→fringe, lattice donors →acceptors,
both invariants asserted, mass-defect measures. overset/mod.rs:
OversetPisoSolver — advance (exchange rebuilt BEFORE the predictors from the
previous corrected field), alternating Schwarz on the acceptor p' vector with
Anderson(3) (plain Schwarz measured 0.82/round: floating patch, Neumann wall)
and the previous step's vector as warm start (1 round/corrector at steady
state), stop relative to the STEP's p' scale (the MG absolute stop is
1e-9/dt² in pressure — the whole second correction), set_patch_mesh,
snapshot/restore carrying the warm-start vector.
Gates: overlap linear-exact 1e-13, quadratic orders 1.96/1.99 (acceptors),
1.40/1.91 (fringe); half-couplings: patch with exact acceptors Stokes 2.07/1.98
+ 2.08/1.98, upwind 0.84/0.84, background with exact fringe 7.86e-3/2.90e-3/
1.09e-3 (1.44/1.41); two-mesh MMS n=32/64: background 8.717e-3/4.207e-3 (1.03x/
0.97x the embedded circle), patch 1.322e-2/6.904e-3 (1.5-1.6x), orders 1.05/
0.94, patch div <= 5e-13, overlap mass defect 3.6e-3 -> 8.2e-4 of the overlap
flux (under the registered 1e-3 from n=64; disclosed at 32); motion: stationary
patch through set_patch_mesh bit-identical, snapshot/restore with a pending mesh
bit-identical, translating phantom circle 1.22x/1.19x the static level over
4.5 cells. Inherited, disclosed: poisson_equivalence's no-body multigrid pin
fails by 3.9e-9 at d46fb0b (M1's commit; verified in a clean worktree).
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
d46fb0b7a7
commit
afd1bff6ee
@@ -3,7 +3,7 @@
|
||||
//! pressure-correction equation on the 9-point operator, and the flux and
|
||||
//! velocity corrections.
|
||||
|
||||
use super::{CurvilinearPisoSolver, PatchField, SideBc, StepGeometry};
|
||||
use super::{CurvilinearPisoSolver, PatchField, PressureSystem, SideBc, StepGeometry};
|
||||
use crate::mesh::PatchSide;
|
||||
use crate::solvers::incompressible::sparse_bicgstab::{
|
||||
BicgstabResult, CsrMatrix, bicgstab_jacobi, project_mean,
|
||||
@@ -77,10 +77,21 @@ impl CurvilinearPisoSolver {
|
||||
}
|
||||
_ => {
|
||||
let c = mesh.boundary_cell(f);
|
||||
match self.params.boundaries.get(mesh.side(f).expect("boundary")) {
|
||||
let side = mesh.side(f).expect("boundary");
|
||||
if side == PatchSide::Outer && self.acceptors.is_some() {
|
||||
// The acceptor ring's outer faces: the stamped
|
||||
// velocity's own flux (read only by the overlap
|
||||
// mass-defect measure).
|
||||
return uh[c] * s[0] + vh[c] * s[1];
|
||||
}
|
||||
match self.params.boundaries.get(side) {
|
||||
SideBc::Velocity => {
|
||||
let (ub, vb) =
|
||||
self.boundary_velocity(face.centre[0], face.centre[1], t_new);
|
||||
let (ub, vb) = self.boundary_velocity(
|
||||
side,
|
||||
face.centre[0],
|
||||
face.centre[1],
|
||||
t_new,
|
||||
);
|
||||
ub * s[0] + vb * s[1]
|
||||
}
|
||||
SideBc::Outlet => uh[c] * s[0] + vh[c] * s[1] - dt / rho * lp[f],
|
||||
@@ -105,7 +116,7 @@ impl CurvilinearPisoSolver {
|
||||
]
|
||||
.iter()
|
||||
.any(|&s| self.params.boundaries.get(s) == SideBc::Outlet);
|
||||
if has_outlet {
|
||||
if has_outlet || self.acceptors.is_some() {
|
||||
return 0.0;
|
||||
}
|
||||
let (mut net, mut total_len) = (0.0, 0.0);
|
||||
@@ -130,15 +141,22 @@ impl CurvilinearPisoSolver {
|
||||
}
|
||||
|
||||
/// Assemble `−Σ_f sign (dt/ρ) L_f` (positive diagonal) and pick the
|
||||
/// anchor for the pure-Neumann case.
|
||||
pub(super) fn assemble_pressure_matrix(&self, dt: f64) -> (CsrMatrix, Option<usize>) {
|
||||
/// anchor for the pure-Neumann case. Acceptor cells get identity rows
|
||||
/// (their `p'` is Dirichlet) and the interior rows' couplings to them
|
||||
/// are recorded as links, eliminated at solve time.
|
||||
pub(super) fn assemble_pressure_matrix(&self, dt: f64) -> PressureSystem {
|
||||
let mesh = &self.mesh;
|
||||
let rho = self.config.density;
|
||||
let n = mesh.cell_count();
|
||||
let mut tri = Vec::with_capacity(n * 12);
|
||||
let mut links = Vec::new();
|
||||
let mut coefs = Vec::new();
|
||||
let mut any_dirichlet = false;
|
||||
let mut any_dirichlet = self.acceptors.is_some();
|
||||
for c in 0..n {
|
||||
if self.is_acceptor(c) {
|
||||
tri.push((c, c, 1.0));
|
||||
continue;
|
||||
}
|
||||
for (f, sign) in mesh.cell_faces(c) {
|
||||
self.ops
|
||||
.face_gradient_coeffs(mesh, &self.params.boundaries, f, &mut coefs);
|
||||
@@ -146,53 +164,88 @@ impl CurvilinearPisoSolver {
|
||||
any_dirichlet = true;
|
||||
}
|
||||
for &(col, v) in &coefs {
|
||||
tri.push((c, col, -sign * dt / rho * v));
|
||||
let coef = -sign * dt / rho * v;
|
||||
if self.is_acceptor(col) {
|
||||
links.push((c, col, coef));
|
||||
} else {
|
||||
tri.push((c, col, coef));
|
||||
}
|
||||
}
|
||||
}
|
||||
tri.push((c, c, 0.0)); // guarantee a diagonal entry
|
||||
}
|
||||
let mut a = CsrMatrix::from_triplets(n, &tri);
|
||||
let mut matrix = CsrMatrix::from_triplets(n, &tri);
|
||||
let anchor = if any_dirichlet {
|
||||
None
|
||||
} else {
|
||||
// An interior cell away from the seam: (1, 1).
|
||||
let a_cell = mesh.cell(1.min(mesh.nn() - 1), 1.min(mesh.ns() - 1));
|
||||
a.set_row_identity(a_cell);
|
||||
matrix.set_row_identity(a_cell);
|
||||
Some(a_cell)
|
||||
};
|
||||
(a, anchor)
|
||||
PressureSystem {
|
||||
dt,
|
||||
matrix,
|
||||
anchor,
|
||||
links,
|
||||
}
|
||||
}
|
||||
|
||||
/// Solve `−Σ sign (dt/ρ) L_f(p') = −Σ sign F` for `p'` (zero start).
|
||||
pub(super) fn solve_pressure_correction(
|
||||
&self,
|
||||
matrix: &CsrMatrix,
|
||||
anchor: Option<usize>,
|
||||
flux: &[f64],
|
||||
tolerance: f64,
|
||||
) -> (Vec<f64>, BicgstabResult) {
|
||||
/// The right-hand side `−Σ sign F` on the equation-carrying cells, with
|
||||
/// the acceptor couplings eliminated (`rhs −= coef · p'_acceptor`) and
|
||||
/// zero on acceptor rows; mean-projected and anchored when pure Neumann.
|
||||
/// Also returns its L1 norm BEFORE the projection (the incoming
|
||||
/// imbalance the stop is relative to — the static path's
|
||||
/// `divergence_l1`, unchanged to the bit).
|
||||
pub(super) fn pressure_rhs(&self, system: &PressureSystem, flux: &[f64]) -> (Vec<f64>, f64) {
|
||||
let mesh = &self.mesh;
|
||||
let n = mesh.cell_count();
|
||||
let mut rhs = vec![0.0; n];
|
||||
for c in 0..n {
|
||||
if self.is_acceptor(c) {
|
||||
continue;
|
||||
}
|
||||
let mut div = 0.0;
|
||||
for (f, sign) in mesh.cell_faces(c) {
|
||||
div += sign * flux[f];
|
||||
}
|
||||
rhs[c] = -div;
|
||||
}
|
||||
if let Some(a) = anchor {
|
||||
for &(row, acc, coef) in &system.links {
|
||||
rhs[row] -= coef * self.acceptor_correction(acc).unwrap_or(0.0);
|
||||
}
|
||||
let incoming: f64 = rhs.iter().map(|r| r.abs()).sum();
|
||||
if let Some(a) = system.anchor {
|
||||
project_mean(&mut rhs);
|
||||
rhs[a] = 0.0;
|
||||
}
|
||||
(rhs, incoming)
|
||||
}
|
||||
|
||||
/// Solve the assembled system for `p'` (zero start); acceptor entries
|
||||
/// are then set to their Dirichlet values.
|
||||
pub(super) fn solve_pressure_correction(
|
||||
&self,
|
||||
system: &PressureSystem,
|
||||
rhs: Vec<f64>,
|
||||
tolerance: f64,
|
||||
) -> (Vec<f64>, BicgstabResult) {
|
||||
let n = self.mesh.cell_count();
|
||||
let mut pc = vec![0.0; n];
|
||||
let out = bicgstab_jacobi(
|
||||
matrix,
|
||||
&system.matrix,
|
||||
&rhs,
|
||||
&mut pc,
|
||||
tolerance,
|
||||
self.params.max_poisson_iterations,
|
||||
);
|
||||
if self.acceptors.is_some() {
|
||||
for c in 0..n {
|
||||
if let Some(v) = self.acceptor_correction(c) {
|
||||
pc[c] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
(pc, out)
|
||||
}
|
||||
|
||||
@@ -205,6 +258,9 @@ impl CurvilinearPisoSolver {
|
||||
field.flux[f] -= dt / rho * lp[f];
|
||||
}
|
||||
for c in 0..mesh.cell_count() {
|
||||
if self.is_acceptor(c) {
|
||||
continue;
|
||||
}
|
||||
let g = self.pressure_gradient(pc, c);
|
||||
field.u[c] -= dt / rho * g[0];
|
||||
field.v[c] -= dt / rho * g[1];
|
||||
@@ -212,10 +268,12 @@ impl CurvilinearPisoSolver {
|
||||
}
|
||||
}
|
||||
|
||||
/// Total cell mass imbalance `Σ_c |Σ_f sign F_f|`.
|
||||
/// Total cell mass imbalance `Σ_c |Σ_f sign F_f|` over the cells that
|
||||
/// carry continuity (acceptors excluded).
|
||||
pub(super) fn divergence_l1(&self, flux: &[f64]) -> f64 {
|
||||
let mesh = &self.mesh;
|
||||
(0..mesh.cell_count())
|
||||
.filter(|&c| !self.is_acceptor(c))
|
||||
.map(|c| {
|
||||
mesh.cell_faces(c)
|
||||
.iter()
|
||||
@@ -226,10 +284,12 @@ impl CurvilinearPisoSolver {
|
||||
.sum()
|
||||
}
|
||||
|
||||
/// Largest cell mass imbalance `|Σ sign F_f|`.
|
||||
/// Largest cell mass imbalance `|Σ sign F_f|` over the cells that
|
||||
/// carry continuity (acceptors excluded).
|
||||
pub(super) fn max_divergence(&self, flux: &[f64]) -> f64 {
|
||||
let mesh = &self.mesh;
|
||||
(0..mesh.cell_count())
|
||||
.filter(|&c| !self.is_acceptor(c))
|
||||
.map(|c| {
|
||||
mesh.cell_faces(c)
|
||||
.iter()
|
||||
|
||||
Reference in New Issue
Block a user