From 448fb620ac9b1c92bc09e4a39865ca13939c7189 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Mon, 30 Dec 2024 14:15:53 -0800 Subject: [PATCH 1/3] add unit test showing failure in #1087 --- control/tests/timeresp_test.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/control/tests/timeresp_test.py b/control/tests/timeresp_test.py index e5e24b990..f3a0ecc15 100644 --- a/control/tests/timeresp_test.py +++ b/control/tests/timeresp_test.py @@ -1247,13 +1247,14 @@ def test_to_pandas(): np.testing.assert_equal(df['x[1]'], resp.states[1]) # Change the time points - sys = ct.rss(2, 1, 1) + sys = ct.rss(2, 1, 2) T = np.linspace(0, timepts[-1]/2, timepts.size * 2) - resp = ct.input_output_response(sys, timepts, np.sin(timepts), t_eval=T) + resp = ct.input_output_response( + sys, timepts, [np.sin(timepts), 0], t_eval=T) df = resp.to_pandas() np.testing.assert_equal(df['time'], resp.time) - np.testing.assert_equal(df['u[0]'], resp.inputs) - np.testing.assert_equal(df['y[0]'], resp.outputs) + np.testing.assert_equal(df['u[0]'], resp.inputs[0]) + np.testing.assert_equal(df['y[0]'], resp.outputs[0]) np.testing.assert_equal(df['x[0]'], resp.states[0]) np.testing.assert_equal(df['x[1]'], resp.states[1]) @@ -1265,6 +1266,17 @@ def test_to_pandas(): np.testing.assert_equal(df['u[0]'], resp.inputs) np.testing.assert_equal(df['y[0]'], resp.inputs * 5) + # https://github.com/python-control/python-control/issues/1087 + model = ct.rss( + states=['x0', 'x1'], outputs=['y0', 'y1'], + inputs=['u0', 'u1'], name='My Model') + T = np.linspace(0, 10, 100, endpoint=False) + X0 = np.zeros(model.nstates) + res = ct.step_response(model, T=T, X0=X0, input=0) + df = res.to_pandas() + np.testing.assert_equal(df['time'], res.time) + np.testing.assert_equal(df['y1'], res.outputs['y1']) + @pytest.mark.skipif(pandas_check(), reason="pandas installed") def test_no_pandas(): From 96d32289c203333de8f007f5525ba5ec8797448f Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Mon, 30 Dec 2024 20:40:30 -0800 Subject: [PATCH 2/3] fix TimeResponseData.to_pandas() for multi-trace responses --- control/tests/timeresp_test.py | 18 +++++++++++++++--- control/timeresp.py | 21 +++++++++++++++------ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/control/tests/timeresp_test.py b/control/tests/timeresp_test.py index f3a0ecc15..5949a4441 100644 --- a/control/tests/timeresp_test.py +++ b/control/tests/timeresp_test.py @@ -1266,16 +1266,28 @@ def test_to_pandas(): np.testing.assert_equal(df['u[0]'], resp.inputs) np.testing.assert_equal(df['y[0]'], resp.inputs * 5) + # Multi-trace data # https://github.com/python-control/python-control/issues/1087 model = ct.rss( states=['x0', 'x1'], outputs=['y0', 'y1'], inputs=['u0', 'u1'], name='My Model') T = np.linspace(0, 10, 100, endpoint=False) X0 = np.zeros(model.nstates) - res = ct.step_response(model, T=T, X0=X0, input=0) + + res = ct.step_response(model, T=T, X0=X0, input=0) # extract single trace + df = res.to_pandas() + np.testing.assert_equal( + df[df['trace'] == 'From u0']['time'], res.time) + np.testing.assert_equal( + df[df['trace'] == 'From u0']['y1'], res.outputs['y1', 0]) + + res = ct.step_response(model, T=T, X0=X0) # all traces df = res.to_pandas() - np.testing.assert_equal(df['time'], res.time) - np.testing.assert_equal(df['y1'], res.outputs['y1']) + for i, label in enumerate(res.trace_labels): + np.testing.assert_equal( + df[df['trace'] == label]['time'], res.time) + np.testing.assert_equal( + df[df['trace'] == label]['u0'], res.inputs['u0', i]) @pytest.mark.skipif(pandas_check(), reason="pandas installed") diff --git a/control/timeresp.py b/control/timeresp.py index 072db60de..67641d239 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -718,8 +718,10 @@ def __len__(self): def to_pandas(self): """Convert response data to pandas data frame. - Creates a pandas data frame using the input, output, and state - labels for the time response. + Creates a pandas data frame using the input, output, and state labels + for the time response. The column labels are given by the input and + output (and state, when present) labels, with time labeled by 'time' + and traces (for multi-trace responses) labeled by 'trace'. """ if not pandas_check(): @@ -727,16 +729,23 @@ def to_pandas(self): import pandas # Create a dict for setting up the data frame - data = {'time': self.time} + data = {'time': np.tile( + self.time, self.ntraces if self.ntraces > 0 else 1)} + if self.ntraces > 0: + data['trace'] = np.hstack([ + np.full(self.time.size, label) for label in self.trace_labels]) if self.ninputs > 0: data.update( - {name: self.u[i] for i, name in enumerate(self.input_labels)}) + {name: self.u[i].reshape(-1) + for i, name in enumerate(self.input_labels)}) if self.noutputs > 0: data.update( - {name: self.y[i] for i, name in enumerate(self.output_labels)}) + {name: self.y[i].reshape(-1) + for i, name in enumerate(self.output_labels)}) if self.nstates > 0: data.update( - {name: self.x[i] for i, name in enumerate(self.state_labels)}) + {name: self.x[i].reshape(-1) + for i, name in enumerate(self.state_labels)}) return pandas.DataFrame(data) From 7707ea8a665bcd4b3936b9d66635ecd8288a99ce Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Thu, 2 Jan 2025 08:23:01 -0800 Subject: [PATCH 3/3] update unit test per @slivinsgston review --- control/tests/timeresp_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/control/tests/timeresp_test.py b/control/tests/timeresp_test.py index 5949a4441..ae28975ee 100644 --- a/control/tests/timeresp_test.py +++ b/control/tests/timeresp_test.py @@ -1278,6 +1278,8 @@ def test_to_pandas(): df = res.to_pandas() np.testing.assert_equal( df[df['trace'] == 'From u0']['time'], res.time) + np.testing.assert_equal( + df[df['trace'] == 'From u0']['u0'], res.inputs['u0', 0]) np.testing.assert_equal( df[df['trace'] == 'From u0']['y1'], res.outputs['y1', 0]) @@ -1287,7 +1289,9 @@ def test_to_pandas(): np.testing.assert_equal( df[df['trace'] == label]['time'], res.time) np.testing.assert_equal( - df[df['trace'] == label]['u0'], res.inputs['u0', i]) + df[df['trace'] == label]['u1'], res.inputs['u1', i]) + np.testing.assert_equal( + df[df['trace'] == label]['y0'], res.outputs['y0', i]) @pytest.mark.skipif(pandas_check(), reason="pandas installed") 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