From aa9ef958c53dcdbf04384784ab5d1cabf42a08b2 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Fri, 21 Jul 2023 14:24:30 -0700 Subject: [PATCH 01/12] fix name of default name for duplcated system in interconnect --- control/nlsys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/control/nlsys.py b/control/nlsys.py index 82b6aeef3..2f8d549e4 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -2132,8 +2132,8 @@ def interconnect( If a system is duplicated in the list of systems to be connected, a warning is generated and a copy of the system is created with the name of the new system determined by adding the prefix and suffix - strings in config.defaults['iosys.linearized_system_name_prefix'] - and config.defaults['iosys.linearized_system_name_suffix'], with the + strings in config.defaults['iosys.duplicate_system_name_prefix'] + and config.defaults['iosys.duplicate_system_name_suffix'], with the default being to add the suffix '$copy' to the system name. In addition to explicit lists of system signals, it is possible to From 5e92bb405e7db4466933d1bf35cbe72065f1c0d7 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Mon, 24 Jul 2023 10:05:16 -0700 Subject: [PATCH 02/12] remove namedio from docstrings --- control/iosys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/control/iosys.py b/control/iosys.py index 99f0e7db6..53cda7d19 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -539,7 +539,7 @@ def isctime(sys, strict=False): return sys.isctime(strict) -# Utility function to parse nameio keywords +# Utility function to parse iosys keywords def _process_iosys_keywords( keywords={}, defaults={}, static=False, end=False): """Process iosys specification. @@ -611,7 +611,7 @@ def pop_with_default(kw, defval=None, return_list=True): return name, inputs, outputs, states, dt # -# Parse 'dt' in for named I/O system +# Parse 'dt' for I/O system # # The 'dt' keyword is used to set the timebase for a system. Its # processing is a bit unusual: if it is not specified at all, then the From 4e96553e2cf0e7bcb4b562c7347bf64f34f2bb50 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Mon, 24 Jul 2023 12:37:25 -0700 Subject: [PATCH 03/12] add signal_table method to show implicit interconnections --- control/nlsys.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/control/nlsys.py b/control/nlsys.py index 2f8d549e4..041a29e59 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -1001,6 +1001,69 @@ def unused_signals(self): return ({inputs[i][:2]: inputs[i][2] for i in unused_sysinp}, {outputs[i][:2]: outputs[i][2] for i in unused_sysout}) + def signal_table(self, show_names=False): + """Print table of signal names, sources, and destinations. + + Intended primarily for systems that have been connected implicitly + using signal names. + + Parameters + ---------- + show_names : bool (optional) + Instead of printing out the system number, print out the name of + each system. Default is False because system name is not usually + specified when performing implicit interconnection using + :func:`interconnect`. + + Examples + -------- + >>> P = ct.ss(1,1,1,0, inputs='u', outputs='y') + >>> C = ct.tf(10, [.1, 1], inputs='e', outputs='u') + >>> L = ct.interconnect([C, P], inputs='e', outputs='y') + >>> L.signal_table() + signal | source | destination + -------------------------------------------------------------- + e | input | system 0 + u | system 0 | system 1 + y | system 1 | output + """ + + spacing = 26 + print('signal'.ljust(10) + '| source'.ljust(spacing) + '| destination') + print('-'*(10 + spacing * 2)) + + # collect signal labels + signal_labels = [] + for sys in self.syslist: + signal_labels += sys.input_labels + sys.output_labels + signal_labels = set(signal_labels) + + for signal_label in signal_labels: + print(signal_label.ljust(10), end='') + sources = '| ' + dests = '| ' + + # overall interconnected system inputs and outputs + if self.find_input(signal_label) is not None: + sources += 'input' + if self.find_output(signal_label) is not None: + dests += 'output' + + # internal connections + for idx, sys in enumerate(self.syslist): + loc = sys.find_output(signal_label) + if loc is not None: + if not sources.endswith(' '): + sources += ', ' + sources += sys.name if show_names else 'system ' + str(idx) + loc = sys.find_input(signal_label) + if loc is not None: + if not dests.endswith(' '): + dests += ', ' + dests += sys.name if show_names else 'system ' + str(idx) + print(sources.ljust(spacing), end='') + print(dests.ljust(spacing), end='\n') + def _find_inputs_by_basename(self, basename): """Find all subsystem inputs matching basename From b5925c37408d110011b059c73fe23664c1c9bba2 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Mon, 24 Jul 2023 14:03:58 -0700 Subject: [PATCH 04/12] unit test --- control/tests/interconnect_test.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/control/tests/interconnect_test.py b/control/tests/interconnect_test.py index 3a333aef5..9779cbe08 100644 --- a/control/tests/interconnect_test.py +++ b/control/tests/interconnect_test.py @@ -201,6 +201,24 @@ def test_interconnect_docstring(): np.testing.assert_almost_equal(T.C @ T. A @ T.B, T_ss.C @ T_ss.A @ T_ss.B) np.testing.assert_almost_equal(T.D, T_ss.D) +def test_signal_table(capsys): + P = ct.ss(1,1,1,0, inputs='u', outputs='y') + C = ct.tf(10, [.1, 1], inputs='e', outputs='u') + L = ct.interconnect([C, P], inputs='e', outputs='y') + L.signal_table() + captured = capsys.readouterr().out + + # break the following strings separately because the printout order varies + # because signals are stored as a dict + mystrings = \ + ["signal | source | destination", + "-------------------------------------------------------------", + "e | input | system 0", + "u | system 0 | system 1", + "y | system 1 | output"] + + for str_ in mystrings: + assert str_ in captured def test_interconnect_exceptions(): # First make sure the docstring example works From 614de832ad906de71cd29433703b14014439e9d8 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Mon, 24 Jul 2023 15:16:28 -0700 Subject: [PATCH 05/12] add signal_table function, test against show_names --- control/nlsys.py | 37 ++++++++++++++++++++++++++++-- control/tests/interconnect_test.py | 35 +++++++++++++++++++--------- doc/control.rst | 1 + 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/control/nlsys.py b/control/nlsys.py index 041a29e59..80bb7d303 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -31,7 +31,7 @@ __all__ = ['NonlinearIOSystem', 'InterconnectedSystem', 'nlsys', 'input_output_response', 'find_eqpt', 'linearize', - 'interconnect'] + 'interconnect', 'signal_table'] class NonlinearIOSystem(InputOutputSystem): @@ -1020,7 +1020,7 @@ def signal_table(self, show_names=False): >>> P = ct.ss(1,1,1,0, inputs='u', outputs='y') >>> C = ct.tf(10, [.1, 1], inputs='e', outputs='u') >>> L = ct.interconnect([C, P], inputs='e', outputs='y') - >>> L.signal_table() + >>> L.signal_table() # doctest: +SKIP signal | source | destination -------------------------------------------------------------- e | input | system 0 @@ -2563,3 +2563,36 @@ def _convert_static_iosystem(sys): return NonlinearIOSystem( None, lambda t, x, u, params: sys @ u, outputs=sys.shape[0], inputs=sys.shape[1]) + +def signal_table(sys, **kwargs): + """Print table of signal names, sources, and destinations. + + Intended primarily for systems that have been connected implicitly + using signal names. + + Parameters + ---------- + sys : :class:`InterconnectedSystem` + Interconnected system object + show_names : bool (optional) + Instead of printing out the system number, print out the name of + each system. Default is False because system name is not usually + specified when performing implicit interconnection using + :func:`interconnect`. + + Examples + -------- + >>> P = ct.ss(1,1,1,0, inputs='u', outputs='y') + >>> C = ct.tf(10, [.1, 1], inputs='e', outputs='u') + >>> L = ct.interconnect([C, P], inputs='e', outputs='y') + >>> ct.signal_table(L) # doctest: +SKIP + signal | source | destination + -------------------------------------------------------------- + e | input | system 0 + u | system 0 | system 1 + y | system 1 | output + """ + assert isinstance(sys, InterconnectedSystem), "system must be"\ + "an InterconnectedSystem." + + sys.signal_table(**kwargs) diff --git a/control/tests/interconnect_test.py b/control/tests/interconnect_test.py index 9779cbe08..89827db79 100644 --- a/control/tests/interconnect_test.py +++ b/control/tests/interconnect_test.py @@ -201,24 +201,37 @@ def test_interconnect_docstring(): np.testing.assert_almost_equal(T.C @ T. A @ T.B, T_ss.C @ T_ss.A @ T_ss.B) np.testing.assert_almost_equal(T.D, T_ss.D) -def test_signal_table(capsys): - P = ct.ss(1,1,1,0, inputs='u', outputs='y') - C = ct.tf(10, [.1, 1], inputs='e', outputs='u') +@pytest.mark.parametrize("show_names", (True, False)) +def test_signal_table(capsys, show_names): + P = ct.ss(1,1,1,0, inputs='u', outputs='y', name='P') + C = ct.tf(10, [.1, 1], inputs='e', outputs='u', name='C') L = ct.interconnect([C, P], inputs='e', outputs='y') - L.signal_table() - captured = capsys.readouterr().out + L.signal_table(show_names=show_names) + captured_from_method = capsys.readouterr().out + + ct.signal_table(L, show_names=show_names) + captured_from_function = capsys.readouterr().out # break the following strings separately because the printout order varies - # because signals are stored as a dict + # because signal names are stored as a set mystrings = \ ["signal | source | destination", - "-------------------------------------------------------------", - "e | input | system 0", - "u | system 0 | system 1", - "y | system 1 | output"] + "-------------------------------------------------------------"] + if show_names: + mystrings += \ + ["e | input | C", + "u | C | P", + "y | P | output"] + else: + mystrings += \ + ["e | input | system 0", + "u | system 0 | system 1", + "y | system 1 | output"] for str_ in mystrings: - assert str_ in captured + assert str_ in captured_from_method + assert str_ in captured_from_function + def test_interconnect_exceptions(): # First make sure the docstring example works diff --git a/doc/control.rst b/doc/control.rst index a2fb8e69b..c9f8bc97f 100644 --- a/doc/control.rst +++ b/doc/control.rst @@ -36,6 +36,7 @@ System interconnections negate parallel series + signal_table Frequency domain plotting From 91aac8f5394bae7cd8716acf7dd332d7f43e7875 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Mon, 24 Jul 2023 15:57:08 -0700 Subject: [PATCH 06/12] remove **kwargs --- control/nlsys.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/control/nlsys.py b/control/nlsys.py index 80bb7d303..b1f43fe39 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -2564,7 +2564,7 @@ def _convert_static_iosystem(sys): None, lambda t, x, u, params: sys @ u, outputs=sys.shape[0], inputs=sys.shape[1]) -def signal_table(sys, **kwargs): +def signal_table(sys, show_names=False): """Print table of signal names, sources, and destinations. Intended primarily for systems that have been connected implicitly @@ -2595,4 +2595,4 @@ def signal_table(sys, **kwargs): assert isinstance(sys, InterconnectedSystem), "system must be"\ "an InterconnectedSystem." - sys.signal_table(**kwargs) + sys.signal_table(show_names=show_names) From 796acad0a56855a103a48d586647869c3e383d4a Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Tue, 25 Jul 2023 10:28:09 -0700 Subject: [PATCH 07/12] switch docstring example to use system names --- control/nlsys.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/control/nlsys.py b/control/nlsys.py index b1f43fe39..2f64c3211 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -1017,15 +1017,15 @@ def signal_table(self, show_names=False): Examples -------- - >>> P = ct.ss(1,1,1,0, inputs='u', outputs='y') - >>> C = ct.tf(10, [.1, 1], inputs='e', outputs='u') + >>> P = ct.ss(1,1,1,0, inputs='u', outputs='y', name='P') + >>> C = ct.tf(10, [.1, 1], inputs='e', outputs='u', name='C') >>> L = ct.interconnect([C, P], inputs='e', outputs='y') - >>> L.signal_table() # doctest: +SKIP + >>> L.signal_table(show_names=True) # doctest: +SKIP signal | source | destination -------------------------------------------------------------- - e | input | system 0 - u | system 0 | system 1 - y | system 1 | output + e | input | C + u | C | P + y | P | output """ spacing = 26 @@ -1053,12 +1053,12 @@ def signal_table(self, show_names=False): for idx, sys in enumerate(self.syslist): loc = sys.find_output(signal_label) if loc is not None: - if not sources.endswith(' '): + if not sources.endswith(', '): sources += ', ' sources += sys.name if show_names else 'system ' + str(idx) loc = sys.find_input(signal_label) if loc is not None: - if not dests.endswith(' '): + if not dests.endswith(', '): dests += ', ' dests += sys.name if show_names else 'system ' + str(idx) print(sources.ljust(spacing), end='') @@ -2582,15 +2582,15 @@ def signal_table(sys, show_names=False): Examples -------- - >>> P = ct.ss(1,1,1,0, inputs='u', outputs='y') - >>> C = ct.tf(10, [.1, 1], inputs='e', outputs='u') + >>> P = ct.ss(1,1,1,0, inputs='u', outputs='y', name='P') + >>> C = ct.tf(10, [.1, 1], inputs='e', outputs='u', name='C') >>> L = ct.interconnect([C, P], inputs='e', outputs='y') - >>> ct.signal_table(L) # doctest: +SKIP + >>> L.signal_table(show_names=True) # doctest: +SKIP signal | source | destination -------------------------------------------------------------- - e | input | system 0 - u | system 0 | system 1 - y | system 1 | output + e | input | C + u | C | P + y | P | output """ assert isinstance(sys, InterconnectedSystem), "system must be"\ "an InterconnectedSystem." From b787a9ef8f44850161c8983d727ad8a2f20018c6 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Tue, 25 Jul 2023 14:58:02 -0700 Subject: [PATCH 08/12] add tests for auto-sum and auto-split --- control/nlsys.py | 16 +++---- control/tests/interconnect_test.py | 77 ++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 16 deletions(-) diff --git a/control/nlsys.py b/control/nlsys.py index 2f64c3211..d3fbc7609 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -1021,14 +1021,14 @@ def signal_table(self, show_names=False): >>> C = ct.tf(10, [.1, 1], inputs='e', outputs='u', name='C') >>> L = ct.interconnect([C, P], inputs='e', outputs='y') >>> L.signal_table(show_names=True) # doctest: +SKIP - signal | source | destination - -------------------------------------------------------------- - e | input | C - u | C | P - y | P | output + signal | source | destination + -------------------------------------------------------------------- + e | input | C + u | C | P + y | P | output """ - spacing = 26 + spacing = 32 print('signal'.ljust(10) + '| source'.ljust(spacing) + '| destination') print('-'*(10 + spacing * 2)) @@ -1053,12 +1053,12 @@ def signal_table(self, show_names=False): for idx, sys in enumerate(self.syslist): loc = sys.find_output(signal_label) if loc is not None: - if not sources.endswith(', '): + if not sources.endswith(' '): sources += ', ' sources += sys.name if show_names else 'system ' + str(idx) loc = sys.find_input(signal_label) if loc is not None: - if not dests.endswith(', '): + if not dests.endswith(' '): dests += ', ' dests += sys.name if show_names else 'system ' + str(idx) print(sources.ljust(spacing), end='') diff --git a/control/tests/interconnect_test.py b/control/tests/interconnect_test.py index 89827db79..675c94402 100644 --- a/control/tests/interconnect_test.py +++ b/control/tests/interconnect_test.py @@ -215,23 +215,84 @@ def test_signal_table(capsys, show_names): # break the following strings separately because the printout order varies # because signal names are stored as a set mystrings = \ - ["signal | source | destination", - "-------------------------------------------------------------"] + ["signal | source | destination", + "------------------------------------------------------------------"] if show_names: mystrings += \ - ["e | input | C", - "u | C | P", - "y | P | output"] + ["e | input | C", + "u | C | P", + "y | P | output"] else: mystrings += \ - ["e | input | system 0", - "u | system 0 | system 1", - "y | system 1 | output"] + ["e | input | system 0", + "u | system 0 | system 1", + "y | system 1 | output"] for str_ in mystrings: assert str_ in captured_from_method assert str_ in captured_from_function + # check auto-sum + P1 = ct.ss(1,1,1,0, inputs='u', outputs='y', name='P1') + P2 = ct.tf(10, [.1, 1], inputs='e', outputs='y', name='P2') + P3 = ct.tf(10, [.1, 1], inputs='x', outputs='y', name='P3') + P = ct.interconnect([P1, P2, P3], inputs=['e', 'u', 'x'], outputs='y') + P.signal_table(show_names=show_names) + captured_from_method = capsys.readouterr().out + + ct.signal_table(P, show_names=show_names) + captured_from_function = capsys.readouterr().out + + mystrings = \ + ["signal | source | destination", + "-------------------------------------------------------------------"] + if show_names: + mystrings += \ + ["u | input | P1", + "e | input | P2", + "x | input | P3", + "y | P1, P2, P3 | output"] + else: + mystrings += \ + ["u | input | system 0", + "e | input | system 1", + "x | input | system 2", + "y | system 0, system 1, system 2 | output"] + + for str_ in mystrings: + assert str_ in captured_from_method + assert str_ in captured_from_function + + # check auto-split + P1 = ct.ss(1,1,1,0, inputs='u', outputs='x', name='P1') + P2 = ct.tf(10, [.1, 1], inputs='u', outputs='y', name='P2') + P3 = ct.tf(10, [.1, 1], inputs='u', outputs='z', name='P3') + P = ct.interconnect([P1, P2, P3], inputs=['u'], outputs=['x','y','z']) + P.signal_table(show_names=show_names) + captured_from_method = capsys.readouterr().out + + ct.signal_table(P, show_names=show_names) + 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, system 1, system 2", + "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 From c2b4480c3d1e285e8a398427352209d89d49428c Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Mon, 14 Aug 2023 11:10:31 -0700 Subject: [PATCH 09/12] rename function to connection_table, add option to change column width --- control/nlsys.py | 51 ++++++++++++++++++------------ control/tests/interconnect_test.py | 14 ++++---- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/control/nlsys.py b/control/nlsys.py index d3fbc7609..05b45b8fc 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -31,7 +31,7 @@ __all__ = ['NonlinearIOSystem', 'InterconnectedSystem', 'nlsys', 'input_output_response', 'find_eqpt', 'linearize', - 'interconnect', 'signal_table'] + 'interconnect', 'connection_table'] class NonlinearIOSystem(InputOutputSystem): @@ -1001,11 +1001,11 @@ def unused_signals(self): return ({inputs[i][:2]: inputs[i][2] for i in unused_sysinp}, {outputs[i][:2]: outputs[i][2] for i in unused_sysout}) - def signal_table(self, show_names=False): - """Print table of signal names, sources, and destinations. + def connection_table(self, show_names=False, column_width=32): + """Print table of connections inside an interconnected system model. - Intended primarily for systems that have been connected implicitly - using signal names. + Intended primarily for :class:`InterconnectedSystems` that have been + connected implicitly using signal names. Parameters ---------- @@ -1014,13 +1014,15 @@ def signal_table(self, show_names=False): each system. Default is False because system name is not usually specified when performing implicit interconnection using :func:`interconnect`. + column_width : int (optional) + Character width of printed columns Examples -------- >>> P = ct.ss(1,1,1,0, inputs='u', outputs='y', name='P') >>> C = ct.tf(10, [.1, 1], inputs='e', outputs='u', name='C') >>> L = ct.interconnect([C, P], inputs='e', outputs='y') - >>> L.signal_table(show_names=True) # doctest: +SKIP + >>> L.connection_table(show_names=True) # doctest: +SKIP signal | source | destination -------------------------------------------------------------------- e | input | C @@ -1028,9 +1030,12 @@ def signal_table(self, show_names=False): y | P | output """ - spacing = 32 - print('signal'.ljust(10) + '| source'.ljust(spacing) + '| destination') - print('-'*(10 + spacing * 2)) + print('signal'.ljust(10) + '| source'.ljust(column_width) + \ + '| destination') + print('-'*(10 + column_width * 2)) + + # TODO: version of this method that is better suited + # to explicitly-connected systems # collect signal labels signal_labels = [] @@ -1061,8 +1066,12 @@ def signal_table(self, show_names=False): if not dests.endswith(' '): dests += ', ' dests += sys.name if show_names else 'system ' + str(idx) - print(sources.ljust(spacing), end='') - print(dests.ljust(spacing), end='\n') + if len(sources) >= column_width: + sources = sources[:column_width - 3] + '.. ' + print(sources.ljust(column_width), end='') + if len(dests) > column_width: + dests = dests[:column_width - 3] + '.. ' + print(dests.ljust(column_width), end='\n') def _find_inputs_by_basename(self, basename): """Find all subsystem inputs matching basename @@ -2018,7 +2027,7 @@ def interconnect( signals are given names, then the forms 'sys.sig' or ('sys', 'sig') are also recognized. Finally, for multivariable systems the signal index can be given as a list, for example '(subsys_i, [inp_j1, ..., - inp_jn])'; as a slice, for example, 'sys.sig[i:j]'; or as a base + inp_jn])'; or as a slice, for example, 'sys.sig[i:j]'; or as a base name `sys.sig` (which matches `sys.sig[i]`). Similarly, each output-spec should describe an output signal from @@ -2235,8 +2244,7 @@ def interconnect( raise ValueError('check_unused is False, but either ' + 'ignore_inputs or ignore_outputs non-empty') - if connections is False and not inplist and not outlist \ - and not inputs and not outputs: + if connections is False and not any((inplist, outlist, inputs, outputs)): # user has disabled auto-connect, and supplied neither input # nor output mappings; assume they know what they're doing check_unused = False @@ -2564,11 +2572,11 @@ def _convert_static_iosystem(sys): None, lambda t, x, u, params: sys @ u, outputs=sys.shape[0], inputs=sys.shape[1]) -def signal_table(sys, show_names=False): - """Print table of signal names, sources, and destinations. +def connection_table(sys, show_names=False, column_width=32): + """Print table of connections inside an interconnected system model. - Intended primarily for systems that have been connected implicitly - using signal names. + Intended primarily for :class:`InterconnectedSystems` that have been + connected implicitly using signal names. Parameters ---------- @@ -2579,13 +2587,16 @@ def signal_table(sys, show_names=False): each system. Default is False because system name is not usually specified when performing implicit interconnection using :func:`interconnect`. + column_width : int (optional) + Character width of printed columns + Examples -------- >>> P = ct.ss(1,1,1,0, inputs='u', outputs='y', name='P') >>> C = ct.tf(10, [.1, 1], inputs='e', outputs='u', name='C') >>> L = ct.interconnect([C, P], inputs='e', outputs='y') - >>> L.signal_table(show_names=True) # doctest: +SKIP + >>> L.connection_table(show_names=True) # doctest: +SKIP signal | source | destination -------------------------------------------------------------- e | input | C @@ -2595,4 +2606,4 @@ def signal_table(sys, show_names=False): assert isinstance(sys, InterconnectedSystem), "system must be"\ "an InterconnectedSystem." - sys.signal_table(show_names=show_names) + sys.connection_table(show_names=show_names) diff --git a/control/tests/interconnect_test.py b/control/tests/interconnect_test.py index 675c94402..e57567333 100644 --- a/control/tests/interconnect_test.py +++ b/control/tests/interconnect_test.py @@ -202,14 +202,14 @@ def test_interconnect_docstring(): np.testing.assert_almost_equal(T.D, T_ss.D) @pytest.mark.parametrize("show_names", (True, False)) -def test_signal_table(capsys, show_names): +def test_connection_table(capsys, show_names): P = ct.ss(1,1,1,0, inputs='u', outputs='y', name='P') C = ct.tf(10, [.1, 1], inputs='e', outputs='u', name='C') L = ct.interconnect([C, P], inputs='e', outputs='y') - L.signal_table(show_names=show_names) + L.connection_table(show_names=show_names) captured_from_method = capsys.readouterr().out - ct.signal_table(L, show_names=show_names) + ct.connection_table(L, show_names=show_names) captured_from_function = capsys.readouterr().out # break the following strings separately because the printout order varies @@ -237,10 +237,10 @@ def test_signal_table(capsys, show_names): P2 = ct.tf(10, [.1, 1], inputs='e', outputs='y', name='P2') P3 = ct.tf(10, [.1, 1], inputs='x', outputs='y', name='P3') P = ct.interconnect([P1, P2, P3], inputs=['e', 'u', 'x'], outputs='y') - P.signal_table(show_names=show_names) + P.connection_table(show_names=show_names) captured_from_method = capsys.readouterr().out - ct.signal_table(P, show_names=show_names) + ct.connection_table(P, show_names=show_names) captured_from_function = capsys.readouterr().out mystrings = \ @@ -268,10 +268,10 @@ def test_signal_table(capsys, show_names): P2 = ct.tf(10, [.1, 1], inputs='u', outputs='y', name='P2') P3 = ct.tf(10, [.1, 1], inputs='u', outputs='z', name='P3') P = ct.interconnect([P1, P2, P3], inputs=['u'], outputs=['x','y','z']) - P.signal_table(show_names=show_names) + P.connection_table(show_names=show_names) captured_from_method = capsys.readouterr().out - ct.signal_table(P, show_names=show_names) + ct.connection_table(P, show_names=show_names) captured_from_function = capsys.readouterr().out mystrings = \ From 58e1b601e84c2943a5a62eb42767a50da4627498 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Mon, 14 Aug 2023 12:07:33 -0700 Subject: [PATCH 10/12] 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 --- control/nlsys.py | 36 ++++++++++++++++++------------ control/statesp.py | 3 ++- control/tests/interconnect_test.py | 30 ++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/control/nlsys.py b/control/nlsys.py index 05b45b8fc..44eba2962 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -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] @@ -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 = [] @@ -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 ' @@ -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: @@ -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 @@ -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: @@ -2528,7 +2536,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) # check for implicitly dropped signals if check_unused: @@ -2536,7 +2544,7 @@ def interconnect( # 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 @@ -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) diff --git a/control/statesp.py b/control/statesp.py index 362945ad6..38dd2388d 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -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): # # Because this is a "hybrid" object, the initialization proceeds in # stages. We first create an empty InputOutputSystem of the @@ -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: diff --git a/control/tests/interconnect_test.py b/control/tests/interconnect_test.py index e57567333..a37b18eec 100644 --- a/control/tests/interconnect_test.py +++ b/control/tests/interconnect_test.py @@ -276,7 +276,7 @@ def test_connection_table(capsys, show_names): mystrings = \ ["signal | source | destination", - "-------------------------------------------------------------------"] + "-------------------------------------------------------------------"] if show_names: mystrings += \ ["u | input | P1, P2, P3", @@ -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') From 36379972b9f538990f72ab079f70c89c71576d58 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Mon, 14 Aug 2023 12:55:38 -0700 Subject: [PATCH 11/12] added connection_table to documentation in control.rst --- doc/control.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/control.rst b/doc/control.rst index c9f8bc97f..96714bf7d 100644 --- a/doc/control.rst +++ b/doc/control.rst @@ -36,7 +36,7 @@ System interconnections negate parallel series - signal_table + connection_table Frequency domain plotting From 5c23f1a64dfe8c2927a27c96966a5616152318a6 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 16 Sep 2023 08:17:54 -0700 Subject: [PATCH 12/12] change "(optional)" to ", optional" per numpydoc --- control/nlsys.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/control/nlsys.py b/control/nlsys.py index 44eba2962..fd6e207fc 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -395,7 +395,7 @@ def dynamics(self, t, x, u, params=None): current state u : array_like input - params : dict (optional) + params : dict, optional system parameter values Returns @@ -436,7 +436,7 @@ def output(self, t, x, u, params=None): current state u : array_like input - params : dict (optional) + params : dict, optional system parameter values Returns @@ -1012,13 +1012,13 @@ def connection_table(self, show_names=False, column_width=32): Parameters ---------- - show_names : bool (optional) + show_names : bool, optional Instead of printing out the system number, print out the name of each system. Default is False because system name is not usually specified when performing implicit interconnection using :func:`interconnect`. - column_width : int (optional) - Character width of printed columns + column_width : int, optional + Character width of printed columns. Examples -------- @@ -2590,13 +2590,13 @@ def connection_table(sys, show_names=False, column_width=32): ---------- sys : :class:`InterconnectedSystem` Interconnected system object - show_names : bool (optional) + show_names : bool, optional Instead of printing out the system number, print out the name of each system. Default is False because system name is not usually specified when performing implicit interconnection using :func:`interconnect`. - column_width : int (optional) - Character width of printed columns + column_width : int, optional + Character width of printed columns. Examples 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