test: MRT refinement coverage and obsolete rejection fix

Remove MRT from the FixedPatch2D rejection matrix (TRT and other
rejections retained); convert host MRT tests to stdlib unittest; add GPU
gates for unshifted dense-vs-blocked MRT two-level roots (exact bits) and
a shifted all-blocked three-level MRT smoke checking per-level omega.
No numerical source change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-18 10:31:25 +00:00
1 parent 00d7a54829
commit 3ea131e92c
3 files changed
+64 -21

No files matched your search

+1 -1
View File
@@ -142,7 +142,7 @@ class FixedPatchTests(unittest.TestCase):
FixedPatch2D.from_config(LBMConfig(nx=nx, ny=ny, viscosity=.1), bounds) FixedPatch2D.from_config(LBMConfig(nx=nx, ny=ny, viscosity=.1), bounds)
def test_unsupported_backend_and_fine_relaxation_rejected(self): def test_unsupported_backend_and_fine_relaxation_rejected(self):
for kwargs in ({"collision": "TRT"}, {"collision": "MRT"}, {"streaming": "esopull"}, for kwargs in ({"collision": "TRT"}, {"streaming": "esopull"},
{"les_enabled": True}, {"les_enabled": True},
{"store_precision": "FP16S"}, {"lattice_model": "D3Q19"}, {"store_precision": "FP16S"}, {"lattice_model": "D3Q19"},
{"data_type": "FP64"}, {"omega_min": 1.0}): {"data_type": "FP64"}, {"omega_min": 1.0}):
+24 -20
View File
@@ -1,25 +1,26 @@
"""Host guards and source contract for MRT reuse in refined/blocked stepping (no CUDA).""" """Host guards and source contract for MRT reuse in refined/blocked stepping (no CUDA)."""
from pathlib import Path from pathlib import Path
import pytest import unittest
from CelerisLab.config import LBMConfig from CelerisLab.config import LBMConfig
from CelerisLab.lbm.refinement import FixedPatch2D from CelerisLab.lbm.refinement import FixedPatch2D
KERNELS = Path(__file__).resolve().parents[2] / "src/CelerisLab/lbm/kernels" KERNELS = Path(__file__).resolve().parents[2] / "src/CelerisLab/lbm/kernels"
BOUNDS = (8, 6, 16, 14)
@pytest.mark.parametrize("collision", ["SRT", "MRT"]) class RefinementMRTHost(unittest.TestCase):
def test_patch_accepts_implemented_models(collision): def test_patch_accepts_implemented_models(self):
FixedPatch2D.from_config(LBMConfig(nx=24, ny=20, viscosity=.1, collision=collision), (8, 6, 16, 14)) for collision in ("SRT", "MRT"):
with self.subTest(collision=collision):
FixedPatch2D.from_config(LBMConfig(nx=24, ny=20, viscosity=.1, collision=collision), BOUNDS)
def test_patch_rejects_unimplemented(self):
for options in (dict(collision="TRT"), dict(collision="MRT", les_enabled=True)):
with self.subTest(options=options), self.assertRaises(ValueError):
FixedPatch2D.from_config(LBMConfig(nx=24, ny=20, viscosity=.1, **options), BOUNDS)
@pytest.mark.parametrize("options", [dict(collision="TRT"), dict(collision="MRT", les_enabled=True)]) def test_srt_statements_and_guards_preserved(self):
def test_patch_rejects_unimplemented(options):
with pytest.raises(ValueError):
FixedPatch2D.from_config(LBMConfig(nx=24, ny=20, viscosity=.1, **options), (8, 6, 16, 14))
def test_srt_statements_and_guards_preserved():
"""MRT is an additive compile-time branch; SRT statements and other guards remain.""" """MRT is an additive compile-time branch; SRT statements and other guards remain."""
blocks = (KERNELS / "blocks.cu").read_text() blocks = (KERNELS / "blocks.cu").read_text()
channel = (KERNELS / "step/block_channel.cu").read_text() channel = (KERNELS / "step/block_channel.cu").read_text()
@@ -27,14 +28,17 @@ def test_srt_statements_and_guards_preserved():
for text in ("compute_feq_from_momentum(drho, jx, jy, ux, uy, feq);", for text in ("compute_feq_from_momentum(drho, jx, jy, ux, uy, feq);",
"compute_feq(rho, ux, uy, feq);", "compute_feq(rho, ux, uy, feq);",
"collide_srt_no_force(f, feq, d_params.omega);"): "collide_srt_no_force(f, feq, d_params.omega);"):
assert text in blocks self.assertIn(text, blocks)
assert "collide_srt(f, feq, forcing, omega);" in channel self.assertIn("collide_srt(f, feq, forcing, omega);", channel)
assert "collide_srt_no_force(f, feq, d_params.omega);" in refinement self.assertIn("collide_srt_no_force(f, feq, d_params.omega);", refinement)
for source in (blocks, refinement): for source in (blocks, refinement):
assert "(COLLISION_MODEL != 0 && COLLISION_MODEL != 2)" in source self.assertIn("(COLLISION_MODEL != 0 && COLLISION_MODEL != 2)", source)
assert "USE_LES" in source and "STORE_PRECISION != 0" in source self.assertIn("USE_LES", source)
assert "USE_DDF_SHIFTING ||" in refinement # dense/mixed shifted stays rejected self.assertIn("STORE_PRECISION != 0", source)
self.assertIn('#if COLLISION_MODEL == 2\n#include "operators/collision_mrt.cuh"\n#endif', source)
self.assertIn("USE_DDF_SHIFTING ||", refinement) # dense/mixed shifted stays rejected
for source in (blocks, channel, refinement): for source in (blocks, channel, refinement):
assert source.count("collide_mrt_no_force(") == 1 # one MRT-only collision site each self.assertEqual(source.count("collide_mrt_no_force("), 1)
for source in (blocks, refinement):
assert '#if COLLISION_MODEL == 2\n#include "operators/collision_mrt.cuh"\n#endif' in source
if __name__ == "__main__": unittest.main()
@@ -5,6 +5,7 @@ Parity compares the blocked MRT channel root with uniform MRT OneStep: the exist
pressure-NEQ outlet branch differs by collision model, so SRT is never the reference. pressure-NEQ outlet branch differs by collision model, so SRT is never the reference.
AMR checks are finite-evolution/split-invariance smoke tests, not benchmark validation. AMR checks are finite-evolution/split-invariance smoke tests, not benchmark validation.
""" """
from dataclasses import replace
import unittest import unittest
import numpy as np import numpy as np
@@ -13,6 +14,7 @@ from CelerisLab.cuda.context import CudaContext
from CelerisLab.lbm.population_storage import _encode_physical from CelerisLab.lbm.population_storage import _encode_physical
from CelerisLab.lbm.refinement_stepper import FixedPatchStepper from CelerisLab.lbm.refinement_stepper import FixedPatchStepper
from test_channel_root_gpu import run_root, state from test_channel_root_gpu import run_root, state
from test_refinement_blocked_base import raw, exact
class RefinementMRTGPU(unittest.TestCase): class RefinementMRTGPU(unittest.TestCase):
@@ -51,5 +53,42 @@ class RefinementMRTGPU(unittest.TestCase):
else: else:
for a, b in zip(reference, raw.values): np.testing.assert_array_equal(a, b) for a, b in zip(reference, raw.values): np.testing.assert_array_equal(a, b)
def test_dense_versus_blocked_root_mrt_two_level(self):
"""Unshifted MRT: dense root (PatchCoarseStep) vs blocked root (BlockStep), same fine blocks."""
from test_refinement_blocked_base import BASE_BLOCKS, FINE_BLOCK
from test_refinement_blocked_fine import NX, NY, BOUNDS
import test_refinement_blocked_fine as fixture
cfg = LBMConfig(nx=NX, ny=NY, viscosity=.1, collision="MRT")
with CudaContext(0) as context:
results = {}
for base_block in (None, *BASE_BLOCKS):
with FixedPatchStepper(cfg, BOUNDS, context=context, fine_block=FINE_BLOCK,
base_block=base_block) as backend:
backend.initialize(*[fixture.state(*level.shape[1:], seed) for seed, level in enumerate(backend._levels)])
backend.step(4)
results[base_block] = raw(backend)
for base_block in BASE_BLOCKS:
for level, (a, b) in enumerate(zip(results[base_block], results[None])):
exact(self, a, b, f"base={base_block} level={level}")
def test_shifted_all_blocked_three_level_mrt_smoke(self):
"""Per-level shear rate omega_l = 1/(3*2**l*nu + 1/2); finite ACTIVE leaves."""
cfg = LBMConfig(nx=16, ny=16, viscosity=.1, collision="MRT", ddf_shifting=True)
kw = dict(fine_block=(4, 4), base_block=(4, 4), child=(8, 8, 16, 16), child_block=(4, 4))
with CudaContext(0) as context:
with FixedPatchStepper(cfg, (4, 4, 12, 12), context=context, **kw) as backend:
self.assertEqual(len(backend._level_cfgs), 3)
for level, level_cfg in enumerate(backend._level_cfgs):
checked = replace(level_cfg)
checked.validate() # omega is derived only by validate()
self.assertEqual(checked.collision, "MRT")
self.assertTrue(checked.ddf_shifting)
self.assertTrue(np.isclose(checked.omega, 1 / (3 * 2**level * .1 + .5), rtol=1e-14))
backend.initialize(*[state(*level.shape[1:]) for level in backend._levels])
backend.step(4)
values = backend._download_raw().values
for a, mask in zip(values, backend._state):
self.assertTrue(np.isfinite(a[:, mask]).all())
if __name__ == "__main__": unittest.main() if __name__ == "__main__": unittest.main()