1616
1717import unittest
1818import numpy as np
19+ import control
1920import control .matlab as matlab
2021
2122class TestConvert (unittest .TestCase ):
@@ -27,9 +28,9 @@ def setUp(self):
2728 # Number of times to run each of the randomized tests.
2829 self .numTests = 1 #almost guarantees failure
2930 # Maximum number of states to test + 1
30- self .maxStates = 20
31+ self .maxStates = 4
3132 # Maximum number of inputs and outputs to test + 1
32- self .maxIO = 10
33+ self .maxIO = 5
3334 # Set to True to print systems to the output.
3435 self .debug = False
3536
@@ -42,20 +43,35 @@ def printSys(self, sys, ind):
4243
4344 def testConvert (self ):
4445 """Test state space to transfer function conversion."""
45- #Currently it only tests that a TF->SS->TF generates an unchanged TF
4646 verbose = self .debug
47+ from control .statesp import _mimo2siso
4748
4849 #print __doc__
4950
51+ # Machine precision for floats.
52+ eps = np .finfo (float ).eps
53+
5054 for states in range (1 , self .maxStates ):
5155 for inputs in range (1 , self .maxIO ):
5256 for outputs in range (1 , self .maxIO ):
53- #start with a random SS system and transform to TF
54- #then back to SS, check that the matrices are the same.
57+ # start with a random SS system and transform to TF then
58+ # back to SS, check that the matrices are the same.
5559 ssOriginal = matlab .rss (states , inputs , outputs )
5660 if (verbose ):
5761 self .printSys (ssOriginal , 1 )
5862
63+ # Make sure the system is not degenerate
64+ Cmat = control .ctrb (ssOriginal .A , ssOriginal .B )
65+ if (np .linalg .matrix_rank (Cmat ) != states ):
66+ if (verbose ):
67+ print " skipping (not reachable)"
68+ continue
69+ Omat = control .obsv (ssOriginal .A , ssOriginal .C )
70+ if (np .linalg .matrix_rank (Omat ) != states ):
71+ if (verbose ):
72+ print " skipping (not observable)"
73+ continue
74+
5975 tfOriginal = matlab .tf (ssOriginal )
6076 if (verbose ):
6177 self .printSys (tfOriginal , 2 )
@@ -67,27 +83,77 @@ def testConvert(self):
6783 tfTransformed = matlab .tf (ssTransformed )
6884 if (verbose ):
6985 self .printSys (tfTransformed , 4 )
70-
86+
87+ # Check to see if the state space systems have same dim
88+ if (ssOriginal .states != ssTransformed .states ):
89+ print "WARNING: state space dimension mismatch: " + \
90+ "%d versus %d" % \
91+ (ssOriginal .states , ssTransformed .states )
92+
93+ # Now make sure the frequency responses match
94+ # Since bode() only handles SISO, go through each I/O pair
95+ # For phase, take sine and cosine to avoid +/- 360 offset
7196 for inputNum in range (inputs ):
7297 for outputNum in range (outputs ):
73- np .testing .assert_array_almost_equal (\
74- tfOriginal .num [outputNum ][inputNum ], \
75- tfTransformed .num [outputNum ][inputNum ], \
76- err_msg = 'numerator mismatch' )
98+ if (verbose ):
99+ print "Checking input %d, output %d" \
100+ % (inputNum , outputNum )
101+ ssorig_mag , ssorig_phase , ssorig_omega = \
102+ control .bode (_mimo2siso (ssOriginal , \
103+ inputNum , outputNum ), \
104+ deg = False , Plot = False )
105+ ssorig_real = ssorig_mag * np .cos (ssorig_phase )
106+ ssorig_imag = ssorig_mag * np .sin (ssorig_phase )
107+
108+ #
109+ # Make sure TF has same frequency response
110+ #
111+ num = tfOriginal .num [outputNum ][inputNum ]
112+ den = tfOriginal .den [outputNum ][inputNum ]
113+ tforig = control .tf (num , den )
114+
115+ tforig_mag , tforig_phase , tforig_omega = \
116+ control .bode (tforig , ssorig_omega , \
117+ deg = False , Plot = False )
118+
119+ tforig_real = tforig_mag * np .cos (tforig_phase )
120+ tforig_imag = tforig_mag * np .sin (tforig_phase )
121+ np .testing .assert_array_almost_equal ( \
122+ ssorig_real , tforig_real )
123+ np .testing .assert_array_almost_equal ( \
124+ ssorig_imag , tforig_imag )
125+
126+ #
127+ # Make sure xform'd SS has same frequency response
128+ #
129+ ssxfrm_mag , ssxfrm_phase , ssxfrm_omega = \
130+ control .bode (_mimo2siso (ssTransformed , \
131+ inputNum , outputNum ), \
132+ ssorig_omega , \
133+ deg = False , Plot = False )
134+ ssxfrm_real = ssxfrm_mag * np .cos (ssxfrm_phase )
135+ ssxfrm_imag = ssxfrm_mag * np .sin (ssxfrm_phase )
136+ np .testing .assert_array_almost_equal ( \
137+ ssorig_real , ssxfrm_real )
138+ np .testing .assert_array_almost_equal ( \
139+ ssorig_imag , ssxfrm_imag )
140+
141+ #
142+ # Make sure xform'd TF has same frequency response
143+ #
144+ num = tfTransformed .num [outputNum ][inputNum ]
145+ den = tfTransformed .den [outputNum ][inputNum ]
146+ tfxfrm = control .tf (num , den )
147+ tfxfrm_mag , tfxfrm_phase , tfxfrm_omega = \
148+ control .bode (tfxfrm , ssorig_omega , \
149+ deg = False , Plot = False )
77150
78- np .testing .assert_array_almost_equal (\
79- tfOriginal .den [outputNum ][inputNum ], \
80- tfTransformed .den [outputNum ][inputNum ],
81- err_msg = 'denominator mismatch' )
82-
83- #To test the ss systems is harder because they aren't the same
84- #realization. This could be done with checking that they have the
85- #same freq response with bode, but apparently it doesn't work
86- #the way it should right now:
87- ## Bode should work like this:
88- #[mag,phase,freq]=bode(sys)
89- #it doesn't seem to......
90- #This should be added.
151+ tfxfrm_real = tfxfrm_mag * np .cos (tfxfrm_phase )
152+ tfxfrm_imag = tfxfrm_mag * np .sin (tfxfrm_phase )
153+ np .testing .assert_array_almost_equal ( \
154+ ssorig_real , tfxfrm_real )
155+ np .testing .assert_array_almost_equal ( \
156+ ssorig_imag , tfxfrm_imag )
91157
92158def suite ():
93159 return unittest .TestLoader ().loadTestsFromTestCase (TestConvert )
0 commit comments