Skip to content

Commit 02080e0

Browse files
committed
BugFix: tf2ss now handles static MIMO gains with no Slycot
Check for static-gain (i.e., constant) transfer function matrix, and handle specially. Added unit tests for static SISO and MIMO plants.
1 parent 8caf661 commit 02080e0

File tree

2 files changed

+44
-10
lines changed

2 files changed

+44
-10
lines changed

control/statesp.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
# Python 3 compatibility (needs to go here)
1212
from __future__ import print_function
13+
from __future__ import division # for _convertToStateSpace
1314

1415
"""Copyright (c) 2010 by California Institute of Technology
1516
All rights reserved.
@@ -647,6 +648,7 @@ def _convertToStateSpace(sys, **kw):
647648
"""
648649

649650
from .xferfcn import TransferFunction
651+
import itertools
650652
if isinstance(sys, StateSpace):
651653
if len(kw):
652654
raise TypeError("If sys is a StateSpace, _convertToStateSpace \
@@ -679,16 +681,26 @@ def _convertToStateSpace(sys, **kw):
679681
ssout[3][:sys.outputs, :states],
680682
ssout[4], sys.dt)
681683
except ImportError:
682-
# If slycot is not available, use signal.lti (SISO only)
683-
if (sys.inputs != 1 or sys.outputs != 1):
684-
raise TypeError("No support for MIMO without slycot")
685-
686-
# TODO: do we want to squeeze first and check dimenations?
687-
# I think this will fail if num and den aren't 1-D after
688-
# the squeeze
689-
lti_sys = lti(squeeze(sys.num), squeeze(sys.den))
690-
return StateSpace(lti_sys.A, lti_sys.B, lti_sys.C, lti_sys.D,
691-
sys.dt)
684+
# No Slycot. Scipy tf->ss can't handle MIMO, but static MIMO is an easy special case we can check for here
685+
maxn = max(max(len(n) for n in nrow)
686+
for nrow in sys.num)
687+
maxd = max(max(len(d) for d in drow)
688+
for drow in sys.den)
689+
if 1==maxn and 1==maxd:
690+
D = empty((sys.outputs,sys.inputs),dtype=float)
691+
for i,j in itertools.product(range(sys.outputs),range(sys.inputs)):
692+
D[i,j] = sys.num[i][j][0] / sys.den[i][j][0]
693+
return StateSpace([], [], [], D, sys.dt)
694+
else:
695+
if (sys.inputs != 1 or sys.outputs != 1):
696+
raise TypeError("No support for MIMO without slycot")
697+
698+
# TODO: do we want to squeeze first and check dimenations?
699+
# I think this will fail if num and den aren't 1-D after
700+
# the squeeze
701+
lti_sys = lti(squeeze(sys.num), squeeze(sys.den))
702+
return StateSpace(lti_sys.A, lti_sys.B, lti_sys.C, lti_sys.D,
703+
sys.dt)
692704

693705
elif isinstance(sys, (int, float, complex)):
694706
if "inputs" in kw:

control/tests/convert_test.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,28 @@ def testConvertMIMO(self):
184184
if (not slycot_check()):
185185
self.assertRaises(TypeError, control.tf2ss, tfcn)
186186

187+
def testTf2ssStaticSiso(self):
188+
"""Regression: tf2ss for SISO static gain"""
189+
import control
190+
gsiso = control.tf2ss(control.tf(23, 46))
191+
self.assertEqual(0, gsiso.states)
192+
self.assertEqual(1, gsiso.inputs)
193+
self.assertEqual(1, gsiso.outputs)
194+
# in all cases ratios are exactly representable, so assert_array_equal is fine
195+
np.testing.assert_array_equal([[0.5]], gsiso.D)
196+
197+
def testTf2ssStaticMimo(self):
198+
"""Regression: tf2ss for MIMO static gain"""
199+
import control
200+
# 2x3 TFM
201+
gmimo = control.tf2ss(control.tf([[ [23], [3], [5] ], [ [-1], [0.125], [101.3] ]],
202+
[[ [46], [0.1], [80] ], [ [2], [-0.1], [1] ]]))
203+
self.assertEqual(0, gmimo.states)
204+
self.assertEqual(3, gmimo.inputs)
205+
self.assertEqual(2, gmimo.outputs)
206+
d = np.matrix([[0.5, 30, 0.0625], [-0.5, -1.25, 101.3]])
207+
np.testing.assert_array_equal(d, gmimo.D)
208+
187209

188210
def suite():
189211
return unittest.TestLoader().loadTestsFromTestCase(TestConvert)

0 commit comments

Comments
 (0)