simulation: consume a zero-motion translation target; empty diagnostics shape
A pending target equal to the applied offset uploaded the same record, so _run_moving saw no advance and never committed it: pending stayed latched, blocking run(n > 1) and direct steps. Commit whenever a target was pending (no_donor check and hold-record re-upload still only on advancing steps). moving_diagnostics() without moving circles now returns (0, DIAG_FLOATS) instead of the stale (0, 6). Tests for both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7886906517
commit
0d6df6e6b8
@@ -242,7 +242,8 @@ class Simulation:
|
||||
self._require_usable()
|
||||
tables = self.stepper.moving
|
||||
if tables is None:
|
||||
return np.zeros((0, 6), np.float32)
|
||||
from .body.motion import MovingTables
|
||||
return np.zeros((0, MovingTables.DIAG_FLOATS), np.float32)
|
||||
self.stream.synchronize()
|
||||
return tables.download_diagnostics()
|
||||
|
||||
@@ -785,6 +786,7 @@ class Simulation:
|
||||
(docs/moving_bodies.md 4a, 5). Any failure leaves the Simulation unusable."""
|
||||
tables = self.stepper.moving
|
||||
circles = [(obj.motion, obj.obj_id) for obj in self._moving_bodies()]
|
||||
pending = any(motion.pending is not None for motion, _ in circles)
|
||||
try:
|
||||
advancing = tables.upload_records(circles, stream)
|
||||
self.stepper._step(steps, action_gpu=self.bodies.action_gpu,
|
||||
@@ -797,10 +799,12 @@ class Simulation:
|
||||
no_donor = tables.download_diagnostics()[:, tables.DIAG.index("no_donor")]
|
||||
if np.any(no_donor > 0):
|
||||
raise RuntimeError("translating circle refill found no donor cell")
|
||||
if pending: # a target equal to the applied offset is consumed as a hold step
|
||||
for motion, _ in circles:
|
||||
motion.commit()
|
||||
tables.upload_records(circles, stream) # first hold: old = new = applied, u = 0
|
||||
stream.synchronize()
|
||||
if advancing:
|
||||
tables.upload_records(circles, stream) # first hold: old = new = applied, u = 0
|
||||
stream.synchronize()
|
||||
self._attach_moving()
|
||||
except BaseException as exc:
|
||||
for motion, _ in circles: # device geometry/state unknown after any failure
|
||||
|
||||
@@ -297,6 +297,26 @@ def test_run_one_advances_and_commits_after_sync(monkeypatch):
|
||||
assert ("step", 3, False) in writes and motion.applied == (0., .25)
|
||||
|
||||
|
||||
def test_zero_motion_target_is_consumed_by_run_one(monkeypatch):
|
||||
sim, writes, motion = advancing_simulation(monkeypatch)
|
||||
motion.set_target((0., 0.), (0., 0.)) # equal to applied: a hold target
|
||||
sim._attach_moving()
|
||||
assert sim.stepper.blocked == module.PENDING_BLOCKED
|
||||
sim.run(1, stream=STREAM)
|
||||
assert motion.pending is None and motion.applied == (0., 0.)
|
||||
assert sim.stepper.blocked is None and ("step", 1, False) in writes
|
||||
writes.clear()
|
||||
sim.run(3, stream=STREAM) # no stale latch
|
||||
assert ("step", 3, False) in writes
|
||||
|
||||
|
||||
def test_moving_diagnostics_empty_shape_matches_layout(monkeypatch):
|
||||
from CelerisLab.body.motion import MovingTables
|
||||
sim = make_simulation(monkeypatch)
|
||||
sim._failed, sim.stepper = None, SimpleNamespace(moving=None)
|
||||
assert sim.moving_diagnostics().shape == (0, MovingTables.DIAG_FLOATS)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("failure", ("upload", "first_sync", "no_donor", "hold_upload",
|
||||
"hold_sync"))
|
||||
def test_failed_advance_fails_simulation(monkeypatch, failure):
|
||||
|
||||
Reference in New Issue
Block a user