423 lines
17 KiB
Python
423 lines
17 KiB
Python
#!/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()
|