Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
rename combine_traces to combine_time_responses, updated trace labels
  • Loading branch information
murrayrm committed Jul 10, 2023
commit bab5071da0292f3ba8860b3a0fdc05c8cd9661ba
21 changes: 11 additions & 10 deletions control/tests/timeplot_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ def test_legend_map():
title='MIMO step response with custom legend placement')


def test_combine_traces():
def test_combine_time_responses():
sys_mimo = ct.rss(4, 2, 2)
timepts = np.linspace(0, 10, 100)

Expand All @@ -261,21 +261,21 @@ def test_combine_traces():
U = np.vstack([np.cos(2*timepts), np.sin(timepts)])
resp2 = ct.input_output_response(sys_mimo, timepts, U)

combresp1 = ct.combine_traces([resp1, resp2])
combresp1 = ct.combine_time_responses([resp1, resp2])
assert combresp1.ntraces == 2
np.testing.assert_equal(combresp1.y[:, 0, :], resp1.y)
np.testing.assert_equal(combresp1.y[:, 1, :], resp2.y)

# Combine two responses with ntrace != 0
resp3 = ct.step_response(sys_mimo, timepts)
resp4 = ct.step_response(sys_mimo, timepts)
combresp2 = ct.combine_traces([resp3, resp4])
combresp2 = ct.combine_time_responses([resp3, resp4])
assert combresp2.ntraces == resp3.ntraces + resp4.ntraces
np.testing.assert_equal(combresp2.y[:, 0:2, :], resp3.y)
np.testing.assert_equal(combresp2.y[:, 2:4, :], resp4.y)

# Mixture
combresp3 = ct.combine_traces([resp1, resp2, resp3])
combresp3 = ct.combine_time_responses([resp1, resp2, resp3])
assert combresp3.ntraces == resp3.ntraces + resp4.ntraces
np.testing.assert_equal(combresp3.y[:, 0, :], resp1.y)
np.testing.assert_equal(combresp3.y[:, 1, :], resp2.y)
Expand All @@ -286,30 +286,31 @@ def test_combine_traces():

# Rename the traces
labels = ["T1", "T2", "T3", "T4"]
combresp4 = ct.combine_traces([resp1, resp2, resp3], trace_labels=labels)
combresp4 = ct.combine_time_responses(
[resp1, resp2, resp3], trace_labels=labels)
assert combresp4.trace_labels == labels

# Automatically generated trace label names and types
resp5 = ct.step_response(sys_mimo, timepts)
resp5.title = "test"
resp5.trace_labels = None
resp5.trace_types = None
combresp5 = ct.combine_traces([resp1, resp5])
combresp5 = ct.combine_time_responses([resp1, resp5])
assert combresp5.trace_labels == [resp1.title] + \
["test, trace 0", "test, trace 1"]
assert combresp4.trace_types == [None, None, 'step', 'step']

with pytest.raises(ValueError, match="must have the same number"):
resp = ct.step_response(ct.rss(4, 2, 3), timepts)
combresp = ct.combine_traces([resp1, resp])
combresp = ct.combine_time_responses([resp1, resp])

with pytest.raises(ValueError, match="trace labels does not match"):
combresp = ct.combine_traces(
combresp = ct.combine_time_responses(
[resp1, resp2], trace_labels=["T1", "T2", "T3"])

with pytest.raises(ValueError, match="must have the same time"):
resp = ct.step_response(ct.rss(4, 2, 3), timepts/2)
combresp6 = ct.combine_traces([resp1, resp])
combresp6 = ct.combine_time_responses([resp1, resp])


@slycotonly
Expand Down Expand Up @@ -494,7 +495,7 @@ def test_errors():
U = np.vstack([np.cos(2*timepts), np.sin(timepts)])
resp2 = ct.input_output_response(sys_mimo, timepts, U)

ct.combine_traces(
ct.combine_time_responses(
[resp1, resp2], trace_labels=["Scenario #1", "Scenario #2"]).plot(
transpose=True,
title="I/O responses for 2x2 MIMO system, multiple traces "
Expand Down
7 changes: 3 additions & 4 deletions control/timeplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from . import config

__all__ = ['time_response_plot', 'combine_traces', 'get_plot_axes']
__all__ = ['time_response_plot', 'combine_time_responses', 'get_plot_axes']

# Default font dictionary
_timeplot_rcParams = mpl.rcParams.copy()
Expand Down Expand Up @@ -412,7 +412,7 @@ def _make_line_label(signal_index, signal_labels, trace_index):
for i in range(ninputs):
label = _make_line_label(i, data.input_labels, trace)

if add_initial_zero and data.trace_types \
if add_initial_zero and data.ntraces > i \
and data.trace_types[i] == 'step':
x = np.hstack([np.array([data.time[0]]), data.time])
y = np.hstack([np.array([0]), inputs[i][trace]])
Expand Down Expand Up @@ -606,7 +606,6 @@ def _make_line_label(signal_index, signal_labels, trace_index):
labels = [line.get_label() for line in ax.get_lines()]

# Look for a common prefix (up to a space)
# TODO: fix error in 1x2, overlay, transpose (Fig 24)
common_prefix = commonprefix(labels)
last_space = common_prefix.rfind(', ')
if last_space < 0 or plot_inputs == 'overlay':
Expand Down Expand Up @@ -671,7 +670,7 @@ def _make_line_label(signal_index, signal_labels, trace_index):
return out


def combine_traces(response_list, trace_labels=None, title=None):
def combine_time_responses(response_list, trace_labels=None, title=None):
"""Combine multiple individual time responses into a multi-trace response.

This function combines multiple instances of :class:`TimeResponseData`
Expand Down
14 changes: 7 additions & 7 deletions control/timeresp.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ def __init__(
# Check and store trace labels, if present
self.trace_labels = _process_labels(
trace_labels, "trace", self.ntraces)
self.trace_types = trace_types # TODO: rename to kind?
self.trace_types = trace_types

# Figure out if the system is SISO
if issiso is None:
Expand Down Expand Up @@ -1169,7 +1169,7 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False,
tout, yout, xout, U, issiso=sys.issiso(),
output_labels=sys.output_labels, input_labels=sys.input_labels,
state_labels=sys.state_labels, sysname=sys.name, plot_inputs=True,
title="Forced response for " + sys.name,
title="Forced response for " + sys.name, trace_types=['forced'],
transpose=transpose, return_x=return_x, squeeze=squeeze)


Expand Down Expand Up @@ -1386,8 +1386,7 @@ def step_response(sys, T=None, X0=0, input=None, output=None, T_num=None,
uout = np.empty((ninputs, ninputs, T.size))

# Simulate the response for each input
trace_labels = []
trace_types = []
trace_labels, trace_types = [], []
for i in range(sys.ninputs):
# If input keyword was specified, only simulate for that input
if isinstance(input, int) and i != input:
Expand Down Expand Up @@ -1761,7 +1760,7 @@ def initial_response(sys, T=None, X0=0, output=None, T_num=None,
response.t, yout, response.x, None, issiso=issiso,
output_labels=output_labels, input_labels=None,
state_labels=sys.state_labels, sysname=sys.name,
title="Initial response for " + sys.name,
title="Initial response for " + sys.name, trace_types=['initial'],
transpose=transpose, return_x=return_x, squeeze=squeeze)


Expand Down Expand Up @@ -1888,14 +1887,15 @@ def impulse_response(sys, T=None, input=None, output=None, T_num=None,
uout = np.full((ninputs, ninputs, np.asarray(T).size), None)

# Simulate the response for each input
trace_labels = []
trace_labels, trace_types = [], []
for i in range(sys.ninputs):
# If input keyword was specified, only handle that case
if isinstance(input, int) and i != input:
continue

# Save a label for this plot
trace_labels.append(f"From {sys.input_labels[i]}")
trace_types.append('impulse')

#
# Compute new X0 that contains the impulse
Expand Down Expand Up @@ -1935,7 +1935,7 @@ def impulse_response(sys, T=None, input=None, output=None, T_num=None,
response.time, yout, xout, uout, issiso=issiso,
output_labels=output_labels, input_labels=input_labels,
state_labels=sys.state_labels, trace_labels=trace_labels,
title="Impulse response for " + sys.name,
trace_types=trace_types, title="Impulse response for " + sys.name,
sysname=sys.name, plot_inputs=False, transpose=transpose,
return_x=return_x, squeeze=squeeze)

Expand Down
10 changes: 5 additions & 5 deletions doc/plotting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ following figure::
U2 = np.vstack([np.cos(2*timepts), np.sin(timepts)])
resp2 = ct.input_output_response(sys_mimo, timepts, U2)

ct.combine_traces(
ct.combine_time_responses(
[resp1, resp2], trace_labels=["Scenario #1", "Scenario #2"]).plot(
transpose=True,
title="I/O responses for 2x2 MIMO system, multiple traces "
Expand All @@ -114,9 +114,9 @@ following figure::
.. image:: timeplot-mimo_ioresp-mt_tr.png

This figure also illustrates the ability to create "multi-trace" plots
using the :func:`~control.combine_traces` function. The line properties
that are used when combining signals and traces are set by the
`input_props`, `output_props` and `trace_props` parameters for
using the :func:`~control.combine_time_responses` function. The line
properties that are used when combining signals and traces are set by
the `input_props`, `output_props` and `trace_props` parameters for
:func:`~control.time_response_plot`.

Additional customization is possible using the `input_props`,
Expand All @@ -138,5 +138,5 @@ Plotting functions
:toctree: generated/

~control.time_response_plot
~control.combine_traces
~control.combine_time_responses
~control.get_plot_axes