Skip to content

Commit 25747c7

Browse files
authored
Merge pull request #122 from murrayrm/noslycot
Allow unit tests to run without slycot
2 parents 99cc2e9 + 7de3cc8 commit 25747c7

11 files changed

+133
-64
lines changed

control/statesp.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,10 @@ def _convertToStateSpace(sys, **kw):
670670
ssout[3][:sys.outputs, :states],
671671
ssout[4], sys.dt)
672672
except ImportError:
673+
# If slycot is not available, use signal.lti (SISO only)
674+
if (sys.inputs != 1 or sys.outputs != 1):
675+
raise TypeError("No support for MIMO without slycot")
676+
673677
# TODO: do we want to squeeze first and check dimenations?
674678
# I think this will fail if num and den aren't 1-D after
675679
# the squeeze

control/tests/convert_test.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from control.statefbk import ctrb, obsv
2323
from control.freqplot import bode
2424
from control.matlab import tf
25-
25+
from control.exception import slycot_check
2626

2727
class TestConvert(unittest.TestCase):
2828
"""Test state space and transfer function conversions."""
@@ -35,7 +35,8 @@ def setUp(self):
3535
# Maximum number of states to test + 1
3636
self.maxStates = 4
3737
# Maximum number of inputs and outputs to test + 1
38-
self.maxIO = 5
38+
# If slycot is not installed, just check SISO
39+
self.maxIO = 5 if slycot_check() else 2
3940
# Set to True to print systems to the output.
4041
self.debug = False
4142
# get consistent results
@@ -161,6 +162,29 @@ def testConvert(self):
161162
np.testing.assert_array_almost_equal( \
162163
ssorig_imag, tfxfrm_imag)
163164

165+
def testConvertMIMO(self):
166+
"""Test state space to transfer function conversion."""
167+
verbose = self.debug
168+
169+
# Do a MIMO conversation and make sure that it is processed
170+
# correctly both with and without slycot
171+
#
172+
# Example from issue #120, jgoppert
173+
import control
174+
175+
# Set up a transfer function (should always work)
176+
tfcn = control.tf([[[-235, 1.146e4],
177+
[-235, 1.146E4],
178+
[-235, 1.146E4, 0]]],
179+
[[[1, 48.78, 0],
180+
[1, 48.78, 0, 0],
181+
[0.008, 1.39, 48.78]]])
182+
183+
# Convert to state space and look for an error
184+
if (not slycot_check()):
185+
self.assertRaises(TypeError, control.tf2ss, tfcn)
186+
187+
164188
def suite():
165189
return unittest.TestLoader().loadTestsFromTestCase(TestConvert)
166190

control/tests/frd_test.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from control.frdata import FRD, _convertToFRD
1212
from control import bdalg
1313
from control import freqplot
14+
from control.exception import slycot_check
1415
import matplotlib.pyplot as plt
1516

1617

@@ -179,6 +180,7 @@ def testNyquist(self):
179180
freqplot.nyquist(f1, f1.omega)
180181
# plt.savefig('/dev/null', format='svg')
181182

183+
@unittest.skipIf(not slycot_check(), "slycot not installed")
182184
def testMIMO(self):
183185
sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]],
184186
[[1.0, 0.0], [0.0, 1.0]],
@@ -193,6 +195,7 @@ def testMIMO(self):
193195
sys.freqresp([0.1, 1.0, 10])[1],
194196
f1.freqresp([0.1, 1.0, 10])[1])
195197

198+
@unittest.skipIf(not slycot_check(), "slycot not installed")
196199
def testMIMOfb(self):
197200
sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]],
198201
[[1.0, 0.0], [0.0, 1.0]],
@@ -208,6 +211,7 @@ def testMIMOfb(self):
208211
f1.freqresp([0.1, 1.0, 10])[1],
209212
f2.freqresp([0.1, 1.0, 10])[1])
210213

214+
@unittest.skipIf(not slycot_check(), "slycot not installed")
211215
def testMIMOfb2(self):
212216
sys = StateSpace(np.matrix('-2.0 0 0; 0 -1 1; 0 0 -3'),
213217
np.matrix('1.0 0; 0 0; 0 1'),
@@ -223,6 +227,7 @@ def testMIMOfb2(self):
223227
f1.freqresp([0.1, 1.0, 10])[1],
224228
f2.freqresp([0.1, 1.0, 10])[1])
225229

230+
@unittest.skipIf(not slycot_check(), "slycot not installed")
226231
def testMIMOMult(self):
227232
sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]],
228233
[[1.0, 0.0], [0.0, 1.0]],
@@ -238,6 +243,7 @@ def testMIMOMult(self):
238243
(f1*f2).freqresp([0.1, 1.0, 10])[1],
239244
(sys*sys).freqresp([0.1, 1.0, 10])[1])
240245

246+
@unittest.skipIf(not slycot_check(), "slycot not installed")
241247
def testMIMOSmooth(self):
242248
sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]],
243249
[[1.0, 0.0], [0.0, 1.0]],

control/tests/freqresp_test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import numpy as np
1111
from control.statesp import StateSpace
1212
from control.matlab import ss, tf, bode
13+
from control.exception import slycot_check
1314
import matplotlib.pyplot as plt
1415

1516
class TestFreqresp(unittest.TestCase):
@@ -42,6 +43,7 @@ def test_doubleint(self):
4243
sys = ss(A, B, C, D);
4344
bode(sys);
4445

46+
@unittest.skipIf(not slycot_check(), "slycot not installed")
4547
def test_mimo(self):
4648
# MIMO
4749
B = np.matrix('1,0;0,1')

control/tests/matlab_test.py

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import scipy as sp
1616
from control.matlab import *
1717
from control.frdata import FRD
18+
from control.exception import slycot_check
1819
import warnings
1920

2021
# for running these through Matlab or Octave
@@ -165,12 +166,13 @@ def testStep(self):
165166
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
166167
np.testing.assert_array_almost_equal(tout, t)
167168

168-
#Test MIMO system, which contains ``siso_ss1`` twice
169-
sys = self.mimo_ss1
170-
y_00, _t = step(sys, T=t, input=0, output=0)
171-
y_11, _t = step(sys, T=t, input=1, output=1)
172-
np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4)
173-
np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4)
169+
if slycot_check():
170+
# Test MIMO system, which contains ``siso_ss1`` twice
171+
sys = self.mimo_ss1
172+
y_00, _t = step(sys, T=t, input=0, output=0)
173+
y_11, _t = step(sys, T=t, input=1, output=1)
174+
np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4)
175+
np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4)
174176

175177
def testImpulse(self):
176178
t = np.linspace(0, 1, 10)
@@ -206,12 +208,13 @@ def testImpulse(self):
206208
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
207209
np.testing.assert_array_almost_equal(tout, t)
208210

209-
#Test MIMO system, which contains ``siso_ss1`` twice
210-
sys = self.mimo_ss1
211-
y_00, _t = impulse(sys, T=t, input=0, output=0)
212-
y_11, _t = impulse(sys, T=t, input=1, output=1)
213-
np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4)
214-
np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4)
211+
if slycot_check():
212+
#Test MIMO system, which contains ``siso_ss1`` twice
213+
sys = self.mimo_ss1
214+
y_00, _t = impulse(sys, T=t, input=0, output=0)
215+
y_11, _t = impulse(sys, T=t, input=1, output=1)
216+
np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4)
217+
np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4)
215218

216219
def testInitial(self):
217220
#Test SISO system
@@ -229,13 +232,14 @@ def testInitial(self):
229232
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
230233
np.testing.assert_array_almost_equal(tout, t)
231234

232-
#Test MIMO system, which contains ``siso_ss1`` twice
233-
sys = self.mimo_ss1
234-
x0 = np.matrix(".5; 1.; .5; 1.")
235-
y_00, _t = initial(sys, T=t, X0=x0, input=0, output=0)
236-
y_11, _t = initial(sys, T=t, X0=x0, input=1, output=1)
237-
np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4)
238-
np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4)
235+
if slycot_check():
236+
#Test MIMO system, which contains ``siso_ss1`` twice
237+
sys = self.mimo_ss1
238+
x0 = np.matrix(".5; 1.; .5; 1.")
239+
y_00, _t = initial(sys, T=t, X0=x0, input=0, output=0)
240+
y_11, _t = initial(sys, T=t, X0=x0, input=1, output=1)
241+
np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4)
242+
np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4)
239243

240244
def testLsim(self):
241245
t = np.linspace(0, 1, 10)
@@ -259,18 +263,19 @@ def testLsim(self):
259263
yout, _t, _xout = lsim(self.siso_ss1, u, t, x0)
260264
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
261265

262-
#Test MIMO system, which contains ``siso_ss1`` twice
263-
#first system: initial value, second system: step response
264-
u = np.array([[0., 1.], [0, 1], [0, 1], [0, 1], [0, 1],
265-
[0, 1], [0, 1], [0, 1], [0, 1], [0, 1]])
266-
x0 = np.matrix(".5; 1; 0; 0")
267-
youttrue = np.array([[11., 9.], [8.1494, 17.6457], [5.9361, 24.7072],
268-
[4.2258, 30.4855], [2.9118, 35.2234],
269-
[1.9092, 39.1165], [1.1508, 42.3227],
270-
[0.5833, 44.9694], [0.1645, 47.1599],
271-
[-0.1391, 48.9776]])
272-
yout, _t, _xout = lsim(self.mimo_ss1, u, t, x0)
273-
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
266+
if slycot_check():
267+
#Test MIMO system, which contains ``siso_ss1`` twice
268+
#first system: initial value, second system: step response
269+
u = np.array([[0., 1.], [0, 1], [0, 1], [0, 1], [0, 1],
270+
[0, 1], [0, 1], [0, 1], [0, 1], [0, 1]])
271+
x0 = np.matrix(".5; 1; 0; 0")
272+
youttrue = np.array([[11., 9.], [8.1494, 17.6457],
273+
[5.9361, 24.7072], [4.2258, 30.4855],
274+
[2.9118, 35.2234], [1.9092, 39.1165],
275+
[1.1508, 42.3227], [0.5833, 44.9694],
276+
[0.1645, 47.1599], [-0.1391, 48.9776]])
277+
yout, _t, _xout = lsim(self.mimo_ss1, u, t, x0)
278+
np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
274279

275280
def testMargin(self):
276281
#! TODO: check results to make sure they are OK
@@ -310,11 +315,12 @@ def testDcgain(self):
310315
gain_sim],
311316
[59, 59, 59, 59, 59])
312317

313-
# Test with MIMO system, which contains ``siso_ss1`` twice
314-
gain_mimo = dcgain(self.mimo_ss1)
315-
# print('gain_mimo: \n', gain_mimo)
316-
np.testing.assert_array_almost_equal(gain_mimo, [[59., 0 ],
317-
[0, 59.]])
318+
if slycot_check():
319+
# Test with MIMO system, which contains ``siso_ss1`` twice
320+
gain_mimo = dcgain(self.mimo_ss1)
321+
# print('gain_mimo: \n', gain_mimo)
322+
np.testing.assert_array_almost_equal(gain_mimo, [[59., 0 ],
323+
[0, 59.]])
318324

319325
def testBode(self):
320326
bode(self.siso_ss1)
@@ -370,29 +376,35 @@ def testEvalfr(self):
370376
evalfr(self.siso_tf1, w)
371377
evalfr(self.siso_tf2, w)
372378
evalfr(self.siso_tf3, w)
373-
np.testing.assert_array_almost_equal(
374-
evalfr(self.mimo_ss1, w),
375-
np.array( [[44.8-21.4j, 0.], [0., 44.8-21.4j]]))
379+
if slycot_check():
380+
np.testing.assert_array_almost_equal(
381+
evalfr(self.mimo_ss1, w),
382+
np.array( [[44.8-21.4j, 0.], [0., 44.8-21.4j]]))
376383

384+
@unittest.skipIf(not slycot_check(), "slycot not installed")
377385
def testHsvd(self):
378386
hsvd(self.siso_ss1)
379387
hsvd(self.siso_ss2)
380388
hsvd(self.siso_ss3)
381389

390+
@unittest.skipIf(not slycot_check(), "slycot not installed")
382391
def testBalred(self):
383392
balred(self.siso_ss1, 1)
384393
balred(self.siso_ss2, 2)
385394
balred(self.siso_ss3, [2, 2])
386395

396+
@unittest.skipIf(not slycot_check(), "slycot not installed")
387397
def testModred(self):
388398
modred(self.siso_ss1, [1])
389399
modred(self.siso_ss2 * self.siso_ss3, [0, 1])
390400
modred(self.siso_ss3, [1], 'matchdc')
391401
modred(self.siso_ss3, [1], 'truncate')
392402

403+
@unittest.skipIf(not slycot_check(), "slycot not installed")
393404
def testPlace(self):
394405
place(self.siso_ss1.A, self.siso_ss1.B, [-2, -2])
395406

407+
@unittest.skipIf(not slycot_check(), "slycot not installed")
396408
def testLQR(self):
397409
(K, S, E) = lqr(self.siso_ss1.A, self.siso_ss1.B, np.eye(2), np.eye(1))
398410
(K, S, E) = lqr(self.siso_ss2.A, self.siso_ss2.B, np.eye(3), \
@@ -416,6 +428,7 @@ def testObsv(self):
416428
obsv(self.siso_ss1.A, self.siso_ss1.C)
417429
obsv(self.siso_ss2.A, self.siso_ss2.C)
418430

431+
@unittest.skipIf(not slycot_check(), "slycot not installed")
419432
def testGram(self):
420433
gram(self.siso_ss1, 'c')
421434
gram(self.siso_ss2, 'c')
@@ -452,6 +465,7 @@ def testSISOssdata(self):
452465
for i in range(len(ssdata_1)):
453466
np.testing.assert_array_almost_equal(ssdata_1[i], ssdata_2[i])
454467

468+
@unittest.skipIf(not slycot_check(), "slycot not installed")
455469
def testMIMOssdata(self):
456470
m = (self.mimo_ss1.A, self.mimo_ss1.B, self.mimo_ss1.C, self.mimo_ss1.D)
457471
ssdata_1 = ssdata(self.mimo_ss1);
@@ -532,6 +546,7 @@ def testFRD(self):
532546
frd2 = frd(frd1.fresp[0,0,:], omega)
533547
assert isinstance(frd2, FRD)
534548

549+
@unittest.skipIf(not slycot_check(), "slycot not installed")
535550
def testMinreal(self, verbose=False):
536551
"""Test a minreal model reduction"""
537552
#A = [-2, 0.5, 0; 0.5, -0.3, 0; 0, 0, -0.1]

control/tests/minreal_test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
from control.statesp import StateSpace
1111
from control.xferfcn import TransferFunction
1212
from itertools import permutations
13+
from control.exception import slycot_check
1314

15+
@unittest.skipIf(not slycot_check(), "slycot not installed")
1416
class TestMinreal(unittest.TestCase):
1517
"""Tests for the StateSpace class."""
1618

control/tests/statefbk_test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,13 @@ def check_LQR(self, K, S, poles, Q, R):
144144
np.testing.assert_array_almost_equal(poles, poles_expected)
145145

146146

147+
@unittest.skipIf(not slycot_check(), "slycot not installed")
147148
def test_LQR_integrator(self):
148149
A, B, Q, R = 0., 1., 10., 2.
149150
K, S, poles = lqr(A, B, Q, R)
150151
self.check_LQR(K, S, poles, Q, R)
151152

153+
@unittest.skipIf(not slycot_check(), "slycot not installed")
152154
def test_LQR_3args(self):
153155
sys = ss(0., 1., 1., 0.)
154156
Q, R = 10., 2.

control/tests/statesp_test.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from control import matlab
1111
from control.statesp import StateSpace, _convertToStateSpace
1212
from control.xferfcn import TransferFunction
13+
from control.exception import slycot_check
1314

1415
class TestStateSpace(unittest.TestCase):
1516
"""Tests for the StateSpace class."""
@@ -114,6 +115,7 @@ def testEvalFr(self):
114115

115116
np.testing.assert_almost_equal(sys.evalfr(1.), resp)
116117

118+
@unittest.skipIf(not slycot_check(), "slycot not installed")
117119
def testFreqResp(self):
118120
"""Evaluate the frequency response at multiple frequencies."""
119121

@@ -139,6 +141,7 @@ def testFreqResp(self):
139141
np.testing.assert_almost_equal(phase, truephase)
140142
np.testing.assert_equal(omega, trueomega)
141143

144+
@unittest.skipIf(not slycot_check(), "slycot not installed")
142145
def testMinreal(self):
143146
"""Test a minreal model reduction"""
144147
#A = [-2, 0.5, 0; 0.5, -0.3, 0; 0, 0, -0.1]

0 commit comments

Comments
 (0)