|
28 | 28 |
|
29 | 29 | from . import config |
30 | 30 |
|
31 | | -__all__ = ['ioresp_plot'] |
| 31 | +__all__ = ['ioresp_plot', 'combine_traces'] |
32 | 32 |
|
33 | 33 | # Default font dictionary |
34 | 34 | _timeplot_rcParams = mpl.rcParams.copy() |
@@ -71,11 +71,11 @@ def ioresp_plot( |
71 | 71 | The matplotlib Axes to draw the figure on. If not specified, the |
72 | 72 | Axes for the current figure are used or, if there is no current |
73 | 73 | figure with the correct number and shape of Axes, a new figure is |
74 | | - created. The default shape of the array should be (data.ntraces, |
75 | | - data.ninputs + data.inputs), but if combine_traces == True then |
76 | | - only one row is needed and if combine_signals == True then only one |
77 | | - or two columns are needed (depending on plot_inputs and |
78 | | - plot_outputs). |
| 74 | + created. The default shape of the array should be (noutputs + |
| 75 | + ninputs, ntraces), but if `combine_traces` is set to `True` then |
| 76 | + only one row is needed and if `combine_signals` is set to `True` |
| 77 | + then only one or two columns are needed (depending on plot_inputs |
| 78 | + and plot_outputs). |
79 | 79 | plot_inputs : bool or str, optional |
80 | 80 | Sets how and where to plot the inputs: |
81 | 81 | * False: don't plot the inputs |
@@ -121,8 +121,7 @@ def ioresp_plot( |
121 | 121 | value is used if legend_map is None. |
122 | 122 | add_initial_zero : bool |
123 | 123 | Add an initial point of zero at the first time point for all |
124 | | - inputs. This is useful when the initial value of the input is |
125 | | - nonzero (for example in a step input). Default is True. |
| 124 | + inputs with type 'step'. Default is True. |
126 | 125 | trace_cycler: :class:`~matplotlib.Cycler` |
127 | 126 | Line style cycle to use for traces. Default = ['-', '--', ':', '-.']. |
128 | 127 |
|
@@ -367,7 +366,8 @@ def _make_line_label(signal_index, signal_labels, trace_index): |
367 | 366 | for i in range(ninputs): |
368 | 367 | label = _make_line_label(i, data.input_labels, trace) |
369 | 368 |
|
370 | | - if add_initial_zero: # start trace from the origin |
| 369 | + if add_initial_zero and data.trace_types \ |
| 370 | + and data.trace_types[i] == 'step': |
371 | 371 | x = np.hstack([np.array([data.time[0]]), data.time]) |
372 | 372 | y = np.hstack([np.array([0]), inputs[i][trace]]) |
373 | 373 | else: |
@@ -603,3 +603,98 @@ def _make_line_label(signal_index, signal_labels, trace_index): |
603 | 603 | fig.suptitle(new_title) |
604 | 604 |
|
605 | 605 | return out |
| 606 | + |
| 607 | + |
| 608 | +def combine_traces(trace_list, trace_labels=None, title=None): |
| 609 | + """Combine multiple individual time responses into a multi-trace response. |
| 610 | +
|
| 611 | + This function combines multiple instances of :class:`TimeResponseData` |
| 612 | + into a multi-trace :class:`TimeResponseData` object. |
| 613 | +
|
| 614 | + Parameters |
| 615 | + ---------- |
| 616 | + trace_list : list of :class:`TimeResponseData` objects |
| 617 | + Traces to be combined. |
| 618 | + trace_labels : list of str, optional |
| 619 | + List of labels for each trace. If not specified, trace names are |
| 620 | + taken from the input data or set to None. |
| 621 | +
|
| 622 | + Returns |
| 623 | + ------- |
| 624 | + data : :class:`TimeResponseData` |
| 625 | + Multi-trace input/output data. |
| 626 | +
|
| 627 | + """ |
| 628 | + from .timeresp import TimeResponseData |
| 629 | + |
| 630 | + # Save the first trace as the base case |
| 631 | + base = trace_list[0] |
| 632 | + |
| 633 | + # Process keywords |
| 634 | + title = base.title if title is None else title |
| 635 | + |
| 636 | + # Figure out the size of the data (and check for consistency) |
| 637 | + ntraces = max(1, base.ntraces) |
| 638 | + |
| 639 | + # Initial pass through trace list to count things up and do error checks |
| 640 | + for trace in trace_list[1:]: |
| 641 | + # Make sure the time vector is the same |
| 642 | + if not np.allclose(base.t, trace.t): |
| 643 | + raise ValueError("all traces must have the same time vector") |
| 644 | + |
| 645 | + # Make sure the dimensions are all the same |
| 646 | + if base.ninputs != trace.ninputs or base.noutputs != trace.noutputs \ |
| 647 | + or base.nstates != trace.nstates: |
| 648 | + raise ValuError("all traces must have the same number of " |
| 649 | + "inputs, outputs, and states") |
| 650 | + |
| 651 | + ntraces += max(1, trace.ntraces) |
| 652 | + |
| 653 | + # Create data structures for the new time response data object |
| 654 | + inputs = np.empty((base.ninputs, ntraces, base.t.size)) |
| 655 | + outputs = np.empty((base.noutputs, ntraces, base.t.size)) |
| 656 | + states = np.empty((base.nstates, ntraces, base.t.size)) |
| 657 | + |
| 658 | + # See whether we should create labels or not |
| 659 | + if trace_labels is None: |
| 660 | + generate_trace_labels = True |
| 661 | + trace_labels = [] |
| 662 | + elif len(trace_labels) != ntraces: |
| 663 | + raise ValueError( |
| 664 | + "number of trace labels does not match number of traces") |
| 665 | + else: |
| 666 | + generate_trace_labels = False |
| 667 | + |
| 668 | + offset = 0 |
| 669 | + trace_types = [] |
| 670 | + for trace in trace_list: |
| 671 | + if trace.ntraces == 0: |
| 672 | + # Single trace |
| 673 | + inputs[:, offset, :] = trace.u |
| 674 | + outputs[:, offset, :] = trace.y |
| 675 | + states[:, offset, :] = trace.x |
| 676 | + if generate_trace_labels: |
| 677 | + trace_labels.append(trace.title) |
| 678 | + if trace.trace_types is not None: |
| 679 | + trace_types.append(trace.types[0]) |
| 680 | + offset += 1 |
| 681 | + else: |
| 682 | + for i in range(trace.ntraces): |
| 683 | + inputs[:, offset, :] = trace.u[:, i, :] |
| 684 | + outputs[:, offset, :] = trace.y[:, i, :] |
| 685 | + states[:, offset, :] = trace.x[:, i, :] |
| 686 | + if generate_trace_labels and trace.trace_labels is not None: |
| 687 | + trace_labels.append(trace.trace_labels) |
| 688 | + else: |
| 689 | + trace_labels.append(trace.title, f", trace {i}") |
| 690 | + if trace.trace_types is not None: |
| 691 | + trace_types.append(trace.trace_types) |
| 692 | + offset += trace.ntraces |
| 693 | + |
| 694 | + return TimeResponseData( |
| 695 | + base.t, outputs, states, inputs, issiso=base.issiso, |
| 696 | + output_labels=base.output_labels, input_labels=base.input_labels, |
| 697 | + state_labels=base.state_labels, title=title, transpose=base.transpose, |
| 698 | + return_x=base.return_x, squeeze=base.squeeze, sysname=base.sysname, |
| 699 | + trace_labels=trace_labels, trace_types=trace_types, |
| 700 | + plot_inputs=base.plot_inputs) |
0 commit comments