Skip to content

Commit c3453ad

Browse files
authored
Merge faaa40e into 2435a6a
2 parents 2435a6a + faaa40e commit c3453ad

File tree

12 files changed

+289
-211
lines changed

12 files changed

+289
-211
lines changed

control/tests/conftest.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77
import control
88

99
def pytest_runtest_setup(item):
10-
if (not control.exception.slycot_check()
11-
and any(mark.name == 'slycot'
12-
for mark in item.iter_markers())):
13-
pytest.skip("slycot not installed")
10+
if not control.exception.slycot_check():
11+
if any(mark.name == 'slycot'
12+
for mark in item.iter_markers()):
13+
pytest.skip("slycot not installed")
14+
elif any(mark.name == 'noslycot'
15+
for mark in item.iter_markers()):
16+
# used, e.g., for tests checking ControlSlycot
17+
pytest.skip("slycot installed")
1418

1519
if (not control.exception.cvxopt_check()
1620
and any(mark.name == 'cvxopt'

control/tests/convert_test.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,13 @@
2121
from control import rss, ss, ss2tf, tf, tf2ss
2222
from control.statefbk import ctrb, obsv
2323
from control.freqplot import bode
24-
from control.exception import slycot_check, ControlMIMONotImplemented
24+
from control.exception import ControlMIMONotImplemented
2525

2626

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

3532

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

5047
@pytest.mark.usefixtures("legacy_plot_signature")
5148
@pytest.mark.parametrize("states", range(1, maxStates))
52-
@pytest.mark.parametrize("inputs", range(1, maxIO))
53-
@pytest.mark.parametrize("outputs", range(1, maxIO))
49+
# If slycot is not installed, just check SISO
50+
@pytest.mark.parametrize("inputs",
51+
[1] + [pytest.param(i, marks=pytest.mark.slycot)
52+
for i in range(2, 5)])
53+
@pytest.mark.parametrize("outputs",
54+
[1] + [pytest.param(i, marks=pytest.mark.slycot)
55+
for i in range(2, 5)])
5456
def testConvert(self, fixedseed, states, inputs, outputs):
5557
"""Test state space to transfer function conversion.
5658
@@ -147,7 +149,11 @@ def testConvert(self, fixedseed, states, inputs, outputs):
147149
np.testing.assert_array_almost_equal(
148150
ssorig_imag, tfxfrm_imag, decimal=5)
149151

150-
def testConvertMIMO(self):
152+
153+
@pytest.mark.parametrize('have_slycot',
154+
[pytest.param(True, marks=pytest.mark.slycot),
155+
pytest.param(False, marks=pytest.mark.noslycot)])
156+
def testConvertMIMO(self, have_slycot):
151157
"""Test state space to transfer function conversion.
152158
153159
Do a MIMO conversion and make sure that it is processed
@@ -165,7 +171,7 @@ def testConvertMIMO(self):
165171
[0.008, 1.39, 48.78]]])
166172

167173
# Convert to state space and look for an error
168-
if (not slycot_check()):
174+
if not have_slycot:
169175
with pytest.raises(ControlMIMONotImplemented):
170176
tf2ss(tsys)
171177
else:

control/tests/interconnect_test.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,12 @@ def test_summation_exceptions():
5656
ct.summing_junction('u', 'y', dimension=False)
5757

5858

59-
@pytest.mark.parametrize("dim", [1, 3])
59+
@pytest.mark.parametrize("dim",
60+
[1, pytest.param(3, marks=pytest.mark.slycot)])
6061
def test_interconnect_implicit(dim):
6162
"""Test the use of implicit connections in interconnect()"""
6263
import random
6364

64-
if dim != 1 and not ct.slycot_check():
65-
pytest.xfail("slycot not installed")
66-
6765
# System definition
6866
P = ct.rss(2, dim, dim, strictly_proper=True, name='P')
6967

control/tests/lti_test.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import control as ct
99
from control import NonlinearIOSystem, c2d, common_timebase, isctime, \
1010
isdtime, issiso, ss, tf, tf2ss
11-
from control.exception import slycot_check
1211
from control.lti import LTI, bandwidth, damp, dcgain, evalfr, poles, zeros
1312

1413

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

191+
def p(*args):
192+
# convenience for parametrize below
193+
return pytest.param(*args, marks=pytest.mark.slycot)
194+
192195
@pytest.mark.usefixtures("editsdefaults")
193196
@pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.frd])
194197
@pytest.mark.parametrize("nstate, nout, ninp, omega, squeeze, shape", [
@@ -201,26 +204,26 @@ def test_isdtime(self, objfun, arg, dt, ref, strictref):
201204
[3, 1, 1, 0.1, False, (1, 1)],
202205
[3, 1, 1, [0.1], False, (1, 1, 1)],
203206
[3, 1, 1, [0.1, 1, 10], False, (1, 1, 3)],
204-
[1, 2, 1, 0.1, None, (2, 1)], # SIMO
205-
[1, 2, 1, [0.1], None, (2, 1, 1)],
206-
[1, 2, 1, [0.1, 1, 10], None, (2, 1, 3)],
207-
[2, 2, 1, 0.1, True, (2,)],
208-
[2, 2, 1, [0.1], True, (2,)],
209-
[3, 2, 1, 0.1, False, (2, 1)],
210-
[3, 2, 1, [0.1], False, (2, 1, 1)],
211-
[3, 2, 1, [0.1, 1, 10], False, (2, 1, 3)],
212-
[1, 1, 2, [0.1, 1, 10], None, (1, 2, 3)], # MISO
213-
[2, 1, 2, [0.1, 1, 10], True, (2, 3)],
214-
[3, 1, 2, [0.1, 1, 10], False, (1, 2, 3)],
215-
[1, 1, 2, 0.1, None, (1, 2)],
216-
[1, 1, 2, 0.1, True, (2,)],
217-
[1, 1, 2, 0.1, False, (1, 2)],
218-
[1, 2, 2, [0.1, 1, 10], None, (2, 2, 3)], # MIMO
219-
[2, 2, 2, [0.1, 1, 10], True, (2, 2, 3)],
220-
[3, 2, 2, [0.1, 1, 10], False, (2, 2, 3)],
221-
[1, 2, 2, 0.1, None, (2, 2)],
222-
[2, 2, 2, 0.1, True, (2, 2)],
223-
[3, 2, 2, 0.1, False, (2, 2)],
207+
p(1, 2, 1, 0.1, None, (2, 1)),
208+
p(1, 2, 1, [0.1], None, (2, 1, 1)),
209+
p(1, 2, 1, [0.1, 1, 10], None, (2, 1, 3)),
210+
p(2, 2, 1, 0.1, True, (2,)),
211+
p(2, 2, 1, [0.1], True, (2,)),
212+
p(3, 2, 1, 0.1, False, (2, 1)),
213+
p(3, 2, 1, [0.1], False, (2, 1, 1)),
214+
p(3, 2, 1, [0.1, 1, 10], False, (2, 1, 3)),
215+
p(1, 1, 2, [0.1, 1, 10], None, (1, 2, 3)), # MISO
216+
p(2, 1, 2, [0.1, 1, 10], True, (2, 3)),
217+
p(3, 1, 2, [0.1, 1, 10], False, (1, 2, 3)),
218+
p(1, 1, 2, 0.1, None, (1, 2)),
219+
p(1, 1, 2, 0.1, True, (2,)),
220+
p(1, 1, 2, 0.1, False, (1, 2)),
221+
p(1, 2, 2, [0.1, 1, 10], None, (2, 2, 3)), # MIMO
222+
p(2, 2, 2, [0.1, 1, 10], True, (2, 2, 3)),
223+
p(3, 2, 2, [0.1, 1, 10], False, (2, 2, 3)),
224+
p(1, 2, 2, 0.1, None, (2, 2)),
225+
p(2, 2, 2, 0.1, True, (2, 2)),
226+
p(3, 2, 2, 0.1, False, (2, 2)),
224227
])
225228
@pytest.mark.parametrize("omega_type", ["numpy", "native"])
226229
def test_squeeze(self, fcn, nstate, nout, ninp, omega, squeeze, shape,
@@ -229,9 +232,6 @@ def test_squeeze(self, fcn, nstate, nout, ninp, omega, squeeze, shape,
229232
# Create the system to be tested
230233
if fcn == ct.frd:
231234
sys = fcn(ct.rss(nstate, nout, ninp), [1e-2, 1e-1, 1, 1e1, 1e2])
232-
elif fcn == ct.tf and (nout > 1 or ninp > 1) and not slycot_check():
233-
pytest.skip("Conversion of MIMO systems to transfer functions "
234-
"requires slycot.")
235235
else:
236236
sys = fcn(ct.rss(nstate, nout, ninp))
237237

control/tests/margin_test.py

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from control import ControlMIMONotImplemented, FrequencyResponseData, \
1616
StateSpace, TransferFunction, margin, phase_crossover_frequencies, \
1717
stability_margins, disk_margins, tf, ss
18-
from control.exception import slycot_check
1918

2019
s = TransferFunction.s
2120

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

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

407-
if slycot_check():
408-
# Balanced (S - T) disk-based stability margins at plant output
407+
# Balanced (S - T) disk-based stability margins at plant output
408+
DMo, DGMo, DPMo = disk_margins(Lo, omega, skew=0.0)
409+
assert_allclose([DMo], [0.3754], atol=0.1) # disk margin of 0.3754
410+
assert_allclose([DGMo], [3.3], atol=0.1) # disk-based gain margin of 3.3 dB
411+
assert_allclose([DPMo], [21.26], atol=0.1) # disk-based phase margin of 21.26 deg
412+
413+
# Balanced (S - T) disk-based stability margins at plant input
414+
DMi, DGMi, DPMi = disk_margins(Li, omega, skew=0.0)
415+
assert_allclose([DMi], [0.3754], atol=0.1) # disk margin of 0.3754
416+
assert_allclose([DGMi], [3.3], atol=0.1) # disk-based gain margin of 3.3 dB
417+
assert_allclose([DPMi], [21.26], atol=0.1) # disk-based phase margin of 21.26 deg
418+
419+
420+
@pytest.mark.noslycot
421+
def test_mimo_disk_margin_exception():
422+
# Slycot not installed. Should throw exception.
423+
# Frequencies of interest
424+
omega = np.logspace(-1, 3, 1001)
425+
426+
# Loop transfer gain
427+
P = ss([[0, 10], [-10, 0]], np.eye(2), [[1, 10], [-10, 1]], 0) # plant
428+
K = ss([], [], [], [[1, -2], [0, 1]]) # controller
429+
Lo = P * K # loop transfer function, broken at plant output
430+
with pytest.raises(ControlMIMONotImplemented,\
431+
match="Need slycot to compute MIMO disk_margins"):
409432
DMo, DGMo, DPMo = disk_margins(Lo, omega, skew=0.0)
410-
assert_allclose([DMo], [0.3754], atol=0.1) # disk margin of 0.3754
411-
assert_allclose([DGMo], [3.3], atol=0.1) # disk-based gain margin of 3.3 dB
412-
assert_allclose([DPMo], [21.26], atol=0.1) # disk-based phase margin of 21.26 deg
413-
414-
# Balanced (S - T) disk-based stability margins at plant input
415-
DMi, DGMi, DPMi = disk_margins(Li, omega, skew=0.0)
416-
assert_allclose([DMi], [0.3754], atol=0.1) # disk margin of 0.3754
417-
assert_allclose([DGMi], [3.3], atol=0.1) # disk-based gain margin of 3.3 dB
418-
assert_allclose([DPMi], [21.26], atol=0.1) # disk-based phase margin of 21.26 deg
419-
else:
420-
# Slycot not installed. Should throw exception.
421-
with pytest.raises(ControlMIMONotImplemented,\
422-
match="Need slycot to compute MIMO disk_margins"):
423-
DMo, DGMo, DPMo = disk_margins(Lo, omega, skew=0.0)
424433

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

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

453-
if slycot_check():
454-
# Balanced (S - T) disk-based stability margins at plant output
455-
DMo, DGMo, DPMo = disk_margins(Lo, omega, skew=0.0, returnall=True)
456-
assert_allclose([omega[np.argmin(DMo)]], [omega[0]],\
457-
atol=0.01) # sensitivity peak at 0 rad/s (or smallest provided)
458-
assert_allclose([min(DMo)], [0.3754], atol=0.1) # disk margin of 0.3754
459-
assert_allclose([DGMo[np.argmin(DMo)]], [3.3],\
460-
atol=0.1) # disk-based gain margin of 3.3 dB
461-
assert_allclose([DPMo[np.argmin(DMo)]], [21.26],\
462-
atol=0.1) # disk-based phase margin of 21.26 deg
463-
464-
# Balanced (S - T) disk-based stability margins at plant input
465-
DMi, DGMi, DPMi = disk_margins(Li, omega, skew=0.0, returnall=True)
466-
assert_allclose([omega[np.argmin(DMi)]], [omega[0]],\
467-
atol=0.01) # sensitivity peak at 0 rad/s (or smallest provided)
468-
assert_allclose([min(DMi)], [0.3754],\
469-
atol=0.1) # disk margin of 0.3754
470-
assert_allclose([DGMi[np.argmin(DMi)]], [3.3],\
471-
atol=0.1) # disk-based gain margin of 3.3 dB
472-
assert_allclose([DPMi[np.argmin(DMi)]], [21.26],\
473-
atol=0.1) # disk-based phase margin of 21.26 deg
474-
else:
475-
# Slycot not installed. Should throw exception.
476-
with pytest.raises(ControlMIMONotImplemented,\
477-
match="Need slycot to compute MIMO disk_margins"):
478-
DMo, DGMo, DPMo = disk_margins(Lo, omega, skew=0.0, returnall=True)
464+
# Balanced (S - T) disk-based stability margins at plant output
465+
DMo, DGMo, DPMo = disk_margins(Lo, omega, skew=0.0, returnall=True)
466+
assert_allclose([omega[np.argmin(DMo)]], [omega[0]],\
467+
atol=0.01) # sensitivity peak at 0 rad/s (or smallest provided)
468+
assert_allclose([min(DMo)], [0.3754], atol=0.1) # disk margin of 0.3754
469+
assert_allclose([DGMo[np.argmin(DMo)]], [3.3],\
470+
atol=0.1) # disk-based gain margin of 3.3 dB
471+
assert_allclose([DPMo[np.argmin(DMo)]], [21.26],\
472+
atol=0.1) # disk-based phase margin of 21.26 deg
473+
474+
# Balanced (S - T) disk-based stability margins at plant input
475+
DMi, DGMi, DPMi = disk_margins(Li, omega, skew=0.0, returnall=True)
476+
assert_allclose([omega[np.argmin(DMi)]], [omega[0]],\
477+
atol=0.01) # sensitivity peak at 0 rad/s (or smallest provided)
478+
assert_allclose([min(DMi)], [0.3754],\
479+
atol=0.1) # disk margin of 0.3754
480+
assert_allclose([DGMi[np.argmin(DMi)]], [3.3],\
481+
atol=0.1) # disk-based gain margin of 3.3 dB
482+
assert_allclose([DPMi[np.argmin(DMi)]], [21.26],\
483+
atol=0.1) # disk-based phase margin of 21.26 deg

0 commit comments

Comments
 (0)