Files
rustytorch/crates/specialized/rtx-cfd/src/solvers/incompressible/curvilinear/balance.rs
T
Omar SobhandClaude Fable 5.1 0215c7d6a5
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
rtx-cfd: CurvilinearPisoSolver::momentum_balance — the patch's own momentum balance on its solved cells in the scheme's fluxes (outward ρFu_f with the predictor's face value, Laplacian-form μ∇u·S on the solved/acceptor interface and the wall, the least-squares pressure volume sum vs the face-pressure integrals); flux_force, wall_force, pressure_defect δP; overset_cfd1 prints it and the acceptor band's mismatch, saves the patch flux, and RTX_OVERSET_CFD1_LOAD=dir runs the diagnostics offline on saved fields
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01X2GmJXeQ2njUecEKiJZ1G2
2026-09-06 14:13:04 -07:00

150 lines
7.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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
}
}