From 2469af70d0844396c18a7c404b173a57a799901e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 10:28:11 +0000 Subject: [PATCH] test: dense/blocked parity rows for MRT bodies and two fine bodies Extends DenseBlockedRouteParity (test-only, on 1573942) with the residual dense-supported combinations named before any dense-route deletion: - test_mrt_bodies: MRT on a periodic root with a passive circle, an actuated circle (+/-) and a velocity Jet with an amplitude switch (MRT slot/reservoir is rejected on every route: on_site_velocity is SRT-only); - test_two_bodies: two circles on a wider patch (4, 4, 28, 20), periodic and slot/reservoir, Jet on one + passive other, and two actuated (+/-); routes D/D, D/B [None, (8, 8)], B/B [(12, 8), (8, 8)]. Same gate: exact FP32 bits and NaN masks at 0, 1, 2, 8 and after re-initialize; D/B vs B/B raw-exchange ledgers equal for every body. Docstring table updated; no tolerance, no src change. Co-Authored-By: Claude Opus 5.5 --- .../multigrid/test_refinement_blocked_base.py | 60 ++++++++++++++++--- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/tests/validation/multigrid/test_refinement_blocked_base.py b/tests/validation/multigrid/test_refinement_blocked_base.py index 5ecf3ae..5209000 100644 --- a/tests/validation/multigrid/test_refinement_blocked_base.py +++ b/tests/validation/multigrid/test_refinement_blocked_base.py @@ -23,13 +23,16 @@ Route capabilities (rejections checked before CUDA by test_route_capability_matr channel root rejected rejected yes several L1 rectangles rejected rejected yes depth >= 3 with a dense level rejected rejected yes + MRT, periodic root, circle/actuation/Jet yes yes yes (parity gate) + two fine bodies (Jet + passive, two actuated) yes yes yes (parity gate) + MRT with slot/reservoir root rejected on every route (on_site_velocity is SRT-only) Slot/reservoir walls are compile-time WALL_* macros on a dense root and the d_block_wall constant on a blocked root: BC-encoding parity, bit-identical by this gate. Dense fine bodies use CurvedLinkSoA + CurvedBoundaryKernel (n_objects > 0), blocked ones BlockCurvedBoundary; same links and physics, -compared through the population rows. Parked here: MRT (covered by -test_refinement_mrt_gpu::test_dense_versus_blocked_mrt_two_level), shifted and -depth >= 3 (no dense route to compare), several bodies (later slice). +compared through the population rows. MRT body-free is also covered by +test_refinement_mrt_gpu::test_dense_versus_blocked_mrt_two_level. Parked: +shifted and depth >= 3 (no dense route to compare). """ from contextlib import ExitStack, contextmanager, nullcontext from dataclasses import replace @@ -354,6 +357,25 @@ class BlockedBaseGPU(unittest.TestCase): ROUTES = (("D/D", [None, None]), ("D/B", [None, FINE_BLOCK]), ("B/B", [(12, 8), FINE_BLOCK])) +# Two-body rows: patch (4, 4, 28, 20) gives a 40 x 56 fine window, tiled (8, 8). +WIDE_BOUNDS = (4, 4, 28, 20) +WIDE_ROUTES = (("D/D", [None, None]), ("D/B", [None, (8, 8)]), ("B/B", [(12, 8), (8, 8)])) +LEFT, RIGHT, RADIUS = (12.0, 12.0), (20.0, 12.0), 2.15 + + +def body(ident, center, spec=None): + from CelerisLab.body.geometry.circle import CircleGeometry + from CelerisLab.body.objects import SimObject + return SimObject(ident, geometry=CircleGeometry(*center, RADIUS), center=center, radius=RADIUS, + surface_actuation=spec) + + +TWO_BODY_CASES = ( + ("jet + passive", lambda: [body(0, LEFT), body(1, RIGHT)], + lambda: [actuation(SPEED).as_jet(LEFT, RADIUS)], -SPEED), + ("actuated +/-", lambda: [body(0, LEFT, actuation(SPEED)), body(1, RIGHT, actuation(-SPEED))], + lambda: [], None), +) def same_summary(a, b): @@ -414,17 +436,18 @@ def replace_value(tree, path, value=None): class DenseBlockedRouteParity(unittest.TestCase): """D/D vs D/B vs B/B, unshifted SRT, one circle or one velocity Jet; see module docstring.""" - def run_routes(self, mode, objects=(), jets=(), switch=None): + def run_routes(self, mode, objects=(), jets=(), switch=None, cfg=None, bounds=BOUNDS, routes=ROUTES): from CelerisLab.cuda.context import CudaContext - cfg = config(mode) - p = FixedPatch2D.from_config(cfg, BOUNDS) + cfg = cfg or config(mode) + p = FixedPatch2D.from_config(cfg, bounds) arrays = state(NY, NX, 1), state(*p.fine_shape, 2) + bodies = sorted({obj.obj_id for obj in objects}) results = {} with CudaContext(0) as context: - for name, blocks in ROUTES: + for name, blocks in routes: observe = bool(objects) and blocks[1] is not None snapshots, ledgers = [], [] - with FixedPatchStepper(cfg, context=context, coarse_boundary=mode, refinements=[BOUNDS], + with FixedPatchStepper(cfg, context=context, coarse_boundary=mode, refinements=[bounds], level_blocks=blocks, level_objects=[(), tuple(objects)], level_jets=[(), tuple(jets)], observe_raw_exchange=observe) as backend: @@ -435,7 +458,8 @@ class DenseBlockedRouteParity(unittest.TestCase): backend.step(step - backend.step_count) snapshots.append(backend.download() + raw(backend)) if observe and step: - ledgers.append(tuple(backend.read_raw_exchange(0, window=window) + ledgers.append(tuple(backend.read_raw_exchange(body, window=window) + for body in bodies for window in ("last_base_step", "last_substep"))) backend.initialize(*arrays) snapshots.append(backend.download() + raw(backend)) @@ -475,6 +499,24 @@ class DenseBlockedRouteParity(unittest.TestCase): self.check(f"{mode} jet switch", self.run_routes(mode, objects=[circle()], jets=[jet()], switch=-SPEED)) + def test_mrt_bodies(self): + """MRT, periodic root (on_site_velocity slot/reservoir is SRT-only on every route).""" + cfg = replace(config("periodic"), collision="MRT") + for label, objects, jets, switch in (("circle", [circle()], (), None), + ("actuated +", [circle(actuation(SPEED))], (), None), + ("actuated -", [circle(actuation(-SPEED))], (), None), + ("jet switch", [circle()], [jet()], -SPEED)): + self.check(f"MRT periodic {label}", + self.run_routes("periodic", objects, jets, switch, cfg=cfg)) + + def test_two_bodies(self): + """Two circles on a wider patch (same tiles on every route): Jet + passive, two actuated.""" + for mode in ("periodic", "slot_reservoir"): + for label, objects, jets, switch in TWO_BODY_CASES: + self.check(f"{mode} two bodies {label}", + self.run_routes(mode, objects(), jets(), switch, bounds=WIDE_BOUNDS, + routes=WIDE_ROUTES)) + if __name__ == "__main__": unittest.main()