5555# of the functions that already existing in that package to be used
5656# directly.
5757#
58- class StateSpace ( signal . lti ) :
58+ class StateSpace :
5959 """The StateSpace class is used to represent linear input/output systems.
6060 """
6161 # Initialization
62- def __init__ (self , * args , ** keywords ):
63- # First initialize the parent object
64- signal .lti .__init__ (self , * args , ** keywords )
62+ def __init__ (self , A , B , C , D ):
63+ self .A = A
64+ self .B = B
65+ self .C = C
66+ self .D = D
67+
68+ self .states = A .shape [0 ]
69+ self .inputs = B .shape [1 ]
70+ self .outputs = C .shape [0 ]
71+
72+ # Check that the matrix sizes are consistent.
73+ if self .states != A .shape [1 ]:
74+ raise ValueError ("A must be square." )
75+ if self .states != B .shape [0 ]:
76+ raise ValueError ("B must have the same row size as A." )
77+ if self .states != C .shape [1 ]:
78+ raise ValueError ("C must have the same column size as A." )
79+ if self .inputs != D .shape [1 ]:
80+ raise ValueError ("D must have the same column size as B." )
81+ if self .outputs != D .shape [0 ]:
82+ raise ValueError ("D must have the same row size as C." )
6583
6684 # Style to use for printing
6785 def __str__ (self ):
@@ -76,7 +94,7 @@ def freqresp(self, omega=None):
7694 """Compute the response of a system to a list of frequencies"""
7795 # Generate and save a transfer function matrix
7896 #! TODO: This is currently limited to SISO systems
79- nout , nin = self .D .shape
97+ # nout, nin = self.D.shape
8098
8199 # Compute the denominator from the A matrix
82100 den = sp .poly1d (sp .poly (self .A ))
@@ -99,7 +117,9 @@ def evalfr(self, freq):
99117 return None
100118
101119 # Compute poles and zeros
102- def poles (self ): return sp .roots (sp .poly (self .A ))
120+ def poles (self ):
121+ return sp .roots (sp .poly (self .A ))
122+
103123 def zeros (self ):
104124 den = sp .poly1d (sp .poly (self .A ))
105125
@@ -248,3 +268,83 @@ def convertToStateSpace(sys, inputs=1, outputs=1):
248268
249269 else :
250270 raise TypeError ("can't convert given type to StateSpace system" )
271+
272+ def rss (states = 1 , inputs = 1 , outputs = 1 ):
273+ """Create a stable random state space object."""
274+
275+ import numpy
276+ from numpy .random import rand , randn
277+
278+ # Make some poles for A. Preallocate a complex array.
279+ poles = numpy .zeros (states ) + numpy .zeros (states ) * 0.j
280+ i = 0
281+ while i < states - 1 :
282+ if rand () < 0.05 and i != 0 :
283+ # Small chance of copying poles, if we're not at the first element.
284+ if poles [i - 1 ].imag == 0 :
285+ # Copy previous real pole.
286+ poles [i ] = poles [i - 1 ]
287+ i += 1
288+ else :
289+ # Copy previous complex conjugate pair of poles.
290+ poles [i :i + 2 ] = poles [i - 2 :i ]
291+ i += 2
292+ elif rand () < 0.6 :
293+ # Real pole.
294+ poles [i ] = - sp .exp (randn ()) + 0.j
295+ i += 1
296+ else :
297+ # Complex conjugate pair of poles.
298+ poles [i ] = complex (- sp .exp (randn ()), sp .exp (randn ()))
299+ poles [i + 1 ] = complex (poles [i ].real , - poles [i ].imag )
300+ i += 2
301+ # When we reach this point, we either have one or zero poles left to fill.
302+ # Put a real pole if there is one space left.
303+ if i == states - 1 :
304+ poles [i ] = - sp .exp (randn ()) + 0.j
305+
306+ # Now put the poles in A as real blocks on the diagonal.
307+ A = numpy .zeros ((states , states ))
308+ i = 0
309+ while i < states :
310+ if poles [i ].imag == 0 :
311+ A [i , i ] = poles [i ].real
312+ i += 1
313+ else :
314+ A [i , i ] = A [i + 1 , i + 1 ] = poles [i ].real
315+ A [i , i + 1 ] = poles [i ].imag
316+ A [i + 1 , i ] = - poles [i ].imag
317+ i += 2
318+
319+ # Finally, apply a transformation so that A is not block-diagonal.
320+ while True :
321+ T = randn (states , states )
322+ try :
323+ A = numpy .dot (numpy .linalg .solve (T , A ), T ) # A = T \ A * T
324+ break
325+ except numpy .linalg .linalg .LinAlgError :
326+ # In the unlikely event that T is rank-deficient, iterate again.
327+ pass
328+
329+ # Make the remaining matrices.
330+ B = randn (states , inputs )
331+ C = randn (outputs , states )
332+ D = randn (outputs , inputs )
333+
334+ # Make masks to zero out some of the elements.
335+ while True :
336+ B_mask = rand (states , inputs ) < 0.8
337+ if sp .any (B_mask ): # Retry if we get all zeros.
338+ break
339+ while True :
340+ C_mask = rand (outputs , states ) < 0.8
341+ if sp .any (C_mask ): # Retry if we get all zeros.
342+ break
343+ D_mask = rand (outputs , inputs ) < 0.3
344+
345+ # Apply masks.
346+ B = B * B_mask
347+ C = C * C_mask
348+ D = D * D_mask
349+
350+ return StateSpace (A , B , C , D )
0 commit comments