From f626cbcda2fddca505d1997f7a7061cdce2df9cd Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 18:25:24 +0000 Subject: [PATCH] test: bitwise checkpoint round trip for a three-cylinder body scene Save -> load into a fresh Simulation must restore populations (bitwise), flags, body and step counts, and both copies must stay bitwise identical after 40 further steps. Jets remain non-checkpointable (documented). Co-Authored-By: Claude Opus 5.5 --- .../integration/test_checkpoint_bodies_gpu.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/integration/test_checkpoint_bodies_gpu.py diff --git a/tests/integration/test_checkpoint_bodies_gpu.py b/tests/integration/test_checkpoint_bodies_gpu.py new file mode 100644 index 0000000..103ec48 --- /dev/null +++ b/tests/integration/test_checkpoint_bodies_gpu.py @@ -0,0 +1,66 @@ +"""Checkpoint save -> load round trip for a scene with passive bodies. Requires GPU. + +A fresh Simulation restored from the checkpoint must hold the same populations, +flags, body count and step count, and must stay bitwise identical to the original +after both continue for the same number of steps. Checkpoints with Jets are +refused (documented limitation) and are not covered here. +""" + +import os +import tempfile +import unittest + +import numpy as np + +from ._cases import make_sim + +# Three separated cylinders (pinball-like); cut-link receivers stay in bulk fluid. +CIRCLES = (((40, 32), 6), ((56, 22), 6), ((56, 42), 6)) + + +def scene(): + sim = make_sim() + sim.initialize() + for center, radius in CIRCLES: + sim.add_body("circle", center=center, radius=radius) + sim.sync_bodies() + return sim + + +class CheckpointBodiesGPU(unittest.TestCase): + def test_round_trip_is_bitwise_and_continues_identically(self): + sim = scene() + sim.run(60) + with tempfile.NamedTemporaryFile(suffix=".h5", delete=False) as f: + path = f.name + restored = None + try: + saved = sim.save_checkpoint(path) + restored = make_sim() + restored.initialize() + restored.load_checkpoint(saved) + + self.assertEqual(restored.bodies.count, len(CIRCLES)) + self.assertEqual(restored.stepper.step_count, sim.stepper.step_count) + np.testing.assert_array_equal(restored.get_flags(), sim.get_flags()) + a, b = sim.get_ddf(), restored.get_ddf() + self.assertEqual(a.dtype, b.dtype) + self.assertEqual(a.tobytes(), b.tobytes()) + + sim.run(40) + restored.run(40) + self.assertEqual(restored.stepper.step_count, sim.stepper.step_count) + a, b = sim.get_ddf(), restored.get_ddf() + self.assertTrue(np.isfinite(a).all()) + self.assertEqual(a.tobytes(), b.tobytes()) + for i in range(len(CIRCLES)): + self.assertTrue(np.all(np.isfinite(restored.read_force(i)))) + finally: + if restored is not None: + restored.close() + os.unlink(path) + sim.close() + + +if __name__ == "__main__": + unittest.main()