rtx-cfd embedded3 item 9: wall.rs (binary ghost mask: three face families, trilinear stencil with periodic-z wrap, z-weighted least-squares ghost fit, flux compatibility correction; slip walls allowed as touched sides) + loads.rs (surface-stress route with probes, control-volume route with full-span z faces skipped); the step wired (body/mask, predicates, anchor, ghost re-imposition). Gates HELD: CFD1 ny 41 nz 1 CV 15.6156 / surface 15.7126 both to 1e-6 of the 2D record; nz 4 periodic CV 1.1e-6 / surface 4.2e-4; sphere MMS order 0.89, div 1e-8, ghost correction 1.0e-4 → 2.0e-5, both load routes' errors falling (0.153 → 0.115 surface, 0.214 → 0.139 CV)
CI / Build CPU-Only (Explicit) (push) Failing after 4s
Documentation / Build API Documentation (push) Failing after 4s
Documentation / Build User Guide (push) Successful in 4s
CI / Format Check (push) Failing after 12s
CI / Build (ubuntu-latest) (push) Failing after 1m57s
CI / Clippy Check (push) Failing after 2m20s
Performance Benchmarks / Run Benchmarks (push) Successful in 2m50s
CI / Build (macos-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
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (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 / Build CPU-Only (Explicit) (push) Failing after 4s
Documentation / Build API Documentation (push) Failing after 4s
Documentation / Build User Guide (push) Successful in 4s
CI / Format Check (push) Failing after 12s
CI / Build (ubuntu-latest) (push) Failing after 1m57s
CI / Clippy Check (push) Failing after 2m20s
Performance Benchmarks / Run Benchmarks (push) Successful in 2m50s
CI / Build (macos-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
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
d4ffac9ac7
commit
d337afa8f9
@@ -0,0 +1,655 @@
|
||||
//! The binary ghost wall — the 2D `EmbeddedMask` on the 3D grid. Cells are
|
||||
//! fluid where φ(centre) > 0; an interior face between two cells that are
|
||||
//! not both fluid is a ghost (within 1.5 h of the surface) or solid; a
|
||||
//! ghost face is prescribed the least-squares linear reconstruction
|
||||
//! through the fluid nodes of the trilinear stencil around the probe (one
|
||||
//! cell beyond the face's mirror image along the normal) plus the foot of
|
||||
//! the normal with its surface velocity — exact for linear fields — with
|
||||
//! the profile along the normal as the fallback; the ghost faces bounding
|
||||
//! fluid cells share a flux compatibility correction.
|
||||
|
||||
use super::Grid;
|
||||
use super::body::Body;
|
||||
use super::step::{Boundaries, Side};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FaceKind {
|
||||
Fluid,
|
||||
Ghost,
|
||||
Solid,
|
||||
}
|
||||
|
||||
/// The wall treatment of an embedded body.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum WallScheme {
|
||||
/// The 2D binary mask ported (this module).
|
||||
#[default]
|
||||
GhostBinary,
|
||||
/// The apertured cut-cell wall (item 10).
|
||||
CutCell,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct StencilNode {
|
||||
pub(crate) idx: usize,
|
||||
pub(crate) x: f64,
|
||||
pub(crate) y: f64,
|
||||
pub(crate) z: f64,
|
||||
/// The trilinear weight (the fallback profile's interpolation).
|
||||
pub(crate) weight: f64,
|
||||
/// The z-direction weight alone: the least-squares weight of the node,
|
||||
/// so that a z-invariant field fits exactly as the 2D four-node fit
|
||||
/// (the two planes share the in-plane nodes' unit weight).
|
||||
pub(crate) zw: f64,
|
||||
pub(crate) fallback: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Ghost {
|
||||
idx: usize,
|
||||
x: f64,
|
||||
y: f64,
|
||||
z: f64,
|
||||
foot: (f64, f64, f64),
|
||||
u_surface: f64,
|
||||
s_face: f64,
|
||||
s_probe: f64,
|
||||
nodes: Vec<StencilNode>,
|
||||
/// Outward-from-fluid sign for the compatibility correction (0 when no
|
||||
/// fluid cell is adjacent).
|
||||
flux_sign: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Mask {
|
||||
grid: Grid,
|
||||
periodic_z: bool,
|
||||
cell_fluid: Vec<bool>,
|
||||
u_kind: Vec<FaceKind>,
|
||||
v_kind: Vec<FaceKind>,
|
||||
w_kind: Vec<FaceKind>,
|
||||
u_ghosts: Vec<Ghost>,
|
||||
v_ghosts: Vec<Ghost>,
|
||||
w_ghosts: Vec<Ghost>,
|
||||
anchor: usize,
|
||||
fluid_cells: usize,
|
||||
}
|
||||
|
||||
/// The z lattice position of a query: the lower plane index, the upper
|
||||
/// plane index and the weight of the upper plane. Periodic z wraps; a wall
|
||||
/// clamps (and a query outside the lattice takes the nearest plane).
|
||||
pub(crate) fn z_planes(gz: f64, planes: usize, periodic: bool) -> (usize, usize, f64) {
|
||||
if planes <= 1 {
|
||||
return (0, 0, 0.0);
|
||||
}
|
||||
if periodic {
|
||||
let n = planes as f64;
|
||||
let w = gz.rem_euclid(n);
|
||||
let k0 = w.floor() as usize % planes;
|
||||
((k0) % planes, (k0 + 1) % planes, w - w.floor())
|
||||
} else {
|
||||
let k0 = gz.floor().clamp(0.0, (planes - 2) as f64) as usize;
|
||||
(k0, k0 + 1, (gz - k0 as f64).clamp(0.0, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// The nodes of velocity component `c` (0 u, 1 v, 2 w) around `point`
|
||||
/// with trilinear weights (a single plane of nodes in a direction
|
||||
/// collapses to one node), clamped in x and y, wrapped or clamped in z;
|
||||
/// `fallback(idx)` supplies the value of a node that is not a fluid face.
|
||||
pub(crate) fn stencil_nodes(
|
||||
point: (f64, f64, f64),
|
||||
c: usize,
|
||||
g: Grid,
|
||||
periodic_z: bool,
|
||||
fallback: impl Fn(usize) -> Option<f64>,
|
||||
) -> Vec<StencilNode> {
|
||||
let (nx, ny, nz, dx, dy, dz) = (g.nx, g.ny, g.nz, g.dx, g.dy, g.dz);
|
||||
// w faces on a periodic grid: the plane k = nz is the plane 0.
|
||||
let (gx, gy, gz, max_i, max_j, planes_k) = match c {
|
||||
0 => (
|
||||
point.0 / dx,
|
||||
point.1 / dy - 0.5,
|
||||
point.2 / dz - 0.5,
|
||||
nx,
|
||||
ny - 1,
|
||||
nz,
|
||||
),
|
||||
1 => (
|
||||
point.0 / dx - 0.5,
|
||||
point.1 / dy,
|
||||
point.2 / dz - 0.5,
|
||||
nx - 1,
|
||||
ny,
|
||||
nz,
|
||||
),
|
||||
_ => (
|
||||
point.0 / dx - 0.5,
|
||||
point.1 / dy - 0.5,
|
||||
point.2 / dz,
|
||||
nx - 1,
|
||||
ny - 1,
|
||||
if periodic_z { nz } else { nz + 1 },
|
||||
),
|
||||
};
|
||||
let i0 = gx.floor().clamp(0.0, (max_i.max(1) - 1) as f64) as usize;
|
||||
let j0 = gy.floor().clamp(0.0, (max_j.max(1) - 1) as f64) as usize;
|
||||
let fx = (gx - i0 as f64).clamp(0.0, 1.0);
|
||||
let fy = (gy - j0 as f64).clamp(0.0, 1.0);
|
||||
let (k0, k1, fz) = z_planes(gz, planes_k, periodic_z);
|
||||
let pos = |k: usize, j: usize, i: usize| match c {
|
||||
0 => (i as f64 * dx, (j as f64 + 0.5) * dy, (k as f64 + 0.5) * dz),
|
||||
1 => ((i as f64 + 0.5) * dx, j as f64 * dy, (k as f64 + 0.5) * dz),
|
||||
_ => ((i as f64 + 0.5) * dx, (j as f64 + 0.5) * dy, k as f64 * dz),
|
||||
};
|
||||
let index = |k: usize, j: usize, i: usize| match c {
|
||||
0 => g.uface(k, j, i),
|
||||
1 => g.vface(k, j, i),
|
||||
_ => g.wface(k, j, i),
|
||||
};
|
||||
let dirs = |m: usize, f: f64| {
|
||||
if m <= 1 {
|
||||
vec![(0usize, 1.0)]
|
||||
} else {
|
||||
vec![(0, 1.0 - f), (1, f)]
|
||||
}
|
||||
};
|
||||
let mut out = Vec::with_capacity(8);
|
||||
let kz = |dk: usize| if dk == 0 { k0 } else { k1 };
|
||||
for (dk, wk) in dirs(planes_k, fz) {
|
||||
for (dj, wj) in dirs(max_j, fy) {
|
||||
for (di, wi) in dirs(max_i, fx) {
|
||||
let (k, j, i) = (kz(dk), j0 + dj, i0 + di);
|
||||
let (x, y, z) = pos(k, j, i);
|
||||
let idx = index(k, j, i);
|
||||
out.push(StencilNode {
|
||||
idx,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
weight: wi * wj * wk,
|
||||
zw: wk,
|
||||
fallback: fallback(idx),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Weighted least-squares fit of `a + b (x−x0) + c (y−y0) + d (z−z0)`
|
||||
/// through `pts` (each `(x, y, z, value, weight)`; zero-weight points drop
|
||||
/// out) evaluated at `at`; directions in which every weighted point has
|
||||
/// the same coordinate are dropped. `None` if the normal matrix is
|
||||
/// singular relative to its scale.
|
||||
pub(crate) fn linear_fit(pts_w: &[(f64, f64, f64, f64, f64)], at: (f64, f64, f64)) -> Option<f64> {
|
||||
let pts: Vec<(f64, f64, f64, f64, f64)> = pts_w.iter().copied().filter(|p| p.4 > 0.0).collect();
|
||||
let spread = |f: &dyn Fn(&(f64, f64, f64, f64, f64)) -> f64| {
|
||||
let (lo, hi) = pts
|
||||
.iter()
|
||||
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), p| {
|
||||
(lo.min(f(p)), hi.max(f(p)))
|
||||
});
|
||||
hi - lo > 1e-12 * (hi.abs() + lo.abs() + 1e-300)
|
||||
};
|
||||
let on = [true, spread(&|p| p.0), spread(&|p| p.1), spread(&|p| p.2)];
|
||||
let cols: Vec<usize> = (0..4).filter(|&c| on[c]).collect();
|
||||
let m = cols.len();
|
||||
if pts.len() < m {
|
||||
return None;
|
||||
}
|
||||
let mut a = vec![vec![0.0; m + 1]; m];
|
||||
for p in &pts {
|
||||
let full = [1.0, p.0 - at.0, p.1 - at.1, p.2 - at.2];
|
||||
let r: Vec<f64> = cols.iter().map(|&c| full[c]).collect();
|
||||
for i in 0..m {
|
||||
for j in 0..m {
|
||||
a[i][j] += p.4 * r[i] * r[j];
|
||||
}
|
||||
a[i][m] += p.4 * r[i] * p.3;
|
||||
}
|
||||
}
|
||||
let scale: f64 = (0..m).map(|i| a[i][i]).product();
|
||||
if scale <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
let mut det = 1.0;
|
||||
for col in 0..m {
|
||||
let piv = (col..m)
|
||||
.max_by(|&p, &q| a[p][col].abs().partial_cmp(&a[q][col].abs()).unwrap())
|
||||
.unwrap();
|
||||
a.swap(col, piv);
|
||||
let d = a[col][col];
|
||||
det *= d;
|
||||
if d.abs() <= 1e-14 * scale.powf(1.0 / m as f64) {
|
||||
return None;
|
||||
}
|
||||
for r in col + 1..m {
|
||||
let f = a[r][col] / d;
|
||||
for c in col..=m {
|
||||
a[r][c] -= f * a[col][c];
|
||||
}
|
||||
}
|
||||
}
|
||||
if det.abs() <= 1e-10 * scale {
|
||||
return None;
|
||||
}
|
||||
let mut x = vec![0.0; m];
|
||||
for i in (0..m).rev() {
|
||||
let mut s = a[i][m];
|
||||
for j in i + 1..m {
|
||||
s -= a[i][j] * x[j];
|
||||
}
|
||||
x[i] = s / a[i][i];
|
||||
}
|
||||
Some(x[0])
|
||||
}
|
||||
|
||||
impl Ghost {
|
||||
fn reconstruct(&self, values: &[f64]) -> f64 {
|
||||
let mut pts: Vec<(f64, f64, f64, f64, f64)> = self
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|n| n.fallback.is_none())
|
||||
.map(|n| (n.x, n.y, n.z, values[n.idx], n.zw))
|
||||
.collect();
|
||||
pts.push((self.foot.0, self.foot.1, self.foot.2, self.u_surface, 1.0));
|
||||
if let Some(val) = linear_fit(&pts, (self.x, self.y, self.z)) {
|
||||
return val;
|
||||
}
|
||||
let mut probe = 0.0;
|
||||
for n in &self.nodes {
|
||||
probe += n.weight * n.fallback.unwrap_or_else(|| values[n.idx]);
|
||||
}
|
||||
self.u_surface + (probe - self.u_surface) * (self.s_face / self.s_probe)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mask {
|
||||
/// Classify the grid against `body` at `t`. A solid cell on a domain
|
||||
/// side is refused unless that side is a Velocity, SlipWall or Periodic
|
||||
/// side (an outlet may not be blocked).
|
||||
pub fn build(body: &Body, g: Grid, t: f64, b: Boundaries) -> Result<Self, String> {
|
||||
let (nx, ny, nz, dx, dy, dz) = (g.nx, g.ny, g.nz, g.dx, g.dy, g.dz);
|
||||
let periodic = b.z0 == Side::Periodic;
|
||||
let xc = |i: usize| (i as f64 + 0.5) * dx;
|
||||
let yc = |j: usize| (j as f64 + 0.5) * dy;
|
||||
let zc = |k: usize| (k as f64 + 0.5) * dz;
|
||||
let mut cell_fluid = vec![true; g.cells()];
|
||||
let mut fluid_cells = 0;
|
||||
let mut anchor = None;
|
||||
let allowed = |side: Side| matches!(side, Side::Velocity | Side::Periodic | Side::SlipWall);
|
||||
for k in 0..nz {
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
let fluid = body.phi(xc(i), yc(j), zc(k), t) > 0.0;
|
||||
let idx = g.cell(k, j, i);
|
||||
cell_fluid[idx] = fluid;
|
||||
if fluid {
|
||||
fluid_cells += 1;
|
||||
if anchor.is_none() {
|
||||
anchor = Some(idx);
|
||||
}
|
||||
} else {
|
||||
let touches = (i == 0 && !allowed(b.x0))
|
||||
|| (i + 1 == nx && !allowed(b.x1))
|
||||
|| (j == 0 && !allowed(b.y0))
|
||||
|| (j + 1 == ny && !allowed(b.y1))
|
||||
|| (k == 0 && !allowed(b.z0))
|
||||
|| (k + 1 == nz && !allowed(b.z1));
|
||||
if touches {
|
||||
return Err(format!(
|
||||
"embedded body reaches a domain side that is not a Velocity/Periodic side at cell ({k}, {j}, {i})"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(anchor) = anchor else {
|
||||
return Err("embedded body covers the whole domain".into());
|
||||
};
|
||||
let h_min = dx.min(dy).min(dz);
|
||||
let reach = 1.5 * h_min;
|
||||
let eps = 1e-6 * h_min;
|
||||
let is_fluid = |k: usize, j: usize, i: usize| cell_fluid[g.cell(k, j, i)];
|
||||
let mut u_kind = vec![FaceKind::Fluid; g.n_ufaces()];
|
||||
let mut v_kind = vec![FaceKind::Fluid; g.n_vfaces()];
|
||||
let mut w_kind = vec![FaceKind::Fluid; g.n_wfaces()];
|
||||
let kind_of = |phi: f64| {
|
||||
if phi > -reach {
|
||||
FaceKind::Ghost
|
||||
} else {
|
||||
FaceKind::Solid
|
||||
}
|
||||
};
|
||||
for k in 0..nz {
|
||||
for j in 0..ny {
|
||||
for i in 1..nx {
|
||||
if !(is_fluid(k, j, i - 1) && is_fluid(k, j, i)) {
|
||||
u_kind[g.uface(k, j, i)] =
|
||||
kind_of(body.phi(i as f64 * dx, yc(j), zc(k), t));
|
||||
}
|
||||
}
|
||||
}
|
||||
for j in 1..ny {
|
||||
for i in 0..nx {
|
||||
if !(is_fluid(k, j - 1, i) && is_fluid(k, j, i)) {
|
||||
v_kind[g.vface(k, j, i)] =
|
||||
kind_of(body.phi(xc(i), j as f64 * dy, zc(k), t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let w_range = if periodic { 0..nz } else { 1..nz };
|
||||
for k in w_range.clone() {
|
||||
let below = if k > 0 { k - 1 } else { nz - 1 };
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
if !(is_fluid(below, j, i) && is_fluid(k, j, i)) {
|
||||
w_kind[g.wface(k, j, i)] =
|
||||
kind_of(body.phi(xc(i), yc(j), k as f64 * dz, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if periodic {
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
w_kind[g.wface(nz, j, i)] = w_kind[g.wface(0, j, i)];
|
||||
}
|
||||
}
|
||||
}
|
||||
let face_pos = |c: usize, k: usize, j: usize, i: usize| match c {
|
||||
0 => (i as f64 * dx, yc(j), zc(k)),
|
||||
1 => (xc(i), j as f64 * dy, zc(k)),
|
||||
_ => (xc(i), yc(j), k as f64 * dz),
|
||||
};
|
||||
let kji_of = |c: usize, idx: usize| -> (usize, usize, usize) {
|
||||
match c {
|
||||
0 => (idx / ((nx + 1) * ny), (idx / (nx + 1)) % ny, idx % (nx + 1)),
|
||||
1 => (idx / (nx * (ny + 1)), (idx / nx) % (ny + 1), idx % nx),
|
||||
_ => (idx / (nx * ny), (idx / nx) % ny, idx % nx),
|
||||
}
|
||||
};
|
||||
let build_ghost = |c: usize,
|
||||
kinds: &[FaceKind],
|
||||
idx: usize,
|
||||
(x, y, z): (f64, f64, f64),
|
||||
flux_sign: f64|
|
||||
-> Ghost {
|
||||
let s_face = body.phi(x, y, z, t);
|
||||
let (n1, n2, n3) = body.normal(x, y, z, t, eps);
|
||||
let foot = (x - s_face * n1, y - s_face * n2, z - s_face * n3);
|
||||
let s_probe = s_face.abs() + h_min;
|
||||
let probe = (
|
||||
foot.0 + s_probe * n1,
|
||||
foot.1 + s_probe * n2,
|
||||
foot.2 + s_probe * n3,
|
||||
);
|
||||
let vel = body.surface_velocity(foot.0, foot.1, foot.2, t);
|
||||
let u_surface = [vel.0, vel.1, vel.2][c];
|
||||
let nodes = stencil_nodes(probe, c, g, periodic, |nidx| {
|
||||
if kinds[nidx] == FaceKind::Fluid {
|
||||
None
|
||||
} else {
|
||||
let (kk, jj, ii) = kji_of(c, nidx);
|
||||
let (px, py, pz) = face_pos(c, kk, jj, ii);
|
||||
let s = body.phi(px, py, pz, t);
|
||||
let (m1, m2, m3) = body.normal(px, py, pz, t, eps);
|
||||
let f = body.surface_velocity(px - s * m1, py - s * m2, pz - s * m3, t);
|
||||
Some([f.0, f.1, f.2][c])
|
||||
}
|
||||
});
|
||||
Ghost {
|
||||
idx,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
foot,
|
||||
u_surface,
|
||||
s_face,
|
||||
s_probe,
|
||||
nodes,
|
||||
flux_sign,
|
||||
}
|
||||
};
|
||||
let mut u_ghosts = Vec::new();
|
||||
let mut v_ghosts = Vec::new();
|
||||
let mut w_ghosts = Vec::new();
|
||||
for k in 0..nz {
|
||||
for j in 0..ny {
|
||||
for i in 1..nx {
|
||||
let idx = g.uface(k, j, i);
|
||||
if u_kind[idx] == FaceKind::Ghost {
|
||||
let sign = if is_fluid(k, j, i - 1) {
|
||||
1.0
|
||||
} else if is_fluid(k, j, i) {
|
||||
-1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
u_ghosts.push(build_ghost(0, &u_kind, idx, face_pos(0, k, j, i), sign));
|
||||
}
|
||||
}
|
||||
}
|
||||
for j in 1..ny {
|
||||
for i in 0..nx {
|
||||
let idx = g.vface(k, j, i);
|
||||
if v_kind[idx] == FaceKind::Ghost {
|
||||
let sign = if is_fluid(k, j - 1, i) {
|
||||
1.0
|
||||
} else if is_fluid(k, j, i) {
|
||||
-1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
v_ghosts.push(build_ghost(1, &v_kind, idx, face_pos(1, k, j, i), sign));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for k in w_range {
|
||||
let below = if k > 0 { k - 1 } else { nz - 1 };
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
let idx = g.wface(k, j, i);
|
||||
if w_kind[idx] == FaceKind::Ghost {
|
||||
let sign = if is_fluid(below, j, i) {
|
||||
1.0
|
||||
} else if is_fluid(k, j, i) {
|
||||
-1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
w_ghosts.push(build_ghost(2, &w_kind, idx, face_pos(2, k, j, i), sign));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Self {
|
||||
grid: g,
|
||||
periodic_z: periodic,
|
||||
cell_fluid,
|
||||
u_kind,
|
||||
v_kind,
|
||||
w_kind,
|
||||
u_ghosts,
|
||||
v_ghosts,
|
||||
w_ghosts,
|
||||
anchor,
|
||||
fluid_cells,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_fluid_cell(&self, idx: usize) -> bool {
|
||||
self.cell_fluid[idx]
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn u_kind(&self, idx: usize) -> FaceKind {
|
||||
self.u_kind[idx]
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn v_kind(&self, idx: usize) -> FaceKind {
|
||||
self.v_kind[idx]
|
||||
}
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn w_kind(&self, idx: usize) -> FaceKind {
|
||||
self.w_kind[idx]
|
||||
}
|
||||
#[must_use]
|
||||
pub fn anchor(&self) -> usize {
|
||||
self.anchor
|
||||
}
|
||||
#[must_use]
|
||||
pub fn fluid_cells(&self) -> usize {
|
||||
self.fluid_cells
|
||||
}
|
||||
#[must_use]
|
||||
pub fn ghost_faces(&self) -> usize {
|
||||
self.u_ghosts.len() + self.v_ghosts.len() + self.w_ghosts.len()
|
||||
}
|
||||
#[must_use]
|
||||
pub fn grid(&self) -> Grid {
|
||||
self.grid
|
||||
}
|
||||
#[must_use]
|
||||
pub fn periodic_z(&self) -> bool {
|
||||
self.periodic_z
|
||||
}
|
||||
|
||||
/// Impose the wall on `(u, v, w)` from the same field.
|
||||
pub fn impose(&self, body: &Body, u: &mut [f64], v: &mut [f64], w: &mut [f64], t: f64) -> f64 {
|
||||
let (us, vs, ws) = (u.to_vec(), v.to_vec(), w.to_vec());
|
||||
self.impose_from(body, &us, &vs, &ws, u, v, w, t)
|
||||
}
|
||||
|
||||
/// Solid faces: the surface velocity; ghost faces: the reconstruction
|
||||
/// from the SOURCE field, minus the shared flux compatibility
|
||||
/// correction over the flux-carrying ghosts. Returns the correction.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn impose_from(
|
||||
&self,
|
||||
body: &Body,
|
||||
u_src: &[f64],
|
||||
v_src: &[f64],
|
||||
w_src: &[f64],
|
||||
u: &mut [f64],
|
||||
v: &mut [f64],
|
||||
w: &mut [f64],
|
||||
t: f64,
|
||||
) -> f64 {
|
||||
let g = self.grid;
|
||||
let (nx, ny, nz, dx, dy, dz) = (g.nx, g.ny, g.nz, g.dx, g.dy, g.dz);
|
||||
for k in 0..nz {
|
||||
for j in 0..ny {
|
||||
for i in 1..nx {
|
||||
let idx = g.uface(k, j, i);
|
||||
if self.u_kind[idx] == FaceKind::Solid {
|
||||
u[idx] = body
|
||||
.surface_velocity(
|
||||
i as f64 * dx,
|
||||
(j as f64 + 0.5) * dy,
|
||||
(k as f64 + 0.5) * dz,
|
||||
t,
|
||||
)
|
||||
.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
for j in 1..ny {
|
||||
for i in 0..nx {
|
||||
let idx = g.vface(k, j, i);
|
||||
if self.v_kind[idx] == FaceKind::Solid {
|
||||
v[idx] = body
|
||||
.surface_velocity(
|
||||
(i as f64 + 0.5) * dx,
|
||||
j as f64 * dy,
|
||||
(k as f64 + 0.5) * dz,
|
||||
t,
|
||||
)
|
||||
.1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for k in 0..=nz {
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
let idx = g.wface(k, j, i);
|
||||
if self.w_kind[idx] == FaceKind::Solid {
|
||||
w[idx] = body
|
||||
.surface_velocity(
|
||||
(i as f64 + 0.5) * dx,
|
||||
(j as f64 + 0.5) * dy,
|
||||
k as f64 * dz,
|
||||
t,
|
||||
)
|
||||
.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let u_vals: Vec<f64> = self
|
||||
.u_ghosts
|
||||
.iter()
|
||||
.map(|gh| gh.reconstruct(u_src))
|
||||
.collect();
|
||||
let v_vals: Vec<f64> = self
|
||||
.v_ghosts
|
||||
.iter()
|
||||
.map(|gh| gh.reconstruct(v_src))
|
||||
.collect();
|
||||
let w_vals: Vec<f64> = self
|
||||
.w_ghosts
|
||||
.iter()
|
||||
.map(|gh| gh.reconstruct(w_src))
|
||||
.collect();
|
||||
let (au, av, aw) = (dy * dz, dx * dz, dx * dy);
|
||||
let mut net = 0.0;
|
||||
let mut area = 0.0;
|
||||
for (gh, &val) in self.u_ghosts.iter().zip(&u_vals) {
|
||||
if gh.flux_sign != 0.0 {
|
||||
net += gh.flux_sign * val * au;
|
||||
area += au;
|
||||
}
|
||||
}
|
||||
for (gh, &val) in self.v_ghosts.iter().zip(&v_vals) {
|
||||
if gh.flux_sign != 0.0 {
|
||||
net += gh.flux_sign * val * av;
|
||||
area += av;
|
||||
}
|
||||
}
|
||||
for (gh, &val) in self.w_ghosts.iter().zip(&w_vals) {
|
||||
if gh.flux_sign != 0.0 {
|
||||
net += gh.flux_sign * val * aw;
|
||||
area += aw;
|
||||
}
|
||||
}
|
||||
let correction = if area > 0.0 { net / area } else { 0.0 };
|
||||
for (gh, &val) in self.u_ghosts.iter().zip(&u_vals) {
|
||||
u[gh.idx] = val - gh.flux_sign * correction;
|
||||
}
|
||||
for (gh, &val) in self.v_ghosts.iter().zip(&v_vals) {
|
||||
v[gh.idx] = val - gh.flux_sign * correction;
|
||||
}
|
||||
for (gh, &val) in self.w_ghosts.iter().zip(&w_vals) {
|
||||
w[gh.idx] = val - gh.flux_sign * correction;
|
||||
}
|
||||
// The periodic seam: the w face at k = nz is the face at k = 0.
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
let (f0, fn_) = (g.wface(0, j, i), g.wface(nz, j, i));
|
||||
if self.w_kind[f0] != FaceKind::Fluid && self.w_kind[fn_] == self.w_kind[f0] {
|
||||
w[fn_] = w[f0];
|
||||
}
|
||||
}
|
||||
}
|
||||
correction
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user