Skip to content

Commit 7c69c80

Browse files
committed
test forced_response omitting parameters as documented
1 parent 66b381f commit 7c69c80

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
@@ -74,6 +74,8 @@ def siso_ss2_dtnone(self, siso_ss2):
7474
ss2 = siso_ss2.sys
7575
T = TSys(StateSpace(ss2.A, ss2.B, ss2.C, 0, None))
7676
T.t = np.arange(0, 10, 1.)
77+
T.ystep = np.array([ 0., 86., -72., 230., -360., 806.,
78+
-1512., 3110., -6120., 12326.])
7779
return T
7880

7981
@pytest.fixture
@@ -136,12 +138,18 @@ def siso_dtf0(self):
136138
def siso_dtf1(self):
137139
T = TSys(TransferFunction([1], [1, 1, 0.25], True))
138140
T.t = np.arange(0, 5, 1)
141+
T.ystep = np.array([0. , 0. , 1. , 0. , 0.75])
139142
return T
140143

141144
@pytest.fixture
142145
def siso_dtf2(self):
143146
T = TSys(TransferFunction([1], [1, 1, 0.25], 0.2))
144147
T.t = np.arange(0, 5, 0.2)
148+
T.ystep =np.array([0. , 0. , 1. , 0. , 0.75 , 0.25 ,
149+
0.5625, 0.375 , 0.4844, 0.4219, 0.457 , 0.4375,
150+
0.4482, 0.4424, 0.4456, 0.4438, 0.4448, 0.4443,
151+
0.4445, 0.4444, 0.4445, 0.4444, 0.4445, 0.4444,
152+
0.4444])
145153
return T
146154

147155
@pytest.fixture
@@ -707,6 +715,58 @@ def test_forced_response_legacy(self):
707715
t, y = ct.forced_response(sys, T, U)
708716
t, y, x = ct.forced_response(sys, T, U, return_x=True)
709717

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

711771
@pytest.mark.parametrize("u, x0, xtrue",
712772
[(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)