def testDcgain(self): #Create different forms of a SISO system A, B, C, D = self.siso_ss1.A, self.siso_ss1.B, self.siso_ss1.C, \ self.siso_ss1.D Z, P, k = sp.signal.ss2zpk(A, B, C, D) num, den = sp.signal.ss2tf(A, B, C, D) sys_ss = self.siso_ss1 #Compute the gain with ``dcgain`` gain_abcd = dcgain(A, B, C, D) gain_zpk = dcgain(Z, P, k) gain_numden = dcgain(np.squeeze(num), den) gain_sys_ss = dcgain(sys_ss) # print('\ngain_abcd:', gain_abcd, 'gain_zpk:', gain_zpk) # print('gain_numden:', gain_numden, 'gain_sys_ss:', gain_sys_ss) #Compute the gain with a long simulation t = linspace(0, 1000, 1000) y, _t = step(sys_ss, t) gain_sim = y[-1] # print('gain_sim:', gain_sim) #All gain values must be approximately equal to the known gain np.testing.assert_array_almost_equal( [gain_abcd, gain_zpk, gain_numden, gain_sys_ss, gain_sim], [59, 59, 59, 59, 59]) if slycot_check(): # Test with MIMO system, which contains ``siso_ss1`` twice gain_mimo = dcgain(self.mimo_ss1) # print('gain_mimo: \n', gain_mimo) np.testing.assert_array_almost_equal(gain_mimo, [[59., 0], [0, 59.]])
def testLsim(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]) yout, tout, _xout = lsim(self.siso_ss1, u, t) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) yout, _t, _xout = lsim(self.siso_tf3, u, t) 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]) yout, _t, _xout = lsim(self.siso_ss1, u, t, x0) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) if slycot_check(): #Test MIMO system, which contains ``siso_ss1`` twice #first system: initial value, second system: step response u = np.array([[0., 1.], [0, 1], [0, 1], [0, 1], [0, 1], [0, 1], [0, 1], [0, 1], [0, 1], [0, 1]]) x0 = np.matrix(".5; 1; 0; 0") youttrue = np.array([[11., 9.], [8.1494, 17.6457], [5.9361, 24.7072], [4.2258, 30.4855], [2.9118, 35.2234], [1.9092, 39.1165], [1.1508, 42.3227], [0.5833, 44.9694], [0.1645, 47.1599], [-0.1391, 48.9776]]) yout, _t, _xout = lsim(self.mimo_ss1, u, t, x0) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
def testConvertMIMO(self): """Test state space to transfer function conversion. Do a MIMO conversion and make sure that it is processed correctly both with and without slycot Example from issue gh-120, jgoppert """ # Set up a 1x3 transfer function (should always work) tsys = tf([[[-235, 1.146e4], [-235, 1.146E4], [-235, 1.146E4, 0]]], [[[1, 48.78, 0], [1, 48.78, 0, 0], [0.008, 1.39, 48.78]]]) # Convert to state space and look for an error if (not slycot_check()): with pytest.raises(TypeError): tf2ss(tsys) else: ssys = tf2ss(tsys) assert ssys.B.shape[1] == 3 assert ssys.C.shape[0] == 1
def test_dcgain(self): """Test function dcgain with different systems""" if slycot_check(): #Test MIMO systems A, B, C, D = self.make_MIMO_mats() gain1 = dcgain(ss(A, B, C, D)) gain2 = dcgain(A, B, C, D) sys_tf = ss2tf(A, B, C, D) gain3 = dcgain(sys_tf) gain4 = dcgain(sys_tf.num, sys_tf.den) #print("gain1:", gain1) assert_array_almost_equal(gain1, array([[0.0269, 0.], [0., 0.0269]]), decimal=4) assert_array_almost_equal(gain1, gain2) assert_array_almost_equal(gain3, gain4) assert_array_almost_equal(gain1, gain4) #Test SISO systems A, B, C, D = self.make_SISO_mats() gain1 = dcgain(ss(A, B, C, D)) assert_array_almost_equal(gain1, array([[0.0269]]), decimal=4)
def testDcgain(self): #Create different forms of a SISO system A, B, C, D = self.siso_ss1.A, self.siso_ss1.B, self.siso_ss1.C, \ self.siso_ss1.D Z, P, k = sp.signal.ss2zpk(A, B, C, D) num, den = sp.signal.ss2tf(A, B, C, D) sys_ss = self.siso_ss1 #Compute the gain with ``dcgain`` gain_abcd = dcgain(A, B, C, D) gain_zpk = dcgain(Z, P, k) gain_numden = dcgain(np.squeeze(num), den) gain_sys_ss = dcgain(sys_ss) # print('\ngain_abcd:', gain_abcd, 'gain_zpk:', gain_zpk) # print('gain_numden:', gain_numden, 'gain_sys_ss:', gain_sys_ss) #Compute the gain with a long simulation t = linspace(0, 1000, 1000) y, _t = step(sys_ss, t) gain_sim = y[-1] # print('gain_sim:', gain_sim) #All gain values must be approximately equal to the known gain np.testing.assert_array_almost_equal( [gain_abcd, gain_zpk, gain_numden, gain_sys_ss, gain_sim], [59, 59, 59, 59, 59]) if slycot_check(): # Test with MIMO system, which contains ``siso_ss1`` twice gain_mimo = dcgain(self.mimo_ss1) # print('gain_mimo: \n', gain_mimo) np.testing.assert_array_almost_equal(gain_mimo, [[59., 0 ], [0, 59.]])
def test_care_g2(self): A = array([[-2, -1],[-1, -1]]) Q = array([[0, 0],[0, 1]]) B = array([[1],[0]]) R = 1 S = array([[1],[0]]) E = array([[2, 1],[1, 2]]) X,L,G = care(A,B,Q,R,S,E) # print("The solution obtained is", X) Gref = 1/R * (B.T @ X @ E + S.T) assert_array_almost_equal( A.T @ X @ E + E.T @ X @ A - (E.T @ X @ B + S) @ Gref + Q , zeros((2,2))) assert_array_almost_equal(Gref , G) # Compare methods if slycot_check(): X_scipy, L_scipy, G_scipy = care( A, B, Q, R, S, E, method='scipy') X_slycot, L_slycot, G_slycot = care( A, B, Q, R, S, E, method='slycot') assert_array_almost_equal(X_scipy, X_slycot) assert_array_almost_equal(L_scipy, L_slycot) assert_array_almost_equal(G_scipy, G_slycot)
def test_DLQE(self, matarrayin, method): if method == 'slycot' and not slycot_check(): return A, G, C, QN, RN = (matarrayin([[X]]) for X in [0., .1, 1., 10., 2.]) L, P, poles = dlqe(A, G, C, QN, RN, method=method) self.check_DLQE(L, P, poles, G, QN, RN)
def test_DLQR_3args(self, matarrayin, matarrayout, method): if method == 'slycot' and not slycot_check(): return dsys = ss(0., 1., 1., 0., .1) Q, R = (matarrayin([[X]]) for X in [10., 2.]) K, S, poles = dlqr(dsys, Q, R, method=method) self.check_DLQR(K, S, poles, Q, R)
def test_dcgain(self): """Test function dcgain with different systems""" if slycot_check(): #Test MIMO systems A, B, C, D = self.make_MIMO_mats() gain1 = dcgain(ss(A, B, C, D)) gain2 = dcgain(A, B, C, D) sys_tf = ss2tf(A, B, C, D) gain3 = dcgain(sys_tf) gain4 = dcgain(sys_tf.num, sys_tf.den) #print("gain1:", gain1) assert_array_almost_equal(gain1, array([[0.0269, 0. ], [0. , 0.0269]]), decimal=4) assert_array_almost_equal(gain1, gain2) assert_array_almost_equal(gain3, gain4) assert_array_almost_equal(gain1, gain4) #Test SISO systems A, B, C, D = self.make_SISO_mats() gain1 = dcgain(ss(A, B, C, D)) assert_array_almost_equal(gain1, array([[0.0269]]), decimal=4)
class TestHinf(unittest.TestCase): def setUp(self): # Use array instead of matrix (and save old value to restore at end) control.use_numpy_matrix(False) @unittest.skipIf(not slycot_check(), "slycot not installed") def testHinfsyn(self): """Test hinfsyn""" p = control.ss(-1, [[1, 1]], [[1], [1]], [[0, 1], [1, 0]]) k, cl, gam, rcond = control.robust.hinfsyn(p, 1, 1) # from Octave, which also uses SB10AD: # a= -1; b1= 1; b2= 1; c1= 1; c2= 1; d11= 0; d12= 1; d21= 1; d22= 0; # g = ss(a,[b1,b2],[c1;c2],[d11,d12;d21,d22]); # [k,cl] = hinfsyn(g,1,1); np.testing.assert_array_almost_equal(k.A, [[-3]]) np.testing.assert_array_almost_equal(k.B, [[1]]) np.testing.assert_array_almost_equal(k.C, [[-1]]) np.testing.assert_array_almost_equal(k.D, [[0]]) np.testing.assert_array_almost_equal(cl.A, [[-1, -1], [1, -3]]) np.testing.assert_array_almost_equal(cl.B, [[1], [1]]) np.testing.assert_array_almost_equal(cl.C, [[1, -1]]) np.testing.assert_array_almost_equal(cl.D, [[0]]) # TODO: add more interesting examples def tearDown(self): control.config.reset_defaults()
def test_squeeze(self, fcn, nstate, nout, ninp, omega, squeeze, shape, omega_type): """Test correct behavior of frequencey response squeeze parameter.""" # Create the system to be tested if fcn == ct.frd: sys = fcn(ct.rss(nstate, nout, ninp), [1e-2, 1e-1, 1, 1e1, 1e2]) elif fcn == ct.tf and (nout > 1 or ninp > 1) and not slycot_check(): pytest.skip("Conversion of MIMO systems to transfer functions " "requires slycot.") else: sys = fcn(ct.rss(nstate, nout, ninp)) if omega_type == "numpy": omega = np.asarray(omega) isscalar = omega.ndim == 0 # keep the ndarray type even for scalars s = np.asarray(omega * 1j) else: isscalar = not hasattr(omega, '__len__') if isscalar: s = omega * 1J else: s = [w * 1J for w in omega] # Call the transfer function directly and make sure shape is correct assert sys(s, squeeze=squeeze).shape == shape # Make sure that evalfr also works as expected assert ct.evalfr(sys, s, squeeze=squeeze).shape == shape # Check frequency response mag, phase, _ = sys.frequency_response(omega, squeeze=squeeze) if isscalar and squeeze is not True: # sys.frequency_response() expects a list as an argument # Add the shape of the input to the expected shape assert mag.shape == shape + (1, ) assert phase.shape == shape + (1, ) else: assert mag.shape == shape assert phase.shape == shape # Make sure the default shape lines up with squeeze=None case if squeeze is None: assert sys(s).shape == shape # Changing config.default to False should return 3D frequency response ct.config.set_defaults('control', squeeze_frequency_response=False) mag, phase, _ = sys.frequency_response(omega) if isscalar: assert mag.shape == (sys.noutputs, sys.ninputs, 1) assert phase.shape == (sys.noutputs, sys.ninputs, 1) assert sys(s).shape == (sys.noutputs, sys.ninputs) assert ct.evalfr(sys, s).shape == (sys.noutputs, sys.ninputs) else: assert mag.shape == (sys.noutputs, sys.ninputs, len(omega)) assert phase.shape == (sys.noutputs, sys.ninputs, len(omega)) assert sys(s).shape == \ (sys.noutputs, sys.ninputs, len(omega)) assert ct.evalfr(sys, s).shape == \ (sys.noutputs, sys.ninputs, len(omega))
def testInitial(self): #Test SISO system sys = self.siso_ss1 t = np.linspace(0, 1, 10) 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 ]) yout, tout = initial(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 yout, tout, xout = initial(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) if slycot_check(): #Test MIMO system, which contains ``siso_ss1`` twice sys = self.mimo_ss1 x0 = np.matrix(".5; 1.; .5; 1.") y_00, _t = initial(sys, T=t, X0=x0, input=0, output=0) y_11, _t = initial(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 testEvalfr(self): w = 1j np.testing.assert_almost_equal(evalfr(self.siso_ss1, w), 44.8-21.4j) evalfr(self.siso_ss2, w) evalfr(self.siso_ss3, w) evalfr(self.siso_tf1, w) evalfr(self.siso_tf2, w) evalfr(self.siso_tf3, w) if slycot_check(): np.testing.assert_array_almost_equal( evalfr(self.mimo_ss1, w), np.array( [[44.8-21.4j, 0.], [0., 44.8-21.4j]]))
def testEvalfr(self): w = 1j np.testing.assert_almost_equal(evalfr(self.siso_ss1, w), 44.8 - 21.4j) evalfr(self.siso_ss2, w) evalfr(self.siso_ss3, w) evalfr(self.siso_tf1, w) evalfr(self.siso_tf2, w) evalfr(self.siso_tf3, w) if slycot_check(): np.testing.assert_array_almost_equal( evalfr(self.mimo_ss1, w), np.array([[44.8 - 21.4j, 0.], [0., 44.8 - 21.4j]]))
def test_squeeze(self, fcn, nstate, nout, ninp, omega, squeeze, shape): # Create the system to be tested if fcn == ct.frd: sys = fcn(ct.rss(nstate, nout, ninp), [1e-2, 1e-1, 1, 1e1, 1e2]) elif fcn == ct.tf and (nout > 1 or ninp > 1) and not slycot_check(): pytest.skip("Conversion of MIMO systems to transfer functions " "requires slycot.") else: sys = fcn(ct.rss(nstate, nout, ninp)) # Convert the frequency list to an array for easy of use isscalar = not hasattr(omega, '__len__') omega = np.array(omega) # Call the transfer function directly and make sure shape is correct assert sys(omega * 1j, squeeze=squeeze).shape == shape # Make sure that evalfr also works as expected assert ct.evalfr(sys, omega * 1j, squeeze=squeeze).shape == shape # Check frequency response mag, phase, _ = sys.frequency_response(omega, squeeze=squeeze) if isscalar and squeeze is not True: # sys.frequency_response() expects a list as an argument # Add the shape of the input to the expected shape assert mag.shape == shape + (1, ) assert phase.shape == shape + (1, ) else: assert mag.shape == shape assert phase.shape == shape # Make sure the default shape lines up with squeeze=None case if squeeze is None: assert sys(omega * 1j).shape == shape # Changing config.default to False should return 3D frequency response ct.config.set_defaults('control', squeeze_frequency_response=False) mag, phase, _ = sys.frequency_response(omega) if isscalar: assert mag.shape == (sys.noutputs, sys.ninputs, 1) assert phase.shape == (sys.noutputs, sys.ninputs, 1) assert sys(omega * 1j).shape == (sys.noutputs, sys.ninputs) assert ct.evalfr(sys, omega * 1j).shape == (sys.noutputs, sys.ninputs) else: assert mag.shape == (sys.noutputs, sys.ninputs, len(omega)) assert phase.shape == (sys.noutputs, sys.ninputs, len(omega)) assert sys(omega * 1j).shape == \ (sys.noutputs, sys.ninputs, len(omega)) assert ct.evalfr(sys, omega * 1j).shape == \ (sys.noutputs, sys.ninputs, len(omega))
def setUp(self): """Set up testing parameters.""" # Number of times to run each of the randomized tests. self.numTests = 1 # almost guarantees failure # Maximum number of states to test + 1 self.maxStates = 4 # Maximum number of inputs and outputs to test + 1 # If slycot is not installed, just check SISO self.maxIO = 5 if slycot_check() else 2 # Set to True to print systems to the output. self.debug = False # get consistent results np.random.seed(7)
class TestMixsyn(unittest.TestCase): """Test control.robust.mixsyn""" def setUp(self): # Use array instead of matrix (and save old value to restore at end) control.use_numpy_matrix(False) # it's a relatively simple wrapper; compare results with augw, hinfsyn @unittest.skipIf(not slycot_check(), "slycot not installed") def testSiso(self): """mixsyn with SISO system""" from control import tf, augw, hinfsyn, mixsyn from control import ss # Skogestad+Postlethwaite, Multivariable Feedback Control, 1st Ed., Example 2.11 s = tf([1, 0], 1) # plant g = 200 / (10 * s + 1) / (0.05 * s + 1) ** 2 # sensitivity weighting M = 1.5 wb = 10 A = 1e-4 w1 = (s / M + wb) / (s + wb * A) # KS weighting w2 = tf(1, 1) p = augw(g, w1, w2) kref, clref, gam, rcond = hinfsyn(p, 1, 1) ktest, cltest, info = mixsyn(g, w1, w2) # check similar to S+P's example np.testing.assert_allclose(gam, 1.37, atol=1e-2) # mixsyn is a convenience wrapper around augw and hinfsyn, so # results will be exactly the same. Given than, use the lazy # but fragile testing option. np.testing.assert_allclose(ktest.A, kref.A) np.testing.assert_allclose(ktest.B, kref.B) np.testing.assert_allclose(ktest.C, kref.C) np.testing.assert_allclose(ktest.D, kref.D) np.testing.assert_allclose(cltest.A, clref.A) np.testing.assert_allclose(cltest.B, clref.B) np.testing.assert_allclose(cltest.C, clref.C) np.testing.assert_allclose(cltest.D, clref.D) np.testing.assert_allclose(gam, info[0]) np.testing.assert_allclose(rcond, info[1]) def tearDown(self): control.config.reset_defaults()
class TestH2(unittest.TestCase): @unittest.skipIf(not slycot_check(), "slycot not installed") def testH2syn(self): "Test h2syn" p = control.ss(-1, [1, 1], [[1], [1]], [[0, 1], [1, 0]]) k = control.robust.h2syn(p, 1, 1) # from Octave, which also uses SB10HD for H-2 synthesis: # a= -1; b1= 1; b2= 1; c1= 1; c2= 1; d11= 0; d12= 1; d21= 1; d22= 0; # g = ss(a,[b1,b2],[c1;c2],[d11,d12;d21,d22]); # k = h2syn(g,1,1); # the solution is the same as for the hinfsyn test np.testing.assert_array_almost_equal(k.A, [[-3]]) np.testing.assert_array_almost_equal(k.B, [[1]]) np.testing.assert_array_almost_equal(k.C, [[-1]]) np.testing.assert_array_almost_equal(k.D, [[0]])
def testImpulse(self): t = np.linspace(0, 1, 10) # test transfer function yout, tout = impulse(self.siso_tf1, T=t) youttrue = np.array([ 0., 0.0994, 0.1779, 0.2388, 0.2850, 0.3188, 0.3423, 0.3573, 0.3654, 0.3679 ]) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) # produce a warning for a system with direct feedthrough with warnings.catch_warnings(): warnings.simplefilter("ignore") #Test SISO system sys = self.siso_ss1 youttrue = np.array([ 86., 70.1808, 57.3753, 46.9975, 38.5766, 31.7344, 26.1668, 21.6292, 17.9245, 14.8945 ]) yout, tout = impulse(sys, T=t) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) # Play with arguments yout, tout = impulse(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]) yout, tout = impulse(sys, T=t, X0=X0) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) yout, tout, xout = impulse(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) if slycot_check(): #Test MIMO system, which contains ``siso_ss1`` twice sys = self.mimo_ss1 y_00, _t = impulse(sys, T=t, input=0, output=0) y_11, _t = impulse(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)
def testConvertMIMO(self): """Test state space to transfer function conversion.""" verbose = self.debug # Do a MIMO conversation and make sure that it is processed # correctly both with and without slycot # # Example from issue #120, jgoppert import control # Set up a transfer function (should always work) tfcn = control.tf( [[[-235, 1.146e4], [-235, 1.146E4], [-235, 1.146E4, 0]]], [[[1, 48.78, 0], [1, 48.78, 0, 0], [0.008, 1.39, 48.78]]]) # Convert to state space and look for an error if (not slycot_check()): self.assertRaises(TypeError, control.tf2ss, tfcn)
def test_care(self, matarrayin): """Test stabilizing and anti-stabilizing feedback, continuous""" A = matarrayin(np.diag([1, -1])) B = matarrayin(np.identity(2)) Q = matarrayin(np.identity(2)) R = matarrayin(np.identity(2)) S = matarrayin(np.zeros((2, 2))) E = matarrayin(np.identity(2)) X, L, G = care(A, B, Q, R, S, E, stabilizing=True) assert np.all(np.real(L) < 0) if slycot_check(): X, L, G = care(A, B, Q, R, S, E, stabilizing=False) assert np.all(np.real(L) > 0) else: with pytest.raises(ControlArgument, match="'scipy' not valid"): X, L, G = care(A, B, Q, R, S, E, stabilizing=False)
def test_lyap(self): A = array([[-1, 1], [-1, 0]]) Q = array([[1, 0], [0, 1]]) X = lyap(A, Q) # print("The solution obtained is ", X) assert_array_almost_equal(A @ X + X @ A.T + Q, zeros((2,2))) A = array([[1, 2], [-3, -4]]) Q = array([[3, 1], [1, 1]]) X = lyap(A,Q) # print("The solution obtained is ", X) assert_array_almost_equal(A @ X + X @ A.T + Q, zeros((2,2))) # Compare methods if slycot_check(): X_scipy = lyap(A, Q, method='scipy') X_slycot = lyap(A, Q, method='slycot') assert_array_almost_equal(X_scipy, X_slycot)
class TestHinf(unittest.TestCase): @unittest.skipIf(not slycot_check(), "slycot not installed") def testHinfsyn(self): "Test hinfsyn" p = control.ss(-1, [1, 1], [[1], [1]], [[0, 1], [1, 0]]) k, cl, gam, rcond = control.robust.hinfsyn(p, 1, 1) # from Octave, which also uses SB10AD: # a= -1; b1= 1; b2= 1; c1= 1; c2= 1; d11= 0; d12= 1; d21= 1; d22= 0; # g = ss(a,[b1,b2],[c1;c2],[d11,d12;d21,d22]); # [k,cl] = hinfsyn(g,1,1); np.testing.assert_array_almost_equal(k.A, [[-3]]) np.testing.assert_array_almost_equal(k.B, [[1]]) np.testing.assert_array_almost_equal(k.C, [[-1]]) np.testing.assert_array_almost_equal(k.D, [[0]]) np.testing.assert_array_almost_equal(cl.A, [[-1, -1], [1, -3]]) np.testing.assert_array_almost_equal(cl.B, [[1], [1]]) np.testing.assert_array_almost_equal(cl.C, [[1, -1]]) np.testing.assert_array_almost_equal(cl.D, [[0]])
def test_care(self): A = array([[-2, -1],[-1, -1]]) Q = array([[0, 0],[0, 1]]) B = array([[1, 0],[0, 4]]) X, L, G = care(A, B, Q) # print("The solution obtained is", X) M = A.T @ X + X @ A - X @ B @ B.T @ X + Q assert_array_almost_equal(M, zeros((2,2))) assert_array_almost_equal(B.T @ X, G) # Compare methods if slycot_check(): X_scipy, L_scipy, G_scipy = care(A, B, Q, method='scipy') X_slycot, L_slycot, G_slycot = care(A, B, Q, method='slycot') assert_array_almost_equal(X_scipy, X_slycot) assert_array_almost_equal(np.sort(L_scipy), np.sort(L_slycot)) assert_array_almost_equal(G_scipy, G_slycot)
def testStep(self): t = np.linspace(0, 1, 10) # Test transfer function yout, tout = step(self.siso_tf1, T=t) youttrue = np.array([ 0, 0.0057, 0.0213, 0.0446, 0.0739, 0.1075, 0.1443, 0.1832, 0.2235, 0.2642 ]) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) # Test SISO system with direct feedthrough sys = self.siso_ss1 youttrue = np.array([ 9., 17.6457, 24.7072, 30.4855, 35.2234, 39.1165, 42.3227, 44.9694, 47.1599, 48.9776 ]) yout, tout = step(sys, T=t) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) # Play with arguments yout, tout = step(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]) yout, tout = step(sys, T=t, X0=X0) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) yout, tout, xout = step(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) if slycot_check(): # Test MIMO system, which contains ``siso_ss1`` twice sys = self.mimo_ss1 y_00, _t = step(sys, T=t, input=0, output=0) y_11, _t = step(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)
def test_lyap_sylvester(self): A = 5 B = array([[4, 3], [4, 3]]) C = array([2, 1]) X = lyap(A, B, C) # print("The solution obtained is ", X) assert_array_almost_equal(A * X + X @ B + C, zeros((1,2))) A = array([[2, 1], [1, 2]]) B = array([[1, 2], [0.5, 0.1]]) C = array([[1, 0], [0, 1]]) X = lyap(A, B, C) # print("The solution obtained is ", X) assert_array_almost_equal(A @ X + X @ B + C, zeros((2,2))) # Compare methods if slycot_check(): X_scipy = lyap(A, B, C, method='scipy') X_slycot = lyap(A, B, C, method='slycot') assert_array_almost_equal(X_scipy, X_slycot)
def testImpulse(self): t = np.linspace(0, 1, 10) # test transfer function yout, tout = impulse(self.siso_tf1, T=t) youttrue = np.array([0., 0.0994, 0.1779, 0.2388, 0.2850, 0.3188, 0.3423, 0.3573, 0.3654, 0.3679]) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) # produce a warning for a system with direct feedthrough with warnings.catch_warnings(): warnings.simplefilter("ignore") #Test SISO system sys = self.siso_ss1 youttrue = np.array([86., 70.1808, 57.3753, 46.9975, 38.5766, 31.7344, 26.1668, 21.6292, 17.9245, 14.8945]) yout, tout = impulse(sys, T=t) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) # Play with arguments yout, tout = impulse(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]); yout, tout = impulse(sys, T=t, X0=X0) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) yout, tout, xout = impulse(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) if slycot_check(): #Test MIMO system, which contains ``siso_ss1`` twice sys = self.mimo_ss1 y_00, _t = impulse(sys, T=t, input=0, output=0) y_11, _t = impulse(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)
def testConvertMIMO(self): """Test state space to transfer function conversion.""" verbose = self.debug # Do a MIMO conversation and make sure that it is processed # correctly both with and without slycot # # Example from issue #120, jgoppert import control # Set up a transfer function (should always work) tfcn = control.tf([[[-235, 1.146e4], [-235, 1.146E4], [-235, 1.146E4, 0]]], [[[1, 48.78, 0], [1, 48.78, 0, 0], [0.008, 1.39, 48.78]]]) # Convert to state space and look for an error if (not slycot_check()): self.assertRaises(TypeError, control.tf2ss, tfcn)
class TestH2(unittest.TestCase): def setUp(self): # Use array instead of matrix (and save old value to restore at end) control.use_numpy_matrix(False) @unittest.skipIf(not slycot_check(), "slycot not installed") def testH2syn(self): """Test h2syn""" p = control.ss(-1, [[1, 1]], [[1], [1]], [[0, 1], [1, 0]]) k = control.robust.h2syn(p, 1, 1) # from Octave, which also uses SB10HD for H-2 synthesis: # a= -1; b1= 1; b2= 1; c1= 1; c2= 1; d11= 0; d12= 1; d21= 1; d22= 0; # g = ss(a,[b1,b2],[c1;c2],[d11,d12;d21,d22]); # k = h2syn(g,1,1); # the solution is the same as for the hinfsyn test np.testing.assert_array_almost_equal(k.A, [[-3]]) np.testing.assert_array_almost_equal(k.B, [[1]]) np.testing.assert_array_almost_equal(k.C, [[-1]]) np.testing.assert_array_almost_equal(k.D, [[0]]) def tearDown(self): control.config.reset_defaults()
def testStep(self): t = np.linspace(0, 1, 10) # Test transfer function yout, tout = step(self.siso_tf1, T=t) youttrue = np.array([0, 0.0057, 0.0213, 0.0446, 0.0739, 0.1075, 0.1443, 0.1832, 0.2235, 0.2642]) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) # Test SISO system with direct feedthrough sys = self.siso_ss1 youttrue = np.array([9., 17.6457, 24.7072, 30.4855, 35.2234, 39.1165, 42.3227, 44.9694, 47.1599, 48.9776]) yout, tout = step(sys, T=t) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) # Play with arguments yout, tout = step(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]); yout, tout = step(sys, T=t, X0=X0) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) yout, tout, xout = step(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) if slycot_check(): # Test MIMO system, which contains ``siso_ss1`` twice sys = self.mimo_ss1 y_00, _t = step(sys, T=t, input=0, output=0) y_11, _t = step(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)
class TestFreqresp(unittest.TestCase): def setUp(self): self.A = np.matrix('1,1;0,1') self.C = np.matrix('1,0') self.omega = np.linspace(10e-2, 10e2, 1000) def test_siso(self): B = np.matrix('0;1') D = 0 sys = StateSpace(self.A, B, self.C, D) # test frequency response frq = sys.freqresp(self.omega) # test bode plot bode(sys) # Convert to transfer function and test bode systf = tf(sys) bode(systf) def test_doubleint(self): # 30 May 2016, RMM: added to replicate typecast bug in freqresp.py A = np.matrix('0, 1; 0, 0') B = np.matrix('0; 1') C = np.matrix('1, 0') D = 0 sys = ss(A, B, C, D) bode(sys) @unittest.skipIf(not slycot_check(), "slycot not installed") def test_mimo(self): # MIMO B = np.matrix('1,0;0,1') D = np.matrix('0,0') sysMIMO = ss(self.A, B, self.C, D) frqMIMO = sysMIMO.freqresp(self.omega) tfMIMO = tf(sysMIMO)
def testInitial(self): #Test SISO system sys = self.siso_ss1 t = np.linspace(0, 1, 10) 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]) yout, tout = initial(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 yout, tout, xout = initial(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) if slycot_check(): #Test MIMO system, which contains ``siso_ss1`` twice sys = self.mimo_ss1 x0 = np.matrix(".5; 1.; .5; 1.") y_00, _t = initial(sys, T=t, X0=x0, input=0, output=0) y_11, _t = initial(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)
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.matrix(".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) 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_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
class TestStateSpace(unittest.TestCase): """Tests for the StateSpace class.""" def setUp(self): """Set up a MIMO system to test operations on.""" A = [[-3., 4., 2.], [-1., -3., 0.], [2., 5., 3.]] B = [[1., 4.], [-3., -3.], [-2., 1.]] C = [[4., 2., -3.], [1., 4., 3.]] D = [[-2., 4.], [0., 1.]] a = [[4., 1.], [2., -3]] b = [[5., 2.], [-3., -3.]] c = [[2., -4], [0., 1.]] d = [[3., 2.], [1., -1.]] self.sys1 = StateSpace(A, B, C, D) self.sys2 = StateSpace(a, b, c, d) def testPole(self): """Evaluate the poles of a MIMO system.""" p = np.sort(self.sys1.pole()) true_p = np.sort([3.34747678408874, -3.17373839204437 + 1.47492908003839j, -3.17373839204437 - 1.47492908003839j]) np.testing.assert_array_almost_equal(p, true_p) def testZero(self): """Evaluate the zeros of a SISO system.""" sys = StateSpace(self.sys1.A, [[3.], [-2.], [4.]], [[-1., 3., 2.]], [[-4.]]) z = sys.zero() np.testing.assert_array_almost_equal(z, [4.26864638637134, -3.75932319318567 + 1.10087776649554j, -3.75932319318567 - 1.10087776649554j]) def testAdd(self): """Add two MIMO systems.""" A = [[-3., 4., 2., 0., 0.], [-1., -3., 0., 0., 0.], [2., 5., 3., 0., 0.], [0., 0., 0., 4., 1.], [0., 0., 0., 2., -3.]] B = [[1., 4.], [-3., -3.], [-2., 1.], [5., 2.], [-3., -3.]] C = [[4., 2., -3., 2., -4.], [1., 4., 3., 0., 1.]] D = [[1., 6.], [1., 0.]] sys = self.sys1 + self.sys2 np.testing.assert_array_almost_equal(sys.A, A) np.testing.assert_array_almost_equal(sys.B, B) np.testing.assert_array_almost_equal(sys.C, C) np.testing.assert_array_almost_equal(sys.D, D) def testSub(self): """Subtract two MIMO systems.""" A = [[-3., 4., 2., 0., 0.], [-1., -3., 0., 0., 0.], [2., 5., 3., 0., 0.], [0., 0., 0., 4., 1.], [0., 0., 0., 2., -3.]] B = [[1., 4.], [-3., -3.], [-2., 1.], [5., 2.], [-3., -3.]] C = [[4., 2., -3., -2., 4.], [1., 4., 3., 0., -1.]] D = [[-5., 2.], [-1., 2.]] sys = self.sys1 - self.sys2 np.testing.assert_array_almost_equal(sys.A, A) np.testing.assert_array_almost_equal(sys.B, B) np.testing.assert_array_almost_equal(sys.C, C) np.testing.assert_array_almost_equal(sys.D, D) def testMul(self): """Multiply two MIMO systems.""" A = [[4., 1., 0., 0., 0.], [2., -3., 0., 0., 0.], [2., 0., -3., 4., 2.], [-6., 9., -1., -3., 0.], [-4., 9., 2., 5., 3.]] B = [[5., 2.], [-3., -3.], [7., -2.], [-12., -3.], [-5., -5.]] C = [[-4., 12., 4., 2., -3.], [0., 1., 1., 4., 3.]] D = [[-2., -8.], [1., -1.]] sys = self.sys1 * self.sys2 np.testing.assert_array_almost_equal(sys.A, A) np.testing.assert_array_almost_equal(sys.B, B) np.testing.assert_array_almost_equal(sys.C, C) np.testing.assert_array_almost_equal(sys.D, D) def testEvalFr(self): """Evaluate the frequency response at one frequency.""" A = [[-2, 0.5], [0.5, -0.3]] B = [[0.3, -1.3], [0.1, 0.]] C = [[0., 0.1], [-0.3, -0.2]] D = [[0., -0.8], [-0.3, 0.]] sys = StateSpace(A, B, C, D) resp = [[4.37636761487965e-05 - 0.0152297592997812j, -0.792603938730853 + 0.0261706783369803j], [-0.331544857768052 + 0.0576105032822757j, 0.128919037199125 - 0.143824945295405j]] np.testing.assert_almost_equal(sys.evalfr(1.), resp) @unittest.skipIf(not slycot_check(), "slycot not installed") def testFreqResp(self): """Evaluate the frequency response at multiple frequencies.""" A = [[-2, 0.5], [0.5, -0.3]] B = [[0.3, -1.3], [0.1, 0.]] C = [[0., 0.1], [-0.3, -0.2]] D = [[0., -0.8], [-0.3, 0.]] sys = StateSpace(A, B, C, D) truemag = [[[0.0852992637230322, 0.00103596611395218], [0.935374692849736, 0.799380720864549]], [[0.55656854563842, 0.301542699860857], [0.609178071542849, 0.0382108097985257]]] truephase = [[[-0.566195599644593, -1.68063565332582], [3.0465958317514, 3.14141384339534]], [[2.90457947657161, 3.10601268291914], [-0.438157380501337, -1.40720969147217]]] trueomega = [0.1, 10.] mag, phase, omega = sys.freqresp(trueomega) np.testing.assert_almost_equal(mag, truemag) np.testing.assert_almost_equal(phase, truephase) np.testing.assert_equal(omega, trueomega) @unittest.skipIf(not slycot_check(), "slycot not installed") def testMinreal(self): """Test a minreal model reduction""" #A = [-2, 0.5, 0; 0.5, -0.3, 0; 0, 0, -0.1] A = [[-2, 0.5, 0], [0.5, -0.3, 0], [0, 0, -0.1]] #B = [0.3, -1.3; 0.1, 0; 1, 0] B = [[0.3, -1.3], [0.1, 0.], [1.0, 0.0]] #C = [0, 0.1, 0; -0.3, -0.2, 0] C = [[0., 0.1, 0.0], [-0.3, -0.2, 0.0]] #D = [0 -0.8; -0.3 0] D = [[0., -0.8], [-0.3, 0.]] # sys = ss(A, B, C, D) sys = StateSpace(A, B, C, D) sysr = sys.minreal() self.assertEqual(sysr.states, 2) self.assertEqual(sysr.inputs, sys.inputs) self.assertEqual(sysr.outputs, sys.outputs) np.testing.assert_array_almost_equal( eigvals(sysr.A), [-2.136154, -0.1638459]) def testAppendSS(self): """Test appending two state-space systems""" A1 = [[-2, 0.5, 0], [0.5, -0.3, 0], [0, 0, -0.1]] B1 = [[0.3, -1.3], [0.1, 0.], [1.0, 0.0]] C1 = [[0., 0.1, 0.0], [-0.3, -0.2, 0.0]] D1 = [[0., -0.8], [-0.3, 0.]] A2 = [[-1.]] B2 = [[1.2]] C2 = [[0.5]] D2 = [[0.4]] A3 = [[-2, 0.5, 0, 0], [0.5, -0.3, 0, 0], [0, 0, -0.1, 0], [0, 0, 0., -1.]] B3 = [[0.3, -1.3, 0], [0.1, 0., 0], [1.0, 0.0, 0], [0., 0, 1.2]] C3 = [[0., 0.1, 0.0, 0.0], [-0.3, -0.2, 0.0, 0.0], [0., 0., 0., 0.5]] D3 = [[0., -0.8, 0.], [-0.3, 0., 0.], [0., 0., 0.4]] sys1 = StateSpace(A1, B1, C1, D1) sys2 = StateSpace(A2, B2, C2, D2) sys3 = StateSpace(A3, B3, C3, D3) sys3c = sys1.append(sys2) np.testing.assert_array_almost_equal(sys3.A, sys3c.A) np.testing.assert_array_almost_equal(sys3.B, sys3c.B) np.testing.assert_array_almost_equal(sys3.C, sys3c.C) np.testing.assert_array_almost_equal(sys3.D, sys3c.D) def testAppendTF(self): """Test appending a state-space system with a tf""" A1 = [[-2, 0.5, 0], [0.5, -0.3, 0], [0, 0, -0.1]] B1 = [[0.3, -1.3], [0.1, 0.], [1.0, 0.0]] C1 = [[0., 0.1, 0.0], [-0.3, -0.2, 0.0]] D1 = [[0., -0.8], [-0.3, 0.]] s = TransferFunction([1, 0], [1]) h = 1/(s+1)/(s+2) sys1 = StateSpace(A1, B1, C1, D1) sys2 = _convertToStateSpace(h) sys3c = sys1.append(sys2) np.testing.assert_array_almost_equal(sys1.A, sys3c.A[:3,:3]) np.testing.assert_array_almost_equal(sys1.B, sys3c.B[:3,:2]) np.testing.assert_array_almost_equal(sys1.C, sys3c.C[:2,:3]) np.testing.assert_array_almost_equal(sys1.D, sys3c.D[:2,:2]) np.testing.assert_array_almost_equal(sys2.A, sys3c.A[3:,3:]) np.testing.assert_array_almost_equal(sys2.B, sys3c.B[3:,2:]) np.testing.assert_array_almost_equal(sys2.C, sys3c.C[2:,3:]) np.testing.assert_array_almost_equal(sys2.D, sys3c.D[2:,2:]) np.testing.assert_array_almost_equal(sys3c.A[:3,3:], np.zeros( (3, 2)) ) np.testing.assert_array_almost_equal(sys3c.A[3:,:3], np.zeros( (2, 3)) ) def testArrayAccessSS(self): sys1 = StateSpace([[1., 2.], [3., 4.]], [[5., 6.], [6., 8.]], [[9., 10.], [11., 12.]], [[13., 14.], [15., 16.]], 1) sys1_11 = sys1[0,1] np.testing.assert_array_almost_equal(sys1_11.A, sys1.A) np.testing.assert_array_almost_equal(sys1_11.B, sys1.B[:,1]) np.testing.assert_array_almost_equal(sys1_11.C, sys1.C[0,:]) np.testing.assert_array_almost_equal(sys1_11.D, sys1.D[0,1]) assert sys1.dt == sys1_11.dt def test_dcgain_cont(self): """Test DC gain for continuous-time state-space systems""" sys = StateSpace(-2.,6.,5.,0) np.testing.assert_equal(sys.dcgain(), 15.) sys2 = StateSpace(-2, [6., 4.], [[5.],[7.],[11]], np.zeros((3,2))) expected = np.array([[15., 10.], [21., 14.], [33., 22.]]) np.testing.assert_array_equal(sys2.dcgain(), expected) sys3 = StateSpace(0., 1., 1., 0.) np.testing.assert_equal(sys3.dcgain(), np.nan) def test_dcgain_discr(self): """Test DC gain for discrete-time state-space systems""" # static gain sys = StateSpace([], [], [], 2, True) np.testing.assert_equal(sys.dcgain(), 2) # averaging filter sys = StateSpace(0.5, 0.5, 1, 0, True) np.testing.assert_almost_equal(sys.dcgain(), 1) # differencer sys = StateSpace(0, 1, -1, 1, True) np.testing.assert_equal(sys.dcgain(), 0) # summer sys = StateSpace(1, 1, 1, 0, True) np.testing.assert_equal(sys.dcgain(), np.nan) def test_dcgain_integrator(self): """DC gain when eigenvalue at DC returns appropriately sized array of nan""" # the SISO case is also tested in test_dc_gain_{cont,discr} import itertools # iterate over input and output sizes, and continuous (dt=None) and discrete (dt=True) time for inputs,outputs,dt in itertools.product(range(1,6),range(1,6),[None,True]): states = max(inputs,outputs) # a matrix that is singular at DC, and has no "useless" states as in _remove_useless_states a = np.triu(np.tile(2,(states,states))) # eigenvalues all +2, except for ... a[0,0] = 0 if dt is None else 1 b = np.eye(max(inputs,states))[:states,:inputs] c = np.eye(max(outputs,states))[:outputs,:states] d = np.zeros((outputs,inputs)) sys = StateSpace(a,b,c,d,dt) dc = np.squeeze(np.tile(np.nan,(outputs,inputs))) np.testing.assert_array_equal(dc, sys.dcgain()) def test_scalarStaticGain(self): """Regression: can we create a scalar static gain?""" g1=StateSpace([],[],[],[2]) g2=StateSpace([],[],[],[3]) # make sure StateSpace internals, specifically ABC matrix # sizes, are OK for LTI operations g3 = g1*g2 self.assertEqual(6, g3.D[0,0]) g4 = g1+g2 self.assertEqual(5, g4.D[0,0]) g5 = g1.feedback(g2) self.assertAlmostEqual(2./7, g5.D[0,0]) g6 = g1.append(g2) np.testing.assert_array_equal(np.diag([2,3]),g6.D) def test_matrixStaticGain(self): """Regression: can we create matrix static gains?""" d1 = np.matrix([[1,2,3],[4,5,6]]) d2 = np.matrix([[7,8],[9,10],[11,12]]) g1=StateSpace([],[],[],d1) # _remove_useless_states was making A = [[0]] self.assertEqual((0,0), g1.A.shape) g2=StateSpace([],[],[],d2) g3=StateSpace([],[],[],d2.T) h1 = g1*g2 np.testing.assert_array_equal(d1*d2, h1.D) h2 = g1+g3 np.testing.assert_array_equal(d1+d2.T, h2.D) h3 = g1.feedback(g2) np.testing.assert_array_almost_equal(solve(np.eye(2)+d1*d2,d1), h3.D) h4 = g1.append(g2) np.testing.assert_array_equal(block_diag(d1,d2),h4.D) def test_remove_useless_states(self): """Regression: _remove_useless_states gives correct ABC sizes""" g1 = StateSpace(np.zeros((3,3)), np.zeros((3,4)), np.zeros((5,3)), np.zeros((5,4))) self.assertEqual((0,0), g1.A.shape) self.assertEqual((0,4), g1.B.shape) self.assertEqual((5,0), g1.C.shape) self.assertEqual((5,4), g1.D.shape) self.assertEqual(0, g1.states) def test_BadEmptyMatrices(self): """Mismatched ABCD matrices when some are empty""" self.assertRaises(ValueError,StateSpace, [1], [], [], [1]) self.assertRaises(ValueError,StateSpace, [1], [1], [], [1]) self.assertRaises(ValueError,StateSpace, [1], [], [1], [1]) self.assertRaises(ValueError,StateSpace, [], [1], [], [1]) self.assertRaises(ValueError,StateSpace, [], [1], [1], [1]) self.assertRaises(ValueError,StateSpace, [], [], [1], [1]) self.assertRaises(ValueError,StateSpace, [1], [1], [1], []) def test_minrealStaticGain(self): """Regression: minreal on static gain was failing""" g1 = StateSpace([],[],[],[1]) g2 = g1.minreal() np.testing.assert_array_equal(g1.A, g2.A) np.testing.assert_array_equal(g1.B, g2.B) np.testing.assert_array_equal(g1.C, g2.C) np.testing.assert_array_equal(g1.D, g2.D)
#!/usr/bin/env python # # slycot_convert_test.py - test SLICOT-based conversions # RMM, 30 Mar 2011 (based on TestSlycot from v0.4a) from __future__ import print_function import unittest import numpy as np from control import matlab from control.exception import slycot_check @unittest.skipIf(not slycot_check(), "slycot not installed") class TestSlycot(unittest.TestCase): """TestSlycot compares transfer function and state space conversions for various numbers of inputs,outputs and states. 1. Usually passes for SISO systems of any state dim, occasonally, there will be a dimension mismatch if the original randomly generated ss system is not minimal because td04ad returns a minimal system. 2. For small systems with many inputs, n<<m, the tests fail because td04ad returns a minimal ss system which has fewer states than the original system. It is typical for systems with many more inputs than states to have extraneous states. 3. For systems with larger dimensions, n~>5 and with 2 or more outputs the conversion to statespace (td04ad) intermittently results in an equivalent realization of higher order than the original tf order. We think this has to do with minimu realization tolerances in the Fortran. The algorithm doesn't
class TestAugw(unittest.TestCase): "Test control.robust.augw" # tolerance for system equality TOL = 1e-8 def siso_almost_equal(self,g,h): """siso_almost_equal(g,h) -> None Raises AssertionError if g and h, two SISO LTI objects, are not almost equal""" from control import tf, minreal gmh = tf(minreal(g-h,verbose=False)) if not (gmh.num[0][0]<self.TOL).all(): maxnum = max(abs(gmh.num[0][0])) raise AssertionError('systems not approx equal; max num. coeff is {}\nsys 1:\n{}\nsys 2:\n{}'.format(maxnum,g,h)) @unittest.skipIf(not slycot_check(), "slycot not installed") def testSisoW1(self): "SISO plant with S weighting" from control import augw, ss g = ss([-1.],[1.],[1.],[1.]) w1 = ss([-2],[2.],[1.],[2.]) p = augw(g,w1) self.assertEqual(2,p.outputs) self.assertEqual(2,p.inputs) # w->z1 should be w1 self.siso_almost_equal(w1,p[0,0]) # w->v should be 1 self.siso_almost_equal(ss([],[],[],[1]),p[1,0]) # u->z1 should be -w1*g self.siso_almost_equal(-w1*g,p[0,1]) # u->v should be -g self.siso_almost_equal(-g,p[1,1]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testSisoW2(self): "SISO plant with KS weighting" from control import augw, ss g = ss([-1.],[1.],[1.],[1.]) w2 = ss([-2],[1.],[1.],[2.]) p = augw(g,w2=w2) self.assertEqual(2,p.outputs) self.assertEqual(2,p.inputs) # w->z2 should be 0 self.siso_almost_equal(ss([],[],[],0),p[0,0]) # w->v should be 1 self.siso_almost_equal(ss([],[],[],[1]),p[1,0]) # u->z2 should be w2 self.siso_almost_equal(w2,p[0,1]) # u->v should be -g self.siso_almost_equal(-g,p[1,1]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testSisoW3(self): "SISO plant with T weighting" from control import augw, ss g = ss([-1.],[1.],[1.],[1.]) w3 = ss([-2],[1.],[1.],[2.]) p = augw(g,w3=w3) self.assertEqual(2,p.outputs) self.assertEqual(2,p.inputs) # w->z3 should be 0 self.siso_almost_equal(ss([],[],[],0),p[0,0]) # w->v should be 1 self.siso_almost_equal(ss([],[],[],[1]),p[1,0]) # u->z3 should be w3*g self.siso_almost_equal(w3*g,p[0,1]) # u->v should be -g self.siso_almost_equal(-g,p[1,1]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testSisoW123(self): "SISO plant with all weights" from control import augw, ss g = ss([-1.],[1.],[1.],[1.]) w1 = ss([-2.],[2.],[1.],[2.]) w2 = ss([-3.],[3.],[1.],[3.]) w3 = ss([-4.],[4.],[1.],[4.]) p = augw(g,w1,w2,w3) self.assertEqual(4,p.outputs) self.assertEqual(2,p.inputs) # w->z1 should be w1 self.siso_almost_equal(w1,p[0,0]) # w->z2 should be 0 self.siso_almost_equal(0,p[1,0]) # w->z3 should be 0 self.siso_almost_equal(0,p[2,0]) # w->v should be 1 self.siso_almost_equal(ss([],[],[],[1]),p[3,0]) # u->z1 should be -w1*g self.siso_almost_equal(-w1*g,p[0,1]) # u->z2 should be w2 self.siso_almost_equal(w2,p[1,1]) # u->z3 should be w3*g self.siso_almost_equal(w3*g,p[2,1]) # u->v should be -g self.siso_almost_equal(-g,p[3,1]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testMimoW1(self): "MIMO plant with S weighting" from control import augw, ss g = ss([[-1.,-2],[-3,-4]], [[1.,0.],[0.,1.]], [[1.,0.],[0.,1.]], [[1.,0.],[0.,1.]]) w1 = ss([-2],[2.],[1.],[2.]) p = augw(g,w1) self.assertEqual(4,p.outputs) self.assertEqual(4,p.inputs) # w->z1 should be diag(w1,w1) self.siso_almost_equal(w1,p[0,0]) self.siso_almost_equal(0, p[0,1]) self.siso_almost_equal(0, p[1,0]) self.siso_almost_equal(w1,p[1,1]) # w->v should be I self.siso_almost_equal(1, p[2,0]) self.siso_almost_equal(0, p[2,1]) self.siso_almost_equal(0, p[3,0]) self.siso_almost_equal(1, p[3,1]) # u->z1 should be -w1*g self.siso_almost_equal(-w1*g[0,0],p[0,2]) self.siso_almost_equal(-w1*g[0,1],p[0,3]) self.siso_almost_equal(-w1*g[1,0],p[1,2]) self.siso_almost_equal(-w1*g[1,1],p[1,3]) # # u->v should be -g self.siso_almost_equal(-g[0,0],p[2,2]) self.siso_almost_equal(-g[0,1],p[2,3]) self.siso_almost_equal(-g[1,0],p[3,2]) self.siso_almost_equal(-g[1,1],p[3,3]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testMimoW2(self): "MIMO plant with KS weighting" from control import augw, ss g = ss([[-1.,-2],[-3,-4]], [[1.,0.],[0.,1.]], [[1.,0.],[0.,1.]], [[1.,0.],[0.,1.]]) w2 = ss([-2],[2.],[1.],[2.]) p = augw(g,w2=w2) self.assertEqual(4,p.outputs) self.assertEqual(4,p.inputs) # w->z2 should be 0 self.siso_almost_equal(0, p[0,0]) self.siso_almost_equal(0, p[0,1]) self.siso_almost_equal(0, p[1,0]) self.siso_almost_equal(0, p[1,1]) # w->v should be I self.siso_almost_equal(1, p[2,0]) self.siso_almost_equal(0, p[2,1]) self.siso_almost_equal(0, p[3,0]) self.siso_almost_equal(1, p[3,1]) # u->z2 should be w2 self.siso_almost_equal(w2, p[0,2]) self.siso_almost_equal(0, p[0,3]) self.siso_almost_equal(0, p[1,2]) self.siso_almost_equal(w2, p[1,3]) # # u->v should be -g self.siso_almost_equal(-g[0,0], p[2,2]) self.siso_almost_equal(-g[0,1], p[2,3]) self.siso_almost_equal(-g[1,0], p[3,2]) self.siso_almost_equal(-g[1,1], p[3,3]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testMimoW3(self): "MIMO plant with T weighting" from control import augw, ss g = ss([[-1.,-2],[-3,-4]], [[1.,0.],[0.,1.]], [[1.,0.],[0.,1.]], [[1.,0.],[0.,1.]]) w3 = ss([-2],[2.],[1.],[2.]) p = augw(g,w3=w3) self.assertEqual(4,p.outputs) self.assertEqual(4,p.inputs) # w->z3 should be 0 self.siso_almost_equal(0, p[0,0]) self.siso_almost_equal(0, p[0,1]) self.siso_almost_equal(0, p[1,0]) self.siso_almost_equal(0, p[1,1]) # w->v should be I self.siso_almost_equal(1, p[2,0]) self.siso_almost_equal(0, p[2,1]) self.siso_almost_equal(0, p[3,0]) self.siso_almost_equal(1, p[3,1]) # u->z3 should be w3*g self.siso_almost_equal(w3*g[0,0], p[0,2]) self.siso_almost_equal(w3*g[0,1], p[0,3]) self.siso_almost_equal(w3*g[1,0], p[1,2]) self.siso_almost_equal(w3*g[1,1], p[1,3]) # # u->v should be -g self.siso_almost_equal(-g[0,0], p[2,2]) self.siso_almost_equal(-g[0,1], p[2,3]) self.siso_almost_equal(-g[1,0], p[3,2]) self.siso_almost_equal(-g[1,1], p[3,3]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testMimoW123(self): "MIMO plant with all weights" from control import augw, ss, append g = ss([[-1.,-2],[-3,-4]], [[1.,0.],[0.,1.]], [[1.,0.],[0.,1.]], [[1.,0.],[0.,1.]]) # this should be expaned to w1*I w1 = ss([-2.],[2.],[1.],[2.]) # diagonal weighting w2 = append(ss([-3.],[3.],[1.],[3.]), ss([-4.],[4.],[1.],[4.])) # full weighting w3 = ss([[-4.,-5],[-6,-7]], [[2.,3.],[5.,7.]], [[11.,13.],[17.,19.]], [[23.,29.],[31.,37.]]) p = augw(g,w1,w2,w3) self.assertEqual(8,p.outputs) self.assertEqual(4,p.inputs) # w->z1 should be w1 self.siso_almost_equal(w1, p[0,0]) self.siso_almost_equal(0, p[0,1]) self.siso_almost_equal(0, p[1,0]) self.siso_almost_equal(w1, p[1,1]) # w->z2 should be 0 self.siso_almost_equal(0, p[2,0]) self.siso_almost_equal(0, p[2,1]) self.siso_almost_equal(0, p[3,0]) self.siso_almost_equal(0, p[3,1]) # w->z3 should be 0 self.siso_almost_equal(0, p[4,0]) self.siso_almost_equal(0, p[4,1]) self.siso_almost_equal(0, p[5,0]) self.siso_almost_equal(0, p[5,1]) # w->v should be I self.siso_almost_equal(1, p[6,0]) self.siso_almost_equal(0, p[6,1]) self.siso_almost_equal(0, p[7,0]) self.siso_almost_equal(1, p[7,1]) # u->z1 should be -w1*g self.siso_almost_equal(-w1*g[0,0], p[0,2]) self.siso_almost_equal(-w1*g[0,1], p[0,3]) self.siso_almost_equal(-w1*g[1,0], p[1,2]) self.siso_almost_equal(-w1*g[1,1], p[1,3]) # u->z2 should be w2 self.siso_almost_equal(w2[0,0], p[2,2]) self.siso_almost_equal(w2[0,1], p[2,3]) self.siso_almost_equal(w2[1,0], p[3,2]) self.siso_almost_equal(w2[1,1], p[3,3]) # u->z3 should be w3*g w3g = w3*g; self.siso_almost_equal(w3g[0,0], p[4,2]) self.siso_almost_equal(w3g[0,1], p[4,3]) self.siso_almost_equal(w3g[1,0], p[5,2]) self.siso_almost_equal(w3g[1,1], p[5,3]) # u->v should be -g self.siso_almost_equal(-g[0,0], p[6,2]) self.siso_almost_equal(-g[0,1], p[6,3]) self.siso_almost_equal(-g[1,0], p[7,2]) self.siso_almost_equal(-g[1,1], p[7,3]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testErrors(self): "Error cases handled" from control import augw,ss # no weights g1by1 = ss(-1,1,1,0) g2by2 = ss(-np.eye(2),np.eye(2),np.eye(2),np.zeros((2,2))) self.assertRaises(ValueError,augw,g1by1) # mismatched size of weight and plant self.assertRaises(ValueError,augw,g1by1,w1=g2by2) self.assertRaises(ValueError,augw,g1by1,w2=g2by2) self.assertRaises(ValueError,augw,g1by1,w3=g2by2)
#!/usr/bin/env python # # minreal_test.py - test state space class # Rvp, 13 Jun 2013 import unittest import numpy as np from scipy.linalg import eigvals from control import matlab from control.statesp import StateSpace from control.xferfcn import TransferFunction from itertools import permutations from control.exception import slycot_check @unittest.skipIf(not slycot_check(), "slycot not installed") class TestMinreal(unittest.TestCase): """Tests for the StateSpace class.""" def setUp(self): np.random.seed(5) # depending on the seed and minreal performance, a number of # reductions is produced. If random gen or minreal change, this # will be likely to fail self.nreductions = 0 def assert_numden_almost_equal(self, n1, n2, d1, d2): n1[np.abs(n1) < 1e-10] = 0. n1 = np.trim_zeros(n1) d1[np.abs(d1) < 1e-10] = 0. d1 = np.trim_zeros(d1) n2[np.abs(n2) < 1e-10] = 0.
class TestStateSpace(unittest.TestCase): """Tests for the StateSpace class.""" def setUp(self): """Set up a MIMO system to test operations on.""" use_numpy_matrix(False) # sys1: 3-states square system (2 inputs x 2 outputs) A322 = [[-3., 4., 2.], [-1., -3., 0.], [2., 5., 3.]] B322 = [[1., 4.], [-3., -3.], [-2., 1.]] C322 = [[4., 2., -3.], [1., 4., 3.]] D322 = [[-2., 4.], [0., 1.]] self.sys322 = StateSpace(A322, B322, C322, D322) # sys1: 2-states square system (2 inputs x 2 outputs) A222 = [[4., 1.], [2., -3]] B222 = [[5., 2.], [-3., -3.]] C222 = [[2., -4], [0., 1.]] D222 = [[3., 2.], [1., -1.]] self.sys222 = StateSpace(A222, B222, C222, D222) # sys3: 6 states non square system (2 inputs x 3 outputs) A623 = np.array([[1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 0, 3, 0, 0, 0], [0, 0, 0, -4, 0, 0], [0, 0, 0, 0, -1, 0], [0, 0, 0, 0, 0, 3]]) B623 = np.array([[0, -1], [-1, 0], [1, -1], [0, 0], [0, 1], [-1, -1]]) C623 = np.array([[1, 0, 0, 1, 0, 0], [0, 1, 0, 1, 0, 1], [0, 0, 1, 0, 0, 1]]) D623 = np.zeros((3, 2)) self.sys623 = StateSpace(A623, B623, C623, D623) def test_matlab_style_constructor(self): # Use (deprecated?) matrix-style construction string (w/ warnings off) import warnings warnings.filterwarnings("ignore") # turn off warnings sys = StateSpace("-1 1; 0 2", "0; 1", "1, 0", "0") warnings.resetwarnings() # put things back to original state self.assertEqual(sys.A.shape, (2, 2)) self.assertEqual(sys.B.shape, (2, 1)) self.assertEqual(sys.C.shape, (1, 2)) self.assertEqual(sys.D.shape, (1, 1)) if defaults['statesp.use_numpy_matrix']: for X in [sys.A, sys.B, sys.C, sys.D]: self.assertTrue(isinstance(X, np.matrix)) else: for X in [sys.A, sys.B, sys.C, sys.D]: self.assertTrue(isinstance(X, np.ndarray)) def test_pole(self): """Evaluate the poles of a MIMO system.""" p = np.sort(self.sys322.pole()) true_p = np.sort([ 3.34747678408874, -3.17373839204437 + 1.47492908003839j, -3.17373839204437 - 1.47492908003839j ]) np.testing.assert_array_almost_equal(p, true_p) def test_zero_empty(self): """Test to make sure zero() works with no zeros in system.""" sys = _convertToStateSpace(TransferFunction([1], [1, 2, 1])) np.testing.assert_array_equal(sys.zero(), np.array([])) @unittest.skipIf(not slycot_check(), "slycot not installed") def test_zero_siso(self): """Evaluate the zeros of a SISO system.""" # extract only first input / first output system of sys222. This system is denoted sys111 # or tf111 tf111 = ss2tf(self.sys222) sys111 = tf2ss(tf111[0, 0]) # compute zeros as root of the characteristic polynomial at the numerator of tf111 # this method is simple and assumed as valid in this test true_z = np.sort(tf111[0, 0].zero()) # Compute the zeros through ab08nd, which is tested here z = np.sort(sys111.zero()) np.testing.assert_almost_equal(true_z, z) @unittest.skipIf(not slycot_check(), "slycot not installed") def test_zero_mimo_sys322_square(self): """Evaluate the zeros of a square MIMO system.""" z = np.sort(self.sys322.zero()) true_z = np.sort([44.41465, -0.490252, -5.924398]) np.testing.assert_array_almost_equal(z, true_z) @unittest.skipIf(not slycot_check(), "slycot not installed") def test_zero_mimo_sys222_square(self): """Evaluate the zeros of a square MIMO system.""" z = np.sort(self.sys222.zero()) true_z = np.sort([-10.568501, 3.368501]) np.testing.assert_array_almost_equal(z, true_z) @unittest.skipIf(not slycot_check(), "slycot not installed") def test_zero_mimo_sys623_non_square(self): """Evaluate the zeros of a non square MIMO system.""" z = np.sort(self.sys623.zero()) true_z = np.sort([2., -1.]) np.testing.assert_array_almost_equal(z, true_z) def test_add_ss(self): """Add two MIMO systems.""" A = [[-3., 4., 2., 0., 0.], [-1., -3., 0., 0., 0.], [2., 5., 3., 0., 0.], [0., 0., 0., 4., 1.], [0., 0., 0., 2., -3.]] B = [[1., 4.], [-3., -3.], [-2., 1.], [5., 2.], [-3., -3.]] C = [[4., 2., -3., 2., -4.], [1., 4., 3., 0., 1.]] D = [[1., 6.], [1., 0.]] sys = self.sys322 + self.sys222 np.testing.assert_array_almost_equal(sys.A, A) np.testing.assert_array_almost_equal(sys.B, B) np.testing.assert_array_almost_equal(sys.C, C) np.testing.assert_array_almost_equal(sys.D, D) def test_subtract_ss(self): """Subtract two MIMO systems.""" A = [[-3., 4., 2., 0., 0.], [-1., -3., 0., 0., 0.], [2., 5., 3., 0., 0.], [0., 0., 0., 4., 1.], [0., 0., 0., 2., -3.]] B = [[1., 4.], [-3., -3.], [-2., 1.], [5., 2.], [-3., -3.]] C = [[4., 2., -3., -2., 4.], [1., 4., 3., 0., -1.]] D = [[-5., 2.], [-1., 2.]] sys = self.sys322 - self.sys222 np.testing.assert_array_almost_equal(sys.A, A) np.testing.assert_array_almost_equal(sys.B, B) np.testing.assert_array_almost_equal(sys.C, C) np.testing.assert_array_almost_equal(sys.D, D) def test_multiply_ss(self): """Multiply two MIMO systems.""" A = [[4., 1., 0., 0., 0.], [2., -3., 0., 0., 0.], [2., 0., -3., 4., 2.], [-6., 9., -1., -3., 0.], [-4., 9., 2., 5., 3.]] B = [[5., 2.], [-3., -3.], [7., -2.], [-12., -3.], [-5., -5.]] C = [[-4., 12., 4., 2., -3.], [0., 1., 1., 4., 3.]] D = [[-2., -8.], [1., -1.]] sys = self.sys322 * self.sys222 np.testing.assert_array_almost_equal(sys.A, A) np.testing.assert_array_almost_equal(sys.B, B) np.testing.assert_array_almost_equal(sys.C, C) np.testing.assert_array_almost_equal(sys.D, D) def test_evalfr(self): """Evaluate the frequency response at one frequency.""" A = [[-2, 0.5], [0.5, -0.3]] B = [[0.3, -1.3], [0.1, 0.]] C = [[0., 0.1], [-0.3, -0.2]] D = [[0., -0.8], [-0.3, 0.]] sys = StateSpace(A, B, C, D) resp = [[ 4.37636761487965e-05 - 0.0152297592997812j, -0.792603938730853 + 0.0261706783369803j ], [ -0.331544857768052 + 0.0576105032822757j, 0.128919037199125 - 0.143824945295405j ]] # Correct versions of the call np.testing.assert_almost_equal(evalfr(sys, 1j), resp) np.testing.assert_almost_equal(sys._evalfr(1.), resp) # Deprecated version of the call (should generate warning) import warnings with warnings.catch_warnings(record=True) as w: # Set up warnings filter to only show warnings in control module warnings.filterwarnings("ignore") warnings.filterwarnings("always", module="control") # Make sure that we get a pending deprecation warning sys.evalfr(1.) assert len(w) == 1 assert issubclass(w[-1].category, PendingDeprecationWarning) # Leave the warnings filter like we found it warnings.resetwarnings() @unittest.skipIf(not slycot_check(), "slycot not installed") def test_freq_resp(self): """Evaluate the frequency response at multiple frequencies.""" A = [[-2, 0.5], [0.5, -0.3]] B = [[0.3, -1.3], [0.1, 0.]] C = [[0., 0.1], [-0.3, -0.2]] D = [[0., -0.8], [-0.3, 0.]] sys = StateSpace(A, B, C, D) true_mag = [[[0.0852992637230322, 0.00103596611395218], [0.935374692849736, 0.799380720864549]], [[0.55656854563842, 0.301542699860857], [0.609178071542849, 0.0382108097985257]]] true_phase = [[[-0.566195599644593, -1.68063565332582], [3.0465958317514, 3.14141384339534]], [[2.90457947657161, 3.10601268291914], [-0.438157380501337, -1.40720969147217]]] true_omega = [0.1, 10.] mag, phase, omega = sys.freqresp(true_omega) np.testing.assert_almost_equal(mag, true_mag) np.testing.assert_almost_equal(phase, true_phase) np.testing.assert_equal(omega, true_omega) @unittest.skipIf(not slycot_check(), "slycot not installed") def test_minreal(self): """Test a minreal model reduction.""" # A = [-2, 0.5, 0; 0.5, -0.3, 0; 0, 0, -0.1] A = [[-2, 0.5, 0], [0.5, -0.3, 0], [0, 0, -0.1]] # B = [0.3, -1.3; 0.1, 0; 1, 0] B = [[0.3, -1.3], [0.1, 0.], [1.0, 0.0]] # C = [0, 0.1, 0; -0.3, -0.2, 0] C = [[0., 0.1, 0.0], [-0.3, -0.2, 0.0]] # D = [0 -0.8; -0.3 0] D = [[0., -0.8], [-0.3, 0.]] # sys = ss(A, B, C, D) sys = StateSpace(A, B, C, D) sysr = sys.minreal() self.assertEqual(sysr.states, 2) self.assertEqual(sysr.inputs, sys.inputs) self.assertEqual(sysr.outputs, sys.outputs) np.testing.assert_array_almost_equal(eigvals(sysr.A), [-2.136154, -0.1638459]) def test_append_ss(self): """Test appending two state-space systems.""" A1 = [[-2, 0.5, 0], [0.5, -0.3, 0], [0, 0, -0.1]] B1 = [[0.3, -1.3], [0.1, 0.], [1.0, 0.0]] C1 = [[0., 0.1, 0.0], [-0.3, -0.2, 0.0]] D1 = [[0., -0.8], [-0.3, 0.]] A2 = [[-1.]] B2 = [[1.2]] C2 = [[0.5]] D2 = [[0.4]] A3 = [[-2, 0.5, 0, 0], [0.5, -0.3, 0, 0], [0, 0, -0.1, 0], [0, 0, 0., -1.]] B3 = [[0.3, -1.3, 0], [0.1, 0., 0], [1.0, 0.0, 0], [0., 0, 1.2]] C3 = [[0., 0.1, 0.0, 0.0], [-0.3, -0.2, 0.0, 0.0], [0., 0., 0., 0.5]] D3 = [[0., -0.8, 0.], [-0.3, 0., 0.], [0., 0., 0.4]] sys1 = StateSpace(A1, B1, C1, D1) sys2 = StateSpace(A2, B2, C2, D2) sys3 = StateSpace(A3, B3, C3, D3) sys3c = sys1.append(sys2) np.testing.assert_array_almost_equal(sys3.A, sys3c.A) np.testing.assert_array_almost_equal(sys3.B, sys3c.B) np.testing.assert_array_almost_equal(sys3.C, sys3c.C) np.testing.assert_array_almost_equal(sys3.D, sys3c.D) def test_append_tf(self): """Test appending a state-space system with a tf""" A1 = [[-2, 0.5, 0], [0.5, -0.3, 0], [0, 0, -0.1]] B1 = [[0.3, -1.3], [0.1, 0.], [1.0, 0.0]] C1 = [[0., 0.1, 0.0], [-0.3, -0.2, 0.0]] D1 = [[0., -0.8], [-0.3, 0.]] s = TransferFunction([1, 0], [1]) h = 1 / (s + 1) / (s + 2) sys1 = StateSpace(A1, B1, C1, D1) sys2 = _convertToStateSpace(h) sys3c = sys1.append(sys2) np.testing.assert_array_almost_equal(sys1.A, sys3c.A[:3, :3]) np.testing.assert_array_almost_equal(sys1.B, sys3c.B[:3, :2]) np.testing.assert_array_almost_equal(sys1.C, sys3c.C[:2, :3]) np.testing.assert_array_almost_equal(sys1.D, sys3c.D[:2, :2]) np.testing.assert_array_almost_equal(sys2.A, sys3c.A[3:, 3:]) np.testing.assert_array_almost_equal(sys2.B, sys3c.B[3:, 2:]) np.testing.assert_array_almost_equal(sys2.C, sys3c.C[2:, 3:]) np.testing.assert_array_almost_equal(sys2.D, sys3c.D[2:, 2:]) np.testing.assert_array_almost_equal(sys3c.A[:3, 3:], np.zeros((3, 2))) np.testing.assert_array_almost_equal(sys3c.A[3:, :3], np.zeros((2, 3))) def test_array_access_ss(self): sys1 = StateSpace([[1., 2.], [3., 4.]], [[5., 6.], [6., 8.]], [[9., 10.], [11., 12.]], [[13., 14.], [15., 16.]], 1) sys1_11 = sys1[0, 1] np.testing.assert_array_almost_equal(sys1_11.A, sys1.A) np.testing.assert_array_almost_equal(sys1_11.B, sys1.B[:, [1]]) np.testing.assert_array_almost_equal(sys1_11.C, sys1.C[[0], :]) np.testing.assert_array_almost_equal(sys1_11.D, sys1.D[0, 1]) assert sys1.dt == sys1_11.dt def test_dc_gain_cont(self): """Test DC gain for continuous-time state-space systems.""" sys = StateSpace(-2., 6., 5., 0) np.testing.assert_equal(sys.dcgain(), 15.) sys2 = StateSpace(-2, [[6., 4.]], [[5.], [7.], [11]], np.zeros((3, 2))) expected = np.array([[15., 10.], [21., 14.], [33., 22.]]) np.testing.assert_array_equal(sys2.dcgain(), expected) sys3 = StateSpace(0., 1., 1., 0.) np.testing.assert_equal(sys3.dcgain(), np.nan) def test_dc_gain_discr(self): """Test DC gain for discrete-time state-space systems.""" # static gain sys = StateSpace([], [], [], 2, True) np.testing.assert_equal(sys.dcgain(), 2) # averaging filter sys = StateSpace(0.5, 0.5, 1, 0, True) np.testing.assert_almost_equal(sys.dcgain(), 1) # differencer sys = StateSpace(0, 1, -1, 1, True) np.testing.assert_equal(sys.dcgain(), 0) # summer sys = StateSpace(1, 1, 1, 0, True) np.testing.assert_equal(sys.dcgain(), np.nan) def test_dc_gain_integrator(self): """DC gain when eigenvalue at DC returns appropriately sized array of nan.""" # the SISO case is also tested in test_dc_gain_{cont,discr} import itertools # iterate over input and output sizes, and continuous (dt=None) and discrete (dt=True) time for inputs, outputs, dt in itertools.product(range(1, 6), range(1, 6), [None, True]): states = max(inputs, outputs) # a matrix that is singular at DC, and has no "useless" states as in # _remove_useless_states a = np.triu(np.tile(2, (states, states))) # eigenvalues all +2, except for ... a[0, 0] = 0 if dt is None else 1 b = np.eye(max(inputs, states))[:states, :inputs] c = np.eye(max(outputs, states))[:outputs, :states] d = np.zeros((outputs, inputs)) sys = StateSpace(a, b, c, d, dt) dc = np.squeeze(np.tile(np.nan, (outputs, inputs))) np.testing.assert_array_equal(dc, sys.dcgain()) def test_scalar_static_gain(self): """Regression: can we create a scalar static gain?""" g1 = StateSpace([], [], [], [2]) g2 = StateSpace([], [], [], [3]) # make sure StateSpace internals, specifically ABC matrix # sizes, are OK for LTI operations g3 = g1 * g2 self.assertEqual(6, g3.D[0, 0]) g4 = g1 + g2 self.assertEqual(5, g4.D[0, 0]) g5 = g1.feedback(g2) np.testing.assert_array_almost_equal(2. / 7, g5.D[0, 0]) g6 = g1.append(g2) np.testing.assert_array_equal(np.diag([2, 3]), g6.D) def test_matrix_static_gain(self): """Regression: can we create matrix static gains?""" d1 = np.array([[1, 2, 3], [4, 5, 6]]) d2 = np.array([[7, 8], [9, 10], [11, 12]]) g1 = StateSpace([], [], [], d1) # _remove_useless_states was making A = [[0]] self.assertEqual((0, 0), g1.A.shape) g2 = StateSpace([], [], [], d2) g3 = StateSpace([], [], [], d2.T) h1 = g1 * g2 np.testing.assert_array_equal(np.dot(d1, d2), h1.D) h2 = g1 + g3 np.testing.assert_array_equal(d1 + d2.T, h2.D) h3 = g1.feedback(g2) np.testing.assert_array_almost_equal( solve(np.eye(2) + np.dot(d1, d2), d1), h3.D) h4 = g1.append(g2) np.testing.assert_array_equal(block_diag(d1, d2), h4.D) def test_remove_useless_states(self): """Regression: _remove_useless_states gives correct ABC sizes.""" g1 = StateSpace(np.zeros((3, 3)), np.zeros((3, 4)), np.zeros((5, 3)), np.zeros((5, 4))) self.assertEqual((0, 0), g1.A.shape) self.assertEqual((0, 4), g1.B.shape) self.assertEqual((5, 0), g1.C.shape) self.assertEqual((5, 4), g1.D.shape) self.assertEqual(0, g1.states) def test_bad_empty_matrices(self): """Mismatched ABCD matrices when some are empty.""" self.assertRaises(ValueError, StateSpace, [1], [], [], [1]) self.assertRaises(ValueError, StateSpace, [1], [1], [], [1]) self.assertRaises(ValueError, StateSpace, [1], [], [1], [1]) self.assertRaises(ValueError, StateSpace, [], [1], [], [1]) self.assertRaises(ValueError, StateSpace, [], [1], [1], [1]) self.assertRaises(ValueError, StateSpace, [], [], [1], [1]) self.assertRaises(ValueError, StateSpace, [1], [1], [1], []) def test_minreal_static_gain(self): """Regression: minreal on static gain was failing.""" g1 = StateSpace([], [], [], [1]) g2 = g1.minreal() np.testing.assert_array_equal(g1.A, g2.A) np.testing.assert_array_equal(g1.B, g2.B) np.testing.assert_array_equal(g1.C, g2.C) np.testing.assert_array_equal(g1.D, g2.D) def test_empty(self): """Regression: can we create an empty StateSpace object?""" g1 = StateSpace([], [], [], []) self.assertEqual(0, g1.states) self.assertEqual(0, g1.inputs) self.assertEqual(0, g1.outputs) def test_matrix_to_state_space(self): """_convertToStateSpace(matrix) gives ss([],[],[],D)""" D = np.array([[1, 2, 3], [4, 5, 6]]) g = _convertToStateSpace(D) def empty(shape): m = np.array([]) m.shape = shape return m np.testing.assert_array_equal(empty((0, 0)), g.A) np.testing.assert_array_equal(empty((0, D.shape[1])), g.B) np.testing.assert_array_equal(empty((D.shape[0], 0)), g.C) np.testing.assert_array_equal(D, g.D) def test_lft(self): """ test lft function with result obtained from matlab implementation""" # test case A = [[1, 2, 3], [1, 4, 5], [2, 3, 4]] B = [[0, 2], [5, 6], [5, 2]] C = [[1, 4, 5], [2, 3, 0]] D = [[0, 0], [3, 0]] P = StateSpace(A, B, C, D) Ak = [[0, 2, 3], [2, 3, 5], [2, 1, 9]] Bk = [[1, 1], [2, 3], [9, 4]] Ck = [[1, 4, 5], [2, 3, 6]] Dk = [[0, 2], [0, 0]] K = StateSpace(Ak, Bk, Ck, Dk) # case 1 pk = P.lft(K, 2, 1) Amatlab = [ 1, 2, 3, 4, 6, 12, 1, 4, 5, 17, 38, 61, 2, 3, 4, 9, 26, 37, 2, 3, 0, 3, 14, 18, 4, 6, 0, 8, 27, 35, 18, 27, 0, 29, 109, 144 ] Bmatlab = [0, 10, 10, 7, 15, 58] Cmatlab = [1, 4, 5, 0, 0, 0] Dmatlab = [0] np.testing.assert_allclose(np.array(pk.A).reshape(-1), Amatlab) np.testing.assert_allclose(np.array(pk.B).reshape(-1), Bmatlab) np.testing.assert_allclose(np.array(pk.C).reshape(-1), Cmatlab) np.testing.assert_allclose(np.array(pk.D).reshape(-1), Dmatlab) # case 2 pk = P.lft(K) Amatlab = [ 1, 2, 3, 4, 6, 12, -3, -2, 5, 11, 14, 31, -2, -3, 4, 3, 2, 7, 0.6, 3.4, 5, -0.6, -0.4, 0, 0.8, 6.2, 10, 0.2, -4.2, -4, 7.4, 33.6, 45, -0.4, -8.6, -3 ] Bmatlab = [] Cmatlab = [] Dmatlab = [] np.testing.assert_allclose(np.array(pk.A).reshape(-1), Amatlab) np.testing.assert_allclose(np.array(pk.B).reshape(-1), Bmatlab) np.testing.assert_allclose(np.array(pk.C).reshape(-1), Cmatlab) np.testing.assert_allclose(np.array(pk.D).reshape(-1), Dmatlab) def test_horner(self): """Test horner() function""" # Make sure we can compute the transfer function at a complex value self.sys322.horner(1. + 1.j) # Make sure result agrees with frequency response mag, phase, omega = self.sys322.freqresp([1]) np.testing.assert_array_almost_equal( self.sys322.horner(1.j), mag[:, :, 0] * np.exp(1.j * phase[:, :, 0])) def tearDown(self): reset_defaults() # reset configuration defaults
# System matrices A = np.matrix([[1, -1, 1.], [1, -k / m, -b / m], [1, 1, 1]]) B = np.matrix([[0], [1 / m], [1]]) C = np.matrix([[1., 0, 1.]]) sys = ss(A, B, C, 0) # Python control may be used without slycot, for example for a pole placement. # Eigenvalue placement w = [-3, -2, -1] K = place(A, B, w) print("[python-control (from scipy)] K = ", K) print("[python-control (from scipy)] eigs = ", np.linalg.eig(A - B * K)[0]) # Before using one of its routine, check that slycot is installed. w = np.array([-3, -2, -1]) if slycot_check(): # Import routine sb01bd used for pole placement. from slycot import sb01bd n = 3 # Number of states m = 1 # Number of inputs npp = 3 # Number of placed eigen values alpha = 1 # Maximum threshold for eigen values dico = 'D' # Discrete system _, _, _, _, _, K, _ = sb01bd(n, m, npp, alpha, A, B, w, dico, tol=0.0, ldwork=None) print("[slycot] K = ", K) print("[slycot] eigs = ", np.linalg.eig(A + np.dot(B, K))[0]) else: print("Slycot is not installed.")