60
60
from scipy .signal import StateSpace as signalStateSpace
61
61
from warnings import warn
62
62
63
- from .exception import ControlSlycot
63
+ from .exception import ControlSlycot , slycot_check , ControlMIMONotImplemented
64
64
from .frdata import FrequencyResponseData
65
65
from .lti import LTI , _process_frequency_response
66
- from .iosys import InputOutputSystem , common_timebase , isdtime , \
66
+ from .iosys import InputOutputSystem , common_timebase , isdtime , issiso , \
67
67
_process_iosys_keywords , _process_dt_keyword , _process_signal_list
68
68
from .nlsys import NonlinearIOSystem , InterconnectedSystem
69
69
from . import config
@@ -1583,6 +1583,13 @@ def ss(*args, **kwargs):
1583
1583
--------
1584
1584
tf, ss2tf, tf2ss
1585
1585
1586
+ Notes
1587
+ -----
1588
+ If a transfer function is passed as the sole positional argument, the
1589
+ system will be converted to state space form in the same way as calling
1590
+ :func:`~control.tf2ss`. The `method` keyword can be used to select the
1591
+ method for conversion.
1592
+
1586
1593
Examples
1587
1594
--------
1588
1595
Create a Linear I/O system object from matrices.
@@ -1615,10 +1622,13 @@ def ss(*args, **kwargs):
1615
1622
warn ("state labels specified for "
1616
1623
"non-unique state space realization" )
1617
1624
1625
+ # Allow method to be specified (eg, tf2ss)
1626
+ method = kwargs .pop ('method' , None )
1627
+
1618
1628
# Create a state space system from an LTI system
1619
1629
sys = StateSpace (
1620
1630
_convert_to_statespace (
1621
- sys ,
1631
+ sys , method = method ,
1622
1632
use_prefix_suffix = not sys ._generic_name_check ()),
1623
1633
** kwargs )
1624
1634
@@ -1765,6 +1775,10 @@ def tf2ss(*args, **kwargs):
1765
1775
name : string, optional
1766
1776
System name. If unspecified, a generic name <sys[id]> is generated
1767
1777
with a unique integer id.
1778
+ method : str, optional
1779
+ Set the method used for computing the result. Current methods are
1780
+ 'slycot' and 'scipy'. If set to None (default), try 'slycot' first
1781
+ and then 'scipy' (SISO only).
1768
1782
1769
1783
Raises
1770
1784
------
@@ -1781,6 +1795,13 @@ def tf2ss(*args, **kwargs):
1781
1795
tf
1782
1796
ss2tf
1783
1797
1798
+ Notes
1799
+ -----
1800
+ The ``slycot`` routine used to convert a transfer function into state
1801
+ space form appears to have a bug and in some (rare) instances may not
1802
+ return a system with the same poles as the input transfer function.
1803
+ For SISO systems, setting ``method=scipy`` can be used as an alternative.
1804
+
1784
1805
Examples
1785
1806
--------
1786
1807
>>> num = [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]]]
@@ -2189,7 +2210,7 @@ def _f2s(f):
2189
2210
return s
2190
2211
2191
2212
2192
- def _convert_to_statespace (sys , use_prefix_suffix = False ):
2213
+ def _convert_to_statespace (sys , use_prefix_suffix = False , method = None ):
2193
2214
"""Convert a system to state space form (if needed).
2194
2215
2195
2216
If sys is already a state space, then it is returned. If sys is a
@@ -2213,13 +2234,17 @@ def _convert_to_statespace(sys, use_prefix_suffix=False):
2213
2234
raise ValueError ("transfer function is non-proper; can't "
2214
2235
"convert to StateSpace system" )
2215
2236
2216
- try :
2237
+ if method is None and slycot_check () or method == 'slycot' :
2238
+ if not slycot_check ():
2239
+ raise ValueError ("method='slycot' requires slycot" )
2240
+
2217
2241
from slycot import td04ad
2218
2242
2219
2243
# Change the numerator and denominator arrays so that the transfer
2220
2244
# function matrix has a common denominator.
2221
2245
# matrices are also sized/padded to fit td04ad
2222
2246
num , den , denorder = sys .minreal ()._common_den ()
2247
+ num , den , denorder = sys ._common_den ()
2223
2248
2224
2249
# transfer function to state space conversion now should work!
2225
2250
ssout = td04ad ('C' , sys .ninputs , sys .noutputs ,
@@ -2230,9 +2255,8 @@ def _convert_to_statespace(sys, use_prefix_suffix=False):
2230
2255
ssout [1 ][:states , :states ], ssout [2 ][:states , :sys .ninputs ],
2231
2256
ssout [3 ][:sys .noutputs , :states ], ssout [4 ], sys .dt )
2232
2257
2233
- except ImportError :
2234
- # No Slycot. Scipy tf->ss can't handle MIMO, but static
2235
- # MIMO is an easy special case we can check for here
2258
+ elif method in [None , 'scipy' ]:
2259
+ # Scipy tf->ss can't handle MIMO, but SISO is OK
2236
2260
maxn = max (max (len (n ) for n in nrow )
2237
2261
for nrow in sys .num )
2238
2262
maxd = max (max (len (d ) for d in drow )
@@ -2244,12 +2268,15 @@ def _convert_to_statespace(sys, use_prefix_suffix=False):
2244
2268
D [i , j ] = sys .num [i ][j ][0 ] / sys .den [i ][j ][0 ]
2245
2269
newsys = StateSpace ([], [], [], D , sys .dt )
2246
2270
else :
2247
- if sys .ninputs != 1 or sys .noutputs != 1 :
2248
- raise TypeError ("No support for MIMO without slycot" )
2271
+ if not issiso (sys ):
2272
+ raise ControlMIMONotImplemented (
2273
+ "MIMO system conversion not supported without Slycot" )
2249
2274
2250
2275
A , B , C , D = \
2251
2276
sp .signal .tf2ss (squeeze (sys .num ), squeeze (sys .den ))
2252
2277
newsys = StateSpace (A , B , C , D , sys .dt )
2278
+ else :
2279
+ raise ValueError (f"unknown { method = } " )
2253
2280
2254
2281
# Copy over the signal (and system) names
2255
2282
newsys ._copy_names (
0 commit comments