From 88a37c316bf5bdaa492c45998d951284fae0a21d Mon Sep 17 00:00:00 2001 From: Holger Mueller Date: Wed, 22 Jan 2020 17:54:34 +0100 Subject: [PATCH] Enhanced formatting for complex and low digit count --- NanoVNASaver/Chart.py | 4 +- NanoVNASaver/Formatting.py | 25 ++------- NanoVNASaver/RFTools.py | 21 +++---- NanoVNASaver/SITools.py | 4 +- test/test_formatting.py | 112 +++++++++++++++++++++++++++++++++++++ test/test_sitools.py | 24 ++++++-- test_master.py | 2 +- 7 files changed, 151 insertions(+), 41 deletions(-) create mode 100644 test/test_formatting.py diff --git a/NanoVNASaver/Chart.py b/NanoVNASaver/Chart.py index e301841..f7e6752 100644 --- a/NanoVNASaver/Chart.py +++ b/NanoVNASaver/Chart.py @@ -4096,7 +4096,7 @@ class CapacitanceChart(FrequencyChart): self.span = span target_ticks = math.floor(self.chartHeight / 60) - fmt = Format(max_nr_digits=3) + fmt = Format(max_nr_digits=1) for i in range(target_ticks): val = minValue + (i / target_ticks) * span y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight) @@ -4223,7 +4223,7 @@ class InductanceChart(FrequencyChart): self.span = span target_ticks = math.floor(self.chartHeight / 60) - fmt = Format(max_nr_digits=3) + fmt = Format(max_nr_digits=1) for i in range(target_ticks): val = minValue + (i / target_ticks) * span y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight) diff --git a/NanoVNASaver/Formatting.py b/NanoVNASaver/Formatting.py index ce33846..4523375 100644 --- a/NanoVNASaver/Formatting.py +++ b/NanoVNASaver/Formatting.py @@ -23,6 +23,8 @@ FMT_FREQ_INPUTS = SITools.Format(max_nr_digits=10, allow_strip=True, printable_m FMT_Q_FACTOR = SITools.Format(max_nr_digits=4, assume_infinity=False, min_offset=0, max_offset=0, allow_strip=True) FMT_GROUP_DELAY = SITools.Format(max_nr_digits=5, space_str=" ") FMT_REACT = SITools.Format(max_nr_digits=5, space_str=" ", allow_strip=True) +FMT_COMPLEX = SITools.Format(max_nr_digits=3, allow_strip=True, + printable_min=0, unprintable_under="- ") def format_frequency(freq: float, fmt=FMT_FREQ) -> str: @@ -68,7 +70,7 @@ def format_inductance(val: float, allow_negative: bool = True) -> str: def format_group_delay(val: float) -> str: - return str(SITools.Value(val, "s", fmt=FMT_GROUP_DELAY)) + return str(SITools.Value(val, "s", FMT_GROUP_DELAY)) def format_phase(val: float) -> str: @@ -76,21 +78,6 @@ def format_phase(val: float) -> str: def format_complex_imp(z: complex) -> str: - if z.real > 0: - if z.real >= 1000: - s = f"{z.real/1000:.3g}k" - else: - s = f"{z.real:.4g}" - else: - s = "- " - if z.imag < 0: - s += " -j" - else: - s += " +j" - if abs(z.imag) >= 1000: - s += f"{abs(z.imag)/1000:.3g}k" - elif abs(z.imag) < 0.1: - s += f"{abs(z.imag)*1000:.3g}m" - else: - s += f"{abs(z.imag):.3g}" - return s + " \N{OHM SIGN}" + re = SITools.Value(z.real, fmt=FMT_COMPLEX) + im = SITools.Value(abs(z.imag), fmt=FMT_COMPLEX) + return f"{re}{'-' if z.imag < 0 else '+'}j{im} \N{OHM SIGN}" diff --git a/NanoVNASaver/RFTools.py b/NanoVNASaver/RFTools.py index 6a27da0..b530dbd 100644 --- a/NanoVNASaver/RFTools.py +++ b/NanoVNASaver/RFTools.py @@ -38,20 +38,15 @@ def parallel_to_serial(z: complex) -> complex: 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(z_sq_sum / z.real, z_sq_sum / z.imag) - elif z.real != 0 and z_sq_sum > 0: - return complex(z_sq_sum / z.real, math.inf) - elif z.real != 0 and z_sq_sum < 0: - return complex(z_sq_sum / z.real, -math.inf) - elif z.imag != 0 and z_sq_sum > 0: - return complex(math.inf, z_sq_sum / z.real) - elif z.imag != 0 and z_sq_sum < 0: - return complex(-math.inf, z_sq_sum / z.real) - elif z_sq_sum == 0: - return complex(0, 0) - else: + 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 impedance_to_capacitance(z: complex, freq: float) -> float: diff --git a/NanoVNASaver/SITools.py b/NanoVNASaver/SITools.py index c0fef60..0744855 100644 --- a/NanoVNASaver/SITools.py +++ b/NanoVNASaver/SITools.py @@ -59,7 +59,7 @@ class Value: value: Union[Number, str] = 0, unit: str = "", fmt=Format()): - assert 3 <= fmt.max_nr_digits <= 30 + assert 1 <= fmt.max_nr_digits <= 30 assert -8 <= fmt.min_offset <= fmt.max_offset <= 8 assert fmt.parse_clamp_min < fmt.parse_clamp_max assert fmt.printable_min < fmt.printable_max @@ -91,7 +91,7 @@ class Value: real = float(self._value) / (10 ** (offset * 3)) - if fmt.max_nr_digits < 4: + if fmt.max_nr_digits < 3: formstr = ".0f" else: max_digits = fmt.max_nr_digits + ( diff --git a/test/test_formatting.py b/test/test_formatting.py new file mode 100644 index 0000000..55ddba9 --- /dev/null +++ b/test/test_formatting.py @@ -0,0 +1,112 @@ +# NanoVNASaver +# A python program to view and export Touchstone data from a NanoVNA +# Copyright (C) 2019. Rune B. Broberg +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import unittest + +# Import targets to be tested +from NanoVNASaver import Formatting as fmt + + +class TestCases(unittest.TestCase): + + def test_format_frequency(self): + self.assertEqual(fmt.format_frequency(1), '1.00000 Hz') + self.assertEqual(fmt.format_frequency(12), '12.0000 Hz') + self.assertEqual(fmt.format_frequency(123), '123.000 Hz') + self.assertEqual(fmt.format_frequency(1234), '1.23400 kHz') + self.assertEqual(fmt.format_frequency(1234567), '1.23457 MHz') + self.assertEqual(fmt.format_frequency(1234567890), '1.23457 GHz') + self.assertEqual(fmt.format_frequency(0), '0.00000 Hz') + self.assertEqual(fmt.format_frequency(-1), '-1.00000 Hz') + + def test_format_frequency_inputs(self): + self.assertEqual(fmt.format_frequency_inputs(1), '1Hz') + self.assertEqual(fmt.format_frequency_inputs(12), '12Hz') + self.assertEqual(fmt.format_frequency_inputs(123), '123Hz') + self.assertEqual(fmt.format_frequency_inputs(1234), '1.234kHz') + self.assertEqual(fmt.format_frequency_inputs(1234567), '1.234567MHz') + self.assertEqual(fmt.format_frequency_inputs(1234567890), '1.23456789GHz') + self.assertEqual(fmt.format_frequency_inputs(0), '0Hz') + self.assertEqual(fmt.format_frequency_inputs(-1), '- Hz') + + def test_format_gain(self): + self.assertEqual(fmt.format_gain(1), '1.000 dB') + self.assertEqual(fmt.format_gain(12), '12.000 dB') + self.assertEqual(fmt.format_gain(1.23456), '1.235 dB') + self.assertEqual(fmt.format_gain(-1), '-1.000 dB') + self.assertEqual(fmt.format_gain(-1, invert=True), '1.000 dB') + + def test_format_q_factor(self): + self.assertEqual(fmt.format_q_factor(1), '1') + self.assertEqual(fmt.format_q_factor(12), '12') + self.assertEqual(fmt.format_q_factor(123), '123') + self.assertEqual(fmt.format_q_factor(1234), '1234') + self.assertEqual(fmt.format_q_factor(12345), '\N{INFINITY}') + self.assertEqual(fmt.format_q_factor(-1), '\N{INFINITY}') + self.assertEqual(fmt.format_q_factor(1.2345), '1.234') + + def test_format_vswr(self): + self.assertEqual(fmt.format_vswr(1), '1.000') + self.assertEqual(fmt.format_vswr(1.234), '1.234') + self.assertEqual(fmt.format_vswr(12345.12345), '12345.123') + + def test_format_resistance(self): + self.assertEqual(fmt.format_resistance(1), '1 \N{OHM SIGN}') + self.assertEqual(fmt.format_resistance(12), '12 \N{OHM SIGN}') + self.assertEqual(fmt.format_resistance(123), '123 \N{OHM SIGN}') + self.assertEqual(fmt.format_resistance(1234), '1.234 k\N{OHM SIGN}') + self.assertEqual(fmt.format_resistance(12345), '12.345 k\N{OHM SIGN}') + self.assertEqual(fmt.format_resistance(123456), '123.46 k\N{OHM SIGN}') + self.assertEqual(fmt.format_resistance(-1), '- \N{OHM SIGN}') + + def test_format_capacitance(self): + self.assertEqual(fmt.format_capacitance(1), '1 F') + self.assertEqual(fmt.format_capacitance(1e-3), '1 mF') + self.assertEqual(fmt.format_capacitance(1e-6), '1 µF') + self.assertEqual(fmt.format_capacitance(1e-9), '1 nF') + self.assertEqual(fmt.format_capacitance(1e-12), '1 pF') + self.assertEqual(fmt.format_capacitance(-1), '-1 F') + self.assertEqual(fmt.format_capacitance(-1, False), '- pF') + + def test_format_inductance(self): + self.assertEqual(fmt.format_inductance(1), '1 H') + self.assertEqual(fmt.format_inductance(1e-3), '1 mH') + self.assertEqual(fmt.format_inductance(1e-6), '1 µH') + self.assertEqual(fmt.format_inductance(1e-9), '1 nH') + self.assertEqual(fmt.format_inductance(1e-12), '1 pH') + self.assertEqual(fmt.format_inductance(-1), '-1 H') + self.assertEqual(fmt.format_inductance(-1, False), '- nH') + + def test_format_group_delay(self): + self.assertEqual(fmt.format_group_delay(1), '1.0000 s') + self.assertEqual(fmt.format_group_delay(1e-9), '1.0000 ns') + self.assertEqual(fmt.format_group_delay(1.23456e-9), '1.2346 ns') + + def test_format_phase(self): + self.assertEqual(fmt.format_phase(0), '0.00°') + self.assertEqual(fmt.format_phase(1), '57.30°') + self.assertEqual(fmt.format_phase(-1), '-57.30°') + self.assertEqual(fmt.format_phase(3.1416), '180.00°') + self.assertEqual(fmt.format_phase(6.2831), '360.00°') + self.assertEqual(fmt.format_phase(9.4247), '540.00°') + self.assertEqual(fmt.format_phase(-3.1416), '-180.00°') + + def test_format_complex_imp(self): + self.assertEqual(fmt.format_complex_imp(complex(1, 0)), '1+j0 \N{OHM SIGN}') + self.assertEqual(fmt.format_complex_imp(complex(1234, 1234)), '1.23k+j1.23k \N{OHM SIGN}') + self.assertEqual(fmt.format_complex_imp(complex(1234, -1234)), '1.23k-j1.23k \N{OHM SIGN}') + self.assertEqual(fmt.format_complex_imp(complex(1.234, 1234)), '1.23+j1.23k \N{OHM SIGN}') + self.assertEqual(fmt.format_complex_imp(complex(-1, 1.23e-3)), '- +j1.23m \N{OHM SIGN}') diff --git a/test/test_sitools.py b/test/test_sitools.py index 38a9a3a..43c9b29 100644 --- a/test/test_sitools.py +++ b/test/test_sitools.py @@ -23,13 +23,15 @@ from math import inf F_DEFAULT = Format() -F_ASSERT_DIGITS_1 = Format(max_nr_digits=2) +F_ASSERT_DIGITS_0 = Format(max_nr_digits=0) F_ASSERT_DIGITS_2 = Format(max_nr_digits=31) F_ASSERT_OFFSET_1 = Format(min_offset=-9) F_ASSERT_OFFSET_2 = Format(max_offset=9) F_ASSERT_OFFSET_3 = Format(min_offset=9) F_ASSERT_CLAMP = Format(parse_clamp_min=10, parse_clamp_max=9) +F_DIGITS_1 = Format(max_nr_digits=1, min_offset=-2, + max_offset=2, assume_infinity=False) F_DIGITS_3 = Format(max_nr_digits=3, min_offset=-2, max_offset=2, assume_infinity=False) F_DIGITS_4 = Format(max_nr_digits=4) @@ -42,7 +44,7 @@ F_WITH_UNDERSCORE = Format(space_str="_") class TestTSIToolsValue(unittest.TestCase): def test_format_assertions(self): - self.assertRaises(AssertionError, Value, fmt=F_ASSERT_DIGITS_1) + self.assertRaises(AssertionError, Value, fmt=F_ASSERT_DIGITS_0) self.assertRaises(AssertionError, Value, fmt=F_ASSERT_DIGITS_2) self.assertRaises(AssertionError, Value, fmt=F_ASSERT_OFFSET_1) self.assertRaises(AssertionError, Value, fmt=F_ASSERT_OFFSET_2) @@ -95,8 +97,8 @@ class TestTSIToolsValue(unittest.TestCase): self.assertEqual(str(Value(1e-30)), "0.00000") self.assertEqual(float(Value(1e-30)), 1e-30) - def test_format_digits_3(self): - v = Value(fmt=F_DIGITS_3) + def test_format_digits_1(self): + v = Value(fmt=F_DIGITS_1) self.assertEqual(str(v.parse("1")), "1") self.assertEqual(str(v.parse("10")), "10") self.assertEqual(str(v.parse("100")), "100") @@ -109,6 +111,20 @@ class TestTSIToolsValue(unittest.TestCase): self.assertEqual(str(v.parse("1e-3")), "1m") self.assertEqual(str(v.parse("1e-9")), "0") + def test_format_digits_3(self): + v = Value(fmt=F_DIGITS_3) + self.assertEqual(str(v.parse("1")), "1.00") + self.assertEqual(str(v.parse("10")), "10.0") + self.assertEqual(str(v.parse("100")), "100") + self.assertEqual(str(v.parse("1e3")), "1.00k") + self.assertEqual(str(v.parse("1e4")), "10.0k") + self.assertEqual(str(v.parse("1e5")), "100k") + self.assertEqual(str(v.parse("1e9")), "1000M") + self.assertEqual(str(v.parse("1e-1")), "100m") + self.assertEqual(str(v.parse("1e-2")), "10.0m") + self.assertEqual(str(v.parse("1e-3")), "1.00m") + self.assertEqual(str(v.parse("1e-9")), "0.00") + def test_format_digits_4(self): v = Value(fmt=F_DIGITS_4) self.assertEqual(str(v.parse("1")), "1.000") diff --git a/test_master.py b/test_master.py index 3749f87..b071326 100644 --- a/test_master.py +++ b/test_master.py @@ -31,7 +31,7 @@ import unittest if __name__ == '__main__': sys.path.append('.') loader = unittest.TestLoader() - tests = loader.discover('.') + tests = loader.discover('./test') testRunner = unittest.runner.TextTestRunner( failfast=False, verbosity=2)