106 lines
4.4 KiB
Python
106 lines
4.4 KiB
Python
"""Tests for Kamil ADC neutral preprocessing-set generation."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
|
|
import numpy as np
|
|
|
|
from python_app.models.run_config_model import RunConfigModel
|
|
from python_app.workflows.kamil_adc_neutral_preprocess import build_kamil_adc_neutral_s21_sets
|
|
|
|
|
|
class KamilAdcNeutralPreprocessTest(unittest.TestCase):
|
|
def test_builds_passthrough_s21_sets_for_current_sweep(self) -> None:
|
|
config = RunConfigModel.from_dict(
|
|
{
|
|
"radar": {
|
|
"model": "kamil_adc",
|
|
"sweep": {
|
|
"start_hz": 1_000_000.0,
|
|
"stop_hz": 4_000_000.0,
|
|
"if_bandwidth_hz": 1.0,
|
|
"stimulus_power_dbm": -10.0,
|
|
},
|
|
},
|
|
"switches": {
|
|
"port1": {"positions": 1},
|
|
"port2": {"positions": 2},
|
|
},
|
|
"run": {
|
|
"combos": [
|
|
{"input": 0, "output": 0},
|
|
{"input": 1, "output": 0},
|
|
],
|
|
},
|
|
}
|
|
)
|
|
|
|
calibration, reference = build_kamil_adc_neutral_s21_sets(config, point_count=4)
|
|
|
|
expected_frequency = np.linspace(1_000_000.0, 4_000_000.0, 4, dtype=np.float32)
|
|
self.assertEqual(len(calibration.traces), 2)
|
|
self.assertEqual(len(reference.traces), 2)
|
|
self.assertEqual(
|
|
[(trace.combo.input, trace.combo.output) for trace in calibration.traces],
|
|
[(0, 0), (1, 0)],
|
|
)
|
|
for trace in calibration.traces:
|
|
np.testing.assert_array_equal(trace.frequency_hz, expected_frequency)
|
|
np.testing.assert_array_equal(trace.s11, np.zeros(4, dtype=np.complex64))
|
|
np.testing.assert_array_equal(trace.s21, np.ones(4, dtype=np.complex64))
|
|
for trace in reference.traces:
|
|
np.testing.assert_array_equal(trace.frequency_hz, expected_frequency)
|
|
np.testing.assert_array_equal(trace.s11, np.zeros(4, dtype=np.complex64))
|
|
np.testing.assert_array_equal(trace.s21, np.zeros(4, dtype=np.complex64))
|
|
|
|
def test_rejects_non_kamil_config(self) -> None:
|
|
config = RunConfigModel.from_dict({"radar": {"model": "librevna"}})
|
|
|
|
with self.assertRaisesRegex(ValueError, "kamil_adc"):
|
|
build_kamil_adc_neutral_s21_sets(config, point_count=4)
|
|
|
|
@staticmethod
|
|
def _kamil_config() -> RunConfigModel:
|
|
return RunConfigModel.from_dict(
|
|
{
|
|
"radar": {
|
|
"model": "kamil_adc",
|
|
"sweep": {"start_hz": 1_000_000.0, "stop_hz": 4_000_000.0,
|
|
"if_bandwidth_hz": 1.0, "stimulus_power_dbm": -10.0},
|
|
},
|
|
"switches": {"port1": {"positions": 1}, "port2": {"positions": 2}},
|
|
"run": {"combos": [{"input": 0, "output": 0}, {"input": 1, "output": 0}]},
|
|
}
|
|
)
|
|
|
|
def test_point_count_zero_or_negative_raises(self) -> None:
|
|
config = self._kamil_config()
|
|
for bad in (0, -1):
|
|
with self.subTest(point_count=bad), self.assertRaisesRegex(ValueError, "point count"):
|
|
build_kamil_adc_neutral_s21_sets(config, point_count=bad)
|
|
|
|
def test_single_point_sweep(self) -> None:
|
|
calibration, _reference = build_kamil_adc_neutral_s21_sets(self._kamil_config(), point_count=1)
|
|
for trace in calibration.traces:
|
|
self.assertEqual(trace.frequency_hz.tolist(), [1_000_000.0])
|
|
self.assertEqual(trace.s21.shape, (1,))
|
|
|
|
def test_dtypes_are_float32_and_complex64(self) -> None:
|
|
calibration, _reference = build_kamil_adc_neutral_s21_sets(self._kamil_config(), point_count=4)
|
|
trace = calibration.traces[0]
|
|
self.assertEqual(trace.frequency_hz.dtype, np.float32)
|
|
self.assertEqual(trace.s21.dtype, np.complex64)
|
|
self.assertEqual(trace.s11.dtype, np.complex64)
|
|
|
|
def test_calibration_s21_is_a_nonzero_divisor(self) -> None:
|
|
# The C++ through-calibrator divides measured/calibration, so calibration S21
|
|
# must never be zero — that is the whole point of the '1+0j neutral' contract.
|
|
calibration, _reference = build_kamil_adc_neutral_s21_sets(self._kamil_config(), point_count=4)
|
|
for trace in calibration.traces:
|
|
self.assertTrue(bool(np.all(trace.s21 != 0)))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|