@@ -116,7 +116,7 @@ def test_margin_3input(tsys):
116116 (([1 ], [1 , 1 ]), [0. ], [1. ]),
117117 (([2 ], [1 , 3 , 3 , 1 ]), [1.732 , 0. ], [- 0.25 , 2. ]),
118118 ((np .array ([3 , 11 , 3 ]) * 1e-4 , [1. , - 2.7145 , 2.4562 , - 0.7408 ], .1 ),
119- [10 * np . pi , 1.6235 , 0. ], [ 7.23432e-05 , - 0.28598 , 1.88889 ]),
119+ [1.6235 , 0. , 10 * np . pi ], [ - 0.28598 , 1.88889 , 7.23432e-05 ]),
120120 (([200.0 ], [1.0 , 21.0 , 20.0 , 0.0 ]),
121121 [4.47213595 , 0 ], [- 0.47619048 , inf ]),
122122 ])
@@ -171,26 +171,6 @@ def test_stability_margins_discrete_nyquist(num, den, gm_ref, method):
171171 assert_allclose (wpc , np .pi / L .dt , rtol = 1e-6 )
172172
173173
174- def test_z_filter_nyquist_endpoint ():
175- """_z_filter keeps z = -1 exactly, without snapping nearby roots.
176-
177- A genuine root just below pi keeps its computed frequency, its
178- lower-half conjugate stays excluded, and -1 is kept whether its
179- imaginary zero is positive or negative (arg pi or -pi).
180- """
181- from control .margins import _z_filter
182- # note: the -1 literals must be built with complex() - the expression
183- # -1 - 0j has a *positive* imaginary zero, so it does not cover arg -pi
184- zend = [complex (- 1. , - 0. ), complex (- 1. , 0. )]
185- assert np .angle (zend [0 ]) == - np .pi
186- assert np .angle (zend [1 ]) == np .pi
187- z = np .array ([np .exp (1j * (np .pi - 1e-6 )),
188- np .exp (- 1j * (np .pi - 1e-6 ))] + zend )
189- zf , omega = _z_filter (z , dt = 2.0 , eps = 0.1 )
190- assert_allclose (np .sort (omega ),
191- np .sort ([np .pi / 2 , np .pi / 2 , (np .pi - 1e-6 ) / 2 ]))
192-
193-
194174@pytest .mark .parametrize ("method" , ["poly" , "frd" , "best" ])
195175def test_stability_margins_discrete_nyquist_positive (method ):
196176 """L(-1) > 0: no phase crossover at the Nyquist frequency."""
@@ -202,6 +182,26 @@ def test_stability_margins_discrete_nyquist_positive(method):
202182 assert gm == inf
203183
204184
185+ def test_stability_margins_moving_average_phase ():
186+ """No gain crossover at w = pi/dt when |L(-1)| = 0.
187+
188+ Regression test for the review on gh-1268: for an even-length moving
189+ average the |L| = 1 polynomial has off-circle roots with argument
190+ exactly pi, which must not be accepted as a gain crossover at the
191+ Nyquist frequency (that reported pm = 0 for a loop whose true phase
192+ margin is about 70 degrees).
193+ """
194+ N = 16
195+ num = np .r_ [0.0 , np .full (N , 1.0 / N )]
196+ L = TransferFunction (num , np .polysub (np .r_ [1.0 , np .zeros (N )], num ), 1. )
197+ assert abs (complex (L (- 1 ))) < 1e-12
198+ with warnings .catch_warnings ():
199+ warnings .simplefilter ("ignore" )
200+ gm , pm , sm , wpc , wgc , wms = stability_margins (L , method = "poly" )
201+ assert wpc != pytest .approx (np .pi ) or gm == inf
202+ assert pm > 0 and wgc != pytest .approx (np .pi )
203+
204+
205205
206206def test_frd ():
207207 """Test FrequencyResonseData margins"""
0 commit comments