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 = [
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: