rtx-cfd: curvilinear collocated PISO on a structured patch (overset A-P0, WIP) — PatchMesh (right-handed s,n; periodic seam with shift; face metrics), patch generators (TFI, skewed annulus, sheared/varying-skew channels), CSR + Jacobi-BiCGSTAB, the Zang–Street–Koseff incremental step with the node-based 9-point L_f, LSQ gradients, explicit and line-implicit-n predictors, adjustPhi; tests: mesh metrics (5 green), operators exact on linear fields incl. the seam (green), sparse (2 green), MMS ladder (Cartesian 16/32: 1.37–1.39x the staggered error, order 0.83; n=64 stalls at a |du/dt| floor 2e-4 — open, tolerance-scaling hypothesis), annulus/Poiseuille not yet run
CI / Distributed Training Tests (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 (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 / CI Success (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
CI / Distributed Training Tests (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 (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 / CI Success (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (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
1347bc6772
commit
52da75a3a9
@@ -0,0 +1,223 @@
|
||||
//! The pressure step: face fluxes from the predictor with the compact
|
||||
//! pressure term, boundary-flux adjustment for closed patches, the
|
||||
//! pressure-correction equation on the 9-point operator, and the flux and
|
||||
//! velocity corrections.
|
||||
|
||||
use super::{CurvilinearPisoSolver, PatchField, SideBc};
|
||||
use crate::mesh::PatchSide;
|
||||
use crate::solvers::incompressible::sparse_bicgstab::{
|
||||
BicgstabResult, CsrMatrix, bicgstab_jacobi, project_mean,
|
||||
};
|
||||
|
||||
impl CurvilinearPisoSolver {
|
||||
/// Node values of a pressure-like cell field: zero on outlet sides,
|
||||
/// extrapolated elsewhere.
|
||||
pub(super) fn pressure_nodes(&self, p: &[f64]) -> Vec<f64> {
|
||||
let b = &self.params.boundaries;
|
||||
self.ops
|
||||
.node_values(&self.mesh, p, &|side: PatchSide, _| match b.get(side) {
|
||||
SideBc::Outlet => Some(0.0),
|
||||
SideBc::Velocity => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// `L_f(p)` on every face (zero on Neumann faces, outlet value zero).
|
||||
pub(super) fn pressure_face_gradients(&self, p: &[f64]) -> Vec<f64> {
|
||||
let mesh = &self.mesh;
|
||||
let pn = self.pressure_nodes(p);
|
||||
(0..mesh.faces().len())
|
||||
.map(|f| {
|
||||
let bval = mesh
|
||||
.side(f)
|
||||
.and_then(|s| match self.params.boundaries.get(s) {
|
||||
SideBc::Outlet => Some(0.0),
|
||||
SideBc::Velocity => None,
|
||||
});
|
||||
self.ops.face_gradient_flux(mesh, f, p, &pn, bval)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Least-squares cell gradient of a pressure-like field (outlet faces
|
||||
/// at zero, Neumann faces left out).
|
||||
pub(super) fn pressure_gradient(&self, p: &[f64], c: usize) -> [f64; 2] {
|
||||
let mesh = &self.mesh;
|
||||
self.ops.gradient(mesh, c, p, &|f| match mesh.side(f) {
|
||||
Some(s) if self.params.boundaries.get(s) == SideBc::Outlet => Some(0.0),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// `F* = interp(û)·S − (dt/ρ) L_f(p^n)` on interior and outlet faces,
|
||||
/// the prescribed flux on velocity faces (at `t_new`).
|
||||
pub(super) fn predicted_fluxes(
|
||||
&self,
|
||||
uh: &[f64],
|
||||
vh: &[f64],
|
||||
p: &[f64],
|
||||
dt: f64,
|
||||
t_new: f64,
|
||||
) -> Vec<f64> {
|
||||
let mesh = &self.mesh;
|
||||
let rho = self.config.density;
|
||||
let lp = self.pressure_face_gradients(p);
|
||||
mesh.faces()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(f, face)| match (face.owner, face.neigh) {
|
||||
(Some(o), Some(n)) => {
|
||||
let w = face.w;
|
||||
let uf = w * uh[o] + (1.0 - w) * uh[n];
|
||||
let vf = w * vh[o] + (1.0 - w) * vh[n];
|
||||
uf * face.s[0] + vf * face.s[1] - dt / rho * lp[f]
|
||||
}
|
||||
_ => {
|
||||
let c = mesh.boundary_cell(f);
|
||||
match self.params.boundaries.get(mesh.side(f).expect("boundary")) {
|
||||
SideBc::Velocity => {
|
||||
let (ub, vb) =
|
||||
self.boundary_velocity(face.centre[0], face.centre[1], t_new);
|
||||
ub * face.s[0] + vb * face.s[1]
|
||||
}
|
||||
SideBc::Outlet => uh[c] * face.s[0] + vh[c] * face.s[1] - dt / rho * lp[f],
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// On a patch with no outlet the prescribed boundary fluxes must sum
|
||||
/// to zero for the projection to be solvable; the O(h²) defect of
|
||||
/// face-centre sampling is spread over the velocity faces by area
|
||||
/// (OpenFOAM's `adjustPhi`). Returns the defect removed.
|
||||
pub(super) fn adjust_boundary_flux(&self, flux: &mut [f64]) -> f64 {
|
||||
let mesh = &self.mesh;
|
||||
let has_outlet = [
|
||||
PatchSide::Inner,
|
||||
PatchSide::Outer,
|
||||
PatchSide::SStart,
|
||||
PatchSide::SEnd,
|
||||
]
|
||||
.iter()
|
||||
.any(|&s| self.params.boundaries.get(s) == SideBc::Outlet);
|
||||
if has_outlet {
|
||||
return 0.0;
|
||||
}
|
||||
let (mut net, mut total_len) = (0.0, 0.0);
|
||||
for (f, face) in mesh.faces().iter().enumerate() {
|
||||
if mesh.side(f).is_some() {
|
||||
let out_sign = if face.owner.is_some() { 1.0 } else { -1.0 };
|
||||
net += out_sign * flux[f];
|
||||
total_len += (face.s[0] * face.s[0] + face.s[1] * face.s[1]).sqrt();
|
||||
}
|
||||
}
|
||||
if total_len == 0.0 {
|
||||
return net;
|
||||
}
|
||||
for (f, face) in mesh.faces().iter().enumerate() {
|
||||
if mesh.side(f).is_some() {
|
||||
let out_sign = if face.owner.is_some() { 1.0 } else { -1.0 };
|
||||
let len = (face.s[0] * face.s[0] + face.s[1] * face.s[1]).sqrt();
|
||||
flux[f] -= out_sign * net * len / total_len;
|
||||
}
|
||||
}
|
||||
net
|
||||
}
|
||||
|
||||
/// 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>) {
|
||||
let mesh = &self.mesh;
|
||||
let rho = self.config.density;
|
||||
let n = mesh.cell_count();
|
||||
let mut tri = Vec::with_capacity(n * 12);
|
||||
let mut coefs = Vec::new();
|
||||
let mut any_dirichlet = false;
|
||||
for c in 0..n {
|
||||
for (f, sign) in mesh.cell_faces(c) {
|
||||
self.ops
|
||||
.face_gradient_coeffs(mesh, &self.params.boundaries, f, &mut coefs);
|
||||
if mesh.side(f).is_some() && !coefs.is_empty() {
|
||||
any_dirichlet = true;
|
||||
}
|
||||
for &(col, v) in &coefs {
|
||||
tri.push((c, col, -sign * dt / rho * v));
|
||||
}
|
||||
}
|
||||
tri.push((c, c, 0.0)); // guarantee a diagonal entry
|
||||
}
|
||||
let mut a = 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);
|
||||
Some(a_cell)
|
||||
};
|
||||
(a, anchor)
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
let mesh = &self.mesh;
|
||||
let n = mesh.cell_count();
|
||||
let mut rhs = vec![0.0; n];
|
||||
for c in 0..n {
|
||||
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 {
|
||||
project_mean(&mut rhs);
|
||||
rhs[a] = 0.0;
|
||||
}
|
||||
let mut pc = vec![0.0; n];
|
||||
let out = bicgstab_jacobi(
|
||||
matrix,
|
||||
&rhs,
|
||||
&mut pc,
|
||||
tolerance,
|
||||
self.params.max_poisson_iterations,
|
||||
);
|
||||
(pc, out)
|
||||
}
|
||||
|
||||
/// `F −= (dt/ρ) L_f(p')`, `u −= (dt/ρ) ∇p'`, `p += p'`.
|
||||
pub(super) fn apply_correction(&self, field: &mut PatchField, pc: &[f64], dt: f64) {
|
||||
let mesh = &self.mesh;
|
||||
let rho = self.config.density;
|
||||
let lp = self.pressure_face_gradients(pc);
|
||||
for f in 0..mesh.faces().len() {
|
||||
field.flux[f] -= dt / rho * lp[f];
|
||||
}
|
||||
for c in 0..mesh.cell_count() {
|
||||
let g = self.pressure_gradient(pc, c);
|
||||
field.u[c] -= dt / rho * g[0];
|
||||
field.v[c] -= dt / rho * g[1];
|
||||
field.p[c] += pc[c];
|
||||
}
|
||||
}
|
||||
|
||||
/// Largest cell mass imbalance `|Σ sign F_f|`.
|
||||
pub(super) fn max_divergence(&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()
|
||||
})
|
||||
.fold(0.0, f64::max)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user