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 )
@@ -142,3 +144,69 @@ def observable_form(xsys):
142144 zsys .B = Tzx * xsys .B
143145
144146 return zsys , Tzx
147+
148+ def modal_form (xsys ):
149+ """Convert a system into modal canonical form
150+
151+ Parameters
152+ ----------
153+ xsys : StateSpace object
154+ System to be transformed, with state `x`
155+
156+ Returns
157+ -------
158+ zsys : StateSpace object
159+ System in modal canonical form, with state `z`
160+ T : matrix
161+ Coordinate transformation: z = T * x
162+ """
163+ # Check to make sure we have a SISO system
164+ if not issiso (xsys ):
165+ raise ControlNotImplemented (
166+ "Canonical forms for MIMO systems not yet supported" )
167+
168+ # Create a new system, starting with a copy of the old one
169+ zsys = StateSpace (xsys )
170+
171+ # Calculate eigenvalues and matrix of eigenvectors Tzx,
172+ eigval , eigvec = eig (xsys .A )
173+
174+ # Eigenvalues and according eigenvectors are not sorted,
175+ # thus modal transformation is ambiguous
176+ # Sorting eigenvalues and respective vectors by largest to smallest eigenvalue
177+ idx = eigval .argsort ()[::- 1 ]
178+ eigval = eigval [idx ]
179+ eigvec = eigvec [:,idx ]
180+
181+ # If all eigenvalues are real, the matrix of eigenvectors is Tzx directly
182+ if not iscomplex (eigval ).any ():
183+ Tzx = eigvec
184+ else :
185+ # A is an arbitrary semisimple matrix
186+
187+ # Keep track of complex conjugates (need only one)
188+ lst_conjugates = []
189+ Tzx = None
190+ for val , vec in zip (eigval , eigvec .T ):
191+ if iscomplex (val ):
192+ if val not in lst_conjugates :
193+ lst_conjugates .append (val .conjugate ())
194+ if Tzx is not None :
195+ Tzx = hstack ((Tzx , hstack ((vec .real .T , vec .imag .T ))))
196+ else :
197+ Tzx = hstack ((vec .real .T , vec .imag .T ))
198+ else :
199+ # if conjugate has already been seen, skip this eigenvalue
200+ lst_conjugates .remove (val )
201+ else :
202+ if Tzx is not None :
203+ Tzx = hstack ((Tzx , vec .real .T ))
204+ else :
205+ Tzx = vec .real .T
206+
207+ # Generate the system matrices for the desired canonical form
208+ zsys .A = solve (Tzx , xsys .A ).dot (Tzx )
209+ zsys .B = solve (Tzx , xsys .B )
210+ zsys .C = xsys .C .dot (Tzx )
211+
212+ return zsys , Tzx
0 commit comments