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. |