|
From: <kk...@us...> - 2011-02-08 22:13:23
|
Revision: 43
http://python-control.svn.sourceforge.net/python-control/?rev=43&view=rev
Author: kkchen
Date: 2011-02-08 22:13:17 +0000 (Tue, 08 Feb 2011)
Log Message:
-----------
Added drss to statesp.py; added drss unit tests to TestStateSp.py.
Also changed the permissions of Test*.py to 755.
Kevin K. Chen <kk...@pr...>
Modified Paths:
--------------
branches/control-0.4a/src/TestStateSp.py
branches/control-0.4a/src/statesp.py
Modified: branches/control-0.4a/src/TestStateSp.py
===================================================================
--- branches/control-0.4a/src/TestStateSp.py 2011-02-08 22:13:13 UTC (rev 42)
+++ branches/control-0.4a/src/TestStateSp.py 2011-02-08 22:13:17 UTC (rev 43)
@@ -5,34 +5,72 @@
import unittest
class TestRss(unittest.TestCase):
- """These are tests for the proper functionality of statesp.rss."""
-
- def setUp(self):
- # Number of times to run each of the randomized tests.
- self.numTests = 100
-
- def testShape(self):
- """Test that rss outputs have the right state, input, and output
- size."""
-
- for states in range(1, 10):
- for inputs in range(1, 5):
- for outputs in range(1, 5):
- sys = ss.rss(states, inputs, outputs)
- self.assertEqual(sys.states, states)
- self.assertEqual(sys.inputs, inputs)
- self.assertEqual(sys.outputs, outputs)
-
- def testPole(self):
- """Test that the poles of rss outputs have a negative real part."""
-
- for states in range(1, 10):
- for inputs in range(1, 5):
- for outputs in range(1, 5):
- sys = ss.rss(states, inputs, outputs)
- p = sys.poles()
- for z in p:
- self.assertTrue(z.real < 0)
-
+ """These are tests for the proper functionality of statesp.rss."""
+
+ def setUp(self):
+ # Number of times to run each of the randomized tests.
+ self.numTests = 100
+ # Maxmimum number of states to test + 1
+ self.maxStates = 10
+ # Maximum number of inputs and outputs to test + 1
+ self.maxIO = 5
+
+ def testShape(self):
+ """Test that rss outputs have the right state, input, and output
+ size."""
+
+ for states in range(1, self.maxStates):
+ for inputs in range(1, self.maxIO):
+ for outputs in range(1, self.maxIO):
+ sys = ss.rss(states, inputs, outputs)
+ self.assertEqual(sys.states, states)
+ self.assertEqual(sys.inputs, inputs)
+ self.assertEqual(sys.outputs, outputs)
+
+ def testPole(self):
+ """Test that the poles of rss outputs have a negative real part."""
+
+ for states in range(1, self.maxStates):
+ for inputs in range(1, self.maxIO):
+ for outputs in range(1, self.maxIO):
+ sys = ss.rss(states, inputs, outputs)
+ p = sys.poles()
+ for z in p:
+ self.assertTrue(z.real < 0)
+
+class TestDrss(unittest.TestCase):
+ """These are tests for the proper functionality of statesp.drss."""
+
+ def setUp(self):
+ # Number of times to run each of the randomized tests.
+ self.numTests = 100
+ # Maxmimum number of states to test + 1
+ self.maxStates = 10
+ # Maximum number of inputs and outputs to test + 1
+ self.maxIO = 5
+
+ def testShape(self):
+ """Test that drss outputs have the right state, input, and output
+ size."""
+
+ for states in range(1, self.maxStates):
+ for inputs in range(1, self.maxIO):
+ for outputs in range(1, self.maxIO):
+ sys = ss.drss(states, inputs, outputs)
+ self.assertEqual(sys.states, states)
+ self.assertEqual(sys.inputs, inputs)
+ self.assertEqual(sys.outputs, outputs)
+
+ def testPole(self):
+ """Test that the poles of drss outputs have less than unit magnitude."""
+
+ for states in range(1, self.maxStates):
+ for inputs in range(1, self.maxIO):
+ for outputs in range(1, self.maxIO):
+ sys = ss.drss(states, inputs, outputs)
+ p = sys.poles()
+ for z in p:
+ self.assertTrue(abs(z) < 1)
+
if __name__ == "__main__":
- unittest.main()
\ No newline at end of file
+ unittest.main()
\ No newline at end of file
Modified: branches/control-0.4a/src/statesp.py
===================================================================
--- branches/control-0.4a/src/statesp.py 2011-02-08 22:13:13 UTC (rev 42)
+++ branches/control-0.4a/src/statesp.py 2011-02-08 22:13:17 UTC (rev 43)
@@ -58,7 +58,7 @@
class StateSpace:
"""The StateSpace class is used to represent linear input/output systems.
"""
- # Initialization
+
def __init__(self, A, B, C, D):
self.A = A
self.B = B
@@ -68,7 +68,12 @@
self.states = A.shape[0]
self.inputs = B.shape[1]
self.outputs = C.shape[0]
-
+
+ # Check that the inputs are arrays or matrices.
+ if not (isinstance(A, sp.ndarray) and isinstance(B, sp.ndarray) and
+ isinstance(C, sp.ndarray) and isinstance(D, sp.ndarray)):
+ raise ValueError("A, B, C, and D must be arrays or matrices.")
+
# Check that the matrix sizes are consistent.
if self.states != A.shape[1]:
raise ValueError("A must be square.")
@@ -224,9 +229,11 @@
# Check to make sure the dimensions are OK
if ((self.inputs != other.outputs) or (self.outputs != other.inputs)):
- raise ValueError, "State space systems don't have compatible inputs/outputs for feedback."
+ raise ValueError, "State space systems don't have compatible \
+ inputs/outputs for feedback."
- # note that if there is an algebraic loop then this matrix inversion won't work
+ # note that if there is an algebraic loop then this matrix inversion
+ # won't work
# (I-D1 D2) or (I-D2 D1) will be singular
# the easiest way to get this is to have D1 = I, D2 = I
#! TODO: trap this error and report algebraic loop
@@ -237,9 +244,11 @@
E12 = inv(eye(self.inputs)+sign*other.D*self.D)
A = concatenate((
- concatenate(( self.A-sign*self.B*E12*other.D*self.C, -sign*self.B*E12*other.C ),axis=1),
- concatenate(( other.B*E21*self.C, other.A-sign*other.B*E21*self.D*other.C ),axis=1),
- ),axis=0)
+ concatenate(( self.A-sign*self.B*E12*other.D*self.C,
+ -sign*self.B*E12*other.C ),axis=1),
+ concatenate(( other.B*E21*self.C,
+ other.A-sign*other.B*E21*self.D*other.C ),axis=1),),
+ axis=0)
B = concatenate( (self.B*E12, other.B*E21*self.D), axis=0 )
C = concatenate( (E21*self.C, -sign*E21*self.D*other.C), axis=1 )
D = E21*self.D
@@ -270,17 +279,43 @@
raise TypeError("can't convert given type to StateSpace system")
def rss(states=1, inputs=1, outputs=1):
- """Create a stable random state space object."""
+ """Create a stable continuous random state space object."""
+ return rss_generate(states, inputs, outputs, 'c')
+
+def drss(states=1, inputs=1, outputs=1):
+ """Create a stable discrete random state space object."""
+
+ return rss_generate(states, inputs, outputs, 'd')
+
+def rss_generate(states, inputs, outputs, type):
+ """This does the actual random state space generation expected from rss and
+ drss. type is 'c' for continuous systems and 'd' for discrete systems."""
+
import numpy
from numpy.random import rand, randn
+
+ # Probability of repeating a previous root.
+ pRepeat = 0.05
+ # Probability of choosing a real root. Note that when choosing a complex
+ # root, the conjugate gets chosen as well. So the expected proportion of
+ # real roots is pReal / (pReal + 2 * (1 - pReal)).
+ pReal = 0.6
+ # Probability that an element in B or C will not be masked out.
+ pBCmask = 0.8
+ # Probability that an element in D will not be masked out.
+ pDmask = 0.3
+ # Probability that D = 0.
+ pDzero = 0.5
# Make some poles for A. Preallocate a complex array.
poles = numpy.zeros(states) + numpy.zeros(states) * 0.j
i = 0
- while i < states - 1:
- if rand() < 0.05 and i != 0:
- # Small chance of copying poles, if we're not at the first element.
+
+ while i < states:
+ if rand() < pRepeat and i != 0 and i != states - 1:
+ # Small chance of copying poles, if we're not at the first or last
+ # element.
if poles[i-1].imag == 0:
# Copy previous real pole.
poles[i] = poles[i-1]
@@ -289,19 +324,24 @@
# Copy previous complex conjugate pair of poles.
poles[i:i+2] = poles[i-2:i]
i += 2
- elif rand() < 0.6:
- # Real pole.
- poles[i] = -sp.exp(randn()) + 0.j
+ elif rand() < pReal or i == states - 1:
+ # No-oscillation pole.
+ if type == 'c':
+ poles[i] = -sp.exp(randn()) + 0.j
+ elif type == 'd':
+ poles[i] = 2. * rand() - 1.
i += 1
else:
- # Complex conjugate pair of poles.
- poles[i] = complex(-sp.exp(randn()), sp.exp(randn()))
+ # Complex conjugate pair of oscillating poles.
+ if type == 'c':
+ poles[i] = complex(-sp.exp(randn()), 3. * sp.exp(randn()))
+ elif type == 'd':
+ mag = rand()
+ phase = 2. * numpy.pi * rand()
+ poles[i] = complex(mag * numpy.cos(phase),
+ mag * numpy.sin(phase))
poles[i+1] = complex(poles[i].real, -poles[i].imag)
i += 2
- # When we reach this point, we either have one or zero poles left to fill.
- # Put a real pole if there is one space left.
- if i == states - 1:
- poles[i] = -sp.exp(randn()) + 0.j
# Now put the poles in A as real blocks on the diagonal.
A = numpy.zeros((states, states))
@@ -315,7 +355,6 @@
A[i, i+1] = poles[i].imag
A[i+1, i] = -poles[i].imag
i += 2
-
# Finally, apply a transformation so that A is not block-diagonal.
while True:
T = randn(states, states)
@@ -333,18 +372,21 @@
# Make masks to zero out some of the elements.
while True:
- B_mask = rand(states, inputs) < 0.8
- if sp.any(B_mask): # Retry if we get all zeros.
+ Bmask = rand(states, inputs) < pBCmask
+ if sp.any(Bmask): # Retry if we get all zeros.
break
while True:
- C_mask = rand(outputs, states) < 0.8
- if sp.any(C_mask): # Retry if we get all zeros.
+ Cmask = rand(outputs, states) < pBCmask
+ if sp.any(Cmask): # Retry if we get all zeros.
break
- D_mask = rand(outputs, inputs) < 0.3
+ if rand() < pDzero:
+ Dmask = numpy.zeros((outputs, inputs))
+ else:
+ Dmask = rand(outputs, inputs) < pDmask
# Apply masks.
- B = B * B_mask
- C = C * C_mask
- D = D * D_mask
+ B = B * Bmask
+ C = C * Cmask
+ D = D * Dmask
- return StateSpace(A, B, C, D)
+ return StateSpace(A, B, C, D)
\ No newline at end of file
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|