rtx-cfd: the wall treatment is second order, not first — correct the record
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
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

The manufactured-solution test carried a hypothesis for why the observed
order sits below 1: that `(u_P - u_wall)/(dy/2)` approximates the wall
gradient at y = dy/4 rather than at the wall, making the near-wall rows
first order.

Measuring in the Stokes limit refutes it. With convection negligible every
remaining operator is second order, so the observed rate there reports the
wall treatment directly:

    rho = 1.000  (Re = 20.00)   3.52e-2  1.95e-2  1.04e-2   orders 0.85 0.91
    rho = 0.001  (Re =  0.02)   2.21e-3  5.35e-4  1.28e-4   orders 2.05 2.06

2.05 and 2.06. The half-cell wall term is second-order accurate and the
Stokes discretisation reaches its nominal rate. The shortfall at Re = 20 is
first-order upwind and nothing else, which is what a first-order convection
scheme is supposed to give.

Comment corrected rather than left standing: a plausible explanation that
happens to be wrong is worse than none, because it sends the next person
to fix something that is not broken.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-19 17:42:14 -07:00
co-authored by Claude Opus 5
parent 698c844926
commit 1a740e0b2c
@@ -59,7 +59,9 @@
//! below are the ones that a plausible-but-wrong discretisation fails: //! below are the ones that a plausible-but-wrong discretisation fails:
//! //!
//! 1. **Observed order of the velocity error.** 0.85 and 0.91 over //! 1. **Observed order of the velocity error.** 0.85 and 0.91 over
//! 16 -> 32 -> 64, approaching 1 from below. It was 0.48. //! 16 -> 32 -> 64, approaching 1 from below. It was 0.48. In the Stokes
//! limit the same measurement gives 2.05 and 2.06, so the shortfall below 1
//! is first-order upwind convection, not the discretisation.
//! 2. **Divergence, split between the outer ring of cells and the interior.** //! 2. **Divergence, split between the outer ring of cells and the interior.**
//! A converged SIMPLE solve must satisfy discrete continuity to solver //! A converged SIMPLE solve must satisfy discrete continuity to solver
//! tolerance in *every* cell. Splitting the measure is what exposes a //! tolerance in *every* cell. Splitting the measure is what exposes a
@@ -308,17 +310,28 @@ async fn observed_order_matches_the_convection_scheme() -> CfdResult<()> {
); );
// 0.80, not 1.0. The measured rates are 0.848 (16 -> 32) and 0.905 // 0.80, not 1.0. The measured rates are 0.848 (16 -> 32) and 0.905
// (32 -> 64) approaching 1 from below, which is what a first-order scheme // (32 -> 64), approaching 1 from below which is exactly what first-order
// carrying a first-order wall treatment does: `(u_P - u_wall)/(dy/2)` // upwind convection gives.
// approximates the wall gradient at `y = dy/4` rather than at the wall, so //
// the near-wall rows converge at first order too but with a larger // The wall treatment is *not* the limiter, contrary to what this comment
// constant, and its share of the norm shrinks as the mesh refines. // first claimed. Repeating the measurement in the Stokes limit, where
// convection is negligible and every remaining operator is second order,
// gives observed order 2.05 and 2.06:
//
// rho = 1.000 (Re = 20.00) 3.5162e-2 1.9537e-2 1.0375e-2 0.85 0.91
// rho = 0.001 (Re = 0.02) 2.2131e-3 5.3510e-4 1.2812e-4 2.05 2.06
//
// So the near-wall half-cell term is second-order accurate and the whole
// Stokes discretisation reaches its nominal rate. The shortfall below 1 at
// Re = 20 is upwind's `O(h)` numerical viscosity and nothing else, which
// means a second-order convection scheme is now the thing that moves this
// number — and that work is no longer blocked behind an unverified
// discretisation.
// //
// The bound sits just under the worst measured rate rather than at the // The bound sits just under the worst measured rate rather than at the
// theoretical 1, because asserting a rate the solver does not yet reach // theoretical 1 because the rate is still climbing at 64. It still has
// either fails the suite or invites someone to quietly weaken it later. It // teeth: before the near-wall rows became unknowns the rates were 0.475 and
// still has teeth: before the near-wall rows became unknowns the rates were // 0.470, and this fails them by a wide margin.
// 0.475 and 0.470, and this fails them by a wide margin.
for (i, &rate) in rates.iter().enumerate() { for (i, &rate) in rates.iter().enumerate() {
assert!( assert!(
rate > 0.80, rate > 0.80,