@@ -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 ,)),
0 commit comments