rtx-cfd: OverlapMap::region_force — momentum flux into a background region (control-volume face formula, one-sided next to holes); overset_cfd1 prints the four momentum routes (CV box, ring outer, hole boundary, wall) and their defects at the settled state
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (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 / Test (ubuntu-latest) (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 / CI Success (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (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 / Test (ubuntu-latest) (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 / CI Success (push) Canceled after 0s
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01X2GmJXeQ2njUecEKiJZ1G2
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
5f780447de
commit
134ac03870
@@ -509,6 +509,160 @@ impl OverlapMap {
|
|||||||
|
|
||||||
/// Background-side overlap mass defect: `Σ_fringe |Σ_f sign F_f|`
|
/// Background-side overlap mass defect: `Σ_fringe |Σ_f sign F_f|`
|
||||||
/// (volume flux), the continuity the fringe cells do not enforce.
|
/// (volume flux), the continuity the fringe cells do not enforce.
|
||||||
|
/// The force the background transmits INTO the region of cells whose
|
||||||
|
/// class satisfies `inside` — the sum over the region's boundary faces
|
||||||
|
/// of `sigma·n − rho u (u·n)` with `n` pointing out of the region, the
|
||||||
|
/// control-volume formula of `EmbeddedMask::control_volume_force`
|
||||||
|
/// without the unsteady term (a settled-state diagnostic). Values are
|
||||||
|
/// taken from non-hole cells only: a face next to a hole cell uses the
|
||||||
|
/// one-sided stencil from its valid side, so the hole boundary itself
|
||||||
|
/// (`inside = Hole`) is evaluated from the fringe's stamped values.
|
||||||
|
///
|
||||||
|
/// Two regions make the P4 momentum-defect measurement: `Fringe |
|
||||||
|
/// Hole` (what the active region passes to the ring) and `Hole` (what
|
||||||
|
/// the ring passes on); their difference is the fringe ring's momentum
|
||||||
|
/// defect, and the hole boundary against the patch's wall force is the
|
||||||
|
/// patch region's.
|
||||||
|
pub fn region_force(
|
||||||
|
&self,
|
||||||
|
field: &FlowField,
|
||||||
|
rho: f64,
|
||||||
|
mu: f64,
|
||||||
|
inside: impl Fn(CellClass) -> bool,
|
||||||
|
) -> (f64, f64) {
|
||||||
|
let (nx, ny, dx, dy) = (self.nx, self.ny, self.dx, self.dy);
|
||||||
|
let valid = |j: isize, i: isize| -> bool {
|
||||||
|
j >= 0
|
||||||
|
&& i >= 0
|
||||||
|
&& (j as usize) < ny
|
||||||
|
&& (i as usize) < nx
|
||||||
|
&& self.class(j as usize, i as usize) != CellClass::Hole
|
||||||
|
};
|
||||||
|
let is_in = |j: isize, i: isize| -> bool {
|
||||||
|
j >= 0
|
||||||
|
&& i >= 0
|
||||||
|
&& (j as usize) < ny
|
||||||
|
&& (i as usize) < nx
|
||||||
|
&& inside(self.class(j as usize, i as usize))
|
||||||
|
};
|
||||||
|
// Face-located u is valid when either adjacent cell is; likewise v.
|
||||||
|
let uf_valid = |j: isize, i: isize| valid(j, i - 1) || valid(j, i);
|
||||||
|
let vf_valid = |j: isize, i: isize| valid(j - 1, i) || valid(j, i);
|
||||||
|
let u = |j: isize, i: isize| field.u[(j as usize, i as usize)];
|
||||||
|
let v = |j: isize, i: isize| field.v[(j as usize, i as usize)];
|
||||||
|
let p = |j: isize, i: isize| field.p[(j as usize, i as usize)];
|
||||||
|
// Cell-centred v and u (averages of the cell's two faces).
|
||||||
|
let v_c = |j: isize, i: isize| 0.5 * (v(j, i) + v(j + 1, i));
|
||||||
|
let u_c = |j: isize, i: isize| 0.5 * (u(j, i) + u(j, i + 1));
|
||||||
|
// Average of the valid members of a pair, or `None`.
|
||||||
|
let pair = |a: Option<f64>, b: Option<f64>| match (a, b) {
|
||||||
|
(Some(a), Some(b)) => Some(0.5 * (a + b)),
|
||||||
|
(Some(a), None) | (None, Some(a)) => Some(a),
|
||||||
|
(None, None) => None,
|
||||||
|
};
|
||||||
|
// Derivative across `x0 → x1 → x2` (spacing `h`): central when both
|
||||||
|
// ends are valid, one-sided otherwise, zero when nothing is.
|
||||||
|
let deriv = |m: Option<f64>, c: f64, pl: Option<f64>, h: f64| match (m, pl) {
|
||||||
|
(Some(m), Some(pl)) => (pl - m) / (2.0 * h),
|
||||||
|
(Some(m), None) => (c - m) / h,
|
||||||
|
(None, Some(pl)) => (pl - c) / h,
|
||||||
|
(None, None) => 0.0,
|
||||||
|
};
|
||||||
|
let (mut fx, mut fy) = (0.0, 0.0);
|
||||||
|
for jc in 0..ny as isize {
|
||||||
|
for ic in 0..nx as isize {
|
||||||
|
if !is_in(jc, ic) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Vertical faces: west (u face ic, n = −x) and east (ic + 1, +x).
|
||||||
|
for (i, sign, nj, ni) in [(ic, -1.0, jc, ic - 1), (ic + 1, 1.0, jc, ic + 1)] {
|
||||||
|
if is_in(nj, ni) || nj < 0 || ni < 0 || ni >= nx as isize {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let j = jc;
|
||||||
|
let un = u(j, i);
|
||||||
|
let p_f = pair(
|
||||||
|
valid(j, i - 1).then(|| p(j, i - 1)),
|
||||||
|
valid(j, i).then(|| p(j, i)),
|
||||||
|
)
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
let dudx = deriv(
|
||||||
|
uf_valid(j, i - 1).then(|| u(j, i - 1)),
|
||||||
|
un,
|
||||||
|
(i < nx as isize && uf_valid(j, i + 1)).then(|| u(j, i + 1)),
|
||||||
|
dx,
|
||||||
|
);
|
||||||
|
let dudy = deriv(
|
||||||
|
(j >= 1 && uf_valid(j - 1, i)).then(|| u(j - 1, i)),
|
||||||
|
un,
|
||||||
|
(j + 1 < ny as isize && uf_valid(j + 1, i)).then(|| u(j + 1, i)),
|
||||||
|
dy,
|
||||||
|
);
|
||||||
|
let vw = valid(j, i - 1).then(|| v_c(j, i - 1));
|
||||||
|
let ve = valid(j, i).then(|| v_c(j, i));
|
||||||
|
let dvdx = match (vw, ve) {
|
||||||
|
(Some(a), Some(b)) => (b - a) / dx,
|
||||||
|
(Some(a), None) => {
|
||||||
|
(a - if valid(j, i - 2) { v_c(j, i - 2) } else { a }) / dx
|
||||||
|
}
|
||||||
|
(None, Some(b)) => {
|
||||||
|
((if valid(j, i + 1) { v_c(j, i + 1) } else { b }) - b) / dx
|
||||||
|
}
|
||||||
|
(None, None) => 0.0,
|
||||||
|
};
|
||||||
|
let v_f = pair(vw, ve).unwrap_or(0.0);
|
||||||
|
let sxx = -p_f + 2.0 * mu * dudx;
|
||||||
|
let sxy = mu * (dudy + dvdx);
|
||||||
|
fx += sign * (sxx - rho * un * un) * dy;
|
||||||
|
fy += sign * (sxy - rho * v_f * un) * dy;
|
||||||
|
}
|
||||||
|
// Horizontal faces: south (v face jc, n = −y) and north (jc + 1, +y).
|
||||||
|
for (j, sign, nj, ni) in [(jc, -1.0, jc - 1, ic), (jc + 1, 1.0, jc + 1, ic)] {
|
||||||
|
if is_in(nj, ni) || nj < 0 || nj >= ny as isize {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let i = ic;
|
||||||
|
let vn = v(j, i);
|
||||||
|
let p_f = pair(
|
||||||
|
valid(j - 1, i).then(|| p(j - 1, i)),
|
||||||
|
valid(j, i).then(|| p(j, i)),
|
||||||
|
)
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
let dvdy = deriv(
|
||||||
|
vf_valid(j - 1, i).then(|| v(j - 1, i)),
|
||||||
|
vn,
|
||||||
|
(j < ny as isize && vf_valid(j + 1, i)).then(|| v(j + 1, i)),
|
||||||
|
dy,
|
||||||
|
);
|
||||||
|
let dvdx = deriv(
|
||||||
|
(i >= 1 && vf_valid(j, i - 1)).then(|| v(j, i - 1)),
|
||||||
|
vn,
|
||||||
|
(i + 1 < nx as isize && vf_valid(j, i + 1)).then(|| v(j, i + 1)),
|
||||||
|
dx,
|
||||||
|
);
|
||||||
|
let us = valid(j - 1, i).then(|| u_c(j - 1, i));
|
||||||
|
let un_ = valid(j, i).then(|| u_c(j, i));
|
||||||
|
let dudy = match (us, un_) {
|
||||||
|
(Some(a), Some(b)) => (b - a) / dy,
|
||||||
|
(Some(a), None) => {
|
||||||
|
(a - if valid(j - 2, i) { u_c(j - 2, i) } else { a }) / dy
|
||||||
|
}
|
||||||
|
(None, Some(b)) => {
|
||||||
|
((if valid(j + 1, i) { u_c(j + 1, i) } else { b }) - b) / dy
|
||||||
|
}
|
||||||
|
(None, None) => 0.0,
|
||||||
|
};
|
||||||
|
let u_f = pair(us, un_).unwrap_or(0.0);
|
||||||
|
let syy = -p_f + 2.0 * mu * dvdy;
|
||||||
|
let sxy = mu * (dudy + dvdx);
|
||||||
|
fx += sign * (sxy - rho * u_f * vn) * dx;
|
||||||
|
fy += sign * (syy - rho * vn * vn) * dx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(fx, fy)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn background_mass_defect(&self, field: &FlowField) -> f64 {
|
pub fn background_mass_defect(&self, field: &FlowField) -> f64 {
|
||||||
let (dx, dy) = (self.dx, self.dy);
|
let (dx, dy) = (self.dx, self.dy);
|
||||||
self.fringe_cells
|
self.fringe_cells
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
use rtx_cfd::mesh::PatchSide;
|
use rtx_cfd::mesh::PatchSide;
|
||||||
use rtx_cfd::mesh::patch_gen::cylinder_flag_patch;
|
use rtx_cfd::mesh::patch_gen::cylinder_flag_patch;
|
||||||
use rtx_cfd::solvers::incompressible::{
|
use rtx_cfd::solvers::incompressible::{
|
||||||
AleBoundaries, CurvilinearParameters, CurvilinearPisoSolver, EmbeddedParameters,
|
AleBoundaries, CellClass, CurvilinearParameters, CurvilinearPisoSolver, EmbeddedParameters,
|
||||||
EmbeddedPisoSolver, FlowField, NormalDiffusion, OversetField, OversetParameters,
|
EmbeddedPisoSolver, FlowField, NormalDiffusion, OversetField, OversetParameters,
|
||||||
OversetPisoSolver, PatchConvection, PatchField, PoissonSolverKind, SideBoundary,
|
OversetPisoSolver, PatchConvection, PatchField, PoissonSolverKind, SideBoundary,
|
||||||
};
|
};
|
||||||
@@ -262,6 +262,29 @@ async fn run_cfd1(ny: usize) -> CfdResult<Cfd1> {
|
|||||||
.patch()
|
.patch()
|
||||||
.surface_force(&field.patch, PatchSide::Inner, solver.time());
|
.surface_force(&field.patch, PatchSide::Inner, solver.time());
|
||||||
let (drag_cv, lift_cv) = cv_force(&field, &solver);
|
let (drag_cv, lift_cv) = cv_force(&field, &solver);
|
||||||
|
// P4 momentum-defect measurement: the force the background transmits
|
||||||
|
// into the ring (fringe + hole), into the hole alone, and the patch's
|
||||||
|
// wall force — consecutive differences are the active region's
|
||||||
|
// residual, the fringe ring's momentum defect, and the patch region's.
|
||||||
|
let ring = solver
|
||||||
|
.overlap()
|
||||||
|
.region_force(&field.background, RHO, mu, |c| c != CellClass::Active);
|
||||||
|
let hole = solver
|
||||||
|
.overlap()
|
||||||
|
.region_force(&field.background, RHO, mu, |c| c == CellClass::Hole);
|
||||||
|
let wall = load.total();
|
||||||
|
println!(
|
||||||
|
" momentum routes ny = {ny}: CV box ({drag_cv:.4}, {lift_cv:.4}) | ring outer ({:.4}, {:.4}) | hole boundary ({:.4}, {:.4}) | wall ({:.4}, {:.4}); defects [% of wall drag]: active {:+.2} fringe ring {:+.2} patch region {:+.2}",
|
||||||
|
ring.0,
|
||||||
|
ring.1,
|
||||||
|
hole.0,
|
||||||
|
hole.1,
|
||||||
|
wall[0],
|
||||||
|
wall[1],
|
||||||
|
100.0 * (drag_cv - ring.0) / wall[0],
|
||||||
|
100.0 * (ring.0 - hole.0) / wall[0],
|
||||||
|
100.0 * (hole.0 - wall[0]) / wall[0],
|
||||||
|
);
|
||||||
Ok(Cfd1 {
|
Ok(Cfd1 {
|
||||||
drag_surface: load.total()[0],
|
drag_surface: load.total()[0],
|
||||||
lift_surface: load.total()[1],
|
lift_surface: load.total()[1],
|
||||||
|
|||||||
Reference in New Issue
Block a user