Skip to content

Commit 8aa68eb

Browse files
committed
move input/output processing and add __call__ to change keywords
1 parent 03f0e28 commit 8aa68eb

File tree

2 files changed

+99
-27
lines changed

2 files changed

+99
-27
lines changed

control/tests/trdata_test.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,39 @@ def test_trdata_shapes(nin, nout, squeeze):
119119

120120
# Check state space dimensions (not affected by squeeze)
121121
assert res.states.shape == (sys.nstates, ntimes)
122+
123+
124+
def test_response_copy():
125+
# Generate some initial data to use
126+
sys_siso = ct.rss(4, 1, 1)
127+
response_siso = ct.step_response(sys_siso)
128+
siso_ntimes = response_siso.time.size
129+
130+
sys_mimo = ct.rss(4, 2, 1)
131+
response_mimo = ct.step_response(sys_mimo)
132+
mimo_ntimes = response_mimo.time.size
133+
134+
# Transpose
135+
response_mimo_transpose = response_mimo(transpose=True)
136+
assert response_mimo.outputs.shape == (2, 1, mimo_ntimes)
137+
assert response_mimo_transpose.outputs.shape == (mimo_ntimes, 2, 1)
138+
assert response_mimo.states.shape == (4, 1, mimo_ntimes)
139+
assert response_mimo_transpose.states.shape == (mimo_ntimes, 4, 1)
140+
141+
# Squeeze
142+
response_siso_as_mimo = response_siso(squeeze=False)
143+
assert response_siso_as_mimo.outputs.shape == (1, 1, siso_ntimes)
144+
assert response_siso_as_mimo.states.shape == (4, siso_ntimes)
145+
146+
response_mimo_squeezed = response_mimo(squeeze=True)
147+
assert response_mimo_squeezed.outputs.shape == (2, mimo_ntimes)
148+
assert response_mimo_squeezed.states.shape == (4, 1, mimo_ntimes)
149+
150+
# Squeeze and transpose
151+
response_mimo_sqtr = response_mimo(squeeze=True, transpose=True)
152+
assert response_mimo_sqtr.outputs.shape == (mimo_ntimes, 2)
153+
assert response_mimo_sqtr.states.shape == (mimo_ntimes, 4, 1)
154+
155+
# Unknown keyword
156+
with pytest.raises(ValueError, match="unknown"):
157+
response_bad_kw = response_mimo(input=0)

control/timeresp.py

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import scipy as sp
7777
from numpy import einsum, maximum, minimum
7878
from scipy.linalg import eig, eigvals, matrix_balance, norm
79+
from copy import copy
7980

8081
from . import config
8182
from .lti import isctime, isdtime
@@ -172,15 +173,6 @@ class TimeResponseData():
172173
response. If ntraces is 0 then the data represents a single trace
173174
with the trace index surpressed in the data.
174175
175-
input_index : int, optional
176-
If set to an integer, represents the input index for the input signal.
177-
Default is ``None``, in which case all inputs should be given.
178-
179-
output_index : int, optional
180-
If set to an integer, represents the output index for the output
181-
response. Default is ``None``, in which case all outputs should be
182-
given.
183-
184176
Notes
185177
-----
186178
1. For backward compatibility with earlier versions of python-control,
@@ -199,12 +191,16 @@ class TimeResponseData():
199191
response[1]: returns the output vector
200192
response[2]: returns the state vector
201193
194+
3. The default settings for ``return_x``, ``squeeze`` and ``transpose``
195+
can be changed by calling the class instance and passing new values:
196+
197+
response(tranpose=True).input
198+
202199
"""
203200

204201
def __init__(
205202
self, time, outputs, states=None, inputs=None, issiso=None,
206-
transpose=False, return_x=False, squeeze=None,
207-
multi_trace=False, input_index=None, output_index=None
203+
transpose=False, return_x=False, squeeze=None, multi_trace=False
208204
):
209205
"""Create an input/output time response object.
210206
@@ -271,12 +267,6 @@ def __init__(
271267
a MIMO system, the ``input`` attribute should then be set to
272268
indicate which trace is being specified. Default is ``False``.
273269
274-
input_index : int, optional
275-
If present, the response represents only the listed input.
276-
277-
output_index : int, optional
278-
If present, the response represents only the listed output.
279-
280270
"""
281271
#
282272
# Process and store the basic input/output elements
@@ -394,8 +384,7 @@ def __init__(
394384
raise ValueError("Keyword `issiso` does not match data")
395385

396386
# Set the value to be used for future processing
397-
self.issiso = issiso or \
398-
(input_index is not None and output_index is not None)
387+
self.issiso = issiso
399388

400389
# Keep track of whether to squeeze inputs, outputs, and states
401390
if not (squeeze is True or squeeze is None or squeeze is False):
@@ -405,10 +394,50 @@ def __init__(
405394
# Store legacy keyword values (only needed for legacy interface)
406395
self.transpose = transpose
407396
self.return_x = return_x
408-
self.input_index, self.output_index = input_index, output_index
397+
398+
def __call__(self, **kwargs):
399+
"""Change value of processing keywords.
400+
401+
Calling the time response object will create a copy of the object and
402+
change the values of the keywords used to control the ``outputs``,
403+
``states``, and ``inputs`` properties.
404+
405+
Parameters
406+
----------
407+
squeeze : bool, optional
408+
If squeeze=True, access to the output response will
409+
remove single-dimensional entries from the shape of the inputs
410+
and outputs even if the system is not SISO. If squeeze=False,
411+
keep the input as a 2D or 3D array (indexed by the input (if
412+
multi-input), trace (if single input) and time) and the output
413+
as a 3D array (indexed by the output, trace, and time) even if
414+
the system is SISO.
415+
416+
transpose : bool, optional
417+
If True, transpose all input and output arrays (for backward
418+
compatibility with MATLAB and :func:`scipy.signal.lsim`).
419+
Default value is False.
420+
421+
return_x : bool, optional
422+
If True, return the state vector when enumerating result by
423+
assigning to a tuple (default = False).
424+
"""
425+
# Make a copy of the object
426+
response = copy(self)
427+
428+
# Update any keywords that we were passed
429+
response.transpose = kwargs.pop('transpose', self.transpose)
430+
response.squeeze = kwargs.pop('squeeze', self.squeeze)
431+
432+
# Make sure no unknown keywords were passed
433+
if len(kwargs) != 0:
434+
raise ValueError("unknown parameter(s) %s" % kwargs)
435+
436+
return response
409437

410438
@property
411439
def time(self):
440+
412441
"""Time vector.
413442
414443
Time values of the input/output response(s).
@@ -1180,10 +1209,12 @@ def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None,
11801209
xout[:, inpidx, :] = response.x
11811210
uout[:, inpidx, :] = U
11821211

1212+
# Figure out if the system is SISO or not
1213+
issiso = sys.issiso() or (input is not None and output is not None)
1214+
11831215
return TimeResponseData(
1184-
response.time, yout, xout, uout, issiso=sys.issiso(),
1185-
transpose=transpose, return_x=return_x, squeeze=squeeze,
1186-
input_index=input, output_index=output)
1216+
response.time, yout, xout, uout, issiso=issiso,
1217+
transpose=transpose, return_x=return_x, squeeze=squeeze)
11871218

11881219

11891220
def step_info(sysdata, T=None, T_num=None, yfinal=None,
@@ -1509,9 +1540,12 @@ def initial_response(sys, T=None, X0=0., input=0, output=None, T_num=None,
15091540
# Compute the forced response
15101541
response = forced_response(sys, T, 0, X0)
15111542

1543+
# Figure out if the system is SISO or not
1544+
issiso = sys.issiso() or (input is not None and output is not None)
1545+
15121546
# Store the response without an input
15131547
return TimeResponseData(
1514-
response.t, response.y, response.x, None, issiso=sys.issiso(),
1548+
response.t, response.y, response.x, None, issiso=issiso,
15151549
transpose=transpose, return_x=return_x, squeeze=squeeze)
15161550

15171551

@@ -1669,10 +1703,12 @@ def impulse_response(sys, T=None, X0=0., input=None, output=None, T_num=None,
16691703
yout[:, inpidx, :] = response.y
16701704
xout[:, inpidx, :] = response.x
16711705

1706+
# Figure out if the system is SISO or not
1707+
issiso = sys.issiso() or (input is not None and output is not None)
1708+
16721709
return TimeResponseData(
1673-
response.time, yout, xout, uout, issiso=sys.issiso(),
1674-
transpose=transpose, return_x=return_x, squeeze=squeeze,
1675-
input_index=input, output_index=output)
1710+
response.time, yout, xout, uout, issiso=issiso,
1711+
transpose=transpose, return_x=return_x, squeeze=squeeze)
16761712

16771713

16781714
# utility function to find time period and time increment using pole locations

0 commit comments

Comments
 (0)