From e8ee43f5766d831a6ae34a73c8f9e0feffd436f9 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 | 50 +++++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 20 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..f64e0ba76 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -5,9 +5,10 @@ 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.statesp import StateSpace, _convertToStateSpace,tf2ss from control.xferfcn import TransferFunction class TestStateSpace(unittest.TestCase): @@ -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 738264455e0fa71f9c4f5c6162b014c7350ed45f 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 | 15 ++++----------- control/tests/statesp_test.py | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/control/statesp.py b/control/statesp.py index 40811a065..cc77eae2b 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -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 f64e0ba76..bc7cc524e 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,18 @@ 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) + + def test_BadEmptyMatrices(self): """Mismatched ABCD matrices when some are empty""" self.assertRaises(ValueError,StateSpace, [1], [], [], [1]) From 27942600f44afc75c540f252dd21dcb85170a775 Mon Sep 17 00:00:00 2001 From: Rory Yorke Date: Fri, 12 Aug 2016 18:45:01 +0200 Subject: [PATCH 3/4] MessageFix: remove extra 'C' --- control/statesp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/statesp.py b/control/statesp.py index cc77eae2b..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]: From e640b1785739a81254ccde069a777cfcb574ca13 Mon Sep 17 00:00:00 2001 From: Rory Yorke Date: Sun, 18 Sep 2016 21:30:08 +0200 Subject: [PATCH 4/4] BugFix: allow empty (no input or output) StateSpace objects. Conflicts: control/tests/statesp_test.py --- control/statesp.py | 28 ++++++++++++++++++++-------- control/tests/statesp_test.py | 24 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/control/statesp.py b/control/statesp.py index b74c5cb71..4e5a3ed3f 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -52,7 +52,7 @@ import numpy as np from numpy import all, angle, any, array, asarray, concatenate, cos, delete, \ - dot, empty, exp, eye, matrix, ones, pi, poly, poly1d, roots, shape, sin, \ + dot, empty, exp, eye, ones, pi, poly, poly1d, roots, shape, sin, \ zeros, squeeze from numpy.random import rand, randn from numpy.linalg import solve, eigvals, matrix_rank @@ -66,6 +66,19 @@ __all__ = ['StateSpace', 'ss', 'rss', 'drss', 'tf2ss', 'ssdata'] + +def _matrix(a): + """_matrix(a) -> numpy.matrix + a - passed to numpy.matrix + Wrapper around numpy.matrix; unlike that function, _matrix([]) will be 0x0 + """ + from numpy import matrix + am = matrix(a) + if (1,0) == am.shape: + am.shape = (0,0) + return am + + class StateSpace(LTI): """A class for representing state-space models @@ -122,7 +135,7 @@ def __init__(self, *args): else: raise ValueError("Needs 1 or 4 arguments; received %i." % len(args)) - A, B, C, D = [matrix(M) for M in (A, B, C, D)] + 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) @@ -327,8 +340,9 @@ def __rmul__(self, other): return _convertToStateSpace(other) * self # try to treat this as a matrix + # TODO: doesn't _convertToStateSpace do this anyway? try: - X = matrix(other) + X = _matrix(other) C = X * self.C D = X * self.D return StateSpace(self.A, self.B, C, D, self.dt) @@ -686,11 +700,9 @@ def _convertToStateSpace(sys, **kw): # If this is a matrix, try to create a constant feedthrough try: - D = matrix(sys) - outputs, inputs = D.shape - - return StateSpace(0., zeros((1, inputs)), zeros((outputs, 1)), D) - except Exception(e): + D = _matrix(sys) + return StateSpace([], [], [], D) + except Exception as e: print("Failure to assume argument is matrix-like in" \ " _convertToStateSpace, result %s" % e) diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index bc7cc524e..5948bf727 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -253,6 +253,7 @@ def test_scalarStaticGain(self): g6 = g1.append(g2) np.testing.assert_array_equal(np.diag([2,3]),g6.D) + def test_matrixStaticGain(self): """Regression: can we create matrix static gains?""" d1 = np.matrix([[1,2,3],[4,5,6]]) @@ -297,6 +298,29 @@ def test_BadEmptyMatrices(self): self.assertRaises(ValueError,StateSpace, [], [], [1], [1]) self.assertRaises(ValueError,StateSpace, [1], [1], [1], []) + + def test_Empty(self): + """Regression: can we create an empty StateSpace object?""" + g1=StateSpace([],[],[],[]) + self.assertEqual(0,g1.states) + self.assertEqual(0,g1.inputs) + self.assertEqual(0,g1.outputs) + + + def test_MatrixToStateSpace(self): + """_convertToStateSpace(matrix) gives ss([],[],[],D)""" + D = np.matrix([[1,2,3],[4,5,6]]) + g = _convertToStateSpace(D) + def empty(shape): + m = np.matrix([]) + m.shape = shape + return m + np.testing.assert_array_equal(empty((0,0)), g.A) + np.testing.assert_array_equal(empty((0,D.shape[1])), g.B) + np.testing.assert_array_equal(empty((D.shape[0],0)), g.C) + np.testing.assert_array_equal(D,g.D) + + class TestRss(unittest.TestCase): """These are tests for the proper functionality of statesp.rss.""" 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