Files
CelerisLab/docs/validation/taylor_green.md
T
ClaudeandClaude Opus 5.5 caa573f978 test: adopt native-validated 24-target probe layout
Horizontal y=56 and vertical x=38 through box 1, normal coordinates
[12.5..26.125] (root128 physical). Nearest-ACTIVE mapping with
deterministic tie rule (distance, finer level, smaller j, smaller i),
duplicate-cell rejection, stored centers and displacements. Replaces my
earlier offsets, which mapped two target pairs to the same U128 cell.
U512 spacing 128/512 and root step r -> U512 step 4r unchanged.
Tests: 24 unique cells per case, nested 3 L0 + 5 L1 + 4 L2 per transect,
U128 max displacement 0.625.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 03:13:43 +00:00

24 KiB
Raw Blame History

Periodic 2D Taylor–Green AMR campaign (prepared, not GPU-qualified)

This validation harness complements DFG Re100 and Jet/body tests. It measures smooth 2D periodic decay and errors crossing fixed refinement interfaces at Re_L=100 and 1000. It does not certify 3D turbulence, LES, Jet performance, wall conditions, dynamic refinement or general high-Re flows.

Reference and conventions

Primary implementation-author documentation, checked 2026-10-18:

We use the TensorMesh phase, rescaled to length L=N root cells:

k = 2*pi/L; A(t) = U*exp(-2*nu*k*k*t)
ux = -A*cos(k*x)*sin(k*y)
uy =  A*sin(k*x)*cos(k*y)
p/rho0 = -A*A/4 * (cos(2*k*x)+cos(2*k*y))
E = <|u|^2>/2 = U*U/4 * exp(-4*nu*k*k*t)

Pressure is kinematic, with rho0=1. A global velocity sign reversal leaves pressure unchanged; exchanging sine/cosine without the corresponding pressure phase can reverse its sign. CPU tests independently check momentum residuals. The nonlinear acceleration is a pressure gradient, not zero; the AeroSim page's prose calling advection zero is not adopted. Do not mix phase variants from different references. The formulas above are verified by substitution, not by trusting a copied pressure expression.

Re_L=UL/nu, not Re_k=U/(nuk)=Re_L/(2pi). U=.03 in lattice units, Ma=U/cs=.0519615, cs^2=1/3, nu_root=.03N/Re_L. Acoustic refinement uses h_l=dt_l=2^-l: lattice velocity stays U, nu_l=2^lnu_root and tau_l=.5+3nu_l. The worst root tau is .50288 (N32/Re1000), omega=1.98855, inside the existing config guard. No viscosity clamping or guard override.

All initial arrays are physical FP64 equilibrium populations, supplied to the public shifted-FP32 postcollision-G initializer. rho=1+3p at each true cell center. These are valid equilibrium postcollision states, but do not include Chapman–Enskog nonequilibrium stress; startup acoustic/kinetic error remains in the measurements and is shared with uniform baselines. The incompressible reference is not an exact finite-Mach LBM solution. This is why resolution alone need not remove the full error floor.

Geometry and workload

All patches use level-wide union form, so origins are zero, including fine arrays with holes. Coordinates are (index+.5)*2^-level. Metrics integrate only ACTIVE leaves, each weighted by 4^-level. Covered cells and halo cells never enter norms or mass. Geometry tests prove exact single coverage at root-cell resolution. All blocks are 8x8, and both root dimensions are periodic.

At each root N=32,64:

  • 2 levels: L1 rectangle (N/8,N/8,7N/8,3N/4), in L0 indices.
  • 3 levels: same L1 plus L2 rectangle (3N/4,N/2,3N/2,5N/4), in L1 indices.

These off-axis boundaries cross nonzero flow and gradients rather than sitting only on symmetry or stagnation lines. Whole geometry scales with N.

16 cases: at each Re, uniform N=32,64,128,256 plus AMR (root32/root64)x(depth2/depth3). Each AMR case therefore has both a same-root uniform baseline and a uniform baseline at its finest spacing. Uniform N is its own lattice length, so nu and step count are scaled to represent the same nondimensional solution. Samples t*=tU/L = 0,.12,.24,.36,.48. End time is 16*N base steps, a meaningful .48 domain turnover or 3.016 vortex-scale turnover times (scale 1/k). At Re1000 energy decays about 7.3%; at Re100 about 53.1%. No plots or full fields; each case writes five diagnostic rows and provenance, incrementally.

Running (GPU owner must schedule launches)

CPU checks, from the repository root:

PYTHONPATH=src python -m unittest discover -s tests/validation/taylor_green -p 'test_*.py' -v

One GPU case, on the scheduled worker after checking occupancy:

CUDA_VISIBLE_DEVICES=<physical-id> python tests/validation/taylor_green/run_case.py --repo /path/to/reviewed/checkout --re 100 --n 32 --depth 1 --output output/taylor_green/re100_n32_d1.json

Depth 1 is uniform. Run uniform cases before AMR for each Re; inspect baseline screening failures before proceeding. For a complete matrix, repeat the uniform call with N=32,64,128,256 and AMR calls with N=32,64 / depth=2,3, at each Re. Use serial process startup on the parent's assigned Horsea GPUs. This harness does not schedule or acquire GPUs. No GPU was used during preparation.

python tests/validation/taylor_green/assess.py output/taylor_green/re*_n*_d*.json --output output/taylor_green/assessment.json

The local API/geometry review used clean staging commit cd3a657f9d. This is not a claim of results on d79 or any historical commit. Each runtime output records actual Git HEAD/dirty state, an aggregate source-tree hash, plus SHA256 of all harness Python files. Assessment refuses dirty checkouts and mixed source or harness identities. Keep reports outside the checkout or .gitignore them so new outputs do not change its dirty flag between cases. The source-tree hash excludes bytecode caches; production qualification requires a clean checkout. The runner uses the existing PeriodicBlockStepper / FixedPatchStepper periodic paths only. It does not depend on the new channel-root implementation.

Preregistered assessment, before any GPU results

All sampled times must satisfy finite screening ceilings:

  • Relative composite velocity L2 <= 5%
  • Relative globally gauge-adjusted pressure L2 <= 15%
  • Relative kinetic-energy error <= 5%
  • Composite mass drift from downloaded t0 <= 1e-5

These are screening budgets, not literature-certified tolerances. Acceptance also requires all 16 cases, common source/harness, correct nondimensional times and viscosity, and baseline-relative checks on worst post-startup-sample errors:

  • AMR error <= max(2 * same-root uniform error, declared numerical floor).
  • Root64 AMR error <= max(.9 * root32 AMR error, floor), for each depth/Re.
  • Uniform256 error <= max(.9 * uniform32 error, floor).

Floors: velocity .002, pressure .02, energy .004. They are declared low-Mach / equilibrium-startup budgets, not fitted to observed results. Ma^2=.0027 motivates their scale; it is an asymptotic expectation, not a proof of those coefficients. The coarse baseline sets the AMR budget because much of the domain remains coarse. The finest uniform error and AMR/fine ratio are always reported, with no claim that partial refinement should match a fully fine domain. A pass below a floor is bounded accuracy, not a measured convergence order. Report errors and ratios even for a pass. Failure requires diagnosis or a separately preregistered follow-up, never retroactive tolerance tuning. Higher Re is deliberately absent; Re100/1000 must first be stable, resolved and reviewed.

Diagnostics use one global pressure gauge, direct cell-center analytic values, kinetic energy per unit reference mass, and actual leaf cell areas. Composite midpoint quadrature introduces a small initial energy integration error, retained rather than subtracted. Mass drift is relative to downloaded t0, so density initialization and mixed-grid quadrature do not masquerade as time drift.

Pilot scheduling and transfer-consistency scope

Start with one uniform case and one matched AMR case; the 16-case matrix is a qualification plan, not authorization to launch every case automatically. The GPU owner reviews the pilot before expanding it. Each process prints a flushed READY only after initialization, a real base step exercising every level's kernel/halo/transfer path, and a finite positive-density download check. That step belongs to the measured trajectory; it is not discarded warmup. PROGRESS messages contain each of the five diagnostic samples. READY alone is not an accuracy pass. Reports include per-level active cells, hops and collision updates.

The complete matrix costs 768,540,672 active collision updates and 21,504 root intervals, excluding ghost streaming, halos, transfers and diagnostic work. Uniform N256 is the largest case (268,435,456 updates / 4096 intervals per Re). AMR N64/depth3 needs 50,987,008 updates / 1024 intervals. These are work counts, not GPU wall-time estimates. Baseline comparisons remain available on failed screens and partial pilots; absent/nonfinite operands are null, never passing.

Acoustic viscosity scaling and unchanged lattice velocity do not by themselves prove nonequilibrium-stress consistency across refinement interfaces. A smooth collocated Chapman–Enskog postcollision nonequilibrium has a (tau-1)*h factor; the existing implementation instead uses volumetric piecewise-constant explosion and stream-only ghosts. Whether that actual transfer construction achieves the needed stress accuracy is an unresolved question for this pilot to measure. It is not a proven defect, and a collocated-state scaling factor must not be inserted into this different transfer operator without a separate derivation and validation. This campaign measures the current operator and makes no theoretical stress-consistency certification or production-kernel change.

Repository integration

The four Python harness files remain together under tests/validation/taylor_green, so runtime provenance hashes precisely this harness directory. Formatting changes produce a new harness identity; preparation and integrated receipts must not be mixed. All analytic definitions, numerical floors, assessment gates, sample times, and source-import assertions are unchanged. Production source is unchanged from the cd3 snapshot; these periodic tests do not waive the separate channel GPU gate.

Oblique shear wave (test-only diagnostic, not GPU-qualified)

shear_wave.py reuses the frozen oracle's constants, cell centers and geometry. oracle.py is byte-identical, and test_shear_wave.py pins its eec4677 SHA-256. run_case.py --flow shear_wave selects the shear wave; the default flow and its receipt are the unchanged Taylor–Green path.

Convention

Integer mode (2,1) on the L=N periodic root domain:

  • the wavevector k = (2*pi/L)*(2,1) points 26.565° from +x;
  • mode (1,2) would point 63.435°, and a wrong-mode test catches a swap.

Polarization is E = (-1,2)/sqrt(5), perpendicular to k:

u = U*E*sin(k.x + phi)*exp(-nu*|k|^2*t),  phi = 0,  p = const,  rho0 = 1

nu = U*N/100, the Taylor–Green Re100 viscosity at the same N. This is an exact incompressible Navier–Stokes solution. The initial condition is FP64 second-order equilibrium at the actual leaf centers, with no non-equilibrium part.

Physical caveats

  1. Scope of the cubic-term argument. The second-order D2Q9 equilibrium lacks the cubic third moment rho*u_a*u_b*u_g of the Maxwellian (Dellar, https://people.maths.ox.ac.uk/dellar/papers/Galilean.pdf, eq. 3). At Navier–Stokes order this produces a stress error proportional to d_g(rho u_a u_b u_g).

    For the ideal constant-density, zero-background transverse wave, k.E = 0 makes that contracted derivative vanish. That is the only claim made. It does not remove other discrete or finite-Mach errors. It is not guaranteed once AMR introduces density, longitudinal or higher-mode contamination. The analytic NS field itself is exact.

  2. Startup. Equilibrium initialization is not the discrete shear eigenmode (Mei et al., https://doi.org/10.1016/j.compfluid.2005.08.008).

    • The decay rate is a free-intercept least-squares fit of ln R over the samples with t* >= 0.12, where R = hypot(a_hat, b_hat).
    • The intercept cancels only a constant amplitude prefactor, and only once a single exponential dominates.
    • Per-interval rates and the maximum log-fit residual are kept, so a lingering transient stays visible.
    • Late sampling alone does not show that transients have gone.

Metrics

Computed on ACTIVE leaf cells, each weighted by its area 4**-level in root-cell units. The partition must cover the domain exactly once, and one global pressure gauge is used.

  • a_hat, b_hat: weighted least-squares fit of u.E onto sin/cos, using the 2x2 Gram matrix of the actual composite quadrature. A wrong Fourier mode need not project to exactly zero on this quadrature.
  • amplitude_error = R/A_exact - 1.
  • phase_drift = atan2(b_hat, a_hat), in radians, reported separately.
  • Rates are positive (minus the fitted slope).
    • Per root step they are compared with nu*|k|^2. Uniform 2N uses its own nu = 0.0768 at N=256 and doubled step counts.
    • In t* they are compared with 5*(2*pi)^2/Re = 1.97392088.
    • rate_error = |rate/exact - 1|.
  • velocity_l2, residual_l2 (velocity left after removing the fitted mode) and longitudinal_l2 (the u.k_hat part), all relative to the exact-field norm. A longitudinal perturbation also raises the full-vector residual and the velocity L2.
  • spurious_pressure_rms: the raw gauged rms of p = (rho-1)/3.
  • spurious_pressure_scaled: that rms divided by A_exact(t)^2/4, a continuum reference scale. It is not the discrete, globally gauged Taylor–Green norm on the composite grid. Exact pressure is constant, so no relative-to-exact claim is made. Max and per-level values are kept.

Preregistered screens (shear_wave.assess)

Run set: uniform N, uniform 2N, and refined depth-2 runs at root N.

  • eps(N) = (|k| h_root)^2 = 5*(2*pi/N)^2: dimensionless, coefficient one, 0.01205 at N=128. This is a chosen screen, not a proven error bound.
  • Absolute screens:
    • rate_error, max |phase_drift| (radians) and max residual_l2 must each be <= eps;
    • velocity_l2 <= 0.05, mass <= 1e-5, spurious_pressure_scaled <= 0.02.
  • Budgets keep the assess.py form, refined <= max(2*control_N, floor):
    • floors are 0.002 for rate and residual, 0.02 for pressure;
    • the comparative pressure budget is always redundant: max(2*control, 0.02) is never below the 0.02 absolute ceiling;
    • raw ratios to both the root and finest controls, including raw pressure rms, are always reported.
  • Every record is validated before any screen is applied:
    • exact scheduled steps and t*, with no duplicates;
    • every numeric field finite, including list fields and density;
    • error quantities nonnegative and rho_min > 0;
    • signed quantities (amplitudes, amplitude_error, phase_drift, per-interval rate errors) keep their sign.
  • Outcomes are kept distinct:
    • INVALID_CONTROL: a control or refined run is missing, a control is incomplete or invalid, or the records come from mixed sources;
    • INVALID_RUN: a refined record fails validation;
    • CONTROL_SCREEN_FAIL: complete, valid controls fail their own screens. Each refined run's own_screens_pass is still reported, but controlled_pass is false, so it is never a controlled-comparison pass;
    • PASS/FAIL: every refined run's controlled_pass (own screens and budgets against valid, passing controls).
    • Finite modal extinction (late R = 0) is a physical failure, not malformed data. The run gets rate_fit_valid=false with null fit quantities, and its other metrics are kept. Nothing is clipped and no rate is invented. The result is CONTROL_SCREEN_FAIL for a control, or FAIL for a refined run.
    • Screens are never retuned.

Limitations

One flow, one phase and one rectangle, SRT only. The rectangle's vertical faces span 0.625 tangential periods and its horizontal faces 1.5, so this is not placement robustness. Two resolutions show a trend, not an order.

Receipt assessment and runner integrity

  • Fixed four-case N128 contract: python assess_shear_wave.py U128.json U256.json R128_constant.json R128_projected.json --output report.json --expected-revision <sha>. It reuses the shared provenance, build, workload and sample-time checks from assess_projected_pairs.py, and adds shear metadata checks (flow, mode, polarization, angle, phase, eps, limits, disabled early trace, and pinned shear_wave.py hash).
    • Missing, extra, duplicate or mixed-source records give INVALID_SET, as does any failed shared check.
    • Otherwise the result comes from shear_wave.assess, which separates INVALID_CONTROL from CONTROL_SCREEN_FAIL.
  • run_case.verify_source(..., flow="shear_wave") requires the imported shear_wave.py to be the selected harness file with the pinned SHEAR_WAVE_SHA256. The default TG flow does not import it.
  • Sample and READY finiteness use finite_row, which accepts scalar and list diagnostics. For TG's all-scalar rows it behaves the same as before.
  • Merge note: native 92b8afe adds a scaled_n256 receipt block. When the two are merged, keep both blocks and make --flow shear_wave and scaled_n256 mutually exclusive. Not merged here.

Scaled shear diagnostic (opt-in scaled_shear_n256)

Runner: run_case.py --flow shear_wave --diagnostic scaled_shear_n256 --re 100 with one of these four cases:

  • --n 256 --depth 1 (U256)
  • --n 512 --depth 1 (U512)
  • --n 256 --depth 2 --explosion constant (constant R256)
  • --n 256 --depth 2 --explosion projected_linear (projected R256)

Everything physical is unchanged from N128:

  • rectangles(256,2) covers the same physical box;
  • same t* schedule (16N root steps);
  • nu = U*N/100;
  • same eps(N), limits and floors.

scaled_n256 (Taylor–Green, from native 92b8afe) and scaled_shear_n256 each require their own flow, so they are mutually exclusive. N512 is accepted only through one of the two diagnostics. The shear diagnostic takes no early-pressure trace.

Assessment:

python assess_shear_wave.py U256.json U512.json R256_constant.json R256_projected.json \
    --diagnostic scaled_shear_n256 --reference-files <four de5b0cc N128 receipts> \
    --output report.json --expected-revision <sha>
  • The scaled N256/512 case, build and workload checks come from the 92b8afe assessor. Its TG-only diagnostic identity and early-sampling checks are not used. Instead every record must carry diagnostic=scaled_shear_n256, and N128 records must carry none.
  • The reference N128 receipts are hash-pinned to the audited de5b0cc evidence (commit cba6bca). They are never relabelled as fresh controls.
  • The report always includes, for each case:
    • the raw signed rate bias rate/exact - 1 and the interval errors;
    • each refined run's signed excess over each uniform control (rate bias, residual, raw pressure rms);
    • the N128→N256 signed changes and ratios. The ratio is null when the N128 value is zero.
  • These are descriptive. Passing or failing another floor-controlled screen is not the conclusion, and a two-resolution ratio is not a convergence order.
  • Portability: the receipt's recomputed eps = |k|^2 is compared within 4 ULPs (close_ulps), not by exact equality. A cross-runtime replay differed by one ULP: 0.012047856934923534 on Horsea vs 0.012047856934923532 elsewhere. Source and file hashes, case identity, the other metadata and every scientific threshold stay exact.

Nested shear preset (opt-in nested_shear_n128)

A fixed three-level, two-region preset, defined in shear_nested.py. The wave, phase, Re100/U=0.03, initialization, five sample times, eps(root N), limits and floors are the same as before.

  • Refinements, given as parent-frame (x0, y0, x1, y1) rectangles on level-wide, zero-origin unions:
    • L0→L1: (16,16,60,112), (68,24,112,120)
    • L1→L2: (44,44,108,212), (148,60,212,228)
  • Leaves [7936, 12288, 86016], hops [2048, 4096, 8192], work 771227648 per refined case; 3723493376 for all four cases.
  • Level ν [.0384, .0768, .1536], τ [.6152, .7304, .9608].

Cases (run_case.py --flow shear_wave --diagnostic nested_shear_n128 --re 100):

  • --n 128 --depth 1
  • --n 512 --depth 1 — the finest control is 4N, not 2N
  • --n 128 --depth 3
  • --n 128 --depth 3 --explosion projected_linear [--final-snapshot]

--final-snapshot is allowed only for the projected depth-3 case. It writes <output>.final_macro.npz, containing:

  • ACTIVE leaves packed as level/i/j;
  • rho, ux, uy from the FP64 physical populations at the final root synchronization;
  • embedded metadata: exact time, phase, mode and config.

It contains no populations, restart state or time series. Its SHA-256 and size go into the receipt. A file over 8 MiB is removed and reported, never kept. All old paths stay scalar-only.

Assessment: assess_shear_wave.py U128.json U512.json R128c.json R128p.json --diagnostic nested_shear_n128 --output report.json --expected-revision <sha>.

  • It reuses the case-agnostic provenance checks from the TG assessor, plus exact nested scales, builds, patch, workload, metadata and the pinned shear_nested.py hash.
  • The physics is shear_wave.assess generalized only in its depth-3 / 4N shape contract; the metrics are unchanged.
  • shear_wave.py is byte-identical (81d5c8ee…), so historical receipt identities and verdicts are untouched.
  • This preset changes placement and refined volume as well as depth. It gives no isolated cost of an extra level and no resolution trend.

Depth-2 twin of the nested preset (opt-in twin_shear_n128)

shear_twin.py uses the same two L0→L1 boxes as shear_nested.py, with no L2. Leaves are [7936, 33792], hops [2048, 4096], and work is 154664960 per refined case (611319808 for all four).

The campaign is four fresh runs: run_case.py --flow shear_wave --diagnostic twin_shear_n128 --re 100 with

  • --n 128 --depth 1
  • --n 256 --depth 1 (the finest control is 2N)
  • --n 128 --depth 2
  • --n 128 --depth 2 --explosion projected_linear

There is no snapshot option. Older exact controls serve only as consistency checks; they are not reused as references.

Assessment uses the same preset contract as the nested preset, through assess_shear_wave.py ... --diagnostic twin_shear_n128:

  • the physics is the unchanged depth-2 / 2N shear_wave.assess;
  • receipts must pin shear_twin.py, shear_nested.py and shear_wave.py;
  • shear_nested.py stays byte-identical (fa6cfe87…), so the 335624c nested receipts still validate.

Labels:

  • Twin vs nested is the effect of adding this nested L2 refinement. Removing L2 also changes the finest spacing and refined volume, so it does not isolate abstract depth.
  • Twin vs the single-box depth-2 runs is a placement/geometry contrast at equal depth.
  • Neither contrast is additive by construction.
  • The thin-band/acoustic explanation for the nested L1 pressure is a hypothesis, not established.

Signed interface-probe traces (opt-in --probe-trace, mechanism diagnostic)

shear_probes.py (pinned) uses native's validated 24-target layout through box 1, in root-128 physical coordinates:

  • a horizontal line y = 56, crossing the vertical faces x = 16 and 22;
  • a vertical line x = 38, crossing the horizontal faces y = 16 and 22;
  • normal coordinates 12.5, 14.5, 15.5, 16.25, 17.25, 19.25, 20.75, 21.75, 22.125, 23.125, 24.125, 26.125.

Each target maps to the nearest ACTIVE leaf center. The deterministic tie rule is: distance, then finer level, then smaller j, then smaller i. This gives 24 unique cells in every case, and nested has 3 L0 + 5 L1 + 4 L2 per transect. Maximum displacement from target to center is 0.625 root cells in U128 (0.177 in U512).

Allowed cases:

  • nested U128, nested U512, nested projected depth 3;
  • twin projected depth 2.

The option can't be combined with --final-snapshot.

Sampling:

  • The receipt stores each probe's leaf level, i/j, actual cell center and displacement from its target, all in root-physical units (N=128).
  • Grids differ between cases, so centers are not collocated. Analytic fields are evaluated at each actual center (shear_probes.residuals), and comparisons across cases are between residuals at nearby but distinct centers.
  • Times are root-physical steps 0–128 and 1920–2048. U512 is sampled every 4 lattice steps to match.
  • Each sample stores rho, ux, uy from the FP64 physical populations, POST-collision G at root synchronization only. No substep content is observed, and grid-scale alternation can alias.

What's kept and enforced:

  • The five qualification samples stay unchanged.
  • No field dumps. The trace is validated for exact schedule, finiteness and a 600 kB cap. If invalid it is dropped and an error is recorded, keeping the scalar receipt.
  • There are no new pass gates; this is a mechanism diagnostic only.