From 34a7a4924fd2bf91b17d724c3d7d0b4a4b3a6178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ant=C3=B4nio=20Cardoso?= Date: Thu, 25 May 2023 20:38:24 -0300 Subject: [PATCH 1/5] Add missing labels when returning TimeResponseData --- control/timeresp.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/control/timeresp.py b/control/timeresp.py index 638a07329..3ce4318b2 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -1111,6 +1111,8 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False, return TimeResponseData( tout, yout, xout, U, issiso=sys.issiso(), + output_labels=sys.output_labels, input_labels=sys.input_labels, + state_labels=sys.state_labels, transpose=transpose, return_x=return_x, squeeze=squeeze) @@ -1374,8 +1376,16 @@ def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None, # Figure out if the system is SISO or not issiso = sys.issiso() or (input is not None and output is not None) + # Select only the given input and output, if any + input_labels = sys.input_labels if input is None \ + else sys.input_labels[input] + output_labels = sys.output_labels if output is None \ + else sys.output_labels[output] + return TimeResponseData( response.time, yout, xout, uout, issiso=issiso, + output_labels=output_labels, input_labels=input_labels, + state_labels=sys.state_labels, transpose=transpose, return_x=return_x, squeeze=squeeze) @@ -1704,9 +1714,15 @@ def initial_response(sys, T=None, X0=0., input=0, output=None, T_num=None, # Figure out if the system is SISO or not issiso = sys.issiso() or (input is not None and output is not None) + # Select only the given output, if any + output_labels = sys.output_labels if output is None \ + else sys.output_labels[0] + # Store the response without an input return TimeResponseData( response.t, response.y, response.x, None, issiso=issiso, + output_labels=output_labels, input_labels=None, + state_labels=sys.state_labels, transpose=transpose, return_x=return_x, squeeze=squeeze) @@ -1798,7 +1814,7 @@ def impulse_response(sys, T=None, X0=0., input=None, output=None, T_num=None, ----- This function uses the `forced_response` function to compute the time response. For continuous time systems, the initial condition is altered to - account for the initial impulse. For discrete-time aystems, the impulse is + account for the initial impulse. For discrete-time aystems, the impulse is sized so that it has unit area. Examples @@ -1869,8 +1885,16 @@ def impulse_response(sys, T=None, X0=0., input=None, output=None, T_num=None, # Figure out if the system is SISO or not issiso = sys.issiso() or (input is not None and output is not None) + # Select only the given input and output, if any + input_labels = sys.input_labels if input is None \ + else sys.input_labels[input] + output_labels = sys.output_labels if output is None \ + else sys.output_labels[output] + return TimeResponseData( response.time, yout, xout, uout, issiso=issiso, + output_labels=output_labels, input_labels=input_labels, + state_labels=sys.state_labels, transpose=transpose, return_x=return_x, squeeze=squeeze) From a78e85c3345154384f09ef7fd3cbd462caa92b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ant=C3=B4nio=20Cardoso?= Date: Sat, 20 May 2023 16:26:40 -0300 Subject: [PATCH 2/5] Test if labels are transferred to the response and we can still relabel them --- control/tests/trdata_test.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/control/tests/trdata_test.py b/control/tests/trdata_test.py index 734d35599..028e53580 100644 --- a/control/tests/trdata_test.py +++ b/control/tests/trdata_test.py @@ -196,15 +196,20 @@ def test_response_copy(): with pytest.raises(ValueError, match="not enough"): t, y, x = response_mimo - # Labels - assert response_mimo.output_labels is None - assert response_mimo.state_labels is None - assert response_mimo.input_labels is None + # Make sure labels are transferred to the response + assert response_siso.output_labels == sys_siso.output_labels + assert response_siso.state_labels == sys_siso.state_labels + assert response_siso.input_labels == sys_siso.input_labels + assert response_mimo.output_labels == sys_mimo.output_labels + assert response_mimo.state_labels == sys_mimo.state_labels + assert response_mimo.input_labels == sys_mimo.input_labels + + # Check relabelling response = response_mimo( output_labels=['y1', 'y2'], input_labels='u', - state_labels=["x[%d]" % i for i in range(4)]) + state_labels=["x%d" % i for i in range(4)]) assert response.output_labels == ['y1', 'y2'] - assert response.state_labels == ['x[0]', 'x[1]', 'x[2]', 'x[3]'] + assert response.state_labels == ['x0', 'x1', 'x2', 'x3'] assert response.input_labels == ['u'] # Unknown keyword @@ -231,6 +236,17 @@ def test_trdata_labels(): np.testing.assert_equal( response.input_labels, ["u[%d]" % i for i in range(sys.ninputs)]) + # Make sure the selected input and output are both correctly transferred to the response + for nu in range(sys.ninputs): + for ny in range(sys.noutputs): + step_response = ct.step_response(sys, T, input=nu, output=ny) + assert step_response.input_labels == [sys.input_labels[nu]] + assert step_response.output_labels == [sys.output_labels[ny]] + + init_response = ct.initial_response(sys, T, input=nu, output=ny) + assert init_response.input_labels == None + assert init_response.output_labels == [sys.output_labels[ny]] + def test_trdata_multitrace(): # From 56c72c7d34093df9deb143143e0e3f33882e9922 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 27 May 2023 09:59:32 -0700 Subject: [PATCH 3/5] use *_labels in input_output_response for consistency --- control/iosys.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/control/iosys.py b/control/iosys.py index 78444f7c1..dca00d3e5 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -1862,7 +1862,7 @@ def ufun(t): return TimeResponseData( t_eval, y, None, u, issiso=sys.issiso(), - output_labels=sys.output_index, input_labels=sys.input_index, + output_labels=sys.output_labels, input_labels=sys.input_labels, transpose=transpose, return_x=return_x, squeeze=squeeze) # Create a lambda function for the right hand side @@ -1941,8 +1941,8 @@ def ivp_rhs(t, x): return TimeResponseData( soln.t, y, soln.y, u, issiso=sys.issiso(), - output_labels=sys.output_index, input_labels=sys.input_index, - state_labels=sys.state_index, + output_labels=sys.output_labels, input_labels=sys.input_labels, + state_labels=sys.state_labels, transpose=transpose, return_x=return_x, squeeze=squeeze) @@ -2881,7 +2881,7 @@ def interconnect( # Look for the signal name as a system input for sys in syslist: - if signal_name in sys.input_index.keys(): + if signal_name in sys.input_labels: connection.append(sign + sys.name + "." + signal_name) # Make sure we found the name From d744a5423901f2fefb3544adac41847388d9778d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ant=C3=B4nio=20Cardoso?= Date: Sat, 27 May 2023 14:32:04 -0300 Subject: [PATCH 4/5] Propagate labels and names when converting between mimo/siso/simo --- control/statesp.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/control/statesp.py b/control/statesp.py index 41f92ae21..8661d8741 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -1777,7 +1777,9 @@ def _mimo2siso(sys, input, output, warn_conversion=False): new_B = sys.B[:, input] new_C = sys.C[output, :] new_D = sys.D[output, input] - sys = StateSpace(sys.A, new_B, new_C, new_D, sys.dt) + sys = StateSpace(sys.A, new_B, new_C, new_D, sys.dt, + name=sys.name, + inputs=sys.input_labels[input], outputs=sys.output_labels[output]) return sys @@ -1826,7 +1828,9 @@ def _mimo2simo(sys, input, warn_conversion=False): # Y = C*X + D*U new_B = sys.B[:, input:input+1] new_D = sys.D[:, input:input+1] - sys = StateSpace(sys.A, new_B, sys.C, new_D, sys.dt) + sys = StateSpace(sys.A, new_B, sys.C, new_D, sys.dt, + name=sys.name, + inputs=sys.input_labels[input], outputs=sys.output_labels) return sys From bd83e8c9060f69d9e87afd861dda4b4bf3223bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ant=C3=B4nio=20Cardoso?= Date: Sat, 27 May 2023 15:36:04 -0300 Subject: [PATCH 5/5] Fix _process_labels to accept a single str label --- control/timeresp.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/control/timeresp.py b/control/timeresp.py index 3ce4318b2..bd8595cfe 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -694,7 +694,10 @@ def _process_labels(labels, signal, length): raise ValueError("Name dictionary for %s is incomplete" % signal) # Convert labels to a list - labels = list(labels) + if isinstance(labels, str): + labels = [labels] + else: + labels = list(labels) # Make sure the signal list is the right length and type if len(labels) != length: