570 lines
24 KiB
Python
570 lines
24 KiB
Python
#!/usr/bin/env python
|
|
#
|
|
# timeresp_test.py - test time response functions
|
|
# RMM, 17 Jun 2011 (based on TestMatlab from v0.4c)
|
|
#
|
|
# This test suite just goes through and calls all of the MATLAB
|
|
# functions using different systems and arguments to make sure that
|
|
# nothing crashes. It doesn't test actual functionality; the module
|
|
# specific unit tests will do that.
|
|
|
|
import unittest
|
|
import numpy as np
|
|
from control.timeresp import *
|
|
from control.statesp import *
|
|
from control.xferfcn import TransferFunction, _convert_to_transfer_function
|
|
from control.dtime import c2d
|
|
from control.exception import slycot_check
|
|
|
|
class TestTimeresp(unittest.TestCase):
|
|
def setUp(self):
|
|
"""Set up some systems for testing out MATLAB functions"""
|
|
A = np.matrix("1. -2.; 3. -4.")
|
|
B = np.matrix("5.; 7.")
|
|
C = np.matrix("6. 8.")
|
|
D = np.matrix("9.")
|
|
self.siso_ss1 = StateSpace(A, B, C, D)
|
|
|
|
# Create some transfer functions
|
|
self.siso_tf1 = TransferFunction([1], [1, 2, 1])
|
|
self.siso_tf2 = _convert_to_transfer_function(self.siso_ss1)
|
|
|
|
# Create MIMO system, contains ``siso_ss1`` twice
|
|
A = np.matrix("1. -2. 0. 0.;"
|
|
"3. -4. 0. 0.;"
|
|
"0. 0. 1. -2.;"
|
|
"0. 0. 3. -4. ")
|
|
B = np.matrix("5. 0.;"
|
|
"7. 0.;"
|
|
"0. 5.;"
|
|
"0. 7. ")
|
|
C = np.matrix("6. 8. 0. 0.;"
|
|
"0. 0. 6. 8. ")
|
|
D = np.matrix("9. 0.;"
|
|
"0. 9. ")
|
|
self.mimo_ss1 = StateSpace(A, B, C, D)
|
|
|
|
# Create discrete time systems
|
|
self.siso_dtf1 = TransferFunction([1], [1, 1, 0.25], True)
|
|
self.siso_dtf2 = TransferFunction([1], [1, 1, 0.25], 0.2)
|
|
self.siso_dss1 = tf2ss(self.siso_dtf1)
|
|
self.siso_dss2 = tf2ss(self.siso_dtf2)
|
|
self.mimo_dss1 = StateSpace(A, B, C, D, True)
|
|
self.mimo_dss2 = c2d(self.mimo_ss1, 0.2)
|
|
|
|
def test_step_response(self):
|
|
# Test SISO system
|
|
sys = self.siso_ss1
|
|
t = np.linspace(0, 1, 10)
|
|
youttrue = np.array([9., 17.6457, 24.7072, 30.4855, 35.2234, 39.1165,
|
|
42.3227, 44.9694, 47.1599, 48.9776])
|
|
|
|
# SISO call
|
|
tout, yout = step_response(sys, T=t)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(tout, t)
|
|
|
|
# Play with arguments
|
|
tout, yout = step_response(sys, T=t, X0=0)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(tout, t)
|
|
|
|
X0 = np.array([0, 0])
|
|
tout, yout = step_response(sys, T=t, X0=X0)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(tout, t)
|
|
|
|
tout, yout, xout = step_response(sys, T=t, X0=0, return_x=True)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(tout, t)
|
|
|
|
# Test MIMO system, which contains ``siso_ss1`` twice
|
|
sys = self.mimo_ss1
|
|
_t, y_00 = step_response(sys, T=t, input=0, output=0)
|
|
_t, y_11 = step_response(sys, T=t, input=1, output=1)
|
|
np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4)
|
|
|
|
# Make sure continuous and discrete time use same return conventions
|
|
sysc = self.mimo_ss1
|
|
sysd = c2d(sysc, 1) # discrete time system
|
|
Tvec = np.linspace(0, 10, 11) # make sure to use integer times 0..10
|
|
Tc, youtc = step_response(sysc, Tvec, input=0)
|
|
Td, youtd = step_response(sysd, Tvec, input=0)
|
|
np.testing.assert_array_equal(Tc.shape, Td.shape)
|
|
np.testing.assert_array_equal(youtc.shape, youtd.shape)
|
|
|
|
def test_step_info(self):
|
|
# From matlab docs:
|
|
sys = TransferFunction([1,5,5],[1,1.65,5,6.5,2])
|
|
Strue = {
|
|
'RiseTime': 3.8456,
|
|
'SettlingTime': 27.9762,
|
|
'SettlingMin': 2.0689,
|
|
'SettlingMax': 2.6873,
|
|
'Overshoot': 7.4915,
|
|
'Undershoot': 0,
|
|
'Peak': 2.6873,
|
|
'PeakTime': 8.0530
|
|
}
|
|
|
|
S = step_info(sys)
|
|
|
|
# Very arbitrary tolerance because I don't know if the
|
|
# response from the MATLAB is really that accurate.
|
|
# maybe it is a good idea to change the Strue to match
|
|
# but I didn't do it because I don't know if it is
|
|
# accurate either...
|
|
rtol = 2e-2
|
|
np.testing.assert_allclose(
|
|
S.get('RiseTime'),
|
|
Strue.get('RiseTime'),
|
|
rtol=rtol)
|
|
np.testing.assert_allclose(
|
|
S.get('SettlingTime'),
|
|
Strue.get('SettlingTime'),
|
|
rtol=rtol)
|
|
np.testing.assert_allclose(
|
|
S.get('SettlingMin'),
|
|
Strue.get('SettlingMin'),
|
|
rtol=rtol)
|
|
np.testing.assert_allclose(
|
|
S.get('SettlingMax'),
|
|
Strue.get('SettlingMax'),
|
|
rtol=rtol)
|
|
np.testing.assert_allclose(
|
|
S.get('Overshoot'),
|
|
Strue.get('Overshoot'),
|
|
rtol=rtol)
|
|
np.testing.assert_allclose(
|
|
S.get('Undershoot'),
|
|
Strue.get('Undershoot'),
|
|
rtol=rtol)
|
|
np.testing.assert_allclose(
|
|
S.get('Peak'),
|
|
Strue.get('Peak'),
|
|
rtol=rtol)
|
|
np.testing.assert_allclose(
|
|
S.get('PeakTime'),
|
|
Strue.get('PeakTime'),
|
|
rtol=rtol)
|
|
np.testing.assert_allclose(
|
|
S.get('SteadyStateValue'),
|
|
2.50,
|
|
rtol=rtol)
|
|
|
|
def test_impulse_response(self):
|
|
# Test SISO system
|
|
sys = self.siso_ss1
|
|
t = np.linspace(0, 1, 10)
|
|
youttrue = np.array([86., 70.1808, 57.3753, 46.9975, 38.5766, 31.7344,
|
|
26.1668, 21.6292, 17.9245, 14.8945])
|
|
tout, yout = impulse_response(sys, T=t)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(tout, t)
|
|
|
|
# Play with arguments
|
|
tout, yout = impulse_response(sys, T=t, X0=0)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(tout, t)
|
|
|
|
X0 = np.array([0, 0])
|
|
tout, yout = impulse_response(sys, T=t, X0=X0)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(tout, t)
|
|
|
|
tout, yout, xout = impulse_response(sys, T=t, X0=0, return_x=True)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(tout, t)
|
|
|
|
# Test MIMO system, which contains ``siso_ss1`` twice
|
|
sys = self.mimo_ss1
|
|
_t, y_00 = impulse_response(sys, T=t, input=0, output=0)
|
|
_t, y_11 = impulse_response(sys, T=t, input=1, output=1)
|
|
np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4)
|
|
|
|
# Test MIMO system, as mimo, and don't trim outputs
|
|
sys = self.mimo_ss1
|
|
_t, yy = impulse_response(sys, T=t, input=0)
|
|
np.testing.assert_array_almost_equal(
|
|
yy, np.vstack((youttrue, np.zeros_like(youttrue))), decimal=4)
|
|
|
|
def test_initial_response(self):
|
|
# Test SISO system
|
|
sys = self.siso_ss1
|
|
t = np.linspace(0, 1, 10)
|
|
x0 = np.array([[0.5], [1]])
|
|
youttrue = np.array([11., 8.1494, 5.9361, 4.2258, 2.9118, 1.9092,
|
|
1.1508, 0.5833, 0.1645, -0.1391])
|
|
tout, yout = initial_response(sys, T=t, X0=x0)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(tout, t)
|
|
|
|
# Play with arguments
|
|
tout, yout, xout = initial_response(sys, T=t, X0=x0, return_x=True)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(tout, t)
|
|
|
|
# Test MIMO system, which contains ``siso_ss1`` twice
|
|
sys = self.mimo_ss1
|
|
x0 = np.matrix(".5; 1.; .5; 1.")
|
|
_t, y_00 = initial_response(sys, T=t, X0=x0, input=0, output=0)
|
|
_t, y_11 = initial_response(sys, T=t, X0=x0, input=1, output=1)
|
|
np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4)
|
|
|
|
def test_initial_response_no_trim(self):
|
|
# test MIMO system without trimming
|
|
t = np.linspace(0, 1, 10)
|
|
x0 = np.matrix(".5; 1.; .5; 1.")
|
|
youttrue = np.array([11., 8.1494, 5.9361, 4.2258, 2.9118, 1.9092,
|
|
1.1508, 0.5833, 0.1645, -0.1391])
|
|
sys = self.mimo_ss1
|
|
_t, yy = initial_response(sys, T=t, X0=x0)
|
|
np.testing.assert_array_almost_equal(
|
|
yy, np.vstack((youttrue, youttrue)),
|
|
decimal=4)
|
|
|
|
def test_forced_response(self):
|
|
t = np.linspace(0, 1, 10)
|
|
|
|
# compute step response - test with state space, and transfer function
|
|
# objects
|
|
u = np.array([1., 1, 1, 1, 1, 1, 1, 1, 1, 1])
|
|
youttrue = np.array([9., 17.6457, 24.7072, 30.4855, 35.2234, 39.1165,
|
|
42.3227, 44.9694, 47.1599, 48.9776])
|
|
tout, yout, _xout = forced_response(self.siso_ss1, t, u)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
np.testing.assert_array_almost_equal(tout, t)
|
|
_t, yout, _xout = forced_response(self.siso_tf2, t, u)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
|
|
# test with initial value and special algorithm for ``U=0``
|
|
u = 0
|
|
x0 = np.matrix(".5; 1.")
|
|
youttrue = np.array([11., 8.1494, 5.9361, 4.2258, 2.9118, 1.9092,
|
|
1.1508, 0.5833, 0.1645, -0.1391])
|
|
_t, yout, _xout = forced_response(self.siso_ss1, t, u, x0)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
|
|
# Test MIMO system, which contains ``siso_ss1`` twice
|
|
# first system: initial value, second system: step response
|
|
u = np.array([[0., 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[1., 1, 1, 1, 1, 1, 1, 1, 1, 1]])
|
|
x0 = np.array([[.5], [1], [0], [0]])
|
|
youttrue = np.array([[11., 8.1494, 5.9361, 4.2258, 2.9118, 1.9092,
|
|
1.1508, 0.5833, 0.1645, -0.1391],
|
|
[9., 17.6457, 24.7072, 30.4855, 35.2234, 39.1165,
|
|
42.3227, 44.9694, 47.1599, 48.9776]])
|
|
_t, yout, _xout = forced_response(self.mimo_ss1, t, u, x0)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
|
|
# Test discrete MIMO system to use correct convention for input
|
|
sysc = self.mimo_ss1
|
|
dt=t[1]-t[0]
|
|
sysd = c2d(sysc, dt) # discrete time system
|
|
Tc, youtc, _xoutc = forced_response(sysc, t, u, x0)
|
|
Td, youtd, _xoutd = forced_response(sysd, t, u, x0)
|
|
np.testing.assert_array_equal(Tc.shape, Td.shape)
|
|
np.testing.assert_array_equal(youtc.shape, youtd.shape)
|
|
np.testing.assert_array_almost_equal(youtc, youtd, decimal=4)
|
|
|
|
# Test discrete MIMO system without default T argument
|
|
u = np.array([[0., 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
[1., 1, 1, 1, 1, 1, 1, 1, 1, 1]])
|
|
x0 = np.array([[.5], [1], [0], [0]])
|
|
youttrue = np.array([[11., 8.1494, 5.9361, 4.2258, 2.9118, 1.9092,
|
|
1.1508, 0.5833, 0.1645, -0.1391],
|
|
[9., 17.6457, 24.7072, 30.4855, 35.2234, 39.1165,
|
|
42.3227, 44.9694, 47.1599, 48.9776]])
|
|
_t, yout, _xout = forced_response(sysd, U=u, X0=x0)
|
|
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
|
|
|
|
def test_lsim_double_integrator(self):
|
|
# Note: scipy.signal.lsim fails if A is not invertible
|
|
A = np.mat("0. 1.;0. 0.")
|
|
B = np.mat("0.; 1.")
|
|
C = np.mat("1. 0.")
|
|
D = 0.
|
|
sys = StateSpace(A, B, C, D)
|
|
|
|
def check(u, x0, xtrue):
|
|
_t, yout, xout = forced_response(sys, t, u, x0)
|
|
np.testing.assert_array_almost_equal(xout, xtrue, decimal=6)
|
|
ytrue = np.squeeze(np.asarray(C.dot(xtrue)))
|
|
np.testing.assert_array_almost_equal(yout, ytrue, decimal=6)
|
|
|
|
# test with zero input
|
|
npts = 10
|
|
t = np.linspace(0, 1, npts)
|
|
u = np.zeros_like(t)
|
|
x0 = np.array([2., 3.])
|
|
xtrue = np.zeros((2, npts))
|
|
xtrue[0, :] = x0[0] + t * x0[1]
|
|
xtrue[1, :] = x0[1]
|
|
check(u, x0, xtrue)
|
|
|
|
# test with step input
|
|
u = np.ones_like(t)
|
|
xtrue = np.array([0.5 * t**2, t])
|
|
x0 = np.array([0., 0.])
|
|
check(u, x0, xtrue)
|
|
|
|
# test with linear input
|
|
u = t
|
|
xtrue = np.array([1./6. * t**3, 0.5 * t**2])
|
|
check(u, x0, xtrue)
|
|
|
|
def test_discrete_initial(self):
|
|
h1 = TransferFunction([1.], [1., 0.], 1.)
|
|
t, yout = impulse_response(h1, np.arange(4))
|
|
np.testing.assert_array_equal(yout, [0., 1., 0., 0.])
|
|
|
|
@unittest.skipIf(not slycot_check(), "slycot not installed")
|
|
def test_step_robustness(self):
|
|
"Unit test: https://github.com/python-control/python-control/issues/240"
|
|
# Create 2 input, 2 output system
|
|
num = [ [[0], [1]], [[1], [0]] ]
|
|
|
|
den1 = [ [[1], [1,1]], [[1,4], [1]] ]
|
|
sys1 = TransferFunction(num, den1)
|
|
|
|
den2 = [ [[1], [1e-10, 1, 1]], [[1,4], [1]] ] # slight perturbation
|
|
sys2 = TransferFunction(num, den2)
|
|
|
|
# Compute step response from input 1 to output 1, 2
|
|
t1, y1 = step_response(sys1, input=0)
|
|
t2, y2 = step_response(sys2, input=0)
|
|
np.testing.assert_array_almost_equal(y1, y2)
|
|
|
|
def test_time_vector(self):
|
|
"Unit test: https://github.com/python-control/python-control/issues/239"
|
|
# Discrete time simulations with specified time vectors
|
|
Tin1 = np.arange(0, 5, 1) # matches dtf1, dss1; multiple of 0.2
|
|
Tin2 = np.arange(0, 5, 0.2) # matches dtf2, dss2
|
|
Tin3 = np.arange(0, 5, 0.5) # incompatible with 0.2
|
|
|
|
# Initial conditions to use for the different systems
|
|
siso_x0 = [1, 2]
|
|
mimo_x0 = [1, 2, 3, 4]
|
|
|
|
#
|
|
# Easy cases: make sure that output sample time matches input
|
|
#
|
|
# No timebase in system => output should match input
|
|
#
|
|
# Initial response
|
|
tout, yout = initial_response(self.siso_dtf1, Tin2, siso_x0,
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin2)
|
|
|
|
# Impulse response
|
|
tout, yout = impulse_response(self.siso_dtf1, Tin2,
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin2)
|
|
|
|
# Step response
|
|
tout, yout = step_response(self.siso_dtf1, Tin2,
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin2)
|
|
|
|
# Forced response with specified time vector
|
|
tout, yout, xout = forced_response(self.siso_dtf1, Tin2, np.sin(Tin2),
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin2)
|
|
|
|
# Forced response with no time vector, no sample time (should use 1)
|
|
tout, yout, xout = forced_response(self.siso_dtf1, None, np.sin(Tin1),
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin1)
|
|
|
|
# MIMO forced response
|
|
tout, yout, xout = forced_response(self.mimo_dss1, Tin1,
|
|
(np.sin(Tin1), np.cos(Tin1)),
|
|
mimo_x0)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
self.assertEqual(np.shape(tout), np.shape(yout[1,:]))
|
|
np.testing.assert_array_equal(tout, Tin1)
|
|
|
|
# Matching timebase in system => output should match input
|
|
#
|
|
# Initial response
|
|
tout, yout = initial_response(self.siso_dtf2, Tin2, siso_x0,
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin2)
|
|
|
|
# Impulse response
|
|
tout, yout = impulse_response(self.siso_dtf2, Tin2,
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin2)
|
|
|
|
# Step response
|
|
tout, yout = step_response(self.siso_dtf2, Tin2,
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin2)
|
|
|
|
# Forced response
|
|
tout, yout, xout = forced_response(self.siso_dtf2, Tin2, np.sin(Tin2),
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin2)
|
|
|
|
# Forced response with no time vector, use sample time
|
|
tout, yout, xout = forced_response(self.siso_dtf2, None, np.sin(Tin2),
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin2)
|
|
|
|
# Compatible timebase in system => output should match input
|
|
#
|
|
# Initial response
|
|
tout, yout = initial_response(self.siso_dtf2, Tin1, siso_x0,
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin1)
|
|
|
|
# Impulse response
|
|
tout, yout = impulse_response(self.siso_dtf2, Tin1,
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin1)
|
|
|
|
# Step response
|
|
tout, yout = step_response(self.siso_dtf2, Tin1,
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin1)
|
|
|
|
# Forced response
|
|
tout, yout, xout = forced_response(self.siso_dtf2, Tin1, np.sin(Tin1),
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
np.testing.assert_array_equal(tout, Tin1)
|
|
|
|
#
|
|
# Interpolation of the input (to match scipy.signal.dlsim)
|
|
#
|
|
# Initial response
|
|
tout, yout, xout = forced_response(self.siso_dtf2, Tin1,
|
|
np.sin(Tin1), interpolate=True,
|
|
squeeze=False)
|
|
self.assertEqual(np.shape(tout), np.shape(yout[0,:]))
|
|
self.assertTrue(np.allclose(tout[1:] - tout[:-1], self.siso_dtf2.dt))
|
|
|
|
#
|
|
# Incompatible cases: make sure an error is thrown
|
|
#
|
|
# System timebase and given time vector are incompatible
|
|
#
|
|
# Initial response
|
|
with self.assertRaises(Exception) as context:
|
|
tout, yout = initial_response(self.siso_dtf2, Tin3, siso_x0,
|
|
squeeze=False)
|
|
self.assertTrue(isinstance(context.exception, ValueError))
|
|
|
|
def test_discrete_time_steps(self):
|
|
"""Make sure rounding errors in sample time are handled properly"""
|
|
# See https://github.com/python-control/python-control/issues/332)
|
|
#
|
|
# These tests play around with the input time vector to make sure that
|
|
# small rounding errors don't generate spurious errors.
|
|
|
|
# Discrete time system to use for simulation
|
|
# self.siso_dtf2 = TransferFunction([1], [1, 1, 0.25], 0.2)
|
|
|
|
# Set up a time range and simulate
|
|
T = np.arange(0, 100, 0.2)
|
|
tout1, yout1 = step_response(self.siso_dtf2, T)
|
|
|
|
# Simulate every other time step
|
|
T = np.arange(0, 100, 0.4)
|
|
tout2, yout2 = step_response(self.siso_dtf2, T)
|
|
np.testing.assert_array_almost_equal(tout1[::2], tout2)
|
|
np.testing.assert_array_almost_equal(yout1[::2], yout2)
|
|
|
|
# Add a small error into some of the time steps
|
|
T = np.arange(0, 100, 0.2)
|
|
T[1:-2:2] -= 1e-12 # tweak second value and a few others
|
|
tout3, yout3 = step_response(self.siso_dtf2, T)
|
|
np.testing.assert_array_almost_equal(tout1, tout3)
|
|
np.testing.assert_array_almost_equal(yout1, yout3)
|
|
|
|
# Add a small error into some of the time steps (w/ skipping)
|
|
T = np.arange(0, 100, 0.4)
|
|
T[1:-2:2] -= 1e-12 # tweak second value and a few others
|
|
tout4, yout4 = step_response(self.siso_dtf2, T)
|
|
np.testing.assert_array_almost_equal(tout2, tout4)
|
|
np.testing.assert_array_almost_equal(yout2, yout4)
|
|
|
|
# Make sure larger errors *do* generate an error
|
|
T = np.arange(0, 100, 0.2)
|
|
T[1:-2:2] -= 1e-3 # change second value and a few others
|
|
self.assertRaises(ValueError, step_response, self.siso_dtf2, T)
|
|
|
|
def test_time_series_data_convention(self):
|
|
"""Make sure time series data matches documentation conventions"""
|
|
# SISO continuous time
|
|
t, y = step_response(self.siso_ss1)
|
|
self.assertTrue(isinstance(t, np.ndarray)
|
|
and not isinstance(t, np.matrix))
|
|
self.assertTrue(len(t.shape) == 1)
|
|
self.assertTrue(len(y.shape) == 1) # SISO returns "scalar" output
|
|
self.assertTrue(len(t) == len(y)) # Allows direct plotting of output
|
|
|
|
# SISO discrete time
|
|
t, y = step_response(self.siso_dss1)
|
|
self.assertTrue(isinstance(t, np.ndarray)
|
|
and not isinstance(t, np.matrix))
|
|
self.assertTrue(len(t.shape) == 1)
|
|
self.assertTrue(len(y.shape) == 1) # SISO returns "scalar" output
|
|
self.assertTrue(len(t) == len(y)) # Allows direct plotting of output
|
|
|
|
# MIMO continuous time
|
|
tin = np.linspace(0, 10, 100)
|
|
uin = [np.sin(tin), np.cos(tin)]
|
|
t, y, x = forced_response(self.mimo_ss1, tin, uin)
|
|
self.assertTrue(isinstance(t, np.ndarray)
|
|
and not isinstance(t, np.matrix))
|
|
self.assertTrue(len(t.shape) == 1)
|
|
self.assertTrue(len(y[0].shape) == 1)
|
|
self.assertTrue(len(y[1].shape) == 1)
|
|
self.assertTrue(len(t) == len(y[0]))
|
|
self.assertTrue(len(t) == len(y[1]))
|
|
|
|
# MIMO discrete time
|
|
tin = np.linspace(0, 10, 100)
|
|
uin = [np.sin(tin), np.cos(tin)]
|
|
t, y, x = forced_response(self.mimo_dss1, tin, uin)
|
|
self.assertTrue(isinstance(t, np.ndarray)
|
|
and not isinstance(t, np.matrix))
|
|
self.assertTrue(len(t.shape) == 1)
|
|
self.assertTrue(len(y[0].shape) == 1)
|
|
self.assertTrue(len(y[1].shape) == 1)
|
|
self.assertTrue(len(t) == len(y[0]))
|
|
self.assertTrue(len(t) == len(y[1]))
|
|
|
|
# Allow input time as 2D array (output should be 1D)
|
|
tin = np.array(np.linspace(0, 10, 100), ndmin=2)
|
|
t, y = step_response(self.siso_ss1, tin)
|
|
self.assertTrue(isinstance(t, np.ndarray)
|
|
and not isinstance(t, np.matrix))
|
|
self.assertTrue(len(t.shape) == 1)
|
|
self.assertTrue(len(y.shape) == 1) # SISO returns "scalar" output
|
|
self.assertTrue(len(t) == len(y)) # Allows direct plotting of output
|
|
|
|
|
|
def suite():
|
|
return unittest.TestLoader().loadTestsFromTestCase(TestTimeresp)
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|