Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
added field to interconnectedSystem and LinearICSystem to keep track …
…of whether connection was explicit or implicit and issue a warning in connection_table if explicit
  • Loading branch information
sawyerbfuller committed Aug 14, 2023
commit 58e1b601e84c2943a5a62eb42767a50da4627498
36 changes: 22 additions & 14 deletions control/nlsys.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,11 +589,14 @@ class InterconnectedSystem(NonlinearIOSystem):

"""
def __init__(self, syslist, connections=None, inplist=None, outlist=None,
params=None, warn_duplicate=None, **kwargs):
params=None, warn_duplicate=None, connection_type=None,
**kwargs):
"""Create an I/O system from a list of systems + connection info."""
from .statesp import _convert_to_statespace
from .xferfcn import TransferFunction

self.connection_type = connection_type # explicit, implicit, or None

# Convert input and output names to lists if they aren't already
if inplist is not None and not isinstance(inplist, list):
inplist = [inplist]
Expand Down Expand Up @@ -1034,8 +1037,10 @@ def connection_table(self, show_names=False, column_width=32):
'| destination')
print('-'*(10 + column_width * 2))

# TODO: version of this method that is better suited
# to explicitly-connected systems
# TODO: update this method for explicitly-connected systems
if not self.connection_type == 'implicit':
warn('connection_table only gives useful output for implicitly-'\
'connected systems')

# collect signal labels
signal_labels = []
Expand Down Expand Up @@ -2239,6 +2244,7 @@ def interconnect(

dt = kwargs.pop('dt', None) # bypass normal 'dt' processing
name, inputs, outputs, states, _ = _process_iosys_keywords(kwargs)
connection_type = None # explicit, implicit, or None

if not check_unused and (ignore_inputs or ignore_outputs):
raise ValueError('check_unused is False, but either '
Expand All @@ -2249,8 +2255,10 @@ def interconnect(
# nor output mappings; assume they know what they're doing
check_unused = False

# If connections was not specified, set up default connection list
# If connections was not specified, assume implicit interconnection.
# set up default connection list
if connections is None:
connection_type = 'implicit'
# For each system input, look for outputs with the same name
connections = []
for input_sys in syslist:
Expand All @@ -2262,17 +2270,17 @@ def interconnect(
if len(connect) > 1:
connections.append(connect)

auto_connect = True

elif connections is False:
check_unused = False
# Use an empty connections list
connections = []

elif isinstance(connections, list) and \
all([isinstance(cnxn, (str, tuple)) for cnxn in connections]):
# Special case where there is a single connection
connections = [connections]
else:
connection_type = 'explicit'
if isinstance(connections, list) and \
all([isinstance(cnxn, (str, tuple)) for cnxn in connections]):
# Special case where there is a single connection
connections = [connections]

# If inplist/outlist is not present, try using inputs/outputs instead
inplist_none, outlist_none = False, False
Expand Down Expand Up @@ -2507,7 +2515,7 @@ def interconnect(
syslist, connections=connections, inplist=inplist,
outlist=outlist, inputs=inputs, outputs=outputs, states=states,
params=params, dt=dt, name=name, warn_duplicate=warn_duplicate,
**kwargs)
connection_type=connection_type, **kwargs)

# See if we should add any signals
if add_unused:
Expand All @@ -2528,15 +2536,15 @@ def interconnect(
syslist, connections=connections, inplist=inplist,
outlist=outlist, inputs=inputs, outputs=outputs, states=states,
params=params, dt=dt, name=name, warn_duplicate=warn_duplicate,
**kwargs)
connection_type=connection_type, **kwargs)

# check for implicitly dropped signals
if check_unused:
newsys.check_unused_signals(ignore_inputs, ignore_outputs)

# If all subsystems are linear systems, maintain linear structure
if all([isinstance(sys, StateSpace) for sys in newsys.syslist]):
return LinearICSystem(newsys, None)
newsys = LinearICSystem(newsys, None, connection_type=connection_type)

return newsys

Expand Down Expand Up @@ -2606,4 +2614,4 @@ def connection_table(sys, show_names=False, column_width=32):
assert isinstance(sys, InterconnectedSystem), "system must be"\
"an InterconnectedSystem."

sys.connection_table(show_names=show_names)
sys.connection_table(show_names=show_names, column_width=column_width)
Comment on lines +2582 to +2617

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not something that we need to fix in this PR, but we should eventually adopt some mechanism for creating duplicate docstrings from a single primary docstring (see, for example, the discussion here).

3 changes: 2 additions & 1 deletion control/statesp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1459,7 +1459,7 @@ class LinearICSystem(InterconnectedSystem, StateSpace):

"""

def __init__(self, io_sys, ss_sys=None):
def __init__(self, io_sys, ss_sys=None, connection_type=None):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to allow the connection type to be overridden? It seems like this should just be inherited from the underlying InterconnectedSystem.

#
# Because this is a "hybrid" object, the initialization proceeds in
# stages. We first create an empty InputOutputSystem of the
Expand All @@ -1483,6 +1483,7 @@ def __init__(self, io_sys, ss_sys=None):
self.input_map = io_sys.input_map
self.output_map = io_sys.output_map
self.params = io_sys.params
self.connection_type = connection_type

# If we didnt' get a state space system, linearize the full system
if ss_sys is None:
Expand Down
30 changes: 29 additions & 1 deletion control/tests/interconnect_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ def test_connection_table(capsys, show_names):

mystrings = \
["signal | source | destination",
"-------------------------------------------------------------------"]
"-------------------------------------------------------------------"]
if show_names:
mystrings += \
["u | input | P1, P2, P3",
Expand All @@ -294,6 +294,34 @@ def test_connection_table(capsys, show_names):
assert str_ in captured_from_method
assert str_ in captured_from_function

# check change column width
P.connection_table(show_names=show_names, column_width=20)
captured_from_method = capsys.readouterr().out

ct.connection_table(P, show_names=show_names, column_width=20)
captured_from_function = capsys.readouterr().out

mystrings = \
["signal | source | destination",
"------------------------------------------------"]
if show_names:
mystrings += \
["u | input | P1, P2, P3",
"x | P1 | output ",
"y | P2 | output",
"z | P3 | output"]
else:
mystrings += \
["u | input | system 0, syste.. ",
"x | system 0 | output ",
"y | system 1 | output",
"z | system 2 | output"]

for str_ in mystrings:
assert str_ in captured_from_method
assert str_ in captured_from_function


def test_interconnect_exceptions():
# First make sure the docstring example works
P = ct.tf(1, [1, 0], input='u', output='y')
Expand Down