Skip to content

Commit 72f2cf4

Browse files
author
Kevin Chen
committed
Removed scipy.signal.lti from statesp.py; added rss.
The StateSpace class now uses seven data members: A, B, C, D, states, inputs, and outputs. scipy.signal.lti was removed because it does not support MIMO functionality. Also, a working rss has been added to statesp.py. It still has yet to be tested. Kevin K. Chen <kkchen@princeton.edu>
1 parent 2b6e8e6 commit 72f2cf4

1 file changed

Lines changed: 106 additions & 6 deletions

File tree

src/statesp.py

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,31 @@
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

Comments
 (0)