rtx-cfd: PatchConvection::TvdVanAlbada — van Albada deferred correction on the curvilinear predictor (downwind-side linear weight, gradient-ratio r over the face d lengths, far-upwind across the opposite face, boundary faces upwind); annulus MMS orders 2.10/1.69 at 0.24× upwind; cylinder-flag MMS orders 1.98/1.97 (1.06× upwind — diffusion-dominated, recorded); knobs RTX_OVERSET_CFD1_TVD, RTX_OVERSET_MAX_ROUNDS, RTX_CF_SCHEME
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (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
Performance Benchmarks / Run Benchmarks (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
Documentation / Build API Documentation (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:
Omar Sobh
2026-09-06 00:21:48 -07:00
co-authored by Claude Fable 5.1
parent 02c855b9c4
commit 5f780447de
7 changed files with 155 additions and 8 deletions
@@ -88,6 +88,14 @@ pub enum PatchConvection {
Upwind,
/// No convection: the Stokes limit, for the second-order MMS gate.
None,
/// Deferred-correction TVD with the van Albada limiter (the harness's
/// scheme): the upwind face value plus `w_up psi(r) (phi_dn phi_up)`,
/// `w_up` the mesh's linear weight of the downwind side and `r` the
/// ratio of the two one-sided gradients (so a linear field on a
/// stretched row gives `r = 1` and the mesh's own linear face value).
/// Faces whose far-upwind cell lies outside the patch fall back to
/// upwind. Explicit, like the rest of the predictor's convection.
TvdVanAlbada,
}
/// How the across-patch diffusion is time-stepped.
@@ -65,7 +65,7 @@ impl CurvilinearPisoSolver {
// is dropped but the mesh flux stays: the conservative
// update needs `−Σ sign δV_f u_f` whenever the mesh moves.
let fluid = match self.params.convection {
PatchConvection::Upwind => field.flux[f],
PatchConvection::Upwind | PatchConvection::TvdVanAlbada => field.flux[f],
PatchConvection::None => 0.0,
};
let out = sign * (fluid - geo.swept[f] / dt);
@@ -82,6 +82,12 @@ impl CurvilinearPisoSolver {
};
(field.u[up], field.v[up])
}
PatchConvection::TvdVanAlbada => {
let other = if p == c { q } else { p };
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)
}
// The Stokes limit has no upwind scheme to be
// consistent with; the mesh flux takes the linear
// face value and keeps its second order (upwinding
@@ -154,6 +160,54 @@ impl CurvilinearPisoSolver {
(uh, vh)
}
/// The van Albada deferred correction to the upwind face value of `f`
/// between the upwind cell `up` and the downwind cell `dn`: `w_up psi(r)
/// (phi_dn phi_up)` for `u` and `v`, zero when the far-upwind cell
/// (across `up`'s opposite face) lies outside the patch.
fn tvd_correction(&self, field: &PatchField, f: usize, up: usize, dn: usize) -> (f64, f64) {
let mesh = &self.mesh;
let faces = mesh.cell_faces(up);
let Some(pos) = faces.iter().position(|&(g, _)| g == f) else {
return (0.0, 0.0);
};
let g = faces[pos ^ 1].0;
let far_face = &mesh.faces()[g];
let far = match (far_face.owner, far_face.neigh) {
(Some(a), Some(b)) => {
if a == up {
b
} else {
a
}
}
_ => return (0.0, 0.0),
};
let face = &mesh.faces()[f];
// Linear weight of the downwind side: `1 w` when the owner is
// upwind, `w` when the neighbour is.
let w_up = if face.owner == Some(up) {
1.0 - face.w
} else {
face.w
};
let len = |d: [f64; 2]| (d[0] * d[0] + d[1] * d[1]).sqrt();
let (df, dg) = (len(face.d), len(far_face.d));
let limited = |phi: &[f64]| -> f64 {
let near = phi[dn] - phi[up];
if near.abs() < 1e-300 {
return 0.0;
}
let r = (phi[up] - phi[far]) / dg * df / near;
let psi = if r > 0.0 {
(r * r + r) / (r * r + 1.0)
} else {
0.0
};
w_up * psi * near
};
(limited(&field.u), limited(&field.v))
}
/// `(I dt ν L_n/V^{n+1}) û = rhs` along every s-line, Thomas algorithm.
fn solve_lines(
&self,