"""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()