R7-2a: the composite MAC projection on the nested ratio-2 patch (host, additive, uncalled)
embedded3::composite::MacProjection: staggered velocity on the coarse grid and the patch; fine faces own the coarse-fine interface, coarse interface and covered faces are slaved (flux = sum of the fine fluxes); the divergence with the fine cut apertures (+ optional wall flux), the gradient with the Dirichlet boundary and, on the interface, the fine row's own Quadratic-ghost terms, so D G p = b - A p exactly. Composite records the interface faces' terms and the fine cut (matrix unchanged: the phase-1 P1 CSV reproduces digit for digit); solve_bicgstab_with takes a prepared preconditioner. Gates (tests/embedded3_composite_projection.rs): P1 DG identity 4e-16, div after/before <= 7e-14 in every cell class, idempotent 3e-13; P2 MMS orders 1.96-2.02 (L2 and Linf) at the fine/coarse interface faces and the interior; P3 cut sphere div 3e-12, translating body unchanged 4e-14, interface L2 orders 1.96/2.01, cut faces first order as on the uniformly fine grid (composite = uniform fine to 1.6 % of the error); P4 3.7x less time, 4.3x fewer unknowns than uniform fine at the bump's accuracy. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
171da41ed1
commit
cc68336f48
+58
-6
@@ -57,6 +57,41 @@ pub struct Composite {
|
|||||||
pub rhs: Vec<f64>,
|
pub rhs: Vec<f64>,
|
||||||
/// The uniform coarse operator (coarse apertures under the patch too).
|
/// The uniform coarse operator (coarse apertures under the patch too).
|
||||||
pub coarse_problem: Problem,
|
pub coarse_problem: Problem,
|
||||||
|
/// R7-2a: the body's cut on the fine level (apertures, volumes, wall
|
||||||
|
/// vectors), `None` without a body.
|
||||||
|
pub fine_cut: Option<CutGeometry>,
|
||||||
|
/// R7-2a: every fine face on the patch boundary with the fine row's
|
||||||
|
/// interface terms (the MAC projection's gradient on that face).
|
||||||
|
pub iface: Vec<InterfaceFace>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// R7-2a: one fine face on the patch boundary. The fine row of `row` holds
|
||||||
|
/// `Σ terms·p` for this face, i.e. the face's flux OUT of the fine cell is
|
||||||
|
/// `−Σ terms·p` (and the coarse row holds its negative: conservative).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct InterfaceFace {
|
||||||
|
/// The fine unknown owning the face.
|
||||||
|
pub row: usize,
|
||||||
|
/// The face's axis (0 x, 1 y, 2 z) and the side of the fine cell it is
|
||||||
|
/// on (−1 / +1).
|
||||||
|
pub axis: usize,
|
||||||
|
pub side: isize,
|
||||||
|
/// The face's index in the fine grid's `axis` face array.
|
||||||
|
pub face: usize,
|
||||||
|
/// The coarse face it is part of (index in the coarse `axis` array).
|
||||||
|
pub coarse_face: usize,
|
||||||
|
pub terms: Vec<(usize, f64)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The index of face `(axis, c)` — the face on the minus side of cell `c`
|
||||||
|
/// (`c[axis]` may equal the grid's extent) — in `g`'s `axis` face array.
|
||||||
|
#[must_use]
|
||||||
|
pub fn face_index(g: Grid, axis: usize, c: [usize; 3]) -> usize {
|
||||||
|
match axis {
|
||||||
|
0 => g.uface(c[2], c[1], c[0]),
|
||||||
|
1 => g.vface(c[2], c[1], c[0]),
|
||||||
|
_ => g.wface(c[2], c[1], c[0]),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const NONE: usize = usize::MAX;
|
const NONE: usize = usize::MAX;
|
||||||
@@ -184,6 +219,7 @@ impl Composite {
|
|||||||
let mut rows: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n];
|
let mut rows: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n];
|
||||||
let mut rhs = vec![0.0; n];
|
let mut rhs = vec![0.0; n];
|
||||||
let mut at_interface = vec![false; n];
|
let mut at_interface = vec![false; n];
|
||||||
|
let mut iface = Vec::new();
|
||||||
|
|
||||||
let step = |c: [usize; 3], d: usize, s: isize| -> [isize; 3] {
|
let step = |c: [usize; 3], d: usize, s: isize| -> [isize; 3] {
|
||||||
let mut o = [c[0] as isize, c[1] as isize, c[2] as isize];
|
let mut o = [c[0] as isize, c[1] as isize, c[2] as isize];
|
||||||
@@ -371,27 +407,41 @@ impl Composite {
|
|||||||
gc[2].div_euclid(2),
|
gc[2].div_euclid(2),
|
||||||
];
|
];
|
||||||
let c_row = cid(cc);
|
let c_row = cid(cc);
|
||||||
|
let mut terms: Vec<(usize, f64)> = Vec::with_capacity(12);
|
||||||
match spec.interface {
|
match spec.interface {
|
||||||
Interface::Direct => {
|
Interface::Direct => {
|
||||||
let coef = hf / 1.5;
|
let coef = hf / 1.5;
|
||||||
rows[row].push((row, coef));
|
terms.push((row, coef));
|
||||||
rows[row].push((c_row, -coef));
|
terms.push((c_row, -coef));
|
||||||
}
|
}
|
||||||
Interface::Octree => {
|
Interface::Octree => {
|
||||||
let coef = hf * hf / hc;
|
let coef = hf * hf / hc;
|
||||||
let kc = [lo[0] + i / 2, lo[1] + j / 2, lo[2] + k / 2];
|
let kc = [lo[0] + i / 2, lo[1] + j / 2, lo[2] + k / 2];
|
||||||
for sib in children(kc) {
|
for sib in children(kc) {
|
||||||
rows[row].push((fid(sib), coef / 8.0));
|
terms.push((fid(sib), coef / 8.0));
|
||||||
}
|
}
|
||||||
rows[row].push((c_row, -coef));
|
terms.push((c_row, -coef));
|
||||||
}
|
}
|
||||||
Interface::Quadratic => {
|
Interface::Quadratic => {
|
||||||
rows[row].push((row, hf));
|
terms.push((row, hf));
|
||||||
for (u, w) in ghost(f, d, s) {
|
for (u, w) in ghost(f, d, s) {
|
||||||
rows[row].push((u, -hf * w));
|
terms.push((u, -hf * w));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rows[row].extend_from_slice(&terms);
|
||||||
|
let mut ff = f;
|
||||||
|
ff[d] += usize::from(s > 0);
|
||||||
|
let mut cf = [cc[0] as usize, cc[1] as usize, cc[2] as usize];
|
||||||
|
cf[d] = if s < 0 { lo[d] } else { hi[d] };
|
||||||
|
iface.push(InterfaceFace {
|
||||||
|
row,
|
||||||
|
axis: d,
|
||||||
|
side: s,
|
||||||
|
face: face_index(fg, d, ff),
|
||||||
|
coarse_face: face_index(cg, d, cf),
|
||||||
|
terms,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -415,6 +465,8 @@ impl Composite {
|
|||||||
a,
|
a,
|
||||||
rhs,
|
rhs,
|
||||||
coarse_problem,
|
coarse_problem,
|
||||||
|
fine_cut,
|
||||||
|
iface,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,472 @@
|
|||||||
|
//! R7-2a: the composite MAC projection on the coarse grid plus the nested
|
||||||
|
//! ratio-2 patch (host prototype, nothing in the step calls it).
|
||||||
|
//!
|
||||||
|
//! Velocity layout: the normal velocity on every coarse face and on every
|
||||||
|
//! fine face of the patch (the embedded3 staggered layout on each level).
|
||||||
|
//! Ownership:
|
||||||
|
//!
|
||||||
|
//! - a coarse face between two uncovered coarse cells, or between one and
|
||||||
|
//! the domain boundary, is owned by the coarse level;
|
||||||
|
//! - every face of the fine level is owned by the fine level, including the
|
||||||
|
//! fine faces on the patch boundary (the coarse–fine interface);
|
||||||
|
//! - a coarse face on the interface, or under the patch, is SLAVED: its
|
||||||
|
//! flux is the sum of the fine fluxes it covers (average-down), so each
|
||||||
|
//! coarse cell next to the patch sees exactly the fluxes the fine cells
|
||||||
|
//! see (conservative across the interface).
|
||||||
|
//!
|
||||||
|
//! Operators, all in the integrated (flux) form of the composite pressure
|
||||||
|
//! operator `A` of [`Composite`] (`A p` = `Σ c_f (p_i − p_nb)` per row):
|
||||||
|
//!
|
||||||
|
//! - `D u`: per unknown, the outward flux `Σ ± a_f |f| u_f` (fine apertures
|
||||||
|
//! `a_f` of the body's cut; a face with no fluid or with an inactive cell
|
||||||
|
//! on either side carries no flux: a Neumann wall), plus an optional wall
|
||||||
|
//! flux per unknown (a moving cut body);
|
||||||
|
//! - `G p`: the face-normal gradient — `(p₊ − p₋)/h` on a coarse or fine
|
||||||
|
//! face, `± (p_b − p)/(h/2)` on the Dirichlet domain boundary, and on a
|
||||||
|
//! fine interface face the row's own interface terms (the Quadratic
|
||||||
|
//! ghost: `(g − p_f)/h_f`), so the coarse interface face's gradient is the
|
||||||
|
//! average of its four fine ones.
|
||||||
|
//!
|
||||||
|
//! By construction `D G p = b − A p` exactly (to round-off), with `b` the
|
||||||
|
//! Dirichlet part of the operator's right-hand side; the projection
|
||||||
|
//! `A p = b − D u*`, `u = u* − G p` therefore leaves `D u` equal to minus
|
||||||
|
//! the linear solve's residual in every cell, the interface cells included.
|
||||||
|
|
||||||
|
use super::assemble::face_index;
|
||||||
|
use super::solve::{CompositeSolve, solve_bicgstab_with};
|
||||||
|
use super::{Composite, CompositeSpec};
|
||||||
|
use crate::solvers::incompressible::embedded3::Grid;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
|
||||||
|
const NONE: usize = usize::MAX;
|
||||||
|
|
||||||
|
/// What a face is on the composite grid.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum FaceClass {
|
||||||
|
/// Coarse face between two uncovered coarse cells.
|
||||||
|
Coarse,
|
||||||
|
/// Coarse face on the (Dirichlet) domain boundary.
|
||||||
|
Boundary,
|
||||||
|
/// Coarse face on the patch boundary (slaved to its 4 fine faces).
|
||||||
|
CoarseInterface,
|
||||||
|
/// Coarse face under the patch (slaved; not part of any row).
|
||||||
|
Covered,
|
||||||
|
/// Fine face between two active fine cells (possibly cut).
|
||||||
|
Fine,
|
||||||
|
/// Fine face on the patch boundary (owns the interface flux).
|
||||||
|
FineInterface,
|
||||||
|
/// Fine face with no fluid or an inactive cell on a side (a wall).
|
||||||
|
Closed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The staggered velocity of both levels (normal component per face, in
|
||||||
|
/// each grid's u / v / w face layout).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MacField {
|
||||||
|
pub coarse: [Vec<f64>; 3],
|
||||||
|
pub fine: [Vec<f64>; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Outcome of one [`MacProjection::project`].
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ProjectionStats {
|
||||||
|
pub iterations: usize,
|
||||||
|
pub rel_residual: f64,
|
||||||
|
pub converged: bool,
|
||||||
|
/// max |D u| over the unknowns before and after.
|
||||||
|
pub div_before: f64,
|
||||||
|
pub div_after: f64,
|
||||||
|
pub solve_s: f64,
|
||||||
|
/// Divergence, gradient and average-down time.
|
||||||
|
pub mac_s: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn n_faces(g: Grid, axis: usize) -> usize {
|
||||||
|
(g.nx + usize::from(axis == 0))
|
||||||
|
* (g.ny + usize::from(axis == 1))
|
||||||
|
* (g.nz + usize::from(axis == 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `(i, j, k)` of face `idx` of `g`'s `axis` array.
|
||||||
|
fn face_coords(g: Grid, axis: usize, idx: usize) -> [usize; 3] {
|
||||||
|
let nx = g.nx + usize::from(axis == 0);
|
||||||
|
let ny = g.ny + usize::from(axis == 1);
|
||||||
|
[idx % nx, (idx / nx) % ny, idx / (nx * ny)]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The composite projection: the operator, its preconditioner and the face
|
||||||
|
/// tables.
|
||||||
|
pub struct MacProjection {
|
||||||
|
pub comp: Composite,
|
||||||
|
pub coarse_class: [Vec<FaceClass>; 3],
|
||||||
|
pub fine_class: [Vec<FaceClass>; 3],
|
||||||
|
/// Fluid area `a_f h_f²` of every fine face (0 on a closed face).
|
||||||
|
pub fine_area: [Vec<f64>; 3],
|
||||||
|
/// The Dirichlet pressure at every boundary face (0 elsewhere).
|
||||||
|
pb: [Vec<f64>; 3],
|
||||||
|
/// The Dirichlet part of the right-hand side (the spec's source, if
|
||||||
|
/// any, included).
|
||||||
|
base_rhs: Vec<f64>,
|
||||||
|
/// Per unknown: its cell (coarse cells for `u < n_coarse`, else fine).
|
||||||
|
cell_of: Vec<usize>,
|
||||||
|
pre: CompositeSolve,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MacProjection {
|
||||||
|
/// Build the composite operator of `spec` (its `source` should be zero:
|
||||||
|
/// it would act as an added mass source) and the face tables.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(spec: &CompositeSpec<'_>, sweeps: usize) -> Self {
|
||||||
|
let comp = Composite::build(spec);
|
||||||
|
let (cg, fg, lo, hi) = (comp.coarse, comp.fine, comp.lo, comp.hi);
|
||||||
|
let hc = cg.dx;
|
||||||
|
let cdims = [cg.nx, cg.ny, cg.nz];
|
||||||
|
let fdims = [fg.nx, fg.ny, fg.nz];
|
||||||
|
let covered = |c: [usize; 3]| (0..3).all(|d| c[d] >= lo[d] && c[d] < hi[d]);
|
||||||
|
let mut coarse_class: [Vec<FaceClass>; 3] = Default::default();
|
||||||
|
let mut pb: [Vec<f64>; 3] = Default::default();
|
||||||
|
for d in 0..3 {
|
||||||
|
let nf = n_faces(cg, d);
|
||||||
|
coarse_class[d] = vec![FaceClass::Coarse; nf];
|
||||||
|
pb[d] = vec![0.0; nf];
|
||||||
|
for idx in 0..nf {
|
||||||
|
let c = face_coords(cg, d, idx);
|
||||||
|
debug_assert_eq!(face_index(cg, d, c), idx);
|
||||||
|
let class = if c[d] == 0 || c[d] == cdims[d] {
|
||||||
|
let mut x = [
|
||||||
|
(c[0] as f64 + 0.5) * hc,
|
||||||
|
(c[1] as f64 + 0.5) * hc,
|
||||||
|
(c[2] as f64 + 0.5) * hc,
|
||||||
|
];
|
||||||
|
x[d] = c[d] as f64 * hc;
|
||||||
|
pb[d][idx] = (spec.dirichlet)(x[0], x[1], x[2]);
|
||||||
|
FaceClass::Boundary
|
||||||
|
} else {
|
||||||
|
let mut m = c;
|
||||||
|
m[d] -= 1;
|
||||||
|
match (covered(m), covered(c)) {
|
||||||
|
(true, true) => FaceClass::Covered,
|
||||||
|
(false, false) => FaceClass::Coarse,
|
||||||
|
_ => FaceClass::CoarseInterface,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
coarse_class[d][idx] = class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let hf2 = fg.dx * fg.dx;
|
||||||
|
let fid = |c: [usize; 3]| comp.fine_id[fg.cell(c[2], c[1], c[0])];
|
||||||
|
let mut fine_class: [Vec<FaceClass>; 3] = Default::default();
|
||||||
|
let mut fine_area: [Vec<f64>; 3] = Default::default();
|
||||||
|
for d in 0..3 {
|
||||||
|
let nf = n_faces(fg, d);
|
||||||
|
fine_class[d] = vec![FaceClass::Closed; nf];
|
||||||
|
fine_area[d] = vec![0.0; nf];
|
||||||
|
let ap = comp.fine_cut.as_ref().map(|cut| match d {
|
||||||
|
0 => &cut.a_u,
|
||||||
|
1 => &cut.a_v,
|
||||||
|
_ => &cut.a_w,
|
||||||
|
});
|
||||||
|
for idx in 0..nf {
|
||||||
|
let c = face_coords(fg, d, idx);
|
||||||
|
let a = ap.map_or(1.0, |ap| ap[idx]);
|
||||||
|
if c[d] == 0 || c[d] == fdims[d] {
|
||||||
|
fine_class[d][idx] = FaceClass::FineInterface;
|
||||||
|
fine_area[d][idx] = hf2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut m = c;
|
||||||
|
m[d] -= 1;
|
||||||
|
if a > 0.0 && fid(m) != NONE && fid(c) != NONE {
|
||||||
|
fine_class[d][idx] = FaceClass::Fine;
|
||||||
|
fine_area[d][idx] = a * hf2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut cell_of = vec![NONE; comp.unknowns()];
|
||||||
|
for (cell, &u) in comp.coarse_id.iter().enumerate() {
|
||||||
|
if u != NONE {
|
||||||
|
cell_of[u] = cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (cell, &u) in comp.fine_id.iter().enumerate() {
|
||||||
|
if u != NONE {
|
||||||
|
cell_of[u] = cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let pre = CompositeSolve::new(&comp, sweeps);
|
||||||
|
let base_rhs = comp.rhs.clone();
|
||||||
|
Self {
|
||||||
|
comp,
|
||||||
|
coarse_class,
|
||||||
|
fine_class,
|
||||||
|
fine_area,
|
||||||
|
pb,
|
||||||
|
base_rhs,
|
||||||
|
cell_of,
|
||||||
|
pre,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A zero field.
|
||||||
|
#[must_use]
|
||||||
|
pub fn zeros(&self) -> MacField {
|
||||||
|
let (cg, fg) = (self.comp.coarse, self.comp.fine);
|
||||||
|
MacField {
|
||||||
|
coarse: std::array::from_fn(|d| vec![0.0; n_faces(cg, d)]),
|
||||||
|
fine: std::array::from_fn(|d| vec![0.0; n_faces(fg, d)]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The centre of face `idx` of `axis` on the fine (`fine`) or coarse
|
||||||
|
/// level, in the coarse grid's coordinates.
|
||||||
|
#[must_use]
|
||||||
|
pub fn face_centre(&self, fine: bool, axis: usize, idx: usize) -> [f64; 3] {
|
||||||
|
let (g, o) = if fine {
|
||||||
|
let hc = self.comp.coarse.dx;
|
||||||
|
let lo = self.comp.lo;
|
||||||
|
(
|
||||||
|
self.comp.fine,
|
||||||
|
[lo[0] as f64 * hc, lo[1] as f64 * hc, lo[2] as f64 * hc],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(self.comp.coarse, [0.0; 3])
|
||||||
|
};
|
||||||
|
let c = face_coords(g, axis, idx);
|
||||||
|
let h = g.dx;
|
||||||
|
let mut x = [
|
||||||
|
o[0] + (c[0] as f64 + 0.5) * h,
|
||||||
|
o[1] + (c[1] as f64 + 0.5) * h,
|
||||||
|
o[2] + (c[2] as f64 + 0.5) * h,
|
||||||
|
];
|
||||||
|
x[axis] = o[axis] + c[axis] as f64 * h;
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The unknowns on the minus and plus side of fine face `idx` of `axis`
|
||||||
|
/// (`None` outside the patch or on an inactive cell).
|
||||||
|
#[must_use]
|
||||||
|
pub fn fine_face_cells(&self, axis: usize, idx: usize) -> [Option<usize>; 2] {
|
||||||
|
let fg = self.comp.fine;
|
||||||
|
let fdims = [fg.nx, fg.ny, fg.nz];
|
||||||
|
let c = face_coords(fg, axis, idx);
|
||||||
|
let id = |c: [usize; 3]| {
|
||||||
|
let u = self.comp.fine_id[fg.cell(c[2], c[1], c[0])];
|
||||||
|
(u != NONE).then_some(u)
|
||||||
|
};
|
||||||
|
let minus = (c[axis] > 0).then(|| {
|
||||||
|
let mut m = c;
|
||||||
|
m[axis] -= 1;
|
||||||
|
m
|
||||||
|
});
|
||||||
|
[
|
||||||
|
minus.and_then(id),
|
||||||
|
(c[axis] < fdims[axis]).then_some(c).and_then(id),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Point values of `f(axis, x)` on every face (slaved coarse faces
|
||||||
|
/// then averaged down from the fine ones).
|
||||||
|
#[must_use]
|
||||||
|
pub fn sample(&self, f: &(dyn Fn(usize, [f64; 3]) -> f64 + Sync)) -> MacField {
|
||||||
|
let mut u = self.zeros();
|
||||||
|
for d in 0..3 {
|
||||||
|
u.coarse[d]
|
||||||
|
.par_iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, v)| *v = f(d, self.face_centre(false, d, i)));
|
||||||
|
u.fine[d]
|
||||||
|
.par_iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, v)| *v = f(d, self.face_centre(true, d, i)));
|
||||||
|
}
|
||||||
|
self.average_down(&mut u);
|
||||||
|
u
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The slaved coarse faces (interface and covered): flux = the sum of
|
||||||
|
/// the fine fluxes under them.
|
||||||
|
pub fn average_down(&self, u: &mut MacField) {
|
||||||
|
let (cg, fg, lo) = (self.comp.coarse, self.comp.fine, self.comp.lo);
|
||||||
|
let inv = 1.0 / (cg.dx * cg.dx);
|
||||||
|
for d in 0..3 {
|
||||||
|
let (fine, area, class) = (&u.fine[d], &self.fine_area[d], &self.coarse_class[d]);
|
||||||
|
u.coarse[d].par_iter_mut().enumerate().for_each(|(idx, v)| {
|
||||||
|
if !matches!(class[idx], FaceClass::CoarseInterface | FaceClass::Covered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let c = face_coords(cg, d, idx);
|
||||||
|
let (t1, t2) = match d {
|
||||||
|
0 => (1, 2),
|
||||||
|
1 => (0, 2),
|
||||||
|
_ => (0, 1),
|
||||||
|
};
|
||||||
|
let mut s = 0.0;
|
||||||
|
for a in 0..2 {
|
||||||
|
for b in 0..2 {
|
||||||
|
let mut f = [0usize; 3];
|
||||||
|
f[d] = 2 * (c[d] - lo[d]);
|
||||||
|
f[t1] = 2 * (c[t1] - lo[t1]) + a;
|
||||||
|
f[t2] = 2 * (c[t2] - lo[t2]) + b;
|
||||||
|
let fi = face_index(fg, d, f);
|
||||||
|
s += area[fi] * fine[fi];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*v = s * inv;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `out = D u (+ wall)`: the outward flux of every unknown.
|
||||||
|
pub fn divergence(&self, u: &MacField, wall: Option<&[f64]>, out: &mut [f64]) {
|
||||||
|
let (cg, fg, nc) = (self.comp.coarse, self.comp.fine, self.comp.n_coarse);
|
||||||
|
let ac = cg.dx * cg.dx;
|
||||||
|
out.par_iter_mut().enumerate().for_each(|(r, o)| {
|
||||||
|
let cell = self.cell_of[r];
|
||||||
|
let mut s = 0.0;
|
||||||
|
if r < nc {
|
||||||
|
let (k, j, i) = (cell / (cg.nx * cg.ny), (cell / cg.nx) % cg.ny, cell % cg.nx);
|
||||||
|
for d in 0..3 {
|
||||||
|
let mut p = [i, j, k];
|
||||||
|
let m = face_index(cg, d, p);
|
||||||
|
p[d] += 1;
|
||||||
|
let q = face_index(cg, d, p);
|
||||||
|
s += ac * (u.coarse[d][q] - u.coarse[d][m]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let (k, j, i) = (cell / (fg.nx * fg.ny), (cell / fg.nx) % fg.ny, cell % fg.nx);
|
||||||
|
for d in 0..3 {
|
||||||
|
let mut p = [i, j, k];
|
||||||
|
let m = face_index(fg, d, p);
|
||||||
|
p[d] += 1;
|
||||||
|
let q = face_index(fg, d, p);
|
||||||
|
let a = &self.fine_area[d];
|
||||||
|
s += a[q] * u.fine[d][q] - a[m] * u.fine[d][m];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(w) = wall {
|
||||||
|
s += w[r];
|
||||||
|
}
|
||||||
|
*o = s;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `u −= G p` on every owned face, then the slaved faces averaged down.
|
||||||
|
pub fn subtract_gradient(&self, p: &[f64], u: &mut MacField) {
|
||||||
|
let (cg, fg) = (self.comp.coarse, self.comp.fine);
|
||||||
|
let (hc, hf) = (cg.dx, fg.dx);
|
||||||
|
let cdims = [cg.nx, cg.ny, cg.nz];
|
||||||
|
let (cid, fid) = (&self.comp.coarse_id, &self.comp.fine_id);
|
||||||
|
for d in 0..3 {
|
||||||
|
let (class, pb) = (&self.coarse_class[d], &self.pb[d]);
|
||||||
|
u.coarse[d].par_iter_mut().enumerate().for_each(|(idx, v)| {
|
||||||
|
let c = face_coords(cg, d, idx);
|
||||||
|
let pc = |c: [usize; 3]| p[cid[cg.cell(c[2], c[1], c[0])]];
|
||||||
|
let mut m = c;
|
||||||
|
match class[idx] {
|
||||||
|
FaceClass::Coarse => {
|
||||||
|
m[d] -= 1;
|
||||||
|
*v -= (pc(c) - pc(m)) / hc;
|
||||||
|
}
|
||||||
|
FaceClass::Boundary => {
|
||||||
|
if c[d] == 0 {
|
||||||
|
*v -= (pc(c) - pb[idx]) / (0.5 * hc);
|
||||||
|
} else {
|
||||||
|
debug_assert_eq!(c[d], cdims[d]);
|
||||||
|
m[d] -= 1;
|
||||||
|
*v -= (pb[idx] - pc(m)) / (0.5 * hc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let class = &self.fine_class[d];
|
||||||
|
u.fine[d].par_iter_mut().enumerate().for_each(|(idx, v)| {
|
||||||
|
if class[idx] != FaceClass::Fine {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let c = face_coords(fg, d, idx);
|
||||||
|
let mut m = c;
|
||||||
|
m[d] -= 1;
|
||||||
|
let pf = |c: [usize; 3]| p[fid[fg.cell(c[2], c[1], c[0])]];
|
||||||
|
*v -= (pf(c) - pf(m)) / hf;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let inv = 1.0 / (hf * hf);
|
||||||
|
for f in &self.comp.iface {
|
||||||
|
let t: f64 = f.terms.iter().map(|&(col, w)| w * p[col]).sum();
|
||||||
|
// Out of the fine cell: −t / h_f²; along +axis: × side.
|
||||||
|
u.fine[f.axis][f.face] -= f.side as f64 * (-t * inv);
|
||||||
|
}
|
||||||
|
self.average_down(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `G p` as a field (the Dirichlet values on the boundary faces).
|
||||||
|
#[must_use]
|
||||||
|
pub fn gradient(&self, p: &[f64]) -> MacField {
|
||||||
|
let mut g = self.zeros();
|
||||||
|
self.subtract_gradient(p, &mut g);
|
||||||
|
for d in 0..3 {
|
||||||
|
g.coarse[d].iter_mut().for_each(|v| *v = -*v);
|
||||||
|
g.fine[d].iter_mut().for_each(|v| *v = -*v);
|
||||||
|
}
|
||||||
|
g
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Dirichlet part `b` of `D G p = b − A p`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn boundary_rhs(&self) -> &[f64] {
|
||||||
|
&self.base_rhs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The wall flux of a body translating at `vel` through the fine cut
|
||||||
|
/// cells (`vel · wall vector`), per unknown.
|
||||||
|
#[must_use]
|
||||||
|
pub fn translating_wall_flux(&self, vel: [f64; 3]) -> Vec<f64> {
|
||||||
|
let mut w = vec![0.0; self.comp.unknowns()];
|
||||||
|
if let Some(cut) = self.comp.fine_cut.as_ref() {
|
||||||
|
for (cell, &u) in self.comp.fine_id.iter().enumerate() {
|
||||||
|
if u != NONE {
|
||||||
|
let a = cut.wall[cell];
|
||||||
|
w[u] = vel[0] * a[0] + vel[1] * a[1] + vel[2] * a[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Project `u` onto the discretely divergence-free fields (plus the wall
|
||||||
|
/// flux): `A p = b − D u`, `u −= G p`. `p` is the initial guess and
|
||||||
|
/// returns the pressure (times dt/ρ in a step).
|
||||||
|
pub fn project(
|
||||||
|
&mut self,
|
||||||
|
u: &mut MacField,
|
||||||
|
p: &mut [f64],
|
||||||
|
wall: Option<&[f64]>,
|
||||||
|
tol: f64,
|
||||||
|
max_it: usize,
|
||||||
|
) -> ProjectionStats {
|
||||||
|
let t0 = std::time::Instant::now();
|
||||||
|
let n = self.comp.unknowns();
|
||||||
|
self.average_down(u);
|
||||||
|
let mut div = vec![0.0; n];
|
||||||
|
self.divergence(u, wall, &mut div);
|
||||||
|
let div_before = div.iter().fold(0.0f64, |m, v| m.max(v.abs()));
|
||||||
|
let rhs: Vec<f64> = self.base_rhs.iter().zip(&div).map(|(b, d)| b - d).collect();
|
||||||
|
let mut mac_s = t0.elapsed().as_secs_f64();
|
||||||
|
let st = solve_bicgstab_with(&self.comp, &mut self.pre, &rhs, p, tol, max_it);
|
||||||
|
let t1 = std::time::Instant::now();
|
||||||
|
self.subtract_gradient(p, u);
|
||||||
|
self.divergence(u, wall, &mut div);
|
||||||
|
let div_after = div.iter().fold(0.0f64, |m, v| m.max(v.abs()));
|
||||||
|
mac_s += t1.elapsed().as_secs_f64();
|
||||||
|
ProjectionStats {
|
||||||
|
iterations: st.iterations,
|
||||||
|
rel_residual: st.rel_residual,
|
||||||
|
converged: st.converged,
|
||||||
|
div_before,
|
||||||
|
div_after,
|
||||||
|
solve_s: st.solve_s,
|
||||||
|
mac_s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,10 +27,14 @@
|
|||||||
//! the interface or the coarse uncovered cells.
|
//! the interface or the coarse uncovered cells.
|
||||||
|
|
||||||
mod assemble;
|
mod assemble;
|
||||||
|
mod mac;
|
||||||
mod solve;
|
mod solve;
|
||||||
|
|
||||||
pub use assemble::{Composite, CompositeSpec, Interface, Sdf, uniform_problem};
|
pub use assemble::{
|
||||||
pub use solve::{CompositeSolve, SolveStats, solve_bicgstab};
|
Composite, CompositeSpec, Interface, InterfaceFace, Sdf, face_index, uniform_problem,
|
||||||
|
};
|
||||||
|
pub use mac::{FaceClass, MacField, MacProjection, ProjectionStats};
|
||||||
|
pub use solve::{CompositeSolve, SolveStats, solve_bicgstab, solve_bicgstab_with};
|
||||||
|
|
||||||
/// Compressed sparse rows.
|
/// Compressed sparse rows.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|||||||
@@ -128,9 +128,25 @@ pub fn solve_bicgstab(
|
|||||||
let t0 = std::time::Instant::now();
|
let t0 = std::time::Instant::now();
|
||||||
let mut pre = CompositeSolve::new(c, sweeps);
|
let mut pre = CompositeSolve::new(c, sweeps);
|
||||||
let setup_s = t0.elapsed().as_secs_f64();
|
let setup_s = t0.elapsed().as_secs_f64();
|
||||||
|
let mut st = solve_bicgstab_with(c, &mut pre, &c.rhs, x, tol, max_it);
|
||||||
|
st.setup_s = setup_s;
|
||||||
|
st
|
||||||
|
}
|
||||||
|
|
||||||
|
/// R7-2a: [`solve_bicgstab`] with a prepared preconditioner and an explicit
|
||||||
|
/// right-hand side (the projection solves many right-hand sides on one
|
||||||
|
/// operator). `setup_s` is 0.
|
||||||
|
pub fn solve_bicgstab_with(
|
||||||
|
c: &Composite,
|
||||||
|
pre: &mut CompositeSolve,
|
||||||
|
b: &[f64],
|
||||||
|
x: &mut [f64],
|
||||||
|
tol: f64,
|
||||||
|
max_it: usize,
|
||||||
|
) -> SolveStats {
|
||||||
|
let setup_s = 0.0;
|
||||||
let t1 = std::time::Instant::now();
|
let t1 = std::time::Instant::now();
|
||||||
let n = c.unknowns();
|
let n = c.unknowns();
|
||||||
let b = &c.rhs;
|
|
||||||
let bn = dot(b, b).sqrt().max(f64::MIN_POSITIVE);
|
let bn = dot(b, b).sqrt().max(f64::MIN_POSITIVE);
|
||||||
let mut r = vec![0.0; n];
|
let mut r = vec![0.0; n];
|
||||||
c.a.apply(x, &mut r);
|
c.a.apply(x, &mut r);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user