diff --git a/tests/integration/test_checkpoint_bodies_gpu.py b/tests/integration/test_checkpoint_bodies_gpu.py new file mode 100644 index 0000000..9d67e24 --- /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 with the same scene, 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) + # The loader requires the same body count and geometry: rebuild the scene. + restored = scene() + 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()