added capture time for every sweep
This commit is contained in:
@@ -39,12 +39,19 @@ from python_app.orchestration.shm.ring_writer import ShmRingWriter
|
||||
from python_app.storage.npz.serialize import serialize_result_collection, serialize_trace_collection
|
||||
|
||||
|
||||
def _trace(in_pos: int, out_pos: int, n: int) -> TraceData:
|
||||
def _trace(in_pos: int, out_pos: int, n: int, *, capture_ns: tuple[int, int] = (0, 0)) -> TraceData:
|
||||
"""Build a trace with float32-exact data so round-trips compare exactly."""
|
||||
freq = np.arange(n, dtype=np.float32) + 1.0
|
||||
s11 = (np.arange(n, dtype=np.float32) + 0.5j * np.arange(n, dtype=np.float32)).astype(np.complex64)
|
||||
s21 = (-np.arange(n, dtype=np.float32) + 2.0j * np.arange(n, dtype=np.float32)).astype(np.complex64)
|
||||
return TraceData(combo=ComboKey(input=in_pos, output=out_pos), frequency_hz=freq, s11=s11, s21=s21)
|
||||
return TraceData(
|
||||
combo=ComboKey(input=in_pos, output=out_pos),
|
||||
frequency_hz=freq,
|
||||
s11=s11,
|
||||
s21=s21,
|
||||
capture_start_ns=capture_ns[0],
|
||||
capture_end_ns=capture_ns[1],
|
||||
)
|
||||
|
||||
|
||||
class TraceCollectionRoundTripTest(unittest.TestCase):
|
||||
@@ -52,7 +59,7 @@ class TraceCollectionRoundTripTest(unittest.TestCase):
|
||||
collection = SweepCollection(
|
||||
collection_id=7,
|
||||
monotonic_ns=123,
|
||||
traces=[_trace(0, 0, 4), _trace(3, 1, 2)],
|
||||
traces=[_trace(0, 0, 4, capture_ns=(11, 13)), _trace(3, 1, 2, capture_ns=(15, 19))],
|
||||
capture_start_ns=10,
|
||||
capture_end_ns=20,
|
||||
)
|
||||
@@ -66,6 +73,10 @@ class TraceCollectionRoundTripTest(unittest.TestCase):
|
||||
self.assertTrue(np.array_equal(got.frequency_hz, original.frequency_hz))
|
||||
self.assertTrue(np.array_equal(got.s11, original.s11))
|
||||
self.assertTrue(np.array_equal(got.s21, original.s21))
|
||||
self.assertEqual(
|
||||
(got.capture_start_ns, got.capture_end_ns),
|
||||
(original.capture_start_ns, original.capture_end_ns),
|
||||
)
|
||||
|
||||
def test_raw_round_trips(self) -> None:
|
||||
self._assert_round_trips(RAW_MAGIC)
|
||||
@@ -78,6 +89,34 @@ class TraceCollectionRoundTripTest(unittest.TestCase):
|
||||
decoded = decode_trace_collection(serialize_trace_collection(collection, RAW_MAGIC), RAW_MAGIC)
|
||||
self.assertEqual(decoded.traces, [])
|
||||
|
||||
def test_payload_without_per_trace_window_table_still_decodes(self) -> None:
|
||||
# A producer built before per-trace timing stops after the collection
|
||||
# window; its traces must still decode, with the timestamps left at zero.
|
||||
collection = SweepCollection(
|
||||
collection_id=4,
|
||||
monotonic_ns=5,
|
||||
traces=[_trace(1, 0, 3, capture_ns=(7, 9))],
|
||||
capture_start_ns=6,
|
||||
capture_end_ns=10,
|
||||
)
|
||||
full = serialize_trace_collection(collection, RAW_MAGIC)
|
||||
legacy = full[: -(4 + 16 * len(collection.traces))]
|
||||
|
||||
decoded = decode_trace_collection(legacy, RAW_MAGIC)
|
||||
self.assertEqual((decoded.capture_start_ns, decoded.capture_end_ns), (6, 10))
|
||||
self.assertEqual(len(decoded.traces), 1)
|
||||
self.assertEqual((decoded.traces[0].capture_start_ns, decoded.traces[0].capture_end_ns), (0, 0))
|
||||
|
||||
def test_per_trace_window_count_mismatch_is_rejected(self) -> None:
|
||||
collection = SweepCollection(
|
||||
collection_id=4, monotonic_ns=5, traces=[_trace(1, 0, 3, capture_ns=(7, 9))]
|
||||
)
|
||||
payload = serialize_trace_collection(collection, RAW_MAGIC)
|
||||
# Overwrite the window-table count (u32 before the single 16-byte pair).
|
||||
corrupt = payload[:-20] + struct.pack("<I", 2) + payload[-16:]
|
||||
with self.assertRaises(ValueError):
|
||||
decode_trace_collection(corrupt, RAW_MAGIC)
|
||||
|
||||
|
||||
class ResultCollectionRoundTripTest(unittest.TestCase):
|
||||
def test_all_payload_kinds_round_trip(self) -> None:
|
||||
|
||||
@@ -140,6 +140,36 @@ class SwitchedMatrixComboAcquisitionTest(unittest.TestCase):
|
||||
expected_combos,
|
||||
)
|
||||
|
||||
def test_each_switch_step_stamps_its_traces_with_its_own_capture_window(self) -> None:
|
||||
# The whole point of per-trace timing: three switch steps are measured one
|
||||
# after another, so their traces must NOT all share the collection window.
|
||||
service, _inner, _input_switch = _switched_service(input_steps=3)
|
||||
|
||||
collection = service.acquire_collection(collection_id=7)
|
||||
|
||||
windows_by_step: dict[int, set[tuple[int, int]]] = {}
|
||||
for trace in collection.traces:
|
||||
step = int(trace.combo.input) // _INNER_INPUTS
|
||||
windows_by_step.setdefault(step, set()).add(
|
||||
(int(trace.capture_start_ns), int(trace.capture_end_ns))
|
||||
)
|
||||
|
||||
self.assertEqual(sorted(windows_by_step), [0, 1, 2])
|
||||
for step, windows in windows_by_step.items():
|
||||
self.assertEqual(len(windows), 1, f"step {step} traces disagree on their window")
|
||||
start_ns, end_ns = next(iter(windows))
|
||||
self.assertGreater(start_ns, 0)
|
||||
self.assertGreaterEqual(end_ns, start_ns)
|
||||
# Each step's window sits inside the collection's.
|
||||
self.assertGreaterEqual(start_ns, collection.capture_start_ns)
|
||||
self.assertLessEqual(end_ns, collection.capture_end_ns)
|
||||
|
||||
# Steps are strictly ordered in time — the whole reason the collection-level
|
||||
# window cannot stand in for a per-combo timestamp.
|
||||
step_starts = [next(iter(windows_by_step[step]))[0] for step in sorted(windows_by_step)]
|
||||
self.assertEqual(step_starts, sorted(step_starts))
|
||||
self.assertGreater(len(set(step_starts)), 1)
|
||||
|
||||
|
||||
class ManualComboCaptureUsesTargetedAcquisitionTest(unittest.TestCase):
|
||||
"""The per-combo capture session must not sweep the full widened matrix."""
|
||||
|
||||
Reference in New Issue
Block a user