5454import math
5555import numpy as np
5656from numpy import all , angle , any , array , asarray , concatenate , cos , delete , \
57- dot , empty , exp , eye , matrix , ones , poly , poly1d , roots , shape , sin , \
58- zeros , squeeze
57+ dot , empty , exp , eye , isinf , matrix , ones , pad , shape , sin , zeros , squeeze
5958from numpy .random import rand , randn
6059from numpy .linalg import solve , eigvals , matrix_rank
6160from numpy .linalg .linalg import LinAlgError
@@ -516,17 +515,41 @@ def pole(self):
516515 def zero (self ):
517516 """Compute the zeros of a state space system."""
518517
519- if self .inputs > 1 or self .outputs > 1 :
520- raise NotImplementedError ("StateSpace.zeros is currently \
521- implemented only for SISO systems." )
518+ if not self .states :
519+ return np .array ([])
522520
523- den = poly1d (poly (self .A ))
524- # Compute the numerator based on zeros
525- #! TODO: This is currently limited to SISO systems
526- num = poly1d (poly (self .A - dot (self .B , self .C )) + ((self .D [0 , 0 ] - 1 ) *
527- den ))
528-
529- return roots (num )
521+ # Use AB08ND from Slycot if it's available, otherwise use
522+ # scipy.lingalg.eigvals().
523+ try :
524+ from slycot import ab08nd
525+
526+ out = ab08nd (self .A .shape [0 ], self .B .shape [1 ], self .C .shape [0 ],
527+ self .A , self .B , self .C , self .D )
528+ nu = out [0 ]
529+ return sp .linalg .eigvals (out [8 ][0 :nu ,0 :nu ], out [9 ][0 :nu ,0 :nu ])
530+ except ImportError : # Slycot unavailable. Fall back to scipy.
531+ if self .C .shape [0 ] != self .D .shape [1 ]:
532+ raise NotImplementedError ("StateSpace.zero only supports "
533+ "systems with the same number of "
534+ "inputs as outputs." )
535+
536+ # This implements the QZ algorithm for finding transmission zeros
537+ # from
538+ # https://dspace.mit.edu/bitstream/handle/1721.1/841/P-0802-06587335.pdf.
539+ # The QZ algorithm solves the generalized eigenvalue problem: given
540+ # `L = [A, B; C, D]` and `M = [I_nxn 0]`, find all finite lambda
541+ # for which there exist nontrivial solutions of the equation
542+ # `Lz - lamba Mz`.
543+ #
544+ # The generalized eigenvalue problem is only solvable if its
545+ # arguments are square matrices.
546+ L = concatenate ((concatenate ((self .A , self .B ), axis = 1 ),
547+ concatenate ((self .C , self .D ), axis = 1 )), axis = 0 )
548+ M = pad (eye (self .A .shape [0 ]), ((0 , self .C .shape [0 ]),
549+ (0 , self .B .shape [1 ])), "constant" )
550+ return np .array ([x for x in sp .linalg .eigvals (L , M ,
551+ overwrite_a = True )
552+ if not isinf (x )])
530553
531554 # Feedback around a state space system
532555 def feedback (self , other = 1 , sign = - 1 ):
@@ -757,7 +780,6 @@ def _convertToStateSpace(sys, **kw):
757780 [1., 1., 1.]].
758781
759782 """
760-
761783 from .xferfcn import TransferFunction
762784 import itertools
763785 if isinstance (sys , StateSpace ):
@@ -771,20 +793,17 @@ def _convertToStateSpace(sys, **kw):
771793 try :
772794 from slycot import td04ad
773795 if len (kw ):
774- raise TypeError ("If sys is a TransferFunction, _convertToStateSpace \
775- cannot take keywords." )
796+ raise TypeError ("If sys is a TransferFunction, "
797+ "_convertToStateSpace cannot take keywords." )
776798
777799 # Change the numerator and denominator arrays so that the transfer
778800 # function matrix has a common denominator.
779- num , den = sys ._common_den ()
780- # Make a list of the orders of the denominator polynomials.
781- index = [len (den ) - 1 for i in range (sys .outputs )]
782- # Repeat the common denominator along the rows.
783- den = array ([den for i in range (sys .outputs )])
784- #! TODO: transfer function to state space conversion is still buggy!
785- #print num
786- #print shape(num)
787- ssout = td04ad ('R' ,sys .inputs , sys .outputs , index , den , num ,tol = 0.0 )
801+ # matrices are also sized/padded to fit td04ad
802+ num , den , denorder = sys .minreal ()._common_den ()
803+
804+ # transfer function to state space conversion now should work!
805+ ssout = td04ad ('C' , sys .inputs , sys .outputs ,
806+ denorder , den , num , tol = 0 )
788807
789808 states = ssout [0 ]
790809 return StateSpace (ssout [1 ][:states , :states ],
0 commit comments