LSR/env/lib/python3.6/site-packages/control/tests/xferfcn_test.py
2020-06-04 17:24:47 +02:00

836 lines
33 KiB
Python

#!/usr/bin/env python
#
# xferfcn_test.py - test TransferFunction class
# RMM, 30 Mar 2011 (based on TestXferFcn from v0.4a)
import unittest
import sys as pysys
import numpy as np
from control.statesp import StateSpace, _convertToStateSpace, rss
from control.xferfcn import TransferFunction, _convert_to_transfer_function, \
ss2tf
from control.lti import evalfr
from control.exception import slycot_check
from control.lti import isctime, isdtime
from control.dtime import sample_system
class TestXferFcn(unittest.TestCase):
"""These are tests for functionality and correct reporting of the transfer
function class. Throughout these tests, we will give different input
formats to the xTranferFunction constructor, to try to break it. These
tests have been verified in MATLAB."""
# Tests for raising exceptions.
def test_constructor_bad_input_type(self):
"""Give the constructor invalid input types."""
# MIMO requires lists of lists of vectors (not lists of vectors)
self.assertRaises(
TypeError,
TransferFunction, [[0., 1.], [2., 3.]], [[5., 2.], [3., 0.]])
TransferFunction([[ [0., 1.], [2., 3.] ]], [[ [5., 2.], [3., 0.] ]])
# Single argument of the wrong type
self.assertRaises(TypeError, TransferFunction, [1])
# Too many arguments
self.assertRaises(ValueError, TransferFunction, 1, 2, 3, 4)
# Different numbers of elements in numerator rows
self.assertRaises(
ValueError,
TransferFunction, [ [[0, 1], [2, 3]],
[[4, 5]] ],
[ [[6, 7], [4, 5]],
[[2, 3], [0, 1]] ])
self.assertRaises(
ValueError,
TransferFunction, [ [[0, 1], [2, 3]],
[[4, 5], [6, 7]] ],
[ [[6, 7], [4, 5]],
[[2, 3]] ])
TransferFunction( # This version is OK
[ [[0, 1], [2, 3]], [[4, 5], [6, 7]] ],
[ [[6, 7], [4, 5]], [[2, 3], [0, 1]] ])
def test_constructor_inconsistent_dimension(self):
"""Give constructor numerators, denominators of different sizes."""
self.assertRaises(ValueError, TransferFunction,
[[[1.]]], [[[1.], [2., 3.]]])
self.assertRaises(ValueError, TransferFunction,
[[[1.]]], [[[1.]], [[2., 3.]]])
self.assertRaises(ValueError, TransferFunction,
[[[1.]]], [[[1.], [1., 2.]], [[5., 2.], [2., 3.]]])
def test_constructor_inconsistent_columns(self):
"""Give the constructor inputs that do not have the same number of
columns in each row."""
self.assertRaises(ValueError, TransferFunction,
1., [[[1.]], [[2.], [3.]]])
self.assertRaises(ValueError, TransferFunction,
[[[1.]], [[2.], [3.]]], 1.)
def test_constructor_zero_denominator(self):
"""Give the constructor a transfer function with a zero denominator."""
self.assertRaises(ValueError, TransferFunction, 1., 0.)
self.assertRaises(ValueError, TransferFunction,
[[[1.], [2., 3.]], [[-1., 4.], [3., 2.]]],
[[[1., 0.], [0.]], [[0., 0.], [2.]]])
def test_add_inconsistent_dimension(self):
"""Add two transfer function matrices of different sizes."""
sys1 = TransferFunction([[[1., 2.]]], [[[4., 5.]]])
sys2 = TransferFunction([[[4., 3.]], [[1., 2.]]],
[[[1., 6.]], [[2., 4.]]])
self.assertRaises(ValueError, sys1.__add__, sys2)
self.assertRaises(ValueError, sys1.__sub__, sys2)
self.assertRaises(ValueError, sys1.__radd__, sys2)
self.assertRaises(ValueError, sys1.__rsub__, sys2)
def test_mul_inconsistent_dimension(self):
"""Multiply two transfer function matrices of incompatible sizes."""
sys1 = TransferFunction([[[1., 2.], [4., 5.]], [[2., 5.], [4., 3.]]],
[[[6., 2.], [4., 1.]], [[6., 7.], [2., 4.]]])
sys2 = TransferFunction([[[1.]], [[2.]], [[3.]]],
[[[4.]], [[5.]], [[6.]]])
self.assertRaises(ValueError, sys1.__mul__, sys2)
self.assertRaises(ValueError, sys2.__mul__, sys1)
self.assertRaises(ValueError, sys1.__rmul__, sys2)
self.assertRaises(ValueError, sys2.__rmul__, sys1)
# Tests for TransferFunction._truncatecoeff
def test_truncate_coefficients_non_null_numerator(self):
"""Remove extraneous zeros in polynomial representations."""
sys1 = TransferFunction([0., 0., 1., 2.], [[[0., 0., 0., 3., 2., 1.]]])
np.testing.assert_array_equal(sys1.num, [[[1., 2.]]])
np.testing.assert_array_equal(sys1.den, [[[3., 2., 1.]]])
def test_truncate_coefficients_null_numerator(self):
"""Remove extraneous zeros in polynomial representations."""
sys1 = TransferFunction([0., 0., 0.], 1.)
np.testing.assert_array_equal(sys1.num, [[[0.]]])
np.testing.assert_array_equal(sys1.den, [[[1.]]])
# Tests for TransferFunction.__neg__
def test_reverse_sign_scalar(self):
"""Negate a direct feedthrough system."""
sys1 = TransferFunction(2., np.array([-3.]))
sys2 = - sys1
np.testing.assert_array_equal(sys2.num, [[[-2.]]])
np.testing.assert_array_equal(sys2.den, [[[-3.]]])
def test_reverse_sign_siso(self):
"""Negate a SISO system."""
sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1.])
sys2 = - sys1
np.testing.assert_array_equal(sys2.num, [[[-1., -3., -5.]]])
np.testing.assert_array_equal(sys2.den, [[[1., 6., 2., -1.]]])
@unittest.skipIf(not slycot_check(), "slycot not installed")
def test_reverse_sign_mimo(self):
"""Negate a MIMO system."""
num1 = [[[1., 2.], [0., 3.], [2., -1.]],
[[1.], [4., 0.], [1., -4., 3.]]]
num3 = [[[-1., -2.], [0., -3.], [-2., 1.]],
[[-1.], [-4., 0.], [-1., 4., -3.]]]
den1 = [[[-3., 2., 4.], [1., 0., 0.], [2., -1.]],
[[3., 0., .0], [2., -1., -1.], [1.]]]
sys1 = TransferFunction(num1, den1)
sys2 = - sys1
sys3 = TransferFunction(num3, den1)
for i in range(sys3.outputs):
for j in range(sys3.inputs):
np.testing.assert_array_equal(sys2.num[i][j], sys3.num[i][j])
np.testing.assert_array_equal(sys2.den[i][j], sys3.den[i][j])
# Tests for TransferFunction.__add__
def test_add_scalar(self):
"""Add two direct feedthrough systems."""
sys1 = TransferFunction(1., [[[1.]]])
sys2 = TransferFunction(np.array([2.]), [1.])
sys3 = sys1 + sys2
np.testing.assert_array_equal(sys3.num, 3.)
np.testing.assert_array_equal(sys3.den, 1.)
def test_add_siso(self):
"""Add two SISO systems."""
sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1])
sys2 = TransferFunction([[np.array([-1., 3.])]], [[[1., 0., -1.]]])
sys3 = sys1 + sys2
# If sys3.num is [[[0., 20., 4., -8.]]], then this is wrong!
np.testing.assert_array_equal(sys3.num, [[[20., 4., -8]]])
np.testing.assert_array_equal(sys3.den, [[[1., 6., 1., -7., -2., 1.]]])
@unittest.skipIf(not slycot_check(), "slycot not installed")
def test_add_mimo(self):
"""Add two MIMO systems."""
num1 = [[[1., 2.], [0., 3.], [2., -1.]],
[[1.], [4., 0.], [1., -4., 3.]]]
den1 = [[[-3., 2., 4.], [1., 0., 0.], [2., -1.]],
[[3., 0., .0], [2., -1., -1.], [1.]]]
num2 = [[[0., 0., -1], [2.], [-1., -1.]],
[[1., 2.], [-1., -2.], [4.]]]
den2 = [[[-1.], [1., 2., 3.], [-1., -1.]],
[[-4., -3., 2.], [0., 1.], [1., 0.]]]
num3 = [[[3., -3., -6], [5., 6., 9.], [-4., -2., 2]],
[[3., 2., -3., 2], [-2., -3., 7., 2.], [1., -4., 3., 4]]]
den3 = [[[3., -2., -4.], [1., 2., 3., 0., 0.], [-2., -1., 1.]],
[[-12., -9., 6., 0., 0.], [2., -1., -1.], [1., 0.]]]
sys1 = TransferFunction(num1, den1)
sys2 = TransferFunction(num2, den2)
sys3 = sys1 + sys2
for i in range(sys3.outputs):
for j in range(sys3.inputs):
np.testing.assert_array_equal(sys3.num[i][j], num3[i][j])
np.testing.assert_array_equal(sys3.den[i][j], den3[i][j])
# Tests for TransferFunction.__sub__
def test_subtract_scalar(self):
"""Subtract two direct feedthrough systems."""
sys1 = TransferFunction(1., [[[1.]]])
sys2 = TransferFunction(np.array([2.]), [1.])
sys3 = sys1 - sys2
np.testing.assert_array_equal(sys3.num, -1.)
np.testing.assert_array_equal(sys3.den, 1.)
def test_subtract_siso(self):
"""Subtract two SISO systems."""
sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1])
sys2 = TransferFunction([[np.array([-1., 3.])]], [[[1., 0., -1.]]])
sys3 = sys1 - sys2
sys4 = sys2 - sys1
np.testing.assert_array_equal(sys3.num, [[[2., 6., -12., -10., -2.]]])
np.testing.assert_array_equal(sys3.den, [[[1., 6., 1., -7., -2., 1.]]])
np.testing.assert_array_equal(sys4.num, [[[-2., -6., 12., 10., 2.]]])
np.testing.assert_array_equal(sys4.den, [[[1., 6., 1., -7., -2., 1.]]])
@unittest.skipIf(not slycot_check(), "slycot not installed")
def test_subtract_mimo(self):
"""Subtract two MIMO systems."""
num1 = [[[1., 2.], [0., 3.], [2., -1.]],
[[1.], [4., 0.], [1., -4., 3.]]]
den1 = [[[-3., 2., 4.], [1., 0., 0.], [2., -1.]],
[[3., 0., .0], [2., -1., -1.], [1.]]]
num2 = [[[0., 0., -1], [2.], [-1., -1.]],
[[1., 2.], [-1., -2.], [4.]]]
den2 = [[[-1.], [1., 2., 3.], [-1., -1.]],
[[-4., -3., 2.], [0., 1.], [1., 0.]]]
num3 = [[[-3., 1., 2.], [1., 6., 9.], [0.]],
[[-3., -10., -3., 2], [2., 3., 1., -2], [1., -4., 3., -4]]]
den3 = [[[3., -2., -4], [1., 2., 3., 0., 0.], [1]],
[[-12., -9., 6., 0., 0.], [2., -1., -1], [1., 0.]]]
sys1 = TransferFunction(num1, den1)
sys2 = TransferFunction(num2, den2)
sys3 = sys1 - sys2
for i in range(sys3.outputs):
for j in range(sys3.inputs):
np.testing.assert_array_equal(sys3.num[i][j], num3[i][j])
np.testing.assert_array_equal(sys3.den[i][j], den3[i][j])
# Tests for TransferFunction.__mul__
def test_multiply_scalar(self):
"""Multiply two direct feedthrough systems."""
sys1 = TransferFunction(2., [1.])
sys2 = TransferFunction(1., 4.)
sys3 = sys1 * sys2
sys4 = sys1 * sys2
np.testing.assert_array_equal(sys3.num, [[[2.]]])
np.testing.assert_array_equal(sys3.den, [[[4.]]])
np.testing.assert_array_equal(sys3.num, sys4.num)
np.testing.assert_array_equal(sys3.den, sys4.den)
def test_multiply_siso(self):
"""Multiply two SISO systems."""
sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1])
sys2 = TransferFunction([[[-1., 3.]]], [[[1., 0., -1.]]])
sys3 = sys1 * sys2
sys4 = sys2 * sys1
np.testing.assert_array_equal(sys3.num, [[[-1., 0., 4., 15.]]])
np.testing.assert_array_equal(sys3.den, [[[1., 6., 1., -7., -2., 1.]]])
np.testing.assert_array_equal(sys3.num, sys4.num)
np.testing.assert_array_equal(sys3.den, sys4.den)
@unittest.skipIf(not slycot_check(), "slycot not installed")
def test_multiply_mimo(self):
"""Multiply two MIMO systems."""
num1 = [[[1., 2.], [0., 3.], [2., -1.]],
[[1.], [4., 0.], [1., -4., 3.]]]
den1 = [[[-3., 2., 4.], [1., 0., 0.], [2., -1.]],
[[3., 0., .0], [2., -1., -1.], [1.]]]
num2 = [[[0., 1., 2.]],
[[1., -5.]],
[[-2., 1., 4.]]]
den2 = [[[1., 0., 0., 0.]],
[[-2., 1., 3.]],
[[4., -1., -1., 0.]]]
num3 = [[[-24., 52., -14., 245., -490., -115., 467., -95., -56., 12.,
0., 0., 0.]],
[[24., -132., 138., 345., -768., -106., 510., 41., -79., -69.,
-23., 17., 6., 0.]]]
den3 = [[[48., -92., -84., 183., 44., -97., -2., 12., 0., 0., 0., 0.,
0., 0.]],
[[-48., 60., 84., -81., -45., 21., 9., 0., 0., 0., 0., 0., 0.]]]
sys1 = TransferFunction(num1, den1)
sys2 = TransferFunction(num2, den2)
sys3 = sys1 * sys2
for i in range(sys3.outputs):
for j in range(sys3.inputs):
np.testing.assert_array_equal(sys3.num[i][j], num3[i][j])
np.testing.assert_array_equal(sys3.den[i][j], den3[i][j])
# Tests for TransferFunction.__div__
def test_divide_scalar(self):
"""Divide two direct feedthrough systems."""
sys1 = TransferFunction(np.array([3.]), -4.)
sys2 = TransferFunction(5., 2.)
sys3 = sys1 / sys2
np.testing.assert_array_equal(sys3.num, [[[6.]]])
np.testing.assert_array_equal(sys3.den, [[[-20.]]])
def test_divide_siso(self):
"""Divide two SISO systems."""
sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1])
sys2 = TransferFunction([[[-1., 3.]]], [[[1., 0., -1.]]])
sys3 = sys1 / sys2
sys4 = sys2 / sys1
np.testing.assert_array_equal(sys3.num, [[[1., 3., 4., -3., -5.]]])
np.testing.assert_array_equal(sys3.den, [[[-1., -3., 16., 7., -3.]]])
np.testing.assert_array_equal(sys4.num, sys3.den)
np.testing.assert_array_equal(sys4.den, sys3.num)
def test_div(self):
# Make sure that sampling times work correctly
sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1])
sys2 = TransferFunction([[[-1., 3.]]], [[[1., 0., -1.]]], True)
sys3 = sys1 / sys2
self.assertEqual(sys3.dt, True)
sys2 = TransferFunction([[[-1., 3.]]], [[[1., 0., -1.]]], 0.5)
sys3 = sys1 / sys2
self.assertEqual(sys3.dt, 0.5)
sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1], 0.1)
self.assertRaises(ValueError, TransferFunction.__truediv__, sys1, sys2)
sys1 = sample_system(rss(4, 1, 1), 0.5)
sys3 = TransferFunction.__rtruediv__(sys2, sys1)
self.assertEqual(sys3.dt, 0.5)
def test_pow(self):
sys1 = TransferFunction([1., 3., 5], [1., 6., 2., -1])
self.assertRaises(ValueError, TransferFunction.__pow__, sys1, 0.5)
def test_slice(self):
sys = TransferFunction(
[ [ [1], [2], [3]], [ [3], [4], [5]] ],
[ [[1, 2], [1, 3], [1, 4]], [[1, 4], [1, 5], [1, 6]] ])
sys1 = sys[1:, 1:]
self.assertEqual((sys1.inputs, sys1.outputs), (2, 1))
sys2 = sys[:2, :2]
self.assertEqual((sys2.inputs, sys2.outputs), (2, 2))
sys = TransferFunction(
[ [ [1], [2], [3]], [ [3], [4], [5]] ],
[ [[1, 2], [1, 3], [1, 4]], [[1, 4], [1, 5], [1, 6]] ], 0.5)
sys1 = sys[1:, 1:]
self.assertEqual((sys1.inputs, sys1.outputs), (2, 1))
self.assertEqual(sys1.dt, 0.5)
def test_evalfr_siso(self):
"""Evaluate the frequency response of a SISO system at one frequency."""
sys = TransferFunction([1., 3., 5], [1., 6., 2., -1])
np.testing.assert_array_almost_equal(evalfr(sys, 1j),
np.array([[-0.5 - 0.5j]]))
np.testing.assert_array_almost_equal(
evalfr(sys, 32j),
np.array([[0.00281959302585077 - 0.030628473607392j]]))
# Test call version as well
np.testing.assert_almost_equal(sys(1.j), -0.5 - 0.5j)
np.testing.assert_almost_equal(
sys(32.j), 0.00281959302585077 - 0.030628473607392j)
# Test internal version (with real argument)
np.testing.assert_array_almost_equal(
sys._evalfr(1.), np.array([[-0.5 - 0.5j]]))
np.testing.assert_array_almost_equal(
sys._evalfr(32.),
np.array([[0.00281959302585077 - 0.030628473607392j]]))
# This test only works in Python 3 due to a conflict with the same
# warning type in other test modules (frd_test.py). See
# https://bugs.python.org/issue4180 for more details
@unittest.skipIf(pysys.version_info < (3, 0), "test requires Python 3+")
def test_evalfr_deprecated(self):
sys = TransferFunction([1., 3., 5], [1., 6., 2., -1])
# Deprecated version of the call (should generate warning)
import warnings
with warnings.catch_warnings():
# Make warnings generate an exception
warnings.simplefilter('error')
# Make sure that we get a pending deprecation warning
self.assertRaises(PendingDeprecationWarning, sys.evalfr, 1.)
@unittest.skipIf(pysys.version_info < (3, 0), "test requires Python 3+")
def test_evalfr_dtime(self):
sys = TransferFunction([1., 3., 5], [1., 6., 2., -1], 0.1)
np.testing.assert_array_almost_equal(sys(1j), -0.5 - 0.5j)
@unittest.skipIf(not slycot_check(), "slycot not installed")
def test_evalfr_mimo(self):
"""Evaluate the frequency response of a MIMO system at one frequency."""
num = [[[1., 2.], [0., 3.], [2., -1.]],
[[1.], [4., 0.], [1., -4., 3.]]]
den = [[[-3., 2., 4.], [1., 0., 0.], [2., -1.]],
[[3., 0., .0], [2., -1., -1.], [1.]]]
sys = TransferFunction(num, den)
resp = [[0.147058823529412 + 0.0882352941176471j, -0.75, 1.],
[-0.083333333333333, -0.188235294117647 - 0.847058823529412j,
-1. - 8.j]]
np.testing.assert_array_almost_equal(sys._evalfr(2.), resp)
# Test call version as well
np.testing.assert_array_almost_equal(sys(2.j), resp)
def test_freqresp_siso(self):
"""Evaluate the magnitude and phase of a SISO system at
multiple frequencies."""
sys = TransferFunction([1., 3., 5], [1., 6., 2., -1])
truemag = [[[4.63507337473906, 0.707106781186548, 0.0866592803995351]]]
truephase = [[[-2.89596891081488, -2.35619449019234,
-1.32655885133871]]]
trueomega = [0.1, 1., 10.]
mag, phase, omega = sys.freqresp(trueomega)
np.testing.assert_array_almost_equal(mag, truemag)
np.testing.assert_array_almost_equal(phase, truephase)
np.testing.assert_array_almost_equal(omega, trueomega)
@unittest.skipIf(not slycot_check(), "slycot not installed")
def test_freqresp_mimo(self):
"""Evaluate the magnitude and phase of a MIMO system at
multiple frequencies."""
num = [[[1., 2.], [0., 3.], [2., -1.]],
[[1.], [4., 0.], [1., -4., 3.]]]
den = [[[-3., 2., 4.], [1., 0., 0.], [2., -1.]],
[[3., 0., .0], [2., -1., -1.], [1.]]]
sys = TransferFunction(num, den)
true_omega = [0.1, 1., 10.]
true_mag = [[[0.49628709, 0.30714755, 0.03347381],
[300., 3., 0.03], [1., 1., 1.]],
[[33.333333, 0.33333333, 0.00333333],
[0.39028569, 1.26491106, 0.19875914],
[3.01663720, 4.47213595, 104.92378186]]]
true_phase = [[[3.7128711e-4, 0.18534794,
1.30770596], [-np.pi, -np.pi, -np.pi],
[0., 0., 0.]],
[[-np.pi, -np.pi, -np.pi],
[-1.66852323, -1.89254688, -1.62050658],
[-0.13298964, -1.10714871, -2.75046720]]]
mag, phase, omega = sys.freqresp(true_omega)
np.testing.assert_array_almost_equal(mag, true_mag)
np.testing.assert_array_almost_equal(phase, true_phase)
np.testing.assert_array_equal(omega, true_omega)
# Tests for TransferFunction.pole and TransferFunction.zero.
def test_common_den(self):
""" Test the helper function to compute common denomitators."""
# _common_den() computes the common denominator per input/column.
# The testing columns are:
# 0: no common poles
# 1: regular common poles
# 2: poles with multiplicity,
# 3: complex poles
# 4: complex poles below threshold
eps = np.finfo(float).eps
tol_imag = np.sqrt(eps*5*2*2)*0.9
numin = [[[1.], [1.], [1.], [1.], [1.]],
[[1.], [1.], [1.], [1.], [1.]]]
denin = [[[1., 3., 2.], # 0: poles: [-1, -2]
[1., 6., 11., 6.], # 1: poles: [-1, -2, -3]
[1., 6., 11., 6.], # 2: poles: [-1, -2, -3]
[1., 6., 11., 6.], # 3: poles: [-1, -2, -3]
[1., 6., 11., 6.]], # 4: poles: [-1, -2, -3],
[[1., 12., 47., 60.], # 0: poles: [-3, -4, -5]
[1., 9., 26., 24.], # 1: poles: [-2, -3, -4]
[1., 7., 16., 12.], # 2: poles: [-2, -2, -3]
[1., 7., 17., 15.], # 3: poles: [-2+1J, -2-1J, -3],
np.poly([-2 + tol_imag * 1J, -2 - tol_imag * 1J, -3])]]
numref = np.array([
[[0., 0., 1., 12., 47., 60.],
[0., 0., 0., 1., 4., 0.],
[0., 0., 0., 1., 2., 0.],
[0., 0., 0., 1., 4., 5.],
[0., 0., 0., 1., 2., 0.]],
[[0., 0., 0., 1., 3., 2.],
[0., 0., 0., 1., 1., 0.],
[0., 0., 0., 1., 1., 0.],
[0., 0., 0., 1., 3., 2.],
[0., 0., 0., 1., 1., 0.]]])
denref = np.array(
[[1., 15., 85., 225., 274., 120.],
[1., 10., 35., 50., 24., 0.],
[1., 8., 23., 28., 12., 0.],
[1., 10., 40., 80., 79., 30.],
[1., 8., 23., 28., 12., 0.]])
sys = TransferFunction(numin, denin)
num, den, denorder = sys._common_den()
np.testing.assert_array_almost_equal(num[:2, :, :], numref)
np.testing.assert_array_almost_equal(num[2:, :, :],
np.zeros((3, 5, 6)))
np.testing.assert_array_almost_equal(den, denref)
@unittest.skipIf(not slycot_check(), "slycot not installed")
def test_pole_mimo(self):
"""Test for correct MIMO poles."""
sys = TransferFunction(
[[[1.], [1.]], [[1.], [1.]]],
[[[1., 2.], [1., 3.]], [[1., 4., 4.], [1., 9., 14.]]])
p = sys.pole()
np.testing.assert_array_almost_equal(p, [-2., -2., -7., -3., -2.])
def test_double_cancelling_poles_siso(self):
H = TransferFunction([1, 1], [1, 2, 1])
p = H.pole()
np.testing.assert_array_almost_equal(p, [-1, -1])
# Tests for TransferFunction.feedback
def test_feedback_siso(self):
"""Test for correct SISO transfer function feedback."""
sys1 = TransferFunction([-1., 4.], [1., 3., 5.])
sys2 = TransferFunction([2., 3., 0.], [1., -3., 4., 0])
sys3 = sys1.feedback(sys2)
sys4 = sys1.feedback(sys2, 1)
np.testing.assert_array_equal(sys3.num, [[[-1., 7., -16., 16., 0.]]])
np.testing.assert_array_equal(sys3.den, [[[1., 0., -2., 2., 32., 0.]]])
np.testing.assert_array_equal(sys4.num, [[[-1., 7., -16., 16., 0.]]])
np.testing.assert_array_equal(sys4.den, [[[1., 0., 2., -8., 8., 0.]]])
@unittest.skipIf(not slycot_check(), "slycot not installed")
def test_convert_to_transfer_function(self):
"""Test for correct state space to transfer function conversion."""
A = [[1., -2.], [-3., 4.]]
B = [[6., 5.], [4., 3.]]
C = [[1., -2.], [3., -4.], [5., -6.]]
D = [[1., 0.], [0., 1.], [1., 0.]]
sys = StateSpace(A, B, C, D)
tfsys = _convert_to_transfer_function(sys)
num = [[np.array([1., -7., 10.]), np.array([-1., 10.])],
[np.array([2., -8.]), np.array([1., -2., -8.])],
[np.array([1., 1., -30.]), np.array([7., -22.])]]
den = [[np.array([1., -5., -2.]) for _ in range(sys.inputs)]
for _ in range(sys.outputs)]
for i in range(sys.outputs):
for j in range(sys.inputs):
np.testing.assert_array_almost_equal(tfsys.num[i][j], num[i][j])
np.testing.assert_array_almost_equal(tfsys.den[i][j], den[i][j])
def test_minreal(self):
"""Try the minreal function, and also test easy entry by creation
of a Laplace variable s"""
s = TransferFunction([1, 0], [1])
h = (s + 1) * (s + 2.00000000001) / (s + 2) / (s**2 + s + 1)
hm = h.minreal()
hr = (s + 1) / (s**2 + s + 1)
np.testing.assert_array_almost_equal(hm.num[0][0], hr.num[0][0])
np.testing.assert_array_almost_equal(hm.den[0][0], hr.den[0][0])
np.testing.assert_equal(hm.dt, hr.dt)
def test_minreal_2(self):
"""This one gave a problem, due to poly([]) giving simply 1
instead of numpy.array([1])"""
s = TransferFunction([1, 0], [1])
G = 6205/(s*(s**2 + 13*s + 1281))
Heq = G.feedback(1)
H1 = 1/(s+5)
H2a = Heq/H1
H2b = H2a.minreal()
hr = 6205/(s**2+8*s+1241)
np.testing.assert_array_almost_equal(H2b.num[0][0], hr.num[0][0])
np.testing.assert_array_almost_equal(H2b.den[0][0], hr.den[0][0])
np.testing.assert_equal(H2b.dt, hr.dt)
def test_minreal_3(self):
"""Regression test for minreal of tf([1,1],[1,1])"""
g = TransferFunction([1,1],[1,1]).minreal()
np.testing.assert_array_almost_equal(1.0, g.num[0][0])
np.testing.assert_array_almost_equal(1.0, g.den[0][0])
np.testing.assert_equal(None, g.dt)
def test_minreal_4(self):
"""Check minreal on discrete TFs."""
T = 0.01
z = TransferFunction([1, 0], [1], T)
h = (z - 1.00000000001) * (z + 1.0000000001) / (z**2 - 1)
hm = h.minreal()
hr = TransferFunction([1], [1], T)
np.testing.assert_array_almost_equal(hm.num[0][0], hr.num[0][0])
np.testing.assert_equal(hr.dt, hm.dt)
@unittest.skipIf(not slycot_check(), "slycot not installed")
def test_state_space_conversion_mimo(self):
"""Test conversion of a single input, two-output state-space
system against the same TF"""
s = TransferFunction([1, 0], [1])
b0 = 0.2
b1 = 0.1
b2 = 0.5
a0 = 2.3
a1 = 6.3
a2 = 3.6
a3 = 1.0
h = (b0 + b1*s + b2*s**2)/(a0 + a1*s + a2*s**2 + a3*s**3)
H = TransferFunction([[h.num[0][0]], [(h*s).num[0][0]]],
[[h.den[0][0]], [h.den[0][0]]])
sys = _convertToStateSpace(H)
H2 = _convert_to_transfer_function(sys)
np.testing.assert_array_almost_equal(H.num[0][0], H2.num[0][0])
np.testing.assert_array_almost_equal(H.den[0][0], H2.den[0][0])
np.testing.assert_array_almost_equal(H.num[1][0], H2.num[1][0])
np.testing.assert_array_almost_equal(H.den[1][0], H2.den[1][0])
@unittest.skipIf(not slycot_check(), "slycot not installed")
def test_indexing(self):
tm = ss2tf(rss(5, 3, 3))
# scalar indexing
sys01 = tm[0, 1]
np.testing.assert_array_almost_equal(sys01.num[0][0], tm.num[0][1])
np.testing.assert_array_almost_equal(sys01.den[0][0], tm.den[0][1])
# slice indexing
sys = tm[:2, 1:3]
np.testing.assert_array_almost_equal(sys.num[0][0], tm.num[0][1])
np.testing.assert_array_almost_equal(sys.den[0][0], tm.den[0][1])
np.testing.assert_array_almost_equal(sys.num[0][1], tm.num[0][2])
np.testing.assert_array_almost_equal(sys.den[0][1], tm.den[0][2])
np.testing.assert_array_almost_equal(sys.num[1][0], tm.num[1][1])
np.testing.assert_array_almost_equal(sys.den[1][0], tm.den[1][1])
np.testing.assert_array_almost_equal(sys.num[1][1], tm.num[1][2])
np.testing.assert_array_almost_equal(sys.den[1][1], tm.den[1][2])
def test_matrix_multiply(self):
"""MIMO transfer functions should be multiplyable by constant
matrices"""
s = TransferFunction([1, 0], [1])
b0 = 0.2
b1 = 0.1
b2 = 0.5
a0 = 2.3
a1 = 6.3
a2 = 3.6
a3 = 1.0
h = (b0 + b1*s + b2*s**2)/(a0 + a1*s + a2*s**2 + a3*s**3)
H = TransferFunction([[h.num[0][0]], [(h*s).num[0][0]]],
[[h.den[0][0]], [h.den[0][0]]])
H1 = (np.matrix([[1.0, 0]])*H).minreal()
H2 = (np.matrix([[0, 1.0]])*H).minreal()
np.testing.assert_array_almost_equal(H.num[0][0], H1.num[0][0])
np.testing.assert_array_almost_equal(H.den[0][0], H1.den[0][0])
np.testing.assert_array_almost_equal(H.num[1][0], H2.num[0][0])
np.testing.assert_array_almost_equal(H.den[1][0], H2.den[0][0])
def test_dcgain_cont(self):
"""Test DC gain for continuous-time transfer functions"""
sys = TransferFunction(6, 3)
np.testing.assert_equal(sys.dcgain(), 2)
sys2 = TransferFunction(6, [1, 3])
np.testing.assert_equal(sys2.dcgain(), 2)
sys3 = TransferFunction(6, [1, 0])
np.testing.assert_equal(sys3.dcgain(), np.inf)
num = [[[15], [21], [33]], [[10], [14], [22]]]
den = [[[1, 3], [2, 3], [3, 3]], [[1, 5], [2, 7], [3, 11]]]
sys4 = TransferFunction(num, den)
expected = [[5, 7, 11], [2, 2, 2]]
np.testing.assert_array_equal(sys4.dcgain(), expected)
def test_dcgain_discr(self):
"""Test DC gain for discrete-time transfer functions"""
# static gain
sys = TransferFunction(6, 3, True)
np.testing.assert_equal(sys.dcgain(), 2)
# averaging filter
sys = TransferFunction(0.5, [1, -0.5], True)
np.testing.assert_almost_equal(sys.dcgain(), 1)
# differencer
sys = TransferFunction(1, [1, -1], True)
np.testing.assert_equal(sys.dcgain(), np.inf)
# summer
# causes a RuntimeWarning due to the divide by zero
sys = TransferFunction([1, -1], [1], True)
np.testing.assert_equal(sys.dcgain(), 0)
def test_ss2tf(self):
A = np.array([[-4, -1], [-1, -4]])
B = np.array([[1], [3]])
C = np.array([[3, 1]])
D = 0
sys = ss2tf(A, B, C, D)
true_sys = TransferFunction([6., 14.], [1., 8., 15.])
np.testing.assert_almost_equal(sys.num, true_sys.num)
np.testing.assert_almost_equal(sys.den, true_sys.den)
def test_class_constants(self):
# Make sure that the 's' variable is defined properly
s = TransferFunction.s
G = (s + 1)/(s**2 + 2*s + 1)
np.testing.assert_array_almost_equal(G.num, [[[1, 1]]])
np.testing.assert_array_almost_equal(G.den, [[[1, 2, 1]]])
self.assertTrue(isctime(G, strict=True))
# Make sure that the 'z' variable is defined properly
z = TransferFunction.z
G = (z + 1)/(z**2 + 2*z + 1)
np.testing.assert_array_almost_equal(G.num, [[[1, 1]]])
np.testing.assert_array_almost_equal(G.den, [[[1, 2, 1]]])
self.assertTrue(isdtime(G, strict=True))
def test_printing(self):
# SISO, continuous time
sys = ss2tf(rss(4, 1, 1))
self.assertTrue(isinstance(str(sys), str))
self.assertTrue(isinstance(sys._repr_latex_(), str))
# SISO, discrete time
sys = sample_system(sys, 1)
self.assertTrue(isinstance(str(sys), str))
self.assertTrue(isinstance(sys._repr_latex_(), str))
@unittest.skipIf(not slycot_check(), "slycot not installed")
def test_printing_mimo(self):
# MIMO, continuous time
sys = ss2tf(rss(4, 2, 3))
self.assertTrue(isinstance(str(sys), str))
self.assertTrue(isinstance(sys._repr_latex_(), str))
@unittest.skipIf(not slycot_check(), "slycot not installed")
def test_size_mismatch(self):
sys1 = ss2tf(rss(2, 2, 2))
# Different number of inputs
sys2 = ss2tf(rss(3, 1, 2))
self.assertRaises(ValueError, TransferFunction.__add__, sys1, sys2)
# Different number of outputs
sys2 = ss2tf(rss(3, 2, 1))
self.assertRaises(ValueError, TransferFunction.__add__, sys1, sys2)
# Inputs and outputs don't match
self.assertRaises(ValueError, TransferFunction.__mul__, sys2, sys1)
# Feedback mismatch (MIMO not implemented)
self.assertRaises(NotImplementedError,
TransferFunction.feedback, sys2, sys1)
def test_latex_repr(self):
""" Test latex printout for TransferFunction """
Hc = TransferFunction([1e-5, 2e5, 3e-4],
[1.2e34, 2.3e-4, 2.3e-45])
Hd = TransferFunction([1e-5, 2e5, 3e-4],
[1.2e34, 2.3e-4, 2.3e-45],
.1)
# TODO: make the multiplication sign configurable
expmul = r'\times'
for var, H, suffix in zip(['s', 'z'],
[Hc, Hd],
['', r'\quad dt = 0.1']):
ref = (r'$$\frac{'
r'1 ' + expmul + ' 10^{-5} ' + var + '^2 '
r'+ 2 ' + expmul + ' 10^{5} ' + var + ' + 0.0003'
r'}{'
r'1.2 ' + expmul + ' 10^{34} ' + var + '^2 '
r'+ 0.00023 ' + var + ' '
r'+ 2.3 ' + expmul + ' 10^{-45}'
r'}' + suffix + '$$')
self.assertEqual(H._repr_latex_(), ref)
def suite():
return unittest.TestLoader().loadTestsFromTestCase(TestXferFcn)
if __name__ == "__main__":
unittest.main()