Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (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 / 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_0116sg1Qz1gMv9hdcKP1XUam
509 lines
21 KiB
Rust
509 lines
21 KiB
Rust
//! P4 option B (`docs/overset_metal_campaign.md` §5.11): the momentum
|
||
//! residual of the background's OWN staggered predictor stencil on every
|
||
//! background face, at a settled state.
|
||
//!
|
||
//! On a solved face the discrete equation the composite marched is
|
||
//! `ρ (u^{n+1} − u^n)/dt = ρ · rhs(u^n, p^{n+1})` (predictor plus the
|
||
//! correctors' `−dt ∇p'/ρ`, with `p^{n+1} = p^n + Σ p'`), so the residual
|
||
//! `r = ρ [(u^{n+1} − u^n)/dt − rhs] · dx dy` is zero to rounding there
|
||
//! — the pin that proves the diagnostic IS the solver's operator. On a
|
||
//! PRESCRIBED face the value is stamped from the patch, the equation is
|
||
//! not solved, and `r` is the momentum source the stamping injects, in the
|
||
//! solver's own metric and without the staircase curves' face-formula
|
||
//! error. Summed over the ring it is the fringe ring's momentum defect
|
||
//! (`region_force` ring outer − hole boundary, but exact).
|
||
//!
|
||
//! Validity is decided by the stencil itself: the background field is
|
||
//! copied with `NaN` on every value that is neither the solver's own nor
|
||
//! stamped from the patch (hole cells and hole–hole faces within two cells
|
||
//! of the ring get the patch's interpolated values, `OverlapMap::hole_p`
|
||
//! / `ghost_u` / `ghost_v`, from a band widened three rows into the
|
||
//! hole), the
|
||
//! operator is evaluated as is, and a `NaN` result means the face read
|
||
//! something invalid and is not counted. Under the upwind scheme every
|
||
//! value the stencil reads enters its arithmetic, so the test is exact.
|
||
|
||
use std::collections::HashSet;
|
||
|
||
use super::overlap::CellClass;
|
||
use super::{OversetField, OversetPisoSolver};
|
||
use crate::solvers::incompressible::embedded_body::FaceKind;
|
||
|
||
/// Sums over one class of faces.
|
||
#[derive(Debug, Clone, Copy, Default)]
|
||
pub struct ResidualBucket {
|
||
/// `Σ r` on the u faces (x-momentum source, N/m).
|
||
pub fx: f64,
|
||
/// `Σ r` on the v faces.
|
||
pub fy: f64,
|
||
/// `Σ |r|` on the u faces.
|
||
pub abs_x: f64,
|
||
/// `Σ |r|` on the v faces.
|
||
pub abs_y: f64,
|
||
/// Largest `|r|` on the u faces.
|
||
pub max_abs_x: f64,
|
||
/// Largest `|r|` on the v faces.
|
||
pub max_abs_y: f64,
|
||
/// Faces whose stencil read only valid values.
|
||
pub evaluated: usize,
|
||
/// Of `evaluated`, the u faces.
|
||
pub evaluated_u: usize,
|
||
/// Of `evaluated`, the v faces.
|
||
pub evaluated_v: usize,
|
||
/// Faces of this class.
|
||
pub total: usize,
|
||
}
|
||
|
||
impl ResidualBucket {
|
||
fn add(&mut self, r: f64, is_u: bool) {
|
||
self.total += 1;
|
||
if !r.is_finite() {
|
||
return;
|
||
}
|
||
self.evaluated += 1;
|
||
if is_u {
|
||
self.evaluated_u += 1;
|
||
self.fx += r;
|
||
self.abs_x += r.abs();
|
||
self.max_abs_x = self.max_abs_x.max(r.abs());
|
||
} else {
|
||
self.evaluated_v += 1;
|
||
self.fy += r;
|
||
self.abs_y += r.abs();
|
||
self.max_abs_y = self.max_abs_y.max(r.abs());
|
||
}
|
||
}
|
||
}
|
||
|
||
/// One prescribed face's residual.
|
||
#[derive(Debug, Clone, Copy)]
|
||
pub struct FaceResidual {
|
||
/// A u face (x-momentum) or a v face.
|
||
pub is_u: bool,
|
||
/// Row.
|
||
pub j: usize,
|
||
/// Column.
|
||
pub i: usize,
|
||
/// The residual (N/m), `NaN` when not evaluable.
|
||
pub r: f64,
|
||
/// Between two fringe cells (else fringe–hole).
|
||
pub fringe_fringe: bool,
|
||
/// The pieces of `r` (N/m): unsteady, convective, diffusive, pressure
|
||
/// (`r = time + conv − diff + pres` under upwind on interior faces;
|
||
/// under a limited scheme the convective piece is the upwind part
|
||
/// only and the four do not reconstruct `r`).
|
||
pub pieces: [f64; 4],
|
||
}
|
||
|
||
/// The momentum residual by face class.
|
||
#[derive(Debug, Clone, Default)]
|
||
pub struct MomentumResidual {
|
||
/// Solved faces whose stencil reads only solved values.
|
||
pub solved_far: ResidualBucket,
|
||
/// Solved faces whose stencil reads a fringe cell or a prescribed face.
|
||
pub solved_near: ResidualBucket,
|
||
/// Prescribed faces between two fringe cells (tangential to the ring).
|
||
pub fringe_fringe: ResidualBucket,
|
||
/// Prescribed faces between a fringe and a hole cell (normal to it).
|
||
pub fringe_hole: ResidualBucket,
|
||
/// Prescribed faces between two hole cells: not evaluated (their
|
||
/// control volume lies in the hole).
|
||
pub hole_hole_skipped: usize,
|
||
/// Hole cells given a ghost pressure.
|
||
pub hole_ghosts: usize,
|
||
/// Hole–hole faces given a ghost velocity (beyond the solver's stamps).
|
||
pub ghost_faces: usize,
|
||
/// Solved u faces between an active and a fringe cell (the ring's outer
|
||
/// boundary); their residual is the pressure LEVEL offset `δ · h`.
|
||
pub interface_u: usize,
|
||
/// See `interface_u`.
|
||
pub interface_v: usize,
|
||
/// Every prescribed fringe–fringe / fringe–hole face's residual.
|
||
pub prescribed: Vec<FaceResidual>,
|
||
}
|
||
|
||
impl MomentumResidual {
|
||
/// The composite's pressure level offset `δ` (Pa) between the active
|
||
/// cells and the re-stamped fringe: `max |r| / h` over the interface.
|
||
pub fn level_offset(&self, h: f64) -> f64 {
|
||
self.solved_near.max_abs_x.max(self.solved_near.max_abs_y) / h
|
||
}
|
||
}
|
||
|
||
impl OversetPisoSolver {
|
||
/// The momentum residual of the background's own predictor stencil on
|
||
/// every interior background face, after [`Self::advance`] (the field
|
||
/// holds `u^{n+1}`, `u_old = u^n`, `p = p^{n+1}` with the fringe
|
||
/// re-stamped). `dt` is the step just taken.
|
||
pub fn momentum_residual(&self, field: &OversetField, dt: f64) -> MomentumResidual {
|
||
let (nx, ny, dx, dy) = self.grid;
|
||
let rho = self.background.config().density;
|
||
let t_old = self.background.time() - dt;
|
||
let mask = self
|
||
.background
|
||
.mask()
|
||
.expect("the overset background carries a mask");
|
||
let map = &self.overlap;
|
||
let hole = |j: usize, i: usize| map.class(j, i) == CellClass::Hole;
|
||
|
||
// The masked copy.
|
||
let mut m = field.background.clone();
|
||
for j in 0..ny {
|
||
for i in 0..nx {
|
||
if hole(j, i) {
|
||
m.p[(j, i)] = f64::NAN;
|
||
}
|
||
}
|
||
}
|
||
let ghosts = map.hole_p_values(&field.patch.p);
|
||
map.stamp_hole_p(&mut m.p, &ghosts);
|
||
map.stamp_ghost_faces(&mut m, &field.patch.u, &field.patch.v);
|
||
let prescribed_u: HashSet<(usize, usize)> = map
|
||
.fringe_u
|
||
.iter()
|
||
.chain(&map.ghost_u)
|
||
.map(|e| (e.j, e.i))
|
||
.collect();
|
||
let prescribed_v: HashSet<(usize, usize)> = map
|
||
.fringe_v
|
||
.iter()
|
||
.chain(&map.ghost_v)
|
||
.map(|e| (e.j, e.i))
|
||
.collect();
|
||
for j in 0..ny {
|
||
for i in 0..=nx {
|
||
let valid = (i > 0 && !hole(j, i - 1))
|
||
|| (i < nx && !hole(j, i))
|
||
|| prescribed_u.contains(&(j, i));
|
||
if !valid {
|
||
m.u[(j, i)] = f64::NAN;
|
||
m.u_old[(j, i)] = f64::NAN;
|
||
}
|
||
}
|
||
}
|
||
for j in 0..=ny {
|
||
for i in 0..nx {
|
||
let valid = (j > 0 && !hole(j - 1, i))
|
||
|| (j < ny && !hole(j, i))
|
||
|| prescribed_v.contains(&(j, i));
|
||
if !valid {
|
||
m.v[(j, i)] = f64::NAN;
|
||
m.v_old[(j, i)] = f64::NAN;
|
||
}
|
||
}
|
||
}
|
||
|
||
let ghost_u = |j: usize, i: usize| mask.u_kind(j, i) == FaceKind::Ghost;
|
||
let ghost_v = |j: usize, i: usize| mask.v_kind(j, i) == FaceKind::Ghost;
|
||
let active = |j: usize, i: usize| map.class(j, i) == CellClass::Active;
|
||
let mut out = MomentumResidual {
|
||
hole_ghosts: map.hole_p.len(),
|
||
ghost_faces: map.ghost_u.len() + map.ghost_v.len(),
|
||
..MomentumResidual::default()
|
||
};
|
||
let vol = dx * dy;
|
||
let mut prescribed = Vec::new();
|
||
// Under a limited scheme the far-upwind value enters through
|
||
// `r > 0`, which a NaN fails silently (a silent upwind fallback),
|
||
// so the two-away neighbours are checked explicitly.
|
||
let limited = self.background.parameters().convection_scheme
|
||
!= crate::solvers::incompressible::ConvectionScheme::Upwind;
|
||
let far_ok_u =
|
||
|m: &crate::solvers::incompressible::flow_field::FlowField, j: usize, i: usize| {
|
||
!limited
|
||
|| ((i < 2 || m.u_old[(j, i - 2)].is_finite())
|
||
&& (i + 2 > nx || m.u_old[(j, i + 2)].is_finite())
|
||
&& (j < 2 || m.u_old[(j - 2, i)].is_finite())
|
||
&& (j + 2 >= ny || m.u_old[(j + 2, i)].is_finite()))
|
||
};
|
||
let far_ok_v =
|
||
|m: &crate::solvers::incompressible::flow_field::FlowField, j: usize, i: usize| {
|
||
!limited
|
||
|| ((j < 2 || m.v_old[(j - 2, i)].is_finite())
|
||
&& (j + 2 > ny || m.v_old[(j + 2, i)].is_finite())
|
||
&& (i < 2 || m.v_old[(j, i - 2)].is_finite())
|
||
&& (i + 2 >= nx || m.v_old[(j, i + 2)].is_finite()))
|
||
};
|
||
let mu = self.background.config().viscosity;
|
||
let upw = |f: f64, a: f64, b: f64| if f >= 0.0 { a } else { b };
|
||
// The upwind predictor's pieces on an interior u face, × ρ·vol.
|
||
let u_pieces = |m: &crate::solvers::incompressible::flow_field::FlowField,
|
||
j: usize,
|
||
i: usize| {
|
||
let (u, v) = (&m.u_old, &m.v_old);
|
||
let ue = 0.5 * (u[(j, i)] + u[(j, i + 1)]);
|
||
let uw = 0.5 * (u[(j, i - 1)] + u[(j, i)]);
|
||
let vn = 0.5 * (v[(j + 1, i - 1)] + v[(j + 1, i)]);
|
||
let vs = 0.5 * (v[(j, i - 1)] + v[(j, i)]);
|
||
let conv = (ue * upw(ue, u[(j, i)], u[(j, i + 1)])
|
||
- uw * upw(uw, u[(j, i - 1)], u[(j, i)]))
|
||
/ dx
|
||
+ (vn * upw(vn, u[(j, i)], u[(j + 1, i)]) - vs * upw(vs, u[(j - 1, i)], u[(j, i)]))
|
||
/ dy;
|
||
let diff = (u[(j, i + 1)] - 2.0 * u[(j, i)] + u[(j, i - 1)]) / (dx * dx)
|
||
+ (u[(j + 1, i)] - 2.0 * u[(j, i)] + u[(j - 1, i)]) / (dy * dy);
|
||
let pres = (m.p[(j, i)] - m.p[(j, i - 1)]) / dx;
|
||
[
|
||
rho * (m.u[(j, i)] - m.u_old[(j, i)]) / dt * vol,
|
||
rho * conv * vol,
|
||
mu * diff * vol,
|
||
pres * vol,
|
||
]
|
||
};
|
||
let v_pieces = |m: &crate::solvers::incompressible::flow_field::FlowField,
|
||
j: usize,
|
||
i: usize| {
|
||
let (u, v) = (&m.u_old, &m.v_old);
|
||
let vn = 0.5 * (v[(j, i)] + v[(j + 1, i)]);
|
||
let vs = 0.5 * (v[(j - 1, i)] + v[(j, i)]);
|
||
let ue = 0.5 * (u[(j - 1, i + 1)] + u[(j, i + 1)]);
|
||
let uw = 0.5 * (u[(j - 1, i)] + u[(j, i)]);
|
||
let conv = (vn * upw(vn, v[(j, i)], v[(j + 1, i)])
|
||
- vs * upw(vs, v[(j - 1, i)], v[(j, i)]))
|
||
/ dy
|
||
+ (ue * upw(ue, v[(j, i)], v[(j, i + 1)]) - uw * upw(uw, v[(j, i - 1)], v[(j, i)]))
|
||
/ dx;
|
||
let diff = (v[(j + 1, i)] - 2.0 * v[(j, i)] + v[(j - 1, i)]) / (dy * dy)
|
||
+ (v[(j, i + 1)] - 2.0 * v[(j, i)] + v[(j, i - 1)]) / (dx * dx);
|
||
let pres = (m.p[(j, i)] - m.p[(j - 1, i)]) / dy;
|
||
[
|
||
rho * (m.v[(j, i)] - m.v_old[(j, i)]) / dt * vol,
|
||
rho * conv * vol,
|
||
mu * diff * vol,
|
||
pres * vol,
|
||
]
|
||
};
|
||
|
||
// u faces (j, i), i = 1..nx: cells (j, i−1) | (j, i).
|
||
for j in 0..ny {
|
||
for i in 1..nx {
|
||
let (w, e) = (map.class(j, i - 1), map.class(j, i));
|
||
if (w == CellClass::Active) != (e == CellClass::Active) {
|
||
out.interface_u += 1;
|
||
}
|
||
let bucket = if !ghost_u(j, i) {
|
||
// Stencil: u (j, i±1), (j±1, i); v (j, i−1), (j, i), (j+1, i−1), (j+1, i); p (j, i−1), (j, i).
|
||
let near = !active(j, i - 1)
|
||
|| !active(j, i)
|
||
|| ghost_u(j, i - 1)
|
||
|| ghost_u(j, i + 1)
|
||
|| (j > 0 && ghost_u(j - 1, i))
|
||
|| (j + 1 < ny && ghost_u(j + 1, i))
|
||
|| ghost_v(j, i - 1)
|
||
|| ghost_v(j, i)
|
||
|| ghost_v(j + 1, i - 1)
|
||
|| ghost_v(j + 1, i);
|
||
if near {
|
||
&mut out.solved_near
|
||
} else {
|
||
&mut out.solved_far
|
||
}
|
||
} else {
|
||
match (w, e) {
|
||
(CellClass::Fringe, CellClass::Fringe) => &mut out.fringe_fringe,
|
||
(CellClass::Hole, CellClass::Hole) => {
|
||
out.hole_hole_skipped += 1;
|
||
continue;
|
||
}
|
||
_ => &mut out.fringe_hole,
|
||
}
|
||
};
|
||
let rhs = self.background.u_rhs(&m, j, i, t_old);
|
||
let mut r = rho * ((m.u[(j, i)] - m.u_old[(j, i)]) / dt - rhs) * vol;
|
||
if !far_ok_u(&m, j, i) {
|
||
r = f64::NAN;
|
||
}
|
||
bucket.add(r, true);
|
||
if ghost_u(j, i) {
|
||
prescribed.push(FaceResidual {
|
||
is_u: true,
|
||
j,
|
||
i,
|
||
r,
|
||
fringe_fringe: w == CellClass::Fringe && e == CellClass::Fringe,
|
||
pieces: u_pieces(&m, j, i),
|
||
});
|
||
}
|
||
}
|
||
}
|
||
// v faces (j, i), j = 1..ny: cells (j−1, i) | (j, i).
|
||
for j in 1..ny {
|
||
for i in 0..nx {
|
||
let (s, n) = (map.class(j - 1, i), map.class(j, i));
|
||
if (s == CellClass::Active) != (n == CellClass::Active) {
|
||
out.interface_v += 1;
|
||
}
|
||
let bucket = if !ghost_v(j, i) {
|
||
let near = !active(j - 1, i)
|
||
|| !active(j, i)
|
||
|| ghost_v(j - 1, i)
|
||
|| ghost_v(j + 1, i)
|
||
|| (i > 0 && ghost_v(j, i - 1))
|
||
|| (i + 1 < nx && ghost_v(j, i + 1))
|
||
|| ghost_u(j - 1, i)
|
||
|| ghost_u(j, i)
|
||
|| ghost_u(j - 1, i + 1)
|
||
|| ghost_u(j, i + 1);
|
||
if near {
|
||
&mut out.solved_near
|
||
} else {
|
||
&mut out.solved_far
|
||
}
|
||
} else {
|
||
match (s, n) {
|
||
(CellClass::Fringe, CellClass::Fringe) => &mut out.fringe_fringe,
|
||
(CellClass::Hole, CellClass::Hole) => {
|
||
out.hole_hole_skipped += 1;
|
||
continue;
|
||
}
|
||
_ => &mut out.fringe_hole,
|
||
}
|
||
};
|
||
let rhs = self.background.v_rhs(&m, j, i, t_old);
|
||
let mut r = rho * ((m.v[(j, i)] - m.v_old[(j, i)]) / dt - rhs) * vol;
|
||
if !far_ok_v(&m, j, i) {
|
||
r = f64::NAN;
|
||
}
|
||
bucket.add(r, false);
|
||
if ghost_v(j, i) {
|
||
prescribed.push(FaceResidual {
|
||
is_u: false,
|
||
j,
|
||
i,
|
||
r,
|
||
fringe_fringe: s == CellClass::Fringe && n == CellClass::Fringe,
|
||
pieces: v_pieces(&m, j, i),
|
||
});
|
||
}
|
||
}
|
||
}
|
||
out.prescribed = prescribed;
|
||
out
|
||
}
|
||
}
|
||
|
||
impl OversetPisoSolver {
|
||
/// The force on everything inside the box `(i0, i1, j0, j1)` (cell
|
||
/// index bounds, as `EmbeddedMask::control_volume_force`) in the
|
||
/// SOLVER'S OWN flux form: the predictor's convective flux (upwind
|
||
/// plus the scheme's limited correction),
|
||
/// its diffusive flux and the cell pressure, on the momentum control
|
||
/// volumes' faces that make up the box boundary (u volumes `i0 + 1
|
||
/// ..= i1` × `j0 .. j1`, v volumes `i0 .. i1` × `j0 + 1 ..= j1`),
|
||
/// minus the unsteady term over the box's evaluable volumes. On the
|
||
/// solved faces the residual is rounding, so this is box-INDEPENDENT
|
||
/// to rounding as long as the box stays in the active region — the
|
||
/// gate — whereas the control-volume formula moves by ±0.5 % between
|
||
/// boxes. Returns `(fx, fy)`, positive = drag / lift on the body.
|
||
pub fn solver_metric_force(
|
||
&self,
|
||
field: &OversetField,
|
||
dt: f64,
|
||
(i0, i1, j0, j1): (usize, usize, usize, usize),
|
||
) -> (f64, f64) {
|
||
let (nx, ny, dx, dy) = self.grid;
|
||
assert!(
|
||
i0 >= 1 && i1 + 1 < nx && j0 >= 1 && j1 + 1 < ny,
|
||
"box must be interior"
|
||
);
|
||
let rho = self.background.config().density;
|
||
let mu = self.background.config().viscosity;
|
||
let bg = &field.background;
|
||
let (u, v, p) = (&bg.u_old, &bg.v_old, &bg.p);
|
||
// The predictor's face value: upwind plus the scheme's limited
|
||
// correction with the same far-upwind choice (`u_rhs`), so the
|
||
// flux form is the solver's under upwind AND under TVD.
|
||
let scheme = self.background.parameters().convection_scheme;
|
||
let face = |f: f64, far_up: Option<f64>, up: f64, down: f64, far_dn: Option<f64>| {
|
||
if f >= 0.0 {
|
||
up + scheme.face_correction(far_up, up, down)
|
||
} else {
|
||
down + scheme.face_correction(far_dn, down, up)
|
||
}
|
||
};
|
||
// Outward x-momentum flux through the u-volume face at cell i's
|
||
// centre (n = +x), per unit length.
|
||
let phi_u_x = |j: usize, i: usize| {
|
||
let ue = 0.5 * (u[(j, i)] + u[(j, i + 1)]);
|
||
let uf = face(
|
||
ue,
|
||
(i >= 1).then(|| u[(j, i - 1)]),
|
||
u[(j, i)],
|
||
u[(j, i + 1)],
|
||
(i + 2 <= nx).then(|| u[(j, i + 2)]),
|
||
);
|
||
rho * ue * uf - mu * (u[(j, i + 1)] - u[(j, i)]) / dx + p[(j, i)]
|
||
};
|
||
// Through the u-volume face at v-face row j (n = +y), for u face i.
|
||
let phi_u_y = |j: usize, i: usize| {
|
||
let vn = 0.5 * (v[(j, i - 1)] + v[(j, i)]);
|
||
let uf = face(
|
||
vn,
|
||
(j >= 2).then(|| u[(j - 2, i)]),
|
||
u[(j - 1, i)],
|
||
u[(j, i)],
|
||
(j + 1 < ny).then(|| u[(j + 1, i)]),
|
||
);
|
||
rho * vn * uf - mu * (u[(j, i)] - u[(j - 1, i)]) / dy
|
||
};
|
||
// y-momentum: through the v-volume face at cell j's centre (n = +y).
|
||
let phi_v_y = |j: usize, i: usize| {
|
||
let vn = 0.5 * (v[(j, i)] + v[(j + 1, i)]);
|
||
let vf = face(
|
||
vn,
|
||
(j >= 1).then(|| v[(j - 1, i)]),
|
||
v[(j, i)],
|
||
v[(j + 1, i)],
|
||
(j + 2 <= ny).then(|| v[(j + 2, i)]),
|
||
);
|
||
rho * vn * vf - mu * (v[(j + 1, i)] - v[(j, i)]) / dy + p[(j, i)]
|
||
};
|
||
// Through the v-volume face at u-face column i (n = +x), for v face j.
|
||
let phi_v_x = |j: usize, i: usize| {
|
||
let ue = 0.5 * (u[(j - 1, i)] + u[(j, i)]);
|
||
let vf = face(
|
||
ue,
|
||
(i >= 2).then(|| v[(j, i - 2)]),
|
||
v[(j, i - 1)],
|
||
v[(j, i)],
|
||
(i + 1 < nx).then(|| v[(j, i + 1)]),
|
||
);
|
||
rho * ue * vf - mu * (v[(j, i)] - v[(j, i - 1)]) / dx
|
||
};
|
||
let vol = dx * dy;
|
||
let (mut out_x, mut out_y) = (0.0, 0.0);
|
||
let (mut dt_x, mut dt_y) = (0.0, 0.0);
|
||
for j in j0..j1 {
|
||
out_x += (phi_u_x(j, i1) - phi_u_x(j, i0)) * dy;
|
||
}
|
||
for i in i0 + 1..=i1 {
|
||
out_x += (phi_u_y(j1, i) - phi_u_y(j0, i)) * dx;
|
||
for j in j0..j1 {
|
||
let d = bg.u[(j, i)] - bg.u_old[(j, i)];
|
||
if d.is_finite()
|
||
&& (self.overlap.class(j, i) != CellClass::Hole
|
||
|| self.overlap.class(j, i - 1) != CellClass::Hole)
|
||
{
|
||
dt_x += rho * d / dt * vol;
|
||
}
|
||
}
|
||
}
|
||
for i in i0..i1 {
|
||
out_y += (phi_v_y(j1, i) - phi_v_y(j0, i)) * dx;
|
||
}
|
||
for j in j0 + 1..=j1 {
|
||
out_y += (phi_v_x(j, i1) - phi_v_x(j, i0)) * dy;
|
||
for i in i0..i1 {
|
||
let d = bg.v[(j, i)] - bg.v_old[(j, i)];
|
||
if d.is_finite()
|
||
&& (self.overlap.class(j, i) != CellClass::Hole
|
||
|| self.overlap.class(j - 1, i) != CellClass::Hole)
|
||
{
|
||
dt_y += rho * d / dt * vol;
|
||
}
|
||
}
|
||
}
|
||
(-out_x - dt_x, -out_y - dt_y)
|
||
}
|
||
}
|