Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 85 additions & 3 deletions src/spectrum/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import numpy as np

__all__ = ["tf2zp", 'eqtflength', 'latc2tf', 'latcfilt',
'ss2zpk', 'tf2sos', 'tf2ss', 'tf2zpk', 'zpk2ss', 'zpk2tf']

__all__ = ['eqtflength', 'latc2tf', 'latcfilt',
'sos2ss', 'sos2tf', 'sos2zp',
'ss2zpk', 'tf2zp', 'tf2sos', 'tf2ss', 'tf2zpk', 'zpk2ss', 'zpk2tf']

"""to be done

Expand Down Expand Up @@ -249,3 +249,85 @@ def zpk2ss(z, p, k):
return scipy.signal.zpk2ss(z,p,k)


def sos2tf(sos):
"""Return a single transfer function from a series of second-order sections.

:param array_like sos: Array of second-order filter coefficients, must have
shape ``(n_sections, 6)``. Each row corresponds to a second-order
section, with the first three columns providing the numerator
coefficients and the last three providing the denominator coefficients.

:return:
* b : ndarray Numerator polynomial coefficients.
* a : ndarray Denominator polynomial coefficients.

.. doctest::

>>> import scipy.signal
>>> from spectrum.transfer import sos2tf
>>> sos = scipy.signal.butter(4, 0.2, output='sos')
>>> b, a = sos2tf(sos)

.. note:: wrapper of scipy function sos2tf
.. seealso:: :func:`sos2ss`, :func:`sos2zp`
"""
import scipy.signal
return scipy.signal.sos2tf(sos)


def sos2zp(sos):
"""Return zeros, poles, and gain of a series of second-order sections.

:param array_like sos: Array of second-order filter coefficients, must have
shape ``(n_sections, 6)``. Each row corresponds to a second-order
section, with the first three columns providing the numerator
coefficients and the last three providing the denominator coefficients.

:return:
* z : ndarray Zeros of the transfer function.
* p : ndarray Poles of the transfer function.
* k : float System gain.

.. doctest::

>>> import scipy.signal
>>> from spectrum.transfer import sos2zp
>>> sos = scipy.signal.butter(4, 0.2, output='sos')
>>> z, p, k = sos2zp(sos)

.. note:: wrapper of scipy function sos2zpk
.. seealso:: :func:`sos2ss`, :func:`sos2tf`
"""
import scipy.signal
return scipy.signal.sos2zpk(sos)


def sos2ss(sos):
"""Convert digital filter second-order section parameters to state-space form.

:param array_like sos: Array of second-order filter coefficients, must have
shape ``(n_sections, 6)``. Each row corresponds to a second-order
section, with the first three columns providing the numerator
coefficients and the last three providing the denominator coefficients.

:return:
* A : ndarray State-space representation of the IIR filter.
* B : ndarray State-space representation of the IIR filter.
* C : ndarray State-space representation of the IIR filter.
* D : ndarray State-space representation of the IIR filter.

.. doctest::

>>> import scipy.signal
>>> from spectrum.transfer import sos2ss
>>> sos = scipy.signal.butter(4, 0.2, output='sos')
>>> A, B, C, D = sos2ss(sos)

.. note:: Implemented by chaining :func:`sos2tf` and ``scipy.signal.tf2ss``.
.. seealso:: :func:`sos2tf`, :func:`sos2zp`
"""
import scipy.signal
b, a = sos2tf(sos)
return scipy.signal.tf2ss(b, a)


40 changes: 40 additions & 0 deletions test/test_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,46 @@ def test_ss2zpk():
assert k == kp


def test_sos2tf():
from scipy import signal
sos = signal.butter(4, 0.2, output='sos')
b, a = transfer.sos2tf(sos)
# Verify round-trip: sos2tf then tf2zpk should match sos2zpk
z_tf, p_tf, k_tf = signal.tf2zpk(b, a)
z_sos, p_sos, k_sos = signal.sos2zpk(sos)
assert_almost_equal(abs(k_tf), abs(k_sos), decimal=5)
assert len(z_tf) == len(z_sos)
assert len(p_tf) == len(p_sos)


def test_sos2zp():
from scipy import signal
sos = signal.butter(4, 0.2, output='sos')
z, p, k = transfer.sos2zp(sos)
z_ref, p_ref, k_ref = signal.sos2zpk(sos)
assert_almost_equal(np.sort(np.abs(z)), np.sort(np.abs(z_ref)), decimal=5)
assert_almost_equal(np.sort(np.abs(p)), np.sort(np.abs(p_ref)), decimal=5)
assert_almost_equal(k, k_ref, decimal=5)


def test_sos2ss():
from scipy import signal
sos = signal.butter(4, 0.2, output='sos')
A, B, C, D = transfer.sos2ss(sos)
# Verify state-space matrices are returned with correct shapes
n_sections = sos.shape[0]
order = 2 * n_sections
assert A.shape == (order, order)
assert B.shape == (order, 1)
assert C.shape == (1, order)
assert D.shape == (1, 1)
# Verify round-trip: ss2zpk should recover poles/zeros matching sos2zpk
z_ss, p_ss, k_ss = signal.ss2zpk(A, B, C, D)
z_sos, p_sos, k_sos = signal.sos2zpk(sos)
assert_almost_equal(np.sort(np.abs(p_ss)), np.sort(np.abs(p_sos)), decimal=5)
assert_almost_equal(abs(k_ss), abs(k_sos), decimal=5)





Expand Down