"""A selection of standard filters expressed as analog systems."""
import scipy.signal
import logging
from .analog_system import AnalogSystem
from .topology import zpk2abcd
logger = logging.getLogger(__name__)
[docs]class ButterWorth(AnalogSystem):
"""A Butterworth filter analog system
This class inherits from :py:class:`cbadc.analog_system.AnalogSystem` and creates a convenient
way of creating Butterworth filter analog system representation.
Specifically, we specify the filter by the differential equations
:math:`\dot{\mathbf{x}}(t) = \mathbf{A} \mathbf{x}(t) + \mathbf{B} u(t) + \mathbf{\Gamma} \mathbf{s}(t)`
:math:`\mathbf{y}(t) = \mathbf{C}^\mathsf{T} \mathbf{x}(t) + \mathbf{D} u(t)`
:math:`\\tilde{\mathbf{s}}(t) = \\tilde{\mathbf{\Gamma}}^\mathsf{T} \mathbf{x}(t)`
where
internally :math:`\mathbf{A}` :math:`\mathbf{B}`, :math:`\mathbf{C}^\mathsf{T}`, and :math:`\mathbf{D}`
are determined using the :py:func:`scipy.signal.iirfilter`.
Furthermore, as this system is intended as a pure filter and therefore have no
:math:`\mathbf{\Gamma}` and :math:`\\tilde{\mathbf{\Gamma}}^\mathsf{T}` specified.
Parameters
----------
N: `int`
filter order
Wn: `float`
array containing the critical frequency of the filter.
The frequency is specified in rad/s.
Attributes
----------
N : `int`
state space order :math:`N`.
N_tilde : `int`
number of signal observations :math:`\\tilde{N}`.
M : `int`
number of digital control signals :math:`M`.
M_tilde : `int`
number of control signal observations :math:`\\tilde{M}`.
L : `int`
number of input signals :math:`L`.
A : `array_like`, shape=(N, N)
system matrix :math:`\mathbf{A}`.
B : `array_like`, shape=(N, L)
input matrix :math:`\mathbf{B}`.
CT : `array_like`, shape=(N_tilde, N)
signal observation matrix :math:`\mathbf{C}^\mathsf{T}`.
D: `array_like`, shape=(N_tilde, L)
direct matrix
Gamma : None
control input matrix :math:`\mathbf{\Gamma}`.
Gamma_tildeT : None
control observation matrix :math:`\\tilde{\mathbf{\Gamma}}^\mathsf{T}`.
Wn : float
critical frequency of filter.
See also
--------
:py:class:`cbadc.analog_system.ChebyshevI`
:py:class:`cbadc.analog_system.ChebyshevII`
:py:class:`cbadc.analog_system.Cauer`
Raises
------
:py:class:`InvalidAnalogSystemError`
For faulty analog system parametrization.
"""
def __init__(self, N: int, Wn: float):
"""Create a Butterworth filter"""
# State space order
self.Wn = Wn
# Create filter as chain of biquadratic filters
z, p, k = scipy.signal.iirfilter(
N, Wn, analog=True, btype="lowpass", ftype="butter", output="zpk"
)
A, B, CT, D = zpk2abcd(z, p, k)
AnalogSystem.__init__(self, A, B, CT, None, None, D)
[docs]class ChebyshevI(AnalogSystem):
"""A Chebyshev type I filter's analog system
This class inherits from :py:class:`cbadc.analog_system.AnalogSystem` and is a convenient
way of creating Chebyshev type I filter's analog system representation.
Specifically, we specify the filter by the differential equations
:math:`\dot{\mathbf{x}}(t) = \mathbf{A} \mathbf{x}(t) + \mathbf{B} u(t) + \mathbf{\Gamma} \mathbf{s}(t)`
:math:`\mathbf{y}(t) = \mathbf{C}^\mathsf{T} \mathbf{x}(t) + \mathbf{D} u(t)`
:math:`\\tilde{\mathbf{s}}(t) = \\tilde{\mathbf{\Gamma}}^\mathsf{T} \mathbf{x}(t)`
where
internally :math:`\mathbf{A}` :math:`\mathbf{B}`, :math:`\mathbf{C}^\mathsf{T}`, and :math:`\mathbf{D}`
are determined using the :py:func:`scipy.signal.iirfilter`.
Furthermore, as this system is intended as a pure filter and therefore have no
:math:`\mathbf{\Gamma}` and :math:`\\tilde{\mathbf{\Gamma}}^\mathsf{T}` specified.
Parameters
----------
N: `int`
filter order
Wn: (float, float)
array containing the critical frequencies (low, high) of the filter.
The frequencies are specified in rad/s.
rp: float
maximum ripple in passband. Specified in dB.
Attributes
----------
N : `int`
state space order :math:`N`.
N_tilde : `int`
number of signal observations :math:`\\tilde{N}`.
M : `int`
number of digital control signals :math:`M`.
M_tilde : `int`
number of control signal observations :math:`\\tilde{M}`.
L : `int`
number of input signals :math:`L`.
A : `array_like`, shape=(N, N)
system matrix :math:`\mathbf{A}`.
B : `array_like`, shape=(N, L)
input matrix :math:`\mathbf{B}`.
CT : `array_like`, shape=(N_tilde, N)
signal observation matrix :math:`\mathbf{C}^\mathsf{T}`.
D: `array_like`, shape=(N_tilde, L)
direct matrix
Gamma : None
control input matrix :math:`\mathbf{\Gamma}`.
Gamma_tildeT : None
control observation matrix :math:`\\tilde{\mathbf{\Gamma}}^\mathsf{T}`.
Wn : float
critical frequency of filter.
See also
--------
:py:class:`cbadc.analog_system.ButterWorth`
:py:class:`cbadc.analog_system.ChebyshevII`
:py:class:`cbadc.analog_system.Cauer`
Raises
------
:py:class:`InvalidAnalogSystemError`
For faulty analog system parametrization.
"""
def __init__(self, N: int, Wn: float, rp: float):
"""Create a Chebyshev type I filter"""
# State space order
self.Wn = Wn
self.rp = rp
z, p, k = scipy.signal.iirfilter(
N, Wn, rp, analog=True, btype="lowpass", ftype="cheby1", output="zpk"
)
A, B, CT, D = zpk2abcd(z, p, k)
AnalogSystem.__init__(self, A, B, CT, None, None, D)
[docs]class ChebyshevII(AnalogSystem):
"""A Chebyshev type II filter's analog system
This class inherits from :py:class:`cbadc.analog_system.AnalogSystem` and is a convenient
way of creating Chebyshev type II filter's analog system representation.
Specifically, we specify the filter by the differential equations
:math:`\dot{\mathbf{x}}(t) = \mathbf{A} \mathbf{x}(t) + \mathbf{B} u(t) + \mathbf{\Gamma} \mathbf{s}(t)`
:math:`\mathbf{y}(t) = \mathbf{C}^\mathsf{T} \mathbf{x}(t) + \mathbf{D} u(t)`
:math:`\\tilde{\mathbf{s}}(t) = \\tilde{\mathbf{\Gamma}}^\mathsf{T} \mathbf{x}(t)`
where
internally :math:`\mathbf{A}` :math:`\mathbf{B}`, :math:`\mathbf{C}^\mathsf{T}`, and :math:`\mathbf{D}`
are determined using the :py:func:`scipy.signal.iirfilter`.
Furthermore, as this system is intended as a pure filter and therefore have no
:math:`\mathbf{\Gamma}` and :math:`\\tilde{\mathbf{\Gamma}}^\mathsf{T}` specified.
Parameters
----------
N: `int`
filter order
Wn: (float, float)
array containing the critical frequencies (low, high) of the filter.
The frequencies are specified in rad/s.
rs: float
minimum attenutation in stopband. Specified in dB.
Attributes
----------
N : `int`
state space order :math:`N`.
N_tilde : `int`
number of signal observations :math:`\\tilde{N}`.
M : `int`
number of digital control signals :math:`M`.
M_tilde : `int`
number of control signal observations :math:`\\tilde{M}`.
L : `int`
number of input signals :math:`L`.
A : `array_like`, shape=(N, N)
system matrix :math:`\mathbf{A}`.
B : `array_like`, shape=(N, L)
input matrix :math:`\mathbf{B}`.
CT : `array_like`, shape=(N_tilde, N)
signal observation matrix :math:`\mathbf{C}^\mathsf{T}`.
D: `array_like`, shape=(N_tilde, L)
direct matrix
Gamma : None
control input matrix :math:`\mathbf{\Gamma}`.
Gamma_tildeT : None
control observation matrix :math:`\\tilde{\mathbf{\Gamma}}^\mathsf{T}`.
Wn : float
critical frequency of filter.
rs: float
minimum attenuation in stop band (dB).
See also
--------
:py:class:`cbadc.analog_system.ButterWorth`
:py:class:`cbadc.analog_system.ChebyshevI`
:py:class:`cbadc.analog_system.Cauer`
Raises
------
:py:class:`InvalidAnalogSystemError`
For faulty analog system parametrization.
"""
def __init__(self, N: int, Wn: float, rs: float):
"""Create a Chebyshev type II filter"""
# State space order
self.Wn = Wn
self.rs = rs
z, p, k = scipy.signal.iirfilter(
N, Wn, rs=rs, analog=True, btype="lowpass", ftype="cheby2", output="zpk"
)
A, B, CT, D = zpk2abcd(z, p, k)
AnalogSystem.__init__(self, A, B, CT, None, None, D)
[docs]class Cauer(AnalogSystem):
"""A Cauer (elliptic) filter's analog system
This class inherits from :py:class:`cbadc.analog_system.AnalogSystem` and is a convenient
way of creating Cauer filter's analog system representation.
Specifically, we specify the filter by the differential equations
:math:`\dot{\mathbf{x}}(t) = \mathbf{A} \mathbf{x}(t) + \mathbf{B} u(t) + \mathbf{\Gamma} \mathbf{s}(t)`
:math:`\mathbf{y}(t) = \mathbf{C}^\mathsf{T} \mathbf{x}(t) + \mathbf{D} u(t)`
:math:`\\tilde{\mathbf{s}}(t) = \\tilde{\mathbf{\Gamma}}^\mathsf{T} \mathbf{x}(t)`
where
internally :math:`\mathbf{A}` :math:`\mathbf{B}`, :math:`\mathbf{C}^\mathsf{T}`, and :math:`\mathbf{D}`
are determined using the :py:func:`scipy.signal.iirfilter`.
Furthermore, as this system is intended as a pure filter and therefore have no
:math:`\mathbf{\Gamma}` and :math:`\\tilde{\mathbf{\Gamma}}^\mathsf{T}` specified.
Parameters
----------
N: `int`
filter order
Wn: (float, float)
array containing the critical frequencies (low, high) of the filter.
The frequencies are specified in rad/s.
rp: float
maximum ripple in passband. Specified in dB.
rs: float
minimum attenutation in stopband. Specified in dB.
Attributes
----------
N : `int`
state space order :math:`N`.
N_tilde : `int`
number of signal observations :math:`\\tilde{N}`.
M : `int`
number of digital control signals :math:`M`.
M_tilde : `int`
number of control signal observations :math:`\\tilde{M}`.
L : `int`
number of input signals :math:`L`.
A : `array_like`, shape=(N, N)
system matrix :math:`\mathbf{A}`.
B : `array_like`, shape=(N, L)
input matrix :math:`\mathbf{B}`.
CT : `array_like`, shape=(N_tilde, N)
signal observation matrix :math:`\mathbf{C}^\mathsf{T}`.
D: `array_like`, shape=(N_tilde, L)
direct matrix
Gamma : None
control input matrix :math:`\mathbf{\Gamma}`.
Gamma_tildeT : None
control observation matrix :math:`\\tilde{\mathbf{\Gamma}}^\mathsf{T}`.
Wn : float
critical frequency of filter.
rp: float
maximal ripple in passband, specified in (dB).
rs: float
minimum attenuation in stop band (dB).
See also
--------
:py:class:`cbadc.analog_system.ButterWorth`
:py:class:`cbadc.analog_system.ChebyshevI`
:py:class:`cbadc.analog_system.ChebyshevII`
Raises
------
:py:class:`InvalidAnalogSystemError`
For faulty analog system parametrization.
"""
def __init__(self, N: int, Wn: float, rp: float, rs: float):
"""Create a Cauer filter"""
# State space order
self.Wn = Wn
self.rp = rp
self.rs = rs
z, p, k = scipy.signal.iirfilter(
N, Wn, rp, rs, analog=True, btype="lowpass", ftype="ellip", output="zpk"
)
A, B, CT, D = zpk2abcd(z, p, k)
AnalogSystem.__init__(self, A, B, CT, None, None, D)
[docs]class IIRDesign(AnalogSystem):
"""An analog signal designed using standard IIRDesign tools
This class inherits from :py:class:`cbadc.analog_system.AnalogSystem` and is a convenient
way of creating IIR filters in an analog system representation.
Specifically, we specify the filter by the differential equations
:math:`\dot{\mathbf{x}}(t) = \mathbf{A} \mathbf{x}(t) + \mathbf{B} u(t) + \mathbf{\Gamma} \mathbf{s}(t)`
:math:`\mathbf{y}(t) = \mathbf{C}^\mathsf{T} \mathbf{x}(t) + \mathbf{D} u(t)`
:math:`\\tilde{\mathbf{s}}(t) = \\tilde{\mathbf{\Gamma}}^\mathsf{T} \mathbf{x}(t)`
where
internally :math:`\mathbf{A}` :math:`\mathbf{B}`, :math:`\mathbf{C}^\mathsf{T}`, and :math:`\mathbf{D}`
are determined using the :py:func:`scipy.signal.iirdesign`.
Furthermore, as this system is intended as a pure filter and therefore have no
:math:`\mathbf{\Gamma}` and :math:`\\tilde{\mathbf{\Gamma}}^\mathsf{T}` specified.
Parameters
----------
wp, ws: `float or array_like`, shape=(2,)
Passband and stopband edge frequencies. Possible values are scalars (for lowpass and highpass filters) or ranges (for bandpass and bandstop filters). For digital filters, these are in the same units as fs. By default, fs is 2 half-cycles/sample, so these are normalized from 0 to 1, where 1 is the Nyquist frequency. For example:
* Lowpass: wp = 0.2, ws = 0.3
* Highpass: wp = 0.3, ws = 0.2
* Bandpass: wp = [0.2, 0.5], ws = [0.1, 0.6]
* Bandstop: wp = [0.1, 0.6], ws = [0.2, 0.5]
wp and ws are angular frequencies (e.g., rad/s). Note, that for bandpass and bandstop filters passband must lie strictly inside stopband or vice versa.
gpass: `float`
The maximum loss in the passband (dB).
gstop: `float`
The minimum attenuation in the stopband (dB).
ftype: `string`, `optional`
IIR filter type, defaults to ellip. Complete list of choices:
* Butterworth : ‘butter’
* Chebyshev I : ‘cheby1’
* Chebyshev II : ‘cheby2’
* Cauer/elliptic: ‘ellip’
* Bessel/Thomson: ‘bessel’
Attributes
----------
N : `int`
state space order :math:`N`.
N_tilde : `int`
number of signal observations :math:`\\tilde{N}`.
M : `int`
number of digital control signals :math:`M`.
M_tilde : `int`
number of control signal observations :math:`\\tilde{M}`.
L : `int`
number of input signals :math:`L`.
A : `array_like`, shape=(N, N)
system matrix :math:`\mathbf{A}`.
B : `array_like`, shape=(N, L)
input matrix :math:`\mathbf{B}`.
CT : `array_like`, shape=(N_tilde, N)
signal observation matrix :math:`\mathbf{C}^\mathsf{T}`.
D: `array_like`, shape=(N_tilde, L)
direct matrix
Gamma : None
control input matrix :math:`\mathbf{\Gamma}`.
Gamma_tildeT : None
control observation matrix :math:`\\tilde{\mathbf{\Gamma}}^\mathsf{T}`.
Example
-------
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> import matplotlib.ticker
>>> from cbadc.analog_system import IIRDesign
>>> wp = 2 * np.pi * 1e3
>>> ws = 2 * np.pi * 2e3
>>> gpass = 0.1
>>> gstop = 80
>>> filter = IIRDesign(wp, ws, gpass, gstop)
>>> f = np.logspace(1, 5)
>>> w = 2 * np.pi * f
>>> tf = filter.transfer_function_matrix(w)
>>> fig, ax1 = plt.subplots()
>>> _ = ax1.set_title('Analog filter frequency response')
>>> _ = ax1.set_ylabel('Amplitude [dB]', color='b')
>>> _ = ax1.set_xlabel('Frequency [Hz]')
>>> _ = ax1.semilogx(f, 20 * np.log10(np.abs(tf[0, 0, :])))
>>> _ = ax1.grid()
>>> ax2 = ax1.twinx()
>>> angles = np.unwrap(np.angle(tf[0, 0, :]))
>>> _ = ax2.plot(f, angles, 'g')
>>> _ = ax2.set_ylabel('Angle (radians)', color='g')
>>> _ = ax2.grid()
>>> _ =ax2.axis('tight')
>>> nticks = 8
>>> _ = ax1.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
>>> _ = ax2.yaxis.set_major_locator(matplotlib.ticker.LinearLocator(nticks))
See also
--------
:py:class:`cbadc.analog_system.ButterWorth`
:py:class:`cbadc.analog_system.ChebyshevI`
:py:class:`cbadc.analog_system.ChebyshevII`
:py:class:`cbadc.analog_system.Cauer`
Raises
------
:py:class:`InvalidAnalogSystemError`
For faulty analog system parametrization.
"""
def __init__(self, wp, ws, gpass, gstop, ftype="ellip"):
"""Create a IIR filter"""
z, p, k = scipy.signal.iirdesign(
wp, ws, gpass, gstop, analog=True, ftype=ftype, output="zpk"
)
A, B, CT, D = zpk2abcd(z, p, k)
CT = CT[-1:, :]
D = D[-1:, :]
AnalogSystem.__init__(self, A, B, CT, None, None, D)