From 564b1dcc9e856e2e8958d1b108d04af5d97f70c0 Mon Sep 17 00:00:00 2001 From: Rory Yorke Date: Thu, 11 Aug 2016 21:28:03 +0200 Subject: [PATCH 1/4] BugFix: allow straightforward creation of static gain StateSpace objects. Allows StateSpace([],[],[],D), which failed previously. Static gains have sizes enforced as follows: A 0-by-0, B 0-by-ninputs, C noutputs-by-0. Tests added for instantiation, and sum, product, feedback, and appending, of 1x1, 2x3, and 3x2 static gains StateSpace objects. --- control/statesp.py | 37 ++++++++++++++------------- control/tests/statesp_test.py | 48 ++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/control/statesp.py b/control/statesp.py index 80da2a5f8..40811a065 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -122,34 +122,35 @@ def __init__(self, *args): else: raise ValueError("Needs 1 or 4 arguments; received %i." % len(args)) - # Here we're going to convert inputs to matrices, if the user gave a - # non-matrix type. - #! TODO: [A, B, C, D] = map(matrix, [A, B, C, D])? - matrices = [A, B, C, D] - for i in range(len(matrices)): - # Convert to matrix first, if necessary. - matrices[i] = matrix(matrices[i]) - [A, B, C, D] = matrices - - LTI.__init__(self, B.shape[1], C.shape[0], dt) + A, B, C, D = [matrix(M) for M in (A, B, C, D)] + + # TODO: use super here? + LTI.__init__(self, inputs=D.shape[1], outputs=D.shape[0], dt=dt) self.A = A self.B = B self.C = C self.D = D - self.states = A.shape[0] + self.states = A.shape[1] + + if 0 == self.states: + # static gain + # matrix's default "empty" shape is 1x0 + A.shape = (0,0) + B.shape = (0,self.inputs) + C.shape = (self.outputs,0) # Check that the matrix sizes are consistent. - if self.states != A.shape[1]: + if self.states != A.shape[0]: raise ValueError("A must be square.") if self.states != B.shape[0]: - raise ValueError("B must have the same row size as A.") + raise ValueError("A and B must have the same number of rows.") if self.states != C.shape[1]: - raise ValueError("C must have the same column size as A.") - if self.inputs != D.shape[1]: - raise ValueError("D must have the same column size as B.") - if self.outputs != D.shape[0]: - raise ValueError("D must have the same row size as C.") + raise ValueError("A and C C must have the same number of columns.") + if self.inputs != B.shape[1]: + raise ValueError("B and D must have the same number of columns.") + if self.outputs != C.shape[0]: + raise ValueError("C and D must have the same number of rows.") # Check for states that don't do anything, and remove them. self._remove_useless_states() diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index c19f4f38b..5afe5c5e9 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -5,7 +5,8 @@ import unittest import numpy as np -from scipy.linalg import eigvals +from numpy.linalg import solve +from scipy.linalg import eigvals, block_diag from control import matlab from control.statesp import StateSpace, _convertToStateSpace from control.xferfcn import TransferFunction @@ -235,6 +236,51 @@ def test_dcgain(self): sys3 = StateSpace(0., 1., 1., 0.) np.testing.assert_equal(sys3.dcgain(), np.nan) + + def test_scalarStaticGain(self): + """Regression: can we create a scalar static gain?""" + g1=StateSpace([],[],[],[2]) + g2=StateSpace([],[],[],[3]) + + # make sure StateSpace internals, specifically ABC matrix + # sizes, are OK for LTI operations + g3 = g1*g2 + self.assertEqual(6, g3.D[0,0]) + g4 = g1+g2 + self.assertEqual(5, g4.D[0,0]) + g5 = g1.feedback(g2) + self.assertAlmostEqual(2./7, g5.D[0,0]) + g6 = g1.append(g2) + np.testing.assert_array_equal(np.diag([2,3]),g6.D) + + def test_matrixStaticGain(self): + """Regression: can we create a scalar static gain?""" + d1 = np.matrix([[1,2,3],[4,5,6]]) + d2 = np.matrix([[7,8],[9,10],[11,12]]) + g1=StateSpace([],[],[],d1) + g2=StateSpace([],[],[],d2) + g3=StateSpace([],[],[],d2.T) + + h1 = g1*g2 + np.testing.assert_array_equal(d1*d2, h1.D) + h2 = g1+g3 + np.testing.assert_array_equal(d1+d2.T, h2.D) + h3 = g1.feedback(g2) + np.testing.assert_array_almost_equal(solve(np.eye(2)+d1*d2,d1), h3.D) + h4 = g1.append(g2) + np.testing.assert_array_equal(block_diag(d1,d2),h4.D) + + + def test_BadEmptyMatrices(self): + """Mismatched ABCD matrices when some are empty""" + self.assertRaises(ValueError,StateSpace, [1], [], [], [1]) + self.assertRaises(ValueError,StateSpace, [1], [1], [], [1]) + self.assertRaises(ValueError,StateSpace, [1], [], [1], [1]) + self.assertRaises(ValueError,StateSpace, [], [1], [], [1]) + self.assertRaises(ValueError,StateSpace, [], [1], [1], [1]) + self.assertRaises(ValueError,StateSpace, [], [], [1], [1]) + self.assertRaises(ValueError,StateSpace, [1], [1], [1], []) + class TestRss(unittest.TestCase): """These are tests for the proper functionality of statesp.rss.""" From 4a74cc559468256d179b9f72f0886208ff6b0771 Mon Sep 17 00:00:00 2001 From: Rory Yorke Date: Thu, 11 Aug 2016 22:01:59 +0200 Subject: [PATCH 2/4] BugFix: fix Python 2.7 failure. On Python 2.7, the special case "all states useless" in _remove_useless_states resulted in A=[[0]] (and similarly for B and C). The special case is no longer needed, since empty A, B, C matrices can be handled. numpy.delete does the right thing w.r.t. matrix sizes (e.g., deleting all columns of a nxm matrix gives an nx0 matrix). Added test for this. --- control/statesp.py | 17 +++++------------ control/tests/statesp_test.py | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/control/statesp.py b/control/statesp.py index 40811a065..b74c5cb71 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -146,7 +146,7 @@ def __init__(self, *args): if self.states != B.shape[0]: raise ValueError("A and B must have the same number of rows.") if self.states != C.shape[1]: - raise ValueError("A and C C must have the same number of columns.") + raise ValueError("A and C must have the same number of columns.") if self.inputs != B.shape[1]: raise ValueError("B and D must have the same number of columns.") if self.outputs != C.shape[0]: @@ -180,17 +180,10 @@ def _remove_useless_states(self): useless.append(i) # Remove the useless states. - if all(useless == range(self.states)): - # All the states were useless. - self.A = zeros((1, 1)) - self.B = zeros((1, self.inputs)) - self.C = zeros((self.outputs, 1)) - else: - # A more typical scenario. - self.A = delete(self.A, useless, 0) - self.A = delete(self.A, useless, 1) - self.B = delete(self.B, useless, 0) - self.C = delete(self.C, useless, 1) + self.A = delete(self.A, useless, 0) + self.A = delete(self.A, useless, 1) + self.B = delete(self.B, useless, 0) + self.C = delete(self.C, useless, 1) self.states = self.A.shape[0] self.inputs = self.B.shape[1] diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index 5afe5c5e9..39f4a965d 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -254,10 +254,14 @@ def test_scalarStaticGain(self): np.testing.assert_array_equal(np.diag([2,3]),g6.D) def test_matrixStaticGain(self): - """Regression: can we create a scalar static gain?""" + """Regression: can we create matrix static gains?""" d1 = np.matrix([[1,2,3],[4,5,6]]) d2 = np.matrix([[7,8],[9,10],[11,12]]) g1=StateSpace([],[],[],d1) + + # _remove_useless_states was making A = [[0]] + self.assertEqual((0,0), g1.A.shape) + g2=StateSpace([],[],[],d2) g3=StateSpace([],[],[],d2.T) @@ -271,6 +275,19 @@ def test_matrixStaticGain(self): np.testing.assert_array_equal(block_diag(d1,d2),h4.D) + def test_remove_useless_states(self): + """Regression: _remove_useless_states gives correct ABC sizes""" + g1 = StateSpace(np.zeros((3,3)), + np.zeros((3,4)), + np.zeros((5,3)), + np.zeros((5,4))) + self.assertEqual((0,0), g1.A.shape) + self.assertEqual((0,4), g1.B.shape) + self.assertEqual((5,0), g1.C.shape) + self.assertEqual((5,4), g1.D.shape) + self.assertEqual(0, g1.states) + + def test_BadEmptyMatrices(self): """Mismatched ABCD matrices when some are empty""" self.assertRaises(ValueError,StateSpace, [1], [], [], [1]) From 09f9bab4e5d984dffc9215d2e8e80f2958873759 Mon Sep 17 00:00:00 2001 From: Rory Yorke Date: Fri, 12 Aug 2016 18:54:53 +0200 Subject: [PATCH 3/4] BugFix: allow minreal on static gain StateSpace objects Do this by only calling Slycot's tb01pd for non-static systems. --- control/statesp.py | 28 ++++++++++++++++------------ control/tests/statesp_test.py | 11 +++++++++++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/control/statesp.py b/control/statesp.py index b74c5cb71..0aa568e38 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -471,18 +471,22 @@ def feedback(self, other=1, sign=-1): def minreal(self, tol=0.0): """Calculate a minimal realization, removes unobservable and uncontrollable states""" - try: - from slycot import tb01pd - B = empty((self.states, max(self.inputs, self.outputs))) - B[:,:self.inputs] = self.B - C = empty((max(self.outputs, self.inputs), self.states)) - C[:self.outputs,:] = self.C - A, B, C, nr = tb01pd(self.states, self.inputs, self.outputs, - self.A, B, C, tol=tol) - return StateSpace(A[:nr,:nr], B[:nr,:self.inputs], - C[:self.outputs,:nr], self.D) - except ImportError: - raise TypeError("minreal requires slycot tb01pd") + if self.states: + try: + from slycot import tb01pd + B = empty((self.states, max(self.inputs, self.outputs))) + B[:,:self.inputs] = self.B + C = empty((max(self.outputs, self.inputs), self.states)) + C[:self.outputs,:] = self.C + A, B, C, nr = tb01pd(self.states, self.inputs, self.outputs, + self.A, B, C, tol=tol) + return StateSpace(A[:nr,:nr], B[:nr,:self.inputs], + C[:self.outputs,:nr], self.D) + except ImportError: + raise TypeError("minreal requires slycot tb01pd") + else: + return StateSpace(self) + # TODO: add discrete time check def returnScipySignalLTI(self): diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index 39f4a965d..f33d24a26 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -298,6 +298,17 @@ def test_BadEmptyMatrices(self): self.assertRaises(ValueError,StateSpace, [], [], [1], [1]) self.assertRaises(ValueError,StateSpace, [1], [1], [1], []) + + def test_minrealStaticGain(self): + """Regression: minreal on static gain was failing""" + g1 = StateSpace([],[],[],[1]) + g2 = g1.minreal() + np.testing.assert_array_equal(g1.A, g2.A) + np.testing.assert_array_equal(g1.B, g2.B) + np.testing.assert_array_equal(g1.C, g2.C) + np.testing.assert_array_equal(g1.D, g2.D) + + class TestRss(unittest.TestCase): """These are tests for the proper functionality of statesp.rss.""" From c84debb49c14abe1df4c537ad42cc5e5a9fd75a3 Mon Sep 17 00:00:00 2001 From: Rory Yorke Date: Wed, 31 Aug 2016 21:22:38 +0200 Subject: [PATCH 4/4] BugFix: pole of stateless StateSpace object is empty array --- control/statesp.py | 2 +- control/tests/statesp_test.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/control/statesp.py b/control/statesp.py index 0aa568e38..1c220630a 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -399,7 +399,7 @@ def freqresp(self, omega): def pole(self): """Compute the poles of a state space system.""" - return eigvals(self.A) + return eigvals(self.A) if self.states else np.array([]) def zero(self): """Compute the zeros of a state space system.""" diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index f33d24a26..bcb411026 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -378,6 +378,12 @@ def testPole(self): self.assertTrue(abs(z) < 1) + def testPoleStatic(self): + """Regression: pole() of static gain is empty array""" + np.testing.assert_array_equal(np.array([]), + StateSpace([],[],[],[[1]]).pole()) + + def suite(): return unittest.TestLoader().loadTestsFromTestCase(TestStateSpace) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy