Skip to content

fix parallel input or output interconnect bug (issue #1015) #1017

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
62 changes: 38 additions & 24 deletions control/nlsys.py
Original file line number Diff line number Diff line change
Expand Up @@ -2086,10 +2086,8 @@ def interconnect(

inplist : list of input connections, optional
List of connections for how the inputs for the overall system are
mapped to the subsystem inputs. The input specification is similar to
the form defined in the connection specification, except that
connections do not specify an input-spec, since these are the system
inputs. The entries for a connection are thus of the form:
mapped to the subsystem inputs. The entries for a connection are
of the form:

[input-spec1, input-spec2, ...]

Expand All @@ -2102,11 +2100,10 @@ def interconnect(

outlist : list of output connections, optional
List of connections for how the outputs from the subsystems are
mapped to overall system outputs. The output connection
description is the same as the form defined in the inplist
specification (including the optional gain term). Numbered outputs
must be chosen from the list of subsystem outputs, but named
outputs can also be contained in the list of subsystem inputs.
mapped to overall system outputs. The entries for a connection are
of the form:

[output-spec1, output-spec2, ...]

If an output connection contains more than one signal specification,
then those signals are added together (multiplying by the any gain
Expand Down Expand Up @@ -2219,7 +2216,7 @@ def interconnect(
... inplist=['C'], outlist=['P'])

A feedback system can also be constructed using the
:func:`~control.summing_block` function and the ability to
:func:`~control.summing_junction` function and the ability to
automatically interconnect signals with the same names:

>>> P = ct.tf(1, [1, 0], inputs='u', outputs='y')
Expand Down Expand Up @@ -2425,15 +2422,22 @@ def interconnect(
elif not found_system:
raise ValueError("could not find signal %s" % sname)
else:
# Regular signal specification
if not isinstance(connection, list):
dprint(f" converting item to list")
connection = [connection]
for spec in connection:
isys, indices, gain = _parse_spec(syslist, spec, 'input')
if isinstance(connection, list):
# Passed a list => create input map
dprint(f" detected input list")
signal_list = []
for spec in connection:
isys, indices, gain = _parse_spec(syslist, spec, 'input')
for isig in indices:
signal_list.append((isys, isig, gain))
dprint(f" adding input {(isys, isig, gain)} to list")
new_inplist.append(signal_list)
else:
# Passed a single signal name => add individual input(s)
isys, indices, gain = _parse_spec(syslist, connection, 'input')
for isig in indices:
dprint(f" adding input {(isys, isig, gain)}")
new_inplist.append((isys, isig, gain))
dprint(f" adding input {(isys, isig, gain)}")
inplist, inputs = new_inplist, new_inputs
dprint(f" {inplist=}\n {inputs=}")

Expand Down Expand Up @@ -2499,17 +2503,15 @@ def interconnect(
elif not found_system:
raise ValueError("could not find signal %s" % sname)
else:
# Regular signal specification
if not isinstance(connection, list):
dprint(f" converting item to list")
connection = [connection]
for spec in connection:
# Utility function to find named output or input signal
def _find_output_or_input_signal(spec):
signal_list = []
try:
# First trying looking in the output signals
osys, indices, gain = _parse_spec(syslist, spec, 'output')
for osig in indices:
dprint(f" adding output {(osys, osig, gain)}")
new_outlist.append((osys, osig, gain))
signal_list.append((osys, osig, gain))
except ValueError:
# If not, see if we can find it in inputs
isys, indices, gain = _parse_spec(
Expand All @@ -2518,9 +2520,21 @@ def interconnect(
for isig in indices:
# Use string form to allow searching input list
dprint(f" adding input {(isys, isig, gain)}")
new_outlist.append(
signal_list.append(
(syslist[isys].name,
syslist[isys].input_labels[isig], gain))
return signal_list

if isinstance(connection, list):
# Passed a list => create input map
dprint(f" detected output list")
signal_list = []
for spec in connection:
signal_list += _find_output_or_input_signal(spec)
new_outlist.append(signal_list)
else:
new_outlist += _find_output_or_input_signal(connection)

outlist, outputs = new_outlist, new_outputs
dprint(f" {outlist=}\n {outputs=}")

Expand Down
16 changes: 16 additions & 0 deletions control/tests/interconnect_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,3 +689,19 @@ def test_interconnect_params():
timepts = np.linspace(0, 10)
resp = ct.input_output_response(sys, timepts, 0, params={'a': -1})
assert resp.states[0, -1].item() < 2 * math.exp(-10)


# Bug identified in issue #1015
def test_parallel_interconnect():
sys1 = ct.rss(2, 1, 1, name='S1')
sys2 = ct.rss(2, 1, 1, name='S2')

sys_bd = sys1 + sys2
sys_ic = ct.interconnect(
[sys1, sys2],
inplist=[['S1.u[0]', 'S2.u[0]']],
outlist=[['S1.y[0]', 'S2.y[0]']])
np.testing.assert_allclose(sys_bd.A, sys_ic.A)
np.testing.assert_allclose(sys_bd.B, sys_ic.B)
np.testing.assert_allclose(sys_bd.C, sys_ic.C)
np.testing.assert_allclose(sys_bd.D, sys_ic.D)
Loading