Skip to content

Commit e838b4b

Browse files
committed
initial implementation of MIMO step, impulse response
1 parent 0a08ff2 commit e838b4b

File tree

2 files changed

+219
-80
lines changed

2 files changed

+219
-80
lines changed

control/tests/timeresp_test.py

Lines changed: 74 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -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() and squeeze is not False:
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() and squeeze is not False:
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):

0 commit comments

Comments
 (0)