Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
pytest-parametrize and slycot-mark tests in mateqn_test.py
Test solvers dependent on Slycot when "slycot" test marker is
specified.  These tests are now parametrized by method.
  • Loading branch information
Rory Yorke
Rory Yorke committed Nov 8, 2025
commit 63ce37d983c3d0298ecb0ef6c05d0dfb4d4c0e5e
153 changes: 95 additions & 58 deletions control/tests/mateqn_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,53 +39,56 @@
import pytest
from scipy.linalg import eigvals, solve

import control as ct
from control.mateqn import lyap, dlyap, care, dare
from control.exception import ControlArgument, ControlDimension, slycot_check
from control.exception import ControlArgument, ControlDimension


class TestMatrixEquations:
"""These are tests for the matrix equation solvers in mateqn.py"""

def test_lyap(self):
@pytest.mark.parametrize('method',
['scipy',
pytest.param('slycot', marks=pytest.mark.slycot)])
def test_lyap(self, method):
A = array([[-1, 1], [-1, 0]])
Q = array([[1, 0], [0, 1]])
X = lyap(A, Q)
X = lyap(A, Q, method=method)
# print("The solution obtained is ", X)
assert_array_almost_equal(A @ X + X @ A.T + Q, zeros((2,2)))

A = array([[1, 2], [-3, -4]])
Q = array([[3, 1], [1, 1]])
X = lyap(A,Q)
X = lyap(A,Q, method=method)
# print("The solution obtained is ", X)
assert_array_almost_equal(A @ X + X @ A.T + Q, zeros((2,2)))

# Compare methods
if slycot_check():
if method == 'slycot':
X_scipy = lyap(A, Q, method='scipy')
X_slycot = lyap(A, Q, method='slycot')
assert_array_almost_equal(X_scipy, X_slycot)
assert_array_almost_equal(X_scipy, X)

def test_lyap_sylvester(self):
@pytest.mark.parametrize('method',
['scipy',
pytest.param('slycot', marks=pytest.mark.slycot)])
def test_lyap_sylvester(self, method):
A = 5
B = array([[4, 3], [4, 3]])
C = array([2, 1])
X = lyap(A, B, C)
X = lyap(A, B, C, method=method)
# print("The solution obtained is ", X)
assert_array_almost_equal(A * X + X @ B + C, zeros((1,2)))

A = array([[2, 1], [1, 2]])
B = array([[1, 2], [0.5, 0.1]])
C = array([[1, 0], [0, 1]])
X = lyap(A, B, C)
X = lyap(A, B, C, method=method)
# print("The solution obtained is ", X)
assert_array_almost_equal(A @ X + X @ B + C, zeros((2,2)))

# Compare methods
if slycot_check():
if method=='slycot':
X_scipy = lyap(A, B, C, method='scipy')
X_slycot = lyap(A, B, C, method='slycot')
assert_array_almost_equal(X_scipy, X_slycot)
assert_array_almost_equal(X_scipy, X)

@pytest.mark.slycot
def test_lyap_g(self):
Expand All @@ -101,19 +104,27 @@ def test_lyap_g(self):
with pytest.raises(ControlArgument, match="'scipy' not valid"):
X = lyap(A, Q, None, E, method='scipy')

def test_dlyap(self):
@pytest.mark.parametrize('method',
['scipy',
pytest.param('slycot', marks=pytest.mark.slycot)])
def test_dlyap(self, method):
A = array([[-0.6, 0],[-0.1, -0.4]])
Q = array([[1,0],[0,1]])
X = dlyap(A,Q)
X = dlyap(A,Q,method=method)
# print("The solution obtained is ", X)
assert_array_almost_equal(A @ X @ A.T - X + Q, zeros((2,2)))

A = array([[-0.6, 0],[-0.1, -0.4]])
Q = array([[3, 1],[1, 1]])
X = dlyap(A,Q)
X = dlyap(A,Q,method=method)
# print("The solution obtained is ", X)
assert_array_almost_equal(A @ X @ A.T - X + Q, zeros((2,2)))

# Compare methods
if method=='slycot':
X_scipy = dlyap(A,Q, method='scipy')
assert_array_almost_equal(X_scipy, X)

@pytest.mark.slycot
def test_dlyap_g(self):
A = array([[-0.6, 0],[-0.1, -0.4]])
Expand Down Expand Up @@ -148,35 +159,40 @@ def test_dlyap_sylvester(self):
with pytest.raises(ControlArgument, match="'scipy' not valid"):
X = dlyap(A, B, C, method='scipy')

def test_care(self):
@pytest.mark.parametrize('method',
['scipy',
pytest.param('slycot', marks=pytest.mark.slycot)])
def test_care(self, method):
A = array([[-2, -1],[-1, -1]])
Q = array([[0, 0],[0, 1]])
B = array([[1, 0],[0, 4]])

X, L, G = care(A, B, Q)
X, L, G = care(A, B, Q, method=method)
# print("The solution obtained is", X)
M = A.T @ X + X @ A - X @ B @ B.T @ X + Q
assert_array_almost_equal(M,
zeros((2,2)))
assert_array_almost_equal(B.T @ X, G)

# Compare methods
if slycot_check():
if method == 'slycot':
X_scipy, L_scipy, G_scipy = care(A, B, Q, method='scipy')
X_slycot, L_slycot, G_slycot = care(A, B, Q, method='slycot')
assert_array_almost_equal(X_scipy, X_slycot)
assert_array_almost_equal(np.sort(L_scipy), np.sort(L_slycot))
assert_array_almost_equal(G_scipy, G_slycot)

def test_care_g(self):
assert_array_almost_equal(X_scipy, X)
assert_array_almost_equal(np.sort(L_scipy), np.sort(L))
assert_array_almost_equal(G_scipy, G)

@pytest.mark.parametrize('method',
['scipy',
pytest.param('slycot', marks=pytest.mark.slycot)])
def test_care_g(self, method):
A = array([[-2, -1],[-1, -1]])
Q = array([[0, 0],[0, 1]])
B = array([[1, 0],[0, 4]])
R = array([[2, 0],[0, 1]])
S = array([[0, 0],[0, 0]])
E = array([[2, 1],[1, 2]])

X,L,G = care(A,B,Q,R,S,E)
X,L,G = care(A,B,Q,R,S,E,method=method)
# print("The solution obtained is", X)
Gref = solve(R, B.T @ X @ E + S.T)
assert_array_almost_equal(Gref, G)
Expand All @@ -186,24 +202,25 @@ def test_care_g(self):
zeros((2,2)))

# Compare methods
if slycot_check():
if method=='slycot':
X_scipy, L_scipy, G_scipy = care(
A, B, Q, R, S, E, method='scipy')
X_slycot, L_slycot, G_slycot = care(
A, B, Q, R, S, E, method='slycot')
assert_array_almost_equal(X_scipy, X_slycot)
assert_array_almost_equal(np.sort(L_scipy), np.sort(L_slycot))
assert_array_almost_equal(G_scipy, G_slycot)

def test_care_g2(self):
assert_array_almost_equal(X_scipy, X)
assert_array_almost_equal(np.sort(L_scipy), np.sort(L))
assert_array_almost_equal(G_scipy, G)

@pytest.mark.parametrize('method',
['scipy',
pytest.param('slycot', marks=pytest.mark.slycot)])
def test_care_g2(self, method):
A = array([[-2, -1],[-1, -1]])
Q = array([[0, 0],[0, 1]])
B = array([[1],[0]])
R = 1
S = array([[1],[0]])
E = array([[2, 1],[1, 2]])

X,L,G = care(A,B,Q,R,S,E)
X,L,G = care(A,B,Q,R,S,E,method=method)
# print("The solution obtained is", X)
Gref = 1/R * (B.T @ X @ E + S.T)
assert_array_almost_equal(
Expand All @@ -213,22 +230,23 @@ def test_care_g2(self):
assert_array_almost_equal(Gref , G)

# Compare methods
if slycot_check():
if method=='slycot':
X_scipy, L_scipy, G_scipy = care(
A, B, Q, R, S, E, method='scipy')
X_slycot, L_slycot, G_slycot = care(
A, B, Q, R, S, E, method='slycot')
assert_array_almost_equal(X_scipy, X_slycot)
assert_array_almost_equal(L_scipy, L_slycot)
assert_array_almost_equal(G_scipy, G_slycot)

def test_dare(self):
assert_array_almost_equal(X_scipy, X)
assert_array_almost_equal(L_scipy, L)
assert_array_almost_equal(G_scipy, G)

@pytest.mark.parametrize('method',
['scipy',
pytest.param('slycot', marks=pytest.mark.slycot)])
def test_dare(self, method):
A = array([[-0.6, 0],[-0.1, -0.4]])
Q = array([[2, 1],[1, 0]])
B = array([[2, 1],[0, 1]])
R = array([[1, 0],[0, 1]])

X, L, G = dare(A, B, Q, R)
X, L, G = dare(A, B, Q, R, method=method)
# print("The solution obtained is", X)
Gref = solve(B.T @ X @ B + R, B.T @ X @ A)
assert_array_almost_equal(Gref, G)
Expand All @@ -243,7 +261,7 @@ def test_dare(self):
B = array([[1],[0]])
R = 2

X, L, G = dare(A, B, Q, R)
X, L, G = dare(A, B, Q, R, method=method)
# print("The solution obtained is", X)
AtXA = A.T @ X @ A
AtXB = A.T @ X @ B
Expand All @@ -256,6 +274,7 @@ def test_dare(self):
lam = eigvals(A - B @ G)
assert_array_less(abs(lam), 1.0)

@pytest.mark.slycot
def test_dare_compare(self):
A = np.array([[-0.6, 0], [-0.1, -0.4]])
Q = np.array([[2, 1], [1, 0]])
Expand All @@ -267,23 +286,24 @@ def test_dare_compare(self):
# Solve via scipy
X_scipy, L_scipy, G_scipy = dare(A, B, Q, R, method='scipy')

# Solve via slycot
if ct.slycot_check():
X_slicot, L_slicot, G_slicot = dare(
A, B, Q, R, S, E, method='scipy')
np.testing.assert_almost_equal(X_scipy, X_slicot)
np.testing.assert_almost_equal(L_scipy, L_slicot)
np.testing.assert_almost_equal(G_scipy, G_slicot)
X_slicot, L_slicot, G_slicot = dare(
A, B, Q, R, S, E, method='scipy')
np.testing.assert_almost_equal(X_scipy, X_slicot)
np.testing.assert_almost_equal(L_scipy, L_slicot)
np.testing.assert_almost_equal(G_scipy, G_slicot)

def test_dare_g(self):
@pytest.mark.parametrize('method',
['scipy',
pytest.param('slycot', marks=pytest.mark.slycot)])
def test_dare_g(self, method):
A = array([[-0.6, 0],[-0.1, -0.4]])
Q = array([[2, 1],[1, 3]])
B = array([[1, 5],[2, 4]])
R = array([[1, 0],[0, 1]])
S = array([[1, 0],[2, 0]])
E = array([[2, 1],[1, 2]])

X, L, G = dare(A, B, Q, R, S, E)
X, L, G = dare(A, B, Q, R, S, E, method=method)
# print("The solution obtained is", X)
Gref = solve(B.T @ X @ B + R, B.T @ X @ A + S.T)
assert_array_almost_equal(Gref, G)
Expand All @@ -293,16 +313,26 @@ def test_dare_g(self):
# check for stable closed loop
lam = eigvals(A - B @ G, E)
assert_array_less(abs(lam), 1.0)

def test_dare_g2(self):
# Compare methods
if method=='slycot':
X_scipy, L_scipy, G_scipy = dare(
A, B, Q, R, S, E, method='scipy')
assert_array_almost_equal(X_scipy, X)
assert_array_almost_equal(L_scipy, L)
assert_array_almost_equal(G_scipy, G)

@pytest.mark.parametrize('method',
['scipy',
pytest.param('slycot', marks=pytest.mark.slycot)])
def test_dare_g2(self, method):
A = array([[-0.6, 0], [-0.1, -0.4]])
Q = array([[2, 1], [1, 3]])
B = array([[1], [2]])
R = 1
S = array([[1], [2]])
E = array([[2, 1], [1, 2]])

X, L, G = dare(A, B, Q, R, S, E)
X, L, G = dare(A, B, Q, R, S, E, method=method)
# print("The solution obtained is", X)
AtXA = A.T @ X @ A
AtXB = A.T @ X @ B
Expand All @@ -316,6 +346,13 @@ def test_dare_g2(self):
lam = eigvals(A - B @ G, E)
assert_array_less(abs(lam), 1.0)

if method=='slycot':
X_scipy, L_scipy, G_scipy = dare(
A, B, Q, R, S, E, method='scipy')
assert_array_almost_equal(X_scipy, X)
assert_array_almost_equal(L_scipy, L)
assert_array_almost_equal(G_scipy, G)

def test_raise(self):
""" Test exception raise for invalid inputs """

Expand Down