diff --git a/control/statesp.py b/control/statesp.py index 80da2a5f8..b766bf917 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -667,6 +667,10 @@ def _convertToStateSpace(sys, **kw): ssout[3][:sys.outputs, :states], ssout[4], sys.dt) except ImportError: + # If slycot is not available, use signal.lti (SISO only) + if (sys.inputs != 1 or sys.outputs != 1): + raise TypeError("No support for MIMO without slycot") + # TODO: do we want to squeeze first and check dimenations? # I think this will fail if num and den aren't 1-D after # the squeeze diff --git a/control/tests/convert_test.py b/control/tests/convert_test.py index c2c2d8e47..775118909 100644 --- a/control/tests/convert_test.py +++ b/control/tests/convert_test.py @@ -22,7 +22,7 @@ from control.statefbk import ctrb, obsv from control.freqplot import bode from control.matlab import tf - +from control.exception import slycot_check class TestConvert(unittest.TestCase): """Test state space and transfer function conversions.""" @@ -35,7 +35,8 @@ def setUp(self): # Maximum number of states to test + 1 self.maxStates = 4 # Maximum number of inputs and outputs to test + 1 - self.maxIO = 5 + # If slycot is not installed, just check SISO + self.maxIO = 5 if slycot_check() else 2 # Set to True to print systems to the output. self.debug = False # get consistent results @@ -161,6 +162,29 @@ def testConvert(self): np.testing.assert_array_almost_equal( \ ssorig_imag, tfxfrm_imag) + def testConvertMIMO(self): + """Test state space to transfer function conversion.""" + verbose = self.debug + + # Do a MIMO conversation and make sure that it is processed + # correctly both with and without slycot + # + # Example from issue #120, jgoppert + import control + + # Set up a transfer function (should always work) + tfcn = control.tf([[[-235, 1.146e4], + [-235, 1.146E4], + [-235, 1.146E4, 0]]], + [[[1, 48.78, 0], + [1, 48.78, 0, 0], + [0.008, 1.39, 48.78]]]) + + # Convert to state space and look for an error + if (not slycot_check()): + self.assertRaises(TypeError, control.tf2ss, tfcn) + + def suite(): return unittest.TestLoader().loadTestsFromTestCase(TestConvert) diff --git a/control/tests/frd_test.py b/control/tests/frd_test.py index 4fa54742a..dca1c762c 100644 --- a/control/tests/frd_test.py +++ b/control/tests/frd_test.py @@ -11,6 +11,7 @@ from control.frdata import FRD, _convertToFRD from control import bdalg from control import freqplot +from control.exception import slycot_check import matplotlib.pyplot as plt @@ -179,6 +180,7 @@ def testNyquist(self): freqplot.nyquist(f1, f1.omega) # plt.savefig('/dev/null', format='svg') + @unittest.skipIf(not slycot_check(), "slycot not installed") def testMIMO(self): sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]], [[1.0, 0.0], [0.0, 1.0]], @@ -193,6 +195,7 @@ def testMIMO(self): sys.freqresp([0.1, 1.0, 10])[1], f1.freqresp([0.1, 1.0, 10])[1]) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testMIMOfb(self): sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]], [[1.0, 0.0], [0.0, 1.0]], @@ -208,6 +211,7 @@ def testMIMOfb(self): f1.freqresp([0.1, 1.0, 10])[1], f2.freqresp([0.1, 1.0, 10])[1]) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testMIMOfb2(self): sys = StateSpace(np.matrix('-2.0 0 0; 0 -1 1; 0 0 -3'), np.matrix('1.0 0; 0 0; 0 1'), @@ -223,6 +227,7 @@ def testMIMOfb2(self): f1.freqresp([0.1, 1.0, 10])[1], f2.freqresp([0.1, 1.0, 10])[1]) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testMIMOMult(self): sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]], [[1.0, 0.0], [0.0, 1.0]], @@ -238,6 +243,7 @@ def testMIMOMult(self): (f1*f2).freqresp([0.1, 1.0, 10])[1], (sys*sys).freqresp([0.1, 1.0, 10])[1]) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testMIMOSmooth(self): sys = StateSpace([[-0.5, 0.0], [0.0, -1.0]], [[1.0, 0.0], [0.0, 1.0]], diff --git a/control/tests/freqresp_test.py b/control/tests/freqresp_test.py index cc25bc37c..167108082 100644 --- a/control/tests/freqresp_test.py +++ b/control/tests/freqresp_test.py @@ -10,6 +10,7 @@ import numpy as np from control.statesp import StateSpace from control.matlab import ss, tf, bode +from control.exception import slycot_check import matplotlib.pyplot as plt class TestFreqresp(unittest.TestCase): @@ -42,6 +43,7 @@ def test_doubleint(self): sys = ss(A, B, C, D); bode(sys); + @unittest.skipIf(not slycot_check(), "slycot not installed") def test_mimo(self): # MIMO B = np.matrix('1,0;0,1') diff --git a/control/tests/matlab_test.py b/control/tests/matlab_test.py index e1bc43bcc..b7fba51eb 100644 --- a/control/tests/matlab_test.py +++ b/control/tests/matlab_test.py @@ -15,6 +15,7 @@ import scipy as sp from control.matlab import * from control.frdata import FRD +from control.exception import slycot_check import warnings # for running these through Matlab or Octave @@ -165,12 +166,13 @@ def testStep(self): np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) - #Test MIMO system, which contains ``siso_ss1`` twice - sys = self.mimo_ss1 - y_00, _t = step(sys, T=t, input=0, output=0) - y_11, _t = step(sys, T=t, input=1, output=1) - np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4) - np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4) + if slycot_check(): + # Test MIMO system, which contains ``siso_ss1`` twice + sys = self.mimo_ss1 + y_00, _t = step(sys, T=t, input=0, output=0) + y_11, _t = step(sys, T=t, input=1, output=1) + np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4) + np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4) def testImpulse(self): t = np.linspace(0, 1, 10) @@ -206,12 +208,13 @@ def testImpulse(self): np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) - #Test MIMO system, which contains ``siso_ss1`` twice - sys = self.mimo_ss1 - y_00, _t = impulse(sys, T=t, input=0, output=0) - y_11, _t = impulse(sys, T=t, input=1, output=1) - np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4) - np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4) + if slycot_check(): + #Test MIMO system, which contains ``siso_ss1`` twice + sys = self.mimo_ss1 + y_00, _t = impulse(sys, T=t, input=0, output=0) + y_11, _t = impulse(sys, T=t, input=1, output=1) + np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4) + np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4) def testInitial(self): #Test SISO system @@ -229,13 +232,14 @@ def testInitial(self): np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) np.testing.assert_array_almost_equal(tout, t) - #Test MIMO system, which contains ``siso_ss1`` twice - sys = self.mimo_ss1 - x0 = np.matrix(".5; 1.; .5; 1.") - y_00, _t = initial(sys, T=t, X0=x0, input=0, output=0) - y_11, _t = initial(sys, T=t, X0=x0, input=1, output=1) - np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4) - np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4) + if slycot_check(): + #Test MIMO system, which contains ``siso_ss1`` twice + sys = self.mimo_ss1 + x0 = np.matrix(".5; 1.; .5; 1.") + y_00, _t = initial(sys, T=t, X0=x0, input=0, output=0) + y_11, _t = initial(sys, T=t, X0=x0, input=1, output=1) + np.testing.assert_array_almost_equal(y_00, youttrue, decimal=4) + np.testing.assert_array_almost_equal(y_11, youttrue, decimal=4) def testLsim(self): t = np.linspace(0, 1, 10) @@ -259,18 +263,19 @@ def testLsim(self): yout, _t, _xout = lsim(self.siso_ss1, u, t, x0) np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) - #Test MIMO system, which contains ``siso_ss1`` twice - #first system: initial value, second system: step response - u = np.array([[0., 1.], [0, 1], [0, 1], [0, 1], [0, 1], - [0, 1], [0, 1], [0, 1], [0, 1], [0, 1]]) - x0 = np.matrix(".5; 1; 0; 0") - youttrue = np.array([[11., 9.], [8.1494, 17.6457], [5.9361, 24.7072], - [4.2258, 30.4855], [2.9118, 35.2234], - [1.9092, 39.1165], [1.1508, 42.3227], - [0.5833, 44.9694], [0.1645, 47.1599], - [-0.1391, 48.9776]]) - yout, _t, _xout = lsim(self.mimo_ss1, u, t, x0) - np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) + if slycot_check(): + #Test MIMO system, which contains ``siso_ss1`` twice + #first system: initial value, second system: step response + u = np.array([[0., 1.], [0, 1], [0, 1], [0, 1], [0, 1], + [0, 1], [0, 1], [0, 1], [0, 1], [0, 1]]) + x0 = np.matrix(".5; 1; 0; 0") + youttrue = np.array([[11., 9.], [8.1494, 17.6457], + [5.9361, 24.7072], [4.2258, 30.4855], + [2.9118, 35.2234], [1.9092, 39.1165], + [1.1508, 42.3227], [0.5833, 44.9694], + [0.1645, 47.1599], [-0.1391, 48.9776]]) + yout, _t, _xout = lsim(self.mimo_ss1, u, t, x0) + np.testing.assert_array_almost_equal(yout, youttrue, decimal=4) def testMargin(self): #! TODO: check results to make sure they are OK @@ -310,11 +315,12 @@ def testDcgain(self): gain_sim], [59, 59, 59, 59, 59]) - # Test with MIMO system, which contains ``siso_ss1`` twice - gain_mimo = dcgain(self.mimo_ss1) - # print('gain_mimo: \n', gain_mimo) - np.testing.assert_array_almost_equal(gain_mimo, [[59., 0 ], - [0, 59.]]) + if slycot_check(): + # Test with MIMO system, which contains ``siso_ss1`` twice + gain_mimo = dcgain(self.mimo_ss1) + # print('gain_mimo: \n', gain_mimo) + np.testing.assert_array_almost_equal(gain_mimo, [[59., 0 ], + [0, 59.]]) def testBode(self): bode(self.siso_ss1) @@ -370,29 +376,35 @@ def testEvalfr(self): evalfr(self.siso_tf1, w) evalfr(self.siso_tf2, w) evalfr(self.siso_tf3, w) - np.testing.assert_array_almost_equal( - evalfr(self.mimo_ss1, w), - np.array( [[44.8-21.4j, 0.], [0., 44.8-21.4j]])) + if slycot_check(): + np.testing.assert_array_almost_equal( + evalfr(self.mimo_ss1, w), + np.array( [[44.8-21.4j, 0.], [0., 44.8-21.4j]])) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testHsvd(self): hsvd(self.siso_ss1) hsvd(self.siso_ss2) hsvd(self.siso_ss3) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testBalred(self): balred(self.siso_ss1, 1) balred(self.siso_ss2, 2) balred(self.siso_ss3, [2, 2]) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testModred(self): modred(self.siso_ss1, [1]) modred(self.siso_ss2 * self.siso_ss3, [1, 2]) modred(self.siso_ss3, [1], 'matchdc') modred(self.siso_ss3, [1], 'truncate') + @unittest.skipIf(not slycot_check(), "slycot not installed") def testPlace(self): place(self.siso_ss1.A, self.siso_ss1.B, [-2, -2]) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testLQR(self): (K, S, E) = lqr(self.siso_ss1.A, self.siso_ss1.B, np.eye(2), np.eye(1)) (K, S, E) = lqr(self.siso_ss2.A, self.siso_ss2.B, np.eye(3), \ @@ -416,6 +428,7 @@ def testObsv(self): obsv(self.siso_ss1.A, self.siso_ss1.C) obsv(self.siso_ss2.A, self.siso_ss2.C) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testGram(self): gram(self.siso_ss1, 'c') gram(self.siso_ss2, 'c') @@ -452,6 +465,7 @@ def testSISOssdata(self): for i in range(len(ssdata_1)): np.testing.assert_array_almost_equal(ssdata_1[i], ssdata_2[i]) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testMIMOssdata(self): m = (self.mimo_ss1.A, self.mimo_ss1.B, self.mimo_ss1.C, self.mimo_ss1.D) ssdata_1 = ssdata(self.mimo_ss1); @@ -532,6 +546,7 @@ def testFRD(self): frd2 = frd(frd1.fresp[0,0,:], omega) assert isinstance(frd2, FRD) + @unittest.skipIf(not slycot_check(), "slycot not installed") 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 dabab5c4c..add3a977c 100644 --- a/control/tests/minreal_test.py +++ b/control/tests/minreal_test.py @@ -10,7 +10,9 @@ from control.statesp import StateSpace from control.xferfcn import TransferFunction from itertools import permutations +from control.exception import slycot_check +@unittest.skipIf(not slycot_check(), "slycot not installed") class TestMinreal(unittest.TestCase): """Tests for the StateSpace class.""" diff --git a/control/tests/statefbk_test.py b/control/tests/statefbk_test.py index e94ba0d0e..c2d771556 100644 --- a/control/tests/statefbk_test.py +++ b/control/tests/statefbk_test.py @@ -145,11 +145,13 @@ def check_LQR(self, K, S, poles, Q, R): np.testing.assert_array_almost_equal(poles, poles_expected) + @unittest.skipIf(not slycot_check(), "slycot not installed") def test_LQR_integrator(self): A, B, Q, R = 0., 1., 10., 2. K, S, poles = lqr(A, B, Q, R) self.check_LQR(K, S, poles, Q, R) + @unittest.skipIf(not slycot_check(), "slycot not installed") def test_LQR_3args(self): sys = ss(0., 1., 1., 0.) Q, R = 10., 2. diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index c19f4f38b..0191d2cf0 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -9,6 +9,7 @@ from control import matlab from control.statesp import StateSpace, _convertToStateSpace from control.xferfcn import TransferFunction +from control.exception import slycot_check class TestStateSpace(unittest.TestCase): """Tests for the StateSpace class.""" @@ -113,6 +114,7 @@ def testEvalFr(self): np.testing.assert_almost_equal(sys.evalfr(1.), resp) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testFreqResp(self): """Evaluate the frequency response at multiple frequencies.""" @@ -138,6 +140,7 @@ def testFreqResp(self): np.testing.assert_almost_equal(phase, truephase) np.testing.assert_equal(omega, trueomega) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testMinreal(self): """Test a minreal model reduction""" #A = [-2, 0.5, 0; 0.5, -0.3, 0; 0, 0, -0.1] diff --git a/control/tests/test_control_matlab.py b/control/tests/test_control_matlab.py index fb7a0cf33..e45b52523 100644 --- a/control/tests/test_control_matlab.py +++ b/control/tests/test_control_matlab.py @@ -2,9 +2,6 @@ Copyright (C) 2011 by Eike Welk. Test the control.matlab toolbox. - -NOTE: this script is not part of the standard python-control unit -tests. Needs to be integrated into unit test files. ''' import unittest @@ -19,6 +16,7 @@ ss2tf from control.statesp import _mimo2siso from control.timeresp import _check_convert_array +from control.exception import slycot_check import warnings class TestControlMatlab(unittest.TestCase): @@ -69,23 +67,24 @@ def make_MIMO_mats(self): def test_dcgain(self): """Test function dcgain with different systems""" - #Test MIMO systems - A, B, C, D = self.make_MIMO_mats() - - gain1 = dcgain(ss(A, B, C, D)) - gain2 = dcgain(A, B, C, D) - sys_tf = ss2tf(A, B, C, D) - gain3 = dcgain(sys_tf) - gain4 = dcgain(sys_tf.num, sys_tf.den) - #print("gain1:", gain1) - - assert_array_almost_equal(gain1, - array([[0.0269, 0. ], - [0. , 0.0269]]), - decimal=4) - assert_array_almost_equal(gain1, gain2) - assert_array_almost_equal(gain3, gain4) - assert_array_almost_equal(gain1, gain4) + if slycot_check(): + #Test MIMO systems + A, B, C, D = self.make_MIMO_mats() + + gain1 = dcgain(ss(A, B, C, D)) + gain2 = dcgain(A, B, C, D) + sys_tf = ss2tf(A, B, C, D) + gain3 = dcgain(sys_tf) + gain4 = dcgain(sys_tf.num, sys_tf.den) + #print("gain1:", gain1) + + assert_array_almost_equal(gain1, + array([[0.0269, 0. ], + [0. , 0.0269]]), + decimal=4) + assert_array_almost_equal(gain1, gain2) + assert_array_almost_equal(gain3, gain4) + assert_array_almost_equal(gain1, gain4) #Test SISO systems A, B, C, D = self.make_SISO_mats() diff --git a/control/tests/xferfcn_test.py b/control/tests/xferfcn_test.py index 595bf20d4..2d114fea6 100644 --- a/control/tests/xferfcn_test.py +++ b/control/tests/xferfcn_test.py @@ -7,9 +7,9 @@ import numpy as np from control.statesp import StateSpace, _convertToStateSpace from control.xferfcn import TransferFunction, _convertToTransferFunction +from control.exception import slycot_check # from control.lti import isdtime - class TestXferFcn(unittest.TestCase): """These are tests for functionality and correct reporting of the transfer function class. Throughout these tests, we will give different input @@ -113,6 +113,8 @@ def testNegSISO(self): np.testing.assert_array_equal(sys2.num, [[[-1., -3., -5.]]]) np.testing.assert_array_equal(sys2.den, [[[1., 6., 2., -1.]]]) + + @unittest.skipIf(not slycot_check(), "slycot not installed") def testNegMIMO(self): """Negate a MIMO system.""" @@ -155,6 +157,7 @@ def testAddSISO(self): np.testing.assert_array_equal(sys3.num, [[[20., 4., -8]]]) np.testing.assert_array_equal(sys3.den, [[[1., 6., 1., -7., -2., 1.]]]) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testAddMIMO(self): """Add two MIMO systems.""" @@ -205,6 +208,7 @@ def testSubSISO(self): np.testing.assert_array_equal(sys4.num, [[[-2., -6., 12., 10., 2.]]]) np.testing.assert_array_equal(sys4.den, [[[1., 6., 1., -7., -2., 1.]]]) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testSubMIMO(self): """Add two MIMO systems.""" @@ -258,6 +262,7 @@ def testMulSISO(self): np.testing.assert_array_equal(sys3.num, sys4.num) np.testing.assert_array_equal(sys3.den, sys4.den) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testMulMIMO(self): """Multiply two MIMO systems.""" @@ -330,6 +335,7 @@ def testEvalFrSISO(self): np.testing.assert_almost_equal(sys(32.j), 0.00281959302585077 - 0.030628473607392j) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testEvalFrMIMO(self): """Evaluate the frequency response of a MIMO system at one frequency.""" @@ -366,6 +372,7 @@ def testFreqRespSISO(self): np.testing.assert_array_almost_equal(phase, truephase) np.testing.assert_array_almost_equal(omega, trueomega) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testFreqRespMIMO(self): """Evaluate the magnitude and phase of a MIMO system at multiple frequencies.""" @@ -395,7 +402,8 @@ def testFreqRespMIMO(self): np.testing.assert_array_equal(omega, trueomega) # Tests for TransferFunction.pole and TransferFunction.zero. - + + @unittest.skipIf(not slycot_check(), "slycot not installed") def testPoleMIMO(self): """Test for correct MIMO poles.""" @@ -421,6 +429,7 @@ def testFeedbackSISO(self): np.testing.assert_array_equal(sys4.num, [[[-1., 7., -16., 16., 0.]]]) np.testing.assert_array_equal(sys4.den, [[[1., 0., 2., -8., 8., 0.]]]) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testConvertToTransferFunction(self): """Test for correct state space to transfer function conversion.""" @@ -472,6 +481,7 @@ def testMinreal3(self): np.testing.assert_array_almost_equal(1.0, g.num[0][0]) np.testing.assert_array_almost_equal(1.0, g.den[0][0]) + @unittest.skipIf(not slycot_check(), "slycot not installed") def testMIMO(self): """Test conversion of a single input, two-output state-space system against the same TF""" diff --git a/control/xferfcn.py b/control/xferfcn.py index 7556d9049..3ff21a08b 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -125,7 +125,9 @@ def __init__(self, *args): # but be careful. data = [num, den] for i in range(len(data)): - if isinstance(data[i], (int, float, complex)): + # Check for a scalar (including 0d ndarray) + if (isinstance(data[i], (int, float, complex)) or + (isinstance(data[i], ndarray) and data[i].ndim == 0)): # Convert scalar to list of list of array. if (isinstance(data[i], int)): # Convert integers to floats at this point
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: