refinement: FixedPatchStepper(refinements=[]) owns a single blocked level (S1)
A zero-refinement FixedPatchStepper is the PeriodicBlockStepper capability: one doubly periodic blocked SRT level (no LES, Jets or exchange observation) whose level_objects[0] are stationary passive circles. The circle tables come from the same checks, now blocks.static_circle_tables (moved from block_stepper, pycuda-free); BFL, halo and BlockStep launches are the shared helpers, with an all-ACTIVE (null) kind. Base-level bodies stay rejected for depth >= 2. PeriodicBlockStepper and its callers are unchanged (deleted only after S1's GPU result). Tests: host contract rejections before CUDA for the single level; GPU bitwise comparison against PeriodicBlockStepper for every split, with and without the circle (the S2 gate). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a0700ab370
commit
f764d68c81
@@ -25,6 +25,7 @@ from CelerisLab.cuda import compiler
|
||||
from CelerisLab.lbm import block_stepper as implementation
|
||||
from CelerisLab.lbm.blocks import NO_DONOR, BlockLayout2D
|
||||
from CelerisLab.lbm.block_stepper import PeriodicBlockStepper
|
||||
from CelerisLab.lbm.refinement_stepper import FixedPatchStepper
|
||||
from CelerisLab.lbm.descriptors import D2Q9_EX, D2Q9_EY
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
@@ -118,6 +119,23 @@ class CpuContracts(unittest.TestCase):
|
||||
def forbidden_context(self):
|
||||
return Mock(activate=Mock(side_effect=AssertionError("CUDA reached")), sm_arch="sm_70")
|
||||
|
||||
def test_single_level_fixed_patch_contract_before_cuda(self):
|
||||
def single(cfg=None, **kw):
|
||||
options = dict(context=self.forbidden_context(), refinements=[], level_blocks=[(12, 16)])
|
||||
options.update(kw)
|
||||
return FixedPatchStepper(cfg or config(), **options)
|
||||
for cfg, kw in ((None, dict(level_blocks=[None])),
|
||||
(None, dict(coarse_boundary="channel")),
|
||||
(None, dict(coarse_boundary="slot_reservoir")),
|
||||
(config(collision="MRT"), {}),
|
||||
(None, dict(observe_raw_exchange=True)),
|
||||
(None, dict(level_jets=[(object(),)])),
|
||||
(None, dict(level_objects=[(circle((15.5, 4.0), 4.3),)]))): # touches edge
|
||||
with self.subTest(kw=kw), self.assertRaises(ValueError):
|
||||
single(cfg, **kw)
|
||||
with self.assertRaisesRegex(AssertionError, "CUDA reached"):
|
||||
single(level_objects=[(circle(),)])
|
||||
|
||||
def test_unsupported_inputs_rejected_before_cuda(self):
|
||||
layout = BlockLayout2D.build(NY, NX, 12, 16)
|
||||
cases = ((config(), BlockLayout2D.build(NY, NX, 12, 16, periodic=(True, False))),
|
||||
@@ -139,7 +157,7 @@ class CpuContracts(unittest.TestCase):
|
||||
with self.subTest(center=center, radius=radius), self.assertRaises(ValueError):
|
||||
PeriodicBlockStepper(config(), layout, context=self.forbidden_context(),
|
||||
objects=[circle(center, radius)])
|
||||
with patch.object(implementation, "_static_circles") as prepare:
|
||||
with patch.object(implementation, "static_circle_tables") as prepare:
|
||||
with self.assertRaisesRegex(AssertionError, "CUDA reached"): # body-free path
|
||||
PeriodicBlockStepper(config(), layout, context=self.forbidden_context(), objects=iter(()))
|
||||
prepare.assert_not_called()
|
||||
@@ -177,7 +195,7 @@ class CpuContracts(unittest.TestCase):
|
||||
|
||||
def test_static_circle_tables(self):
|
||||
layout = BlockLayout2D.build(NY, NX, 12, 16)
|
||||
tables = implementation._static_circles(layout, (circle(),))
|
||||
tables = implementation.static_circle_tables(layout, (circle(),))
|
||||
links = CircleGeometry(*CENTER, RADIUS).build_cut_links(NX, NY, domain_flags=dense_flags())
|
||||
self.assertEqual(len(tables["cell"]), len(links))
|
||||
np.testing.assert_array_equal(tables["q"], np.array([l.q for l in links], np.float32))
|
||||
@@ -331,6 +349,29 @@ class PeriodicBlockGPU(unittest.TestCase):
|
||||
*(a - b for a, b in zip(after, before))), flush=True)
|
||||
self.assertLessEqual(error, ORACLE_ATOL)
|
||||
|
||||
def test_single_level_fixed_patch_is_bit_identical(self):
|
||||
"""FixedPatchStepper(refinements=[]) reproduces PeriodicBlockStepper bit for bit."""
|
||||
from CelerisLab.cuda.context import CudaContext
|
||||
g0 = initial_state()
|
||||
with CudaContext(0) as context:
|
||||
for (by, bx), objects in [(split, objects) for split in SPLITS
|
||||
for objects in ((), (circle(),))]:
|
||||
with self.subTest(split=(by, bx), bodies=len(objects)):
|
||||
layout = BlockLayout2D.build(NY, NX, by, bx)
|
||||
with PeriodicBlockStepper(config(), layout, context=context,
|
||||
objects=objects) as backend:
|
||||
backend.initialize(g0)
|
||||
backend.step(STEPS)
|
||||
expected = backend.download()
|
||||
with FixedPatchStepper(config(), context=context, refinements=[],
|
||||
level_blocks=[(by, bx)],
|
||||
level_objects=[objects]) as backend:
|
||||
backend.initialize(g0)
|
||||
backend.step(STEPS)
|
||||
self.assertEqual(backend.step_count, STEPS)
|
||||
(actual,) = backend.download()
|
||||
np.testing.assert_array_equal(actual, expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user