Skip to content

Commit 405bf77

Browse files
authored
Merge pull request #925 from sawyerbfuller/connections
2 parents 42c6fb1 + 5c23f1a commit 405bf77

File tree

5 files changed

+259
-22
lines changed

5 files changed

+259
-22
lines changed

control/iosys.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ def isctime(sys, strict=False):
539539
return sys.isctime(strict)
540540

541541

542-
# Utility function to parse nameio keywords
542+
# Utility function to parse iosys keywords
543543
def _process_iosys_keywords(
544544
keywords={}, defaults={}, static=False, end=False):
545545
"""Process iosys specification.
@@ -611,7 +611,7 @@ def pop_with_default(kw, defval=None, return_list=True):
611611
return name, inputs, outputs, states, dt
612612

613613
#
614-
# Parse 'dt' in for named I/O system
614+
# Parse 'dt' for I/O system
615615
#
616616
# The 'dt' keyword is used to set the timebase for a system. Its
617617
# processing is a bit unusual: if it is not specified at all, then the

control/nlsys.py

Lines changed: 134 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
__all__ = ['NonlinearIOSystem', 'InterconnectedSystem', 'nlsys',
3333
'input_output_response', 'find_eqpt', 'linearize',
34-
'interconnect']
34+
'interconnect', 'connection_table']
3535

3636

3737
class 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)

control/statesp.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1459,7 +1459,7 @@ class LinearICSystem(InterconnectedSystem, StateSpace):
14591459
14601460
"""
14611461

1462-
def __init__(self, io_sys, ss_sys=None):
1462+
def __init__(self, io_sys, ss_sys=None, connection_type=None):
14631463
#
14641464
# Because this is a "hybrid" object, the initialization proceeds in
14651465
# stages. We first create an empty InputOutputSystem of the
@@ -1483,6 +1483,7 @@ def __init__(self, io_sys, ss_sys=None):
14831483
self.input_map = io_sys.input_map
14841484
self.output_map = io_sys.output_map
14851485
self.params = io_sys.params
1486+
self.connection_type = connection_type
14861487

14871488
# If we didnt' get a state space system, linearize the full system
14881489
if ss_sys is None:

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy