@@ -76,7 +76,8 @@ class for a set of subclasses that are used to implement specific
7676 Parameter values for the systems. Passed to the evaluation functions
7777 for the system as default values, overriding internal defaults.
7878 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.
8081
8182 Attributes
8283 ----------
@@ -108,6 +109,14 @@ class for a set of subclasses that are used to implement specific
108109 The default is to return the entire system state.
109110
110111 """
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+
111120 def __init__ (self , inputs = None , outputs = None , states = None , params = {},
112121 dt = None , name = None ):
113122 """Create an input/output system.
@@ -143,7 +152,8 @@ def __init__(self, inputs=None, outputs=None, states=None, params={},
143152 functions for the system as default values, overriding internal
144153 defaults.
145154 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.
147157
148158 Returns
149159 -------
@@ -152,9 +162,9 @@ def __init__(self, inputs=None, outputs=None, states=None, params={},
152162
153163 """
154164 # 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
158168
159169 # Parse and store the number of inputs, outputs, and states
160170 self .set_inputs (inputs )
@@ -204,29 +214,19 @@ def __mul__(sys2, sys1):
204214 if dt is False :
205215 raise ValueError ("System timebases are not compabile" )
206216
217+ inplist = [(0 ,i ) for i in range (sys1 .ninputs )]
218+ outlist = [(1 ,i ) for i in range (sys2 .noutputs )]
207219 # Return the series interconnection between the systems
208- newsys = InterconnectedSystem ((sys1 , sys2 ))
220+ newsys = InterconnectedSystem ((sys1 , sys2 ), inplist = inplist , outlist = outlist )
209221
210- # Set up the connecton map
222+ # Set up the connection map manually
211223 newsys .set_connect_map (np .block (
212224 [[np .zeros ((sys1 .ninputs , sys1 .noutputs )),
213225 np .zeros ((sys1 .ninputs , sys2 .noutputs ))],
214226 [np .eye (sys2 .ninputs , sys1 .noutputs ),
215227 np .zeros ((sys2 .ninputs , sys2 .noutputs ))]]
216228 ))
217229
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-
230230 # Return the newly created system
231231 return newsys
232232
@@ -271,18 +271,10 @@ def __add__(sys1, sys2):
271271 ninputs = sys1 .ninputs
272272 noutputs = sys1 .noutputs
273273
274+ inplist = [[(0 ,i ),(1 ,i )] for i in range (ninputs )]
275+ outlist = [[(0 ,i ),(1 ,i )] for i in range (noutputs )]
274276 # 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 )
286278
287279 # Return the newly created system
288280 return newsys
@@ -301,16 +293,10 @@ def __neg__(sys):
301293 if sys .ninputs is None or sys .noutputs is None :
302294 raise ValueError ("Can't determine number of inputs or outputs" )
303295
296+ inplist = [(0 ,i ) for i in range (sys .ninputs )]
297+ outlist = [(0 ,i ,- 1 ) for i in range (sys .noutputs )]
304298 # 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 )
314300
315301 # Return the newly created system
316302 return newsys
@@ -482,29 +468,20 @@ def feedback(self, other=1, sign=-1, params={}):
482468 if dt is False :
483469 raise ValueError ("System timebases are not compabile" )
484470
471+ inplist = [(0 ,i ) for i in range (self .ninputs )]
472+ outlist = [(0 ,i ) for i in range (self .noutputs )]
485473 # 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 )
487476
488- # Set up the connecton map
477+ # Set up the connecton map manually
489478 newsys .set_connect_map (np .block (
490479 [[np .zeros ((self .ninputs , self .noutputs )),
491480 sign * np .eye (self .ninputs , other .noutputs )],
492481 [np .eye (other .ninputs , self .noutputs ),
493482 np .zeros ((other .ninputs , other .noutputs ))]]
494483 ))
495484
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-
508485 # Return the newly created system
509486 return newsys
510487
@@ -564,9 +541,11 @@ def linearize(self, x0, u0, t=0, params={}, eps=1e-6):
564541 linsys = StateSpace (A , B , C , D , self .dt , remove_useless = False )
565542 return LinearIOSystem (linsys )
566543
567- def copy (self ):
544+ def copy (self , newname = None ):
568545 """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
570549
571550
572551class LinearIOSystem (InputOutputSystem , StateSpace ):
@@ -610,7 +589,8 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None,
610589 functions for the system as default values, overriding internal
611590 defaults.
612591 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.
614594
615595 Returns
616596 -------
@@ -728,7 +708,8 @@ def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None,
728708 * dt = True Discrete time with unspecified sampling time
729709
730710 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.
732713
733714 Returns
734715 -------
@@ -808,10 +789,13 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
808789 syslist : array_like of InputOutputSystems
809790 The list of input/output systems to be connected
810791
811- connections : tuple of connection specifications, optional
792+ connections : list of tuple of connection specifications, optional
812793 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:
815799
816800 (input-spec, output-spec1, output-spec2, ...)
817801
@@ -835,10 +819,15 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
835819 If omitted, the connection map (matrix) can be specified using the
836820 :func:`~control.InterconnectedSystem.set_connect_map` method.
837821
838- inplist : tuple of input specifications, optional
822+ inplist : List of tuple of input specifications, optional
839823 List of specifications for how the inputs for the overall system
840824 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+
842831 Each system input is added to the input for the listed subsystem.
843832
844833 If omitted, the input map can be specified using the
@@ -847,14 +836,31 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
847836 outlist : tuple of output specifications, optional
848837 List of specifications for how the outputs for the subsystems are
849838 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
851840 (including the optional gain term). Numbered outputs must be
852841 chosen from the list of subsystem outputs, but named outputs can
853842 also be contained in the list of subsystem inputs.
854843
855844 If omitted, the output map can be specified using the
856845 `set_output_map` method.
857846
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+
858864 params : dict, optional
859865 Parameter values for the systems. Passed to the evaluation
860866 functions for the system as default values, overriding internal
@@ -871,7 +877,8 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
871877 * dt = True Discrete time with unspecified sampling time
872878
873879 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.
875882
876883 """
877884 # Convert input and output names to lists if they aren't already
@@ -885,8 +892,9 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
885892 nstates = 0 ; self .state_offset = []
886893 ninputs = 0 ; self .input_offset = []
887894 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 ):
890898 # Make sure time bases are consistent
891899 # TODO: Use lti._find_timebase() instead?
892900 if dt is None and sys .dt is not None :
@@ -912,36 +920,44 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
912920 ninputs += sys .ninputs
913921 noutputs += sys .noutputs
914922
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 ()]
930944
931945 # Create the I/O system
932946 super (InterconnectedSystem , self ).__init__ (
933947 inputs = len (inplist ), outputs = len (outlist ),
934- states = nstates , params = params , dt = dt )
948+ states = states , params = params , dt = dt , name = name )
935949
936950 # 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." )
945961
946962 # Convert the list of interconnections to a connection map (matrix)
947963 self .connect_map = np .zeros ((ninputs , noutputs ))
@@ -960,9 +976,11 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
960976
961977 # Convert the output list to a matrix: maps subsystems to system
962978 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
966984
967985 # Save the parameters for the system
968986 self .params = params .copy ()
0 commit comments