66from .statesp import StateSpace
77from .statefbk import ctrb , obsv
88
9- from numpy import zeros , shape , poly
10- from numpy .linalg import solve , matrix_rank
9+ from numpy import zeros , shape , poly , iscomplex , hstack
10+ from numpy .linalg import solve , matrix_rank , eig
1111
1212__all__ = ['canonical_form' , 'reachable_form' , 'observable_form' ]
1313
@@ -22,7 +22,7 @@ def canonical_form(xsys, form='reachable'):
2222 Canonical form for transformation. Chosen from:
2323 * 'reachable' - reachable canonical form
2424 * 'observable' - observable canonical form
25- * 'modal' - modal canonical form [not implemented]
25+ * 'modal' - modal canonical form
2626
2727 Returns
2828 -------
@@ -37,6 +37,8 @@ def canonical_form(xsys, form='reachable'):
3737 return reachable_form (xsys )
3838 elif form == 'observable' :
3939 return observable_form (xsys )
40+ elif form == 'modal' :
41+ return modal_form (xsys )
4042 else :
4143 raise ControlNotImplemented (
4244 "Canonical form '%s' not yet implemented" % form )
@@ -139,3 +141,69 @@ def observable_form(xsys):
139141 zsys .B = Tzx * xsys .B
140142
141143 return zsys , Tzx
144+
145+ def modal_form (xsys ):
146+ """Convert a system into modal canonical form
147+
148+ Parameters
149+ ----------
150+ xsys : StateSpace object
151+ System to be transformed, with state `x`
152+
153+ Returns
154+ -------
155+ zsys : StateSpace object
156+ System in modal canonical form, with state `z`
157+ T : matrix
158+ Coordinate transformation: z = T * x
159+ """
160+ # Check to make sure we have a SISO system
161+ if not issiso (xsys ):
162+ raise ControlNotImplemented (
163+ "Canonical forms for MIMO systems not yet supported" )
164+
165+ # Create a new system, starting with a copy of the old one
166+ zsys = StateSpace (xsys )
167+
168+ # Calculate eigenvalues and matrix of eigenvectors Tzx,
169+ eigval , eigvec = eig (xsys .A )
170+
171+ # Eigenvalues and according eigenvectors are not sorted,
172+ # thus modal transformation is ambiguous
173+ # Sorting eigenvalues and respective vectors by largest to smallest eigenvalue
174+ idx = eigval .argsort ()[::- 1 ]
175+ eigval = eigval [idx ]
176+ eigvec = eigvec [:,idx ]
177+
178+ # If all eigenvalues are real, the matrix of eigenvectors is Tzx directly
179+ if not iscomplex (eigval ).any ():
180+ Tzx = eigvec
181+ else :
182+ # A is an arbitrary semisimple matrix
183+
184+ # Keep track of complex conjugates (need only one)
185+ lst_conjugates = []
186+ Tzx = None
187+ for val , vec in zip (eigval , eigvec .T ):
188+ if iscomplex (val ):
189+ if val not in lst_conjugates :
190+ lst_conjugates .append (val .conjugate ())
191+ if Tzx is not None :
192+ Tzx = hstack ((Tzx , hstack ((vec .real .T , vec .imag .T ))))
193+ else :
194+ Tzx = hstack ((vec .real .T , vec .imag .T ))
195+ else :
196+ # if conjugate has already been seen, skip this eigenvalue
197+ lst_conjugates .remove (val )
198+ else :
199+ if Tzx is not None :
200+ Tzx = hstack ((Tzx , vec .real .T ))
201+ else :
202+ Tzx = vec .real .T
203+
204+ # Generate the system matrices for the desired canonical form
205+ zsys .A = solve (Tzx , xsys .A ).dot (Tzx )
206+ zsys .B = solve (Tzx , xsys .B )
207+ zsys .C = xsys .C .dot (Tzx )
208+
209+ return zsys , Tzx
0 commit comments