diff --git a/control/tests/bdalg_test.py b/control/tests/bdalg_test.py index 50ae9e8a9..cec10f904 100644 --- a/control/tests/bdalg_test.py +++ b/control/tests/bdalg_test.py @@ -347,19 +347,19 @@ def test_bdalg_udpate_names_errors(): sys2 = ctrl.rss(2, 1, 1) with pytest.raises(ValueError, match="number of inputs does not match"): - sys = ctrl.series(sys1, sys2, inputs=2) + ctrl.series(sys1, sys2, inputs=2) with pytest.raises(ValueError, match="number of outputs does not match"): - sys = ctrl.series(sys1, sys2, outputs=2) + ctrl.series(sys1, sys2, outputs=2) with pytest.raises(ValueError, match="number of states does not match"): - sys = ctrl.series(sys1, sys2, states=2) + ctrl.series(sys1, sys2, states=2) with pytest.raises(ValueError, match="number of states does not match"): - sys = ctrl.series(ctrl.tf(sys1), ctrl.tf(sys2), states=2) + ctrl.series(ctrl.tf(sys1), ctrl.tf(sys2), states=2) with pytest.raises(TypeError, match="unrecognized keywords"): - sys = ctrl.series(sys1, sys2, dt=1) + ctrl.series(sys1, sys2, dt=1) class TestEnsureTf: diff --git a/control/tests/bspline_test.py b/control/tests/bspline_test.py index 0ac59094d..e15915182 100644 --- a/control/tests/bspline_test.py +++ b/control/tests/bspline_test.py @@ -11,11 +11,9 @@ import numpy as np import pytest -import scipy as sp import control as ct import control.flatsys as fs -import control.optimal as opt def test_bspline_basis(): Tf = 10 @@ -182,40 +180,40 @@ def test_kinematic_car_multivar(): def test_bspline_errors(): # Breakpoints must be a 1D array, in increasing order with pytest.raises(NotImplementedError, match="not yet supported"): - basis = fs.BSplineFamily([[0, 1, 3], [0, 2, 3]], [3, 3]) + fs.BSplineFamily([[0, 1, 3], [0, 2, 3]], [3, 3]) with pytest.raises(ValueError, match="breakpoints must be convertable to a 1D array"): - basis = fs.BSplineFamily([[[0, 1], [0, 1]], [[0, 1], [0, 1]]], [3, 3]) + fs.BSplineFamily([[[0, 1], [0, 1]], [[0, 1], [0, 1]]], [3, 3]) with pytest.raises(ValueError, match="must have at least 2 values"): - basis = fs.BSplineFamily([10], 2) + fs.BSplineFamily([10], 2) with pytest.raises(ValueError, match="must be strictly increasing"): - basis = fs.BSplineFamily([1, 3, 2], 2) + fs.BSplineFamily([1, 3, 2], 2) # Smoothness can't be more than dimension of splines - basis = fs.BSplineFamily([0, 1], 4, 3) # OK + fs.BSplineFamily([0, 1], 4, 3) # OK with pytest.raises(ValueError, match="degree must be greater"): - basis = fs.BSplineFamily([0, 1], 4, 4) # not OK + fs.BSplineFamily([0, 1], 4, 4) # not OK # nvars must be an integer with pytest.raises(TypeError, match="vars must be an integer"): - basis = fs.BSplineFamily([0, 1], 4, 3, vars=['x1', 'x2']) + fs.BSplineFamily([0, 1], 4, 3, vars=['x1', 'x2']) # degree, smoothness must match nvars with pytest.raises(ValueError, match="length of 'degree' does not match"): - basis = fs.BSplineFamily([0, 1], [4, 4, 4], 3, vars=2) + fs.BSplineFamily([0, 1], [4, 4, 4], 3, vars=2) # degree, smoothness must be list of ints - basis = fs.BSplineFamily([0, 1], [4, 4], 3, vars=2) # OK + fs.BSplineFamily([0, 1], [4, 4], 3, vars=2) # OK with pytest.raises(ValueError, match="could not parse 'degree'"): - basis = fs.BSplineFamily([0, 1], [4, '4'], 3, vars=2) + fs.BSplineFamily([0, 1], [4, '4'], 3, vars=2) # degree must be strictly positive with pytest.raises(ValueError, match="'degree'; must be at least 1"): - basis = fs.BSplineFamily([0, 1], 0, 1) + fs.BSplineFamily([0, 1], 0, 1) # smoothness must be non-negative with pytest.raises(ValueError, match="'smoothness'; must be at least 0"): - basis = fs.BSplineFamily([0, 1], 2, -1) + fs.BSplineFamily([0, 1], 2, -1) diff --git a/control/tests/config_test.py b/control/tests/config_test.py index 646a20a16..be3fba5c9 100644 --- a/control/tests/config_test.py +++ b/control/tests/config_test.py @@ -339,11 +339,8 @@ def test_system_indexing(self): {'dt': 0.1} ]) def test_repr_format(self, kwargs): - from ..statesp import StateSpace - from numpy import array - sys = ct.ss([[1]], [[1]], [[1]], [[0]], **kwargs) - new = eval(repr(sys)) + new = eval(repr(sys), None, {'StateSpace':ct.StateSpace, 'array':np.array}) for attr in ['A', 'B', 'C', 'D']: assert getattr(new, attr) == getattr(sys, attr) for prop in ['input_labels', 'output_labels', 'state_labels']: diff --git a/control/tests/conftest.py b/control/tests/conftest.py index bf3920a02..c10dcc225 100644 --- a/control/tests/conftest.py +++ b/control/tests/conftest.py @@ -1,8 +1,5 @@ """conftest.py - pytest local plugins, fixtures, marks and functions.""" -import os -from contextlib import contextmanager - import matplotlib as mpl import numpy as np import pytest diff --git a/control/tests/ctrlplot_test.py b/control/tests/ctrlplot_test.py index 221085328..958d855b2 100644 --- a/control/tests/ctrlplot_test.py +++ b/control/tests/ctrlplot_test.py @@ -74,7 +74,6 @@ def setup_plot_arguments(resp_fcn, plot_fcn, compute_time_response=True): case ct.gangof4_response, _: args1 = (sys1, sys1c) args2 = (sys2, sys1c) - default_labels = ["P=sys[1]", "P=sys[2]"] case ct.frequency_response, ct.nichols_plot: args1 = (sys1, None) # to allow *fmt in linestyle test @@ -239,10 +238,10 @@ def test_plot_ax_processing(resp_fcn, plot_fcn): # Call the plotting function, passing the axes if resp_fcn is not None: resp = resp_fcn(*args, **kwargs, **resp_kwargs) - cplt4 = resp.plot(**kwargs, **meth_kwargs, ax=ax) + resp.plot(**kwargs, **meth_kwargs, ax=ax) else: # No response function available; just plot the data - cplt4 = plot_fcn(*args, **kwargs, **plot_kwargs, ax=ax) + plot_fcn(*args, **kwargs, **plot_kwargs, ax=ax) # Check to make sure original settings did not change assert fig._suptitle.get_text() == title @@ -331,19 +330,9 @@ def test_plot_label_processing(resp_fcn, plot_fcn): @pytest.mark.parametrize("resp_fcn, plot_fcn", resp_plot_fcns) @pytest.mark.usefixtures('mplcleanup') def test_plot_linestyle_processing(resp_fcn, plot_fcn): - # Create some systems to use - sys1 = ct.rss(2, 1, 1, strictly_proper=True, name="sys[1]") - sys1c = ct.rss(4, 1, 1, strictly_proper=True, name="sys[1]_C") - sys2 = ct.rss(4, 1, 1, strictly_proper=True, name="sys[2]") - # Set up arguments args1, args2, _, kwargs, meth_kwargs, plot_kwargs, resp_kwargs = \ setup_plot_arguments(resp_fcn, plot_fcn) - default_labels = ["sys[1]", "sys[2]"] - expected_labels = ["sys1_", "sys2_"] - match resp_fcn, plot_fcn: - case ct.gangof4_response, _: - default_labels = ["P=sys[1]", "P=sys[2]"] # Set line color cplt1 = plot_fcn(*args1, **kwargs, **plot_kwargs, color='r') @@ -491,16 +480,10 @@ def test_mimo_plot_legend_processing(resp_fcn, plot_fcn): @pytest.mark.parametrize("resp_fcn, plot_fcn", resp_plot_fcns) @pytest.mark.usefixtures('mplcleanup') def test_plot_title_processing(resp_fcn, plot_fcn): - # Create some systems to use - sys1 = ct.rss(2, 1, 1, strictly_proper=True, name="sys[1]") - sys1c = ct.rss(4, 1, 1, strictly_proper=True, name="sys[1]_C") - sys2 = ct.rss(2, 1, 1, strictly_proper=True, name="sys[2]") - # Set up arguments args1, args2, argsc, kwargs, meth_kwargs, plot_kwargs, resp_kwargs = \ setup_plot_arguments(resp_fcn, plot_fcn) default_title = "sys[1], sys[2]" - expected_title = "sys1_, sys2_" match resp_fcn, plot_fcn: case ct.gangof4_response, _: default_title = "P=sys[1], C=sys[1]_C, P=sys[2], C=sys[1]_C" @@ -536,7 +519,7 @@ def test_plot_title_processing(resp_fcn, plot_fcn): case ct.input_output_response, _: title_prefix = "Input/output response for " case _: - raise RuntimeError(f"didn't recognize {resp_fnc}, {plot_fnc}") + raise RuntimeError(f"didn't recognize {resp_fcn}, {plot_fcn}") # Generate the first plot, with default title cplt1 = plot_fcn(*args1, **kwargs, **plot_kwargs) @@ -587,11 +570,9 @@ def test_plot_title_processing(resp_fcn, plot_fcn): @pytest.mark.usefixtures('mplcleanup') def test_tickmark_label_processing(plot_fcn): # Generate the response that we will use for plotting - top_row, bot_row = 0, -1 match plot_fcn: case ct.bode_plot: resp = ct.frequency_response(ct.rss(4, 2, 2)) - top_row = 1 case ct.time_response_plot: resp = ct.step_response(ct.rss(4, 2, 2)) case ct.gangof4_plot: @@ -625,20 +606,9 @@ def test_tickmark_label_processing(plot_fcn): @pytest.mark.parametrize("resp_fcn, plot_fcn", resp_plot_fcns) @pytest.mark.usefixtures('mplcleanup', 'editsdefaults') def test_rcParams(resp_fcn, plot_fcn): - # Create some systems to use - sys1 = ct.rss(2, 1, 1, strictly_proper=True, name="sys[1]") - sys1c = ct.rss(4, 1, 1, strictly_proper=True, name="sys[1]_C") - sys2 = ct.rss(2, 1, 1, strictly_proper=True, name="sys[2]") - # Set up arguments args1, args2, argsc, kwargs, meth_kwargs, plot_kwargs, resp_kwargs = \ setup_plot_arguments(resp_fcn, plot_fcn) - default_title = "sys[1], sys[2]" - expected_title = "sys1_, sys2_" - match resp_fcn, plot_fcn: - case ct.gangof4_response, _: - default_title = "P=sys[1], C=sys[1]_C, P=sys[2], C=sys[1]_C" - # Create new set of rcParams my_rcParams = {} for key in ct.ctrlplot.rcParams: diff --git a/control/tests/descfcn_test.py b/control/tests/descfcn_test.py index a5f7a06c2..e91738e82 100644 --- a/control/tests/descfcn_test.py +++ b/control/tests/descfcn_test.py @@ -205,12 +205,12 @@ def test_describing_function_exceptions(): assert saturation(3) == 2 # Turn off the bias check - bias = ct.describing_function(saturation, 0, zero_check=False) + ct.describing_function(saturation, 0, zero_check=False) # Function should evaluate to zero at zero amplitude f = lambda x: x + 0.5 with pytest.raises(ValueError, match="must evaluate to zero"): - bias = ct.describing_function(f, 0, zero_check=True) + ct.describing_function(f, 0, zero_check=True) # Evaluate at a negative amplitude with pytest.raises(ValueError, match="cannot evaluate"): @@ -236,4 +236,4 @@ def test_describing_function_exceptions(): # Describing function plot for non-describing function object resp = ct.frequency_response(H_simple) with pytest.raises(TypeError, match="data must be DescribingFunction"): - cplt = ct.describing_function_plot(resp) + ct.describing_function_plot(resp) diff --git a/control/tests/discrete_test.py b/control/tests/discrete_test.py index 9dbc3eb00..9b87bd61b 100644 --- a/control/tests/discrete_test.py +++ b/control/tests/discrete_test.py @@ -231,14 +231,14 @@ def testisctime(self, tsys): def testAddition(self, tsys): # State space addition - sys = tsys.siso_ss1 + tsys.siso_ss1d - sys = tsys.siso_ss1 + tsys.siso_ss1c - sys = tsys.siso_ss1c + tsys.siso_ss1 - sys = tsys.siso_ss1d + tsys.siso_ss1 - sys = tsys.siso_ss1c + tsys.siso_ss1c - sys = tsys.siso_ss1d + tsys.siso_ss1d - sys = tsys.siso_ss3d + tsys.siso_ss3d - sys = tsys.siso_ss1d + tsys.siso_ss3d + _sys = tsys.siso_ss1 + tsys.siso_ss1d + _sys = tsys.siso_ss1 + tsys.siso_ss1c + _sys = tsys.siso_ss1c + tsys.siso_ss1 + _sys = tsys.siso_ss1d + tsys.siso_ss1 + _sys = tsys.siso_ss1c + tsys.siso_ss1c + _sys = tsys.siso_ss1d + tsys.siso_ss1d + _sys = tsys.siso_ss3d + tsys.siso_ss3d + _sys = tsys.siso_ss1d + tsys.siso_ss3d with pytest.raises(ValueError): StateSpace.__add__(tsys.mimo_ss1c, tsys.mimo_ss1d) @@ -246,14 +246,14 @@ def testAddition(self, tsys): StateSpace.__add__(tsys.mimo_ss1d, tsys.mimo_ss2d) # Transfer function addition - sys = tsys.siso_tf1 + tsys.siso_tf1d - sys = tsys.siso_tf1 + tsys.siso_tf1c - sys = tsys.siso_tf1c + tsys.siso_tf1 - sys = tsys.siso_tf1d + tsys.siso_tf1 - sys = tsys.siso_tf1c + tsys.siso_tf1c - sys = tsys.siso_tf1d + tsys.siso_tf1d - sys = tsys.siso_tf2d + tsys.siso_tf2d - sys = tsys.siso_tf1d + tsys.siso_tf3d + _sys = tsys.siso_tf1 + tsys.siso_tf1d + _sys = tsys.siso_tf1 + tsys.siso_tf1c + _sys = tsys.siso_tf1c + tsys.siso_tf1 + _sys = tsys.siso_tf1d + tsys.siso_tf1 + _sys = tsys.siso_tf1c + tsys.siso_tf1c + _sys = tsys.siso_tf1d + tsys.siso_tf1d + _sys = tsys.siso_tf2d + tsys.siso_tf2d + _sys = tsys.siso_tf1d + tsys.siso_tf3d with pytest.raises(ValueError): TransferFunction.__add__(tsys.siso_tf1c, tsys.siso_tf1d) @@ -261,22 +261,22 @@ def testAddition(self, tsys): TransferFunction.__add__(tsys.siso_tf1d, tsys.siso_tf2d) # State space + transfer function - sys = tsys.siso_ss1c + tsys.siso_tf1c - sys = tsys.siso_tf1c + tsys.siso_ss1c - sys = tsys.siso_ss1d + tsys.siso_tf1d - sys = tsys.siso_tf1d + tsys.siso_ss1d + _sys = tsys.siso_ss1c + tsys.siso_tf1c + _sys = tsys.siso_tf1c + tsys.siso_ss1c + _sys = tsys.siso_ss1d + tsys.siso_tf1d + _sys = tsys.siso_tf1d + tsys.siso_ss1d with pytest.raises(ValueError): TransferFunction.__add__(tsys.siso_tf1c, tsys.siso_ss1d) def testMultiplication(self, tsys): # State space multiplication - sys = tsys.siso_ss1 * tsys.siso_ss1d - sys = tsys.siso_ss1 * tsys.siso_ss1c - sys = tsys.siso_ss1c * tsys.siso_ss1 - sys = tsys.siso_ss1d * tsys.siso_ss1 - sys = tsys.siso_ss1c * tsys.siso_ss1c - sys = tsys.siso_ss1d * tsys.siso_ss1d - sys = tsys.siso_ss1d * tsys.siso_ss3d + _sys = tsys.siso_ss1 * tsys.siso_ss1d + _sys = tsys.siso_ss1 * tsys.siso_ss1c + _sys = tsys.siso_ss1c * tsys.siso_ss1 + _sys = tsys.siso_ss1d * tsys.siso_ss1 + _sys = tsys.siso_ss1c * tsys.siso_ss1c + _sys = tsys.siso_ss1d * tsys.siso_ss1d + _sys = tsys.siso_ss1d * tsys.siso_ss3d with pytest.raises(ValueError): StateSpace.__mul__(tsys.mimo_ss1c, tsys.mimo_ss1d) @@ -284,13 +284,13 @@ def testMultiplication(self, tsys): StateSpace.__mul__(tsys.mimo_ss1d, tsys.mimo_ss2d) # Transfer function multiplication - sys = tsys.siso_tf1 * tsys.siso_tf1d - sys = tsys.siso_tf1 * tsys.siso_tf1c - sys = tsys.siso_tf1c * tsys.siso_tf1 - sys = tsys.siso_tf1d * tsys.siso_tf1 - sys = tsys.siso_tf1c * tsys.siso_tf1c - sys = tsys.siso_tf1d * tsys.siso_tf1d - sys = tsys.siso_tf1d * tsys.siso_tf3d + _sys = tsys.siso_tf1 * tsys.siso_tf1d + _sys = tsys.siso_tf1 * tsys.siso_tf1c + _sys = tsys.siso_tf1c * tsys.siso_tf1 + _sys = tsys.siso_tf1d * tsys.siso_tf1 + _sys = tsys.siso_tf1c * tsys.siso_tf1c + _sys = tsys.siso_tf1d * tsys.siso_tf1d + _sys = tsys.siso_tf1d * tsys.siso_tf3d with pytest.raises(ValueError): TransferFunction.__mul__(tsys.siso_tf1c, tsys.siso_tf1d) @@ -298,10 +298,10 @@ def testMultiplication(self, tsys): TransferFunction.__mul__(tsys.siso_tf1d, tsys.siso_tf2d) # State space * transfer function - sys = tsys.siso_ss1c * tsys.siso_tf1c - sys = tsys.siso_tf1c * tsys.siso_ss1c - sys = tsys.siso_ss1d * tsys.siso_tf1d - sys = tsys.siso_tf1d * tsys.siso_ss1d + _sys = tsys.siso_ss1c * tsys.siso_tf1c + _sys = tsys.siso_tf1c * tsys.siso_ss1c + _sys = tsys.siso_ss1d * tsys.siso_tf1d + _sys = tsys.siso_tf1d * tsys.siso_ss1d with pytest.raises(ValueError): TransferFunction.__mul__(tsys.siso_tf1c, tsys.siso_ss1d) @@ -309,13 +309,13 @@ def testMultiplication(self, tsys): def testFeedback(self, tsys): # State space feedback - sys = feedback(tsys.siso_ss1, tsys.siso_ss1d) - sys = feedback(tsys.siso_ss1, tsys.siso_ss1c) - sys = feedback(tsys.siso_ss1c, tsys.siso_ss1) - sys = feedback(tsys.siso_ss1d, tsys.siso_ss1) - sys = feedback(tsys.siso_ss1c, tsys.siso_ss1c) - sys = feedback(tsys.siso_ss1d, tsys.siso_ss1d) - sys = feedback(tsys.siso_ss1d, tsys.siso_ss3d) + _sys = feedback(tsys.siso_ss1, tsys.siso_ss1d) + _sys = feedback(tsys.siso_ss1, tsys.siso_ss1c) + _sys = feedback(tsys.siso_ss1c, tsys.siso_ss1) + _sys = feedback(tsys.siso_ss1d, tsys.siso_ss1) + _sys = feedback(tsys.siso_ss1c, tsys.siso_ss1c) + _sys = feedback(tsys.siso_ss1d, tsys.siso_ss1d) + _sys = feedback(tsys.siso_ss1d, tsys.siso_ss3d) with pytest.raises(ValueError): feedback(tsys.mimo_ss1c, tsys.mimo_ss1d) @@ -323,13 +323,13 @@ def testFeedback(self, tsys): feedback(tsys.mimo_ss1d, tsys.mimo_ss2d) # Transfer function feedback - sys = feedback(tsys.siso_tf1, tsys.siso_tf1d) - sys = feedback(tsys.siso_tf1, tsys.siso_tf1c) - sys = feedback(tsys.siso_tf1c, tsys.siso_tf1) - sys = feedback(tsys.siso_tf1d, tsys.siso_tf1) - sys = feedback(tsys.siso_tf1c, tsys.siso_tf1c) - sys = feedback(tsys.siso_tf1d, tsys.siso_tf1d) - sys = feedback(tsys.siso_tf1d, tsys.siso_tf3d) + _sys = feedback(tsys.siso_tf1, tsys.siso_tf1d) + _sys = feedback(tsys.siso_tf1, tsys.siso_tf1c) + _sys = feedback(tsys.siso_tf1c, tsys.siso_tf1) + _sys = feedback(tsys.siso_tf1d, tsys.siso_tf1) + _sys = feedback(tsys.siso_tf1c, tsys.siso_tf1c) + _sys = feedback(tsys.siso_tf1d, tsys.siso_tf1d) + _sys = feedback(tsys.siso_tf1d, tsys.siso_tf3d) with pytest.raises(ValueError): feedback(tsys.siso_tf1c, tsys.siso_tf1d) @@ -337,10 +337,11 @@ def testFeedback(self, tsys): feedback(tsys.siso_tf1d, tsys.siso_tf2d) # State space, transfer function - sys = feedback(tsys.siso_ss1c, tsys.siso_tf1c) - sys = feedback(tsys.siso_tf1c, tsys.siso_ss1c) - sys = feedback(tsys.siso_ss1d, tsys.siso_tf1d) - sys = feedback(tsys.siso_tf1d, tsys.siso_ss1d) + _sys = feedback(tsys.siso_ss1c, tsys.siso_tf1c) + _sys = feedback(tsys.siso_tf1c, tsys.siso_ss1c) + _sys = feedback(tsys.siso_ss1d, tsys.siso_tf1d) + + _sys = feedback(tsys.siso_tf1d, tsys.siso_ss1d) with pytest.raises(ValueError): feedback(tsys.siso_tf1c, tsys.siso_ss1d) @@ -416,11 +417,11 @@ def test_sample_system_prewarp_warning(self, tsys, plantname, discretization_typ wwarp = 1 Ts = 0.1 with pytest.warns(UserWarning, match="prewarp_frequency ignored: incompatible conversion"): - plant_d_warped = plant.sample(Ts, discretization_type, prewarp_frequency=wwarp) + plant.sample(Ts, discretization_type, prewarp_frequency=wwarp) with pytest.warns(UserWarning, match="prewarp_frequency ignored: incompatible conversion"): - plant_d_warped = sample_system(plant, Ts, discretization_type, prewarp_frequency=wwarp) + sample_system(plant, Ts, discretization_type, prewarp_frequency=wwarp) with pytest.warns(UserWarning, match="prewarp_frequency ignored: incompatible conversion"): - plant_d_warped = c2d(plant, Ts, discretization_type, prewarp_frequency=wwarp) + c2d(plant, Ts, discretization_type, prewarp_frequency=wwarp) def test_sample_system_errors(self, tsys): # Check errors diff --git a/control/tests/docstrings_test.py b/control/tests/docstrings_test.py index aa243b607..496df42a3 100644 --- a/control/tests/docstrings_test.py +++ b/control/tests/docstrings_test.py @@ -143,14 +143,6 @@ def test_parameter_docs(module, prefix): continue # Don't fail on non-top-level functions without parameter lists - # TODO: may be able to delete this - if prefix != "" and inspect.getmodule(obj) != module and \ - doc is not None and doc["Parameters"] == [] and \ - doc["Returns"] == [] and doc["Yields"] == []: - fail_if_missing = False - else: - fail_if_missing = True - _info(f"Checking function {objname} against numpydoc", 2) _check_numpydoc_style(obj, doc) @@ -309,7 +301,7 @@ def test_deprecated_functions(module, prefix): # Get the docstring (skip w/ warning if there isn't one) if obj.__doc__ is None: - _warn(f"{objname} is missing docstring") + _warn(f"{obj} is missing docstring") continue else: docstring = inspect.getdoc(obj) @@ -320,13 +312,13 @@ def test_deprecated_functions(module, prefix): if ".. deprecated::" in doc_extended: # Make sure a FutureWarning is issued if not re.search("FutureWarning", source): - _fail(f"{objname} deprecated but does not issue " + _fail(f"{obj} deprecated but does not issue " "FutureWarning") else: if re.search(name + r"(\(\))? is deprecated", docstring) or \ re.search(name + r"(\(\))? is deprecated", source): _fail( - f"{objname} deprecated but with non-standard " + f"{obj} deprecated but with non-standard " "docs/warnings") # @@ -505,13 +497,6 @@ def test_iosys_attribute_lists(cls, ignore_future_warning): # Skip hidden and ignored attributes; methods checked elsewhere continue - # Get the object associated with this attribute - obj = getattr(cls, name, getattr(sys, name)) - if getattr(obj, '__module__', None): - objname = ".".join([obj.__module__.removeprefix("control."), name]) - else: - objname = name - # Try to find documentation in primary class if _check_parameter_docs( cls.__name__, name, docstring, fail_if_missing=False): @@ -539,7 +524,6 @@ def test_iosys_container_classes(cls): # Create a system that we can scan for attributes sys = cls(states=2, outputs=1, inputs=1) - docstring = inspect.getdoc(cls) with warnings.catch_warnings(): warnings.simplefilter('ignore') # debug via sphinx, not here doc = npd.FunctionDoc(cls) diff --git a/control/tests/flatsys_test.py b/control/tests/flatsys_test.py index 10c512bca..c53cf2e9c 100644 --- a/control/tests/flatsys_test.py +++ b/control/tests/flatsys_test.py @@ -543,7 +543,6 @@ def test_point_to_point_errors(self): x0 = [1, 0]; u0 = [0] xf = [0, 0]; uf = [0] Tf = 10 - T = np.linspace(0, Tf, 500) # Cost function timepts = np.linspace(0, Tf, 10) @@ -658,7 +657,6 @@ def test_solve_flat_ocp_errors(self): x0 = [1, 0]; u0 = [0] xf = [0, 0]; uf = [0] Tf = 10 - T = np.linspace(0, Tf, 500) # Cost function timepts = np.linspace(0, Tf, 10) diff --git a/control/tests/frd_test.py b/control/tests/frd_test.py index 54cc94b51..1b370c629 100644 --- a/control/tests/frd_test.py +++ b/control/tests/frd_test.py @@ -3,8 +3,6 @@ RvP, 4 Oct 2012 """ -import sys as pysys - import numpy as np import matplotlib.pyplot as plt import pytest @@ -13,7 +11,7 @@ from control.statesp import StateSpace from control.xferfcn import TransferFunction from control.frdata import frd, _convert_to_frd, FrequencyResponseData -from control import bdalg, evalfr, freqplot +from control import bdalg, freqplot from control.tests.conftest import slycotonly from control.exception import pandas_check @@ -182,11 +180,6 @@ def testFeedback(self, frd_fcn): f1.feedback().frequency_response(chkpts)[0], h1.feedback().frequency_response(chkpts)[0]) - def testFeedback2(self): - h2 = StateSpace([[-1.0, 0], [0, -2.0]], [[0.4], [0.1]], - [[1.0, 0], [0, 1]], [[0.0], [0.0]]) - # h2.feedback([[0.3, 0.2], [0.1, 0.1]]) - def testAppendSiso(self): # Create frequency responses d1 = np.array([1 + 2j, 1 - 2j, 1 + 4j, 1 - 4j, 1 + 6j, 1 - 6j]) @@ -362,7 +355,6 @@ def testAgainstOctave(self): np.array([[1.0, 0], [0, 0], [0, 1]]), np.eye(3), np.zeros((3, 2))) omega = np.logspace(-1, 2, 10) - chkpts = omega[::3] f1 = frd(sys, omega) np.testing.assert_array_almost_equal( (f1.frequency_response([1.0])[0] * @@ -379,13 +371,13 @@ def test_frequency_mismatch(self, recwarn): sys1 = frd([1, 2, 3], [4, 5, 6]) sys2 = frd([2, 3, 4], [5, 6, 7]) with pytest.raises(NotImplementedError): - sys = sys1 + sys2 + sys1 + sys2 # One frequency range is a subset of another sys1 = frd([1, 2, 3], [4, 5, 6]) sys2 = frd([2, 3], [4, 5]) with pytest.raises(NotImplementedError): - sys = sys1 + sys2 + sys1 + sys2 def test_size_mismatch(self): sys1 = frd(ct.rss(2, 2, 2), np.logspace(-1, 1, 10)) @@ -393,16 +385,16 @@ def test_size_mismatch(self): # Different number of inputs sys2 = frd(ct.rss(3, 1, 2), np.logspace(-1, 1, 10)) with pytest.raises(ValueError): - sys = sys1 + sys2 + sys1 + sys2 # Different number of outputs sys2 = frd(ct.rss(3, 2, 1), np.logspace(-1, 1, 10)) with pytest.raises(ValueError): - sys = sys1 + sys2 + sys1 + sys2 # Inputs and outputs don't match with pytest.raises(ValueError): - sys = sys2 * sys1 + sys2 * sys1 # Feedback mismatch with pytest.raises(ValueError): @@ -580,7 +572,6 @@ def test_truediv_mimo_siso(self): omega = np.logspace(-1, 1, 10) tf_mimo = TransferFunction([1], [1, 0]) * np.eye(2) frd_mimo = frd(tf_mimo, omega) - ss_mimo = ct.tf2ss(tf_mimo) tf_siso = TransferFunction([1], [1, 1]) frd_siso = frd(tf_siso, omega) expected = frd(tf_mimo.__truediv__(tf_siso), omega) @@ -609,7 +600,6 @@ def test_rtruediv_mimo_siso(self): ss_mimo = ct.tf2ss(tf_mimo) tf_siso = TransferFunction([1], [1, 1]) frd_siso = frd(tf_siso, omega) - ss_siso = ct.tf2ss(tf_siso) expected = frd(tf_siso.__rtruediv__(tf_mimo), omega) # Test division of MIMO FRD by SISO FRD @@ -801,9 +791,9 @@ def test_unrecognized_keyword(self): h = TransferFunction([1], [1, 2, 2]) omega = np.logspace(-1, 2, 10) with pytest.raises(TypeError, match="unrecognized keyword"): - sys = FrequencyResponseData(h, omega, unknown=None) + FrequencyResponseData(h, omega, unknown=None) with pytest.raises(TypeError, match="unrecognized keyword"): - sys = ct.frd(h, omega, unknown=None) + ct.frd(h, omega, unknown=None) def test_named_signals(): diff --git a/control/tests/freqplot_test.py b/control/tests/freqplot_test.py index 0b951865a..c0dfa4030 100644 --- a/control/tests/freqplot_test.py +++ b/control/tests/freqplot_test.py @@ -8,7 +8,6 @@ import pytest import control as ct -from control.tests.conftest import editsdefaults, slycotonly pytestmark = pytest.mark.usefixtures("mplcleanup") @@ -167,7 +166,7 @@ def test_line_styles(plt_fcn): sys3 = ct.tf([0.2, 0.1], [1, 0.1, 0.3, 0.1, 0.1], name='sys3') # Create a plot for the first system, with custom styles - lines_default = plt_fcn(sys1) + plt_fcn(sys1) # Now create a plot using *fmt customization lines_fmt = plt_fcn(sys2, None, 'r--') diff --git a/control/tests/interconnect_test.py b/control/tests/interconnect_test.py index d124859fc..aea3cbbc6 100644 --- a/control/tests/interconnect_test.py +++ b/control/tests/interconnect_test.py @@ -15,7 +15,6 @@ import pytest import numpy as np -import scipy as sp import math import control as ct @@ -46,15 +45,15 @@ def test_summing_junction(inputs, output, dimension, D): def test_summation_exceptions(): # Bad input description with pytest.raises(ValueError, match="could not parse input"): - sumblk = ct.summing_junction(np.pi, 'y') + ct.summing_junction(np.pi, 'y') # Bad output description with pytest.raises(ValueError, match="could not parse output"): - sumblk = ct.summing_junction('u', np.pi) + ct.summing_junction('u', np.pi) # Bad input dimension with pytest.raises(ValueError, match="unrecognized dimension"): - sumblk = ct.summing_junction('u', 'y', dimension=False) + ct.summing_junction('u', 'y', dimension=False) @pytest.mark.parametrize("dim", [1, 3]) @@ -346,7 +345,7 @@ def test_interconnect_exceptions(): # NonlinearIOSytem with pytest.raises(TypeError, match="unrecognized keyword"): - nlios = ct.NonlinearIOSystem( + ct.NonlinearIOSystem( None, lambda t, x, u, params: u*u, input_count=1, output_count=1) # Summing junction diff --git a/control/tests/iosys_test.py b/control/tests/iosys_test.py index 10eb7fb68..5d741ae83 100644 --- a/control/tests/iosys_test.py +++ b/control/tests/iosys_test.py @@ -18,7 +18,6 @@ import control as ct import control.flatsys as fs -from control.tests.conftest import slycotonly class TestIOSys: @@ -1412,7 +1411,7 @@ def test_operand_incompatible(self, Pout, Pin, C, op): C = ct.rss(2, 2, 3) with pytest.raises(ValueError, match="incompatible"): - PC = op(P, C) + op(P, C) @pytest.mark.parametrize( "C, op", [ @@ -1709,9 +1708,9 @@ def test_interconnect_unused_input(): with pytest.warns( UserWarning, match=r"Unused input\(s\) in InterconnectedSystem"): - h = ct.interconnect([g,s,k], - inputs=['r'], - outputs=['y']) + ct.interconnect([g,s,k], + inputs=['r'], + outputs=['y']) with warnings.catch_warnings(): # no warning if output explicitly ignored, various argument forms @@ -1719,45 +1718,43 @@ def test_interconnect_unused_input(): # strip out matrix warnings warnings.filterwarnings("ignore", "the matrix subclass", category=PendingDeprecationWarning) - h = ct.interconnect([g,s,k], - inputs=['r'], - outputs=['y'], - ignore_inputs=['n']) + ct.interconnect([g,s,k], + inputs=['r'], + outputs=['y'], + ignore_inputs=['n']) - h = ct.interconnect([g,s,k], - inputs=['r'], - outputs=['y'], - ignore_inputs=['s.n']) + ct.interconnect([g,s,k], + inputs=['r'], + outputs=['y'], + ignore_inputs=['s.n']) # no warning if auto-connect disabled - h = ct.interconnect([g,s,k], - connections=False) + ct.interconnect([g,s,k], + connections=False) # warn if explicity ignored input in fact used with pytest.warns( UserWarning, - match=r"Input\(s\) specified as ignored is \(are\) used:") \ - as record: - h = ct.interconnect([g,s,k], - inputs=['r'], - outputs=['y'], - ignore_inputs=['u','n']) + match=r"Input\(s\) specified as ignored is \(are\) used:"): + ct.interconnect([g,s,k], + inputs=['r'], + outputs=['y'], + ignore_inputs=['u','n']) with pytest.warns( UserWarning, - match=r"Input\(s\) specified as ignored is \(are\) used:") \ - as record: - h = ct.interconnect([g,s,k], - inputs=['r'], - outputs=['y'], - ignore_inputs=['k.e','n']) + match=r"Input\(s\) specified as ignored is \(are\) used:"): + ct.interconnect([g,s,k], + inputs=['r'], + outputs=['y'], + ignore_inputs=['k.e','n']) # error if ignored signal doesn't exist with pytest.raises(ValueError): - h = ct.interconnect([g,s,k], - inputs=['r'], - outputs=['y'], - ignore_inputs=['v']) + ct.interconnect([g,s,k], + inputs=['r'], + outputs=['y'], + ignore_inputs=['v']) def test_interconnect_unused_output(): @@ -1779,10 +1776,10 @@ def test_interconnect_unused_output(): with pytest.warns( UserWarning, - match=r"Unused output\(s\) in InterconnectedSystem:") as record: - h = ct.interconnect([g,s,k], - inputs=['r'], - outputs=['y']) + match=r"Unused output\(s\) in InterconnectedSystem:"): + ct.interconnect([g,s,k], + inputs=['r'], + outputs=['y']) # no warning if output explicitly ignored @@ -1791,43 +1788,43 @@ def test_interconnect_unused_output(): # strip out matrix warnings warnings.filterwarnings("ignore", "the matrix subclass", category=PendingDeprecationWarning) - h = ct.interconnect([g,s,k], - inputs=['r'], - outputs=['y'], - ignore_outputs=['dy']) + ct.interconnect([g,s,k], + inputs=['r'], + outputs=['y'], + ignore_outputs=['dy']) - h = ct.interconnect([g,s,k], - inputs=['r'], - outputs=['y'], - ignore_outputs=['g.dy']) + ct.interconnect([g,s,k], + inputs=['r'], + outputs=['y'], + ignore_outputs=['g.dy']) # no warning if auto-connect disabled - h = ct.interconnect([g,s,k], - connections=False) + ct.interconnect([g,s,k], + connections=False) # warn if explicity ignored output in fact used with pytest.warns( UserWarning, match=r"Output\(s\) specified as ignored is \(are\) used:"): - h = ct.interconnect([g,s,k], - inputs=['r'], - outputs=['y'], - ignore_outputs=['dy','u']) + ct.interconnect([g,s,k], + inputs=['r'], + outputs=['y'], + ignore_outputs=['dy','u']) with pytest.warns( UserWarning, match=r"Output\(s\) specified as ignored is \(are\) used:"): - h = ct.interconnect([g,s,k], - inputs=['r'], - outputs=['y'], - ignore_outputs=['dy', ('k.u')]) + ct.interconnect([g,s,k], + inputs=['r'], + outputs=['y'], + ignore_outputs=['dy', ('k.u')]) # error if ignored signal doesn't exist with pytest.raises(ValueError): - h = ct.interconnect([g,s,k], - inputs=['r'], - outputs=['y'], - ignore_outputs=['v']) + ct.interconnect([g,s,k], + inputs=['r'], + outputs=['y'], + ignore_outputs=['v']) def test_interconnect_add_unused(): @@ -1900,11 +1897,11 @@ def test_input_output_broadcasting(): # Specify only some of the initial conditions with pytest.warns(UserWarning, match="X0 too short; padding"): - resp_short = ct.input_output_response(sys, T, [U[0], [0, 1]], [X0, 1]) + ct.input_output_response(sys, T, [U[0], [0, 1]], [X0, 1]) # Make sure that inconsistent settings don't work with pytest.raises(ValueError, match="inconsistent"): - resp_bad = ct.input_output_response( + ct.input_output_response( sys, T, (U[0, :], U[:2, :-1]), [X0, P0]) @pytest.mark.parametrize("nstates, ninputs, noutputs", [ diff --git a/control/tests/kwargs_test.py b/control/tests/kwargs_test.py index f515bc52f..566b35a28 100644 --- a/control/tests/kwargs_test.py +++ b/control/tests/kwargs_test.py @@ -13,9 +13,10 @@ import inspect import warnings -import matplotlib.pyplot as plt import pytest +import numpy as np + import control import control.flatsys import control.tests.descfcn_test as descfcn_test @@ -343,9 +344,6 @@ def test_response_plot_kwargs(data_fcn, plot_fcn, mimo): 'PoleZeroList.plot': test_response_plot_kwargs, 'InterconnectedSystem.__init__': interconnect_test.test_interconnect_exceptions, - 'StateSpace.__init__': - interconnect_test.test_interconnect_exceptions, - 'StateSpace.sample': test_unrecognized_kwargs, 'NonlinearIOSystem.__init__': interconnect_test.test_interconnect_exceptions, 'StateSpace.__init__': test_unrecognized_kwargs, diff --git a/control/tests/lti_test.py b/control/tests/lti_test.py index 9edf09013..17dc7796e 100644 --- a/control/tests/lti_test.py +++ b/control/tests/lti_test.py @@ -12,8 +12,6 @@ from control.lti import LTI, bandwidth, damp, dcgain, evalfr, poles, zeros from control.tests.conftest import slycotonly -from .conftest import editsdefaults - class TestLTI: @pytest.mark.parametrize("fun, args", [ @@ -26,10 +24,10 @@ def test_poles(self, fun, args): np.testing.assert_allclose(poles(sys), 42) with pytest.raises(AttributeError, match="no attribute 'pole'"): - pole_list = sys.pole() + sys.pole() with pytest.raises(AttributeError, match="no attribute 'pole'"): - pole_list = ct.pole(sys) + ct.pole(sys) @pytest.mark.parametrize("fun, args", [ [tf, (126, [-1, 42])], @@ -41,10 +39,10 @@ def test_zeros(self, fun, args): np.testing.assert_allclose(zeros(sys), 42) with pytest.raises(AttributeError, match="no attribute 'zero'"): - zero_list = sys.zero() + sys.zero() with pytest.raises(AttributeError, match="no attribute 'zero'"): - zero_list = ct.zero(sys) + ct.zero(sys) def test_issiso(self): assert issiso(1) @@ -295,7 +293,7 @@ def test_squeeze_exceptions(self, fcn): sys = fcn(ct.rss(2, 1, 1)) with pytest.raises(ValueError, match="unknown squeeze value"): - resp = sys.frequency_response([1], squeeze='siso') + sys.frequency_response([1], squeeze='siso') with pytest.raises(ValueError, match="unknown squeeze value"): sys([1j], squeeze='siso') with pytest.raises(ValueError, match="unknown squeeze value"): diff --git a/control/tests/matlab2_test.py b/control/tests/matlab2_test.py index 4d135e33e..f8b0d2b40 100644 --- a/control/tests/matlab2_test.py +++ b/control/tests/matlab2_test.py @@ -16,7 +16,6 @@ from control.matlab import ss, step, impulse, initial, lsim, dcgain, ss2tf from control.timeresp import _check_convert_array -from control.tests.conftest import slycotonly class TestControlMatlab: diff --git a/control/tests/matlab_test.py b/control/tests/matlab_test.py index b7e0d25d2..c6a45e2a2 100644 --- a/control/tests/matlab_test.py +++ b/control/tests/matlab_test.py @@ -130,33 +130,33 @@ def mimo(self): def testParallel(self, siso): """Call parallel()""" - sys1 = parallel(siso.ss1, siso.ss2) - sys1 = parallel(siso.ss1, siso.tf2) - sys1 = parallel(siso.tf1, siso.ss2) - sys1 = parallel(1, siso.ss2) - sys1 = parallel(1, siso.tf2) - sys1 = parallel(siso.ss1, 1) - sys1 = parallel(siso.tf1, 1) + _sys1 = parallel(siso.ss1, siso.ss2) + _sys1 = parallel(siso.ss1, siso.tf2) + _sys1 = parallel(siso.tf1, siso.ss2) + _sys1 = parallel(1, siso.ss2) + _sys1 = parallel(1, siso.tf2) + _sys1 = parallel(siso.ss1, 1) + _sys1 = parallel(siso.tf1, 1) def testSeries(self, siso): """Call series()""" - sys1 = series(siso.ss1, siso.ss2) - sys1 = series(siso.ss1, siso.tf2) - sys1 = series(siso.tf1, siso.ss2) - sys1 = series(1, siso.ss2) - sys1 = series(1, siso.tf2) - sys1 = series(siso.ss1, 1) - sys1 = series(siso.tf1, 1) + _sys1 = series(siso.ss1, siso.ss2) + _sys1 = series(siso.ss1, siso.tf2) + _sys1 = series(siso.tf1, siso.ss2) + _sys1 = series(1, siso.ss2) + _sys1 = series(1, siso.tf2) + _sys1 = series(siso.ss1, 1) + _sys1 = series(siso.tf1, 1) def testFeedback(self, siso): """Call feedback()""" - sys1 = feedback(siso.ss1, siso.ss2) - sys1 = feedback(siso.ss1, siso.tf2) - sys1 = feedback(siso.tf1, siso.ss2) - sys1 = feedback(1, siso.ss2) - sys1 = feedback(1, siso.tf2) - sys1 = feedback(siso.ss1, 1) - sys1 = feedback(siso.tf1, 1) + _sys1 = feedback(siso.ss1, siso.ss2) + _sys1 = feedback(siso.ss1, siso.tf2) + _sys1 = feedback(siso.tf1, siso.ss2) + _sys1 = feedback(1, siso.ss2) + _sys1 = feedback(1, siso.tf2) + _sys1 = feedback(siso.ss1, 1) + _sys1 = feedback(siso.tf1, 1) def testPoleZero(self, siso): """Call pole() and zero()""" @@ -582,10 +582,11 @@ def testOpers(self, siso): # siso.tf1 / siso.ss2 def testUnwrap(self): - """Call unwrap()""" + # control.matlab.unwrap phase = np.array(range(1, 100)) / 10. wrapped = phase % (2 * np.pi) unwrapped = unwrap(wrapped) + np.testing.assert_array_almost_equal(phase, unwrapped) def testSISOssdata(self, siso): """Call ssdata() diff --git a/control/tests/modelsimp_test.py b/control/tests/modelsimp_test.py index 7dcda6296..e09446073 100644 --- a/control/tests/modelsimp_test.py +++ b/control/tests/modelsimp_test.py @@ -3,7 +3,6 @@ RMM, 30 Mar 2011 (based on TestModelSimp from v0.4a) """ -import math import warnings import numpy as np @@ -124,6 +123,7 @@ def testMarkovSignature(self): inp = np.array([1, 2]) outp = np.array([2, 4]) mrk = markov(outp, inp, 1, transpose=False) + np.testing.assert_almost_equal(mrk, 2.) # Test mimo example # Mechanical Vibrations: Theory and Application, SI Edition, 1st ed. diff --git a/control/tests/namedio_test.py b/control/tests/namedio_test.py index 34feb5b35..ad74d27ba 100644 --- a/control/tests/namedio_test.py +++ b/control/tests/namedio_test.py @@ -8,7 +8,6 @@ created for that purpose. """ -import re from copy import copy import warnings @@ -285,7 +284,7 @@ def test_duplicate_sysname(): # strip out matrix warnings warnings.filterwarnings("ignore", "the matrix subclass", category=PendingDeprecationWarning) - res = sys * sys + sys * sys # Generate a warning if the system is named sys = ct.rss(4, 1, 1) @@ -293,7 +292,7 @@ def test_duplicate_sysname(): sys.updfcn, sys.outfcn, inputs=sys.ninputs, outputs=sys.noutputs, states=sys.nstates, name='sys') with pytest.warns(UserWarning, match="duplicate object found"): - res = sys * sys + sys * sys # Finding signals @@ -332,10 +331,10 @@ def test_find_signals(): # Invalid signal names def test_invalid_signal_names(): with pytest.raises(ValueError, match="invalid signal name"): - sys = ct.rss(4, inputs="input.signal", outputs=1) + ct.rss(4, inputs="input.signal", outputs=1) with pytest.raises(ValueError, match="invalid system name"): - sys = ct.rss(4, inputs=1, outputs=1, name="system.subsys") + ct.rss(4, inputs=1, outputs=1, name="system.subsys") # Negative system spect @@ -367,8 +366,6 @@ def test_negative_system_spec(): # Named signal representations def test_named_signal_repr(): - from numpy import array - from ..iosys import NamedSignal sys = ct.rss( states=2, inputs=['u1', 'u2'], outputs=['y1', 'y2'], state_prefix='xi') @@ -376,6 +373,9 @@ def test_named_signal_repr(): for signal in ['inputs', 'outputs', 'states']: sig_orig = getattr(resp, signal) - sig_eval = eval(repr(sig_orig)) + sig_eval = eval(repr(sig_orig), + None, + {'array': np.array, + 'NamedSignal': ct.NamedSignal}) assert sig_eval.signal_labels == sig_orig.signal_labels assert sig_eval.trace_labels == sig_orig.trace_labels diff --git a/control/tests/nlsys_test.py b/control/tests/nlsys_test.py index 4b1a235c0..b14a619e0 100644 --- a/control/tests/nlsys_test.py +++ b/control/tests/nlsys_test.py @@ -98,7 +98,7 @@ def test_nlsys_impulse(): # Impulse_response (not implemented) with pytest.raises(ValueError, match="system must be LTI"): - resp_nl = ct.impulse_response(sys_nl, timepts) + ct.impulse_response(sys_nl, timepts) # Test nonlinear systems that are missing inputs or outputs diff --git a/control/tests/nyquist_test.py b/control/tests/nyquist_test.py index 3b27ee27c..42bb210c4 100644 --- a/control/tests/nyquist_test.py +++ b/control/tests/nyquist_test.py @@ -436,7 +436,7 @@ def test_nyquist_legacy(): sys = (0.02 * s**3 - 0.1 * s) / (s**4 + s**3 + s**2 + 0.25 * s + 0.04) with pytest.warns(UserWarning, match="indented contour may miss"): - response = ct.nyquist_plot(sys) + ct.nyquist_plot(sys) def test_discrete_nyquist(): # TODO: add tests to make sure plots make sense @@ -512,7 +512,7 @@ def test_nyquist_frd(): # Computing Nyquist response w/ different frequencies OK if given as a list nyqresp = ct.nyquist_response([sys1, sys2]) - cplt = nyqresp.plot() + nyqresp.plot() warnings.resetwarnings() @@ -522,7 +522,7 @@ def test_no_indent_pole(): sys = ((1 + 5/s)/(1 + 0.5/s))**2 # Double-Lag-Compensator with pytest.raises(RuntimeError, match="evaluate at a pole"): - resp = ct.nyquist_response( + ct.nyquist_response( sys, warn_encirclements=False, indent_direction='none') diff --git a/control/tests/optimal_test.py b/control/tests/optimal_test.py index 4ea436515..fa8fcb941 100644 --- a/control/tests/optimal_test.py +++ b/control/tests/optimal_test.py @@ -81,7 +81,7 @@ def test_finite_horizon_simple(method): sys, time, x0, cost, constraints, squeeze=True, trajectory_method=method, terminal_cost=cost) # include to match MPT3 formulation - t, u_openloop = res.time, res.inputs + _t, u_openloop = res.time, res.inputs np.testing.assert_almost_equal( u_openloop, [-1, -1, 0.1393, 0.3361, -5.204e-16], decimal=4) @@ -186,7 +186,6 @@ def test_mpc_iosystem_aircraft(): # compute the steady state values for a particular value of the input ud = np.array([0.8, -0.3]) xd = np.linalg.inv(np.eye(5) - A) @ B @ ud - yd = C @ xd # provide constraints on the system signals constraints = [opt.input_range_constraint(sys, [-5, -6], [5, 6])] @@ -264,7 +263,7 @@ def test_mpc_iosystem_continuous(): # Continuous time MPC controller not implemented with pytest.raises(NotImplementedError): - ctrl = opt.create_mpc_iosystem(sys, T, cost) + opt.create_mpc_iosystem(sys, T, cost) # Test various constraint combinations; need to use a somewhat convoluted @@ -315,7 +314,7 @@ def test_constraint_specification(constraint_list): # Compute optimal control and compare against MPT3 solution x0 = [4, 0] res = optctrl.compute_trajectory(x0, squeeze=True) - t, u_openloop = res.time, res.inputs + _t, u_openloop = res.time, res.inputs np.testing.assert_almost_equal( u_openloop, [-1, -1, 0.1393, 0.3361, -5.204e-16], decimal=3) @@ -352,7 +351,7 @@ def test_terminal_constraints(sys_args): # Find a path to the origin x0 = np.array([4, 3]) res = optctrl.compute_trajectory(x0, squeeze=True, return_x=True) - t, u1, x1 = res.time, res.inputs, res.states + _t, u1, x1 = res.time, res.inputs, res.states # Bug prior to SciPy 1.6 will result in incorrect results if NumpyVersion(sp.__version__) < '1.6.0': @@ -401,7 +400,7 @@ def test_terminal_constraints(sys_args): # Find a path to the origin res = optctrl.compute_trajectory( x0, squeeze=True, return_x=True, initial_guess=u1) - t, u2, x2 = res.time, res.inputs, res.states + _t, u2, x2 = res.time, res.inputs, res.states # Not all configurations are able to converge (?) if res.success: @@ -416,7 +415,7 @@ def test_terminal_constraints(sys_args): optctrl = opt.OptimalControlProblem( sys, time, cost, constraints, terminal_constraints=final_point) res = optctrl.compute_trajectory(x0, squeeze=True, return_x=True) - t, u3, x3 = res.time, res.inputs, res.states + _t, u3, x3 = res.time, res.inputs, res.states # Check the answers only if we converged if res.success: @@ -448,7 +447,7 @@ def test_optimal_logging(capsys): # Solve it, with logging turned on (with warning due to mixed constraints) with pytest.warns(sp.optimize.OptimizeWarning, match="Equality and inequality .* same element"): - res = opt.solve_optimal_trajectory( + opt.solve_optimal_trajectory( sys, time, x0, cost, input_constraint, terminal_cost=cost, terminal_constraints=state_constraint, log=True) @@ -513,21 +512,21 @@ def test_ocp_argument_errors(): # Trajectory constraints not in the right form with pytest.raises(TypeError, match="constraints must be a list"): - res = opt.solve_optimal_trajectory(sys, time, x0, cost, np.eye(2)) + opt.solve_optimal_trajectory(sys, time, x0, cost, np.eye(2)) # Terminal constraints not in the right form with pytest.raises(TypeError, match="constraints must be a list"): - res = opt.solve_optimal_trajectory( + opt.solve_optimal_trajectory( sys, time, x0, cost, constraints, terminal_constraints=np.eye(2)) # Initial guess in the wrong shape with pytest.raises(ValueError, match="initial guess is the wrong shape"): - res = opt.solve_optimal_trajectory( + opt.solve_optimal_trajectory( sys, time, x0, cost, constraints, initial_guess=np.zeros((4,1,1))) # Unrecognized arguments with pytest.raises(TypeError, match="unrecognized keyword"): - res = opt.solve_optimal_trajectory( + opt.solve_optimal_trajectory( sys, time, x0, cost, constraints, terminal_constraint=None) with pytest.raises(TypeError, match="unrecognized keyword"): @@ -541,21 +540,21 @@ def test_ocp_argument_errors(): # Unrecognized trajectory constraint type constraints = [(None, np.eye(3), [0, 0, 0], [0, 0, 0])] with pytest.raises(TypeError, match="unknown trajectory constraint type"): - res = opt.solve_optimal_trajectory( + opt.solve_optimal_trajectory( sys, time, x0, cost, trajectory_constraints=constraints) # Unrecognized terminal constraint type with pytest.raises(TypeError, match="unknown terminal constraint type"): - res = opt.solve_optimal_trajectory( + opt.solve_optimal_trajectory( sys, time, x0, cost, terminal_constraints=constraints) # Discrete time system checks: solve_ivp keywords not allowed sys = ct.rss(2, 1, 1, dt=True) with pytest.raises(TypeError, match="solve_ivp method, kwargs not allowed"): - res = opt.solve_optimal_trajectory( + opt.solve_optimal_trajectory( sys, time, x0, cost, solve_ivp_method='LSODA') with pytest.raises(TypeError, match="solve_ivp method, kwargs not allowed"): - res = opt.solve_optimal_trajectory( + opt.solve_optimal_trajectory( sys, time, x0, cost, solve_ivp_kwargs={'eps': 0.1}) @@ -629,7 +628,7 @@ def test_equality_constraints(): # Find a path to the origin x0 = np.array([4, 3]) res = optctrl.compute_trajectory(x0, squeeze=True, return_x=True) - t, u1, x1 = res.time, res.inputs, res.states + _t, u1, x1 = res.time, res.inputs, res.states # Bug prior to SciPy 1.6 will result in incorrect results if NumpyVersion(sp.__version__) < '1.6.0': @@ -649,7 +648,7 @@ def final_point_eval(x, u): # Find a path to the origin x0 = np.array([4, 3]) res = optctrl.compute_trajectory(x0, squeeze=True, return_x=True) - t, u2, x2 = res.time, res.inputs, res.states + _t, u2, x2 = res.time, res.inputs, res.states np.testing.assert_almost_equal(x2[:,-1], 0, decimal=4) np.testing.assert_almost_equal(u1, u2) np.testing.assert_almost_equal(x1, x2) @@ -732,8 +731,6 @@ def vehicle_output(t, x, u, params): initial_guess[0, :] = (xf[0] - x0[0]) / Tf # Steering = rate required to turn to proper slope in first segment - straight_seg_length = timepts[-2] - timepts[1] - curved_seg_length = (Tf - straight_seg_length)/2 approximate_angle = math.atan2(xf[1] - x0[1], xf[0] - x0[0]) initial_guess[1, 0] = approximate_angle / (timepts[1] - timepts[0]) initial_guess[1, -1] = -approximate_angle / (timepts[-1] - timepts[-2]) @@ -794,7 +791,7 @@ def test_oep_argument_errors(): # Unrecognized arguments with pytest.raises(TypeError, match="unrecognized keyword"): - res = opt.solve_optimal_estimate(sys, timepts, Y, U, cost, unknown=True) + opt.solve_optimal_estimate(sys, timepts, Y, U, cost, unknown=True) with pytest.raises(TypeError, match="unrecognized keyword"): oep = opt.OptimalEstimationProblem(sys, timepts, cost, unknown=True) @@ -807,4 +804,4 @@ def test_oep_argument_errors(): # Incorrect number of signals with pytest.raises(ValueError, match="incorrect length"): oep = opt.OptimalEstimationProblem(sys, timepts, cost) - mhe = oep.create_mhe_iosystem(estimate_labels=['x1', 'x2', 'x3']) + oep.create_mhe_iosystem(estimate_labels=['x1', 'x2', 'x3']) diff --git a/control/tests/phaseplot_test.py b/control/tests/phaseplot_test.py index e5954280d..fc4edcbea 100644 --- a/control/tests/phaseplot_test.py +++ b/control/tests/phaseplot_test.py @@ -20,7 +20,6 @@ import control as ct import control.phaseplot as pp from control import phase_plot -from control.tests.conftest import mplcleanup # Legacy tests @@ -124,11 +123,11 @@ def test_helper_functions(func, args, kwargs): sys = ct.nlsys( lambda t, x, u, params: [x[0] - 3*x[1], -3*x[0] + x[1]], states=2, inputs=0) - out = func(sys, [-1, 1, -1, 1], *args, **kwargs) + _out = func(sys, [-1, 1, -1, 1], *args, **kwargs) # Test with function rhsfcn = lambda t, x: sys.dynamics(t, x, 0, {}) - out = func(rhsfcn, [-1, 1, -1, 1], *args, **kwargs) + _out = func(rhsfcn, [-1, 1, -1, 1], *args, **kwargs) @pytest.mark.usefixtures('mplcleanup') diff --git a/control/tests/pzmap_test.py b/control/tests/pzmap_test.py index 438732b84..64bbdee3e 100644 --- a/control/tests/pzmap_test.py +++ b/control/tests/pzmap_test.py @@ -111,10 +111,10 @@ def test_pzmap_raises(): sys1 = ct.rss(2, 1, 1) sys2 = sys1.sample(0.1) with pytest.raises(ValueError, match="incompatible time bases"): - pzdata = ct.pole_zero_plot([sys1, sys2], grid=True) + ct.pole_zero_plot([sys1, sys2], grid=True) with pytest.warns(UserWarning, match="axis already exists"): - fig, ax = plt.figure(), plt.axes() + _fig, ax = plt.figure(), plt.axes() ct.pole_zero_plot(sys1, ax=ax, grid='empty') diff --git a/control/tests/rlocus_test.py b/control/tests/rlocus_test.py index 2e74f8649..4d3a08206 100644 --- a/control/tests/rlocus_test.py +++ b/control/tests/rlocus_test.py @@ -134,7 +134,7 @@ def test_root_locus_zoom(self): ax_rlocus.set_xlim((-10.813628105112421, 14.760795435937652)) ax_rlocus.set_ylim((-35.61713798641108, 33.879716621220311)) plt.get_current_fig_manager().toolbar.mode = 'zoom rect' - _RLClickDispatcher(event, system, fig, ax_rlocus, '-') + _RLClickDispatcher(event, system, fig, ax_rlocus, '-') # noqa: F821 zoom_x = ax_rlocus.lines[-2].get_data()[0][0:5] zoom_y = ax_rlocus.lines[-2].get_data()[1][0:5] @@ -161,7 +161,6 @@ def test_rlocus_default_wn(self): # that will take a long time to do the calculation (minutes). # import scipy as sp - import signal # Define a system that exhibits this behavior sys = ct.tf(*sp.signal.zpk2tf( diff --git a/control/tests/statefbk_test.py b/control/tests/statefbk_test.py index ebf531546..3f4b4849a 100644 --- a/control/tests/statefbk_test.py +++ b/control/tests/statefbk_test.py @@ -860,7 +860,7 @@ def test_lqr_integral_discrete(self): K, _, _ = ct.lqr( sys, np.eye(sys.nstates + nintegrators), np.eye(sys.ninputs), integral_action=C_int) - Kp, Ki = K[:, :sys.nstates], K[:, sys.nstates:] + Kp, _Ki = K[:, :sys.nstates], K[:, sys.nstates:] # Create an I/O system for the controller ctrl, clsys = ct.create_statefbk_iosystem( @@ -1237,20 +1237,10 @@ def test_create_statefbk_errors(): def test_create_statefbk_params(unicycle): - # Speeds and angles at which to compute the gains - speeds = [1, 5, 10] - angles = np.linspace(0, pi/2, 4) - points = list(itertools.product(speeds, angles)) - - # Gains for each speed (using LQR controller) Q = np.identity(unicycle.nstates) R = np.identity(unicycle.ninputs) gain, _, _ = ct.lqr(unicycle.linearize([0, 0, 0], [5, 0]), Q, R) - # - # Schedule on desired speed and angle - # - # Create a linear controller ctrl, clsys = ct.create_statefbk_iosystem(unicycle, gain) assert [k for k in ctrl.params.keys()] == [] diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index 2f3aa512f..0806696f0 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -8,7 +8,6 @@ """ import operator -import platform import numpy as np import pytest @@ -23,10 +22,7 @@ from control.statesp import StateSpace, _convert_to_statespace, \ _rss_generate, _statesp_defaults, drss, linfnorm, rss, ss, tf2ss from control.xferfcn import TransferFunction, ss2tf - -from .conftest import assert_tf_close_coeff, editsdefaults, \ - ignore_future_warning, slycotonly - +from .conftest import assert_tf_close_coeff, slycotonly class TestStateSpace: """Tests for the StateSpace class.""" @@ -144,7 +140,7 @@ def test_constructor(self, sys322ABCD, dt, argfun): def test_constructor_invalid(self, args, exc, errmsg): """Test invalid input to StateSpace() constructor""" - with pytest.raises(exc, match=errmsg) as w: + with pytest.raises(exc, match=errmsg): StateSpace(*args) with pytest.raises(exc, match=errmsg): ss(*args) diff --git a/control/tests/stochsys_test.py b/control/tests/stochsys_test.py index 4c0d9665d..6fc87461b 100644 --- a/control/tests/stochsys_test.py +++ b/control/tests/stochsys_test.py @@ -6,7 +6,7 @@ import control as ct import control.optimal as opt -from control import lqe, dlqe, rss, drss, tf, ss, ControlArgument, slycot_check +from control import lqe, dlqe, rss, tf, ControlArgument, slycot_check from math import log, pi # Utility function to check LQE answer @@ -225,26 +225,25 @@ def test_estimator_iosys_ctime(sys_args): def test_estimator_errors(): sys = ct.drss(4, 2, 2, strictly_proper=True) - P0 = np.eye(sys.nstates) QN = np.eye(sys.ninputs) RN = np.eye(sys.noutputs) with pytest.raises(TypeError, match="unrecognized keyword"): - estim = ct.create_estimator_iosystem(sys, QN, RN, unknown=True) + ct.create_estimator_iosystem(sys, QN, RN, unknown=True) with pytest.raises(ct.ControlArgument, match=".* system must be a linear"): sys_tf = ct.tf([1], [1, 1], dt=True) - estim = ct.create_estimator_iosystem(sys_tf, QN, RN) + ct.create_estimator_iosystem(sys_tf, QN, RN) with pytest.raises(ValueError, match="output must be full state"): C = np.eye(2, 4) - estim = ct.create_estimator_iosystem(sys, QN, RN, C=C) + ct.create_estimator_iosystem(sys, QN, RN, C=C) with pytest.raises(ValueError, match="output is the wrong size"): sys_fs = ct.drss(4, 4, 2, strictly_proper=True) sys_fs.C = np.eye(4) C = np.eye(1, 4) - estim = ct.create_estimator_iosystem(sys_fs, QN, RN, C=C) + ct.create_estimator_iosystem(sys_fs, QN, RN, C=C) def test_white_noise(): @@ -430,7 +429,6 @@ def test_mhe(): V = np.array( [0 if i % 2 == 1 else 1 if i % 4 == 0 else -1 for i, t in enumerate(timepts)]).reshape(1, -1) * 0.1 - W = np.sin(timepts / dt) * 1e-3 # Create a moving horizon estimator traj_cost = opt.gaussian_likelihood_cost(sys, Rv, Rw) @@ -482,7 +480,6 @@ def test_indices(ctrl_indices, dist_indices): sysm = ct.ss(sys.A, sys.B[:, ctrl_idx], sys.C, sys.D[:, ctrl_idx]) # Set the simulation time based on the slowest system pole - from math import log T = 10 # Generate a system response with no disturbances diff --git a/control/tests/timeplot_test.py b/control/tests/timeplot_test.py index 9525c7e02..888ff9080 100644 --- a/control/tests/timeplot_test.py +++ b/control/tests/timeplot_test.py @@ -7,8 +7,7 @@ import pytest import control as ct -from control.tests.conftest import mplcleanup, slycotonly - +from control.tests.conftest import slycotonly # Detailed test of (almost) all functionality # @@ -312,15 +311,15 @@ def test_combine_time_responses(): with pytest.raises(ValueError, match="must have the same number"): resp = ct.step_response(ct.rss(4, 2, 3), timepts) - combresp = ct.combine_time_responses([resp1, resp]) + ct.combine_time_responses([resp1, resp]) with pytest.raises(ValueError, match="trace labels does not match"): - combresp = ct.combine_time_responses( + ct.combine_time_responses( [resp1, resp2], trace_labels=["T1", "T2", "T3"]) with pytest.raises(ValueError, match="must have the same time"): resp = ct.step_response(ct.rss(4, 2, 3), timepts/2) - combresp6 = ct.combine_time_responses([resp1, resp]) + ct.combine_time_responses([resp1, resp]) @pytest.mark.parametrize("resp_fcn", [ @@ -415,13 +414,10 @@ def test_timeplot_trace_labels(resp_fcn): # Figure out the expected shape of the system match resp_fcn: case ct.step_response | ct.impulse_response: - shape = (2, 2) kwargs = {} case ct.initial_response: - shape = (2, 1) kwargs = {} case ct.forced_response | ct.input_output_response: - shape = (4, 1) # outputs and inputs both plotted T = np.linspace(0, 10) U = [np.sin(T), np.cos(T)] kwargs = {'T': T, 'U': U} diff --git a/control/tests/timeresp_test.py b/control/tests/timeresp_test.py index aa4987209..8bbd27d73 100644 --- a/control/tests/timeresp_test.py +++ b/control/tests/timeresp_test.py @@ -5,7 +5,6 @@ import numpy as np import pytest -import scipy as sp import control as ct from control import StateSpace, TransferFunction, c2d, isctime, ss2tf, tf2ss @@ -1198,7 +1197,6 @@ def test_squeeze_0_8_4(self, nstate, nout, ninp, squeeze, shape): # Generate system, time, and input vectors sys = ct.rss(nstate, nout, ninp, strictly_proper=True) tvec = np.linspace(0, 1, 8) - uvec =np.ones((sys.ninputs, 1)) @ np.reshape(np.sin(tvec), (1, 8)) _, yvec = ct.initial_response(sys, tvec, 1, squeeze=squeeze) assert yvec.shape == shape @@ -1323,7 +1321,7 @@ def test_no_pandas(): # Convert to pandas with pytest.raises(ImportError, match="pandas"): - df = resp.to_pandas() + resp.to_pandas() # https://github.com/python-control/python-control/issues/1014 @@ -1429,40 +1427,40 @@ def test_timeresp_aliases(): # Aliases resp_short = ct.input_output_response(sys, timepts, 1, X0=[1, 1]) - np.testing.assert_allclose(resp_long.states, resp_posn.states) + np.testing.assert_allclose(resp_long.states, resp_short.states) # Legacy with pytest.warns(PendingDeprecationWarning, match="legacy"): resp_legacy = ct.input_output_response(sys, timepts, 1, x0=[1, 1]) - np.testing.assert_allclose(resp_long.states, resp_posn.states) + np.testing.assert_allclose(resp_long.states, resp_legacy.states) # Check for multiple values: full keyword and alias with pytest.raises(TypeError, match="multiple"): - resp_multiple = ct.input_output_response( + ct.input_output_response( sys, timepts, 1, initial_state=[1, 2], X0=[1, 1]) # Check for multiple values: positional and keyword with pytest.raises(TypeError, match="multiple"): - resp_multiple = ct.input_output_response( + ct.input_output_response( sys, timepts, 1, [1, 2], initial_state=[1, 1]) # Check for multiple values: positional and alias with pytest.raises(TypeError, match="multiple"): - resp_multiple = ct.input_output_response( + ct.input_output_response( sys, timepts, 1, [1, 2], X0=[1, 1]) # Make sure that LTI functions check for keywords with pytest.raises(TypeError, match="unrecognized keyword"): - resp = ct.forced_response(sys, timepts, 1, unknown=True) + ct.forced_response(sys, timepts, 1, unknown=True) with pytest.raises(TypeError, match="unrecognized keyword"): - resp = ct.impulse_response(sys, timepts, unknown=True) + ct.impulse_response(sys, timepts, unknown=True) with pytest.raises(TypeError, match="unrecognized keyword"): - resp = ct.initial_response(sys, timepts, [1, 2], unknown=True) + ct.initial_response(sys, timepts, [1, 2], unknown=True) with pytest.raises(TypeError, match="unrecognized keyword"): - resp = ct.step_response(sys, timepts, unknown=True) + ct.step_response(sys, timepts, unknown=True) with pytest.raises(TypeError, match="unrecognized keyword"): - info = ct.step_info(sys, timepts, unknown=True) + ct.step_info(sys, timepts, unknown=True) diff --git a/control/tests/trdata_test.py b/control/tests/trdata_test.py index 7d0c20e7a..b84369d72 100644 --- a/control/tests/trdata_test.py +++ b/control/tests/trdata_test.py @@ -214,7 +214,7 @@ def test_response_copy(): # Unknown keyword with pytest.raises(TypeError, match="unrecognized keywords"): - response_bad_kw = response_mimo(input=0) + response_mimo(input=0) def test_trdata_labels(): diff --git a/pyproject.toml b/pyproject.toml index a81fc117c..d3754dc52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,6 @@ filterwarnings = [ # TODO: expand to cover all code include = ['control/**.py'] -exclude = ['control/tests/*.py'] [tool.ruff.lint] select = [