diff --git a/control/tests/canonical_test.py b/control/tests/canonical_test.py index ecdaa04cb..63afd51c3 100644 --- a/control/tests/canonical_test.py +++ b/control/tests/canonical_test.py @@ -4,8 +4,6 @@ import pytest import scipy.linalg -from control.tests.conftest import slycotonly - from control import ss, tf, tf2ss from control.canonical import canonical_form, reachable_form, \ observable_form, modal_form, similarity_transform, bdschur @@ -244,7 +242,7 @@ def block_diag_from_eig(eigvals): return scipy.linalg.block_diag(*blocks) -@slycotonly +@pytest.mark.slycot @pytest.mark.parametrize( "eigvals, condmax, blksizes", [ @@ -269,7 +267,7 @@ def test_bdschur_ref(eigvals, condmax, blksizes): np.testing.assert_array_almost_equal(solve(t, a) @ t, b) -@slycotonly +@pytest.mark.slycot @pytest.mark.parametrize( "eigvals, sorted_blk_eigvals, sort", [ @@ -300,7 +298,7 @@ def test_bdschur_sort(eigvals, sorted_blk_eigvals, sort): blk_eigval.imag) -@slycotonly +@pytest.mark.slycot def test_bdschur_defective(): # the eigenvalues of this simple defective matrix cannot be separated # a previous version of the bdschur would fail on this @@ -323,14 +321,14 @@ def test_bdschur_condmax_lt_1(): bdschur(1, condmax=np.nextafter(1, 0)) -@slycotonly +@pytest.mark.slycot def test_bdschur_invalid_sort(): # sort must be in ('continuous', 'discrete') with pytest.raises(ValueError): bdschur(1, sort='no-such-sort') -@slycotonly +@pytest.mark.slycot @pytest.mark.parametrize( "A_true, B_true, C_true, D_true", [(np.diag([4.0, 3.0, 2.0, 1.0]), # order from largest to smallest @@ -390,7 +388,7 @@ def test_modal_form(A_true, B_true, C_true, D_true): C @ np.linalg.matrix_power(A, i) @ B) -@slycotonly +@pytest.mark.slycot @pytest.mark.parametrize( "condmax, len_blksizes", [(1.1, 1), @@ -409,7 +407,7 @@ def test_modal_form_condmax(condmax, len_blksizes): np.testing.assert_array_almost_equal(zsys.D, xsys.D) -@slycotonly +@pytest.mark.slycot @pytest.mark.parametrize( "sys_type", ['continuous', diff --git a/control/tests/conftest.py b/control/tests/conftest.py index c10dcc225..0ad8afeaa 100644 --- a/control/tests/conftest.py +++ b/control/tests/conftest.py @@ -6,13 +6,22 @@ import control +def pytest_runtest_setup(item): + if (not control.exception.slycot_check() + and any(mark.name == 'slycot' + for mark in item.iter_markers())): + pytest.skip("slycot not installed") + + if (not control.exception.cvxopt_check() + and any(mark.name == 'cvxopt' + for mark in item.iter_markers())): + pytest.skip("cvxopt not installed") + + if (not control.exception.pandas_check() + and any(mark.name == 'pandas' + for mark in item.iter_markers())): + pytest.skip("pandas not installed") -# some common pytest marks. These can be used as test decorators or in -# pytest.param(marks=) -slycotonly = pytest.mark.skipif( - not control.exception.slycot_check(), reason="slycot not installed") -cvxoptonly = pytest.mark.skipif( - not control.exception.cvxopt_check(), reason="cvxopt not installed") @pytest.fixture(scope="session", autouse=True) diff --git a/control/tests/convert_test.py b/control/tests/convert_test.py index 7975bbe5a..b3784e0f2 100644 --- a/control/tests/convert_test.py +++ b/control/tests/convert_test.py @@ -22,7 +22,6 @@ from control.statefbk import ctrb, obsv from control.freqplot import bode from control.exception import slycot_check, ControlMIMONotImplemented -from control.tests.conftest import slycotonly # Set to True to print systems to the output. @@ -214,7 +213,7 @@ def testSs2tfStaticMimo(self): np.testing.assert_allclose(numref, np.array(gtf.num) / np.array(gtf.den)) - @slycotonly + @pytest.mark.slycot def testTf2SsDuplicatePoles(self): """Tests for 'too few poles for MIMO tf gh-111'""" num = [[[1], [0]], @@ -225,7 +224,7 @@ def testTf2SsDuplicatePoles(self): s = ss(g) np.testing.assert_allclose(g.poles(), s.poles()) - @slycotonly + @pytest.mark.slycot def test_tf2ss_robustness(self): """Unit test to make sure that tf2ss is working correctly. gh-240""" num = [ [[0], [1]], [[1], [0]] ] diff --git a/control/tests/frd_test.py b/control/tests/frd_test.py index 1b370c629..ab8ce3be6 100644 --- a/control/tests/frd_test.py +++ b/control/tests/frd_test.py @@ -12,8 +12,6 @@ from control.xferfcn import TransferFunction from control.frdata import frd, _convert_to_frd, FrequencyResponseData from control import bdalg, freqplot -from control.tests.conftest import slycotonly -from control.exception import pandas_check class TestFRD: @@ -567,7 +565,7 @@ def test_mul_mimo_siso(self, left, right, expected): np.testing.assert_array_almost_equal(expected_frd.omega, result.omega) np.testing.assert_array_almost_equal(expected_frd.frdata, result.frdata) - @slycotonly + @pytest.mark.slycot def test_truediv_mimo_siso(self): omega = np.logspace(-1, 1, 10) tf_mimo = TransferFunction([1], [1, 0]) * np.eye(2) @@ -592,7 +590,7 @@ def test_truediv_mimo_siso(self): np.testing.assert_array_almost_equal(expected.omega, result.omega) np.testing.assert_array_almost_equal(expected.frdata, result.frdata) - @slycotonly + @pytest.mark.slycot def test_rtruediv_mimo_siso(self): omega = np.logspace(-1, 1, 10) tf_mimo = TransferFunction([1], [1, 0]) * np.eye(2) @@ -821,7 +819,7 @@ def test_named_signals(): assert f1.output_labels == ['y0'] -@pytest.mark.skipif(not pandas_check(), reason="pandas not installed") +@pytest.mark.pandas def test_to_pandas(): # Create a SISO frequency response h1 = TransferFunction([1], [1, 2, 2]) diff --git a/control/tests/freqresp_test.py b/control/tests/freqresp_test.py index a268d38eb..5112a99e9 100644 --- a/control/tests/freqresp_test.py +++ b/control/tests/freqresp_test.py @@ -19,7 +19,6 @@ singular_values_plot, singular_values_response) from control.matlab import bode, rss, ss, tf from control.statesp import StateSpace -from control.tests.conftest import slycotonly from control.xferfcn import TransferFunction pytestmark = pytest.mark.usefixtures("mplcleanup") @@ -61,7 +60,7 @@ def test_freqresp_siso(ss_siso): @pytest.mark.filterwarnings(r"ignore:freqresp\(\) is deprecated") -@slycotonly +@pytest.mark.slycot def test_freqresp_mimo_legacy(ss_mimo): """Test MIMO frequency response calls""" omega = np.linspace(10e-2, 10e2, 1000) @@ -70,7 +69,7 @@ def test_freqresp_mimo_legacy(ss_mimo): ctrl.freqresp(tf_mimo, omega) -@slycotonly +@pytest.mark.slycot def test_freqresp_mimo(ss_mimo): """Test MIMO frequency response calls""" omega = np.linspace(10e-2, 10e2, 1000) diff --git a/control/tests/lti_test.py b/control/tests/lti_test.py index 17dc7796e..45c75f964 100644 --- a/control/tests/lti_test.py +++ b/control/tests/lti_test.py @@ -10,7 +10,6 @@ isdtime, issiso, ss, tf, tf2ss from control.exception import slycot_check from control.lti import LTI, bandwidth, damp, dcgain, evalfr, poles, zeros -from control.tests.conftest import slycotonly class TestLTI: @@ -59,7 +58,7 @@ def test_issiso(self): assert issiso(sys) assert issiso(sys, strict=True) - @slycotonly + @pytest.mark.slycot def test_issiso_mimo(self): # MIMO transfer function sys = tf([[[-1, 41], [1]], [[1, 2], [3, 4]]], diff --git a/control/tests/mateqn_test.py b/control/tests/mateqn_test.py index 0ae5a7db2..d12a7f6ef 100644 --- a/control/tests/mateqn_test.py +++ b/control/tests/mateqn_test.py @@ -42,7 +42,6 @@ import control as ct from control.mateqn import lyap, dlyap, care, dare from control.exception import ControlArgument, ControlDimension, slycot_check -from control.tests.conftest import slycotonly class TestMatrixEquations: @@ -88,7 +87,7 @@ def test_lyap_sylvester(self): X_slycot = lyap(A, B, C, method='slycot') assert_array_almost_equal(X_scipy, X_slycot) - @slycotonly + @pytest.mark.slycot def test_lyap_g(self): A = array([[-1, 2], [-3, -4]]) Q = array([[3, 1], [1, 1]]) @@ -115,7 +114,7 @@ def test_dlyap(self): # print("The solution obtained is ", X) assert_array_almost_equal(A @ X @ A.T - X + Q, zeros((2,2))) - @slycotonly + @pytest.mark.slycot def test_dlyap_g(self): A = array([[-0.6, 0],[-0.1, -0.4]]) Q = array([[3, 1],[1, 1]]) @@ -129,7 +128,7 @@ def test_dlyap_g(self): with pytest.raises(ControlArgument, match="'scipy' not valid"): X = dlyap(A, Q, None, E, method='scipy') - @slycotonly + @pytest.mark.slycot def test_dlyap_sylvester(self): A = 5 B = array([[4, 3], [4, 3]]) diff --git a/control/tests/matlab_test.py b/control/tests/matlab_test.py index c6a45e2a2..d1a71bce3 100644 --- a/control/tests/matlab_test.py +++ b/control/tests/matlab_test.py @@ -30,7 +30,6 @@ from control.exception import ControlArgument from control.frdata import FRD -from control.tests.conftest import slycotonly # for running these through Matlab or Octave ''' @@ -487,21 +486,21 @@ def testEvalfr_mimo(self, mimo): ref = np.array([[44.8 - 21.4j, 0.], [0., 44.8 - 21.4j]]) np.testing.assert_array_almost_equal(fr, ref) - @slycotonly + @pytest.mark.slycot def testHsvd(self, siso): """Call hsvd()""" hsvd(siso.ss1) hsvd(siso.ss2) hsvd(siso.ss3) - @slycotonly + @pytest.mark.slycot def testBalred(self, siso): """Call balred()""" balred(siso.ss1, 1) balred(siso.ss2, 2) balred(siso.ss3, [2, 2]) - @slycotonly + @pytest.mark.slycot def testModred(self, siso): """Call modred()""" modred(siso.ss1, [1]) @@ -509,7 +508,7 @@ def testModred(self, siso): modred(siso.ss1, [1], 'matchdc') modred(siso.ss1, [1], 'truncate') - @slycotonly + @pytest.mark.slycot def testPlace_varga(self, siso): """Call place_varga()""" place_varga(siso.ss1.A, siso.ss1.B, [-2, -2]) @@ -552,7 +551,7 @@ def testObsv(self, siso): obsv(siso.ss1.A, siso.ss1.C) obsv(siso.ss2.A, siso.ss2.C) - @slycotonly + @pytest.mark.slycot def testGram(self, siso): """Call gram()""" gram(siso.ss1, 'c') @@ -696,7 +695,7 @@ def testFRD(self): frd2 = frd(frd1.frdata[0, 0, :], omega) assert isinstance(frd2, FRD) - @slycotonly + @pytest.mark.slycot def testMinreal(self, verbose=False): """Test a minreal model reduction""" # A = [-2, 0.5, 0; 0.5, -0.3, 0; 0, 0, -0.1] diff --git a/control/tests/minreal_test.py b/control/tests/minreal_test.py index 10c56d4ca..e8223184c 100644 --- a/control/tests/minreal_test.py +++ b/control/tests/minreal_test.py @@ -11,7 +11,6 @@ from control.statesp import StateSpace from control.xferfcn import TransferFunction from itertools import permutations -from control.tests.conftest import slycotonly @pytest.fixture @@ -19,7 +18,7 @@ def fixedseed(scope="class"): np.random.seed(5) -@slycotonly +@pytest.mark.slycot @pytest.mark.usefixtures("fixedseed") class TestMinreal: """Tests for the StateSpace class.""" diff --git a/control/tests/modelsimp_test.py b/control/tests/modelsimp_test.py index e09446073..c2773231b 100644 --- a/control/tests/modelsimp_test.py +++ b/control/tests/modelsimp_test.py @@ -14,13 +14,12 @@ from control.exception import ControlArgument, ControlDimension from control.modelsimp import balred, eigensys_realization, hsvd, markov, \ modred -from control.tests.conftest import slycotonly class TestModelsimp: """Test model reduction functions""" - @slycotonly + @pytest.mark.slycot def testHSVD(self): A = np.array([[1., -2.], [3., -4.]]) B = np.array([[5.], [7.]]) @@ -390,7 +389,7 @@ def testModredTruncate(self): np.testing.assert_array_almost_equal(rsys.D, Drtrue) - @slycotonly + @pytest.mark.slycot def testBalredTruncate(self): # controlable canonical realization computed in matlab for the transfer # function: @@ -431,7 +430,7 @@ def testBalredTruncate(self): np.testing.assert_array_almost_equal(Cr, Crtrue, decimal=4) np.testing.assert_array_almost_equal(Dr, Drtrue, decimal=4) - @slycotonly + @pytest.mark.slycot def testBalredMatchDC(self): # controlable canonical realization computed in matlab for the transfer # function: diff --git a/control/tests/optimal_test.py b/control/tests/optimal_test.py index fa8fcb941..fb3f4e716 100644 --- a/control/tests/optimal_test.py +++ b/control/tests/optimal_test.py @@ -12,7 +12,6 @@ import control as ct import control.optimal as opt import control.flatsys as flat -from control.tests.conftest import slycotonly from numpy.lib import NumpyVersion @@ -103,7 +102,7 @@ def test_finite_horizon_simple(method): # optimal control problem with terminal cost set to LQR "cost to go" # gives the same answer as LQR. # -@slycotonly +@pytest.mark.slycot def test_discrete_lqr(): # oscillator model defined in 2D # Source: https://www.mpt3.org/UI/RegulationProblem diff --git a/control/tests/passivity_test.py b/control/tests/passivity_test.py index 4d7c8e6eb..22b73e0da 100644 --- a/control/tests/passivity_test.py +++ b/control/tests/passivity_test.py @@ -5,10 +5,9 @@ import pytest import numpy from control import ss, passivity, tf, sample_system, parallel, feedback -from control.tests.conftest import cvxoptonly from control.exception import ControlArgument, ControlDimension -pytestmark = cvxoptonly +pytestmark = pytest.mark.cvxopt def test_ispassive_ctime(): diff --git a/control/tests/robust_test.py b/control/tests/robust_test.py index fc9c9570d..8434ea6cd 100644 --- a/control/tests/robust_test.py +++ b/control/tests/robust_test.py @@ -5,12 +5,11 @@ from control import append, minreal, ss, tf from control.robust import augw, h2syn, hinfsyn, mixsyn -from control.tests.conftest import slycotonly class TestHinf: - @slycotonly + @pytest.mark.slycot def testHinfsyn(self): """Test hinfsyn""" p = ss(-1, [[1, 1]], [[1], [1]], [[0, 1], [1, 0]]) @@ -32,7 +31,7 @@ def testHinfsyn(self): class TestH2: - @slycotonly + @pytest.mark.slycot def testH2syn(self): """Test h2syn""" p = ss(-1, [[1, 1]], [[1], [1]], [[0, 1], [1, 0]]) @@ -71,7 +70,7 @@ def siso_almost_equal(self, g, h): "sys 2:\n" "{}".format(maxnum, g, h)) - @slycotonly + @pytest.mark.slycot def testSisoW1(self): """SISO plant with S weighting""" g = ss([-1.], [1.], [1.], [1.]) @@ -88,7 +87,7 @@ def testSisoW1(self): # u->v should be -g self.siso_almost_equal(-g, p[1, 1]) - @slycotonly + @pytest.mark.slycot def testSisoW2(self): """SISO plant with KS weighting""" g = ss([-1.], [1.], [1.], [1.]) @@ -105,7 +104,7 @@ def testSisoW2(self): # u->v should be -g self.siso_almost_equal(-g, p[1, 1]) - @slycotonly + @pytest.mark.slycot def testSisoW3(self): """SISO plant with T weighting""" g = ss([-1.], [1.], [1.], [1.]) @@ -122,7 +121,7 @@ def testSisoW3(self): # u->v should be -g self.siso_almost_equal(-g, p[1, 1]) - @slycotonly + @pytest.mark.slycot def testSisoW123(self): """SISO plant with all weights""" g = ss([-1.], [1.], [1.], [1.]) @@ -149,7 +148,7 @@ def testSisoW123(self): # u->v should be -g self.siso_almost_equal(-g, p[3, 1]) - @slycotonly + @pytest.mark.slycot def testMimoW1(self): """MIMO plant with S weighting""" g = ss([[-1., -2], [-3, -4]], @@ -181,7 +180,7 @@ def testMimoW1(self): self.siso_almost_equal(-g[1, 0], p[3, 2]) self.siso_almost_equal(-g[1, 1], p[3, 3]) - @slycotonly + @pytest.mark.slycot def testMimoW2(self): """MIMO plant with KS weighting""" g = ss([[-1., -2], [-3, -4]], @@ -213,7 +212,7 @@ def testMimoW2(self): self.siso_almost_equal(-g[1, 0], p[3, 2]) self.siso_almost_equal(-g[1, 1], p[3, 3]) - @slycotonly + @pytest.mark.slycot def testMimoW3(self): """MIMO plant with T weighting""" g = ss([[-1., -2], [-3, -4]], @@ -245,7 +244,7 @@ def testMimoW3(self): self.siso_almost_equal(-g[1, 0], p[3, 2]) self.siso_almost_equal(-g[1, 1], p[3, 3]) - @slycotonly + @pytest.mark.slycot def testMimoW123(self): """MIMO plant with all weights""" g = ss([[-1., -2], [-3, -4]], @@ -307,7 +306,7 @@ def testMimoW123(self): self.siso_almost_equal(-g[1, 0], p[7, 2]) self.siso_almost_equal(-g[1, 1], p[7, 3]) - @slycotonly + @pytest.mark.slycot def testErrors(self): """Error cases handled""" from control import augw, ss @@ -330,7 +329,7 @@ class TestMixsyn: """Test control.robust.mixsyn""" # it's a relatively simple wrapper; compare results with augw, hinfsyn - @slycotonly + @pytest.mark.slycot def testSiso(self): """mixsyn with SISO system""" # Skogestad+Postlethwaite, Multivariable Feedback Control, 1st Ed., Example 2.11 diff --git a/control/tests/slycot_convert_test.py b/control/tests/slycot_convert_test.py index 25beeb908..2739a4cf1 100644 --- a/control/tests/slycot_convert_test.py +++ b/control/tests/slycot_convert_test.py @@ -7,7 +7,6 @@ import pytest from control import bode, rss, ss, tf -from control.tests.conftest import slycotonly numTests = 5 maxStates = 10 @@ -21,7 +20,7 @@ def fixedseed(): np.random.seed(0) -@slycotonly +@pytest.mark.slycot @pytest.mark.usefixtures("fixedseed") class TestSlycot: """Test Slycot system conversion diff --git a/control/tests/statefbk_test.py b/control/tests/statefbk_test.py index 3f4b4849a..b34150018 100644 --- a/control/tests/statefbk_test.py +++ b/control/tests/statefbk_test.py @@ -16,7 +16,6 @@ from control.mateqn import care, dare from control.statefbk import (ctrb, obsv, place, place_varga, lqr, dlqr, gram, place_acker) -from control.tests.conftest import slycotonly @pytest.fixture @@ -128,7 +127,7 @@ def testCtrbObsvDuality(self): Wo = np.transpose(obsv(A, C)) np.testing.assert_array_almost_equal(Wc,Wo) - @slycotonly + @pytest.mark.slycot def testGramWc(self): A = np.array([[1., -2.], [3., -4.]]) B = np.array([[5., 6.], [7., 8.]]) @@ -144,7 +143,7 @@ def testGramWc(self): Wc = gram(sysd, 'c') np.testing.assert_array_almost_equal(Wc, Wctrue) - @slycotonly + @pytest.mark.slycot def testGramWc2(self): A = np.array([[1., -2.], [3., -4.]]) B = np.array([[5.], [7.]]) @@ -161,7 +160,7 @@ def testGramWc2(self): Wc = gram(sysd, 'c') np.testing.assert_array_almost_equal(Wc, Wctrue) - @slycotonly + @pytest.mark.slycot def testGramRc(self): A = np.array([[1., -2.], [3., -4.]]) B = np.array([[5., 6.], [7., 8.]]) @@ -177,7 +176,7 @@ def testGramRc(self): Rc = gram(sysd, 'cf') np.testing.assert_array_almost_equal(Rc, Rctrue) - @slycotonly + @pytest.mark.slycot def testGramWo(self): A = np.array([[1., -2.], [3., -4.]]) B = np.array([[5., 6.], [7., 8.]]) @@ -193,7 +192,7 @@ def testGramWo(self): Wo = gram(sysd, 'o') np.testing.assert_array_almost_equal(Wo, Wotrue) - @slycotonly + @pytest.mark.slycot def testGramWo2(self): A = np.array([[1., -2.], [3., -4.]]) B = np.array([[5.], [7.]]) @@ -209,7 +208,7 @@ def testGramWo2(self): Wo = gram(sysd, 'o') np.testing.assert_array_almost_equal(Wo, Wotrue) - @slycotonly + @pytest.mark.slycot def testGramRo(self): A = np.array([[1., -2.], [3., -4.]]) B = np.array([[5., 6.], [7., 8.]]) @@ -318,7 +317,7 @@ def testPlace(self): with pytest.raises(ValueError): place(A, B, P_repeated) - @slycotonly + @pytest.mark.slycot def testPlace_varga_continuous(self): """ Check that we can place eigenvalues for dtime=False @@ -345,7 +344,7 @@ def testPlace_varga_continuous(self): self.checkPlaced(P, P_placed) - @slycotonly + @pytest.mark.slycot def testPlace_varga_continuous_partial_eigs(self): """ Check that we are able to use the alpha parameter to only place @@ -365,7 +364,7 @@ def testPlace_varga_continuous_partial_eigs(self): # No guarantee of the ordering, so sort them self.checkPlaced(P_expected, P_placed) - @slycotonly + @pytest.mark.slycot def testPlace_varga_discrete(self): """ Check that we can place poles using dtime=True (discrete time) @@ -379,7 +378,7 @@ def testPlace_varga_discrete(self): # No guarantee of the ordering, so sort them self.checkPlaced(P, P_placed) - @slycotonly + @pytest.mark.slycot def testPlace_varga_discrete_partial_eigs(self): """" Check that we can only assign a single eigenvalue in the discrete @@ -559,7 +558,7 @@ def test_care(self): @pytest.mark.parametrize( "stabilizing", - [True, pytest.param(False, marks=slycotonly)]) + [True, pytest.param(False, marks=pytest.mark.slycot)]) def test_dare(self, stabilizing): """Test stabilizing and anti-stabilizing feedback, discrete""" A = np.diag([0.5, 2]) diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index 3c1411f04..1f6d4a6bb 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -22,7 +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, slycotonly +from .conftest import assert_tf_close_coeff class TestStateSpace: """Tests for the StateSpace class.""" @@ -229,7 +229,7 @@ def test_zero_empty(self): sys = _convert_to_statespace(TransferFunction([1], [1, 2, 1])) np.testing.assert_array_equal(sys.zeros(), np.array([])) - @slycotonly + @pytest.mark.slycot def test_zero_siso(self, sys222): """Evaluate the zeros of a SISO system.""" # extract only first input / first output system of sys222. This system is denoted sys111 @@ -259,7 +259,7 @@ def test_zero_mimo_sys222_square(self, sys222): true_z = np.sort([-10.568501, 3.368501]) np.testing.assert_array_almost_equal(z, true_z) - @slycotonly + @pytest.mark.slycot def test_zero_mimo_sys623_non_square(self, sys623): """Evaluate the zeros of a non square MIMO system.""" @@ -406,7 +406,7 @@ def test_add_sub_mimo_siso(self): ss2tf(result).minreal(), ) - @slycotonly + @pytest.mark.slycot @pytest.mark.parametrize( "left, right, expected", [ @@ -481,7 +481,7 @@ def test_mul_mimo_siso(self, left, right, expected): ss2tf(result).minreal(), ) - @slycotonly + @pytest.mark.slycot @pytest.mark.parametrize( "left, right, expected", [ @@ -556,7 +556,7 @@ def test_rmul_mimo_siso(self, left, right, expected): ss2tf(result).minreal(), ) - @slycotonly + @pytest.mark.slycot @pytest.mark.parametrize("power", [0, 1, 3, -3]) @pytest.mark.parametrize("sysname", ["sys222", "sys322"]) def test_pow(self, request, sysname, power): @@ -575,7 +575,7 @@ def test_pow(self, request, sysname, power): np.testing.assert_allclose(expected.C, result.C) np.testing.assert_allclose(expected.D, result.D) - @slycotonly + @pytest.mark.slycot @pytest.mark.parametrize("order", ["left", "right"]) @pytest.mark.parametrize("sysname", ["sys121", "sys222", "sys322"]) def test_pow_inv(self, request, sysname, order): @@ -599,7 +599,7 @@ def test_pow_inv(self, request, sysname, order): # Check that the output is the same as the input np.testing.assert_allclose(R.outputs, U) - @slycotonly + @pytest.mark.slycot def test_truediv(self, sys222, sys322): """Test state space truediv""" for sys in [sys222, sys322]: @@ -618,7 +618,7 @@ def test_truediv(self, sys222, sys322): ss2tf(result).minreal(), ) - @slycotonly + @pytest.mark.slycot def test_rtruediv(self, sys222, sys322): """Test state space rtruediv""" for sys in [sys222, sys322]: @@ -719,7 +719,7 @@ def test_freq_resp(self): mag, phase, omega = sys.freqresp(true_omega) np.testing.assert_almost_equal(mag, true_mag) - @slycotonly + @pytest.mark.slycot def test_minreal(self): """Test a minreal model reduction.""" # A = [-2, 0.5, 0; 0.5, -0.3, 0; 0, 0, -0.1] @@ -1514,7 +1514,7 @@ def dt_siso(self, request): name, systype, sysargs, dt, refgpeak, reffpeak = request.param return ct.c2d(systype(*sysargs), dt), refgpeak, reffpeak - @slycotonly + @pytest.mark.slycot @pytest.mark.usefixtures('ignore_future_warning') def test_linfnorm_ct_siso(self, ct_siso): sys, refgpeak, reffpeak = ct_siso @@ -1522,7 +1522,7 @@ def test_linfnorm_ct_siso(self, ct_siso): np.testing.assert_allclose(gpeak, refgpeak) np.testing.assert_allclose(fpeak, reffpeak) - @slycotonly + @pytest.mark.slycot @pytest.mark.usefixtures('ignore_future_warning') def test_linfnorm_dt_siso(self, dt_siso): sys, refgpeak, reffpeak = dt_siso @@ -1531,7 +1531,7 @@ def test_linfnorm_dt_siso(self, dt_siso): np.testing.assert_allclose(gpeak, refgpeak) np.testing.assert_allclose(fpeak, reffpeak) - @slycotonly + @pytest.mark.slycot @pytest.mark.usefixtures('ignore_future_warning') def test_linfnorm_ct_mimo(self, ct_siso): siso, refgpeak, reffpeak = ct_siso diff --git a/control/tests/timeplot_test.py b/control/tests/timeplot_test.py index 888ff9080..ea0a290c9 100644 --- a/control/tests/timeplot_test.py +++ b/control/tests/timeplot_test.py @@ -7,7 +7,6 @@ import pytest import control as ct -from control.tests.conftest import slycotonly # Detailed test of (almost) all functionality # @@ -237,7 +236,7 @@ def test_axes_setup(): sys_3x1 = ct.rss(4, 3, 1) -@slycotonly +@pytest.mark.slycot @pytest.mark.usefixtures('mplcleanup') def test_legend_map(): sys_mimo = ct.tf2ss( @@ -371,7 +370,7 @@ def test_list_responses(resp_fcn): assert cplt.lines[row, col][1].get_color() == 'tab:orange' -@slycotonly +@pytest.mark.slycot @pytest.mark.usefixtures('mplcleanup') def test_linestyles(): # Check to make sure we can change line styles diff --git a/control/tests/timeresp_test.py b/control/tests/timeresp_test.py index 8bbd27d73..fdb47fd53 100644 --- a/control/tests/timeresp_test.py +++ b/control/tests/timeresp_test.py @@ -9,7 +9,6 @@ import control as ct from control import StateSpace, TransferFunction, c2d, isctime, ss2tf, tf2ss from control.exception import pandas_check, slycot_check -from control.tests.conftest import slycotonly from control.timeresp import _default_time_vector, _ideal_tfinal_and_dt, \ forced_response, impulse_response, initial_response, step_info, \ step_response @@ -454,7 +453,7 @@ def test_step_info(self, tsystem, systype, time_2d, yfinal): @pytest.mark.parametrize( "tsystem", ['mimo_ss_step_matlab', - pytest.param('mimo_tf_step_info', marks=slycotonly)], + pytest.param('mimo_tf_step_info', marks=pytest.mark.slycot)], indirect=["tsystem"]) def test_step_info_mimo(self, tsystem, systype, yfinal): """Test step info for MIMO systems.""" @@ -799,7 +798,7 @@ def test_lsim_double_integrator(self, u, x0, xtrue): np.testing.assert_array_almost_equal(yout, ytrue, decimal=6) - @slycotonly + @pytest.mark.slycot def test_step_robustness(self): "Test robustness os step_response against denomiantors: gh-240" # Create 2 input, 2 output system @@ -902,9 +901,9 @@ def test_default_timevector_functions_d(self, fun, dt): "siso_dtf2", "siso_ss2_dtnone", # undetermined timebase "mimo_ss2", # MIMO - pytest.param("mimo_tf2", marks=slycotonly), + pytest.param("mimo_tf2", marks=pytest.mark.slycot), "mimo_dss1", - pytest.param("mimo_dtf1", marks=slycotonly), + pytest.param("mimo_dtf1", marks=pytest.mark.slycot), ], indirect=True) @pytest.mark.parametrize("fun", [step_response, @@ -1236,7 +1235,7 @@ def test_response_transpose( assert x.shape == (T.size, sys.nstates) -@pytest.mark.skipif(not pandas_check(), reason="pandas not installed") +@pytest.mark.pandas def test_to_pandas(): # Create a SISO time response sys = ct.rss(2, 1, 1) diff --git a/control/tests/xferfcn_test.py b/control/tests/xferfcn_test.py index d3db08ef6..a9be040ab 100644 --- a/control/tests/xferfcn_test.py +++ b/control/tests/xferfcn_test.py @@ -14,7 +14,7 @@ isdtime, reset_defaults, rss, sample_system, set_defaults, ss, ss2tf, tf, tf2ss, zpk) from control.statesp import _convert_to_statespace -from control.tests.conftest import assert_tf_close_coeff, slycotonly +from control.tests.conftest import assert_tf_close_coeff from control.xferfcn import _convert_to_transfer_function @@ -997,7 +997,7 @@ def test_minreal_4(self): np.testing.assert_allclose(hm.num[0][0], hr.num[0][0]) np.testing.assert_allclose(hr.dt, hm.dt) - @slycotonly + @pytest.mark.slycot def test_state_space_conversion_mimo(self): """Test conversion of a single input, two-output state-space system against the same TF""" diff --git a/pyproject.toml b/pyproject.toml index b47f7462c..494aafe69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,10 +51,16 @@ source = "https://github.com/python-control/python-control" write_to = "control/_version.py" [tool.pytest.ini_options] -addopts = "-ra" +addopts = "-ra --strict-markers" filterwarnings = [ "error:.*matrix subclass:PendingDeprecationWarning", ] +markers = [ + "slycot: tests needing slycot", + "cvxopt: tests needing cvxopt", + "pandas: tests needing pandas", +] + [tool.ruff]