Skip to content
Snippets Groups Projects
Commit cb942282 authored by Luis Dekant's avatar Luis Dekant
Browse files

Merge branch '78-filter-attribute-mixup' into 'develop'

Resolve "filter attribute mixup"

Closes #78

See merge request las-nq/openqlab!41
parents c4551c46 f4962e9b
No related branches found
No related tags found
No related merge requests found
...@@ -191,9 +191,9 @@ class Integrator(Filter): ...@@ -191,9 +191,9 @@ class Integrator(Filter):
def calculate(self): def calculate(self):
z = -self.corner_frequency z = -self.corner_frequency
if self.sF is None: if self.second_parameter is None:
self._second_parameter = self.corner_frequency * 0.001 self.second_parameter = self.corner_frequency * 0.001
p = -self.sF p = -self.second_parameter
k = 1.0 # Gain = 1 k = 1.0 # Gain = 1
return z, p, k return z, p, k
...@@ -226,10 +226,10 @@ class Differentiator(Filter): ...@@ -226,10 +226,10 @@ class Differentiator(Filter):
def calculate(self): def calculate(self):
z = -self.corner_frequency z = -self.corner_frequency
if self.sF is None: if self.second_parameter is None:
self._second_parameter = self.corner_frequency * 10 self.second_parameter = self.corner_frequency * 10
p = -self.sF p = -self.second_parameter
k = self.sF / self.corner_frequency k = self.second_parameter / self.corner_frequency
return z, p, k return z, p, k
@property @property
...@@ -247,9 +247,9 @@ class Differentiator(Filter): ...@@ -247,9 +247,9 @@ class Differentiator(Filter):
class Lowpass(Filter): class Lowpass(Filter):
""" """
Create a 2nd-order lowpass filter with variable quality factor `Q`. Create a 2nd-order lowpass filter with variable quality factor `second_parameter` (might be referenced as `Q` in the description).
The default `Q` of ``1/sqrt(2)`` results in a Butterworth filter with flat passband. The default `second_parameter` of ``1/sqrt(2)`` results in a Butterworth filter with flat passband.
Parameters Parameters
---------- ----------
...@@ -258,18 +258,20 @@ class Lowpass(Filter): ...@@ -258,18 +258,20 @@ class Lowpass(Filter):
""" """
def __init__(self, corner_frequency, Q=0.707, enabled=True): def __init__(self, corner_frequency, second_parameter=0.707, enabled=True):
super().__init__(corner_frequency, Q, enabled) super().__init__(corner_frequency, second_parameter, enabled)
def calculate(self): def calculate(self):
z = [] z = []
corner_frequency = self.corner_frequency corner_frequency = self.corner_frequency
Q = self.Q second_parameter = self.second_parameter
p = [ p = [
-corner_frequency / (2 * Q) -corner_frequency / (2 * second_parameter)
+ ((corner_frequency / (2 * Q)) ** 2 - corner_frequency ** 2) ** 0.5, + ((corner_frequency / (2 * second_parameter)) ** 2 - corner_frequency ** 2)
-corner_frequency / (2 * Q) ** 0.5,
- ((corner_frequency / (2 * Q)) ** 2 - corner_frequency ** 2) ** 0.5, -corner_frequency / (2 * second_parameter)
- ((corner_frequency / (2 * second_parameter)) ** 2 - corner_frequency ** 2)
** 0.5,
] ]
k = corner_frequency ** 2 k = corner_frequency ** 2
...@@ -278,7 +280,7 @@ class Lowpass(Filter): ...@@ -278,7 +280,7 @@ class Lowpass(Filter):
@property @property
def description(self): def description(self):
return "LP2 {0}, Q={1:.4g}".format( return "LP2 {0}, Q={1:.4g}".format(
human_readable(self.corner_frequency, "Hz"), self.Q human_readable(self.corner_frequency, "Hz"), self.second_parameter
) )
@property @property
...@@ -293,29 +295,31 @@ class Lowpass(Filter): ...@@ -293,29 +295,31 @@ class Lowpass(Filter):
class Notch(Filter): class Notch(Filter):
""" """
Create a notch filter at frequency `corner_frequency` with a quality Create a notch filter at frequency `corner_frequency` with a quality
factor `Q`, where the -3dB filter bandwidth ``bw`` is factor `second_parameter`, where the -3dB filter bandwidth ``bw`` is
given by ``Q = corner_frequency/bw``. given by ``second_parameter = corner_frequency/bw``.
Parameters Parameters
---------- ----------
corner_frequency: :obj:`float` corner_frequency: :obj:`float`
Frequency to remove from the spectrum Frequency to remove from the spectrum
Q: :obj:`float` second_parameter: :obj:`float`
Quality factor of the notch filter. Defaults to 1. Quality factor of the notch filter. Defaults to 1. Referenced as `Q` in description.
""" """
def __init__(self, corner_frequency, Q=1, enabled=True): def __init__(self, corner_frequency, second_parameter=1, enabled=True):
super().__init__(corner_frequency, Q, enabled) super().__init__(corner_frequency, second_parameter, enabled)
def calculate(self): def calculate(self):
corner_frequency = self.corner_frequency corner_frequency = self.corner_frequency
Q = self.Q second_parameter = self.second_parameter
z = [corner_frequency * 1j, -corner_frequency * 1j] z = [corner_frequency * 1j, -corner_frequency * 1j]
p = [ p = [
-corner_frequency / (2 * Q) -corner_frequency / (2 * second_parameter)
+ ((corner_frequency / (2 * Q)) ** 2 - corner_frequency ** 2) ** 0.5, + ((corner_frequency / (2 * second_parameter)) ** 2 - corner_frequency ** 2)
-corner_frequency / (2 * Q) ** 0.5,
- ((corner_frequency / (2 * Q)) ** 2 - corner_frequency ** 2) ** 0.5, -corner_frequency / (2 * second_parameter)
- ((corner_frequency / (2 * second_parameter)) ** 2 - corner_frequency ** 2)
** 0.5,
] ]
k = 1 k = 1
return z, p, k return z, p, k
...@@ -323,7 +327,7 @@ class Notch(Filter): ...@@ -323,7 +327,7 @@ class Notch(Filter):
@property @property
def description(self): def description(self):
return "Notch {0}, Q={1:.4g}".format( return "Notch {0}, Q={1:.4g}".format(
human_readable(self.corner_frequency, "Hz"), self.Q human_readable(self.corner_frequency, "Hz"), self.second_parameter
) )
@property @property
...@@ -528,30 +532,30 @@ class ServoDesign: ...@@ -528,30 +532,30 @@ class ServoDesign:
""" """
self.add(Differentiator(corner_frequency, fstop, enabled)) self.add(Differentiator(corner_frequency, fstop, enabled))
def lowpass(self, corner_frequency, Q=0.707, enabled=True): def lowpass(self, corner_frequency, second_parameter=0.707, enabled=True):
""" """
Add a 2nd-order lowpass filter with variable quality factor `Q`. Add a 2nd-order lowpass filter with variable quality factor `second_parameter`.
The default `Q` of ``1/sqrt(2)`` results in a Butterworth filter with flat passband. The default `second_parameter` of ``1/sqrt(2)`` results in a Butterworth filter with flat passband.
Parameters Parameters
---------- ----------
parameter: :obj:`type` parameter: :obj:`type`
parameter description parameter description
""" """
self.add(Lowpass(corner_frequency, Q, enabled)) self.add(Lowpass(corner_frequency, second_parameter, enabled))
def notch(self, corner_frequency, Q=1, enabled=True): def notch(self, corner_frequency, second_parameter=1, enabled=True):
""" """
Add a notch filter at frequency `corner_frequency` with a Add a notch filter at frequency `corner_frequency` with a
quality factor `Q`, where the -3dB filter bandwidth ``bw`` quality factor `second_parameter`, where the -3dB filter bandwidth ``bw``
is given by ``Q = corner_frequency/bw``. is given by ``second_parameter = corner_frequency/bw``.
Parameters Parameters
---------- ----------
corner_frequency: :obj:`float` corner_frequency: :obj:`float`
Frequency to remove from the spectrum Frequency to remove from the spectrum
Q: :obj:`float` second_parameter: :obj:`float`
Quality factor of the notch filter Quality factor of the notch filter
Returns Returns
...@@ -559,7 +563,7 @@ class ServoDesign: ...@@ -559,7 +563,7 @@ class ServoDesign:
:obj:`Servo` :obj:`Servo`
the servo object with added notch filter the servo object with added notch filter
""" """
self.add(Notch(corner_frequency, Q, enabled)) self.add(Notch(corner_frequency, second_parameter, enabled))
#################################### ####################################
# CLASS UTILITY # CLASS UTILITY
......
...@@ -23,33 +23,33 @@ filedir = Path(__file__).parent ...@@ -23,33 +23,33 @@ filedir = Path(__file__).parent
class TestFilter(unittest.TestCase): class TestFilter(unittest.TestCase):
def test_integrator(self): def test_integrator(self):
corner_frequency = 840 corner_frequency = 840
sF = 5300 second_parameter = 5300
i = Integrator(corner_frequency, sF) i = Integrator(corner_frequency, second_parameter)
np.testing.assert_allclose(i.zeros, -corner_frequency) np.testing.assert_allclose(i.zeros, -corner_frequency)
np.testing.assert_allclose(i.poles, -sF) np.testing.assert_allclose(i.poles, -second_parameter)
np.testing.assert_allclose(i.gain, 1) np.testing.assert_allclose(i.gain, 1)
i.sF = corner_frequency * 0.001 i.second_parameter = corner_frequency * 0.001
np.testing.assert_allclose(i.zeros, -corner_frequency) np.testing.assert_allclose(i.zeros, -corner_frequency)
np.testing.assert_allclose(i.poles, -corner_frequency / 1000) np.testing.assert_allclose(i.poles, -corner_frequency / 1000)
def test_differentiator(self): def test_differentiator(self):
corner_frequency = 840 corner_frequency = 840
sF = 5300 second_parameter = 5300
i = Differentiator(corner_frequency, sF) i = Differentiator(corner_frequency, second_parameter)
np.testing.assert_allclose(i.zeros, -corner_frequency) np.testing.assert_allclose(i.zeros, -corner_frequency)
np.testing.assert_allclose(i.poles, -sF) np.testing.assert_allclose(i.poles, -second_parameter)
np.testing.assert_allclose(i.gain, 6.309524) np.testing.assert_allclose(i.gain, 6.309524)
i.sF = None i.second_parameter = None
np.testing.assert_allclose(i.zeros, -corner_frequency) np.testing.assert_allclose(i.zeros, -corner_frequency)
np.testing.assert_allclose(i.poles, -corner_frequency * 10) np.testing.assert_allclose(i.poles, -corner_frequency * 10)
np.testing.assert_allclose(i.gain, 10) np.testing.assert_allclose(i.gain, 10)
def test_lowpass(self): def test_lowpass(self):
corner_frequency = 840 corner_frequency = 840
Q = 3 second_parameter = 3
i1 = Lowpass(corner_frequency, Q) i1 = Lowpass(corner_frequency, second_parameter)
np.testing.assert_allclose(i1.zeros, -corner_frequency) np.testing.assert_allclose(i1.zeros, -corner_frequency)
np.testing.assert_allclose( np.testing.assert_allclose(
i1.poles, [-140.0 + 828.25116963j, -140.0 - 828.25116963j] i1.poles, [-140.0 + 828.25116963j, -140.0 - 828.25116963j]
...@@ -65,16 +65,16 @@ class TestFilter(unittest.TestCase): ...@@ -65,16 +65,16 @@ class TestFilter(unittest.TestCase):
def test_notch(self): def test_notch(self):
corner_frequency = 840 corner_frequency = 840
Q = 1 second_parameter = 1
i1 = Notch(corner_frequency, Q) i1 = Notch(corner_frequency, second_parameter)
np.testing.assert_allclose(i1.zeros, [0.0 + 840.0j, -0.0 - 840.0j]) np.testing.assert_allclose(i1.zeros, [0.0 + 840.0j, -0.0 - 840.0j])
np.testing.assert_allclose( np.testing.assert_allclose(
i1.poles, [-420.0 + 727.46133918j, -420.0 - 727.46133918j] i1.poles, [-420.0 + 727.46133918j, -420.0 - 727.46133918j]
) )
np.testing.assert_allclose(i1.gain, 1) np.testing.assert_allclose(i1.gain, 1)
Q = 32 second_parameter = 32
i2 = Notch(corner_frequency, Q) i2 = Notch(corner_frequency, second_parameter)
np.testing.assert_allclose(i2.zeros, [0.0 + 840.0j, -0.0 - 840.0j]) np.testing.assert_allclose(i2.zeros, [0.0 + 840.0j, -0.0 - 840.0j])
np.testing.assert_allclose( np.testing.assert_allclose(
i2.poles, [-13.125 + 839.89745468j, -13.125 - 839.89745468j] i2.poles, [-13.125 + 839.89745468j, -13.125 - 839.89745468j]
...@@ -217,7 +217,7 @@ class TestServoDesign(unittest.TestCase): ...@@ -217,7 +217,7 @@ class TestServoDesign(unittest.TestCase):
self.sd.integrator(500) self.sd.integrator(500)
self.sd.differentiator(1000, 1000 * 1000) self.sd.differentiator(1000, 1000 * 1000)
self.sd.notch(900, Q=200) self.sd.notch(900, second_parameter=200)
zpk_expected = ( zpk_expected = (
np.array([-500.0, -1000.0, 0.0 + 900.0j, -0.0 - 900.0j]), np.array([-500.0, -1000.0, 0.0 + 900.0j, -0.0 - 900.0j]),
np.array([-0.5, -1e6, -2.25e00 + 899.9971875j, -2.25e00 - 899.9971875j]), np.array([-0.5, -1e6, -2.25e00 + 899.9971875j, -2.25e00 - 899.9971875j]),
...@@ -279,7 +279,7 @@ class TestServoDesign(unittest.TestCase): ...@@ -279,7 +279,7 @@ class TestServoDesign(unittest.TestCase):
self.assertGreaterEqual(ax.get_xlim()[1], 5e3) self.assertGreaterEqual(ax.get_xlim()[1], 5e3)
def test_get_dataframe(self): def test_get_dataframe(self):
self.sd.notch(1e3, Q=3) self.sd.notch(1e3, second_parameter=3)
df = self.sd.plot(plot=False) df = self.sd.plot(plot=False)
self.assertIsInstance(df, DataFrame) self.assertIsInstance(df, DataFrame)
columns = ["Servo A", "Servo P"] columns = ["Servo A", "Servo P"]
...@@ -305,18 +305,18 @@ class TestServoDesign(unittest.TestCase): ...@@ -305,18 +305,18 @@ class TestServoDesign(unittest.TestCase):
self.sd.lowpass(438) self.sd.lowpass(438)
self.assertEqual(self.sd.filters[2].description, "LP2 438 Hz, Q=0.707") self.assertEqual(self.sd.filters[2].description, "LP2 438 Hz, Q=0.707")
self.sd.filters[2].corner_frequency = 1200 self.sd.filters[2].corner_frequency = 1200
self.sd.filters[2].Q = 12 self.sd.filters[2].second_parameter = 12
self.assertEqual(self.sd.filters[2].description, "LP2 1.2 kHz, Q=12") self.assertEqual(self.sd.filters[2].description, "LP2 1.2 kHz, Q=12")
self.sd.notch(438, Q=11) self.sd.notch(438, second_parameter=11)
self.assertEqual(self.sd.filters[3].description, "Notch 438 Hz, Q=11") self.assertEqual(self.sd.filters[3].description, "Notch 438 Hz, Q=11")
self.sd.filters[3].corner_frequency = 1200 self.sd.filters[3].corner_frequency = 1200
self.sd.filters[3].Q = 1.3 self.sd.filters[3].second_parameter = 1.3
self.assertEqual(self.sd.filters[3].description, "Notch 1.2 kHz, Q=1.3") self.assertEqual(self.sd.filters[3].description, "Notch 1.2 kHz, Q=1.3")
def test_discrete_form(self): def test_discrete_form(self):
self.sd.integrator(500) self.sd.integrator(500)
self.sd.notch(900, Q=200) self.sd.notch(900, second_parameter=200)
discrete_orig = { discrete_orig = {
"sampling_frequency": 100000.0, "sampling_frequency": 100000.0,
"gain": 1.0, "gain": 1.0,
...@@ -370,8 +370,8 @@ class TestServoDesign(unittest.TestCase): ...@@ -370,8 +370,8 @@ class TestServoDesign(unittest.TestCase):
self.sd.integrator(500) self.sd.integrator(500)
self.sd.differentiator(1000, 1000 * 1000) self.sd.differentiator(1000, 1000 * 1000)
self.sd.notch(900, Q=200) self.sd.notch(900, second_parameter=200)
self.sd.notch(1400, Q=200, enabled=False) self.sd.notch(1400, second_parameter=200, enabled=False)
self.sd.differentiator(3000, enabled=False) self.sd.differentiator(3000, enabled=False)
zpk_expected = ( zpk_expected = (
np.array([-500.0, -1000.0, 0.0 + 900.0j, -0.0 - 900.0j]), np.array([-500.0, -1000.0, 0.0 + 900.0j, -0.0 - 900.0j]),
...@@ -384,7 +384,7 @@ class TestServoDesign(unittest.TestCase): ...@@ -384,7 +384,7 @@ class TestServoDesign(unittest.TestCase):
def test_discrete_form_with_disabled_filter(self): def test_discrete_form_with_disabled_filter(self):
self.sd.integrator(500) self.sd.integrator(500)
self.sd.notch(900, Q=200) self.sd.notch(900, second_parameter=200)
self.sd.integrator(500, enabled=False) self.sd.integrator(500, enabled=False)
self.sd.lowpass(5000, enabled=False) self.sd.lowpass(5000, enabled=False)
discrete_orig = { discrete_orig = {
...@@ -445,7 +445,7 @@ class TestServoDesign(unittest.TestCase): ...@@ -445,7 +445,7 @@ class TestServoDesign(unittest.TestCase):
np.testing.assert_allclose(f1["sos"], f2["sos"]) np.testing.assert_allclose(f1["sos"], f2["sos"])
def test_jsonpickle(self): def test_jsonpickle(self):
self.sd.notch(900, Q=200) self.sd.notch(900, second_parameter=200)
self.sd.integrator(500) self.sd.integrator(500)
self.sd.lowpass(5000) self.sd.lowpass(5000)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment