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:
Claude
2026-10-26 22:36:05 +00:00
co-authored by Claude Opus 5.5
parent f764d68c81
commit 985db14249
3 changed files with 80 additions and 8 deletions
@@ -130,6 +130,7 @@ class CpuContracts(unittest.TestCase):
(config(collision="MRT"), {}),
(None, dict(observe_raw_exchange=True)),
(None, dict(level_jets=[(object(),)])),
(None, dict(explosion="projected_linear")),
(None, dict(level_objects=[(circle((15.5, 4.0), 4.3),)]))): # touches edge
with self.subTest(kw=kw), self.assertRaises(ValueError):
single(cfg, **kw)
@@ -354,16 +355,17 @@ class PeriodicBlockGPU(unittest.TestCase):
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)):
for (by, bx), objects, shifted in [(split, objects, shifted) for split in SPLITS
for objects in ((), (circle(),))
for shifted in (False, True)]:
with self.subTest(split=(by, bx), bodies=len(objects), shifted=shifted):
layout = BlockLayout2D.build(NY, NX, by, bx)
with PeriodicBlockStepper(config(), layout, context=context,
with PeriodicBlockStepper(config(ddf_shifting=shifted), layout, context=context,
objects=objects) as backend:
backend.initialize(g0)
backend.step(STEPS)
expected = backend.download()
with FixedPatchStepper(config(), context=context, refinements=[],
with FixedPatchStepper(config(ddf_shifting=shifted), context=context, refinements=[],
level_blocks=[(by, bx)],
level_objects=[objects]) as backend:
backend.initialize(g0)