8000 BugFix?: allow straightforward creation of static gain StateSpace obj… · python-control/python-control@315a1ea · GitHub
[go: up one dir, main page]

Skip to content

Commit 315a1ea

Browse files
committed
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.
1 parent cdd3e73 commit 315a1ea

File tree

2 files changed

+67
-20
lines changed

2 files changed

+67
-20
lines changed

control/statesp.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -122,34 +122,35 @@ def __init__(self, *args):
122122
else:
123123
raise ValueError("Needs 1 or 4 arguments; received %i." % len(args))
124124

125-
# Here we're going to convert inputs to matrices, if the user gave a
126-
# non-matrix type.
127-
#! TODO: [A, B, C, D] = map(matrix, [A, B, C, D])?
128-
matrices = [A, B, C, D]
129-
for i in range(len(matrices)):
130-
# Convert to matrix first, if necessary.
131-
matrices[i] = matrix(matrices[i])
132-
[A, B, C, D] = matrices
133-
134-
LTI.__init__(self, B.shape[1], C.shape[0], dt)
125+
A, B, C, D = [matrix(M) for M in (A, B, C, D)]
126+
127+
# TODO: use super here?
128+
LTI.__init__(self, inputs=D.shape[1], outputs=D.shape[0], dt=dt)
135129
self.A = A
136130
self.B = B
137131
self.C = C
138132
self.D = D
139133

140-
self.states = A.shape[0]
134+
self.states = A.shape[1]
135+
136+
if 0 == self.states:
137+
# static gain
138+
# matrix's default "empty" shape is 1x0
139+
A.shape = (0,0)
140+
B.shape = (0,self.inputs)
141+
C.shape = (self.outputs,0)
141142

142143
# Check that the matrix sizes are consistent.
143-
if self.states != A.shape[1]:
144+
if self.states != A.shape[0]:
144145
raise ValueError("A must be square.")
145146
if self.states != B.shape[0]:
146-
raise ValueError("B must have the same row size as A.")
147+
raise ValueError("A and B must have the same number of rows.")
147148
if self.states != C.shape[1]:
148-
raise ValueError("C must have the same column size as A.")
149-
if self.inputs != D.shape[1]:
150-
raise ValueError("D must have the same column size as B.")
151-
if self.outputs != D.shape[0]:
152-
raise ValueError("D must have the same row size as C.")
149+
raise ValueError("A and C C must have the same number of columns.")
150+
if self.inputs != B.shape[1]:
151+
raise ValueError("B and D must have the same number of columns.")
152+
if self.outputs != C.shape[0]:
153+
raise ValueError("C and D must have the same number of rows.")
153154

154155
# Check for states that don't do anything, and remove them.
155156
self._remove_useless_states()

control/tests/statesp_test.py

Lines changed: 48 additions & 2 deletions
< 6A4F td data-grid-cell-id="diff-4c76080acbdea36493458693d93ec10e4e10b2e04ce01c56e8a81567cc1ea0d6-237-278-1" data-selected="false" role="gridcell" style="background-color:var(--diffBlob-additionNum-bgColor, var(--diffBlob-addition-bgColor-num));text-align:center" tabindex="-1" valign="top" class="focusable-grid-cell diff-line-number position-relative left-side">278
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
import unittest
77
import numpy as np
8-
from scipy.linalg import eigvals
8+
from numpy.linalg import solve
9+
from scipy.linalg import eigvals, block_diag
910
from control import matlab
10-
from control.statesp import StateSpace, _convertToStateSpace
11+
from control.statesp import StateSpace, _convertToStateSpace,tf2ss
1112
from control.xferfcn import TransferFunction
1213

1314
class TestStateSpace(unittest.TestCase):
@@ -235,6 +236,51 @@ def test_dcgain(self):
235236
sys3 = StateSpace(0., 1., 1., 0.)
236237
np.testing.assert_equal(sys3.dcgain(), np.nan)
237238

239+
240+
def test_scalarStaticGain(self):
241+
"""Regression: can we create a scalar static gain?"""
242+
g1=StateSpace([],[],[],[2])
243+
g2=StateSpace([],[],[],[3])
244+
245+
# make sure StateSpace internals, specifically ABC matrix
246+
# sizes, are OK for LTI operations
247+
g3 = g1*g2
248+
self.assertEqual(6, g3.D[0,0])
249+
g4 = g1+g2
250+
self.assertEqual(5, g4.D[0,0])
251+
g5 = g1.feedback(g2)
252+
self.assertAlmostEqual(2./7, g5.D[0,0])
253+
g6 = g1.append(g2)
254+
np.testing.assert_array_equal(np.diag([2,3]),g6.D)
255+
256+
def test_matrixStaticGain(self):
257+
"""Regression: can we create a scalar static gain?"""
258+
d1 = np.matrix([[1,2,3],[4,5,6]])
259+
d2 = np.matrix([[7,8],[9,10],[11,12]])
260+
g1=StateSpace([],[],[],d1)
261+
g2=StateSpace([],[],[],d2)
262+
g3=StateSpace([],[],[],d2.T)
263+
264+
h1 = g1*g2
265+
np.testing.assert_array_equal(d1*d2, h1.D)
266+
h2 = g1+g3
267+
np.testing.assert_array_equal(d1+d2.T, h2.D)
268+
h3 = g1.feedback(g2)
269+
np.testing.assert_array_almost_equal(solve(np.eye(2)+d1*d2,d1), h3.D)
270+
h4 = g1.append(g2)
271+
np.testing.assert_array_equal(block_diag(d1,d2),h4.D)
272+
273+
274+
def test_BadEmptyMatrices(self):
275+
"""Mismatched ABCD matrices when some are empty"""
276+
self.assertRaises(ValueError,StateSpace, [1], [], [], [1])
277+
self.assertRaises(ValueError,StateSpace, [1], [1], [], [1])
+
self.assertRaises(ValueError,StateSpace, [1], [], [1], [1])
279+
self.assertRaises(ValueError,StateSpace, [], [1], [], [1])
280+
self.assertRaises(ValueError,StateSpace, [], [1], [1], [1])
281+
self.assertRaises(ValueError,StateSpace, [], [], [1], [1])
282+
self.assertRaises(ValueError,StateSpace, [1], [1], [1], [])
283+
238284
class TestRss(unittest.TestCase):
239285
"""These are tests for the proper functionality of statesp.rss."""
240286

0 commit comments

Comments
 (0)
0