Performance Benchmarks / Run Benchmarks (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
Documentation / Build User Guide (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
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01X2GmJXeQ2njUecEKiJZ1G2
150 lines
7.1 KiB
Rust
150 lines
7.1 KiB
Rust
//! P4 option B, patch side (`docs/overset_metal_campaign.md` §5.11): the
|
||
//! patch's own momentum balance on its SOLVED cells (everything but the
|
||
//! acceptor row), in the scheme's own fluxes.
|
||
//!
|
||
//! The marched equation on a solved cell is the face-flux form of
|
||
//! `predictor.rs` (outward `ρ F_f u_f` with the scheme's face value,
|
||
//! outward `μ L_f(u)` in Laplacian form) plus the projection's pressure
|
||
//! force, which is the least-squares CELL gradient `−A_c ∇p_c` — not a
|
||
//! face pressure, so the pressure term does not telescope: summed over
|
||
//! the solved cells it need not equal the boundary integral of any face
|
||
//! pressure, and that difference `δP` is the patch's momentum
|
||
//! non-conservation. Everything here is a plain sum of the scheme's
|
||
//! terms; the steady balance `−conv_acc + visc_acc + visc_wall − p_ls = 0`
|
||
//! is the gate (the unsteady term is not stored by `PatchField`; at the
|
||
//! settled state it is small and reported as omitted).
|
||
|
||
use super::{CurvilinearPisoSolver, PatchConvection, PatchField, SideBc};
|
||
use crate::mesh::{PatchMesh, PatchSide};
|
||
|
||
/// The pieces of the patch's momentum balance, x / y, N/m.
|
||
#[derive(Debug, Clone, Copy, Default)]
|
||
pub struct PatchBalance {
|
||
/// Outward `ρ F_f u_f` through the solved/acceptor interface faces.
|
||
pub conv_acc: [f64; 2],
|
||
/// Outward `μ ∇u · S` (Laplacian form) through the interface faces.
|
||
pub visc_acc: [f64; 2],
|
||
/// `Σ p_f S_out` on the interface faces, `p_f` linear in the two cells.
|
||
pub p_face_acc: [f64; 2],
|
||
/// Outward `μ ∇u · S` through the wall faces (Dirichlet wall value).
|
||
pub visc_wall: [f64; 2],
|
||
/// `Σ p_f S_out` on the wall faces, `p_f` extrapolated as
|
||
/// `surface_force` does (cell value + least-squares gradient).
|
||
pub p_face_wall: [f64; 2],
|
||
/// `Σ_solved A_c ∇p_c` — the pressure force the scheme applied.
|
||
pub p_ls: [f64; 2],
|
||
/// Solved cells.
|
||
pub cells: usize,
|
||
/// Interface faces.
|
||
pub acc_faces: usize,
|
||
/// Wall faces.
|
||
pub wall_faces: usize,
|
||
}
|
||
|
||
impl PatchBalance {
|
||
/// Steady balance residual of the marched equation on the solved
|
||
/// region: `−conv_acc + visc_acc + visc_wall − p_ls` (the gate).
|
||
pub fn balance(&self) -> [f64; 2] {
|
||
[0, 1].map(|k| -self.conv_acc[k] + self.visc_acc[k] + self.visc_wall[k] - self.p_ls[k])
|
||
}
|
||
/// The body force read through the interface in flux form:
|
||
/// `∮ (σ·n − ρ u u·n)` with `σ` in the scheme's Laplacian form.
|
||
pub fn flux_force(&self) -> [f64; 2] {
|
||
[0, 1].map(|k| -self.p_face_acc[k] + self.visc_acc[k] - self.conv_acc[k])
|
||
}
|
||
/// The wall force in the scheme's own wall fluxes (Laplacian form).
|
||
pub fn wall_force(&self) -> [f64; 2] {
|
||
[0, 1].map(|k| self.p_face_wall[k] - self.visc_wall[k])
|
||
}
|
||
/// The pressure non-conservation `p_ls − p_face_acc − p_face_wall`
|
||
/// (= `flux_force − wall_force` when the balance holds).
|
||
pub fn pressure_defect(&self) -> [f64; 2] {
|
||
[0, 1].map(|k| self.p_ls[k] - self.p_face_acc[k] - self.p_face_wall[k])
|
||
}
|
||
}
|
||
|
||
impl CurvilinearPisoSolver {
|
||
/// The momentum balance of the solved cells at time `t` (see the
|
||
/// module doc). Stationary mesh only.
|
||
pub fn momentum_balance(&self, field: &PatchField, t: f64) -> PatchBalance {
|
||
let mesh: &PatchMesh = &self.mesh;
|
||
let rho = self.config.density;
|
||
let mu = self.config.viscosity;
|
||
let ops = &self.ops;
|
||
let bvel = |side: PatchSide, xy: [f64; 2]| -> Option<(f64, f64)> {
|
||
match self.params.boundaries.get(side) {
|
||
SideBc::Velocity => Some(self.boundary_velocity(side, xy[0], xy[1], t)),
|
||
SideBc::Outlet => None,
|
||
}
|
||
};
|
||
let un = ops.node_values(mesh, &field.u, &|s, xy| bvel(s, xy).map(|v| v.0));
|
||
let vn = ops.node_values(mesh, &field.v, &|s, xy| bvel(s, xy).map(|v| v.1));
|
||
let mut b = PatchBalance::default();
|
||
for c in 0..mesh.cell_count() {
|
||
if self.is_acceptor(c) {
|
||
continue;
|
||
}
|
||
b.cells += 1;
|
||
let g = self.pressure_gradient(&field.p, c);
|
||
let a = mesh.area(c);
|
||
b.p_ls[0] += a * g[0];
|
||
b.p_ls[1] += a * g[1];
|
||
for (f, sign) in mesh.cell_faces(c) {
|
||
let face = &mesh.faces()[f];
|
||
let s_out = [sign * face.s[0], sign * face.s[1]];
|
||
match (face.owner, face.neigh) {
|
||
(Some(p), Some(q)) => {
|
||
let other = if p == c { q } else { p };
|
||
if !self.is_acceptor(other) {
|
||
continue;
|
||
}
|
||
b.acc_faces += 1;
|
||
let out = sign * field.flux[f];
|
||
let (uf, vf) = match self.params.convection {
|
||
PatchConvection::Upwind => {
|
||
let up = if out >= 0.0 { c } else { other };
|
||
(field.u[up], field.v[up])
|
||
}
|
||
PatchConvection::TvdVanAlbada => {
|
||
let (up, dn) = if out >= 0.0 { (c, other) } else { (other, c) };
|
||
let (du, dv) = self.tvd_correction(field, f, up, dn);
|
||
(field.u[up] + du, field.v[up] + dv)
|
||
}
|
||
PatchConvection::None => (0.0, 0.0),
|
||
};
|
||
b.conv_acc[0] += rho * out * uf;
|
||
b.conv_acc[1] += rho * out * vf;
|
||
b.visc_acc[0] +=
|
||
mu * sign * ops.face_gradient_flux(mesh, f, &field.u, &un, None);
|
||
b.visc_acc[1] +=
|
||
mu * sign * ops.face_gradient_flux(mesh, f, &field.v, &vn, None);
|
||
let pf = face.w * field.p[p] + (1.0 - face.w) * field.p[q];
|
||
b.p_face_acc[0] += pf * s_out[0];
|
||
b.p_face_acc[1] += pf * s_out[1];
|
||
}
|
||
_ => {
|
||
let side = mesh.side(f).expect("boundary face has a side");
|
||
if side != PatchSide::Inner {
|
||
continue;
|
||
}
|
||
b.wall_faces += 1;
|
||
let bv = bvel(side, face.centre);
|
||
b.visc_wall[0] += mu
|
||
* sign
|
||
* ops.face_gradient_flux(mesh, f, &field.u, &un, bv.map(|v| v.0));
|
||
b.visc_wall[1] += mu
|
||
* sign
|
||
* ops.face_gradient_flux(mesh, f, &field.v, &vn, bv.map(|v| v.1));
|
||
let xc = mesh.centre(c);
|
||
let dxf = [face.centre[0] - xc[0], face.centre[1] - xc[1]];
|
||
let pf = field.p[c] + g[0] * dxf[0] + g[1] * dxf[1];
|
||
b.p_face_wall[0] += pf * s_out[0];
|
||
b.p_face_wall[1] += pf * s_out[1];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
b
|
||
}
|
||
}
|