Skip to content

Commit 1502d38

Browse files
authored
Merge pull request #514 from murrayrm/mimo_impulse_step_response
MIMO impulse and step response
2 parents 0a08ff2 + 28bbe7f commit 1502d38

File tree

5 files changed

+342
-111
lines changed

5 files changed

+342
-111
lines changed

control/config.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
_control_defaults = {
1818
'control.default_dt': 0,
1919
'control.squeeze_frequency_response': None,
20-
'control.squeeze_time_response': True,
2120
'control.squeeze_time_response': None,
2221
'forced_response.return_x': False,
2322
}

control/iosys.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,8 +1428,9 @@ def input_output_response(sys, T, U=0., X0=0, params={}, method='RK45',
14281428
for i in range(len(T)):
14291429
u = U[i] if len(U.shape) == 1 else U[:, i]
14301430
y[:, i] = sys._out(T[i], [], u)
1431-
return _process_time_response(sys, T, y, [], transpose=transpose,
1432-
return_x=return_x, squeeze=squeeze)
1431+
return _process_time_response(
1432+
sys, T, y, np.array((0, 0, np.asarray(T).size)),
1433+
transpose=transpose, return_x=return_x, squeeze=squeeze)
14331434

14341435
# create X0 if not given, test if X0 has correct shape
14351436
X0 = _check_convert_array(X0, [(nstates,), (nstates, 1)],

control/tests/matlab2_test.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,25 +121,25 @@ def test_step(self, SISO_mats, MIMO_mats, mplcleanup):
121121
#print("gain:", dcgain(sys))
122122

123123
subplot2grid(plot_shape, (0, 0))
124-
t, y = step(sys)
124+
y, t = step(sys)
125125
plot(t, y)
126126

127127
subplot2grid(plot_shape, (0, 1))
128128
T = linspace(0, 2, 100)
129129
X0 = array([1, 1])
130-
t, y = step(sys, T, X0)
130+
y, t = step(sys, T, X0)
131131
plot(t, y)
132132

133133
# Test output of state vector
134-
t, y, x = step(sys, return_x=True)
134+
y, t, x = step(sys, return_x=True)
135135

136136
#Test MIMO system
137137
A, B, C, D = MIMO_mats
138138
sys = ss(A, B, C, D)
139139

140140
subplot2grid(plot_shape, (0, 2))
141-
t, y = step(sys)
142-
plot(t, y)
141+
y, t = step(sys)
142+
plot(t, y[:, 0, 0])
143143

144144
def test_impulse(self, SISO_mats, mplcleanup):
145145
A, B, C, D = SISO_mats
@@ -168,8 +168,8 @@ def test_impulse_mimo(self, MIMO_mats, mplcleanup):
168168
#Test MIMO system
169169
A, B, C, D = MIMO_mats
170170
sys = ss(A, B, C, D)
171-
t, y = impulse(sys)
172-
plot(t, y, label='MIMO System')
171+
y, t = impulse(sys)
172+
plot(t, y[:, :, 0], label='MIMO System')
173173

174174
legend(loc='best')
175175
#show()

control/tests/timeresp_test.py

Lines changed: 109 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ def test_impulse_response_mimo(self, mimo_ss2):
347347
yref_notrim = np.zeros((2, len(t)))
348348
yref_notrim[:1, :] = yref
349349
_t, yy = impulse_response(sys, T=t, input=0)
350-
np.testing.assert_array_almost_equal(yy, yref_notrim, decimal=4)
350+
np.testing.assert_array_almost_equal(yy[:,0,:], yref_notrim, decimal=4)
351351

352352
@pytest.mark.skipif(StrictVersion(sp.__version__) < "1.3",
353353
reason="requires SciPy 1.3 or greater")
@@ -639,9 +639,10 @@ def test_time_vector(self, tsystem, fun, squeeze, matarrayout):
639639
if hasattr(tsystem, 't'):
640640
# tout should always match t, which has shape (n, )
641641
np.testing.assert_allclose(tout, tsystem.t)
642-
if squeeze is False or sys.outputs > 1:
642+
643+
if squeeze is False or not sys.issiso():
643644
assert yout.shape[0] == sys.outputs
644-
assert yout.shape[1] == tout.shape[0]
645+
assert yout.shape[-1] == tout.shape[0]
645646
else:
646647
assert yout.shape == tout.shape
647648

@@ -725,21 +726,22 @@ def test_time_series_data_convention_2D(self, siso_ss1):
725726

726727
@pytest.mark.usefixtures("editsdefaults")
727728
@pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.ss2io])
728-
@pytest.mark.parametrize("nstate, nout, ninp, squeeze, shape", [
729-
[1, 1, 1, None, (8,)],
730-
[2, 1, 1, True, (8,)],
731-
[3, 1, 1, False, (1, 8)],
732-
[3, 2, 1, None, (2, 8)],
733-
[4, 2, 1, True, (2, 8)],
734-
[5, 2, 1, False, (2, 8)],
735-
[3, 1, 2, None, (1, 8)],
736-
[4, 1, 2, True, (8,)],
737-
[5, 1, 2, False, (1, 8)],
738-
[4, 2, 2, None, (2, 8)],
739-
[5, 2, 2, True, (2, 8)],
740-
[6, 2, 2, False, (2, 8)],
729+
@pytest.mark.parametrize("nstate, nout, ninp, squeeze, shape1, shape2", [
730+
# state out in squeeze in/out out-only
731+
[1, 1, 1, None, (8,), (8,)],
732+
[2, 1, 1, True, (8,), (8,)],
733+
[3, 1, 1, False, (1, 1, 8), (1, 8)],
734+
[3, 2, 1, None, (2, 1, 8), (2, 8)],
735+
[4, 2, 1, True, (2, 8), (2, 8)],
736+
[5, 2, 1, False, (2, 1, 8), (2, 8)],
737+
[3, 1, 2, None, (1, 2, 8), (1, 8)],
738+
[4, 1, 2, True, (2, 8), (8,)],
739+
[5, 1, 2, False, (1, 2, 8), (1, 8)],
740+
[4, 2, 2, None, (2, 2, 8), (2, 8)],
741+
[5, 2, 2, True, (2, 2, 8), (2, 8)],
742+
[6, 2, 2, False, (2, 2, 8), (2, 8)],
741743
])
742-
def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape):
744+
def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape1, shape2):
743745
# Figure out if we have SciPy 1+
744746
scipy0 = StrictVersion(sp.__version__) < '1.0'
745747

@@ -750,27 +752,56 @@ def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape):
750752
else:
751753
sys = fcn(ct.rss(nstate, nout, ninp, strictly_proper=True))
752754

753-
# Keep track of expect users warnings
754-
warntype = UserWarning if sys.inputs > 1 else None
755-
756755
# Generate the time and input vectors
757756
tvec = np.linspace(0, 1, 8)
758757
uvec = np.dot(
759758
np.ones((sys.inputs, 1)),
760759
np.reshape(np.sin(tvec), (1, 8)))
761760

761+
#
762762
# Pass squeeze argument and make sure the shape is correct
763-
with pytest.warns(warntype, match="Converting MIMO system"):
764-
_, yvec = ct.impulse_response(sys, tvec, squeeze=squeeze)
765-
assert yvec.shape == shape
763+
#
764+
# For responses that are indexed by the input, check against shape1
765+
# For responses that have no/fixed input, check against shape2
766+
#
766767

767-
_, yvec = ct.initial_response(sys, tvec, 1, squeeze=squeeze)
768-
assert yvec.shape == shape
768+
# Impulse response
769+
if isinstance(sys, StateSpace):
770+
# Check the states as well
771+
_, yvec, xvec = ct.impulse_response(
772+
sys, tvec, squeeze=squeeze, return_x=True)
773+
if sys.issiso():
774+
assert xvec.shape == (sys.states, 8)
775+
else:
776+
assert xvec.shape == (sys.states, sys.inputs, 8)
777+
else:
778+
_, yvec = ct.impulse_response(sys, tvec, squeeze=squeeze)
779+
assert yvec.shape == shape1
769780

770-
with pytest.warns(warntype, match="Converting MIMO system"):
781+
# Step response
782+
if isinstance(sys, StateSpace):
783+
# Check the states as well
784+
_, yvec, xvec = ct.step_response(
785+
sys, tvec, squeeze=squeeze, return_x=True)
786+
if sys.issiso():
787+
assert xvec.shape == (sys.states, 8)
788+
else:
789+
assert xvec.shape == (sys.states, sys.inputs, 8)
790+
else:
771791
_, yvec = ct.step_response(sys, tvec, squeeze=squeeze)
772-
assert yvec.shape == shape
792+
assert yvec.shape == shape1
793+
794+
# Initial response (only indexed by output)
795+
if isinstance(sys, StateSpace):
796+
# Check the states as well
797+
_, yvec, xvec = ct.initial_response(
798+
sys, tvec, 1, squeeze=squeeze, return_x=True)
799+
assert xvec.shape == (sys.states, 8)
800+
else:
801+
_, yvec = ct.initial_response(sys, tvec, 1, squeeze=squeeze)
802+
assert yvec.shape == shape2
773803

804+
# Forced response (only indexed by output)
774805
if isinstance(sys, StateSpace):
775806
# Check the states as well
776807
_, yvec, xvec = ct.forced_response(
@@ -779,52 +810,54 @@ def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape):
779810
else:
780811
# Just check the input/output response
781812
_, yvec = ct.forced_response(sys, tvec, uvec, 0, squeeze=squeeze)
782-
assert yvec.shape == shape
813+
assert yvec.shape == shape2
783814

784815
# Test cases where we choose a subset of inputs and outputs
785816
_, yvec = ct.step_response(
786817
sys, tvec, input=ninp-1, output=nout-1, squeeze=squeeze)
787-
# Possible code if we implemenet a squeeze='siso' option
788818
if squeeze is False:
789819
# Shape should be unsqueezed
790-
assert yvec.shape == (1, 8)
820+
assert yvec.shape == (1, 1, 8)
791821
else:
792822
# Shape should be squeezed
793823
assert yvec.shape == (8, )
794824

795-
# For InputOutputSystems, also test input_output_response
825+
# For InputOutputSystems, also test input/output response
796826
if isinstance(sys, ct.InputOutputSystem) and not scipy0:
797827
_, yvec = ct.input_output_response(sys, tvec, uvec, squeeze=squeeze)
798-
assert yvec.shape == shape
828+
assert yvec.shape == shape2
799829

800830
#
801831
# Changing config.default to False should return 3D frequency response
802832
#
803833
ct.config.set_defaults('control', squeeze_time_response=False)
804834

805-
with pytest.warns(warntype, match="Converting MIMO system"):
806-
_, yvec = ct.impulse_response(sys, tvec)
807-
assert yvec.shape == (sys.outputs, 8)
835+
_, yvec = ct.impulse_response(sys, tvec)
836+
if squeeze is not True or sys.inputs > 1 or sys.outputs > 1:
837+
assert yvec.shape == (sys.outputs, sys.inputs, 8)
808838

809-
_, yvec = ct.initial_response(sys, tvec, 1)
810-
assert yvec.shape == (sys.outputs, 8)
839+
_, yvec = ct.step_response(sys, tvec)
840+
if squeeze is not True or sys.inputs > 1 or sys.outputs > 1:
841+
assert yvec.shape == (sys.outputs, sys.inputs, 8)
811842

812-
with pytest.warns(warntype, match="Converting MIMO system"):
813-
_, yvec = ct.step_response(sys, tvec)
814-
assert yvec.shape == (sys.outputs, 8)
843+
_, yvec = ct.initial_response(sys, tvec, 1)
844+
if squeeze is not True or sys.outputs > 1:
845+
assert yvec.shape == (sys.outputs, 8)
815846

816847
if isinstance(sys, ct.StateSpace):
817848
_, yvec, xvec = ct.forced_response(
818849
sys, tvec, uvec, 0, return_x=True)
819850
assert xvec.shape == (sys.states, 8)
820851
else:
821852
_, yvec = ct.forced_response(sys, tvec, uvec, 0)
822-
assert yvec.shape == (sys.outputs, 8)
853+
if squeeze is not True or sys.outputs > 1:
854+
assert yvec.shape == (sys.outputs, 8)
823855

824856
# For InputOutputSystems, also test input_output_response
825857
if isinstance(sys, ct.InputOutputSystem) and not scipy0:
826858
_, yvec = ct.input_output_response(sys, tvec, uvec)
827-
assert yvec.shape == (sys.noutputs, 8)
859+
if squeeze is not True or sys.outputs > 1:
860+
assert yvec.shape == (sys.outputs, 8)
828861

829862
@pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.ss2io])
830863
def test_squeeze_exception(self, fcn):
@@ -861,3 +894,37 @@ def test_squeeze_0_8_4(self, nstate, nout, ninp, squeeze, shape):
861894

862895
_, yvec = ct.initial_response(sys, tvec, 1, squeeze=squeeze)
863896
assert yvec.shape == shape
897+
898+
@pytest.mark.parametrize(
899+
"nstate, nout, ninp, squeeze, ysh_in, ysh_no, xsh_in", [
900+
[4, 1, 1, None, (8,), (8,), (8, 4)],
901+
[4, 1, 1, True, (8,), (8,), (8, 4)],
902+
[4, 1, 1, False, (8, 1, 1), (8, 1), (8, 4)],
903+
[4, 2, 1, None, (8, 2, 1), (8, 2), (8, 4, 1)],
904+
[4, 2, 1, True, (8, 2), (8, 2), (8, 4, 1)],
905+
[4, 2, 1, False, (8, 2, 1), (8, 2), (8, 4, 1)],
906+
[4, 1, 2, None, (8, 1, 2), (8, 1), (8, 4, 2)],
907+
[4, 1, 2, True, (8, 2), (8,), (8, 4, 2)],
908+
[4, 1, 2, False, (8, 1, 2), (8, 1), (8, 4, 2)],
909+
[4, 2, 2, None, (8, 2, 2), (8, 2), (8, 4, 2)],
910+
[4, 2, 2, True, (8, 2, 2), (8, 2), (8, 4, 2)],
911+
[4, 2, 2, False, (8, 2, 2), (8, 2), (8, 4, 2)],
912+
])
913+
def test_response_transpose(
914+
self, nstate, nout, ninp, squeeze, ysh_in, ysh_no, xsh_in):
915+
sys = ct.rss(nstate, nout, ninp)
916+
T = np.linspace(0, 1, 8)
917+
918+
# Step response - input indexed
919+
t, y, x = ct.step_response(
920+
sys, T, transpose=True, return_x=True, squeeze=squeeze)
921+
assert t.shape == (T.size, )
922+
assert y.shape == ysh_in
923+
assert x.shape == xsh_in
924+
925+
# Initial response - no input indexing
926+
t, y, x = ct.initial_response(
927+
sys, T, 1, transpose=True, return_x=True, squeeze=squeeze)
928+
assert t.shape == (T.size, )
929+
assert y.shape == ysh_no
930+
assert x.shape == (T.size, sys.states)

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