31
31
32
32
__all__ = ['NonlinearIOSystem' , 'InterconnectedSystem' , 'nlsys' ,
33
33
'input_output_response' , 'find_eqpt' , 'linearize' ,
34
- 'interconnect' ]
34
+ 'interconnect' , 'connection_table' ]
35
35
36
36
37
37
class NonlinearIOSystem (InputOutputSystem ):
@@ -395,7 +395,7 @@ def dynamics(self, t, x, u, params=None):
395
395
current state
396
396
u : array_like
397
397
input
398
- params : dict ( optional)
398
+ params : dict, optional
399
399
system parameter values
400
400
401
401
Returns
@@ -436,7 +436,7 @@ def output(self, t, x, u, params=None):
436
436
current state
437
437
u : array_like
438
438
input
439
- params : dict ( optional)
439
+ params : dict, optional
440
440
system parameter values
441
441
442
442
Returns
@@ -589,11 +589,14 @@ class InterconnectedSystem(NonlinearIOSystem):
589
589
590
590
"""
591
591
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 ):
593
594
"""Create an I/O system from a list of systems + connection info."""
594
595
from .statesp import _convert_to_statespace
595
596
from .xferfcn import TransferFunction
596
597
598
+ self .connection_type = connection_type # explicit, implicit, or None
599
+
597
600
# Convert input and output names to lists if they aren't already
598
601
if inplist is not None and not isinstance (inplist , list ):
599
602
inplist = [inplist ]
@@ -1001,6 +1004,80 @@ def unused_signals(self):
1001
1004
return ({inputs [i ][:2 ]: inputs [i ][2 ] for i in unused_sysinp },
1002
1005
{outputs [i ][:2 ]: outputs [i ][2 ] for i in unused_sysout })
1003
1006
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
+
1004
1081
def _find_inputs_by_basename (self , basename ):
1005
1082
"""Find all subsystem inputs matching basename
1006
1083
@@ -1955,7 +2032,7 @@ def interconnect(
1955
2032
signals are given names, then the forms 'sys.sig' or ('sys', 'sig')
1956
2033
are also recognized. Finally, for multivariable systems the signal
1957
2034
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
1959
2036
name `sys.sig` (which matches `sys.sig[i]`).
1960
2037
1961
2038
Similarly, each output-spec should describe an output signal from
@@ -2132,8 +2209,8 @@ def interconnect(
2132
2209
If a system is duplicated in the list of systems to be connected,
2133
2210
a warning is generated and a copy of the system is created with the
2134
2211
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
2137
2214
default being to add the suffix '$copy' to the system name.
2138
2215
2139
2216
In addition to explicit lists of system signals, it is possible to
@@ -2167,19 +2244,21 @@ def interconnect(
2167
2244
2168
2245
dt = kwargs .pop ('dt' , None ) # bypass normal 'dt' processing
2169
2246
name , inputs , outputs , states , _ = _process_iosys_keywords (kwargs )
2247
+ connection_type = None # explicit, implicit, or None
2170
2248
2171
2249
if not check_unused and (ignore_inputs or ignore_outputs ):
2172
2250
raise ValueError ('check_unused is False, but either '
2173
2251
+ 'ignore_inputs or ignore_outputs non-empty' )
2174
2252
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 )):
2177
2254
# user has disabled auto-connect, and supplied neither input
2178
2255
# nor output mappings; assume they know what they're doing
2179
2256
check_unused = False
2180
2257
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
2182
2260
if connections is None :
2261
+ connection_type = 'implicit'
2183
2262
# For each system input, look for outputs with the same name
2184
2263
connections = []
2185
2264
for input_sys in syslist :
@@ -2191,17 +2270,17 @@ def interconnect(
2191
2270
if len (connect ) > 1 :
2192
2271
connections .append (connect )
2193
2272
2194
- auto_connect = True
2195
-
2196
2273
elif connections is False :
2197
2274
check_unused = False
2198
2275
# Use an empty connections list
2199
2276
connections = []
2200
2277
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 ]
2205
2284
2206
2285
# If inplist/outlist is not present, try using inputs/outputs instead
2207
2286
inplist_none , outlist_none = False , False
@@ -2436,7 +2515,7 @@ def interconnect(
2436
2515
syslist , connections = connections , inplist = inplist ,
2437
2516
outlist = outlist , inputs = inputs , outputs = outputs , states = states ,
2438
2517
params = params , dt = dt , name = name , warn_duplicate = warn_duplicate ,
2439
- ** kwargs )
2518
+ connection_type = connection_type , ** kwargs )
2440
2519
2441
2520
# See if we should add any signals
2442
2521
if add_unused :
@@ -2457,15 +2536,15 @@ def interconnect(
2457
2536
syslist , connections = connections , inplist = inplist ,
2458
2537
outlist = outlist , inputs = inputs , outputs = outputs , states = states ,
2459
2538
params = params , dt = dt , name = name , warn_duplicate = warn_duplicate ,
2460
- ** kwargs )
2539
+ connection_type = connection_type , ** kwargs )
2461
2540
2462
2541
# check for implicitly dropped signals
2463
2542
if check_unused :
2464
2543
newsys .check_unused_signals (ignore_inputs , ignore_outputs )
2465
2544
2466
2545
# If all subsystems are linear systems, maintain linear structure
2467
2546
if all ([isinstance (sys , StateSpace ) for sys in newsys .syslist ]):
2468
- return LinearICSystem (newsys , None )
2547
+ newsys = LinearICSystem (newsys , None , connection_type = connection_type )
2469
2548
2470
2549
return newsys
2471
2550
@@ -2500,3 +2579,39 @@ def _convert_static_iosystem(sys):
2500
2579
return NonlinearIOSystem (
2501
2580
None , lambda t , x , u , params : sys @ u ,
2502
2581
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