diff --git a/control/tests/timeresp_test.py b/control/tests/timeresp_test.py index e5e24b990..ae28975ee 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,33 @@ 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) # 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']['u0'], res.inputs['u0', 0]) + 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() + 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]['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") def test_no_pandas(): 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) 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