Jet amplitude setter: failed state on device errors, commit after upload

FixedPatchStepper.set_jet_amplitude validates index/amplitude and builds
the new rows before touching the device; invalid input leaves the
instance usable. A copy/sync error marks it failed (as initialize/step/
download do) and re-raises; host state (_jets, blocked velocity table)
commits only after a successful upload. CurvedLinkSoA.set_jet_amplitude
likewise uploads a new array first and commits jets/surface_velocity
afterwards. Focused mocked-driver tests for dense and blocked copy
failure, invalid input and IndexError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-15 08:49:35 +00:00
1 parent 508d412e43
commit 063508d1a3
3 files changed
+59 -11

No files matched your search

@@ -75,6 +75,38 @@ class JetStepperCPU(unittest.TestCase):
self.assertEqual(backend._jets[0].amplitude, -SPEED)
backend.close()
def test_copy_failure_marks_failed_and_keeps_host_amplitude(self):
for fine_block in (None, FINE_BLOCKS[1]): # dense SoA and blocked tables
backend, _ = mocked([circle()], [jet()], fine_block)
status = backend._status
for bad, error in ((1.5, ValueError), (float("nan"), ValueError)):
with self.assertRaises(error):
backend.set_jet_amplitude(0, bad)
self.assertEqual(backend._status, status) # invalid input stays usable
with self.assertRaises(IndexError):
backend.set_jet_amplitude(1, -SPEED)
self.assertEqual(backend._status, status)
dense = backend._fine_curved
before = (dense.surface_velocity if dense is not None
else backend._fine_tables["surface_velocity"]).copy()
boom = Mock(side_effect=RuntimeError("copy failed"))
with patch.object(cuda, "memcpy_htod", boom), patch.object(cuda, "memcpy_htod_async", boom), \
patch.object(cuda, "pagelocked_empty", side_effect=lambda shape, dtype: np.empty(shape, dtype)):
with self.assertRaisesRegex(RuntimeError, "copy failed"):
backend.set_jet_amplitude(0, -SPEED)
self.assertEqual(backend._status, "failed", fine_block)
self.assertEqual(backend._jets[0].amplitude, SPEED)
if dense is not None:
self.assertEqual(dense.jets[0].amplitude, SPEED)
np.testing.assert_array_equal(dense.surface_velocity, before)
else:
np.testing.assert_array_equal(backend._fine_tables["surface_velocity"], before)
with self.assertRaisesRegex(RuntimeError, "failed"):
backend.step(1)
with self.assertRaisesRegex(RuntimeError, "failed"):
backend.set_jet_amplitude(0, -SPEED)
backend.close()
def test_jet_overlapping_legacy_aperture_rejected_before_cuda(self):
with patch.object(cuda, "Stream", side_effect=AssertionError("CUDA touched")):
with self.assertRaisesRegex(ValueError, "SurfaceActuation"):