@@ -371,3 +371,140 @@ def test_phase_wrap(TF, wrap_phase, min_phase, max_phase):
371371 mag , phase , omega = ctrl .bode (TF , wrap_phase = wrap_phase )
372372 assert (min (phase ) >= min_phase )
373373 assert (max (phase ) <= max_phase )
374+
375+
376+ def test_freqresp_warn_infinite ():
377+ """Test evaluation warnings for transfer functions w/ pole at the origin"""
378+ sys_finite = ctrl .tf ([1 ], [1 , 0.01 ])
379+ sys_infinite = ctrl .tf ([1 ], [1 , 0.01 , 0 ])
380+
381+ # Transfer function with finite zero frequency gain
382+ np .testing .assert_almost_equal (sys_finite (0 ), 100 )
383+ np .testing .assert_almost_equal (sys_finite (0 , warn_infinite = False ), 100 )
384+ np .testing .assert_almost_equal (sys_finite (0 , warn_infinite = True ), 100 )
385+
386+ # Transfer function with infinite zero frequency gain
387+ with pytest .warns (RuntimeWarning , match = "divide by zero" ):
388+ np .testing .assert_almost_equal (
389+ sys_infinite (0 ), complex (np .inf , np .nan ))
390+ with pytest .warns (RuntimeWarning , match = "divide by zero" ):
391+ np .testing .assert_almost_equal (
392+ sys_infinite (0 , warn_infinite = True ), complex (np .inf , np .nan ))
393+ np .testing .assert_almost_equal (
394+ sys_infinite (0 , warn_infinite = False ), complex (np .inf , np .nan ))
395+
396+ # Switch to state space
397+ sys_finite = ctrl .tf2ss (sys_finite )
398+ sys_infinite = ctrl .tf2ss (sys_infinite )
399+
400+ # State space system with finite zero frequency gain
401+ np .testing .assert_almost_equal (sys_finite (0 ), 100 )
402+ np .testing .assert_almost_equal (sys_finite (0 , warn_infinite = False ), 100 )
403+ np .testing .assert_almost_equal (sys_finite (0 , warn_infinite = True ), 100 )
404+
405+ # State space system with infinite zero frequency gain
406+ with pytest .warns (RuntimeWarning , match = "singular matrix" ):
407+ np .testing .assert_almost_equal (
408+ sys_infinite (0 ), complex (np .inf , np .nan ))
409+ with pytest .warns (RuntimeWarning , match = "singular matrix" ):
410+ np .testing .assert_almost_equal (
411+ sys_infinite (0 , warn_infinite = True ), complex (np .inf , np .nan ))
412+ np .testing .assert_almost_equal (sys_infinite (
413+ 0 , warn_infinite = False ), complex (np .inf , np .nan ))
414+
415+
416+ def test_dcgain_consistency ():
417+ """Test to make sure that DC gain is consistently evaluated"""
418+ # Set up transfer function with pole at the origin
419+ sys_tf = ctrl .tf ([1 ], [1 , 0 ])
420+ assert 0 in sys_tf .pole ()
421+
422+ # Set up state space system with pole at the origin
423+ sys_ss = ctrl .tf2ss (sys_tf )
424+ assert 0 in sys_ss .pole ()
425+
426+ # Finite (real) numerator over 0 denominator => inf + nanj
427+ np .testing .assert_equal (
428+ sys_tf (0 , warn_infinite = False ), complex (np .inf , np .nan ))
429+ np .testing .assert_equal (
430+ sys_ss (0 , warn_infinite = False ), complex (np .inf , np .nan ))
431+ np .testing .assert_equal (
432+ sys_tf (0j , warn_infinite = False ), complex (np .inf , np .nan ))
433+ np .testing .assert_equal (
434+ sys_ss (0j , warn_infinite = False ), complex (np .inf , np .nan ))
435+ np .testing .assert_equal (
436+ sys_tf .dcgain (warn_infinite = False ), complex (np .inf , np .nan ))
437+ np .testing .assert_equal (
438+ sys_ss .dcgain (warn_infinite = False ), complex (np .inf , np .nan ))
439+
440+ # Set up transfer function with pole, zero at the origin
441+ sys_tf = ctrl .tf ([1 , 0 ], [1 , 0 ])
442+ assert 0 in sys_tf .pole ()
443+ assert 0 in sys_tf .zero ()
444+
445+ # Pole and zero at the origin should give nan + nanj for the response
446+ np .testing .assert_equal (
447+ sys_tf (0 , warn_infinite = False ), complex (np .nan , np .nan ))
448+ np .testing .assert_equal (
449+ sys_tf (0j , warn_infinite = False ), complex (np .nan , np .nan ))
450+ np .testing .assert_equal (
451+ sys_tf .dcgain (warn_infinite = False ), complex (np .nan , np .nan ))
452+
453+ # Set up state space version
454+ sys_ss = ctrl .tf2ss (ctrl .tf ([1 , 0 ], [1 , 1 ])) * \
455+ ctrl .tf2ss (ctrl .tf ([1 ], [1 , 0 ]))
456+
457+ # Different systems give different representations => test accordingly
458+ if 0 in sys_ss .pole () and 0 in sys_ss .zero ():
459+ # Pole and zero at the origin => should get (nan + nanj)
460+ np .testing .assert_equal (
461+ sys_ss (0 , warn_infinite = False ), complex (np .nan , np .nan ))
462+ np .testing .assert_equal (
463+ sys_ss (0j , warn_infinite = False ), complex (np .nan , np .nan ))
464+ np .testing .assert_equal (
465+ sys_ss .dcgain (warn_infinite = False ), complex (np .nan , np .nan ))
466+ elif 0 in sys_ss .pole ():
467+ # Pole at the origin, but zero elsewhere => should get (inf + nanj)
468+ np .testing .assert_equal (
469+ sys_ss (0 , warn_infinite = False ), complex (np .inf , np .nan ))
470+ np .testing .assert_equal (
471+ sys_ss (0j , warn_infinite = False ), complex (np .inf , np .nan ))
472+ np .testing .assert_equal (
473+ sys_ss .dcgain (warn_infinite = False ), complex (np .inf , np .nan ))
474+ else :
475+ # Near pole/zero cancellation => nothing sensible to check
476+ pass
477+
478+ # Pole with non-zero, complex numerator => inf + infj
479+ s = ctrl .tf ('s' )
480+ sys_tf = (s + 1 ) / (s ** 2 + 1 )
481+ assert 1j in sys_tf .pole ()
482+
483+ # Set up state space system with pole on imaginary axis
484+ sys_ss = ctrl .tf2ss (sys_tf )
485+ assert 1j in sys_tf .pole ()
486+
487+ # Make sure we get correct response if evaluated at the pole
488+ np .testing .assert_equal (
489+ sys_tf (1j , warn_infinite = False ), complex (np .inf , np .inf ))
490+
491+ # For state space, numerical errors come into play
492+ resp_ss = sys_ss (1j , warn_infinite = False )
493+ if np .isfinite (resp_ss ):
494+ assert abs (resp_ss ) > 1e15
495+ else :
496+ if resp_ss != complex (np .inf , np .inf ):
497+ pytest .xfail ("statesp evaluation at poles not fully implemented" )
498+ else :
499+ np .testing .assert_equal (resp_ss , complex (np .inf , np .inf ))
500+
501+ # DC gain is finite
502+ np .testing .assert_almost_equal (sys_tf .dcgain (), 1. )
503+ np .testing .assert_almost_equal (sys_ss .dcgain (), 1. )
504+
505+ # Make sure that we get the *signed* DC gain
506+ sys_tf = - 1 / (s + 1 )
507+ np .testing .assert_almost_equal (sys_tf .dcgain (), - 1 )
508+
509+ sys_ss = ctrl .tf2ss (sys_tf )
510+ np .testing .assert_almost_equal (sys_ss .dcgain (), - 1 )
0 commit comments