Skip to content

Commit f0d764f

Browse files
committed
test forced_response omitting parameters as documented
1 parent 52a3fd4 commit f0d764f

2 files changed

Lines changed: 68 additions & 7 deletions

File tree

control/tests/timeresp_test.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ def siso_ss2_dtnone(self, siso_ss2):
6666
ss2 = siso_ss2.sys
6767
T = TSys(StateSpace(ss2.A, ss2.B, ss2.C, 0, None))
6868
T.t = np.arange(0, 10, 1.)
69+
T.ystep = np.array([ 0., 86., -72., 230., -360., 806.,
70+
-1512., 3110., -6120., 12326.])
6971
return T
7072

7173
@pytest.fixture
@@ -128,12 +130,18 @@ def siso_dtf0(self):
128130
def siso_dtf1(self):
129131
T = TSys(TransferFunction([1], [1, 1, 0.25], True))
130132
T.t = np.arange(0, 5, 1)
133+
T.ystep = np.array([0. , 0. , 1. , 0. , 0.75])
131134
return T
132135

133136
@pytest.fixture
134137
def siso_dtf2(self):
135138
T = TSys(TransferFunction([1], [1, 1, 0.25], 0.2))
136139
T.t = np.arange(0, 5, 0.2)
140+
T.ystep =np.array([0. , 0. , 1. , 0. , 0.75 , 0.25 ,
141+
0.5625, 0.375 , 0.4844, 0.4219, 0.457 , 0.4375,
142+
0.4482, 0.4424, 0.4456, 0.4438, 0.4448, 0.4443,
143+
0.4445, 0.4444, 0.4445, 0.4444, 0.4445, 0.4444,
144+
0.4444])
137145
return T
138146

139147
@pytest.fixture
@@ -719,6 +727,58 @@ def test_forced_response_legacy(self):
719727
t, y = ct.forced_response(sys, T, U)
720728
t, y, x = ct.forced_response(sys, T, U, return_x=True)
721729

730+
@pytest.mark.parametrize(
731+
"tsystem, fr_kwargs, refattr",
732+
[pytest.param("siso_ss1",
733+
{'X0': [0.5, 1], 'T': np.linspace(0, 1, 10)},
734+
'yinitial',
735+
id="ctime no T"),
736+
pytest.param("siso_dtf1",
737+
{'U': np.ones(5,)}, 'ystep',
738+
id="dt=True, no U"),
739+
pytest.param("siso_dtf2",
740+
{'U': np.ones(25,)}, 'ystep',
741+
id="dt=0.2, no U"),
742+
pytest.param("siso_ss2_dtnone",
743+
{'U': np.ones(10,)}, 'ystep',
744+
id="dt=None, no U")],
745+
indirect=["tsystem"])
746+
def test_forced_response_T_U(self, tsystem, fr_kwargs, refattr):
747+
"""Test documented forced_response behavior for parameters T and U."""
748+
t, y = forced_response(tsystem.sys, **fr_kwargs)
749+
np.testing.assert_allclose(t, tsystem.t)
750+
np.testing.assert_allclose(y, getattr(tsystem, refattr), rtol=1e-3)
751+
752+
def test_forced_response_invalid(self, siso_ss1, siso_dss2):
753+
"""Test invalid parameters."""
754+
with pytest.raises(TypeError,
755+
match="StateSpace.*or.*TransferFunction"):
756+
forced_response("not a system")
757+
758+
# ctime
759+
with pytest.raises(ValueError, match="T.*is mandatory for continuous"):
760+
forced_response(siso_ss1.sys)
761+
with pytest.raises(ValueError, match="time values must be equally "
762+
"spaced"):
763+
forced_response(siso_ss1.sys, [0, 0.1, 0.12, 0.4])
764+
765+
# dtime with sys.dt > 0
766+
with pytest.raises(ValueError, match="can't both be zero"):
767+
forced_response(siso_dss2.sys)
768+
with pytest.raises(ValueError, match="must have same elements"):
769+
forced_response(siso_dss2.sys,
770+
T=siso_dss2.t, U=np.random.randn(1, 12))
771+
with pytest.raises(ValueError, match="must have same elements"):
772+
forced_response(siso_dss2.sys,
773+
T=siso_dss2.t, U=np.random.randn(12))
774+
with pytest.raises(ValueError, match="must match sampling time"):
775+
forced_response(siso_dss2.sys, T=siso_dss2.t*0.9)
776+
with pytest.raises(ValueError, match="must be multiples of "
777+
"sampling time"):
778+
forced_response(siso_dss2.sys, T=siso_dss2.t*1.1)
779+
# but this is ok
780+
forced_response(siso_dss2.sys, T=siso_dss2.t*2)
781+
722782

723783
@pytest.mark.parametrize("u, x0, xtrue",
724784
[(np.zeros((10,)),

control/timeresp.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,8 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False,
327327
# Set and/or check time vector in discrete time case
328328
if isdtime(sys):
329329
if T is None:
330-
if U is None:
331-
raise ValueError('Parameters ``T`` and ``U`` can\'t both be'
330+
if U is None or (U.ndim == 0 and U == 0.):
331+
raise ValueError('Parameters ``T`` and ``U`` can\'t both be '
332332
'zero for discrete-time simulation')
333333
# Set T to equally spaced samples with same length as U
334334
if U.ndim == 1:
@@ -339,11 +339,12 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False,
339339
T = np.array(range(n_steps)) * dt
340340
else:
341341
# Make sure the input vector and time vector have same length
342-
# TODO: allow interpolation of the input vector
343342
if (U.ndim == 1 and U.shape[0] != T.shape[0]) or \
344343
(U.ndim > 1 and U.shape[1] != T.shape[0]):
345-
ValueError('Pamameter ``T`` must have same elements as'
346-
' the number of columns in input array ``U``')
344+
raise ValueError('Pamameter ``T`` must have same elements as'
345+
' the number of columns in input array ``U``')
346+
if U.ndim == 0:
347+
U = np.full((n_inputs, T.shape[0]), U)
347348
else:
348349
if T is None:
349350
raise ValueError('Parameter ``T`` is mandatory for continuous '
@@ -370,7 +371,7 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False,
370371
yout = np.zeros((n_outputs, n_steps))
371372

372373
# Separate out the discrete and continuous time cases
373-
if isctime(sys):
374+
if isctime(sys, strict=True):
374375
# Solve the differential equation, copied from scipy.signal.ltisys.
375376
dot = np.dot # Faster and shorter code
376377

@@ -421,7 +422,7 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False,
421422

422423
else:
423424
# Discrete type system => use SciPy signal processing toolbox
424-
if sys.dt is not True:
425+
if sys.dt is not True and sys.dt is not None:
425426
# Make sure that the time increment is a multiple of sampling time
426427

427428
# First make sure that time increment is bigger than sampling time

0 commit comments

Comments
 (0)