diff --git a/tests/unit/test_refinement.py b/tests/unit/test_refinement.py index 1c72c79..f8866fd 100644 --- a/tests/unit/test_refinement.py +++ b/tests/unit/test_refinement.py @@ -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}): diff --git a/tests/unit/test_refinement_mrt.py b/tests/unit/test_refinement_mrt.py index 07f0db8..5f33782 100644 --- a/tests/unit/test_refinement_mrt.py +++ b/tests/unit/test_refinement_mrt.py @@ -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() diff --git a/tests/validation/multigrid/test_refinement_mrt_gpu.py b/tests/validation/multigrid/test_refinement_mrt_gpu.py index 196c03c..7421a38 100644 --- a/tests/validation/multigrid/test_refinement_mrt_gpu.py +++ b/tests/validation/multigrid/test_refinement_mrt_gpu.py @@ -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()