@@ -76,7 +76,8 @@ class for a set of subclasses that are used to implement specific
76
76
Parameter values for the systems. Passed to the evaluation functions
77
77
for the system as default values, overriding internal defaults.
78
78
name : string, optional
79
- System name (used for specifying signals)
79
+ System name (used for specifying signals). If unspecified, a generic
80
+ name <sys[id]> is generated with a unique integer id.
80
81
81
82
Attributes
82
83
----------
@@ -108,6 +109,14 @@ class for a set of subclasses that are used to implement specific
108
109
The default is to return the entire system state.
109
110
110
111
"""
112
+
113
+ idCounter = 0
114
+ def name_or_default (self , name = None ):
115
+ if name is None :
116
+ name = "sys[{}]" .format (InputOutputSystem .idCounter )
117
+ InputOutputSystem .idCounter += 1
118
+ return name
119
+
111
120
def __init__ (self , inputs = None , outputs = None , states = None , params = {},
112
121
dt = None , name = None ):
113
122
"""Create an input/output system.
@@ -143,7 +152,8 @@ def __init__(self, inputs=None, outputs=None, states=None, params={},
143
152
functions for the system as default values, overriding internal
144
153
defaults.
145
154
name : string, optional
146
- System name (used for specifying signals)
155
+ System name (used for specifying signals). If unspecified, a generic
156
+ name <sys[id]> is generated with a unique integer id.
147
157
148
158
Returns
149
159
-------
@@ -152,9 +162,9 @@ def __init__(self, inputs=None, outputs=None, states=None, params={},
152
162
153
163
"""
154
164
# Store the input arguments
155
- self .params = params .copy () # default parameters
156
- self .dt = dt # timebase
157
- self .name = name # system name
165
+ self .params = params .copy () # default parameters
166
+ self .dt = dt # timebase
167
+ self .name = self . name_or_default ( name ) # system name
158
168
159
169
# Parse and store the number of inputs, outputs, and states
160
170
self .set_inputs (inputs )
@@ -204,29 +214,19 @@ def __mul__(sys2, sys1):
204
214
if dt is False :
205
215
raise ValueError ("System timebases are not compabile" )
206
216
217
+ inplist = [(0 ,i ) for i in range (sys1 .ninputs )]
218
+ outlist = [(1 ,i ) for i in range (sys2 .noutputs )]
207
219
# Return the series interconnection between the systems
208
- newsys = InterconnectedSystem ((sys1 , sys2 ))
220
+ newsys = InterconnectedSystem ((sys1 , sys2 ), inplist = inplist , outlist = outlist )
209
221
210
- # Set up the connecton map
222
+ # Set up the connection map manually
211
223
newsys .set_connect_map (np .block (
212
224
[[np .zeros ((sys1 .ninputs , sys1 .noutputs )),
213
225
np .zeros ((sys1 .ninputs , sys2 .noutputs ))],
214
226
[np .eye (sys2 .ninputs , sys1 .noutputs ),
215
227
np .zeros ((sys2 .ninputs , sys2 .noutputs ))]]
216
228
))
217
229
218
- # Set up the input map
219
- newsys .set_input_map (np .concatenate (
220
- (np .eye (sys1 .ninputs ), np .zeros ((sys2 .ninputs , sys1 .ninputs ))),
221
- axis = 0 ))
222
- # TODO: set up input names
223
-
224
- # Set up the output map
225
- newsys .set_output_map (np .concatenate (
226
- (np .zeros ((sys2 .noutputs , sys1 .noutputs )), np .eye (sys2 .noutputs )),
227
- axis = 1 ))
228
- # TODO: set up output names
229
-
230
230
# Return the newly created system
231
231
return newsys
232
232
@@ -271,18 +271,10 @@ def __add__(sys1, sys2):
271
271
ninputs = sys1 .ninputs
272
272
noutputs = sys1 .noutputs
273
273
274
+ inplist = [[(0 ,i ),(1 ,i )] for i in range (ninputs )]
275
+ outlist = [[(0 ,i ),(1 ,i )] for i in range (noutputs )]
274
276
# Create a new system to handle the composition
275
- newsys = InterconnectedSystem ((sys1 , sys2 ))
276
-
277
- # Set up the input map
278
- newsys .set_input_map (np .concatenate (
279
- (np .eye (ninputs ), np .eye (ninputs )), axis = 0 ))
280
- # TODO: set up input names
281
-
282
- # Set up the output map
283
- newsys .set_output_map (np .concatenate (
284
- (np .eye (noutputs ), np .eye (noutputs )), axis = 1 ))
285
- # TODO: set up output names
277
+ newsys = InterconnectedSystem ((sys1 , sys2 ), inplist = inplist , outlist = outlist )
286
278
287
279
# Return the newly created system
288
280
return newsys
@@ -301,16 +293,10 @@ def __neg__(sys):
301
293
if sys .ninputs is None or sys .noutputs is None :
302
294
raise ValueError ("Can't determine number of inputs or outputs" )
303
295
296
+ inplist = [(0 ,i ) for i in range (sys .ninputs )]
297
+ outlist = [(0 ,i ,- 1 ) for i in range (sys .noutputs )]
304
298
# Create a new system to hold the negation
305
- newsys = InterconnectedSystem ((sys ,), dt = sys .dt )
306
-
307
- # Set up the input map (identity)
308
- newsys .set_input_map (np .eye (sys .ninputs ))
309
- # TODO: set up input names
310
-
311
- # Set up the output map (negate the output)
312
- newsys .set_output_map (- np .eye (sys .noutputs ))
313
- # TODO: set up output names
299
+ newsys = InterconnectedSystem ((sys ,), dt = sys .dt , inplist = inplist , outlist = outlist )
314
300
315
301
# Return the newly created system
316
302
return newsys
@@ -482,29 +468,20 @@ def feedback(self, other=1, sign=-1, params={}):
482
468
if dt is False :
483
469
raise ValueError ("System timebases are not compabile" )
484
470
471
+ inplist = [(0 ,i ) for i in range (self .ninputs )]
472
+ outlist = [(0 ,i ) for i in range (self .noutputs )]
485
473
# Return the series interconnection between the systems
486
- newsys = InterconnectedSystem ((self , other ), params = params , dt = dt )
474
+ newsys = InterconnectedSystem ((self , other ), inplist = inplist , outlist = outlist ,
475
+ params = params , dt = dt )
487
476
488
- # Set up the connecton map
477
+ # Set up the connecton map manually
489
478
newsys .set_connect_map (np .block (
490
479
[[np .zeros ((self .ninputs , self .noutputs )),
491
480
sign * np .eye (self .ninputs , other .noutputs )],
492
481
[np .eye (other .ninputs , self .noutputs ),
493
482
np .zeros ((other .ninputs , other .noutputs ))]]
494
483
))
495
484
496
- # Set up the input map
497
- newsys .set_input_map (np .concatenate (
498
- (np .eye (self .ninputs ), np .zeros ((other .ninputs , self .ninputs ))),
499
- axis = 0 ))
500
- # TODO: set up input names
501
-
502
- # Set up the output map
503
- newsys .set_output_map (np .concatenate (
504
- (np .eye (self .noutputs ), np .zeros ((self .noutputs , other .noutputs ))),
505
- axis = 1 ))
506
- # TODO: set up output names
507
-
508
485
# Return the newly created system
509
486
return newsys
510
487
@@ -564,9 +541,11 @@ def linearize(self, x0, u0, t=0, params={}, eps=1e-6):
564
541
linsys = StateSpace (A , B , C , D , self .dt , remove_useless = False )
565
542
return LinearIOSystem (linsys )
566
543
567
- def copy (self ):
544
+ def copy (self , newname = None ):
568
545
"""Make a copy of an input/output system."""
569
- return copy .copy (self )
546
+ newsys = copy .copy (self )
547
+ newsys .name = self .name_or_default ("copy of " + self .name if not newname else newname )
548
+ return newsys
570
549
571
550
572
551
class LinearIOSystem (InputOutputSystem , StateSpace ):
@@ -610,7 +589,8 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None,
610
589
functions for the system as default values, overriding internal
611
590
defaults.
612
591
name : string, optional
613
- System name (used for specifying signals)
592
+ System name (used for specifying signals). If unspecified, a generic
593
+ name <sys[id]> is generated with a unique integer id.
614
594
615
595
Returns
616
596
-------
@@ -728,7 +708,8 @@ def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None,
728
708
* dt = True Discrete time with unspecified sampling time
729
709
730
710
name : string, optional
731
- System name (used for specifying signals).
711
+ System name (used for specifying signals). If unspecified, a generic
712
+ name <sys[id]> is generated with a unique integer id.
732
713
733
714
Returns
734
715
-------
@@ -808,10 +789,13 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
808
789
syslist : array_like of InputOutputSystems
809
790
The list of input/output systems to be connected
810
791
811
- connections : tuple of connection specifications, optional
792
+ connections : list of tuple of connection specifications, optional
812
793
Description of the internal connections between the subsystems.
813
- Each element of the tuple describes an input to one of the
814
- subsystems. The entries are are of the form:
794
+
795
+ [connection1, connection2, ...]
796
+
797
+ Each connection is a tuple that describes an input to one of the
798
+ subsystems. The entries are of the form:
815
799
816
800
(input-spec, output-spec1, output-spec2, ...)
817
801
@@ -835,10 +819,15 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
835
819
If omitted, the connection map (matrix) can be specified using the
836
820
:func:`~control.InterconnectedSystem.set_connect_map` method.
837
821
838
- inplist : tuple of input specifications, optional
822
+ inplist : List of tuple of input specifications, optional
839
823
List of specifications for how the inputs for the overall system
840
824
are mapped to the subsystem inputs. The input specification is
841
- the same as the form defined in the connection specification.
825
+ similar to the form defined in the connection specification, except
826
+ that connections do not specify an input-spec, since these are
827
+ the system inputs. The entries are thus of the form:
828
+
829
+ (output-spec1, output-spec2, ...)
830
+
842
831
Each system input is added to the input for the listed subsystem.
843
832
844
833
If omitted, the input map can be specified using the
@@ -847,14 +836,31 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
847
836
outlist : tuple of output specifications, optional
848
837
List of specifications for how the outputs for the subsystems are
849
838
mapped to overall system outputs. The output specification is the
850
- same as the form defined in the connection specification
839
+ same as the form defined in the inplist specification
851
840
(including the optional gain term). Numbered outputs must be
852
841
chosen from the list of subsystem outputs, but named outputs can
853
842
also be contained in the list of subsystem inputs.
854
843
855
844
If omitted, the output map can be specified using the
856
845
`set_output_map` method.
857
846
847
+ inputs : int, list of str or None, optional
848
+ Description of the system inputs. This can be given as an integer
849
+ count or as a list of strings that name the individual signals.
850
+ If an integer count is specified, the names of the signal will be
851
+ of the form `s[i]` (where `s` is one of `u`, `y`, or `x`). If
852
+ this parameter is not given or given as `None`, the relevant
853
+ quantity will be determined when possible based on other
854
+ information provided to functions using the system.
855
+
856
+ outputs : int, list of str or None, optional
857
+ Description of the system outputs. Same format as `inputs`.
858
+
859
+ states : int, list of str, or None, optional
860
+ Description of the system states. Same format as `inputs`, except
861
+ the state names will be of the form '<subsys_name>.<state_name>',
862
+ for each subsys in syslist and each state_name of each subsys.
863
+
858
864
params : dict, optional
859
865
Parameter values for the systems. Passed to the evaluation
860
866
functions for the system as default values, overriding internal
@@ -871,7 +877,8 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
871
877
* dt = True Discrete time with unspecified sampling time
872
878
873
879
name : string, optional
874
- System name (used for specifying signals).
880
+ System name (used for specifying signals). If unspecified, a generic
881
+ name <sys[id]> is generated with a unique integer id.
875
882
876
883
"""
877
884
# Convert input and output names to lists if they aren't already
@@ -885,8 +892,9 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
885
892
nstates = 0 ; self .state_offset = []
886
893
ninputs = 0 ; self .input_offset = []
887
894
noutputs = 0 ; self .output_offset = []
888
- system_count = 0
889
- for sys in syslist :
895
+ sysobj_name_dct = {}
896
+ sysname_count_dct = {}
897
+ for sysidx , sys in enumerate (syslist ):
890
898
# Make sure time bases are consistent
891
899
# TODO: Use lti._find_timebase() instead?
892
900
if dt is None and sys .dt is not None :
@@ -912,36 +920,44 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
912
920
ninputs += sys .ninputs
913
921
noutputs += sys .noutputs
914
922
915
- # Store the index to the system for later retrieval
916
- # TODO: look for duplicated system names
917
- self .syslist_index [sys .name ] = system_count
918
- system_count += 1
919
-
920
- # Check for duplicate systems or duplicate names
921
- sysobj_list = []
922
- sysname_list = []
923
- for sys in syslist :
924
- if sys in sysobj_list :
925
- warn ("Duplicate object found in system list: %s" % str (sys ))
926
- elif sys .name is not None and sys .name in sysname_list :
927
- warn ("Duplicate name found in system list: %s" % sys .name )
928
- sysobj_list .append (sys )
929
- sysname_list .append (sys .name )
923
+ # Check for duplicate systems or duplicate names
924
+ # Duplicates are renamed sysname_1, sysname_2, etc.
925
+ if sys in sysobj_name_dct :
926
+ sys = sys .copy ()
927
+ warn ("Duplicate object found in system list: %s. Making a copy" % str (sys ))
928
+ if sys .name is not None and sys .name in sysname_count_dct :
929
+ count = sysname_count_dct [sys .name ]
930
+ sysname_count_dct [sys .name ] += 1
931
+ sysname = sys .name + "_" + str (count )
932
+ sysobj_name_dct [sys ] = sysname
933
+ self .syslist_index [sysname ] = sysidx
934
+ warn ("Duplicate name found in system list. Renamed to {}" .format (sysname ))
935
+ else :
936
+ sysname_count_dct [sys .name ] = 1
937
+ sysobj_name_dct [sys ] = sys .name
938
+ self .syslist_index [sys .name ] = sysidx
939
+
940
+ if states is None :
941
+ states = []
942
+ for sys , sysname in sysobj_name_dct .items ():
943
+ states += [sysname + '.' + statename for statename in sys .state_index .keys ()]
930
944
931
945
# Create the I/O system
932
946
super (InterconnectedSystem , self ).__init__ (
933
947
inputs = len (inplist ), outputs = len (outlist ),
934
- states = nstates , params = params , dt = dt )
948
+ states = states , params = params , dt = dt , name = name )
935
949
936
950
# If input or output list was specified, update it
937
- nsignals , self .input_index = \
938
- self ._process_signal_list (inputs , prefix = 'u' )
939
- if nsignals is not None and len (inplist ) != nsignals :
940
- raise ValueError ("Wrong number/type of inputs given." )
941
- nsignals , self .output_index = \
942
- self ._process_signal_list (outputs , prefix = 'y' )
943
- if nsignals is not None and len (outlist ) != nsignals :
944
- raise ValueError ("Wrong number/type of outputs given." )
951
+ if inputs is not None :
952
+ nsignals , self .input_index = \
953
+ self ._process_signal_list (inputs , prefix = 'u' )
954
+ if nsignals is not None and len (inplist ) != nsignals :
955
+ raise ValueError ("Wrong number/type of inputs given." )
956
+ if outputs is not None :
957
+ nsignals , self .output_index = \
958
+ self ._process_signal_list (outputs , prefix = 'y' )
959
+ if nsignals is not None and len (outlist ) != nsignals :
960
+ raise ValueError ("Wrong number/type of outputs given." )
945
961
946
962
# Convert the list of interconnections to a connection map (matrix)
947
963
self .connect_map = np .zeros ((ninputs , noutputs ))
@@ -960,9 +976,11 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
960
976
961
977
# Convert the output list to a matrix: maps subsystems to system
962
978
self .output_map = np .zeros ((self .noutputs , noutputs + ninputs ))
963
- for index in range (len (outlist )):
964
- ylist_index , gain = self ._parse_output_spec (outlist [index ])
965
- self .output_map [index , ylist_index ] = gain
979
+ for index , outspec in enumerate (outlist ):
980
+ if isinstance (outspec , (int , str , tuple )): outspec = [outspec ]
981
+ for spec in outspec :
982
+ ylist_index , gain = self ._parse_output_spec (spec )
983
+ self .output_map [index , ylist_index ] = gain
966
984
967
985
# Save the parameters for the system
968
986
self .params = params .copy ()
0 commit comments