Skip to content

Commit

Permalink
BugFix: allow empty (no input or output) StateSpace objects.
Browse files Browse the repository at this point in the history
Conflicts:
	control/tests/statesp_test.py
  • Loading branch information
roryyorke committed Sep 19, 2016
1 parent 2794260 commit e640b17
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 8 deletions.
28 changes: 20 additions & 8 deletions control/statesp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
24 changes: 24 additions & 0 deletions control/tests/statesp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]])
Expand Down Expand Up @@ -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."""

Expand Down

0 comments on commit e640b17

Please sign in to comment.