refinement: single level matches PeriodicBlockStepper lifecycle; host contract tests
Review follow-ups for S1 (f764d68):
- a single level starts its spare buffer NaN, as PeriodicBlockStepper does,
so stale halos cannot hide (multilevel initialization unchanged);
- projected explosion is rejected for a single level;
- new tests/unit/test_single_level.py: NaN spare after initialize, per-step
launch order halo -> BFL (circles only) -> BlockStep with a null kind and
one wait per step(n), circle solids excluded from the state mask;
- the GPU bitwise comparison also covers shifted storage.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f764d68c81
commit
985db14249
3 files changed
+80
-8
No files matched your search
@@ -0,0 +1,67 @@
|
||||
"""Host contract of FixedPatchStepper(refinements=[]): one blocked level (mocked driver).
|
||||
|
||||
Per step: halo refresh -> static BFL (circles only) -> BlockStep with a null
|
||||
kind, one stream wait per step(n) call, NaN spare after initialize.
|
||||
"""
|
||||
from contextlib import nullcontext
|
||||
from unittest.mock import Mock
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
|
||||
from CelerisLab.body.geometry.circle import CircleGeometry
|
||||
from CelerisLab.body.objects import SimObject
|
||||
from CelerisLab.config import LBMConfig
|
||||
from tests.unit.test_refinement_three_level_multipatch import host_module
|
||||
|
||||
|
||||
def circle(center=(15.5, 11.5), radius=4.3):
|
||||
return SimObject(0, geometry=CircleGeometry(*center, radius), center=center, radius=radius)
|
||||
|
||||
|
||||
def backend(objects=()):
|
||||
host, log = host_module(), []
|
||||
module = Mock()
|
||||
module.get_global.return_value = (Mock(), 20)
|
||||
module.get_function.side_effect = lambda name: Mock(
|
||||
side_effect=lambda *a, **k: log.append((name, a)))
|
||||
host.compiler.build_module.return_value = module
|
||||
host.cuda.mem_alloc.side_effect = lambda size: Mock(size=size)
|
||||
host.cuda.memcpy_htod.side_effect = lambda pointer, array: log.append(("upload", (pointer, array)))
|
||||
run = host.FixedPatchStepper(LBMConfig(nx=32, ny=24, viscosity=.1),
|
||||
context=Mock(activate=nullcontext, sm_arch="sm_70"),
|
||||
refinements=[], level_blocks=[(12, 16)], level_objects=[objects])
|
||||
run._stream.synchronize.side_effect = lambda: log.append(("sync", ()))
|
||||
log.clear()
|
||||
run.initialize(np.full(run._levels[0].shape, 1 / 9, np.float32))
|
||||
return run, log
|
||||
|
||||
|
||||
class SingleLevel(unittest.TestCase):
|
||||
def test_spare_starts_nan_and_current_holds_state(self):
|
||||
run, log = backend()
|
||||
level = run._levels[0]
|
||||
uploads = {id(a[0]): a[1] for name, a in log if name == "upload"}
|
||||
self.assertTrue(np.isnan(uploads[id(level.spare)]).all())
|
||||
self.assertTrue(np.isfinite(level.layout.to_dense(uploads[id(level.current)])).all())
|
||||
|
||||
def test_launch_order_per_step(self):
|
||||
for objects, order in (((), ["BlockHaloCopy", "BlockStep"]),
|
||||
((circle(),), ["BlockHaloCopy", "BlockCurvedBoundary", "BlockStep"])):
|
||||
with self.subTest(bodies=len(objects)):
|
||||
run, log = backend(objects)
|
||||
log.clear()
|
||||
run.step(2)
|
||||
self.assertEqual([name for name, _ in log], order * 2 + ["sync"])
|
||||
step = [a for name, a in log if name == "BlockStep"][0]
|
||||
self.assertEqual(int(step[4]), 0) # null kind: every core cell ACTIVE
|
||||
|
||||
def test_circle_solids_are_not_state(self):
|
||||
run, _ = backend((circle(),))
|
||||
solid = run._level_boundaries[0].tables["solid"]
|
||||
self.assertTrue(solid.any())
|
||||
np.testing.assert_array_equal(run._state[0], ~solid)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user