@@ -214,7 +214,6 @@ def __mul__(sys2, sys1):
214
214
elif isinstance (sys1 , StateSpace ) and isinstance (sys2 , StateSpace ):
215
215
# Special case: maintain linear systems structure
216
216
new_ss_sys = StateSpace .__mul__ (sys2 , sys1 )
217
- # TODO: set input and output names
218
217
new_io_sys = LinearIOSystem (new_ss_sys )
219
218
220
219
return new_io_sys
@@ -825,60 +824,70 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
825
824
826
825
Parameters
827
826
----------
828
- syslist : array_like of InputOutputSystems
827
+ syslist : list of InputOutputSystems
829
828
The list of input/output systems to be connected
830
829
831
- connections : list of tuple of connection specifications , optional
832
- Description of the internal connections between the subsystems.
830
+ connections : list of connections , optional
831
+ Description of the internal connections between the subsystems:
833
832
834
833
[connection1, connection2, ...]
835
834
836
- Each connection is a tuple that describes an input to one of the
837
- subsystems. The entries are of the form:
835
+ Each connection is itself a list that describes an input to one of
836
+ the subsystems. The entries are of the form:
838
837
839
- ( input-spec, output-spec1, output-spec2, ...)
838
+ [ input-spec, output-spec1, output-spec2, ...]
840
839
841
- The input-spec should be a tuple of the form `(subsys_i, inp_j)`
840
+ The input-spec can be in a number of different forms. The lowest
841
+ level representation is a tuple of the form `(subsys_i, inp_j)`
842
842
where `subsys_i` is the index into `syslist` and `inp_j` is the
843
843
index into the input vector for the subsystem. If `subsys_i` has
844
844
a single input, then the subsystem index `subsys_i` can be listed
845
845
as the input-spec. If systems and signals are given names, then
846
846
the form 'sys.sig' or ('sys', 'sig') are also recognized.
847
847
848
- Each output-spec should be a tuple of the form `(subsys_i, out_j,
849
- gain)`. The input will be constructed by summing the listed
850
- outputs after multiplying by the gain term. If the gain term is
851
- omitted, it is assumed to be 1. If the system has a single
852
- output, then the subsystem index `subsys_i` can be listed as the
853
- input-spec. If systems and signals are given names, then the form
854
- 'sys.sig', ('sys', 'sig') or ('sys', 'sig', gain) are also
855
- recognized, and the special form '-sys.sig' can be used to specify
856
- a signal with gain -1.
848
+ Similarly, each output-spec should describe an output signal from
849
+ one of the susystems. The lowest level representation is a tuple
850
+ of the form `(subsys_i, out_j, gain)`. The input will be
851
+ constructed by summing the listed outputs after multiplying by the
852
+ gain term. If the gain term is omitted, it is assumed to be 1.
853
+ If the system has a single output, then the subsystem index
854
+ `subsys_i` can be listed as the input-spec. If systems and
855
+ signals are given names, then the form 'sys.sig', ('sys', 'sig')
856
+ or ('sys', 'sig', gain) are also recognized, and the special form
857
+ '-sys.sig' can be used to specify a signal with gain -1.
857
858
858
859
If omitted, the connection map (matrix) can be specified using the
859
860
:func:`~control.InterconnectedSystem.set_connect_map` method.
860
861
861
- inplist : List of tuple of input specifications , optional
862
- List of specifications for how the inputs for the overall system
862
+ inplist : list of input connections , optional
863
+ List of connections for how the inputs for the overall system
863
864
are mapped to the subsystem inputs. The input specification is
864
- similar to the form defined in the connection specification, except
865
- that connections do not specify an input-spec, since these are
866
- the system inputs. The entries are thus of the form:
865
+ similar to the form defined in the connection specification,
866
+ except that connections do not specify an input-spec, since these
867
+ are the system inputs. The entries for a connection are thus of
868
+ the form:
867
869
868
- (output -spec1, output -spec2, ...)
870
+ [input -spec1, input -spec2, ...]
869
871
870
872
Each system input is added to the input for the listed subsystem.
873
+ If the system input connects to only one subsystem input, a single
874
+ input specification can be given (without the inner list).
871
875
872
876
If omitted, the input map can be specified using the
873
877
`set_input_map` method.
874
878
875
- outlist : tuple of output specifications, optional
876
- List of specifications for how the outputs for the subsystems are
877
- mapped to overall system outputs. The output specification is the
878
- same as the form defined in the inplist specification
879
- (including the optional gain term). Numbered outputs must be
880
- chosen from the list of subsystem outputs, but named outputs can
881
- also be contained in the list of subsystem inputs.
879
+ outlist : list of output connections, optional
880
+ List of connections for how the outputs from the subsystems are
881
+ mapped to overall system outputs. The output connection
882
+ description is the same as the form defined in the inplist
883
+ specification (including the optional gain term). Numbered
884
+ outputs must be chosen from the list of subsystem outputs, but
885
+ named outputs can also be contained in the list of subsystem
886
+ inputs.
887
+
888
+ If an output connection contains more than one signal
889
+ specification, then those signals are added together (multiplying
890
+ by the any gain term) to form the system output.
882
891
883
892
If omitted, the output map can be specified using the
884
893
`set_output_map` method.
@@ -896,9 +905,10 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
896
905
Description of the system outputs. Same format as `inputs`.
897
906
898
907
states : int, list of str, or None, optional
899
- Description of the system states. Same format as `inputs`, except
900
- the state names will be of the form '<subsys_name>.<state_name>',
901
- for each subsys in syslist and each state_name of each subsys.
908
+ Description of the system states. Same format as `inputs`. The
909
+ default is `None`, in which case the states will be given names of
910
+ the form '<subsys_name>.<state_name>', for each subsys in syslist
911
+ and each state_name of each subsys.
902
912
903
913
params : dict, optional
904
914
Parameter values for the systems. Passed to the evaluation
@@ -919,6 +929,29 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
919
929
System name (used for specifying signals). If unspecified, a
920
930
generic name <sys[id]> is generated with a unique integer id.
921
931
932
+ Example
933
+ -------
934
+ P = control.LinearIOSystem(
935
+ ct.rss(2, 2, 2, strictly_proper=True), name='P')
936
+ C = control.LinearIOSystem(control.rss(2, 2, 2), name='C')
937
+ S = control.InterconnectedSystem(
938
+ [P, C],
939
+ connections = [
940
+ ['P.u[0]', 'C.y[0]'], ['P.u[1]', 'C.y[0]'],
941
+ ['C.u[0]', '-P.y[0]'], ['C.u[1]', '-P.y[1]']],
942
+ inplist = ['C.u[0]', 'C.u[1]'],
943
+ outlist = ['P.y[0]', 'P.y[1]'],
944
+ )
945
+
946
+ Notes
947
+ -----
948
+ It is possible to replace lists in most of arguments with tuples
949
+ instead, but strictly speaking the only use of tuples should be in the
950
+ specification of an input- or output-signal via the tuple notation
951
+ `(subsys_i, signal_j, gain)` (where `gain` is optional). If you get
952
+ an unexpected error message about a specification being of the wrong
953
+ type, check your use of tuples.
954
+
922
955
"""
923
956
# Convert input and output names to lists if they aren't already
924
957
if not isinstance (inplist , (list , tuple )):
@@ -1006,24 +1039,40 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
1006
1039
input_index = self ._parse_input_spec (connection [0 ])
1007
1040
for output_spec in connection [1 :]:
1008
1041
output_index , gain = self ._parse_output_spec (output_spec )
1009
- self .connect_map [input_index , output_index ] = gain
1042
+ if self .connect_map [input_index , output_index ] != 0 :
1043
+ warn ("multiple connections given for input %d" %
1044
+ input_index + ". Combining with previous entries." )
1045
+ self .connect_map [input_index , output_index ] += gain
1010
1046
1011
1047
# Convert the input list to a matrix: maps system to subsystems
1012
1048
self .input_map = np .zeros ((ninputs , self .ninputs ))
1013
1049
for index , inpspec in enumerate (inplist ):
1014
1050
if isinstance (inpspec , (int , str , tuple )):
1015
1051
inpspec = [inpspec ]
1052
+ if not isinstance (inpspec , list ):
1053
+ raise ValueError ("specifications in inplist must be of type "
1054
+ "int, str, tuple or list." )
1016
1055
for spec in inpspec :
1017
- self .input_map [self ._parse_input_spec (spec ), index ] = 1
1056
+ ulist_index = self ._parse_input_spec (spec )
1057
+ if self .input_map [ulist_index , index ] != 0 :
1058
+ warn ("multiple connections given for input %d" %
1059
+ index + ". Combining with previous entries." )
1060
+ self .input_map [ulist_index , index ] += 1
1018
1061
1019
1062
# Convert the output list to a matrix: maps subsystems to system
1020
1063
self .output_map = np .zeros ((self .noutputs , noutputs + ninputs ))
1021
1064
for index , outspec in enumerate (outlist ):
1022
1065
if isinstance (outspec , (int , str , tuple )):
1023
1066
outspec = [outspec ]
1067
+ if not isinstance (outspec , list ):
1068
+ raise ValueError ("specifications in outlist must be of type "
1069
+ "int, str, tuple or list." )
1024
1070
for spec in outspec :
1025
1071
ylist_index , gain = self ._parse_output_spec (spec )
1026
- self .output_map [index , ylist_index ] = gain
1072
+ if self .output_map [index , ylist_index ] != 0 :
1073
+ warn ("multiple connections given for output %d" %
1074
+ index + ". Combining with previous entries." )
1075
+ self .output_map [index , ylist_index ] += gain
1027
1076
1028
1077
# Save the parameters for the system
1029
1078
self .params = params .copy ()
@@ -1166,7 +1215,9 @@ def _parse_input_spec(self, spec):
1166
1215
1167
1216
"""
1168
1217
# Parse the signal that we received
1169
- subsys_index , input_index = self ._parse_signal (spec , 'input' )
1218
+ subsys_index , input_index , gain = self ._parse_signal (spec , 'input' )
1219
+ if gain != 1 :
1220
+ raise ValueError ("gain not allowed in spec '%s'." % str (spec ))
1170
1221
1171
1222
# Return the index into the input vector list (ylist)
1172
1223
return self .input_offset [subsys_index ] + input_index
@@ -1195,27 +1246,18 @@ def _parse_output_spec(self, spec):
1195
1246
the gain to use for that output.
1196
1247
1197
1248
"""
1198
- gain = 1 # Default gain
1199
-
1200
- # Check for special forms of the input
1201
- if isinstance (spec , tuple ) and len (spec ) == 3 :
1202
- gain = spec [2 ]
1203
- spec = spec [:2 ]
1204
- elif isinstance (spec , str ) and spec [0 ] == '-' :
1205
- gain = - 1
1206
- spec = spec [1 :]
1207
-
1208
1249
# Parse the rest of the spec with standard signal parsing routine
1209
1250
try :
1210
1251
# Start by looking in the set of subsystem outputs
1211
- subsys_index , output_index = self ._parse_signal (spec , 'output' )
1252
+ subsys_index , output_index , gain = \
1253
+ self ._parse_signal (spec , 'output' )
1212
1254
1213
1255
# Return the index into the input vector list (ylist)
1214
1256
return self .output_offset [subsys_index ] + output_index , gain
1215
1257
1216
1258
except ValueError :
1217
1259
# Try looking in the set of subsystem *inputs*
1218
- subsys_index , input_index = self ._parse_signal (
1260
+ subsys_index , input_index , gain = self ._parse_signal (
1219
1261
spec , 'input or output' , dictname = 'input_index' )
1220
1262
1221
1263
# Return the index into the input vector list (ylist)
@@ -1240,17 +1282,27 @@ def _parse_signal(self, spec, signame='input', dictname=None):
1240
1282
"""
1241
1283
import re
1242
1284
1285
+ gain = 1 # Default gain
1286
+
1287
+ # Check for special forms of the input
1288
+ if isinstance (spec , tuple ) and len (spec ) == 3 :
1289
+ gain = spec [2 ]
1290
+ spec = spec [:2 ]
1291
+ elif isinstance (spec , str ) and spec [0 ] == '-' :
1292
+ gain = - 1
1293
+ spec = spec [1 :]
1294
+
1243
1295
# Process cases where we are given indices as integers
1244
1296
if isinstance (spec , int ):
1245
- return spec , 0
1297
+ return spec , 0 , gain
1246
1298
1247
1299
elif isinstance (spec , tuple ) and len (spec ) == 1 \
1248
1300
and isinstance (spec [0 ], int ):
1249
- return spec [0 ], 0
1301
+ return spec [0 ], 0 , gain
1250
1302
1251
1303
elif isinstance (spec , tuple ) and len (spec ) == 2 \
1252
1304
and all ([isinstance (index , int ) for index in spec ]):
1253
- return spec
1305
+ return spec + ( gain ,)
1254
1306
1255
1307
# Figure out the name of the dictionary to use
1256
1308
if dictname is None :
@@ -1276,7 +1328,7 @@ def _parse_signal(self, spec, signame='input', dictname=None):
1276
1328
raise ValueError ("Couldn't find %s signal '%s.%s'." %
1277
1329
(signame , namelist [0 ], namelist [1 ]))
1278
1330
1279
- return system_index , signal_index
1331
+ return system_index , signal_index , gain
1280
1332
1281
1333
# Handle the ('sys', 'sig'), (i, j), and mixed cases
1282
1334
elif isinstance (spec , tuple ) and len (spec ) == 2 and \
@@ -1289,7 +1341,7 @@ def _parse_signal(self, spec, signame='input', dictname=None):
1289
1341
else :
1290
1342
system_index = self ._find_system (spec [0 ])
1291
1343
if system_index is None :
1292
- raise ValueError ("Couldn't find system %s ." % spec [0 ])
1344
+ raise ValueError ("Couldn't find system '%s' ." % spec [0 ])
1293
1345
1294
1346
if isinstance (spec [1 ], int ):
1295
1347
signal_index = spec [1 ]
@@ -1302,7 +1354,7 @@ def _parse_signal(self, spec, signame='input', dictname=None):
1302
1354
if signal_index is None :
1303
1355
raise ValueError ("Couldn't find signal %s.%s." % tuple (spec ))
1304
1356
1305
- return system_index , signal_index
1357
+ return system_index , signal_index , gain
1306
1358
1307
1359
else :
1308
1360
raise ValueError ("Couldn't parse signal reference %s." % str (spec ))
0 commit comments