some fixes

This commit is contained in:
Ayzen
2026-06-22 15:01:26 +03:00
parent e7f2d25585
commit 7c381facaf
4 changed files with 261 additions and 30 deletions
+52 -1
View File
@@ -225,7 +225,15 @@ class CycleAlignmentToleranceCalibrationTest(unittest.TestCase):
class LastCycleSParameterTest(unittest.TestCase):
"""Reference/measurement division + the corrupt-reference and NaN guards."""
"""Reference/measurement division, gap zero-filling, and the over-loss guard.
A handful of dropped USB frames (points that never arrived, left as ``NaN``)
are emitted as zero so the cycle is kept instead of stalling on recovery, while
delivered points keep their true normalized value. Losing more than
``_MAX_ZERO_FILLED_POINTS_FRACTION`` of a trace is treated as a genuine fault
and rejected as transient. The tiny-trace cases below cross that fraction with a
single missing point, so they still raise.
"""
def test_divides_measurement_by_reference(self) -> None:
reference = {1: np.array([[2 + 0j, 0 + 2j, 4 + 0j]], dtype=complex)}
@@ -233,6 +241,49 @@ class LastCycleSParameterTest(unittest.TestCase):
result = calculate_last_cycle_s_parameters(measurement, reference)
np.testing.assert_allclose(result["s31"], np.array([2 + 0j, 2 + 0j, 0.5 + 0j]))
def test_single_dropped_measurement_is_zero_filled(self) -> None:
# One missing point in a realistically sized trace (< the loss fraction) is
# emitted as zero; every delivered point keeps its real value, no extras.
point_count = 200
reference = {1: np.full((1, point_count), 2 + 0j, dtype=complex)}
measurement = {"S31": np.full((1, point_count), 4 + 0j, dtype=complex)}
measurement["S31"][0, 100] = np.nan + 1j * np.nan
result = calculate_last_cycle_s_parameters(measurement, reference)
self.assertEqual(result["s31"][100], 0)
np.testing.assert_allclose(np.delete(result["s31"], 100), 2 + 0j)
def test_single_dropped_reference_is_zero_filled(self) -> None:
# A missing master reference cannot normalize that point for any trace, so
# the point is zeroed; the rest of the trace is untouched.
point_count = 200
reference = {1: np.full((1, point_count), 2 + 0j, dtype=complex)}
reference[1][0, 50] = np.nan + 1j * np.nan
measurement = {"S31": np.full((1, point_count), 4 + 0j, dtype=complex)}
result = calculate_last_cycle_s_parameters(measurement, reference)
self.assertEqual(result["s31"][50], 0)
np.testing.assert_allclose(np.delete(result["s31"], 50), 2 + 0j)
def test_single_near_zero_reference_is_zero_filled(self) -> None:
# A ~zero incident reference would explode the division into noise; one such
# point is zeroed rather than aborting the whole cycle, and stays finite.
point_count = 200
reference = {1: np.full((1, point_count), 2 + 0j, dtype=complex)}
reference[1][0, 75] = 0 + 0j
measurement = {"S31": np.full((1, point_count), 4 + 0j, dtype=complex)}
result = calculate_last_cycle_s_parameters(measurement, reference)
self.assertEqual(result["s31"][75], 0)
self.assertTrue(np.isfinite(result["s31"]).all())
def test_excessive_loss_is_transient(self) -> None:
# Losing more than the tolerated fraction is a real fault, not a stray
# dropped frame: reject as transient so the caller recovers properly.
point_count = 200
reference = {1: np.full((1, point_count), 2 + 0j, dtype=complex)}
measurement = {"S31": np.full((1, point_count), 4 + 0j, dtype=complex)}
measurement["S31"][0, :40] = np.nan + 1j * np.nan # 20% > 5% tolerance
with self.assertRaises(TransientCollectionError):
calculate_last_cycle_s_parameters(measurement, reference)
def test_nan_reference_is_transient(self) -> None:
reference = {1: np.array([[2 + 0j, np.nan + 1j * np.nan, 4 + 0j]], dtype=complex)}
measurement = {"S31": np.array([[4 + 0j, 4 + 0j, 2 + 0j]], dtype=complex)}