@@ -45,6 +45,14 @@ def setUp(self):
4545 "0. 9. " )
4646 self .mimo_ss1 = StateSpace (A , B , C , D )
4747
48+ # Create discrete time systems
49+ self .siso_dtf1 = TransferFunction ([1 ], [1 , 1 , 0.25 ], True )
50+ self .siso_dtf2 = TransferFunction ([1 ], [1 , 1 , 0.25 ], 0.2 )
51+ self .siso_dss1 = tf2ss (self .siso_dtf1 )
52+ self .siso_dss2 = tf2ss (self .siso_dtf2 )
53+ self .mimo_dss1 = StateSpace (A , B , C , D , True )
54+ self .mimo_dss2 = c2d (self .mimo_ss1 , 0.2 )
55+
4856 def test_step_response (self ):
4957 # Test SISO system
5058 sys = self .siso_ss1
@@ -320,6 +328,123 @@ def test_step_robustness(self):
320328 t2 , y2 = step_response (sys2 , input = 0 )
321329 np .testing .assert_array_almost_equal (y1 , y2 )
322330
331+ def test_time_vector (self ):
332+ "Unit test: https://github.com/python-control/python-control/issues/239"
333+ # Discrete time simulations with specified time vectors
334+ Tin1 = np .arange (0 , 5 , 1 ) # matches dtf1, dss1; multiple of 0.2
335+ Tin2 = np .arange (0 , 5 , 0.2 ) # matches dtf2, dss2
336+ Tin3 = np .arange (0 , 5 , 0.5 ) # incompatible with 0.2
337+
338+ # Initial conditions to use for the different systems
339+ siso_x0 = [1 , 2 ]
340+ mimo_x0 = [1 , 2 , 3 , 4 ]
341+
342+ #
343+ # Easy cases: make sure that output sample time matches input
344+ #
345+ # No timebase in system => output should match input
346+ #
347+ # Initial response
348+ tout , yout = initial_response (self .siso_dtf1 , Tin2 , siso_x0 )
349+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
350+ np .testing .assert_array_equal (tout , Tin2 )
351+
352+ # Impulse response
353+ tout , yout = impulse_response (self .siso_dtf1 , Tin2 )
354+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
355+ np .testing .assert_array_equal (tout , Tin2 )
356+
357+ # Step response
358+ tout , yout = step_response (self .siso_dtf1 , Tin2 )
359+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
360+ np .testing .assert_array_equal (tout , Tin2 )
361+
362+ # Forced response with specified time vector
363+ tout , yout , xout = forced_response (self .siso_dtf1 , Tin2 , np .sin (Tin2 ))
364+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
365+ np .testing .assert_array_equal (tout , Tin2 )
366+
367+ # Forced response with no time vector, no sample time (should use 1)
368+ tout , yout , xout = forced_response (self .siso_dtf1 , None , np .sin (Tin1 ))
369+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
370+ np .testing .assert_array_equal (tout , Tin1 )
371+
372+ # MIMO forced response
373+ tout , yout , xout = forced_response (self .mimo_dss1 , Tin1 ,
374+ (np .sin (Tin1 ), np .cos (Tin1 )),
375+ mimo_x0 )
376+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
377+ self .assertEqual (np .shape (tout ), np .shape (yout [1 ,:]))
378+ np .testing .assert_array_equal (tout , Tin1 )
379+
380+ # Matching timebase in system => output should match input
381+ #
382+ # Initial response
383+ tout , yout = initial_response (self .siso_dtf2 , Tin2 , siso_x0 )
384+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
385+ np .testing .assert_array_equal (tout , Tin2 )
386+
387+ # Impulse response
388+ tout , yout = impulse_response (self .siso_dtf2 , Tin2 )
389+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
390+ np .testing .assert_array_equal (tout , Tin2 )
391+
392+ # Step response
393+ tout , yout = step_response (self .siso_dtf2 , Tin2 )
394+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
395+ np .testing .assert_array_equal (tout , Tin2 )
396+
397+ # Forced response
398+ tout , yout , xout = forced_response (self .siso_dtf2 , Tin2 , np .sin (Tin2 ))
399+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
400+ np .testing .assert_array_equal (tout , Tin2 )
401+
402+ # Forced response with no time vector, use sample time
403+ tout , yout , xout = forced_response (self .siso_dtf2 , None , np .sin (Tin2 ))
404+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
405+ np .testing .assert_array_equal (tout , Tin2 )
406+
407+ # Compatible timebase in system => output should match input
408+ #
409+ # Initial response
410+ tout , yout = initial_response (self .siso_dtf2 , Tin1 , siso_x0 )
411+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
412+ np .testing .assert_array_equal (tout , Tin1 )
413+
414+ # Impulse response
415+ tout , yout = impulse_response (self .siso_dtf2 , Tin1 )
416+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
417+ np .testing .assert_array_equal (tout , Tin1 )
418+
419+ # Step response
420+ tout , yout = step_response (self .siso_dtf2 , Tin1 )
421+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
422+ np .testing .assert_array_equal (tout , Tin1 )
423+
424+ # Forced response
425+ tout , yout , xout = forced_response (self .siso_dtf2 , Tin1 , np .sin (Tin1 ))
426+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
427+ np .testing .assert_array_equal (tout , Tin1 )
428+
429+ #
430+ # Interpolation of the input (to match scipy.signal.dlsim)
431+ #
432+ # Initial response
433+ tout , yout , xout = forced_response (self .siso_dtf2 , Tin1 ,
434+ np .sin (Tin1 ), interpolate = True )
435+ self .assertEqual (np .shape (tout ), np .shape (yout [0 ,:]))
436+ self .assertTrue (np .allclose (tout [1 :] - tout [:- 1 ], self .siso_dtf2 .dt ))
437+
438+ #
439+ # Incompatible cases: make sure an error is thrown
440+ #
441+ # System timebase and given time vector are incompatible
442+ #
443+ # Initial response
444+ with self .assertRaises (Exception ) as context :
445+ tout , yout = initial_response (self .siso_dtf2 , Tin3 , siso_x0 )
446+ self .assertTrue (isinstance (context .exception , ValueError ))
447+
323448def suite ():
324449 return unittest .TestLoader ().loadTestsFromTestCase (TestTimeresp )
325450
0 commit comments