Skip to content

Commit 85231b5

Browse files
committed
move I/O processing to property functions
1 parent 724d1df commit 85231b5

2 files changed

Lines changed: 81 additions & 46 deletions

File tree

control/iosys.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,7 @@ def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None,
879879
def __call__(sys, u, params=None, squeeze=None):
880880
"""Evaluate a (static) nonlinearity at a given input value
881881
882-
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
883883
system at an input `u` gives the output `y = F(u)`, determined by the
884884
output function.
885885
@@ -1572,7 +1572,7 @@ def input_output_response(
15721572
u = U[i] if len(U.shape) == 1 else U[:, i]
15731573
y[:, i] = sys._out(T[i], [], u)
15741574
return InputOutputResponse(
1575-
T, y, np.array((0, 0, np.asarray(T).size)), None, sys=sys,
1575+
T, y, np.zeros((0, 0, np.asarray(T).size)), None, sys=sys,
15761576
transpose=transpose, return_x=return_x, squeeze=squeeze)
15771577

15781578
# create X0 if not given, test if X0 has correct shape

control/timeresp.py

Lines changed: 79 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,12 @@ class InputOutputResponse:
120120
121121
Methods
122122
-------
123-
plot(**kwargs)
123+
plot(**kwargs) [NOT IMPLEMENTED]
124124
Plot the input/output response. Keywords are passed to matplotlib.
125125
126+
set_defaults(**kwargs) [NOT IMPLEMENTED]
127+
Set the default values for accessing the input/output data.
128+
126129
Examples
127130
--------
128131
>>> sys = ct.rss(4, 2, 2)
@@ -144,7 +147,6 @@ class InputOutputResponse:
144147
145148
t, y = step_response(sys)
146149
t, y, x = step_response(sys, return_x=True)
147-
t, y, x, u = step_response(sys, return_x=True, return_u=True)
148150
149151
2. For backward compatibility with earlier version of python-control,
150152
this class has ``__getitem__`` and ``__len__`` methods that allow the
@@ -154,9 +156,9 @@ class InputOutputResponse:
154156
response[1]: returns the output vector
155157
response[2]: returns the state vector
156158
157-
If the index is two-dimensional, a new ``InputOutputResponse`` object
158-
is returned that corresponds to the specified subset of input/output
159-
responses.
159+
3. If a response is indexed using a two-dimensional tuple, a new
160+
``InputOutputResponse`` object is returned that corresponds to the
161+
specified subset of input/output responses. [NOT IMPLEMENTED]
160162
161163
"""
162164

@@ -169,26 +171,46 @@ def __init__(
169171
170172
Parameters
171173
----------
172-
sys : LTI or InputOutputSystem
173-
System that generated the data (used to check if SISO/MIMO).
174-
175-
T : 1D array
174+
t : 1D array
176175
Time values of the output. Ignored if None.
177176
178-
yout : ndarray
179-
Response of the system. This can either be a 1D array indexed
180-
by time (for SISO systems), a 2D array indexed by output and
181-
time (for MIMO systems with no input indexing, such as
182-
initial_response or forced response) or a 3D array indexed by
183-
output, input, and time.
177+
y : ndarray
178+
Output response of the system. This can either be a 1D array
179+
indexed by time (for SISO systems or MISO systems with a specified
180+
input), a 2D array indexed by output and time (for MIMO systems
181+
with no input indexing, such as initial_response or forced
182+
response) or a 3D array indexed by output, input, and time.
183+
184+
x : array, optional
185+
Individual response of each state variable. This should be a 2D
186+
array indexed by the state index and time (for single input
187+
systems) or a 3D array indexed by state, input, and time.
188+
189+
u : array, optional
190+
Inputs used to generate the output. This can either be a 1D array
191+
indexed by time (for SISO systems or MISO/MIMO systems with a
192+
specified input) or a 2D array indexed by input and time.
184193
185-
xout : array, optional
186-
Individual response of each x variable (if return_x is
187-
True). For a SISO system (or if a single input is specified),
188-
this should be a 2D array indexed by the state index and time
189-
(for single input systems) or a 3D array indexed by state,
190-
input, and time. Ignored if None.
194+
sys : LTI or InputOutputSystem, optional
195+
System that generated the data. If desired, the system used to
196+
generate the data can be stored along with the data.
191197
198+
squeeze : bool, optional
199+
By default, if a system is single-input, single-output (SISO) then
200+
the inputs and outputs are returned as a 1D array (indexed by
201+
time) and if a system is multi-input or multi-output, the the
202+
inputs are returned as a 2D array (indexed by input and time) and
203+
the outputs are returned as a 3D array (indexed by output, input,
204+
and time). If squeeze=True, access to the output response will
205+
remove single-dimensional entries from the shape of the inputs and
206+
outputs even if the system is not SISO. If squeeze=False, keep the
207+
input as a 2D array (indexed by the input and time) and the output
208+
as a 3D array (indexed by the output, input, and time) even if the
209+
system is SISO. The default value can be set using
210+
config.defaults['control.squeeze_time_response'].
211+
212+
Additional parameters
213+
---------------------
192214
transpose : bool, optional
193215
If True, transpose all input and output arrays (for backward
194216
compatibility with MATLAB and :func:`scipy.signal.lsim`).
@@ -197,15 +219,6 @@ def __init__(
197219
return_x : bool, optional
198220
If True, return the state vector (default = False).
199221
200-
squeeze : bool, optional
201-
By default, if a system is single-input, single-output (SISO) then
202-
the output response is returned as a 1D array (indexed by time).
203-
If squeeze=True, remove single-dimensional entries from the shape
204-
of the output even if the system is not SISO. If squeeze=False,
205-
keep the output as a 3D array (indexed by the output, input, and
206-
time) even if the system is SISO. The default value can be set
207-
using config.defaults['control.squeeze_time_response'].
208-
209222
input : int, optional
210223
If present, the response represents only the listed input.
211224
@@ -216,10 +229,6 @@ def __init__(
216229
#
217230
# Process and store the basic input/output elements
218231
#
219-
t, y, x = _process_time_response(
220-
sys, t, y, x,
221-
transpose=transpose, return_x=True, squeeze=squeeze,
222-
input=input, output=output)
223232

224233
# Time vector
225234
self.t = np.atleast_1d(t)
@@ -229,30 +238,36 @@ def __init__(
229238
# Output vector
230239
self.yout = np.array(y)
231240
self.noutputs = 1 if len(self.yout.shape) < 2 else self.yout.shape[0]
232-
self.ninputs = 1 if len(self.yout.shape) < 3 else self.yout.shape[-2]
233-
# TODO: Check to make sure time points match
241+
if self.t.shape[-1] != self.yout.shape[-1]:
242+
raise ValueError("Output vector does not match time vector")
234243

235244
# State vector
236245
self.xout = np.array(x)
237-
self.nstates = self.xout.shape[0]
238-
# TODO: Check to make sure time points match
246+
self.nstates = 0 if self.xout is None else self.xout.shape[0]
247+
if self.t.shape[-1] != self.xout.shape[-1]:
248+
raise ValueError("State vector does not match time vector")
239249

240250
# Input vector
241251
self.uout = np.array(u)
242-
# TODO: Check to make sure input shape is OK
243-
# TODO: Check to make sure time points match
252+
if len(self.uout.shape) != 0:
253+
self.ninputs = 1 if len(self.uout.shape) < 2 \
254+
else self.uout.shape[-2]
255+
if self.t.shape[-1] != self.uout.shape[-1]:
256+
raise ValueError("Input vector does not match time vector")
257+
else:
258+
self.ninputs = 0
244259

245260
# If the system was specified, make sure it is compatible
246261
if sys is not None:
247-
if sys.ninputs != self.ninputs:
248-
ValueError("System inputs do not match response data")
249262
if sys.noutputs != self.noutputs:
250263
ValueError("System outputs do not match response data")
251264
if sys.nstates != self.nstates:
252265
ValueError("System states do not match response data")
253266
self.sys = sys
254267

255268
# Keep track of whether to squeeze inputs, outputs, and states
269+
if not (squeeze is True or squeeze is None or squeeze is False):
270+
raise ValueError("unknown squeeze value")
256271
self.squeeze = squeeze
257272

258273
# Store legacy keyword values (only needed for legacy interface)
@@ -263,12 +278,29 @@ def __init__(
263278
# Getter for output (implements squeeze processing)
264279
@property
265280
def y(self):
266-
return self.yout
281+
t, y = _process_time_response(
282+
self.sys, self.t, self.yout, None,
283+
transpose=self.transpose, return_x=False, squeeze=self.squeeze,
284+
input=self.input, output=self.output)
285+
return y
267286

268287
# Getter for state (implements squeeze processing)
269288
@property
270289
def x(self):
271-
return self.xout
290+
t, y, x = _process_time_response(
291+
self.sys, self.t, self.yout, self.xout,
292+
transpose=self.transpose, return_x=True, squeeze=self.squeeze,
293+
input=self.input, output=self.output)
294+
return x
295+
296+
# Getter for state (implements squeeze processing)
297+
@property
298+
def u(self):
299+
t, y = _process_time_response(
300+
self.sys, self.t, self.uout, None,
301+
transpose=self.transpose, return_x=False, squeeze=self.squeeze,
302+
input=self.input, output=self.output)
303+
return x
272304

273305
# Implement iter to allow assigning to a tuple
274306
def __iter__(self):
@@ -685,6 +717,9 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False,
685717
tout = T # Return exact list of time steps
686718
yout = yout[::inc, :]
687719
xout = xout[::inc, :]
720+
else:
721+
# Interpolate the input to get the right number of points
722+
U = sp.interpolate.interp1d(T, U)(tout)
688723

689724
# Transpose the output and state vectors to match local convention
690725
xout = np.transpose(xout)

0 commit comments

Comments
 (0)