Skip to content

Commit 56cecc0

Browse files
authored
Merge pull request #649 from murrayrm/timeresponse_class
Time response data class
2 parents 5155dd5 + 136d6f4 commit 56cecc0

File tree

8 files changed

+1230
-189
lines changed

8 files changed

+1230
-189
lines changed

control/iosys.py

+41-16
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
from warnings import warn
3333

3434
from .statesp import StateSpace, tf2ss, _convert_to_statespace
35-
from .timeresp import _check_convert_array, _process_time_response
35+
from .timeresp import _check_convert_array, _process_time_response, \
36+
TimeResponseData
3637
from .lti import isctime, isdtime, common_timebase
3738
from . import config
3839

@@ -878,7 +879,7 @@ def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None,
878879
def __call__(sys, u, params=None, squeeze=None):
879880
"""Evaluate a (static) nonlinearity at a given input value
880881
881-
If a nonlinear I/O system has not internal state, then evaluating the
882+
If a nonlinear I/O system has no internal state, then evaluating the
882883
system at an input `u` gives the output `y = F(u)`, determined by the
883884
output function.
884885
@@ -907,7 +908,8 @@ def __call__(sys, u, params=None, squeeze=None):
907908

908909
# Evaluate the function on the argument
909910
out = sys._out(0, np.array((0,)), np.asarray(u))
910-
_, out = _process_time_response(sys, None, out, None, squeeze=squeeze)
911+
_, out = _process_time_response(
912+
None, out, issiso=sys.issiso(), squeeze=squeeze)
911913
return out
912914

913915
def _update_params(self, params, warning=False):
@@ -1641,14 +1643,21 @@ def input_output_response(
16411643
----------
16421644
sys : InputOutputSystem
16431645
Input/output system to simulate.
1646+
16441647
T : array-like
16451648
Time steps at which the input is defined; values must be evenly spaced.
1649+
16461650
U : array-like or number, optional
16471651
Input array giving input at each time `T` (default = 0).
1652+
16481653
X0 : array-like or number, optional
16491654
Initial condition (default = 0).
1655+
16501656
return_x : bool, optional
1657+
If True, return the state vector when assigning to a tuple (default =
1658+
False). See :func:`forced_response` for more details.
16511659
If True, return the values of the state at each time (default = False).
1660+
16521661
squeeze : bool, optional
16531662
If True and if the system has a single output, return the system
16541663
output as a 1D array rather than a 2D array. If False, return the
@@ -1657,15 +1666,27 @@ def input_output_response(
16571666
16581667
Returns
16591668
-------
1660-
T : array
1661-
Time values of the output.
1662-
yout : array
1663-
Response of the system. If the system is SISO and squeeze is not
1664-
True, the array is 1D (indexed by time). If the system is not SISO or
1665-
squeeze is False, the array is 2D (indexed by the output number and
1666-
time).
1667-
xout : array
1668-
Time evolution of the state vector (if return_x=True).
1669+
results : TimeResponseData
1670+
Time response represented as a :class:`TimeResponseData` object
1671+
containing the following properties:
1672+
1673+
* time (array): Time values of the output.
1674+
1675+
* outputs (array): Response of the system. If the system is SISO and
1676+
`squeeze` is not True, the array is 1D (indexed by time). If the
1677+
system is not SISO or `squeeze` is False, the array is 2D (indexed
1678+
by output and time).
1679+
1680+
* states (array): Time evolution of the state vector, represented as
1681+
a 2D array indexed by state and time.
1682+
1683+
* inputs (array): Input(s) to the system, indexed by input and time.
1684+
1685+
The return value of the system can also be accessed by assigning the
1686+
function to a tuple of length 2 (time, output) or of length 3 (time,
1687+
output, state) if ``return_x`` is ``True``. If the input/output
1688+
system signals are named, these names will be used as labels for the
1689+
time response.
16691690
16701691
Other parameters
16711692
----------------
@@ -1727,8 +1748,9 @@ def input_output_response(
17271748
for i in range(len(T)):
17281749
u = U[i] if len(U.shape) == 1 else U[:, i]
17291750
y[:, i] = sys._out(T[i], [], u)
1730-
return _process_time_response(
1731-
sys, T, y, np.array((0, 0, np.asarray(T).size)),
1751+
return TimeResponseData(
1752+
T, y, None, U, issiso=sys.issiso(),
1753+
output_labels=sys.output_index, input_labels=sys.input_index,
17321754
transpose=transpose, return_x=return_x, squeeze=squeeze)
17331755

17341756
# create X0 if not given, test if X0 has correct shape
@@ -1823,8 +1845,11 @@ def ivp_rhs(t, x):
18231845
else: # Neither ctime or dtime??
18241846
raise TypeError("Can't determine system type")
18251847

1826-
return _process_time_response(sys, soln.t, y, soln.y, transpose=transpose,
1827-
return_x=return_x, squeeze=squeeze)
1848+
return TimeResponseData(
1849+
soln.t, y, soln.y, U, issiso=sys.issiso(),
1850+
output_labels=sys.output_index, input_labels=sys.input_index,
1851+
state_labels=sys.state_index,
1852+
transpose=transpose, return_x=return_x, squeeze=squeeze)
18281853

18291854

18301855
def find_eqpt(sys, x0, u0=[], y0=None, t=0, params={},

control/optimal.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import logging
1717
import time
1818

19-
from .timeresp import _process_time_response
19+
from .timeresp import TimeResponseData
2020

2121
__all__ = ['find_optimal_input']
2222

@@ -826,13 +826,14 @@ def __init__(
826826
else:
827827
states = None
828828

829-
retval = _process_time_response(
830-
ocp.system, ocp.timepts, inputs, states,
829+
# Process data as a time response (with "outputs" = inputs)
830+
response = TimeResponseData(
831+
ocp.timepts, inputs, states, issiso=ocp.system.issiso(),
831832
transpose=transpose, return_x=return_states, squeeze=squeeze)
832833

833-
self.time = retval[0]
834-
self.inputs = retval[1]
835-
self.states = None if states is None else retval[2]
834+
self.time = response.time
835+
self.inputs = response.outputs
836+
self.states = response.states
836837

837838

838839
# Compute the input for a nonlinear, (constrained) optimal control problem

control/statesp.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1946,10 +1946,10 @@ def rss(states=1, outputs=1, inputs=1, strictly_proper=False):
19461946
----------
19471947
states : int
19481948
Number of state variables
1949-
inputs : int
1950-
Number of system inputs
19511949
outputs : int
19521950
Number of system outputs
1951+
inputs : int
1952+
Number of system inputs
19531953
strictly_proper : bool, optional
19541954
If set to 'True', returns a proper system (no direct term).
19551955

control/tests/timeresp_test.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,7 @@ def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape1, shape2):
11181118
@pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.ss2io])
11191119
def test_squeeze_exception(self, fcn):
11201120
sys = fcn(ct.rss(2, 1, 1))
1121-
with pytest.raises(ValueError, match="unknown squeeze value"):
1121+
with pytest.raises(ValueError, match="Unknown squeeze value"):
11221122
step_response(sys, squeeze=1)
11231123

11241124
@pytest.mark.usefixtures("editsdefaults")

0 commit comments

Comments
 (0)