3131
3232__all__ = ['NonlinearIOSystem' , 'InterconnectedSystem' , 'nlsys' ,
3333 'input_output_response' , 'find_eqpt' , 'linearize' ,
34- 'interconnect' ]
34+ 'interconnect' , 'connection_table' ]
3535
3636
3737class NonlinearIOSystem (InputOutputSystem ):
@@ -395,7 +395,7 @@ def dynamics(self, t, x, u, params=None):
395395 current state
396396 u : array_like
397397 input
398- params : dict ( optional)
398+ params : dict, optional
399399 system parameter values
400400
401401 Returns
@@ -436,7 +436,7 @@ def output(self, t, x, u, params=None):
436436 current state
437437 u : array_like
438438 input
439- params : dict ( optional)
439+ params : dict, optional
440440 system parameter values
441441
442442 Returns
@@ -589,11 +589,14 @@ class InterconnectedSystem(NonlinearIOSystem):
589589
590590 """
591591 def __init__ (self , syslist , connections = None , inplist = None , outlist = None ,
592- params = None , warn_duplicate = None , ** kwargs ):
592+ params = None , warn_duplicate = None , connection_type = None ,
593+ ** kwargs ):
593594 """Create an I/O system from a list of systems + connection info."""
594595 from .statesp import _convert_to_statespace
595596 from .xferfcn import TransferFunction
596597
598+ self .connection_type = connection_type # explicit, implicit, or None
599+
597600 # Convert input and output names to lists if they aren't already
598601 if inplist is not None and not isinstance (inplist , list ):
599602 inplist = [inplist ]
@@ -1001,6 +1004,80 @@ def unused_signals(self):
10011004 return ({inputs [i ][:2 ]: inputs [i ][2 ] for i in unused_sysinp },
10021005 {outputs [i ][:2 ]: outputs [i ][2 ] for i in unused_sysout })
10031006
1007+ def connection_table (self , show_names = False , column_width = 32 ):
1008+ """Print table of connections inside an interconnected system model.
1009+
1010+ Intended primarily for :class:`InterconnectedSystems` that have been
1011+ connected implicitly using signal names.
1012+
1013+ Parameters
1014+ ----------
1015+ show_names : bool, optional
1016+ Instead of printing out the system number, print out the name of
1017+ each system. Default is False because system name is not usually
1018+ specified when performing implicit interconnection using
1019+ :func:`interconnect`.
1020+ column_width : int, optional
1021+ Character width of printed columns.
1022+
1023+ Examples
1024+ --------
1025+ >>> P = ct.ss(1,1,1,0, inputs='u', outputs='y', name='P')
1026+ >>> C = ct.tf(10, [.1, 1], inputs='e', outputs='u', name='C')
1027+ >>> L = ct.interconnect([C, P], inputs='e', outputs='y')
1028+ >>> L.connection_table(show_names=True) # doctest: +SKIP
1029+ signal | source | destination
1030+ --------------------------------------------------------------------
1031+ e | input | C
1032+ u | C | P
1033+ y | P | output
1034+ """
1035+
1036+ print ('signal' .ljust (10 ) + '| source' .ljust (column_width ) + \
1037+ '| destination' )
1038+ print ('-' * (10 + column_width * 2 ))
1039+
1040+ # TODO: update this method for explicitly-connected systems
1041+ if not self .connection_type == 'implicit' :
1042+ warn ('connection_table only gives useful output for implicitly-' \
1043+ 'connected systems' )
1044+
1045+ # collect signal labels
1046+ signal_labels = []
1047+ for sys in self .syslist :
1048+ signal_labels += sys .input_labels + sys .output_labels
1049+ signal_labels = set (signal_labels )
1050+
1051+ for signal_label in signal_labels :
1052+ print (signal_label .ljust (10 ), end = '' )
1053+ sources = '| '
1054+ dests = '| '
1055+
1056+ # overall interconnected system inputs and outputs
1057+ if self .find_input (signal_label ) is not None :
1058+ sources += 'input'
1059+ if self .find_output (signal_label ) is not None :
1060+ dests += 'output'
1061+
1062+ # internal connections
1063+ for idx , sys in enumerate (self .syslist ):
1064+ loc = sys .find_output (signal_label )
1065+ if loc is not None :
1066+ if not sources .endswith (' ' ):
1067+ sources += ', '
1068+ sources += sys .name if show_names else 'system ' + str (idx )
1069+ loc = sys .find_input (signal_label )
1070+ if loc is not None :
1071+ if not dests .endswith (' ' ):
1072+ dests += ', '
1073+ dests += sys .name if show_names else 'system ' + str (idx )
1074+ if len (sources ) >= column_width :
1075+ sources = sources [:column_width - 3 ] + '.. '
1076+ print (sources .ljust (column_width ), end = '' )
1077+ if len (dests ) > column_width :
1078+ dests = dests [:column_width - 3 ] + '.. '
1079+ print (dests .ljust (column_width ), end = '\n ' )
1080+
10041081 def _find_inputs_by_basename (self , basename ):
10051082 """Find all subsystem inputs matching basename
10061083
@@ -1955,7 +2032,7 @@ def interconnect(
19552032 signals are given names, then the forms 'sys.sig' or ('sys', 'sig')
19562033 are also recognized. Finally, for multivariable systems the signal
19572034 index can be given as a list, for example '(subsys_i, [inp_j1, ...,
1958- inp_jn])'; as a slice, for example, 'sys.sig[i:j]'; or as a base
2035+ inp_jn])'; or as a slice, for example, 'sys.sig[i:j]'; or as a base
19592036 name `sys.sig` (which matches `sys.sig[i]`).
19602037
19612038 Similarly, each output-spec should describe an output signal from
@@ -2132,8 +2209,8 @@ def interconnect(
21322209 If a system is duplicated in the list of systems to be connected,
21332210 a warning is generated and a copy of the system is created with the
21342211 name of the new system determined by adding the prefix and suffix
2135- strings in config.defaults['iosys.linearized_system_name_prefix ']
2136- and config.defaults['iosys.linearized_system_name_suffix '], with the
2212+ strings in config.defaults['iosys.duplicate_system_name_prefix ']
2213+ and config.defaults['iosys.duplicate_system_name_suffix '], with the
21372214 default being to add the suffix '$copy' to the system name.
21382215
21392216 In addition to explicit lists of system signals, it is possible to
@@ -2167,19 +2244,21 @@ def interconnect(
21672244
21682245 dt = kwargs .pop ('dt' , None ) # bypass normal 'dt' processing
21692246 name , inputs , outputs , states , _ = _process_iosys_keywords (kwargs )
2247+ connection_type = None # explicit, implicit, or None
21702248
21712249 if not check_unused and (ignore_inputs or ignore_outputs ):
21722250 raise ValueError ('check_unused is False, but either '
21732251 + 'ignore_inputs or ignore_outputs non-empty' )
21742252
2175- if connections is False and not inplist and not outlist \
2176- and not inputs and not outputs :
2253+ if connections is False and not any ((inplist , outlist , inputs , outputs )):
21772254 # user has disabled auto-connect, and supplied neither input
21782255 # nor output mappings; assume they know what they're doing
21792256 check_unused = False
21802257
2181- # If connections was not specified, set up default connection list
2258+ # If connections was not specified, assume implicit interconnection.
2259+ # set up default connection list
21822260 if connections is None :
2261+ connection_type = 'implicit'
21832262 # For each system input, look for outputs with the same name
21842263 connections = []
21852264 for input_sys in syslist :
@@ -2191,17 +2270,17 @@ def interconnect(
21912270 if len (connect ) > 1 :
21922271 connections .append (connect )
21932272
2194- auto_connect = True
2195-
21962273 elif connections is False :
21972274 check_unused = False
21982275 # Use an empty connections list
21992276 connections = []
22002277
2201- elif isinstance (connections , list ) and \
2202- all ([isinstance (cnxn , (str , tuple )) for cnxn in connections ]):
2203- # Special case where there is a single connection
2204- connections = [connections ]
2278+ else :
2279+ connection_type = 'explicit'
2280+ if isinstance (connections , list ) and \
2281+ all ([isinstance (cnxn , (str , tuple )) for cnxn in connections ]):
2282+ # Special case where there is a single connection
2283+ connections = [connections ]
22052284
22062285 # If inplist/outlist is not present, try using inputs/outputs instead
22072286 inplist_none , outlist_none = False , False
@@ -2436,7 +2515,7 @@ def interconnect(
24362515 syslist , connections = connections , inplist = inplist ,
24372516 outlist = outlist , inputs = inputs , outputs = outputs , states = states ,
24382517 params = params , dt = dt , name = name , warn_duplicate = warn_duplicate ,
2439- ** kwargs )
2518+ connection_type = connection_type , ** kwargs )
24402519
24412520 # See if we should add any signals
24422521 if add_unused :
@@ -2457,15 +2536,15 @@ def interconnect(
24572536 syslist , connections = connections , inplist = inplist ,
24582537 outlist = outlist , inputs = inputs , outputs = outputs , states = states ,
24592538 params = params , dt = dt , name = name , warn_duplicate = warn_duplicate ,
2460- ** kwargs )
2539+ connection_type = connection_type , ** kwargs )
24612540
24622541 # check for implicitly dropped signals
24632542 if check_unused :
24642543 newsys .check_unused_signals (ignore_inputs , ignore_outputs )
24652544
24662545 # If all subsystems are linear systems, maintain linear structure
24672546 if all ([isinstance (sys , StateSpace ) for sys in newsys .syslist ]):
2468- return LinearICSystem (newsys , None )
2547+ newsys = LinearICSystem (newsys , None , connection_type = connection_type )
24692548
24702549 return newsys
24712550
@@ -2500,3 +2579,39 @@ def _convert_static_iosystem(sys):
25002579 return NonlinearIOSystem (
25012580 None , lambda t , x , u , params : sys @ u ,
25022581 outputs = sys .shape [0 ], inputs = sys .shape [1 ])
2582+
2583+ def connection_table (sys , show_names = False , column_width = 32 ):
2584+ """Print table of connections inside an interconnected system model.
2585+
2586+ Intended primarily for :class:`InterconnectedSystems` that have been
2587+ connected implicitly using signal names.
2588+
2589+ Parameters
2590+ ----------
2591+ sys : :class:`InterconnectedSystem`
2592+ Interconnected system object
2593+ show_names : bool, optional
2594+ Instead of printing out the system number, print out the name of
2595+ each system. Default is False because system name is not usually
2596+ specified when performing implicit interconnection using
2597+ :func:`interconnect`.
2598+ column_width : int, optional
2599+ Character width of printed columns.
2600+
2601+
2602+ Examples
2603+ --------
2604+ >>> P = ct.ss(1,1,1,0, inputs='u', outputs='y', name='P')
2605+ >>> C = ct.tf(10, [.1, 1], inputs='e', outputs='u', name='C')
2606+ >>> L = ct.interconnect([C, P], inputs='e', outputs='y')
2607+ >>> L.connection_table(show_names=True) # doctest: +SKIP
2608+ signal | source | destination
2609+ --------------------------------------------------------------
2610+ e | input | C
2611+ u | C | P
2612+ y | P | output
2613+ """
2614+ assert isinstance (sys , InterconnectedSystem ), "system must be" \
2615+ "an InterconnectedSystem."
2616+
2617+ sys .connection_table (show_names = show_names , column_width = column_width )
0 commit comments