@@ -437,60 +437,91 @@ def __rdiv__(self, other):
437437
438438 raise NotImplementedError ("StateSpace.__rdiv__ is not implemented yet." )
439439
440- def __call__ (self , s , squeeze = True ):
441- """Evaluate system's transfer function at complex frequencies s/z.
440+ def __call__ (self , x , squeeze = True ):
441+ """Evaluate system's transfer function at complex frequencies.
442+
443+ Evaluates at complex frequency x, where x is s or z dependong on
444+ whether the system is continuous or discrete-time.
442445
443446 If squeeze is True (default) and sys is single input, single output
444- (SISO), return a 1D array or scalar depending on s.
447+ (SISO), return a 1D array or scalar depending on the size of x.
448+ For a MIMO system, returns an (n_outputs, n_inputs, n_x) array.
445449
446450 """
447- # Use TB05AD from Slycot if available
451+ # Use Slycot if available
448452 try :
449- from slycot import tb05ad
450-
451- # preallocate
452- s_arr = np .array (s , ndmin = 1 ) # array-like version of s
453- out = np .empty ((self .outputs , self .inputs , len (s_arr )),
454- dtype = complex )
455- n = self .states
456- m = self .inputs
457- p = self .outputs
458- # The first call both evaluates C(sI-A)^-1 B and also returns
459- # Hessenberg transformed matrices at, bt, ct.
460- result = tb05ad (n , m , p , s_arr [0 ], self .A ,
461- self .B , self .C , job = 'NG' )
462- # When job='NG', result = (at, bt, ct, g_i, hinvb, info)
463- at = result [0 ]
464- bt = result [1 ]
465- ct = result [2 ]
466-
467- # TB05AD frequency evaluation does not include direct feedthrough.
468- out [:, :, 0 ] = result [3 ] + self .D
469-
470- # Now, iterate through the remaining frequencies using the
471- # transformed state matrices, at, bt, ct.
472-
473- # Start at the second frequency, already have the first.
474- for kk , s_kk in enumerate (s_arr [1 :len (s_arr )]):
475- result = tb05ad (n , m , p , s_kk , at , bt , ct , job = 'NH' )
476- # When job='NH', result = (g_i, hinvb, info)
477-
478- # kk+1 because enumerate starts at kk = 0.
479- # but zero-th spot is already filled.
480- out [:, :, kk + 1 ] = result [0 ] + self .D
481-
482- if not hasattr (s , '__len__' ):
483- # received a scalar s, squeeze down the array
484- out = np .squeeze (out , axis = 2 )
485- except ImportError : # Slycot unavailable. Fall back to horner.
486- out = self .horner (s )
453+ out = self .slycot_horner (x )
454+ except ImportError : # Slycot unavailable. use built-in horner.
455+ out = self .horner (x )
456+ if not hasattr (x , '__len__' ):
457+ # received a scalar x, squeeze down the array along last dim
458+ out = np .squeeze (out , axis = 2 )
487459 if squeeze and self .issiso ():
488460 return out [0 ][0 ]
489461 else :
490462 return out
491463
464+ def slycot_horner (self , s ):
465+ """Evaluate system's transfer function at complex frequencies s
466+ using Horner's method from Slycot.
467+
468+ Expects inputs and outputs to be formatted correctly. Use __call__
469+ for a more user-friendly interface.
470+
471+ Parameters
472+ s : array-like
473+
474+ Returns
475+ output : array of size (outputs, inputs, len(s))
476+
477+ """
478+ from slycot import tb05ad
479+
480+ # preallocate
481+ s_arr = np .array (s , ndmin = 1 ) # array-like version of s
482+ out = np .empty ((self .outputs , self .inputs , len (s_arr )),
483+ dtype = complex )
484+ n = self .states
485+ m = self .inputs
486+ p = self .outputs
487+ # The first call both evaluates C(sI-A)^-1 B and also returns
488+ # Hessenberg transformed matrices at, bt, ct.
489+ result = tb05ad (n , m , p , s_arr [0 ], self .A ,
490+ self .B , self .C , job = 'NG' )
491+ # When job='NG', result = (at, bt, ct, g_i, hinvb, info)
492+ at = result [0 ]
493+ bt = result [1 ]
494+ ct = result [2 ]
495+
496+ # TB05AD frequency evaluation does not include direct feedthrough.
497+ out [:, :, 0 ] = result [3 ] + self .D
498+
499+ # Now, iterate through the remaining frequencies using the
500+ # transformed state matrices, at, bt, ct.
501+
502+ # Start at the second frequency, already have the first.
503+ for kk , s_kk in enumerate (s_arr [1 :len (s_arr )]):
504+ result = tb05ad (n , m , p , s_kk , at , bt , ct , job = 'NH' )
505+ # When job='NH', result = (g_i, hinvb, info)
506+
507+ # kk+1 because enumerate starts at kk = 0.
508+ # but zero-th spot is already filled.
509+ out [:, :, kk + 1 ] = result [0 ] + self .D
510+ return out
511+
492512 def horner (self , s ):
493- """Evaluate systems's transfer function at complex frequencies s.
513+ """Evaluate system's transfer function at complex frequencies s
514+ using Horner's method.
515+
516+ Expects inputs and outputs to be formatted correctly. Use __call__
517+ for a more user-friendly interface.
518+
519+ Parameters
520+ s : array-like
521+
522+ Returns
523+ output : array of size (outputs, inputs, len(s))
524+
494525 """
495526 s_arr = np .array (s , ndmin = 1 ) # force to be an array
496527 # Preallocate
@@ -501,9 +532,6 @@ def horner(self, s):
501532 np .dot (self .C ,
502533 solve (s_idx * eye (self .states ) - self .A , self .B )) \
503534 + self .D
504- if not hasattr (s , '__len__' ):
505- # received a scalar s, squeeze down the array along last dim
506- out = np .squeeze (out , axis = 2 )
507535 return out
508536
509537 def freqresp (self , omega ):
@@ -879,7 +907,7 @@ def dcgain(self):
879907 if self .isctime ():
880908 gain = np .asarray (self .D - self .C .dot (np .linalg .solve (self .A , self .B )))
881909 else :
882- gain = self .horner (1 )
910+ gain = np . squeeze ( self .horner (1 ) )
883911 except LinAlgError :
884912 # eigenvalue at DC
885913 gain = np .tile (np .nan , (self .outputs , self .inputs ))
0 commit comments