@@ -45,6 +45,14 @@ def setUp(self):
45
45
"0. 9. " )
46
46
self .mimo_ss1 = StateSpace (A , B , C , D )
47
47
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
+
48
56
def test_step_response (self ):
49
57
# Test SISO system
50
58
sys = self .siso_ss1
@@ -320,6 +328,123 @@ def test_step_robustness(self):
320
328
t2 , y2 = step_response (sys2 , input = 0 )
321
329
np .testing .assert_array_almost_equal (y1 , y2 )
322
330
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
+
323
448
def suite ():
324
449
return unittest .TestLoader ().loadTestsFromTestCase (TestTimeresp )
325
450
0 commit comments