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
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])
278+
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)
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