From 0cafd688960765cde0dc3d16b20d1e475005c0bb Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sun, 31 Mar 2024 09:00:49 -0700 Subject: [PATCH] fix unit tests that generate multiple warnings --- control/tests/freqresp_test.py | 36 +++++++++++++++++++++++++--------- control/tests/nyquist_test.py | 29 +++++++++++++++++++-------- control/tests/xferfcn_test.py | 20 ++++++++++++------- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/control/tests/freqresp_test.py b/control/tests/freqresp_test.py index dd236769a..18c59384d 100644 --- a/control/tests/freqresp_test.py +++ b/control/tests/freqresp_test.py @@ -6,19 +6,21 @@ including bode plots. """ +import math +import re + import matplotlib.pyplot as plt import numpy as np -from numpy.testing import assert_allclose -import math import pytest +from numpy.testing import assert_allclose import control as ctrl +from control.freqplot import (bode_plot, nyquist_plot, nyquist_response, + singular_values_plot, singular_values_response) +from control.matlab import bode, rss, ss, tf from control.statesp import StateSpace -from control.xferfcn import TransferFunction -from control.matlab import ss, tf, bode, rss -from control.freqplot import bode_plot, nyquist_plot, nyquist_response, \ - singular_values_plot, singular_values_response from control.tests.conftest import slycotonly +from control.xferfcn import TransferFunction pytestmark = pytest.mark.usefixtures("mplcleanup") @@ -101,12 +103,17 @@ def test_nyquist_basic(ss_siso): response = nyquist_response(tf_siso, omega_num=20) assert len(response.contour) == 20 - with pytest.warns(UserWarning, match="encirclements was a non-integer"): + with pytest.warns() as record: count, contour = nyquist_plot( tf_siso, plot=False, omega_limits=(1, 100), return_contour=True) assert_allclose(contour[0], 1j) assert_allclose(contour[-1], 100j) + # Check known warnings happened as expected + assert len(record) == 2 + assert re.search("encirclements was a non-integer", str(record[0].message)) + assert re.search("return values .* deprecated", str(record[1].message)) + response = nyquist_response(tf_siso, omega=np.logspace(-1, 1, 10)) assert len(response.contour) == 10 @@ -428,14 +435,25 @@ def test_freqresp_warn_infinite(): np.testing.assert_almost_equal(sys_finite(0, warn_infinite=True), 100) # Transfer function with infinite zero frequency gain - with pytest.warns(RuntimeWarning, match="divide by zero"): + with pytest.warns() as record: np.testing.assert_almost_equal( sys_infinite(0), complex(np.inf, np.nan)) - with pytest.warns(RuntimeWarning, match="divide by zero"): + assert len(record) == 2 # generates two RuntimeWarnings + assert record[0].category is RuntimeWarning + assert re.search("divide by zero", str(record[0].message)) + assert record[1].category is RuntimeWarning + assert re.search("invalid value", str(record[1].message)) + + with pytest.warns() as record: np.testing.assert_almost_equal( sys_infinite(0, warn_infinite=True), complex(np.inf, np.nan)) np.testing.assert_almost_equal( sys_infinite(0, warn_infinite=False), complex(np.inf, np.nan)) + assert len(record) == 2 # generates two RuntimeWarnings + assert record[0].category is RuntimeWarning + assert re.search("divide by zero", str(record[0].message)) + assert record[1].category is RuntimeWarning + assert re.search("invalid value", str(record[1].message)) # Switch to state space sys_finite = ctrl.tf2ss(sys_finite) diff --git a/control/tests/nyquist_test.py b/control/tests/nyquist_test.py index 1100eb01e..a687ee61b 100644 --- a/control/tests/nyquist_test.py +++ b/control/tests/nyquist_test.py @@ -8,11 +8,13 @@ """ +import re import warnings -import pytest -import numpy as np import matplotlib.pyplot as plt +import numpy as np +import pytest + import control as ct pytestmark = pytest.mark.usefixtures("mplcleanup") @@ -66,9 +68,12 @@ def test_nyquist_basic(): assert _Z(sys) == N_sys + _P(sys) # With a larger indent_radius, we get a warning message + wrong answer - with pytest.warns(UserWarning, match="contour may miss closed loop pole"): + with pytest.warns() as rec: N_sys = ct.nyquist_response(sys, indent_radius=0.2) assert _Z(sys) != N_sys + _P(sys) + assert len(rec) == 2 + assert re.search("contour may miss closed loop pole", str(rec[0].message)) + assert re.search("encirclements does not match", str(rec[1].message)) # Unstable system sys = ct.tf([10], [1, 2, 2, 1]) @@ -104,11 +109,15 @@ def test_nyquist_basic(): sys, np.linspace(1e-4, 1e2, 100), indent_radius=1e-2, return_contour=True) assert not all(contour_indented.real == 0) - with pytest.warns(UserWarning, match="encirclements does not match"): + + with pytest.warns() as record: count, contour = ct.nyquist_response( sys, np.linspace(1e-4, 1e2, 100), indent_radius=1e-2, return_contour=True, indent_direction='none') np.testing.assert_almost_equal(contour, 1j*np.linspace(1e-4, 1e2, 100)) + assert len(record) == 2 + assert re.search("encirclements .* non-integer", str(record[0].message)) + assert re.search("encirclements does not match", str(record[1].message)) # Nyquist plot with poles at the origin, omega unspecified sys = ct.tf([1], [1, 3, 2]) * ct.tf([1], [1, 0]) @@ -264,7 +273,7 @@ def test_nyquist_indent_default(indentsys): def test_nyquist_indent_dont(indentsys): # first value of default omega vector was 0.1, replaced by 0. for contour # indent_radius is larger than 0.1 -> no extra quater circle around origin - with pytest.warns(UserWarning, match="encirclements does not match"): + with pytest.warns() as record: count, contour = ct.nyquist_response( indentsys, omega=[0, 0.2, 0.3, 0.4], indent_radius=.1007, plot=False, return_contour=True) @@ -272,6 +281,11 @@ def test_nyquist_indent_dont(indentsys): # second value of omega_vector is larger than indent_radius: not indented assert np.all(contour.real[2:] == 0.) + # Make sure warnings are as expected + assert len(record) == 2 + assert re.search("encirclements .* non-integer", str(record[0].message)) + assert re.search("encirclements does not match", str(record[1].message)) + def test_nyquist_indent_do(indentsys): plt.figure(); @@ -352,9 +366,8 @@ def test_nyquist_exceptions(): ct.nyquist_plot(sys, indent_direction='up') # Discrete time system sampled above Nyquist frequency - sys = ct.drss(2, 1, 1) - sys.dt = 0.01 - with pytest.warns(UserWarning, match="above Nyquist"): + sys = ct.ss([[-0.5, 0], [1, 0.5]], [[0], [1]], [[1, 0]], 0, 0.1) + with pytest.warns(UserWarning, match="evaluation above Nyquist"): ct.nyquist_plot(sys, np.logspace(-2, 3)) diff --git a/control/tests/xferfcn_test.py b/control/tests/xferfcn_test.py index 5fa9b3769..cb5b38cba 100644 --- a/control/tests/xferfcn_test.py +++ b/control/tests/xferfcn_test.py @@ -3,18 +3,19 @@ RMM, 30 Mar 2011 (based on TestXferFcn from v0.4a) """ +import operator +import re + import numpy as np import pytest -import operator import control as ct -from control import StateSpace, TransferFunction, rss, evalfr -from control import ss, ss2tf, tf, tf2ss, zpk -from control import isctime, isdtime, sample_system -from control import defaults, reset_defaults, set_defaults +from control import (StateSpace, TransferFunction, defaults, evalfr, isctime, + isdtime, reset_defaults, rss, sample_system, set_defaults, + ss, ss2tf, tf, tf2ss, zpk) from control.statesp import _convert_to_statespace -from control.xferfcn import _convert_to_transfer_function from control.tests.conftest import slycotonly +from control.xferfcn import _convert_to_transfer_function class TestXferFcn: @@ -836,9 +837,14 @@ def test_dcgain_discr(self): # differencer, with warning sys = TransferFunction(1, [1, -1], True) - with pytest.warns(RuntimeWarning, match="divide by zero"): + with pytest.warns() as record: np.testing.assert_equal( sys.dcgain(warn_infinite=True), np.inf) + assert len(record) == 2 # generates two RuntimeWarnings + assert record[0].category is RuntimeWarning + assert re.search("divide by zero", str(record[0].message)) + assert record[1].category is RuntimeWarning + assert re.search("invalid value", str(record[1].message)) # summer sys = TransferFunction([1, -1], [1], True)