22
33import unittest
44import numpy as np
5- from control import ss
6- from control .canonical import canonical_form
7-
5+ from control import ss , tf
6+ from control .canonical import canonical_form , reachable_form , \
7+ observable_form , modal_form
8+ from control .exception import ControlNotImplemented
89
910class TestCanonical (unittest .TestCase ):
1011 """Tests for the canonical forms class"""
@@ -40,6 +41,11 @@ def test_reachable_form(self):
4041 np .testing .assert_array_almost_equal (sys_check .D , D_true )
4142 np .testing .assert_array_almost_equal (T_check , T_true )
4243
44+ # Reachable form only supports SISO
45+ sys = tf ([[ [1 ], [1 ] ]], [[ [1 , 2 , 1 ], [1 , 2 , 1 ] ]])
46+ np .testing .assert_raises (ControlNotImplemented , reachable_form , sys )
47+
48+
4349 def test_unreachable_system (self ):
4450 """Test reachable canonical form with an unreachable system"""
4551
@@ -76,12 +82,84 @@ def test_modal_form(self):
7682 sys_check , T_check = canonical_form (ss (A , B , C , D ), "modal" )
7783
7884 # Check against the true values
79- #TODO: Test in respect to ambiguous transformation (system characteristics?)
85+ # TODO: Test in respect to ambiguous transformation (system characteristics?)
8086 np .testing .assert_array_almost_equal (sys_check .A , A_true )
8187 #np.testing.assert_array_almost_equal(sys_check.B, B_true)
8288 #np.testing.assert_array_almost_equal(sys_check.C, C_true)
8389 np .testing .assert_array_almost_equal (sys_check .D , D_true )
8490 #np.testing.assert_array_almost_equal(T_check, T_true)
91+
92+ # Check conversion when there are complex eigenvalues
93+ A_true = np .array ([[- 1 , 1 , 0 , 0 ],
94+ [- 1 , - 1 , 0 , 0 ],
95+ [ 0 , 0 , - 2 , 0 ],
96+ [ 0 , 0 , 0 , - 3 ]])
97+ B_true = np .array ([[0 ], [1 ], [0 ], [1 ]])
98+ C_true = np .array ([[1 , 0 , 0 , 1 ]])
99+ D_true = np .array ([[0 ]])
100+
101+ A = np .linalg .solve (T_true , A_true ) * T_true
102+ B = np .linalg .solve (T_true , B_true )
103+ C = C_true * T_true
104+ D = D_true
105+
106+ # Create state space system and convert to modal canonical form
107+ sys_check , T_check = canonical_form (ss (A , B , C , D ), 'modal' )
108+
109+ # Check A and D matrix, which are uniquely defined
110+ np .testing .assert_array_almost_equal (sys_check .A , A_true )
111+ np .testing .assert_array_almost_equal (sys_check .D , D_true )
112+
113+ # B matrix should be all ones (or zero if not controllable)
114+ # TODO: need to update modal_form() to implement this
115+ if np .allclose (T_check , T_true ):
116+ np .testing .assert_array_almost_equal (sys_check .B , B_true )
117+ np .testing .assert_array_almost_equal (sys_check .C , C_true )
118+
119+ # Make sure Hankel coefficients are OK
120+ from numpy .linalg import matrix_power
121+ for i in range (A .shape [0 ]):
122+ np .testing .assert_almost_equal (
123+ np .dot (np .dot (C_true , matrix_power (A_true , i )), B_true ),
124+ np .dot (np .dot (C , matrix_power (A , i )), B ))
125+
126+ # Reorder rows to get complete coverage (real eigenvalue cxrtvfirst)
127+ A_true = np .array ([[- 1 , 0 , 0 , 0 ],
128+ [ 0 , - 2 , 1 , 0 ],
129+ [ 0 , - 1 , - 2 , 0 ],
130+ [ 0 , 0 , 0 , - 3 ]])
131+ B_true = np .array ([[0 ], [0 ], [1 ], [1 ]])
132+ C_true = np .array ([[0 , 1 , 0 , 1 ]])
133+ D_true = np .array ([[0 ]])
134+
135+ A = np .linalg .solve (T_true , A_true ) * T_true
136+ B = np .linalg .solve (T_true , B_true )
137+ C = C_true * T_true
138+ D = D_true
139+
140+ # Create state space system and convert to modal canonical form
141+ sys_check , T_check = canonical_form (ss (A , B , C , D ), 'modal' )
142+
143+ # Check A and D matrix, which are uniquely defined
144+ np .testing .assert_array_almost_equal (sys_check .A , A_true )
145+ np .testing .assert_array_almost_equal (sys_check .D , D_true )
146+
147+ # B matrix should be all ones (or zero if not controllable)
148+ # TODO: need to update modal_form() to implement this
149+ if np .allclose (T_check , T_true ):
150+ np .testing .assert_array_almost_equal (sys_check .B , B_true )
151+ np .testing .assert_array_almost_equal (sys_check .C , C_true )
152+
153+ # Make sure Hankel coefficients are OK
154+ from numpy .linalg import matrix_power
155+ for i in range (A .shape [0 ]):
156+ np .testing .assert_almost_equal (
157+ np .dot (np .dot (C_true , matrix_power (A_true , i )), B_true ),
158+ np .dot (np .dot (C , matrix_power (A , i )), B ))
159+
160+ # Modal form only supports SISO
161+ sys = tf ([[ [1 ], [1 ] ]], [[ [1 , 2 , 1 ], [1 , 2 , 1 ] ]])
162+ np .testing .assert_raises (ControlNotImplemented , modal_form , sys )
85163
86164 def test_observable_form (self ):
87165 """Test the observable canonical form"""
@@ -114,6 +192,11 @@ def test_observable_form(self):
114192 np .testing .assert_array_almost_equal (sys_check .D , D_true )
115193 np .testing .assert_array_almost_equal (T_check , T_true )
116194
195+ # Observable form only supports SISO
196+ sys = tf ([[ [1 ], [1 ] ]], [[ [1 , 2 , 1 ], [1 , 2 , 1 ] ]])
197+ np .testing .assert_raises (ControlNotImplemented , observable_form , sys )
198+
199+
117200 def test_unobservable_system (self ):
118201 """Test observable canonical form with an unobservable system"""
119202
@@ -126,3 +209,18 @@ def test_unobservable_system(self):
126209
127210 # Check if an exception is raised
128211 np .testing .assert_raises (ValueError , canonical_form , sys , "observable" )
212+
213+ def test_arguments (self ):
214+ # Additional unit tests added on 25 May 2019 to increase coverage
215+
216+ # Unknown canonical forms should generate exception
217+ sys = tf ([1 ], [1 , 2 , 1 ])
218+ np .testing .assert_raises (
219+ ControlNotImplemented , canonical_form , sys , 'unknown' )
220+
221+ def suite ():
222+ return unittest .TestLoader ().loadTestsFromTestCase (TestFeedback )
223+
224+
225+ if __name__ == "__main__" :
226+ unittest .main ()
0 commit comments