def sys222(self): """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.]] return StateSpace(A222, B222, C222, D222)
def test_copy_constructor(self): """Test the copy constructor""" # Create a set of matrices for a simple linear system A = np.array([[-1]]) B = np.array([[1]]) C = np.array([[1]]) D = np.array([[0]]) # Create the first linear system and a copy linsys = StateSpace(A, B, C, D) cpysys = StateSpace(linsys) # Change the original A matrix A[0, 0] = -2 np.testing.assert_allclose(linsys.A, [[-1]]) # original value np.testing.assert_allclose(cpysys.A, [[-1]]) # original value # Change the A matrix for the original system linsys.A[0, 0] = -3 np.testing.assert_allclose(cpysys.A, [[-1]]) # original value
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))) assert (0, 0) == g1.A.shape assert (0, 4) == g1.B.shape assert (5, 0) == g1.C.shape assert (5, 4) == g1.D.shape assert 0 == g1.states
def mimoss(self, request): """Test system with various dt values""" n = 5 m = 3 p = 2 bx, bu = np.mgrid[1:n + 1, 1:m + 1] cy, cx = np.mgrid[1:p + 1, 1:n + 1] dy, du = np.mgrid[1:p + 1, 1:m + 1] return StateSpace( np.eye(5) + np.eye(5, 5, 1), bx * bu, cy * cx, dy * du, request.param)
def setUp(self): self.sys1 = TransferFunction([1, 2], [1, 2, 3]) self.sys2 = TransferFunction([1], [1, 2, 3, 4]) self.sys3 = StateSpace([[1., 4.], [3., 2.]], [[1.], [-4.]], [[1., 0.]], [[0.]]) s = TransferFunction([1, 0], [1]) self.sys4 = (8.75*(4*s**2+0.4*s+1))/((100*s+1)*(s**2+0.22*s+1)) * \ 1./(s**2/(10.**2)+2*0.04*s/10.+1) self.stability_margins4 = [ 2.2716, 97.5941, 1.0454, 10.0053, 0.0850, 0.4973 ]
def test_constructor(self, sys322ABCD, dt, argfun): """Test different ways to call the StateSpace() constructor""" args, kwargs = argfun(sys322ABCD + dt) sys = StateSpace(*args, **kwargs) dtref = defaults['control.default_dt'] if len(dt) == 0 else dt[0] np.testing.assert_almost_equal(sys.A, sys322ABCD[0]) np.testing.assert_almost_equal(sys.B, sys322ABCD[1]) np.testing.assert_almost_equal(sys.C, sys322ABCD[2]) np.testing.assert_almost_equal(sys.D, sys322ABCD[3]) assert sys.dt == dtref
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_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 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 testMIMO(self): sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]], [[1.0, 0.0], [0.0, 1.0]], [[1.0, 0.0], [0.0, 1.0]], [[0.0, 0.0], [0.0, 0.0]]) omega = np.logspace(-1, 2, 10) f1 = FRD(sys, omega) np.testing.assert_array_almost_equal( sys.frequency_response([0.1, 1.0, 10])[0], f1.frequency_response([0.1, 1.0, 10])[0]) np.testing.assert_array_almost_equal( sys.frequency_response([0.1, 1.0, 10])[1], f1.frequency_response([0.1, 1.0, 10])[1])
def testMIMOMult(self): sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]], [[1.0, 0.0], [0.0, 1.0]], [[1.0, 0.0], [0.0, 1.0]], [[0.0, 0.0], [0.0, 0.0]]) omega = np.logspace(-1, 2, 10) f1 = FRD(sys, omega) f2 = FRD(sys, omega) np.testing.assert_array_almost_equal( (f1 * f2).freqresp([0.1, 1.0, 10])[0], (sys * sys).freqresp([0.1, 1.0, 10])[0]) np.testing.assert_array_almost_equal( (f1 * f2).freqresp([0.1, 1.0, 10])[1], (sys * sys).freqresp([0.1, 1.0, 10])[1])
def testMIMOfb(self): sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]], [[1.0, 0.0], [0.0, 1.0]], [[1.0, 0.0], [0.0, 1.0]], [[0.0, 0.0], [0.0, 0.0]]) omega = np.logspace(-1, 2, 10) f1 = FRD(sys, omega).feedback([[0.1, 0.3], [0.0, 1.0]]) f2 = FRD(sys.feedback([[0.1, 0.3], [0.0, 1.0]]), omega) np.testing.assert_array_almost_equal( f1.freqresp([0.1, 1.0, 10])[0], f2.freqresp([0.1, 1.0, 10])[0]) np.testing.assert_array_almost_equal( f1.freqresp([0.1, 1.0, 10])[1], f2.freqresp([0.1, 1.0, 10])[1])
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)) for X in [sys.A, sys.B, sys.C, sys.D]: self.assertTrue(isinstance(X, np.matrix))
def test_str(self, sys322): """Test that printing the system works""" tsys = sys322 tref = ("A = [[-3. 4. 2.]\n" " [-1. -3. 0.]\n" " [ 2. 5. 3.]]\n" "\n" "B = [[ 1. 4.]\n" " [-3. -3.]\n" " [-2. 1.]]\n" "\n" "C = [[ 4. 2. -3.]\n" " [ 1. 4. 3.]]\n" "\n" "D = [[-2. 4.]\n" " [ 0. 1.]]\n") assert str(tsys) == tref tsysdtunspec = StateSpace(tsys.A, tsys.B, tsys.C, tsys.D, True) assert str(tsysdtunspec) == tref + "\ndt = True\n" sysdt1 = StateSpace(tsys.A, tsys.B, tsys.C, tsys.D, 1.) assert str(sysdt1) == tref + "\ndt = {}\n".format(1.)
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 testAgainstOctave(self): # with data from octave: #sys = ss([-2 0 0; 0 -1 1; 0 0 -3], [1 0; 0 0; 0 1], eye(3), zeros(3,2)) #bfr = frd(bsys, [1]) sys = StateSpace(np.matrix('-2.0 0 0; 0 -1 1; 0 0 -3'), np.matrix('1.0 0; 0 0; 0 1'), np.eye(3), np.zeros((3, 2))) omega = np.logspace(-1, 2, 10) f1 = FRD(sys, omega) np.testing.assert_array_almost_equal( (f1.freqresp([1.0])[0] * np.exp(1j * f1.freqresp([1.0])[1])).reshape(3, 2), np.matrix('0.4-0.2j 0; 0 0.1-0.2j; 0 0.3-0.1j'))
def plant(self, request): plants = { 'syscont': TransferFunction(1, [1, 3, 0]), 'sysdisc1': c2d(TransferFunction(1, [1, 3, 0]), .1), 'syscont221': StateSpace([[-.3, 0], [1, 0]], [[ -1, ], [ .1, ]], [0, -.3], 0) } return plants[request.param]
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 testAgainstOctave(self): # with data from octave: # sys = ss([-2 0 0; 0 -1 1; 0 0 -3], # [1 0; 0 0; 0 1], eye(3), zeros(3,2)) # bfr = frd(bsys, [1]) sys = StateSpace(np.array([[-2.0, 0, 0], [0, -1, 1], [0, 0, -3]]), np.array([[1.0, 0], [0, 0], [0, 1]]), np.eye(3), np.zeros((3, 2))) omega = np.logspace(-1, 2, 10) f1 = FRD(sys, omega) np.testing.assert_array_almost_equal( (f1.frequency_response([1.0])[0] * np.exp(1j * f1.frequency_response([1.0])[1])).reshape(3, 2), np.array([[0.4 - 0.2j, 0], [0, 0.1 - 0.2j], [0, 0.3 - 0.1j]]))
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 testMIMOfb2(self): sys = StateSpace(np.array([[-2.0, 0, 0], [0, -1, 1], [0, 0, -3]]), np.array([[1.0, 0], [0, 0], [0, 1]]), np.eye(3), np.zeros((3, 2))) omega = np.logspace(-1, 2, 10) K = np.array([[1, 0.3, 0], [0.1, 0, 0]]) f1 = FRD(sys, omega).feedback(K) f2 = FRD(sys.feedback(K), omega) np.testing.assert_array_almost_equal( f1.frequency_response([0.1, 1.0, 10])[0], f2.frequency_response([0.1, 1.0, 10])[0]) np.testing.assert_array_almost_equal( f1.frequency_response([0.1, 1.0, 10])[1], f2.frequency_response([0.1, 1.0, 10])[1])
def testMIMOfb2(self): sys = StateSpace(np.matrix('-2.0 0 0; 0 -1 1; 0 0 -3'), np.matrix('1.0 0; 0 0; 0 1'), np.eye(3), np.zeros((3, 2))) omega = np.logspace(-1, 2, 10) K = np.matrix('1 0.3 0; 0.1 0 0') f1 = FRD(sys, omega).feedback(K) f2 = FRD(sys.feedback(K), omega) np.testing.assert_array_almost_equal( f1.freqresp([0.1, 1.0, 10])[0], f2.freqresp([0.1, 1.0, 10])[0]) np.testing.assert_array_almost_equal( f1.freqresp([0.1, 1.0, 10])[1], f2.freqresp([0.1, 1.0, 10])[1])
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)
def reachable_form(xsys): """Convert a system into reachable canonical form Parameters ---------- xsys : StateSpace object System to be transformed, with state `x` Returns ------- zsys : StateSpace object System in reachable canonical form, with state `z` T : matrix Coordinate transformation: z = T * x """ # Check to make sure we have a SISO system if not issiso(xsys): raise ControlNotImplemented( "Canonical forms for MIMO systems not yet supported") # Create a new system, starting with a copy of the old one zsys = StateSpace(xsys) # Generate the system matrices for the desired canonical form zsys.B = zeros(shape(xsys.B)) zsys.B[0, 0] = 1.0 zsys.A = zeros(shape(xsys.A)) Apoly = poly(xsys.A) # characteristic polynomial for i in range(0, xsys.states): zsys.A[0, i] = -Apoly[i+1] / Apoly[0] if (i+1 < xsys.states): zsys.A[i+1, i] = 1.0 # Compute the reachability matrices for each set of states Wrx = ctrb(xsys.A, xsys.B) Wrz = ctrb(zsys.A, zsys.B) if matrix_rank(Wrx) != xsys.states: raise ValueError("System not controllable to working precision.") # Transformation from one form to another Tzx = solve(Wrx.T, Wrz.T).T # matrix right division, Tzx = Wrz * inv(Wrx) if matrix_rank(Tzx) != xsys.states: raise ValueError("Transformation matrix singular to working precision.") # Finally, compute the output matrix zsys.C = solve(Tzx.T, xsys.C.T).T # matrix right division, zsys.C = xsys.C * inv(Tzx) return zsys, Tzx
def testMIMOSmooth(self): sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]], [[1.0, 0.0], [0.0, 1.0]], [[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]], [[0.0, 0.0], [0.0, 0.0], [0.0, 0.0]]) sys2 = np.array([[1, 0, 0], [0, 1, 0]]) * sys omega = np.logspace(-1, 2, 10) f1 = FRD(sys, omega, smooth=True) f2 = FRD(sys2, omega, smooth=True) np.testing.assert_array_almost_equal( (f1 * f2).frequency_response([0.1, 1.0, 10])[0], (sys * sys2).frequency_response([0.1, 1.0, 10])[0]) np.testing.assert_array_almost_equal( (f1 * f2).frequency_response([0.1, 1.0, 10])[1], (sys * sys2).frequency_response([0.1, 1.0, 10])[1]) np.testing.assert_array_almost_equal( (f1 * f2).frequency_response([0.1, 1.0, 10])[2], (sys * sys2).frequency_response([0.1, 1.0, 10])[2])
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_repr(self): ref322 = """StateSpace(array([[-3., 4., 2.], [-1., -3., 0.], [ 2., 5., 3.]]), array([[ 1., 4.], [-3., -3.], [-2., 1.]]), array([[ 4., 2., -3.], [ 1., 4., 3.]]), array([[-2., 4.], [ 0., 1.]]){dt})""" self.assertEqual(repr(self.sys322), ref322.format(dt='')) sysd = StateSpace(self.sys322.A, self.sys322.B, self.sys322.C, self.sys322.D, 0.4) self.assertEqual(repr(sysd), ref322.format(dt=", 0.4")) array = np.array sysd2 = eval(repr(sysd)) np.testing.assert_allclose(sysd.A, sysd2.A) np.testing.assert_allclose(sysd.B, sysd2.B) np.testing.assert_allclose(sysd.C, sysd2.C) np.testing.assert_allclose(sysd.D, sysd2.D)
def test_siso(): """Test SISO frequency response""" A = np.array([[1, 1], [0, 1]]) B = np.array([[0], [1]]) C = np.array([[1, 0]]) D = 0 sys = StateSpace(A, B, C, D) omega = np.linspace(10e-2, 10e2, 1000) # test frequency response sys.freqresp(omega) # test bode plot bode(sys) # Convert to transfer function and test bode systf = tf(sys) bode(systf)
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 sys623(self): """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)) return StateSpace(A623, B623, C623, D623)