|
From: <kk...@us...> - 2011-02-08 22:15:09
|
Revision: 65
http://python-control.svn.sourceforge.net/python-control/?rev=65&view=rev
Author: kkchen
Date: 2011-02-08 22:15:01 +0000 (Tue, 08 Feb 2011)
Log Message:
-----------
Major changes regarding feedback.
- StateSpace.feedback rewritten in statesp.py, since the old one was wrong.
- bdalg.py rewritten to be simpler and to match the new feedback routines.
- TestBDAlg.py added to test bdalg.feedback.
Kevin K. Chen <kk...@pr...>
Modified Paths:
--------------
branches/control-0.4a/src/bdalg.py
branches/control-0.4a/src/statesp.py
Added Paths:
-----------
branches/control-0.4a/src/TestBDAlg.py
Added: branches/control-0.4a/src/TestBDAlg.py
===================================================================
--- branches/control-0.4a/src/TestBDAlg.py (rev 0)
+++ branches/control-0.4a/src/TestBDAlg.py 2011-02-08 22:15:01 UTC (rev 65)
@@ -0,0 +1,166 @@
+#!/usr/bin/env python
+
+import numpy as np
+from xferfcn import xTransferFunction
+from statesp import StateSpace
+from bdalg import feedback
+import unittest
+
+class TestFeedback(unittest.TestCase):
+ """These are tests for the feedback function in bdalg.py. Currently, some
+ of the tests are not implemented, or are not working properly. TODO: these
+ need to be fixed."""
+
+ def setUp(self):
+ """This contains some random LTI systems and scalars for testing."""
+
+ # Two random SISO systems.
+ self.sys1 = xTransferFunction([1, 2], [1, 2, 3])
+ self.sys2 = StateSpace([[1., 4.], [3., 2.]], [[1.], [-4.]],
+ [[1., 0.]], [[0.]])
+ # Two random scalars.
+ self.x1 = 2.5
+ self.x2 = -3.
+
+ def testScalarScalar(self):
+ """Scalar system with scalar feedback block."""
+
+ ans1 = feedback(self.x1, self.x2)
+ ans2 = feedback(self.x1, self.x2, 1.)
+
+ self.assertAlmostEqual(ans1.num[0][0][0] / ans1.den[0][0][0],
+ -2.5 / 6.5)
+ self.assertAlmostEqual(ans2.num[0][0][0] / ans2.den[0][0][0], 2.5 / 8.5)
+
+ def testScalarSS(self):
+ """Scalar system with state space feedback block."""
+
+ ans1 = feedback(self.x1, self.sys2)
+ ans2 = feedback(self.x1, self.sys2, 1.)
+
+ # This one doesn't work yet. The feedback system has too many states.
+ np.testing.assert_array_almost_equal(ans1.A, [[-1.5, 4.], [13., 2.]])
+ np.testing.assert_array_almost_equal(ans1.B, [[2.5], [-10.]])
+ np.testing.assert_array_almost_equal(ans1.C, [[-2.5, 0.]])
+ np.testing.assert_array_almost_equal(ans1.D, [[2.5]])
+ np.testing.assert_array_almost_equal(ans2.A, [[3.5, 4.], [-7., 2.]])
+ np.testing.assert_array_almost_equal(ans2.B, [[2.5], [-10.]])
+ np.testing.assert_array_almost_equal(ans2.C, [[2.5, 0.]])
+ np.testing.assert_array_almost_equal(ans2.D, [[2.5]])
+
+ def testScalarTF(self):
+ """Scalar system with transfer function feedback block."""
+
+ ans1 = feedback(self.x1, self.sys1)
+ ans2 = feedback(self.x1, self.sys1, 1.)
+
+ np.testing.assert_array_almost_equal(ans1.num, [[[2.5, 5., 7.5]]])
+ np.testing.assert_array_almost_equal(ans1.den, [[[1., 4.5, 8.]]])
+ np.testing.assert_array_almost_equal(ans2.num, [[[2.5, 5., 7.5]]])
+ np.testing.assert_array_almost_equal(ans2.den, [[[1., -0.5, -2.]]])
+
+ def testSSScalar(self):
+ """State space system with scalar feedback block."""
+
+ ans1 = feedback(self.sys2, self.x1)
+ ans2 = feedback(self.sys2, self.x1, 1.)
+
+ # This one doesn't work yet. The feedback system has too many states.
+ np.testing.assert_array_almost_equal(ans1.A, [[-1.5, 4.], [13., 2.]])
+ np.testing.assert_array_almost_equal(ans1.B, [[1.], [-4.]])
+ np.testing.assert_array_almost_equal(ans1.C, [[1., 0.]])
+ np.testing.assert_array_almost_equal(ans1.D, [[0.]])
+ np.testing.assert_array_almost_equal(ans2.A, [[3.5, 4.], [-7., 2.]])
+ np.testing.assert_array_almost_equal(ans2.B, [[1.], [-4.]])
+ np.testing.assert_array_almost_equal(ans2.C, [[1., 0.]])
+ np.testing.assert_array_almost_equal(ans2.D, [[0.]])
+
+ def testSSSS1(self):
+ """State space system with state space feedback block."""
+
+ ans1 = feedback(self.sys2, self.sys2)
+ ans2 = feedback(self.sys2, self.sys2, 1.)
+
+ np.testing.assert_array_almost_equal(ans1.A, [[1., 4., -1., 0.],
+ [3., 2., 4., 0.], [1., 0., 1., 4.], [-4., 0., 3., 2]])
+ np.testing.assert_array_almost_equal(ans1.B, [[1.], [-4.], [0.], [0.]])
+ np.testing.assert_array_almost_equal(ans1.C, [[1., 0., 0., 0.]])
+ np.testing.assert_array_almost_equal(ans1.D, [[0.]])
+ np.testing.assert_array_almost_equal(ans2.A, [[1., 4., 1., 0.],
+ [3., 2., -4., 0.], [1., 0., 1., 4.], [-4., 0., 3., 2.]])
+ np.testing.assert_array_almost_equal(ans2.B, [[1.], [-4.], [0.], [0.]])
+ np.testing.assert_array_almost_equal(ans2.C, [[1., 0., 0., 0.]])
+ np.testing.assert_array_almost_equal(ans2.D, [[0.]])
+
+ def testSSSS2(self):
+ """State space system with state space feedback block, including a
+ direct feedthrough term."""
+
+ sys3 = StateSpace([[-1., 4.], [2., -3]], [[2.], [3.]], [[-3., 1.]],
+ [[-2.]])
+ sys4 = StateSpace([[-3., -2.], [1., 4.]], [[-2.], [-6.]], [[2., -3.]],
+ [[3.]])
+
+ ans1 = feedback(sys3, sys4)
+ ans2 = feedback(sys3, sys4, 1.)
+
+ np.testing.assert_array_almost_equal(ans1.A,
+ [[-4.6, 5.2, 0.8, -1.2], [-3.4, -1.2, 1.2, -1.8],
+ [-1.2, 0.4, -1.4, -4.4], [-3.6, 1.2, 5.8, -3.2]])
+ np.testing.assert_array_almost_equal(ans1.B,
+ [[-0.4], [-0.6], [-0.8], [-2.4]])
+ np.testing.assert_array_almost_equal(ans1.C, [[0.6, -0.2, -0.8, 1.2]])
+ np.testing.assert_array_almost_equal(ans1.D, [[0.4]])
+ np.testing.assert_array_almost_equal(ans2.A,
+ [[-3.57142857142857, 4.85714285714286, 0.571428571428571,
+ -0.857142857142857],
+ [-1.85714285714286, -1.71428571428571, 0.857142857142857,
+ -1.28571428571429],
+ [0.857142857142857, -0.285714285714286, -1.85714285714286,
+ -3.71428571428571],
+ [2.57142857142857, -0.857142857142857, 4.42857142857143,
+ -1.14285714285714]])
+ np.testing.assert_array_almost_equal(ans2.B, [[0.285714285714286],
+ [0.428571428571429], [0.571428571428571], [1.71428571428571]])
+ np.testing.assert_array_almost_equal(ans2.C, [[-0.428571428571429,
+ 0.142857142857143, -0.571428571428571, 0.857142857142857]])
+ np.testing.assert_array_almost_equal(ans2.D, [[-0.285714285714286]])
+
+
+ def testSSTF(self):
+ """State space system with transfer function feedback block."""
+
+ # This functionality is not implemented yet.
+ pass
+
+ def testTFScalar(self):
+ """Transfer function system with scalar feedback block."""
+
+ ans1 = feedback(self.sys1, self.x1)
+ ans2 = feedback(self.sys1, self.x1, 1.)
+
+ np.testing.assert_array_almost_equal(ans1.num, [[[1., 2.]]])
+ np.testing.assert_array_almost_equal(ans1.den, [[[1., 4.5, 8.]]])
+ np.testing.assert_array_almost_equal(ans2.num, [[[1., 2.]]])
+ np.testing.assert_array_almost_equal(ans2.den, [[[1., -0.5, -2.]]])
+
+ def testTFSS(self):
+ """Transfer function system with state space feedback block."""
+
+ # This functionality is not implemented yet.
+ pass
+
+ def testTFTF(self):
+ """Transfer function system with transfer function feedback block."""
+
+ ans1 = feedback(self.sys1, self.sys1)
+ ans2 = feedback(self.sys1, self.sys1, 1.)
+
+ np.testing.assert_array_almost_equal(ans1.num, [[[1., 4., 7., 6.]]])
+ np.testing.assert_array_almost_equal(ans1.den,
+ [[[1., 4., 11., 16., 13.]]])
+ np.testing.assert_array_almost_equal(ans2.num, [[[1., 4., 7., 6.]]])
+ np.testing.assert_array_almost_equal(ans2.den, [[[1., 4., 9., 8., 5.]]])
+
+if __name__ == "__main__":
+ unittest.main()
Property changes on: branches/control-0.4a/src/TestBDAlg.py
___________________________________________________________________
Added: svn:executable
+ *
Modified: branches/control-0.4a/src/bdalg.py
===================================================================
--- branches/control-0.4a/src/bdalg.py 2011-02-08 22:14:56 UTC (rev 64)
+++ branches/control-0.4a/src/bdalg.py 2011-02-08 22:15:01 UTC (rev 65)
@@ -47,86 +47,56 @@
import xferfcn as tf
import statesp as ss
-# Standard interconnections (implemented by objects)
-def series(sys1, sys2): return sys1 * sys2
-def parallel(sys1, sys2): return sys1 + sys2
-def negate(sys): return -sys;
+def feedback(sys1, sys2, sign=-1):
+ """Feedback interconnection between two I/O systems.
-# Feedback interconnection between systems
-#! TODO: This should be optimized for better performance
-#! TODO: Needs to be updated to work for state space systems
-def feedback(sys1, sys2, **keywords):
- """Feedback interconnection between two I/O systems
-
Usage
=====
- sys = feedback(sys1, sys2, **keywords)
+ sys = feedback(sys1, sys2)
+ sys = feedback(sys1, sys2, sign)
Compute the system corresponding to a feedback interconnection between
- sys1 and sys2.
+ sys1 and sys2. When sign is not specified, it assumes a value of -1
+ (negative feedback). A sign of 1 indicates positive feedback.
Parameters
----------
sys1, sys2: linsys
Linear input/output systems
+ sign: scalar
+ Feedback sign.
Return values
-------------
sys: linsys
- Keywords
- --------
- sign: float
- Sign of the interconnection (default = -1)
-
Notes
-----
- 1. This function calls calls xferfcn.feedback if the arguments are
- all either scalars or SISO transfer functions. If one or both
- of the arguments are state space systems, then the remaining
- arguments are converted to state space, as needed, and the
- statesp.feedback function is used instead. Finally, if none of
- that works, then try using sys1.feedback."""
- # Grab keyword arguments
- signparm = keywords.pop("sign", -1);
+ 1. This function calls calls xferfcn.feedback if sys1 is a TransferFunction
+ object and statesp.feedback if sys1 is a StateSpace object. If sys1 is a
+ scalar, then it is converted to sys2's type, and the corresponding
+ feedback function is used. If sys1 and sys2 are both scalars, then use
+ xferfcn.feedback."""
+
+ # Check for correct input types.
+ if not isinstance(sys1, (int, long, float, complex, tf.xTransferFunction,
+ ss.StateSpace)):
+ raise TypeError("sys1 must be a TransferFunction or StateSpace object, \
+or a scalar.")
+ if not isinstance(sys2, (int, long, float, complex, tf.xTransferFunction,
+ ss.StateSpace)):
+ raise TypeError("sys2 must be a TransferFunction or StateSpace object, \
+or a scalar.")
- #
- # Sort out which set of functions to call
- #
- # The main cases we are interested in are those where we use a
- # constant for one of the arguments to the function, in which case
- # we should use the other argument to figure out what type of
- # object we are acting on. Then call the feedback function for
- # that object.
- #
+ # If sys1 is a scalar, convert it to the appropriate LTI type so that we can
+ # its feedback member function.
+ if isinstance(sys1, (int, long, float, complex)):
+ if isinstance(sys2, tf.xTransferFunction):
+ sys1 = tf.convertToTransferFunction(sys1)
+ elif isinstance(sys2, ss.StateSpace):
+ sys1 = ss.convertToStateSpace(sys1)
+ else: # sys2 is a scalar.
+ sys1 = tf.convertToTransferFunction(sys1)
+ sys2 = tf.convertToTransferFunction(sys2)
- if (isinstance(sys1, tf.TransferFunction) and
- (isinstance(sys2, tf.TransferFunction) or
- isinstance(sys2, (int, long, float, complex)))):
- # Use transfer function feedback function
- return sys1.feedback(sys2, sign=signparm)
-
- elif (isinstance(sys2, tf.TransferFunction) and
- isinstance(sys1, (int, long, float, complex))):
- # Convert sys1 to a transfer function and then perform operation
- sys = tf.convertToTransferFunction(sys1);
- return sys.feedback(sys2, sign=signparm)
-
- elif (isinstance(sys1, ss.StateSpace) and
- (isinstance(sys2, ss.StateSpace) or
- isinstance(sys2, (int, long, float, complex)))):
- # Use state space feedback function
- return sys1.feedback(sys2, sign=signparm)
-
- elif (isinstance(sys2, ss.StateSpace) and
- isinstance(sys1, (int, long, float, complex))):
- # Convert sys1 to state space system and then perform operation
- sys = ss.convertToStateSpace(sys1);
- return sys.feedback(sys2, sign=signparm)
-
- else:
- # Assume that the first system has the right member function
- return sys1.feedback(sys2, sign=signparm)
-
- raise TypeError("can't operate on give types")
-
+ return sys1.feedback(sys2, sign)
Modified: branches/control-0.4a/src/statesp.py
===================================================================
--- branches/control-0.4a/src/statesp.py 2011-02-08 22:14:56 UTC (rev 64)
+++ branches/control-0.4a/src/statesp.py 2011-02-08 22:15:01 UTC (rev 65)
@@ -243,27 +243,36 @@
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
- # (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
- #! TODO: use determinant instead of inverse??
- from scipy.linalg import inv
+ from numpy.linalg import inv, det
from numpy import eye
- E21 = inv(eye(self.outputs)+sign*self.D*other.D)
- E12 = inv(eye(self.inputs)+sign*other.D*self.D)
+
+ A1 = self.A
+ B1 = self.B
+ C1 = self.C
+ D1 = self.D
+ A2 = other.A
+ B2 = other.B
+ C2 = other.C
+ D2 = other.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)
- 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
+ F = eye(self.inputs) - sign * D2 * D1
+ if abs(det(F)) < 1.e-6:
+ raise ValueError("I - sign * D2 * D1 is singular.")
+ E = inv(F)
+ T1 = eye(self.outputs) + sign * D1 * E * D2
+ T2 = eye(self.inputs) + sign * E * D2 * D1
+
+ A = concatenate(
+ (concatenate(
+ (A1 + sign * B1 * E * D2 * C1, sign * B1 * E * C2), axis=1),
+ concatenate(
+ (B2 * T1 * C1, A2 + sign * B2 * D1 * E * C2), axis=1)),
+ axis=0)
+ B = concatenate((B1 * T2, B2 * D1 * T2), axis=0)
+ C = concatenate((T1 * C1, sign * D1 * E * C2), axis=1)
+ D = D1 * T2
+
return StateSpace(A, B, C, D)
#
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|