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()
|
self._require_usable()
|
||||||
tables = self.stepper.moving
|
tables = self.stepper.moving
|
||||||
if tables is None:
|
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()
|
self.stream.synchronize()
|
||||||
return tables.download_diagnostics()
|
return tables.download_diagnostics()
|
||||||
|
|
||||||
@@ -785,6 +786,7 @@ class Simulation:
|
|||||||
(docs/moving_bodies.md 4a, 5). Any failure leaves the Simulation unusable."""
|
(docs/moving_bodies.md 4a, 5). Any failure leaves the Simulation unusable."""
|
||||||
tables = self.stepper.moving
|
tables = self.stepper.moving
|
||||||
circles = [(obj.motion, obj.obj_id) for obj in self._moving_bodies()]
|
circles = [(obj.motion, obj.obj_id) for obj in self._moving_bodies()]
|
||||||
|
pending = any(motion.pending is not None for motion, _ in circles)
|
||||||
try:
|
try:
|
||||||
advancing = tables.upload_records(circles, stream)
|
advancing = tables.upload_records(circles, stream)
|
||||||
self.stepper._step(steps, action_gpu=self.bodies.action_gpu,
|
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")]
|
no_donor = tables.download_diagnostics()[:, tables.DIAG.index("no_donor")]
|
||||||
if np.any(no_donor > 0):
|
if np.any(no_donor > 0):
|
||||||
raise RuntimeError("translating circle refill found no donor cell")
|
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:
|
for motion, _ in circles:
|
||||||
motion.commit()
|
motion.commit()
|
||||||
tables.upload_records(circles, stream) # first hold: old = new = applied, u = 0
|
if advancing:
|
||||||
stream.synchronize()
|
tables.upload_records(circles, stream) # first hold: old = new = applied, u = 0
|
||||||
|
stream.synchronize()
|
||||||
self._attach_moving()
|
self._attach_moving()
|
||||||
except BaseException as exc:
|
except BaseException as exc:
|
||||||
for motion, _ in circles: # device geometry/state unknown after any failure
|
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)
|
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",
|
@pytest.mark.parametrize("failure", ("upload", "first_sync", "no_donor", "hold_upload",
|
||||||
"hold_sync"))
|
"hold_sync"))
|
||||||
def test_failed_advance_fails_simulation(monkeypatch, failure):
|
def test_failed_advance_fails_simulation(monkeypatch, failure):
|
||||||
|
|||||||
Reference in New Issue
Block a user