Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 8 additions & 4 deletions control/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
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.slycot_check():
if any(mark.name == 'slycot'
for mark in item.iter_markers()):
pytest.skip("slycot not installed")
elif any(mark.name == 'noslycot'
for mark in item.iter_markers()):
# used, e.g., for tests checking ControlSlycot
pytest.skip("slycot installed")

if (not control.exception.cvxopt_check()
and any(mark.name == 'cvxopt'
Expand Down
22 changes: 14 additions & 8 deletions control/tests/convert_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,13 @@
from control import rss, ss, ss2tf, tf, tf2ss
from control.statefbk import ctrb, obsv
from control.freqplot import bode
from control.exception import slycot_check, ControlMIMONotImplemented
from control.exception import ControlMIMONotImplemented


# Set to True to print systems to the output.
verbose = False
# Maximum number of states to test + 1
maxStates = 4
# Maximum number of inputs and outputs to test + 1
# If slycot is not installed, just check SISO
maxIO = 5 if slycot_check() else 2


@pytest.fixture
Expand All @@ -49,8 +46,13 @@ def printSys(self, sys, ind):

@pytest.mark.usefixtures("legacy_plot_signature")
@pytest.mark.parametrize("states", range(1, maxStates))
@pytest.mark.parametrize("inputs", range(1, maxIO))
@pytest.mark.parametrize("outputs", range(1, maxIO))
# If slycot is not installed, just check SISO
@pytest.mark.parametrize("inputs",
[1] + [pytest.param(i, marks=pytest.mark.slycot)
for i in range(2, 5)])
@pytest.mark.parametrize("outputs",
[1] + [pytest.param(i, marks=pytest.mark.slycot)
for i in range(2, 5)])
def testConvert(self, fixedseed, states, inputs, outputs):
"""Test state space to transfer function conversion.

Expand Down Expand Up @@ -147,7 +149,11 @@ def testConvert(self, fixedseed, states, inputs, outputs):
np.testing.assert_array_almost_equal(
ssorig_imag, tfxfrm_imag, decimal=5)

def testConvertMIMO(self):

@pytest.mark.parametrize('have_slycot',
[pytest.param(True, marks=pytest.mark.slycot),
pytest.param(False, marks=pytest.mark.noslycot)])
def testConvertMIMO(self, have_slycot):
"""Test state space to transfer function conversion.

Do a MIMO conversion and make sure that it is processed
Expand All @@ -165,7 +171,7 @@ def testConvertMIMO(self):
[0.008, 1.39, 48.78]]])

# Convert to state space and look for an error
if (not slycot_check()):
if not have_slycot:
with pytest.raises(ControlMIMONotImplemented):
tf2ss(tsys)
else:
Expand Down
6 changes: 2 additions & 4 deletions control/tests/interconnect_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,12 @@ def test_summation_exceptions():
ct.summing_junction('u', 'y', dimension=False)


@pytest.mark.parametrize("dim", [1, 3])
@pytest.mark.parametrize("dim",
[1, pytest.param(3, marks=pytest.mark.slycot)])
def test_interconnect_implicit(dim):
"""Test the use of implicit connections in interconnect()"""
import random

if dim != 1 and not ct.slycot_check():
pytest.xfail("slycot not installed")

# System definition
P = ct.rss(2, dim, dim, strictly_proper=True, name='P')

Expand Down
48 changes: 24 additions & 24 deletions control/tests/lti_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import control as ct
from control import NonlinearIOSystem, c2d, common_timebase, isctime, \
isdtime, issiso, ss, tf, tf2ss
from control.exception import slycot_check
from control.lti import LTI, bandwidth, damp, dcgain, evalfr, poles, zeros


Expand Down Expand Up @@ -189,6 +188,10 @@ def test_isdtime(self, objfun, arg, dt, ref, strictref):
assert isctime(obj) == ref
assert isctime(obj, strict=True) == strictref

def p(*args):
# convenience for parametrize below
return pytest.param(*args, marks=pytest.mark.slycot)

@pytest.mark.usefixtures("editsdefaults")
@pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.frd])
@pytest.mark.parametrize("nstate, nout, ninp, omega, squeeze, shape", [
Expand All @@ -201,26 +204,26 @@ def test_isdtime(self, objfun, arg, dt, ref, strictref):
[3, 1, 1, 0.1, False, (1, 1)],
[3, 1, 1, [0.1], False, (1, 1, 1)],
[3, 1, 1, [0.1, 1, 10], False, (1, 1, 3)],
[1, 2, 1, 0.1, None, (2, 1)], # SIMO
[1, 2, 1, [0.1], None, (2, 1, 1)],
[1, 2, 1, [0.1, 1, 10], None, (2, 1, 3)],
[2, 2, 1, 0.1, True, (2,)],
[2, 2, 1, [0.1], True, (2,)],
[3, 2, 1, 0.1, False, (2, 1)],
[3, 2, 1, [0.1], False, (2, 1, 1)],
[3, 2, 1, [0.1, 1, 10], False, (2, 1, 3)],
[1, 1, 2, [0.1, 1, 10], None, (1, 2, 3)], # MISO
[2, 1, 2, [0.1, 1, 10], True, (2, 3)],
[3, 1, 2, [0.1, 1, 10], False, (1, 2, 3)],
[1, 1, 2, 0.1, None, (1, 2)],
[1, 1, 2, 0.1, True, (2,)],
[1, 1, 2, 0.1, False, (1, 2)],
[1, 2, 2, [0.1, 1, 10], None, (2, 2, 3)], # MIMO
[2, 2, 2, [0.1, 1, 10], True, (2, 2, 3)],
[3, 2, 2, [0.1, 1, 10], False, (2, 2, 3)],
[1, 2, 2, 0.1, None, (2, 2)],
[2, 2, 2, 0.1, True, (2, 2)],
[3, 2, 2, 0.1, False, (2, 2)],
p(1, 2, 1, 0.1, None, (2, 1)),
p(1, 2, 1, [0.1], None, (2, 1, 1)),
p(1, 2, 1, [0.1, 1, 10], None, (2, 1, 3)),
p(2, 2, 1, 0.1, True, (2,)),
p(2, 2, 1, [0.1], True, (2,)),
p(3, 2, 1, 0.1, False, (2, 1)),
p(3, 2, 1, [0.1], False, (2, 1, 1)),
p(3, 2, 1, [0.1, 1, 10], False, (2, 1, 3)),
p(1, 1, 2, [0.1, 1, 10], None, (1, 2, 3)), # MISO
p(2, 1, 2, [0.1, 1, 10], True, (2, 3)),
p(3, 1, 2, [0.1, 1, 10], False, (1, 2, 3)),
p(1, 1, 2, 0.1, None, (1, 2)),
p(1, 1, 2, 0.1, True, (2,)),
p(1, 1, 2, 0.1, False, (1, 2)),
p(1, 2, 2, [0.1, 1, 10], None, (2, 2, 3)), # MIMO
p(2, 2, 2, [0.1, 1, 10], True, (2, 2, 3)),
p(3, 2, 2, [0.1, 1, 10], False, (2, 2, 3)),
p(1, 2, 2, 0.1, None, (2, 2)),
p(2, 2, 2, 0.1, True, (2, 2)),
p(3, 2, 2, 0.1, False, (2, 2)),
])
@pytest.mark.parametrize("omega_type", ["numpy", "native"])
def test_squeeze(self, fcn, nstate, nout, ninp, omega, squeeze, shape,
Expand All @@ -229,9 +232,6 @@ def test_squeeze(self, fcn, nstate, nout, ninp, omega, squeeze, shape,
# Create the system to be tested
if fcn == ct.frd:
sys = fcn(ct.rss(nstate, nout, ninp), [1e-2, 1e-1, 1, 1e1, 1e2])
elif fcn == ct.tf and (nout > 1 or ninp > 1) and not slycot_check():
pytest.skip("Conversion of MIMO systems to transfer functions "
"requires slycot.")
else:
sys = fcn(ct.rss(nstate, nout, ninp))

Expand Down
91 changes: 48 additions & 43 deletions control/tests/margin_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from control import ControlMIMONotImplemented, FrequencyResponseData, \
StateSpace, TransferFunction, margin, phase_crossover_frequencies, \
stability_margins, disk_margins, tf, ss
from control.exception import slycot_check

s = TransferFunction.s

Expand Down Expand Up @@ -394,6 +393,7 @@ def test_siso_disk_margin():
DM = disk_margins(L, omega, skew=1.0)[0]
assert_allclose([DM], [SM], atol=0.01)

@pytest.mark.slycot
def test_mimo_disk_margin():
# Frequencies of interest
omega = np.logspace(-1, 3, 1001)
Expand All @@ -404,23 +404,32 @@ def test_mimo_disk_margin():
Lo = P * K # loop transfer function, broken at plant output
Li = K * P # loop transfer function, broken at plant input

if slycot_check():
# Balanced (S - T) disk-based stability margins at plant output
# Balanced (S - T) disk-based stability margins at plant output
DMo, DGMo, DPMo = disk_margins(Lo, omega, skew=0.0)
assert_allclose([DMo], [0.3754], atol=0.1) # disk margin of 0.3754
assert_allclose([DGMo], [3.3], atol=0.1) # disk-based gain margin of 3.3 dB
assert_allclose([DPMo], [21.26], atol=0.1) # disk-based phase margin of 21.26 deg

# Balanced (S - T) disk-based stability margins at plant input
DMi, DGMi, DPMi = disk_margins(Li, omega, skew=0.0)
assert_allclose([DMi], [0.3754], atol=0.1) # disk margin of 0.3754
assert_allclose([DGMi], [3.3], atol=0.1) # disk-based gain margin of 3.3 dB
assert_allclose([DPMi], [21.26], atol=0.1) # disk-based phase margin of 21.26 deg


@pytest.mark.noslycot
def test_mimo_disk_margin_exception():
# Slycot not installed. Should throw exception.
# Frequencies of interest
omega = np.logspace(-1, 3, 1001)

# Loop transfer gain
P = ss([[0, 10], [-10, 0]], np.eye(2), [[1, 10], [-10, 1]], 0) # plant
K = ss([], [], [], [[1, -2], [0, 1]]) # controller
Lo = P * K # loop transfer function, broken at plant output
with pytest.raises(ControlMIMONotImplemented,\
match="Need slycot to compute MIMO disk_margins"):
DMo, DGMo, DPMo = disk_margins(Lo, omega, skew=0.0)
assert_allclose([DMo], [0.3754], atol=0.1) # disk margin of 0.3754
assert_allclose([DGMo], [3.3], atol=0.1) # disk-based gain margin of 3.3 dB
assert_allclose([DPMo], [21.26], atol=0.1) # disk-based phase margin of 21.26 deg

# Balanced (S - T) disk-based stability margins at plant input
DMi, DGMi, DPMi = disk_margins(Li, omega, skew=0.0)
assert_allclose([DMi], [0.3754], atol=0.1) # disk margin of 0.3754
assert_allclose([DGMi], [3.3], atol=0.1) # disk-based gain margin of 3.3 dB
assert_allclose([DPMi], [21.26], atol=0.1) # disk-based phase margin of 21.26 deg
else:
# Slycot not installed. Should throw exception.
with pytest.raises(ControlMIMONotImplemented,\
match="Need slycot to compute MIMO disk_margins"):
DMo, DGMo, DPMo = disk_margins(Lo, omega, skew=0.0)

def test_siso_disk_margin_return_all():
# Frequencies of interest
Expand All @@ -439,6 +448,8 @@ def test_siso_disk_margin_return_all():
assert_allclose([DPM[np.argmin(DM)]], [25.8],\
atol=0.1) # disk-based phase margin of 25.8 deg


@pytest.mark.slycot
def test_mimo_disk_margin_return_all():
# Frequencies of interest
omega = np.logspace(-1, 3, 1001)
Expand All @@ -450,29 +461,23 @@ def test_mimo_disk_margin_return_all():
Lo = P * K # loop transfer function, broken at plant output
Li = K * P # loop transfer function, broken at plant input

if slycot_check():
# Balanced (S - T) disk-based stability margins at plant output
DMo, DGMo, DPMo = disk_margins(Lo, omega, skew=0.0, returnall=True)
assert_allclose([omega[np.argmin(DMo)]], [omega[0]],\
atol=0.01) # sensitivity peak at 0 rad/s (or smallest provided)
assert_allclose([min(DMo)], [0.3754], atol=0.1) # disk margin of 0.3754
assert_allclose([DGMo[np.argmin(DMo)]], [3.3],\
atol=0.1) # disk-based gain margin of 3.3 dB
assert_allclose([DPMo[np.argmin(DMo)]], [21.26],\
atol=0.1) # disk-based phase margin of 21.26 deg

# Balanced (S - T) disk-based stability margins at plant input
DMi, DGMi, DPMi = disk_margins(Li, omega, skew=0.0, returnall=True)
assert_allclose([omega[np.argmin(DMi)]], [omega[0]],\
atol=0.01) # sensitivity peak at 0 rad/s (or smallest provided)
assert_allclose([min(DMi)], [0.3754],\
atol=0.1) # disk margin of 0.3754
assert_allclose([DGMi[np.argmin(DMi)]], [3.3],\
atol=0.1) # disk-based gain margin of 3.3 dB
assert_allclose([DPMi[np.argmin(DMi)]], [21.26],\
atol=0.1) # disk-based phase margin of 21.26 deg
else:
# Slycot not installed. Should throw exception.
with pytest.raises(ControlMIMONotImplemented,\
match="Need slycot to compute MIMO disk_margins"):
DMo, DGMo, DPMo = disk_margins(Lo, omega, skew=0.0, returnall=True)
# Balanced (S - T) disk-based stability margins at plant output
DMo, DGMo, DPMo = disk_margins(Lo, omega, skew=0.0, returnall=True)
assert_allclose([omega[np.argmin(DMo)]], [omega[0]],\
atol=0.01) # sensitivity peak at 0 rad/s (or smallest provided)
assert_allclose([min(DMo)], [0.3754], atol=0.1) # disk margin of 0.3754
assert_allclose([DGMo[np.argmin(DMo)]], [3.3],\
atol=0.1) # disk-based gain margin of 3.3 dB
assert_allclose([DPMo[np.argmin(DMo)]], [21.26],\
atol=0.1) # disk-based phase margin of 21.26 deg

# Balanced (S - T) disk-based stability margins at plant input
DMi, DGMi, DPMi = disk_margins(Li, omega, skew=0.0, returnall=True)
assert_allclose([omega[np.argmin(DMi)]], [omega[0]],\
atol=0.01) # sensitivity peak at 0 rad/s (or smallest provided)
assert_allclose([min(DMi)], [0.3754],\
atol=0.1) # disk margin of 0.3754
assert_allclose([DGMi[np.argmin(DMi)]], [3.3],\
atol=0.1) # disk-based gain margin of 3.3 dB
assert_allclose([DPMi[np.argmin(DMi)]], [21.26],\
atol=0.1) # disk-based phase margin of 21.26 deg
Loading
Loading