Skip to content

Commit f701b75

Browse files
committed
added methods dynamics and output to StateSpace
1 parent 9c6c619 commit f701b75

File tree

2 files changed

+123
-3
lines changed

2 files changed

+123
-3
lines changed

control/statesp.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1215,9 +1215,77 @@ def dcgain(self):
12151215

12161216
def _isstatic(self):
12171217
"""True if and only if the system has no dynamics, that is,
1218-
if A and B are zero. """
1218+
if `self.A` and `self.B` are zero.
1219+
"""
12191220
return not np.any(self.A) and not np.any(self.B)
12201221

1222+
def dynamics(self, x, u=None):
1223+
"""Compute the dynamics of the system
1224+
1225+
Given input `u` and state `x`, returns the dynamics of the state-space
1226+
system. If the system is continuous, returns the time derivative dx/dt
1227+
1228+
dx/dt = A x + B u
1229+
1230+
where A and B are the state-space matrices of the system. If the
1231+
system is discrete-time, returns the next value of `x`:
1232+
1233+
x[k+1] = A x[k] + B u[k]
1234+
1235+
The inputs `x` and `u` must be of the correct length.
1236+
1237+
Parameters
1238+
----------
1239+
x : array_like
1240+
current state
1241+
u : array_like
1242+
input (optional)
1243+
1244+
Returns
1245+
-------
1246+
dx/dt or x[k+1] : ndarray
1247+
"""
1248+
1249+
if len(np.atleast_1d(x)) != self.nstates:
1250+
raise ValueError("len(x) must be equal to number of states")
1251+
if u is not None:
1252+
if len(np.atleast_1d(u)) != self.ninputs:
1253+
raise ValueError("len(u) must be equal to number of inputs")
1254+
1255+
return self.A.dot(x) if u is None else self.A.dot(x) + self.B.dot(u)
1256+
1257+
def output(self, x, u=None):
1258+
"""Compute the output of the system
1259+
1260+
Given input `u` and state `x`, returns the output `y` of the
1261+
state-space system:
1262+
1263+
y = C x + D u
1264+
1265+
where A and B are the state-space matrices of the system. The inputs
1266+
`x` and `u` must be of the correct length.
1267+
1268+
Parameters
1269+
----------
1270+
x : array_like
1271+
current state
1272+
u : array_like
1273+
input (optional)
1274+
1275+
Returns
1276+
-------
1277+
y : ndarray
1278+
"""
1279+
if len(np.atleast_1d(x)) != self.nstates:
1280+
raise ValueError("len(x) must be equal to number of states")
1281+
if u is not None:
1282+
if len(np.atleast_1d(u)) != self.ninputs:
1283+
raise ValueError("len(u) must be equal to number of inputs")
1284+
1285+
return self.C.dot(x) if u is None else self.C.dot(x) + self.D.dot(u)
1286+
1287+
1288+
12211289

12221290
# TODO: add discrete time check
12231291
def _convert_to_statespace(sys, **kw):

control/tests/statesp_test.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
import numpy as np
11+
from numpy.testing import assert_allclose, assert_array_almost_equal
1112
import pytest
1213
import operator
1314
from numpy.linalg import solve
@@ -42,14 +43,26 @@ def sys322ABCD(self):
4243
[0., 1.]]
4344
return (A322, B322, C322, D322)
4445

46+
47+
@pytest.fixture
48+
def sys121(self):
49+
"""2 state, 1 input, 1 output (siso) system"""
50+
A121 = [[4., 1.],
51+
[2., -3]]
52+
B121 = [[5.],
53+
[-3.]]
54+
C121 = [[2., -4]]
55+
D121 = [[3.]]
56+
return StateSpace(A121, B121, C121, D121)
57+
4558
@pytest.fixture
4659
def sys322(self, sys322ABCD):
47-
"""3-states square system (2 inputs x 2 outputs)"""
60+
"""3-state square system (2 inputs x 2 outputs)"""
4861
return StateSpace(*sys322ABCD)
4962

5063
@pytest.fixture
5164
def sys222(self):
52-
"""2-states square system (2 inputs x 2 outputs)"""
65+
"""2-state square system (2 inputs x 2 outputs)"""
5366
A222 = [[4., 1.],
5467
[2., -3]]
5568
B222 = [[5., 2.],
@@ -744,6 +757,45 @@ def test_horner(self, sys322):
744757
np.squeeze(sys322.horner(1.j)),
745758
mag[:, :, 0] * np.exp(1.j * phase[:, :, 0]))
746759

760+
@pytest.mark.parametrize('x', [[1, 1], [[1], [1]], np.atleast_2d([1,1]).T])
761+
@pytest.mark.parametrize('u', [None, 0, 1, np.atleast_1d(2)])
762+
def test_dynamics_output_siso(self, x, u, sys121):
763+
assert_array_almost_equal(
764+
sys121.dynamics(x, u),
765+
sys121.A.dot(x) + (0 if u is None else sys121.B.dot(u)))
766+
assert_array_almost_equal(
767+
sys121.output(x, u),
768+
sys121.C.dot(x) + (0 if u is None else sys121.D.dot(u)))
769+
770+
# too few and too many states and inputs
771+
@pytest.mark.parametrize('x', [0, 1, [], [1, 2, 3], np.atleast_1d(2)])
772+
@pytest.mark.parametrize('u', [None, [1, 1], np.atleast_1d((2, 2))])
773+
def test_dynamics_output_siso_fails(self, x, u, sys121):
774+
with pytest.raises(ValueError):
775+
sys121.dynamics(x, u)
776+
with pytest.raises(ValueError):
777+
sys121.output(x, u)
778+
779+
@pytest.mark.parametrize('x',[[1, 1], [[1], [1]], np.atleast_2d([1,1]).T])
780+
@pytest.mark.parametrize('u',
781+
[None, [1, 1], [[1], [1]], np.atleast_2d([1,1]).T])
782+
def test_dynamics_output_mimo(self, x, u, sys222):
783+
assert_array_almost_equal(
784+
sys222.dynamics(x, u),
785+
sys222.A.dot(x) + (0 if u is None else sys222.B.dot(u)))
786+
assert_array_almost_equal(
787+
sys222.output(x, u),
788+
sys222.C.dot(x) + (0 if u is None else sys222.D.dot(u)))
789+
790+
# too few and too many states and inputs
791+
@pytest.mark.parametrize('x', [0, 1, [1, 1, 1]])
792+
@pytest.mark.parametrize('u', [None, 0, 1, [1, 1, 1]])
793+
def test_dynamics_mimo_fails(self, x, u, sys222):
794+
with pytest.raises(ValueError):
795+
sys222.dynamics(x, u)
796+
with pytest.raises(ValueError):
797+
sys222.output(x, u)
798+
747799
class TestRss:
748800
"""These are tests for the proper functionality of statesp.rss."""
749801

0 commit comments

Comments
 (0)