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:
Claude
2026-10-18 10:31:25 +00:00
co-authored by Claude Opus 5.5
parent 00d7a54829
commit 3ea131e92c
3 changed files with 73 additions and 30 deletions
+1 -1
View File
@@ -142,7 +142,7 @@ class FixedPatchTests(unittest.TestCase):
FixedPatch2D.from_config(LBMConfig(nx=nx, ny=ny, viscosity=.1), bounds)
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},
{"store_precision": "FP16S"}, {"lattice_model": "D3Q19"},
{"data_type": "FP64"}, {"omega_min": 1.0}):
+33 -29
View File
@@ -1,40 +1,44 @@
"""Host guards and source contract for MRT reuse in refined/blocked stepping (no CUDA)."""
from pathlib import Path
import pytest
import unittest
from CelerisLab.config import LBMConfig
from CelerisLab.lbm.refinement import FixedPatch2D
KERNELS = Path(__file__).resolve().parents[2] / "src/CelerisLab/lbm/kernels"
BOUNDS = (8, 6, 16, 14)
@pytest.mark.parametrize("collision", ["SRT", "MRT"])
def test_patch_accepts_implemented_models(collision):
FixedPatch2D.from_config(LBMConfig(nx=24, ny=20, viscosity=.1, collision=collision), (8, 6, 16, 14))
class RefinementMRTHost(unittest.TestCase):
def test_patch_accepts_implemented_models(self):
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)
def test_srt_statements_and_guards_preserved(self):
"""MRT is an additive compile-time branch; SRT statements and other guards remain."""
blocks = (KERNELS / "blocks.cu").read_text()
channel = (KERNELS / "step/block_channel.cu").read_text()
refinement = (KERNELS / "refinement.cu").read_text()
for text in ("compute_feq_from_momentum(drho, jx, jy, ux, uy, feq);",
"compute_feq(rho, ux, uy, feq);",
"collide_srt_no_force(f, feq, d_params.omega);"):
self.assertIn(text, blocks)
self.assertIn("collide_srt(f, feq, forcing, omega);", channel)
self.assertIn("collide_srt_no_force(f, feq, d_params.omega);", refinement)
for source in (blocks, refinement):
self.assertIn("(COLLISION_MODEL != 0 && COLLISION_MODEL != 2)", source)
self.assertIn("USE_LES", source)
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):
self.assertEqual(source.count("collide_mrt_no_force("), 1)
@pytest.mark.parametrize("options", [dict(collision="TRT"), dict(collision="MRT", les_enabled=True)])
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."""
blocks = (KERNELS / "blocks.cu").read_text()
channel = (KERNELS / "step/block_channel.cu").read_text()
refinement = (KERNELS / "refinement.cu").read_text()
for text in ("compute_feq_from_momentum(drho, jx, jy, ux, uy, feq);",
"compute_feq(rho, ux, uy, feq);",
"collide_srt_no_force(f, feq, d_params.omega);"):
assert text in blocks
assert "collide_srt(f, feq, forcing, omega);" in channel
assert "collide_srt_no_force(f, feq, d_params.omega);" in refinement
for source in (blocks, refinement):
assert "(COLLISION_MODEL != 0 && COLLISION_MODEL != 2)" in source
assert "USE_LES" in source and "STORE_PRECISION != 0" in source
assert "USE_DDF_SHIFTING ||" in refinement # dense/mixed shifted stays rejected
for source in (blocks, channel, refinement):
assert source.count("collide_mrt_no_force(") == 1 # one MRT-only collision site each
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.
AMR checks are finite-evolution/split-invariance smoke tests, not benchmark validation.
"""
from dataclasses import replace
import unittest
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.refinement_stepper import FixedPatchStepper
from test_channel_root_gpu import run_root, state
from test_refinement_blocked_base import raw, exact
class RefinementMRTGPU(unittest.TestCase):
@@ -51,5 +53,42 @@ class RefinementMRTGPU(unittest.TestCase):
else:
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()