LSR/env/lib/python3.6/site-packages/control/tests/frd_test.py

423 lines
17 KiB
Python
Raw Normal View History

2020-06-04 17:24:47 +02:00
#!/usr/bin/env python
#
# frd_test.py - test FRD class
# RvP, 4 Oct 2012
import unittest
import sys as pysys
import numpy as np
import control as ct
from control.statesp import StateSpace
from control.xferfcn import TransferFunction
from control.frdata import FRD, _convertToFRD
from control import bdalg
from control import freqplot
from control.exception import slycot_check
import matplotlib.pyplot as plt
class TestFRD(unittest.TestCase):
"""These are tests for functionality and correct reporting of the
frequency response data class."""
def testBadInputType(self):
"""Give the constructor invalid input types."""
self.assertRaises(ValueError, FRD)
self.assertRaises(TypeError, FRD, [1])
def testInconsistentDimension(self):
self.assertRaises(TypeError, FRD, [1, 1], [1, 2, 3])
def testSISOtf(self):
# get a SISO transfer function
h = TransferFunction([1], [1, 2, 2])
omega = np.logspace(-1, 2, 10)
frd = FRD(h, omega)
assert isinstance(frd, FRD)
np.testing.assert_array_almost_equal(
frd.freqresp([1.0]), h.freqresp([1.0]))
def testOperators(self):
# get two SISO transfer functions
h1 = TransferFunction([1], [1, 2, 2])
h2 = TransferFunction([1], [0.1, 1])
omega = np.logspace(-1, 2, 10)
f1 = FRD(h1, omega)
f2 = FRD(h2, omega)
np.testing.assert_array_almost_equal(
(f1 + f2).freqresp([0.1, 1.0, 10])[0],
(h1 + h2).freqresp([0.1, 1.0, 10])[0])
np.testing.assert_array_almost_equal(
(f1 + f2).freqresp([0.1, 1.0, 10])[1],
(h1 + h2).freqresp([0.1, 1.0, 10])[1])
np.testing.assert_array_almost_equal(
(f1 - f2).freqresp([0.1, 1.0, 10])[0],
(h1 - h2).freqresp([0.1, 1.0, 10])[0])
np.testing.assert_array_almost_equal(
(f1 - f2).freqresp([0.1, 1.0, 10])[1],
(h1 - h2).freqresp([0.1, 1.0, 10])[1])
# multiplication and division
np.testing.assert_array_almost_equal(
(f1 * f2).freqresp([0.1, 1.0, 10])[1],
(h1 * h2).freqresp([0.1, 1.0, 10])[1])
np.testing.assert_array_almost_equal(
(f1 / f2).freqresp([0.1, 1.0, 10])[1],
(h1 / h2).freqresp([0.1, 1.0, 10])[1])
# with default conversion from scalar
np.testing.assert_array_almost_equal(
(f1 * 1.5).freqresp([0.1, 1.0, 10])[1],
(h1 * 1.5).freqresp([0.1, 1.0, 10])[1])
np.testing.assert_array_almost_equal(
(f1 / 1.7).freqresp([0.1, 1.0, 10])[1],
(h1 / 1.7).freqresp([0.1, 1.0, 10])[1])
np.testing.assert_array_almost_equal(
(2.2 * f2).freqresp([0.1, 1.0, 10])[1],
(2.2 * h2).freqresp([0.1, 1.0, 10])[1])
np.testing.assert_array_almost_equal(
(1.3 / f2).freqresp([0.1, 1.0, 10])[1],
(1.3 / h2).freqresp([0.1, 1.0, 10])[1])
def testOperatorsTf(self):
# get two SISO transfer functions
h1 = TransferFunction([1], [1, 2, 2])
h2 = TransferFunction([1], [0.1, 1])
omega = np.logspace(-1, 2, 10)
f1 = FRD(h1, omega)
f2 = FRD(h2, omega)
f2 # reference to avoid pyflakes error
np.testing.assert_array_almost_equal(
(f1 + h2).freqresp([0.1, 1.0, 10])[0],
(h1 + h2).freqresp([0.1, 1.0, 10])[0])
np.testing.assert_array_almost_equal(
(f1 + h2).freqresp([0.1, 1.0, 10])[1],
(h1 + h2).freqresp([0.1, 1.0, 10])[1])
np.testing.assert_array_almost_equal(
(f1 - h2).freqresp([0.1, 1.0, 10])[0],
(h1 - h2).freqresp([0.1, 1.0, 10])[0])
np.testing.assert_array_almost_equal(
(f1 - h2).freqresp([0.1, 1.0, 10])[1],
(h1 - h2).freqresp([0.1, 1.0, 10])[1])
# multiplication and division
np.testing.assert_array_almost_equal(
(f1 * h2).freqresp([0.1, 1.0, 10])[1],
(h1 * h2).freqresp([0.1, 1.0, 10])[1])
np.testing.assert_array_almost_equal(
(f1 / h2).freqresp([0.1, 1.0, 10])[1],
(h1 / h2).freqresp([0.1, 1.0, 10])[1])
# the reverse does not work
def testbdalg(self):
# get two SISO transfer functions
h1 = TransferFunction([1], [1, 2, 2])
h2 = TransferFunction([1], [0.1, 1])
omega = np.logspace(-1, 2, 10)
f1 = FRD(h1, omega)
f2 = FRD(h2, omega)
np.testing.assert_array_almost_equal(
(bdalg.series(f1, f2)).freqresp([0.1, 1.0, 10])[0],
(bdalg.series(h1, h2)).freqresp([0.1, 1.0, 10])[0])
np.testing.assert_array_almost_equal(
(bdalg.parallel(f1, f2)).freqresp([0.1, 1.0, 10])[0],
(bdalg.parallel(h1, h2)).freqresp([0.1, 1.0, 10])[0])
np.testing.assert_array_almost_equal(
(bdalg.feedback(f1, f2)).freqresp([0.1, 1.0, 10])[0],
(bdalg.feedback(h1, h2)).freqresp([0.1, 1.0, 10])[0])
np.testing.assert_array_almost_equal(
(bdalg.negate(f1)).freqresp([0.1, 1.0, 10])[0],
(bdalg.negate(h1)).freqresp([0.1, 1.0, 10])[0])
# append() and connect() not implemented for FRD objects
# np.testing.assert_array_almost_equal(
# (bdalg.append(f1, f2)).freqresp([0.1, 1.0, 10])[0],
# (bdalg.append(h1, h2)).freqresp([0.1, 1.0, 10])[0])
#
# f3 = bdalg.append(f1, f2, f2)
# h3 = bdalg.append(h1, h2, h2)
# Q = np.mat([ [1, 2], [2, -1] ])
# np.testing.assert_array_almost_equal(
# (bdalg.connect(f3, Q, [2], [1])).freqresp([0.1, 1.0, 10])[0],
# (bdalg.connect(h3, Q, [2], [1])).freqresp([0.1, 1.0, 10])[0])
def testFeedback(self):
h1 = TransferFunction([1], [1, 2, 2])
omega = np.logspace(-1, 2, 10)
f1 = FRD(h1, omega)
np.testing.assert_array_almost_equal(
f1.feedback(1).freqresp([0.1, 1.0, 10])[0],
h1.feedback(1).freqresp([0.1, 1.0, 10])[0])
# Make sure default argument also works
np.testing.assert_array_almost_equal(
f1.feedback().freqresp([0.1, 1.0, 10])[0],
h1.feedback().freqresp([0.1, 1.0, 10])[0])
def testFeedback2(self):
h2 = StateSpace([[-1.0, 0], [0, -2.0]], [[0.4], [0.1]],
[[1.0, 0], [0, 1]], [[0.0], [0.0]])
# h2.feedback([[0.3, 0.2], [0.1, 0.1]])
def testAuto(self):
omega = np.logspace(-1, 2, 10)
f1 = _convertToFRD(1, omega)
f2 = _convertToFRD(np.matrix([[1, 0], [0.1, -1]]), omega)
f2 = _convertToFRD([[1, 0], [0.1, -1]], omega)
f1, f2 # reference to avoid pyflakes error
def testNyquist(self):
h1 = TransferFunction([1], [1, 2, 2])
omega = np.logspace(-1, 2, 40)
f1 = FRD(h1, omega, smooth=True)
freqplot.nyquist(f1, np.logspace(-1, 2, 100))
# plt.savefig('/dev/null', format='svg')
plt.figure(2)
freqplot.nyquist(f1, f1.omega)
# plt.savefig('/dev/null', format='svg')
@unittest.skipIf(not slycot_check(), "slycot not installed")
def testMIMO(self):
sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]],
[[1.0, 0.0], [0.0, 1.0]],
[[1.0, 0.0], [0.0, 1.0]],
[[0.0, 0.0], [0.0, 0.0]])
omega = np.logspace(-1, 2, 10)
f1 = FRD(sys, omega)
np.testing.assert_array_almost_equal(
sys.freqresp([0.1, 1.0, 10])[0],
f1.freqresp([0.1, 1.0, 10])[0])
np.testing.assert_array_almost_equal(
sys.freqresp([0.1, 1.0, 10])[1],
f1.freqresp([0.1, 1.0, 10])[1])
@unittest.skipIf(not slycot_check(), "slycot not installed")
def testMIMOfb(self):
sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]],
[[1.0, 0.0], [0.0, 1.0]],
[[1.0, 0.0], [0.0, 1.0]],
[[0.0, 0.0], [0.0, 0.0]])
omega = np.logspace(-1, 2, 10)
f1 = FRD(sys, omega).feedback([[0.1, 0.3], [0.0, 1.0]])
f2 = FRD(sys.feedback([[0.1, 0.3], [0.0, 1.0]]), omega)
np.testing.assert_array_almost_equal(
f1.freqresp([0.1, 1.0, 10])[0],
f2.freqresp([0.1, 1.0, 10])[0])
np.testing.assert_array_almost_equal(
f1.freqresp([0.1, 1.0, 10])[1],
f2.freqresp([0.1, 1.0, 10])[1])
@unittest.skipIf(not slycot_check(), "slycot not installed")
def testMIMOfb2(self):
sys = StateSpace(np.matrix('-2.0 0 0; 0 -1 1; 0 0 -3'),
np.matrix('1.0 0; 0 0; 0 1'),
np.eye(3), np.zeros((3, 2)))
omega = np.logspace(-1, 2, 10)
K = np.matrix('1 0.3 0; 0.1 0 0')
f1 = FRD(sys, omega).feedback(K)
f2 = FRD(sys.feedback(K), omega)
np.testing.assert_array_almost_equal(
f1.freqresp([0.1, 1.0, 10])[0],
f2.freqresp([0.1, 1.0, 10])[0])
np.testing.assert_array_almost_equal(
f1.freqresp([0.1, 1.0, 10])[1],
f2.freqresp([0.1, 1.0, 10])[1])
@unittest.skipIf(not slycot_check(), "slycot not installed")
def testMIMOMult(self):
sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]],
[[1.0, 0.0], [0.0, 1.0]],
[[1.0, 0.0], [0.0, 1.0]],
[[0.0, 0.0], [0.0, 0.0]])
omega = np.logspace(-1, 2, 10)
f1 = FRD(sys, omega)
f2 = FRD(sys, omega)
np.testing.assert_array_almost_equal(
(f1*f2).freqresp([0.1, 1.0, 10])[0],
(sys*sys).freqresp([0.1, 1.0, 10])[0])
np.testing.assert_array_almost_equal(
(f1*f2).freqresp([0.1, 1.0, 10])[1],
(sys*sys).freqresp([0.1, 1.0, 10])[1])
@unittest.skipIf(not slycot_check(), "slycot not installed")
def testMIMOSmooth(self):
sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]],
[[1.0, 0.0], [0.0, 1.0]],
[[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]],
[[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]])
sys2 = np.matrix([[1, 0, 0], [0, 1, 0]]) * sys
omega = np.logspace(-1, 2, 10)
f1 = FRD(sys, omega, smooth=True)
f2 = FRD(sys2, omega, smooth=True)
np.testing.assert_array_almost_equal(
(f1*f2).freqresp([0.1, 1.0, 10])[0],
(sys*sys2).freqresp([0.1, 1.0, 10])[0])
np.testing.assert_array_almost_equal(
(f1*f2).freqresp([0.1, 1.0, 10])[1],
(sys*sys2).freqresp([0.1, 1.0, 10])[1])
np.testing.assert_array_almost_equal(
(f1*f2).freqresp([0.1, 1.0, 10])[2],
(sys*sys2).freqresp([0.1, 1.0, 10])[2])
def testAgainstOctave(self):
# with data from octave:
# sys = ss([-2 0 0; 0 -1 1; 0 0 -3],
# [1 0; 0 0; 0 1], eye(3), zeros(3,2))
# bfr = frd(bsys, [1])
sys = StateSpace(np.matrix('-2.0 0 0; 0 -1 1; 0 0 -3'),
np.matrix('1.0 0; 0 0; 0 1'),
np.eye(3), np.zeros((3, 2)))
omega = np.logspace(-1, 2, 10)
f1 = FRD(sys, omega)
np.testing.assert_array_almost_equal(
(f1.freqresp([1.0])[0] *
np.exp(1j*f1.freqresp([1.0])[1])).reshape(3, 2),
np.matrix('0.4-0.2j 0; 0 0.1-0.2j; 0 0.3-0.1j'))
def test_string_representation(self):
sys = FRD([1, 2, 3], [4, 5, 6])
print(sys) # Just print without checking
def test_frequency_mismatch(self):
# Overlapping but non-equal frequency ranges
sys1 = FRD([1, 2, 3], [4, 5, 6])
sys2 = FRD([2, 3, 4], [5, 6, 7])
self.assertRaises(NotImplementedError, FRD.__add__, sys1, sys2)
# One frequency range is a subset of another
sys1 = FRD([1, 2, 3], [4, 5, 6])
sys2 = FRD([2, 3], [4, 5])
self.assertRaises(NotImplementedError, FRD.__add__, sys1, sys2)
def test_size_mismatch(self):
sys1 = FRD(ct.rss(2, 2, 2), np.logspace(-1, 1, 10))
# Different number of inputs
sys2 = FRD(ct.rss(3, 1, 2), np.logspace(-1, 1, 10))
self.assertRaises(ValueError, FRD.__add__, sys1, sys2)
# Different number of outputs
sys2 = FRD(ct.rss(3, 2, 1), np.logspace(-1, 1, 10))
self.assertRaises(ValueError, FRD.__add__, sys1, sys2)
# Inputs and outputs don't match
self.assertRaises(ValueError, FRD.__mul__, sys2, sys1)
# Feedback mismatch
self.assertRaises(ValueError, FRD.feedback, sys2, sys1)
def test_operator_conversion(self):
sys_tf = ct.tf([1], [1, 2, 1])
frd_tf = FRD(sys_tf, np.logspace(-1, 1, 10))
frd_2 = FRD(2 * np.ones(10), np.logspace(-1, 1, 10))
# Make sure that we can add, multiply, and feedback constants
sys_add = frd_tf + 2
chk_add = frd_tf + frd_2
np.testing.assert_array_almost_equal(sys_add.omega, chk_add.omega)
np.testing.assert_array_almost_equal(sys_add.fresp, chk_add.fresp)
sys_radd = 2 + frd_tf
chk_radd = frd_2 + frd_tf
np.testing.assert_array_almost_equal(sys_radd.omega, chk_radd.omega)
np.testing.assert_array_almost_equal(sys_radd.fresp, chk_radd.fresp)
sys_sub = frd_tf - 2
chk_sub = frd_tf - frd_2
np.testing.assert_array_almost_equal(sys_sub.omega, chk_sub.omega)
np.testing.assert_array_almost_equal(sys_sub.fresp, chk_sub.fresp)
sys_rsub = 2 - frd_tf
chk_rsub = frd_2 - frd_tf
np.testing.assert_array_almost_equal(sys_rsub.omega, chk_rsub.omega)
np.testing.assert_array_almost_equal(sys_rsub.fresp, chk_rsub.fresp)
sys_mul = frd_tf * 2
chk_mul = frd_tf * frd_2
np.testing.assert_array_almost_equal(sys_mul.omega, chk_mul.omega)
np.testing.assert_array_almost_equal(sys_mul.fresp, chk_mul.fresp)
sys_rmul = 2 * frd_tf
chk_rmul = frd_2 * frd_tf
np.testing.assert_array_almost_equal(sys_rmul.omega, chk_rmul.omega)
np.testing.assert_array_almost_equal(sys_rmul.fresp, chk_rmul.fresp)
sys_rdiv = 2 / frd_tf
chk_rdiv = frd_2 / frd_tf
np.testing.assert_array_almost_equal(sys_rdiv.omega, chk_rdiv.omega)
np.testing.assert_array_almost_equal(sys_rdiv.fresp, chk_rdiv.fresp)
sys_pow = frd_tf**2
chk_pow = FRD(sys_tf**2, np.logspace(-1, 1, 10))
np.testing.assert_array_almost_equal(sys_pow.omega, chk_pow.omega)
np.testing.assert_array_almost_equal(sys_pow.fresp, chk_pow.fresp)
sys_pow = frd_tf**-2
chk_pow = FRD(sys_tf**-2, np.logspace(-1, 1, 10))
np.testing.assert_array_almost_equal(sys_pow.omega, chk_pow.omega)
np.testing.assert_array_almost_equal(sys_pow.fresp, chk_pow.fresp)
# Assertion error if we try to raise to a non-integer power
self.assertRaises(ValueError, FRD.__pow__, frd_tf, 0.5)
# Selected testing on transfer function conversion
sys_add = frd_2 + sys_tf
chk_add = frd_2 + frd_tf
np.testing.assert_array_almost_equal(sys_add.omega, chk_add.omega)
np.testing.assert_array_almost_equal(sys_add.fresp, chk_add.fresp)
# Input/output mismatch size mismatch in rmul
sys1 = FRD(ct.rss(2, 2, 2), np.logspace(-1, 1, 10))
self.assertRaises(ValueError, FRD.__rmul__, frd_2, sys1)
# Make sure conversion of something random generates exception
self.assertRaises(TypeError, FRD.__add__, frd_tf, 'string')
def test_eval(self):
sys_tf = ct.tf([1], [1, 2, 1])
frd_tf = FRD(sys_tf, np.logspace(-1, 1, 3))
np.testing.assert_almost_equal(sys_tf.evalfr(1), frd_tf.eval(1))
# Should get an error if we evaluate at an unknown frequency
self.assertRaises(ValueError, frd_tf.eval, 2)
# 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_tf = ct.tf([1], [1, 2, 1])
frd_tf = FRD(sys_tf, np.logspace(-1, 1, 3))
# 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, frd_tf.evalfr, 1.)
# FRD.evalfr() is being deprecated
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, frd_tf.evalfr, 1.)
def suite():
return unittest.TestLoader().loadTestsFromTestCase(TestFRD)
if __name__ == "__main__":
unittest.main()