Repository: python-control/python-control
Pull request #1106 (2025-01-29 00:46:00)

Turn off warn_infinite for phase_crossover_frequencies() calculation
This PR addresses #1105 where warning messages were being generated if a system had poles at the origin (which generates a phase crossover at infinity).  The fix was simple: warning messages should be turned off when computing the crossover gains (as they are in `stability_margins`).



Patch:
diff --git a/control/margins.py b/control/margins.py
index 301baaf5..019c866b 100644
--- a/control/margins.py
+++ b/control/margins.py
@@ -81,11 +81,16 @@ def _poly_iw_sqr(pol_iw):
 
 def _poly_iw_real_crossing(num_iw, den_iw, epsw):
     # Return w where imag(H(iw)) == 0
+
+    # Compute the imaginary part of H = (num.r + j num.i)/(den.r + j den.i)
     test_w = np.polysub(np.polymul(num_iw.imag, den_iw.real),
                         np.polymul(num_iw.real, den_iw.imag))
+
+    # Find the real-valued w > 0 where imag(H(iw)) = 0
     w = np.roots(test_w)
     w = np.real(w[np.isreal(w)])
     w = w[w >= epsw]
+
     return w
 
 
@@ -471,7 +476,7 @@ def phase_crossover_frequencies(sys):
     omega : ndarray
         1d array of (non-negative) frequencies where Nyquist plot
         intersects the real axis
-    gain : ndarray
+    gains : ndarray
         1d array of corresponding gains
 
     Examples
@@ -493,13 +498,13 @@ def phase_crossover_frequencies(sys):
         omega = _poly_iw_real_crossing(num_iw, den_iw, 0.)
 
         # using real() to avoid rounding errors and results like 1+0j
-        gain = np.real(evalfr(sys, 1J * omega))
+        gains = np.real(sys(omega * 1j, warn_infinite=False))
     else:
         zargs = _poly_z_invz(sys)
         z, omega = _poly_z_real_crossing(*zargs, epsw=0.)
-        gain = np.real(evalfr(sys, z))
+        gains = np.real(sys(z, warn_infinite=False))
 
-    return omega, gain
+    return omega, gains
 
 
 def margin(*args):


Test patch:
diff --git a/control/tests/margin_test.py b/control/tests/margin_test.py
index 07e21114..43cd68ae 100644
--- a/control/tests/margin_test.py
+++ b/control/tests/margin_test.py
@@ -12,12 +12,9 @@ import pytest
 from numpy import inf, nan
 from numpy.testing import assert_allclose
 
-from control.frdata import FrequencyResponseData
-from control.margins import (margin, phase_crossover_frequencies,
-                             stability_margins)
-from control.statesp import StateSpace
-from control.xferfcn import TransferFunction
-from control.exception import ControlMIMONotImplemented
+from control import ControlMIMONotImplemented, FrequencyResponseData, \
+    StateSpace, TransferFunction, margin, phase_crossover_frequencies, \
+    stability_margins
 
 s = TransferFunction.s
 
@@ -111,7 +108,6 @@ def test_margin_3input(tsys):
     out = margin((mag, phase*180/np.pi, omega_))
     assert_allclose(out, np.array(refout)[[0, 1, 3, 4]], atol=1.5e-3)
 
-
 @pytest.mark.parametrize(
     'tfargs, omega_ref, gain_ref',
     [(([1], [1, 2, 3, 4]), [1.7325, 0.], [-0.5, 0.25]),
@@ -119,7 +115,10 @@ def test_margin_3input(tsys):
      (([2], [1, 3, 3, 1]), [1.732, 0.], [-0.25, 2.]),
      ((np.array([3, 11, 3]) * 1e-4, [1., -2.7145, 2.4562, -0.7408], .1),
       [1.6235, 0.], [-0.28598, 1.88889]),
+     (([200.0], [1.0, 21.0, 20.0, 0.0]),
+      [4.47213595, 0], [-0.47619048, inf]),
      ])
+@pytest.mark.filterwarnings("error")
 def test_phase_crossover_frequencies(tfargs, omega_ref, gain_ref):
     """Test phase_crossover_frequencies() function"""
     sys = TransferFunction(*tfargs)