48
48
# Define module default parameter values
49
49
_iosys_defaults = {}
50
50
51
+
51
52
class InputOutputSystem (object ):
52
53
"""A class for representing input/output systems.
53
54
@@ -75,7 +76,7 @@ class for a set of subclasses that are used to implement specific
75
76
System timebase. 0 (default) indicates continuous
76
77
time, True indicates discrete time with unspecified sampling
77
78
time, positive number is discrete time with specified
78
- sampling time, None indicates unspecified timebase (either
79
+ sampling time, None indicates unspecified timebase (either
79
80
continuous or discrete time).
80
81
params : dict, optional
81
82
Parameter values for the systems. Passed to the evaluation functions
@@ -95,7 +96,7 @@ class for a set of subclasses that are used to implement specific
95
96
System timebase. 0 (default) indicates continuous
96
97
time, True indicates discrete time with unspecified sampling
97
98
time, positive number is discrete time with specified
98
- sampling time, None indicates unspecified timebase (either
99
+ sampling time, None indicates unspecified timebase (either
99
100
continuous or discrete time).
100
101
params : dict, optional
101
102
Parameter values for the systems. Passed to the evaluation functions
@@ -118,6 +119,7 @@ class for a set of subclasses that are used to implement specific
118
119
"""
119
120
120
121
idCounter = 0
122
+
121
123
def name_or_default (self , name = None ):
122
124
if name is None :
123
125
name = "sys[{}]" .format (InputOutputSystem .idCounter )
@@ -153,15 +155,15 @@ def __init__(self, inputs=None, outputs=None, states=None, params={},
153
155
System timebase. 0 (default) indicates continuous
154
156
time, True indicates discrete time with unspecified sampling
155
157
time, positive number is discrete time with specified
156
- sampling time, None indicates unspecified timebase (either
158
+ sampling time, None indicates unspecified timebase (either
157
159
continuous or discrete time).
158
160
params : dict, optional
159
161
Parameter values for the systems. Passed to the evaluation
160
162
functions for the system as default values, overriding internal
161
163
defaults.
162
164
name : string, optional
163
- System name (used for specifying signals). If unspecified, a generic
164
- name <sys[id]> is generated with a unique integer id.
165
+ System name (used for specifying signals). If unspecified, a
166
+ generic name <sys[id]> is generated with a unique integer id.
165
167
166
168
Returns
167
169
-------
@@ -190,11 +192,14 @@ def __str__(self):
190
192
"""String representation of an input/output system"""
191
193
str = "System: " + (self .name if self .name else "(None)" ) + "\n "
192
194
str += "Inputs (%s): " % self .ninputs
193
- for key in self .input_index : str += key + ", "
195
+ for key in self .input_index :
196
+ str += key + ", "
194
197
str += "\n Outputs (%s): " % self .noutputs
195
- for key in self .output_index : str += key + ", "
198
+ for key in self .output_index :
199
+ str += key + ", "
196
200
str += "\n States (%s): " % self .nstates
197
- for key in self .state_index : str += key + ", "
201
+ for key in self .state_index :
202
+ str += key + ", "
198
203
return str
199
204
200
205
def __mul__ (sys2 , sys1 ):
@@ -224,10 +229,11 @@ def __mul__(sys2, sys1):
224
229
# Make sure timebase are compatible
225
230
dt = common_timebase (sys1 .dt , sys2 .dt )
226
231
227
- inplist = [(0 ,i ) for i in range (sys1 .ninputs )]
228
- outlist = [(1 ,i ) for i in range (sys2 .noutputs )]
232
+ inplist = [(0 , i ) for i in range (sys1 .ninputs )]
233
+ outlist = [(1 , i ) for i in range (sys2 .noutputs )]
229
234
# Return the series interconnection between the systems
230
- newsys = InterconnectedSystem ((sys1 , sys2 ), inplist = inplist , outlist = outlist )
235
+ newsys = InterconnectedSystem (
236
+ (sys1 , sys2 ), inplist = inplist , outlist = outlist )
231
237
232
238
# Set up the connection map manually
233
239
newsys .set_connect_map (np .block (
@@ -281,10 +287,11 @@ def __add__(sys1, sys2):
281
287
ninputs = sys1 .ninputs
282
288
noutputs = sys1 .noutputs
283
289
284
- inplist = [[(0 ,i ),(1 ,i )] for i in range (ninputs )]
285
- outlist = [[(0 ,i ),(1 ,i )] for i in range (noutputs )]
290
+ inplist = [[(0 , i ), (1 , i )] for i in range (ninputs )]
291
+ outlist = [[(0 , i ), (1 , i )] for i in range (noutputs )]
286
292
# Create a new system to handle the composition
287
- newsys = InterconnectedSystem ((sys1 , sys2 ), inplist = inplist , outlist = outlist )
293
+ newsys = InterconnectedSystem (
294
+ (sys1 , sys2 ), inplist = inplist , outlist = outlist )
288
295
289
296
# Return the newly created system
290
297
return newsys
@@ -303,10 +310,11 @@ def __neg__(sys):
303
310
if sys .ninputs is None or sys .noutputs is None :
304
311
raise ValueError ("Can't determine number of inputs or outputs" )
305
312
306
- inplist = [(0 ,i ) for i in range (sys .ninputs )]
307
- outlist = [(0 ,i , - 1 ) for i in range (sys .noutputs )]
313
+ inplist = [(0 , i ) for i in range (sys .ninputs )]
314
+ outlist = [(0 , i , - 1 ) for i in range (sys .noutputs )]
308
315
# Create a new system to hold the negation
309
- newsys = InterconnectedSystem ((sys ,), dt = sys .dt , inplist = inplist , outlist = outlist )
316
+ newsys = InterconnectedSystem (
317
+ (sys ,), dt = sys .dt , inplist = inplist , outlist = outlist )
310
318
311
319
# Return the newly created system
312
320
return newsys
@@ -476,12 +484,13 @@ def feedback(self, other=1, sign=-1, params={}):
476
484
# Make sure timebases are compatible
477
485
dt = common_timebase (self .dt , other .dt )
478
486
479
- inplist = [(0 ,i ) for i in range (self .ninputs )]
480
- outlist = [(0 ,i ) for i in range (self .noutputs )]
487
+ inplist = [(0 , i ) for i in range (self .ninputs )]
488
+ outlist = [(0 , i ) for i in range (self .noutputs )]
481
489
482
490
# Return the series interconnection between the systems
483
- newsys = InterconnectedSystem ((self , other ), inplist = inplist , outlist = outlist ,
484
- params = params , dt = dt )
491
+ newsys = InterconnectedSystem (
492
+ (self , other ), inplist = inplist , outlist = outlist ,
493
+ params = params , dt = dt )
485
494
486
495
# Set up the connecton map manually
487
496
newsys .set_connect_map (np .block (
@@ -514,8 +523,10 @@ def linearize(self, x0, u0, t=0, params={}, eps=1e-6,
514
523
ninputs = _find_size (self .ninputs , u0 )
515
524
516
525
# Convert x0, u0 to arrays, if needed
517
- if np .isscalar (x0 ): x0 = np .ones ((nstates ,)) * x0
518
- if np .isscalar (u0 ): u0 = np .ones ((ninputs ,)) * u0
526
+ if np .isscalar (x0 ):
527
+ x0 = np .ones ((nstates ,)) * x0
528
+ if np .isscalar (u0 ):
529
+ u0 = np .ones ((ninputs ,)) * u0
519
530
520
531
# Compute number of outputs by evaluating the output function
521
532
noutputs = _find_size (self .noutputs , self ._out (t , x0 , u0 ))
@@ -566,7 +577,8 @@ def linearize(self, x0, u0, t=0, params={}, eps=1e-6,
566
577
def copy (self , newname = None ):
567
578
"""Make a copy of an input/output system."""
568
579
newsys = copy .copy (self )
569
- newsys .name = self .name_or_default ("copy of " + self .name if not newname else newname )
580
+ newsys .name = self .name_or_default (
581
+ "copy of " + self .name if not newname else newname )
570
582
return newsys
571
583
572
584
@@ -605,15 +617,15 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None,
605
617
System timebase. 0 (default) indicates continuous
606
618
time, True indicates discrete time with unspecified sampling
607
619
time, positive number is discrete time with specified
608
- sampling time, None indicates unspecified timebase (either
620
+ sampling time, None indicates unspecified timebase (either
609
621
continuous or discrete time).
610
622
params : dict, optional
611
623
Parameter values for the systems. Passed to the evaluation
612
624
functions for the system as default values, overriding internal
613
625
defaults.
614
626
name : string, optional
615
- System name (used for specifying signals). If unspecified, a generic
616
- name <sys[id]> is generated with a unique integer id.
627
+ System name (used for specifying signals). If unspecified, a
628
+ generic name <sys[id]> is generated with a unique integer id.
617
629
618
630
Returns
619
631
-------
@@ -729,11 +741,11 @@ def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None,
729
741
* dt = 0: continuous time system (default)
730
742
* dt > 0: discrete time system with sampling period 'dt'
731
743
* dt = True: discrete time with unspecified sampling period
732
- * dt = None: no timebase specified
744
+ * dt = None: no timebase specified
733
745
734
746
name : string, optional
735
- System name (used for specifying signals). If unspecified, a generic
736
- name <sys[id]> is generated with a unique integer id.
747
+ System name (used for specifying signals). If unspecified, a
748
+ generic name <sys[id]> is generated with a unique integer id.
737
749
738
750
Returns
739
751
-------
@@ -899,23 +911,28 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
899
911
* dt = 0: continuous time system (default)
900
912
* dt > 0: discrete time system with sampling period 'dt'
901
913
* dt = True: discrete time with unspecified sampling period
902
- * dt = None: no timebase specified
914
+ * dt = None: no timebase specified
903
915
904
916
name : string, optional
905
- System name (used for specifying signals). If unspecified, a generic
906
- name <sys[id]> is generated with a unique integer id.
917
+ System name (used for specifying signals). If unspecified, a
918
+ generic name <sys[id]> is generated with a unique integer id.
907
919
908
920
"""
909
921
# Convert input and output names to lists if they aren't already
910
- if not isinstance (inplist , (list , tuple )): inplist = [inplist ]
911
F438
- if not isinstance (outlist , (list , tuple )): outlist = [outlist ]
922
+ if not isinstance (inplist , (list , tuple )):
923
+ inplist = [inplist ]
924
+ if not isinstance (outlist , (list , tuple )):
925
+ outlist = [outlist ]
912
926
913
927
# Check to make sure all systems are consistent
914
928
self .syslist = syslist
915
929
self .syslist_index = {}
916
- nstates = 0 ; self .state_offset = []
917
- ninputs = 0 ; self .input_offset = []
918
- noutputs = 0 ; self .output_offset = []
930
+ nstates = 0
931
+ self .state_offset = []
932
+ ninputs = 0
933
+ self .input_offset = []
934
+ noutputs = 0
935
+ self .output_offset = []
919
936
sysobj_name_dct = {}
920
937
sysname_count_dct = {}
921
938
for sysidx , sys in enumerate (syslist ):
@@ -943,14 +960,16 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
943
960
# Duplicates are renamed sysname_1, sysname_2, etc.
944
961
if sys in sysobj_name_dct :
945
962
sys = sys .copy ()
946
- warn ("Duplicate object found in system list: %s. Making a copy" % str (sys ))
963
+ warn ("Duplicate object found in system list: %s. "
964
+ "Making a copy" % str (sys ))
947
965
if sys .name is not None and sys .name in sysname_count_dct :
948
966
count = sysname_count_dct [sys .name ]
949
967
sysname_count_dct [sys .name ] += 1
950
968
sysname = sys .name + "_" + str (count )
951
969
sysobj_name_dct [sys ] = sysname
952
970
self .syslist_index [sysname ] = sysidx
953
- warn ("Duplicate name found in system list. Renamed to {}" .format (sysname ))
971
+ warn ("Duplicate name found in system list. "
972
+ "Renamed to {}" .format (sysname ))
954
973
else :
955
974
sysname_count_dct [sys .name ] = 1
956
975
sysobj_name_dct [sys ] = sys .name
@@ -959,7 +978,8 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
959
978
if states is None :
960
979
states = []
961
980
for sys , sysname in sysobj_name_dct .items ():
962
- states += [sysname + '.' + statename for statename in sys .state_index .keys ()]
981
+ states += [sysname + '.' +
982
+ statename for statename in sys .state_index .keys ()]
963
983
964
984
# Create the I/O system
965
985
super (InterconnectedSystem , self ).__init__ (
@@ -989,14 +1009,16 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
989
1009
# Convert the input list to a matrix: maps system to subsystems
990
1010
self .input_map = np .zeros ((ninputs , self .ninputs ))
991
1011
for index , inpspec in enumerate (inplist ):
992
- if isinstance (inpspec , (int , str , tuple )): inpspec = [inpspec ]
1012
+ if isinstance (inpspec , (int , str , tuple )):
1013
+ inpspec = [inpspec ]
993
1014
for spec in inpspec :
994
1015
self .input_map [self ._parse_input_spec (spec ), index ] = 1
995
1016
996
1017
# Convert the output list to a matrix: maps subsystems to system
997
1018
self .output_map = np .zeros ((self .noutputs , noutputs + ninputs ))
998
1019
for index , outspec in enumerate (outlist ):
999
- if isinstance (outspec , (int , str , tuple )): outspec = [outspec ]
1020
+ if isinstance (outspec , (int , str , tuple )):
1021
+ outspec = [outspec ]
1000
1022
for spec in outspec :
1001
1023
ylist_index , gain = self ._parse_output_spec (spec )
1002
1024
self .output_map [index , ylist_index ] = gain
@@ -1041,7 +1063,7 @@ def _rhs(self, t, x, u):
1041
1063
1042
1064
# Go through each system and update the right hand side for that system
1043
1065
xdot = np .zeros ((self .nstates ,)) # Array to hold results
1044
- state_index = 0 ; input_index = 0 # Start at the beginning
1066
+ state_index , input_index = 0 , 0 # Start at the beginning
1045
1067
for sys in self .syslist :
1046
1068
# Update the right hand side for this subsystem
1047
1069
if sys .nstates != 0 :
@@ -1084,7 +1106,7 @@ def _compute_static_io(self, t, x, u):
1084
1106
# TODO (later): see if there is a more efficient way to compute
1085
1107
cycle_count = len (self .syslist ) + 1
1086
1108
while cycle_count > 0 :
1087
- state_index = 0 ; input_index = 0 ; output_index = 0
1109
+ state_index , input_index , output_index =
10000
0 , 0 , 0
1088
1110
for sys in self .syslist :
1089
1111
# Compute outputs for each system from current state
1090
1112
ysys = sys ._out (
@@ -1097,8 +1119,8 @@ def _compute_static_io(self, t, x, u):
1097
1119
1098
1120
# Store the input in the second part of ylist
1099
1121
ylist [noutputs + input_index :
1100
- noutputs + input_index + sys .ninputs ] = \
1101
- ulist [input_index :input_index + sys .ninputs ]
1122
+ noutputs + input_index + sys .ninputs ] = \
1123
+ ulist [input_index :input_index + sys .ninputs ]
1102
1124
1103
1125
# Increment the index pointers
1104
1126
state_index += sys .nstates
@@ -1229,7 +1251,8 @@ def _parse_signal(self, spec, signame='input', dictname=None):
1229
1251
return spec
1230
1252
1231
1253
# Figure out the name of the dictionary to use
1232
- if dictname is None : dictname = signame + '_index'
1254
+ if dictname is None :
1255
+ dictname = signame + '_index'
1233
1256
1234
1257
if isinstance (spec , str ):
1235
1258
# If we got a dotted string, break up into pieces
@@ -1415,7 +1438,8 @@ def input_output_response(sys, T, U=0., X0=0, params={}, method='RK45',
1415
1438
for i in range (len (T )):
1416
1439
u = U [i ] if len (U .shape ) == 1 else U [:, i ]
1417
1440
y [:, i ] = sys ._out (T [i ], [], u )
1418
- if (squeeze ): y = np .squeeze (y )
1441
+ if squeeze :
1442
+ y = np .squeeze (y )
1419
1443
if return_x :
1420
1444
return T , y , []
1421
1445
else :
@@ -1495,7 +1519,8 @@ def ivp_rhs(t, x): return sys._rhs(t, x, u(t))
1495
1519
raise TypeError ("Can't determine system type" )
1496
1520
1497
1521
# Get rid of extra dimensions in the output, of desired
1498
- if (squeeze ): y = np .squeeze (y )
1522
+ if squeeze :
1523
+ y = np .squeeze (y )
1499
1524
1500
1525
if <
10000
span class=pl-s1>return_x:
1501
1526
return soln .t , y , soln .y
@@ -1580,9 +1605,12 @@ def find_eqpt(sys, x0, u0=[], y0=None, t=0, params={},
1580
1605
noutputs = _find_size (sys .noutputs , y0 )
1581
1606
1582
1607
# Convert x0, u0, y0 to arrays, if needed
1583
- if np .isscalar (x0 ): x0 = np .ones ((nstates ,)) * x0
1584
- if np .isscalar (u0 ): u0 = np .ones ((ninputs ,)) * u0
1585
- if np .isscalar (y0 ): y0 = np .ones ((ninputs ,)) * y0
1608
+ if np .isscalar (x0 ):
1609
+ x0 = np .ones ((nstates ,)) * x0
1610
+ if np .isscalar (u0 ):
1611
+ u0 = np .ones ((ninputs ,)) * u0
1612
+ if np .isscalar (y0 ):
1613
+ y0 = np .ones ((ninputs ,)) * y0
1586
1614
1587
1615
# Discrete-time not yet supported
1588
1616
if isdtime (sys , strict = True ):
@@ -1718,7 +1746,8 @@ def rootfun(z):
1718
1746
1719
1747
# Compute the update and output maps
1720
1748
dx = sys ._rhs (t , x , u ) - dx0
1721
- if dtime : dx -= x # TODO: check
1749
+ if dtime :
1750
+ dx -= x # TODO: check
1722
1751
dy = sys ._out (t , x , u ) - y0
1723
1752
1724
1753
# Map the results into the constrained variables
@@ -1736,7 +1765,8 @@ def rootfun(z):
1736
1765
z = (x , u , sys ._out (t , x , u ))
1737
1766
1738
1767
# Return the result based on what the user wants and what we found
1739
- if not return_y : z = z [0 :2 ] # Strip y from result if not desired
1768
+ if not return_y :
1769
+ z = z [0 :2 ] # Strip y from result if not desired
1740
1770
if return_result :
1741
1771
# Return whatever we got, along with the result dictionary
1742
1772
return z + (result ,)
@@ -1810,7 +1840,8 @@ def _find_size(sysval, vecval):
1810
1840
1811
1841
1812
1842
# Convert a state space system into an input/output system (wrapper)
1813
- def ss2io (* args , ** kw ): return LinearIOSystem (* args , ** kw )
1843
+ def ss2io (* args , ** kw ):
1844
+ return LinearIOSystem (* args , ** kw )
1814
1845
ss2io .__doc__ = LinearIOSystem .__init__ .__doc__
1815
1846
1816
1847
0 commit comments