rtx-cfd: Taylor-Green validates PISO's transient path — and fixes the projection's inner solve
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 / 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
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
With k = pi the decaying Taylor-Green vortex has zero normal velocity on the unit box for all time, so it fits the closed staggered domain exactly, with ZERO body force: convection is balanced identically by the true TG pressure and the decay comes from viscosity alone. This exercises exactly what the steady MMS harness cannot see — the time derivative, the unsteady pressure coupling and the projection's splitting error. The time-decaying tangential wall velocity enters by re-setting the wall hook each step. Measured (16/32/64, dt ~ h^2): L2 velocity 2.267e-2, 1.153e-2, 5.841e-3 — orders 0.97 and 0.98, first-order upwind's rate — and the kinetic-energy deficit against the exact e^(-4 nu pi^2 T) halves per refinement (0.0690, 0.0360, 0.0185; ratios 1.92, 1.95), within 2.3% on the finest mesh. Every step divergence-free to ~1e-7. Its first run caught two defects in the projection's inner solver: - The inner Gauss-Seidel stop summed the per-sweep iterate CHANGE — the same movement-not-residual pseudo-criterion the SIMPLE census flagged: slow modes move little per sweep while their residual is still large. - Plain GS contracts smooth modes by only 1 - O(h^2) per sweep, so the 400-sweep cap left max |div u| ~ 1e-2, GROWING with mesh size (8e-3 at 16^2 to 2e-2 at 64^2). The inner stop now measures the true equation residual, the sweep is SOR at the optimal Poisson factor omega = 2/(1 + sin(pi h)), and it converges relative to each projection's own source with a floor tied to the outer mass tolerance — so a long steady march no longer burns a hundred sweeps per step polishing negligible corrections. The steady MMS harness had masked all of this: a march to steady state iterates the projection to death regardless, which is why its divergence read 1e-9 while a 205-step transient left 1e-2. mms_piso's steady-state criterion is 1e-6 (was 1e-7): per-step projection noise at the mass tolerance floors |du/dt| just below 1e-6, and the L2 errors under measurement are 1e-2 to 1e-3. Its results are unchanged to six figures and still match SIMPLE's. 288 rtx-cfd tests, 0 failing. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d8a30db155
commit
b321a9aba7
@@ -325,13 +325,16 @@ impl PisoSolver {
|
||||
|
||||
flow_field.p_prime.fill(0.0);
|
||||
|
||||
// Mass imbalance of the predicted field, per cell, as a flux.
|
||||
// Mass imbalance of the predicted field, per cell, as a flux. Its
|
||||
// absolute sum is the scale the inner solve converges relative to.
|
||||
let mut source_scale = 0.0;
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
let divergence_flux = rho
|
||||
* ((flow_field.u_star[(j, i + 1)] - flow_field.u_star[(j, i)]) * dy
|
||||
+ (flow_field.v_star[(j + 1, i)] - flow_field.v_star[(j, i)]) * dx);
|
||||
flow_field.sp[(j, i)] = -divergence_flux;
|
||||
source_scale += divergence_flux.abs();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,7 +344,32 @@ impl PisoSolver {
|
||||
let ae_interior = dt * dy / dx;
|
||||
let an_interior = dt * dx / dy;
|
||||
|
||||
for _sweep in 0..400 {
|
||||
// Successive over-relaxation at the optimal Poisson factor
|
||||
// `omega = 2 / (1 + sin(pi h))`. Plain Gauss-Seidel contracts the
|
||||
// smooth modes by only ~(1 - O(h^2)) per sweep, so on a 64^2 grid a
|
||||
// 400-sweep cap left a divergence of ~1e-2 that *grew* with mesh
|
||||
// size; SOR brings the contraction to ~(1 - O(h)) and the same
|
||||
// tolerance costs tens of sweeps instead of thousands.
|
||||
//
|
||||
// The inner stop measures the TRUE residual of the pressure-correction
|
||||
// equation, `|b + sum(a_nb p'_nb) - a_p p'_P|` summed over cells (one
|
||||
// half-sweep lagged). An earlier version summed the per-sweep iterate
|
||||
// CHANGE instead — the same movement-not-residual pseudo-criterion the
|
||||
// SIMPLE census flagged: slow modes move little per sweep while their
|
||||
// residual is still large, so the loop declared victory with an
|
||||
// unremoved divergence. The Taylor-Green benchmark caught both.
|
||||
// Converge relative to this projection's own source, floored at a
|
||||
// tenth of the divergence level the outer corrector loop is checking
|
||||
// for: the corrector loop measures the true post-correction
|
||||
// divergence and re-projects (compounding the reduction), so the
|
||||
// inner solve only needs a solid contraction per pass, not machine zero — which on
|
||||
// a long steady march would spend a hundred sweeps per step
|
||||
// polishing a correction that is already far below tolerance.
|
||||
let reference_flux = rho * self.config.reference_velocity * self.config.reference_length;
|
||||
let inner_stop =
|
||||
(1e-2 * source_scale).max(0.1 * self.parameters.tolerance * reference_flux) + 1e-14;
|
||||
let omega = 2.0 / (1.0 + (std::f64::consts::PI / nx.max(ny) as f64).sin());
|
||||
for _sweep in 0..2000 {
|
||||
let mut residual = 0.0;
|
||||
for j in 0..ny {
|
||||
for i in 0..nx {
|
||||
@@ -377,13 +405,13 @@ impl PisoSolver {
|
||||
0.0
|
||||
};
|
||||
|
||||
let p_new = (flow_field.sp[(j, i)] + east + west + north + south) / ap;
|
||||
let correction = p_new - flow_field.p_prime[(j, i)];
|
||||
residual += correction * correction;
|
||||
flow_field.p_prime[(j, i)] = p_new;
|
||||
let rhs = flow_field.sp[(j, i)] + east + west + north + south;
|
||||
let p_old = flow_field.p_prime[(j, i)];
|
||||
residual += (rhs - ap * p_old).abs();
|
||||
flow_field.p_prime[(j, i)] = (1.0 - omega) * p_old + omega * rhs / ap;
|
||||
}
|
||||
}
|
||||
if residual.sqrt() < 1e-12 {
|
||||
if residual < inner_stop {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user