From 53fad767595d93b5b14425ff5a8e313679ae6a23 Mon Sep 17 00:00:00 2001 From: TRON Date: Sun, 18 Oct 2026 07:54:54 -0700 Subject: [PATCH] test: cover TRT restart and actual-rate refinement phase Integrate reviewed test changes from 7c1517de819741907fb945ea0ab7864734fa3856 and 340ba4e1dc6386581839c0b28c8fe8a1f4aeb70b unchanged. Document bounded 2D implementation, evidence and open gates; production source and historical manifests are unchanged. --- docs/validation/2d_scope.md | 51 ++++++++++++++ tests/integration/test_state_gpu.py | 11 +++- .../multigrid/test_refinement_gpu.py | 66 ++++++++++++++++++- 3 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 docs/validation/2d_scope.md diff --git a/docs/validation/2d_scope.md b/docs/validation/2d_scope.md new file mode 100644 index 0000000..f4085d1 --- /dev/null +++ b/docs/validation/2d_scope.md @@ -0,0 +1,51 @@ +# Bounded 2D implementation and validation scope + +This inventory uses dev `8403956a9afc0b0bb89265cec374392013d89016` as its +production source checkpoint. The accompanying tests add coverage; their presence +is not a passing GPU receipt. Historical campaigns retain their original source +identities and acceptance gates. + +## Implementation boundaries + +- Uniform D2Q9 supports SRT, TRT and MRT. An available collision selector does not + qualify every wall, inlet/outlet, forcing, storage or LES combination. +- Fixed refinement supports SRT/MRT, FP32 and double-buffer streaming; TRT and LES + are rejected. Shifted refinement requires all-blocked storage. Refined channel + boundaries have narrower configuration guards than uniform boundaries. +- Uniform checkpoints support compatible same-method continuation. Changing + collision or LES parameters is a different evolution, not exact continuation. + Jet checkpoints and refined checkpoints are unsupported; synchronized remapping + is not persistent restart. +- Keep these conclusions 2D. Local D3Q19 operator tests do not establish a supported + 3D Simulation runtime. + +## Evidence and remaining gaps + +| Area | Available evidence | Boundary of the conclusion | +|---|---|---| +| MRT cylinder flow and observation windows | Full matched uniform/refined MRT DFG and the window-ledger campaign passed their frozen periodicity, reference and matched-comparison gates. The window campaign used `185a919`; dev adds only a docstring correction. | This does not qualify other collision/boundary combinations or every refinement interface. Timing improvements are not additional accuracy evidence. | +| Uniform forced channel | Six Poiseuille GPU cases on `8403956`: SRT/TRT/MRT at wall-normal resolutions H16/H32 passed. | This is the tested analytic channel configuration, not open-boundary or all-layout qualification. | +| Uniform restart | Ten SRT/MRT checkpoint GPU tests passed on `8403956`. | The new representative TRT same-method double-buffer continuation test still requires its own GPU receipt; no LES, Jet or refined checkpoint claim follows. | +| Taylor–Green refinement | Independent topology checks for the exact TG setup are available. The companion phase regression checks the actual lattice-rate configuration. | The physical interface-accuracy gate remains open. Topology and phase setup checks do not establish convergence or close that gate. | +| Porous Couette and annulus | Prior campaign evidence exists and must remain associated with its original setup and source. | The reconstructed annulus is a validation helper, not qualification of a public boundary-condition API. Do not infer absence of evidence from a missing current rerun. | +| LES and other combinations | Separate scalar and uniform-flow normalization evidence exists for the reviewed temporary correction. | That correction is not integrated into this source checkpoint. Do not transfer its acceptance to unchanged production, or to TRT/MRT LES, forcing, EsoPull, objects or refinement. | + +Historical uniform SRT cylinder-startup and inlet-rounding findings remain separate +from the passed MRT DFG result. Raw exchange/window consistency is a measurement +contract, not a general stress or force-accuracy certification. Existing source-pinned +runners may intentionally reject a newer checkout; never silently repin old receipts. + +## Small next batch + +1. Cross-review the added TRT checkpoint and actual-rate TG phase tests, then run + their applicable checks with exact checkout/import receipts. Preserve the + existing SRT checkpoint cases and numerical criteria. +2. Run the single added TRT checkpoint GPU case with the same method on both sides + of save/load. Record its result separately from the ten existing SRT/MRT passes. +3. Continue the existing TG accuracy investigation using its actual fine-level + rates and the established analytic convention. Keep the accuracy gate open + until the relevant physical comparison passes. + +No repeat of the accepted full MRT DFG or Cartesian expansion across unsupported +combinations is required by this document. Main integration remains a separate +review of complete, bounded feature scope. diff --git a/tests/integration/test_state_gpu.py b/tests/integration/test_state_gpu.py index 65d3304..83c1cce 100644 --- a/tests/integration/test_state_gpu.py +++ b/tests/integration/test_state_gpu.py @@ -65,9 +65,14 @@ def test_shifted_topology_add_remove_preserves_cache_and_finite_flow(streaming,p assert np.max(macro['rho'][fluid]) < 1.5 -@pytest.mark.parametrize('streaming',['double_buffer','esopull']) -def test_checkpoint_exact_continuation_and_controls(streaming,tmp_path): - with make_sim(nx=48,ny=24,streaming=streaming,shifted=True) as sim: +@pytest.mark.parametrize('collision,streaming', [ + pytest.param('SRT', 'double_buffer', id='double_buffer'), + pytest.param('SRT', 'esopull', id='esopull'), + pytest.param('TRT', 'double_buffer', id='trt-double_buffer'), +]) +def test_checkpoint_exact_continuation_and_controls(collision,streaming,tmp_path): + # Same collision throughout save/load/continue; this is not a method-switch restart. + with make_sim(nx=48,ny=24,streaming=streaming,shifted=True,collision=collision) as sim: sim.add_body('circle',center=(14,12),radius=3) sim.add_body('force_region',center=(30,12),radius=2) sim.initialize() diff --git a/tests/validation/multigrid/test_refinement_gpu.py b/tests/validation/multigrid/test_refinement_gpu.py index fac88c0..efcfd0a 100644 --- a/tests/validation/multigrid/test_refinement_gpu.py +++ b/tests/validation/multigrid/test_refinement_gpu.py @@ -7,6 +7,7 @@ At most eight coarse steps per case. No files, plots or flow arrays are saved. import hashlib import importlib.util from contextlib import nullcontext +from dataclasses import replace from pathlib import Path import resource import sys @@ -21,7 +22,7 @@ import CelerisLab from CelerisLab.config import LBMConfig from CelerisLab.cuda import compiler_v2 as compiler from CelerisLab.cuda.context import CudaContext -from CelerisLab.lbm.refinement_stepper import PeriodicPatchStepper +from CelerisLab.lbm.refinement_stepper import FixedPatchStepper, PeriodicPatchStepper ROOT = Path(__file__).resolve().parents[3] POP_ATOL = 3e-6 @@ -60,6 +61,69 @@ def global_array(module, name, shape): class CoupledGPU(unittest.TestCase): + def test_actual_tg_rates_one_interval(self): + """Algebraic rate/phase check, not a new physical Taylor--Green benchmark. + + The N64/Re100 TG rates are exercised on the existing small patch with + a nonequilibrium state. One interval checks g=C(f), including the + shifted blocked path; no accuracy gate or production source changes. + """ + self.assertEqual(Path(CelerisLab.__file__).resolve(), ROOT / "src/CelerisLab/__init__.py") + self.assertEqual(Path(compiler._KERNEL_DIR).resolve(), ROOT / "src/CelerisLab/lbm/kernels") + spec = replace(ref.make_spec(collision="srt"), + tau_coarse=.5576, tau_fine=.6152) + maps = ref.build_maps(spec) + state = ref.make_state(spec, velocity=(.03, -.02)) + rng = np.random.default_rng(1) + state = ref.State(*(a * (1 + 1e-3 * rng.standard_normal(a.shape)) + for a in (state.coarse_f, state.fine_f))) + initial = postcollision(state, spec, maps) + next_state, _ = ref.advance_coarse_once(spec, maps, state) + expected = postcollision(next_state, spec, maps) + arrivals = (next_state.coarse_f, next_state.fine_f) + + cases = (("dense_unshifted", False, {}), + ("blocked_shifted", True, + dict(base_block=(4, 4), fine_block=(6, 6)))) + with CudaContext(0) as context: + for label, shifted, storage in cases: + with self.subTest(storage=label): + cfg = LBMConfig(nx=16, ny=16, viscosity=.0192, + ddf_shifting=shifted) + with FixedPatchStepper(cfg, (4, 4, 12, 12), context=context, + **storage) as backend: + self.assertAlmostEqual(backend.patch.tau_coarse, spec.tau_coarse) + self.assertAlmostEqual(backend.patch.tau_fine, spec.tau_fine) + self.assertEqual(tuple(level.shape for level in backend._levels), + ((9, 16, 16), (9, 24, 24))) + # Physical FP64 g is encoded by initialize before FP32 rounding. + backend.initialize(*initial) + before = totals(backend.download(), maps) + backend.step(1) + actual = backend.download() + self.assertEqual(backend.step_count, 1) + backend.patch.check_synchronization( + 1, next_state.tick, phase="postcollision_g") + for got, want, arrival in zip(actual, expected, arrivals): + np.testing.assert_array_equal(np.isnan(got), np.isnan(want)) + np.testing.assert_allclose(got, want, rtol=0, atol=POP_ATOL, + equal_nan=True) + # A raw arrival-f comparison must fail on BOTH levels. + # Equilibrium-only data would make this control vacuous. + with self.assertRaises(AssertionError): + np.testing.assert_allclose(got, arrival, rtol=0, + atol=POP_ATOL, equal_nan=True) + np.testing.assert_allclose(totals(actual, maps), before, + rtol=0, atol=MOMENT_ATOL) + np.testing.assert_allclose(totals(actual, maps), + totals(expected, maps), + rtol=0, atol=MOMENT_ATOL) + error = max(float(np.nanmax(np.abs(a - e))) + for a, e in zip(actual, expected)) + print(f"actual-TG-rate algebraic check: {label} steps=1 " + f"tau=.5576/.6152 max_population={error:.9g}; " + "wrong-phase controls PASS", flush=True) + def test_cleanup_attempts_every_allocation(self): backend = PeriodicPatchStepper.__new__(PeriodicPatchStepper) backend.ctx = Mock(activate=nullcontext)