77import sys as pysys
88import numpy as np
99from control .statesp import StateSpace , _convertToStateSpace , rss
10- from control .xferfcn import TransferFunction , _convert_to_transfer_function , ss2tf
10+ from control .xferfcn import TransferFunction , _convert_to_transfer_function , \
11+ ss2tf
1112from control .lti import evalfr
1213from control .exception import slycot_check
14+ from control .dtime import sample_system
1315# from control.lti import isdtime
1416
1517
@@ -24,23 +26,53 @@ class TestXferFcn(unittest.TestCase):
2426 def test_constructor_bad_input_type (self ):
2527 """Give the constructor invalid input types."""
2628
27- self .assertRaises (TypeError , TransferFunction , [[0. , 1. ], [2. , 3. ]], [[5. , 2. ], [3. , 0. ]])
29+ # MIMO requires lists of lists of vectors (not lists of vectors)
30+ self .assertRaises (
31+ TypeError ,
32+ TransferFunction , [[0. , 1. ], [2. , 3. ]], [[5. , 2. ], [3. , 0. ]])
33+ TransferFunction ([[ [0. , 1. ], [2. , 3. ] ]], [[ [5. , 2. ], [3. , 0. ] ]])
34+
35+ # Single argument of the wrong type
36+ self .assertRaises (TypeError , TransferFunction , [1 ])
37+
38+ # Too many arguments
39+ self .assertRaises (ValueError , TransferFunction , 1 , 2 , 3 , 4 )
40+
41+ # Different numbers of elements in numerator rows
42+ self .assertRaises (
43+ ValueError ,
44+ TransferFunction , [ [[0 , 1 ], [2 , 3 ]],
45+ [[4 , 5 ]] ],
46+ [ [[6 , 7 ], [4 , 5 ]],
47+ [[2 , 3 ], [0 , 1 ]] ])
48+ self .assertRaises (
49+ ValueError ,
50+ TransferFunction , [ [[0 , 1 ], [2 , 3 ]],
51+ [[4 , 5 ], [6 , 7 ]] ],
52+ [ [[6 , 7 ], [4 , 5 ]],
53+ [[2 , 3 ]] ])
54+ TransferFunction ( # This version is OK
55+ [ [[0 , 1 ], [2 , 3 ]], [[4 , 5 ], [6 , 7 ]] ],
56+ [ [[6 , 7 ], [4 , 5 ]], [[2 , 3 ], [0 , 1 ]] ])
2857
2958 def test_constructor_inconsistent_dimension (self ):
30- """Give the constructor a numerator and denominator of different
31- sizes."""
59+ """Give constructor numerators, denominators of different sizes."""
3260
33- self .assertRaises (ValueError , TransferFunction , [[[1. ]]], [[[1. ], [2. , 3. ]]])
34- self .assertRaises (ValueError , TransferFunction , [[[1. ]]], [[[1. ]], [[2. , 3. ]]])
35- self .assertRaises (ValueError , TransferFunction , [[[1. ]]],
36- [[[1. ], [1. , 2. ]], [[5. , 2. ], [2. , 3. ]]])
61+ self .assertRaises (ValueError , TransferFunction ,
62+ [[[1. ]]], [[[1. ], [2. , 3. ]]])
63+ self .assertRaises (ValueError , TransferFunction ,
64+ [[[1. ]]], [[[1. ]], [[2. , 3. ]]])
65+ self .assertRaises (ValueError , TransferFunction ,
66+ [[[1. ]]], [[[1. ], [1. , 2. ]], [[5. , 2. ], [2. , 3. ]]])
3767
3868 def test_constructor_inconsistent_columns (self ):
3969 """Give the constructor inputs that do not have the same number of
4070 columns in each row."""
4171
42- self .assertRaises (ValueError , TransferFunction , 1. , [[[1. ]], [[2. ], [3. ]]])
43- self .assertRaises (ValueError , TransferFunction , [[[1. ]], [[2. ], [3. ]]], 1. )
72+ self .assertRaises (ValueError , TransferFunction ,
73+ 1. , [[[1. ]], [[2. ], [3. ]]])
74+ self .assertRaises (ValueError , TransferFunction ,
75+ [[[1. ]], [[2. ], [3. ]]], 1. )
4476
4577 def test_constructor_zero_denominator (self ):
4678 """Give the constructor a transfer function with a zero denominator."""
@@ -54,7 +86,8 @@ def test_add_inconsistent_dimension(self):
5486 """Add two transfer function matrices of different sizes."""
5587
5688 sys1 = TransferFunction ([[[1. , 2. ]]], [[[4. , 5. ]]])
57- sys2 = TransferFunction ([[[4. , 3. ]], [[1. , 2. ]]], [[[1. , 6. ]], [[2. , 4. ]]])
89+ sys2 = TransferFunction ([[[4. , 3. ]], [[1. , 2. ]]],
90+ [[[1. , 6. ]], [[2. , 4. ]]])
5891 self .assertRaises (ValueError , sys1 .__add__ , sys2 )
5992 self .assertRaises (ValueError , sys1 .__sub__ , sys2 )
6093 self .assertRaises (ValueError , sys1 .__radd__ , sys2 )
@@ -65,7 +98,8 @@ def test_mul_inconsistent_dimension(self):
6598
6699 sys1 = TransferFunction ([[[1. , 2. ], [4. , 5. ]], [[2. , 5. ], [4. , 3. ]]],
67100 [[[6. , 2. ], [4. , 1. ]], [[6. , 7. ], [2. , 4. ]]])
68- sys2 = TransferFunction ([[[1. ]], [[2. ]], [[3. ]]], [[[4. ]], [[5. ]], [[6. ]]])
101+ sys2 = TransferFunction ([[[1. ]], [[2. ]], [[3. ]]],
102+ [[[4. ]], [[5. ]], [[6. ]]])
69103 self .assertRaises (ValueError , sys1 .__mul__ , sys2 )
70104 self .assertRaises (ValueError , sys2 .__mul__ , sys1 )
71105 self .assertRaises (ValueError , sys1 .__rmul__ , sys2 )
@@ -313,25 +347,67 @@ def test_divide_siso(self):
313347 np .testing .assert_array_equal (sys4 .num , sys3 .den )
314348 np .testing .assert_array_equal (sys4 .den , sys3 .num )
315349
350+ def test_div (self ):
351+ # Make sure that sampling times work correctly
352+ sys1 = TransferFunction ([1. , 3. , 5 ], [1. , 6. , 2. , - 1 ])
353+ sys2 = TransferFunction ([[[- 1. , 3. ]]], [[[1. , 0. , - 1. ]]], True )
354+ sys3 = sys1 / sys2
355+ self .assertEqual (sys3 .dt , True )
356+
357+ sys2 = TransferFunction ([[[- 1. , 3. ]]], [[[1. , 0. , - 1. ]]], 0.5 )
358+ sys3 = sys1 / sys2
359+ self .assertEqual (sys3 .dt , 0.5 )
360+
361+ sys1 = TransferFunction ([1. , 3. , 5 ], [1. , 6. , 2. , - 1 ], 0.1 )
362+ self .assertRaises (ValueError , TransferFunction .__truediv__ , sys1 , sys2 )
363+
364+ sys1 = sample_system (rss (4 , 1 , 1 ), 0.5 )
365+ sys3 = TransferFunction .__rtruediv__ (sys2 , sys1 )
366+ self .assertEqual (sys3 .dt , 0.5 )
367+
368+ def test_pow (self ):
369+ sys1 = TransferFunction ([1. , 3. , 5 ], [1. , 6. , 2. , - 1 ])
370+ self .assertRaises (ValueError , TransferFunction .__pow__ , sys1 , 0.5 )
371+
372+ def test_slice (self ):
373+ sys = TransferFunction (
374+ [ [ [1 ], [2 ], [3 ]], [ [3 ], [4 ], [5 ]] ],
375+ [ [[1 , 2 ], [1 , 3 ], [1 , 4 ]], [[1 , 4 ], [1 , 5 ], [1 , 6 ]] ])
376+ sys1 = sys [1 :, 1 :]
377+ self .assertEqual ((sys1 .inputs , sys1 .outputs ), (2 , 1 ))
378+
379+ sys2 = sys [:2 , :2 ]
380+ self .assertEqual ((sys2 .inputs , sys2 .outputs ), (2 , 2 ))
381+
382+ sys = TransferFunction (
383+ [ [ [1 ], [2 ], [3 ]], [ [3 ], [4 ], [5 ]] ],
384+ [ [[1 , 2 ], [1 , 3 ], [1 , 4 ]], [[1 , 4 ], [1 , 5 ], [1 , 6 ]] ], 0.5 )
385+ sys1 = sys [1 :, 1 :]
386+ self .assertEqual ((sys1 .inputs , sys1 .outputs ), (2 , 1 ))
387+ self .assertEqual (sys1 .dt , 0.5 )
388+
316389 def test_evalfr_siso (self ):
317390 """Evaluate the frequency response of a SISO system at one frequency."""
318391
319392 sys = TransferFunction ([1. , 3. , 5 ], [1. , 6. , 2. , - 1 ])
320393
321394 np .testing .assert_array_almost_equal (evalfr (sys , 1j ),
322395 np .array ([[- 0.5 - 0.5j ]]))
323- np .testing .assert_array_almost_equal (evalfr (sys , 32j ),
324- np .array ([[0.00281959302585077 - 0.030628473607392j ]]))
396+ np .testing .assert_array_almost_equal (
397+ evalfr (sys , 32j ),
398+ np .array ([[0.00281959302585077 - 0.030628473607392j ]]))
325399
326400 # Test call version as well
327401 np .testing .assert_almost_equal (sys (1.j ), - 0.5 - 0.5j )
328- np .testing .assert_almost_equal (sys (32.j ), 0.00281959302585077 - 0.030628473607392j )
402+ np .testing .assert_almost_equal (
403+ sys (32.j ), 0.00281959302585077 - 0.030628473607392j )
329404
330405 # Test internal version (with real argument)
331- np .testing .assert_array_almost_equal (sys ._evalfr (1. ),
332- np .array ([[- 0.5 - 0.5j ]]))
333- np .testing .assert_array_almost_equal (sys ._evalfr (32. ),
334- np .array ([[0.00281959302585077 - 0.030628473607392j ]]))
406+ np .testing .assert_array_almost_equal (
407+ sys ._evalfr (1. ), np .array ([[- 0.5 - 0.5j ]]))
408+ np .testing .assert_array_almost_equal (
409+ sys ._evalfr (32. ),
410+ np .array ([[0.00281959302585077 - 0.030628473607392j ]]))
335411
336412 # This test only works in Python 3 due to a conflict with the same
337413 # warning type in other test modules (frd_test.py). See
@@ -349,6 +425,11 @@ def test_evalfr_deprecated(self):
349425 # Make sure that we get a pending deprecation warning
350426 self .assertRaises (PendingDeprecationWarning , sys .evalfr , 1. )
351427
428+ @unittest .skipIf (pysys .version_info < (3 , 0 ), "test requires Python 3+" )
429+ def test_evalfr_dtime (self ):
430+ sys = TransferFunction ([1. , 3. , 5 ], [1. , 6. , 2. , - 1 ], 0.1 )
431+ np .testing .assert_array_almost_equal (sys (1j ), - 0.5 - 0.5j )
432+
352433 @unittest .skipIf (not slycot_check (), "slycot not installed" )
353434 def test_evalfr_mimo (self ):
354435 """Evaluate the frequency response of a MIMO system at one frequency."""
@@ -368,7 +449,8 @@ def test_evalfr_mimo(self):
368449 np .testing .assert_array_almost_equal (sys (2.j ), resp )
369450
370451 def test_freqresp_siso (self ):
371- """Evaluate the magnitude and phase of a SISO system at multiple frequencies."""
452+ """Evaluate the magnitude and phase of a SISO system at
453+ multiple frequencies."""
372454
373455 sys = TransferFunction ([1. , 3. , 5 ], [1. , 6. , 2. , - 1 ])
374456
@@ -385,7 +467,8 @@ def test_freqresp_siso(self):
385467
386468 @unittest .skipIf (not slycot_check (), "slycot not installed" )
387469 def test_freqresp_mimo (self ):
388- """Evaluate the magnitude and phase of a MIMO system at multiple frequencies."""
470+ """Evaluate the magnitude and phase of a MIMO system at
471+ multiple frequencies."""
389472
390473 num = [[[1. , 2. ], [0. , 3. ], [2. , - 1. ]],
391474 [[1. ], [4. , 0. ], [1. , - 4. , 3. ]]]
@@ -394,16 +477,17 @@ def test_freqresp_mimo(self):
394477 sys = TransferFunction (num , den )
395478
396479 true_omega = [0.1 , 1. , 10. ]
397- true_mag = [[[0.496287094505259 , 0.307147558416976 , 0.0334738176210382 ],
480+ true_mag = [[[0.49628709 , 0.30714755 , 0.03347381 ],
398481 [300. , 3. , 0.03 ], [1. , 1. , 1. ]],
399- [[33.3333333333333 , 0.333333333333333 , 0.00333333333333333 ],
400- [0.390285696125482 , 1.26491106406735 , 0.198759144198533 ],
401- [3.01663720059274 , 4.47213595499958 , 104.92378186093 ]]]
402- true_phase = [[[3.7128711165168e-4 , 0.185347949995695 , 1.30770596539255 ],
403- [- np .pi , - np .pi , - np .pi ], [0. , 0. , 0. ]],
482+ [[33.333333 , 0.33333333 , 0.00333333 ],
483+ [0.39028569 , 1.26491106 , 0.19875914 ],
484+ [3.01663720 , 4.47213595 , 104.92378186 ]]]
485+ true_phase = [[[3.7128711e-4 , 0.18534794 ,
486+ 1.30770596 ], [- np .pi , - np .pi , - np .pi ],
487+ [0. , 0. , 0. ]],
404488 [[- np .pi , - np .pi , - np .pi ],
405- [- 1.66852323415362 , - 1.89254688119154 , - 1.62050658356412 ],
406- [- 0.132989648369409 , - 1.1071487177940 , - 2.7504672066207 ]]]
489+ [- 1.66852323 , - 1.89254688 , - 1.62050658 ],
490+ [- 0.13298964 , - 1.10714871 , - 2.75046720 ]]]
407491
408492 mag , phase , omega = sys .freqresp (true_omega )
409493
@@ -417,8 +501,9 @@ def test_freqresp_mimo(self):
417501 def test_pole_mimo (self ):
418502 """Test for correct MIMO poles."""
419503
420- sys = TransferFunction ([[[1. ], [1. ]], [[1. ], [1. ]]],
421- [[[1. , 2. ], [1. , 3. ]], [[1. , 4. , 4. ], [1. , 9. , 14. ]]])
504+ sys = TransferFunction (
505+ [[[1. ], [1. ]], [[1. ], [1. ]]],
506+ [[[1. , 2. ], [1. , 3. ]], [[1. , 4. , 4. ], [1. , 9. , 14. ]]])
422507 p = sys .pole ()
423508
424509 np .testing .assert_array_almost_equal (p , [- 2. , - 2. , - 7. , - 3. , - 2. ])
@@ -619,6 +704,40 @@ def test_ss2tf(self):
619704 np .testing .assert_almost_equal (sys .num , true_sys .num )
620705 np .testing .assert_almost_equal (sys .den , true_sys .den )
621706
707+ def test_printing (self ):
708+ # SISO, continuous time
709+ sys = ss2tf (rss (4 , 1 , 1 ))
710+ self .assertTrue (isinstance (str (sys ), str ))
711+ self .assertTrue (isinstance (sys ._repr_latex_ (), str ))
712+
713+ # SISO, discrete time
714+ sys = sample_system (sys , 1 )
715+ self .assertTrue (isinstance (str (sys ), str ))
716+ self .assertTrue (isinstance (sys ._repr_latex_ (), str ))
717+
718+ # MIMO, continuous time
719+ sys = ss2tf (rss (4 , 2 , 3 ))
720+ self .assertTrue (isinstance (str (sys ), str ))
721+ self .assertTrue (isinstance (sys ._repr_latex_ (), str ))
722+
723+ def test_size_mismatch (self ):
724+ sys1 = ss2tf (rss (2 , 2 , 2 ))
725+
726+ # Different number of inputs
727+ sys2 = ss2tf (rss (3 , 1 , 2 ))
728+ self .assertRaises (ValueError , TransferFunction .__add__ , sys1 , sys2 )
729+
730+ # Different number of outputs
731+ sys2 = ss2tf (rss (3 , 2 , 1 ))
732+ self .assertRaises (ValueError , TransferFunction .__add__ , sys1 , sys2 )
733+
734+ # Inputs and outputs don't match
735+ self .assertRaises (ValueError , TransferFunction .__mul__ , sys2 , sys1 )
736+
737+ # Feedback mismatch (MIMO not implemented)
738+ self .assertRaises (NotImplementedError ,
739+ TransferFunction .feedback , sys2 , sys1 )
740+
622741
623742def suite ():
624743 return unittest .TestLoader ().loadTestsFromTestCase (TestXferFcn )
0 commit comments