From 2e872f8da254a839e3e5488770cb040f9da76b44 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 29 Jun 2024 10:52:38 -0700 Subject: [PATCH 1/5] fix interconnect() issue #1015 --- control/nlsys.py | 57 ++++++++++++++++++++++-------- control/tests/interconnect_test.py | 16 +++++++++ 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/control/nlsys.py b/control/nlsys.py index 358c4b125..68b744759 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -2219,7 +2219,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') @@ -2425,15 +2425,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') + # TODO: refactor code to remove duplication + if isinstance(connection, list): + # Passed a list => create input map + dprint(f" detected input list") + new_inplist.append([]) + for spec in connection: + isys, indices, gain = _parse_spec(syslist, spec, 'input') + for isig in indices: + new_inplist[-1].append((isys, isig, gain)) + dprint(f" adding input {(isys, isig, gain)}") + else: + # Passed a single single => single input + 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=}") @@ -2499,14 +2506,36 @@ 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: + # TODO: refactor code to remove duplication + if isinstance(connection, list): + # Passed a list => create input map + dprint(f" detected output list") + new_outlist.append([]) + for spec in connection: + 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[-1].append((osys, osig, gain)) + except ValueError: + # If not, see if we can find it in inputs + isys, indices, gain = _parse_spec( + syslist, spec, 'input or output', + dictname='input_index') + for isig in indices: + # Use string form to allow searching input list + dprint(f" adding input {(isys, isig, gain)}") + new_outlist[-1].append( + (syslist[isys].name, + syslist[isys].input_labels[isig], gain)) + else: + spec = connection try: # First trying looking in the output signals - osys, indices, gain = _parse_spec(syslist, spec, 'output') + 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)) diff --git a/control/tests/interconnect_test.py b/control/tests/interconnect_test.py index f4b0c59a8..604488ca5 100644 --- a/control/tests/interconnect_test.py +++ b/control/tests/interconnect_test.py @@ -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) From 421131ceb316a0d8c838e187714de7c88976c0aa Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sun, 30 Jun 2024 10:11:31 -0700 Subject: [PATCH 2/5] updated documentation per comment from @bonidydy in #1015 --- control/nlsys.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/control/nlsys.py b/control/nlsys.py index 68b744759..d0ad2c661 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -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, ...] @@ -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 entris 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 From 1964a866c5ab97c6c5b02885346e9a569783d6a1 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Mon, 1 Jul 2024 19:37:34 -0700 Subject: [PATCH 3/5] code cleanup --- control/nlsys.py | 60 +++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/control/nlsys.py b/control/nlsys.py index d0ad2c661..a6e9043da 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -2422,18 +2422,18 @@ def interconnect( elif not found_system: raise ValueError("could not find signal %s" % sname) else: - # TODO: refactor code to remove duplication if isinstance(connection, list): # Passed a list => create input map dprint(f" detected input list") - new_inplist.append([]) + signal_list = [] for spec in connection: isys, indices, gain = _parse_spec(syslist, spec, 'input') for isig in indices: - new_inplist[-1].append((isys, isig, gain)) - dprint(f" adding input {(isys, isig, gain)}") + signal_list.append((isys, isig, gain)) + dprint(f" adding input {(isys, isig, gain)} to list") + new_inplist.append(signal_list) else: - # Passed a single single => single input + # Passed a single signal name => add individual input(s) isys, indices, gain = _parse_spec(syslist, connection, 'input') for isig in indices: new_inplist.append((isys, isig, gain)) @@ -2503,39 +2503,15 @@ def interconnect( elif not found_system: raise ValueError("could not find signal %s" % sname) else: - # TODO: refactor code to remove duplication - if isinstance(connection, list): - # Passed a list => create input map - dprint(f" detected output list") - new_outlist.append([]) - for spec in connection: - 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[-1].append((osys, osig, gain)) - except ValueError: - # If not, see if we can find it in inputs - isys, indices, gain = _parse_spec( - syslist, spec, 'input or output', - dictname='input_index') - for isig in indices: - # Use string form to allow searching input list - dprint(f" adding input {(isys, isig, gain)}") - new_outlist[-1].append( - (syslist[isys].name, - syslist[isys].input_labels[isig], gain)) - else: - spec = 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') + 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)) + dprint(f" adding output {(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( @@ -2544,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=}") From 9a78e33a65ff43b59694dd269b90bd025317806a Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Tue, 2 Jul 2024 07:13:06 -0700 Subject: [PATCH 4/5] fix type pointed out by @sawyerbfuller --- control/nlsys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/nlsys.py b/control/nlsys.py index a6e9043da..c9af0b826 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -2100,7 +2100,7 @@ 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 entris for a connection are + mapped to overall system outputs. The entries for a connection are of the form: [output-spec1, output-spec2, ...] From 584f39d8882fce70ed203acdb2800800b80148c7 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 6 Jul 2024 18:45:59 -0700 Subject: [PATCH 5/5] fixed small indent error --- control/nlsys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/control/nlsys.py b/control/nlsys.py index c9af0b826..a14de1d9c 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -2510,8 +2510,8 @@ def _find_output_or_input_signal(spec): # 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)}") - signal_list.append((osys, osig, gain)) + dprint(f" adding output {(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(