diff --git a/.coveragerc b/.coveragerc index 9228c0b..06411c9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,7 @@ [run] # ignore GUI code atm. omit = + NanoVNASaver/About.py NanoVNASaver/Analysis/*.py NanoVNASaver/Calibration.py NanoVNASaver/Charts/*.py diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index 1f9e016..6228fdb 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -36,7 +36,7 @@ from .Formatting import ( parse_frequency, ) from .Hardware import get_interfaces, get_VNA, InvalidVNA -from .RFTools import Datapoint, corrAttData +from .RFTools import Datapoint, corr_att_data from .Charts.Chart import Chart from .Charts import ( CapacitanceChart, @@ -696,8 +696,7 @@ class NanoVNASaver(QtWidgets.QWidget): if self.dataLock.acquire(blocking=True): self.data = data if self.s21att > 0: - corData12 = corrAttData(data12, self.s21att) - self.data21 = corData12 + self.data21 = corr_att_data(data12, self.s21att) else: self.data21 = data12 else: diff --git a/NanoVNASaver/RFTools.py b/NanoVNASaver/RFTools.py index 56cca42..e23e8a5 100644 --- a/NanoVNASaver/RFTools.py +++ b/NanoVNASaver/RFTools.py @@ -26,26 +26,43 @@ FMT_SHORT = Format(max_nr_digits=4) FMT_SWEEP = Format(max_nr_digits=9, allow_strip=True) -def parallel_to_serial(z: complex) -> complex: - """Convert parallel impedance to serial impedance equivalent""" - z_sq_sum = z.real ** 2 + z.imag ** 2 - # TODO: Fix divide by zero - return complex(z.real * z.imag ** 2 / z_sq_sum, - z.real ** 2 * z.imag / z_sq_sum) +def corr_att_data(data: List[Datapoint], att: float): + """Correct the ratio for a given attenuation on s21 input""" + if att <= 0: + return data + else: + att = 10**(att/20) + ndata = [] + for dp in data: + freq, re, im = dp + orig = complex(re, im) + corrected = orig * att + ndata.append(Datapoint(freq, corrected.real, corrected.imag)) + return ndata -def serial_to_parallel(z: complex) -> complex: - """Convert serial impedance to parallel impedance equivalent""" - z_sq_sum = z.real ** 2 + z.imag ** 2 - if z.real == 0 and z.imag == 0: - return complex(math.inf, math.inf) - if z_sq_sum == 0: - return complex(0, 0) - if z.imag == 0: - return complex(z_sq_sum / z.real, math.copysign(math.inf, z_sq_sum)) - if z.real == 0: - return complex(math.copysign(math.inf, z_sq_sum), z_sq_sum / z.real) - return complex(z_sq_sum / z.real, z_sq_sum / z.imag) +def gamma_to_impedance(gamma: complex, ref_impedance: float = 50) -> complex: + """Calculate impedance from gamma""" + try: + return ((-gamma - 1) / (gamma - 1)) * ref_impedance + except ZeroDivisionError: + return math.inf + + +def groupDelay(data: List[Datapoint], index: int) -> float: + idx0 = clamp_value(index - 1, 0, len(data) - 1) + idx1 = clamp_value(index + 1, 0, len(data) - 1) + delta_angle = data[idx1].phase - data[idx0].phase + delta_freq = data[idx1].freq - data[idx0].freq + if delta_freq == 0: + return 0 + if abs(delta_angle) > math.tau: + if delta_angle > 0: + delta_angle = delta_angle % math.tau + else: + delta_angle = -1 * (delta_angle % math.tau) + val = -delta_angle / math.tau / delta_freq + return val def impedance_to_capacitance(z: complex, freq: float) -> float: @@ -74,17 +91,32 @@ def norm_to_impedance(z: complex, ref_impedance: float = 50) -> complex: return z * ref_impedance +def parallel_to_serial(z: complex) -> complex: + """Convert parallel impedance to serial impedance equivalent""" + z_sq_sum = z.real ** 2 + z.imag ** 2 + # TODO: Fix divide by zero + return complex(z.real * z.imag ** 2 / z_sq_sum, + z.real ** 2 * z.imag / z_sq_sum) + + def reflection_coefficient(z: complex, ref_impedance: float = 50) -> complex: """Calculate reflection coefficient for z""" return (z - ref_impedance) / (z + ref_impedance) -def gamma_to_impedance(gamma: complex, ref_impedance: float = 50) -> complex: - """Calculate impedance from gamma""" - try: - return ((-gamma - 1) / (gamma - 1)) * ref_impedance - except ZeroDivisionError: - return math.inf +def serial_to_parallel(z: complex) -> complex: + """Convert serial impedance to parallel impedance equivalent""" + z_sq_sum = z.real ** 2 + z.imag ** 2 + if z.real == 0 and z.imag == 0: + return complex(math.inf, math.inf) + # only possible if real and imag == 0, therefor commented out + # if z_sq_sum == 0: + # return complex(0, 0) + if z.imag == 0: + return complex(z_sq_sum / z.real, math.copysign(math.inf, z_sq_sum)) + if z.real == 0: + return complex(math.copysign(math.inf, z_sq_sum), z_sq_sum / z.imag) + return complex(z_sq_sum / z.real, z_sq_sum / z.imag) class Datapoint(NamedTuple): @@ -131,36 +163,3 @@ class Datapoint(NamedTuple): def inductiveEquivalent(self, ref_impedance: float = 50) -> float: return impedance_to_inductance(self.impedance(ref_impedance), self.freq) - -def groupDelay(data: List[Datapoint], index: int) -> float: - idx0 = clamp_value(index - 1, 0, len(data) - 1) - idx1 = clamp_value(index + 1, 0, len(data) - 1) - delta_angle = data[idx1].phase - data[idx0].phase - delta_freq = data[idx1].freq - data[idx0].freq - if delta_freq == 0: - return 0 - if abs(delta_angle) > math.tau: - if delta_angle > 0: - delta_angle = delta_angle % math.tau - else: - delta_angle = -1 * (delta_angle % math.tau) - val = -delta_angle / math.tau / delta_freq - return val - - -def corrAttData(data: Datapoint, att: float): - """Correct the ratio for a given attenuation on s21 input""" - - if att <= 0: - return data - else: - att = 10**(att/20) - - ndata = [] - for i in range(len(data)): - freq, re, im = data[i] - orig = complex(re, im) - corrected = orig * att - ndata.append(Datapoint(freq, corrected.real, corrected.imag)) - - return ndata diff --git a/test/test_rftools.py b/test/test_rftools.py index 3df7ead..7fc243a 100644 --- a/test/test_rftools.py +++ b/test/test_rftools.py @@ -81,6 +81,15 @@ class TestRFTools(unittest.TestCase): complex(50, 10)) def test_serial_to_parallel(self): + self.assertEqual( + serial_to_parallel(complex(0, 0)), + complex(math.inf, math.inf)) + self.assertEqual( + serial_to_parallel(complex(50, 0)), + complex(50, math.inf)) + self.assertEqual( + serial_to_parallel(complex(0, 50)), + complex(math.inf, 50)) self.assertAlmostEqual( serial_to_parallel(complex(50, 10)), complex(52, 260)) @@ -105,9 +114,9 @@ class TestRFTools(unittest.TestCase): Datapoint(100002, 0.1091, 0.3130), ] dpoints0 = [ - Datapoint(100000, 0.1091, 0.3118), Datapoint(100000, 0.1091, 0.3124), - Datapoint(100000, 0.1091, 0.3130), + Datapoint(100000, 0.1091, 0.3124), + Datapoint(100000, 0.1091, 0.3124), ] self.assertAlmostEqual(groupDelay(dpoints, 1), -9.514e-5) self.assertEqual(groupDelay(dpoints0, 1), 0.0) @@ -120,6 +129,7 @@ class TestRFToolsDatapoint(unittest.TestCase): self.dp0 = Datapoint(100000, 0, 0) self.dp50 = Datapoint(100000, 1, 0) self.dp75 = Datapoint(100000, 0.2, 0) + self.dp_im50 = Datapoint(100000, 0, 1) def test_properties(self): self.assertEqual(self.dp.z, complex(0.1091, 0.3118)) @@ -134,6 +144,7 @@ class TestRFToolsDatapoint(unittest.TestCase): complex(74.99628755, 52.49617517)) self.assertEqual(self.dp0.qFactor(), 0.0) self.assertEqual(self.dp75.qFactor(), 0.0) + self.assertEqual(self.dp_im50.qFactor(), -1.0) self.assertAlmostEqual(self.dp.qFactor(), 0.6999837) self.assertAlmostEqual(self.dp.capacitiveEquivalent(), -4.54761539e-08) self.assertAlmostEqual(self.dp.inductiveEquivalent(), 5.57001e-05)