8000 Merge pull request #511 from murrayrm/standardize_time_response · python-control/python-control@0a08ff2 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0a08ff2

Browse files
authored
Merge pull request #511 from murrayrm/standardize_time_response
standardize time response return values, return_x/squeeze keyword processing
2 parents 6f674cf + 47bbf16 commit 0a08ff2

File tree

11 files changed

+441
-193
lines changed

11 files changed

+441
-193
lines changed

control/config.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
# Package level default values
1717
_control_defaults = {
1818
'control.default_dt': 0,
19-
'control.squeeze_frequency_response': None
19+
'control.squeeze_frequency_response': None,
20+
'control.squeeze_time_response': True,
21+
'control.squeeze_time_response': None,
22+
'forced_response.return_x': False,
2023
}
2124
defaults = dict(_control_defaults)
2225

@@ -211,6 +214,7 @@ def use_legacy_defaults(version):
211214
#
212215
# Go backwards through releases and reset defaults
213216
#
217+
reset_defaults() # start from a clean slate
214218

215219
# Version 0.9.0:
216220
if major == 0 and minor < 9:
@@ -230,4 +234,10 @@ def use_legacy_defaults(version):
230234
# turned off _remove_useless_states
231235
set_defaults('statesp', remove_useless_states=True)
232236

237+
# forced_response no longer returns x by default
238+
set_defaults('forced_response', return_x=True)
239+
240+
# time responses are only squeezed if SISO
241+
set_defaults('control', squeeze_time_response=True)
242+
233243
return (major, minor, patch)

control/iosys.py

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from warnings import warn
3838

3939
from .statesp import StateSpace, tf2ss
40-
from .timeresp import _check_convert_array
40+
from .timeresp import _check_convert_array, _process_time_response
4141
from .lti import isctime, isdtime, common_timebase
4242
from . import config
4343

@@ -450,6 +450,10 @@ def find_state(self, name):
450450
"""Find the index for a state given its name (`None` if not found)"""
451451
return self.state_index.get(name, None)
452452

453+
def issiso(self):
454+
"""Check to see if a system is single input, single output"""
455+
return self.ninputs == 1 and self.noutputs == 1
456+
453457
def feedback(self, other=1, sign=-1, params={}):
454458
67E6 """Feedback interconnection between two input/output systems
455459
@@ -1353,7 +1357,7 @@ def __init__(self, io_sys, ss_sys=None):
13531357

13541358

13551359
def input_output_response(sys, T, U=0., X0=0, params={}, method='RK45',
1356-
return_x=False, squeeze=True):
1360+
transpose=False, return_x=False, squeeze=None):
13571361

13581362
"""Compute the output response of a system to a given input.
13591363
@@ -1373,18 +1377,22 @@ def input_output_response(sys, T, U=0., X0=0, params={}, method='RK45',
13731377
return_x : bool, optional
13741378
If True, return the values of the state at each time (default = False).
13751379
squeeze : bool, optional
1376-
If True (default), squeeze unused dimensions out of the output
1377-
response. In particular, for a single output system, return a
1378-
vector of shape (nsteps) instead of (nsteps, 1).
1380+
If True and if the system has a single output, return the system
1381+
output as a 1D array rather than a 2D array. If False, return the
1382+
system output as a 2D array even if the system is SISO. Default value
1383+
set by config.defaults['control.squeeze_time_response'].
13791384
13801385
Returns
13811386
-------
13821387
T : array
13831388
Time values of the output.
13841389
yout : array
1385-
Response of the system.
1390+
Response of the system. If the system is SISO and squeeze is not
1391+
True, the array is 1D (indexed by time). If the system is not SISO or
1392+
squeeze is False, the array is 2D (indexed by the output number and
1393+
time).
13861394
xout : array
1387-
Time evolution of the state vector (if return_x=True)
1395+
Time evolution of the state vector (if return_x=True).
13881396
13891397
Raises
13901398
------
@@ -1420,12 +1428,8 @@ def input_output_response(sys, T, U=0., X0=0, params={}, method='RK45',
14201428
for i in range(len(T)):
14211429
u = U[i] if len(U.shape) == 1 else U[:, i]
14221430
y[:, i] = sys._out(T[i], [], u)
1423-
if squeeze:
1424-
y = np.squeeze(y)
1425-
if return_x:
1426-
return T, y, []
1427-
else:
1428-
return T, y
1431+
return _process_time_response(sys, T, y, [], transpose=transpose,
1432+
return_x=return_x, squeeze=squeeze)
14291433

14301434
# create X0 if not given, test if X0 has correct shape
14311435
X0 = _check_convert_array(X0, [(nstates,), (nstates, 1)],
@@ -1500,14 +1504,8 @@ def ivp_rhs(t, x): return sys._rhs(t, x, u(t))
15001504
else: # Neither ctime or dtime??
15011505
raise TypeError("Can't determine system type")
15021506

1503-
# Get rid of extra dimensions in the output, of desired
1504-
if squeeze:
1505-
y = np.squeeze(y)
1506-
1507-
if return_x:
1508-
return soln.t, y, soln.y
1509-
else:
1510-
return soln.t, y
1507+
return _process_time_response(sys, soln.t, y, soln.y, transpose=transpose,
1508+
return_x=return_x, squeeze=squeeze)
15111509

15121510

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

control/matlab/timeresp.py

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,13 @@ def step(sys, T=None, X0=0., input=0, output=None, return_x=False):
5959
'''
6060
from ..timeresp import step_response
6161

62-
T, yout, xout = step_response(sys, T, X0, input, output,
63-
transpose=True, return_x=True)
62+
# Switch output argument order and transpose outputs
63+
out = step_response(sys, T, X0, input, output,
64+
transpose=True, return_x=return_x)
65+
return (out[1], out[0], out[2]) if return_x else (out[1], out[0])
6466

65-
if return_x:
66-
return yout, T, xout
67-
68-
return yout, T
69-
70-
def stepinfo(sys, T=None, SettlingTimeThreshold=0.02, RiseTimeLimits=(0.1, 0.9)):
67+
def stepinfo(sys, T=None, SettlingTimeThreshold=0.02,
68+
RiseTimeLimits=(0.1, 0.9)):
7169
'''
7270
Step response characteristics (Rise time, Settling Time, Peak and others).
7371
@@ -110,6 +108,7 @@ def stepinfo(sys, T=None, SettlingTimeThreshold=0.02, RiseTimeLimits=(0.1, 0.9))
110108
'''
111109
from ..timeresp import step_info
112110

111+
# Call step_info with MATLAB defaults
113112
S = step_info(sys, T, None, SettlingTimeThreshold, RiseTimeLimits)
114113

115114
return S
@@ -164,13 +163,11 @@ def impulse(sys, T=None, X0=0., input=0, output=None, return_x=False):
164163
>>> yout, T = impulse(sys, T)
165164
'''
166165
from ..timeresp import impulse_response
167-
T, yout, xout = impulse_response(sys, T, X0, input, output,
168-
transpose = True, return_x=True)
169-
170-
if return_x:
171-
return yout, T, xout
172166

173-
return yout, T
167+
# Switch output argument order and transpose outputs
168+
out = impulse_response(sys, T, X0, input, output,
169+
transpose = True, return_x=return_x)
170+
return (out[1], out[0], out[2]) if return_x else (out[1], out[0])
174171

175172
def initial(sys, T=None, X0=0., input=None, output=None, return_x=False):
176173
'''
@@ -222,13 +219,12 @@ def initial(sys, T=None, X0=0., input=None, output=None, return_x=False):
222219
223220
'''
224221
from ..timeresp import initial_response
222+
223+
# Switch output argument order and transpose outputs
225224
T, yout, xout = initial_response(sys, T, X0, output=output,
226225
transpose=True, return_x=True)
226+
return (yout, T, xout) if return_x else (yout, T)
227227

228-
if return_x:
229-
return yout, T, xout
230-
231-
return yout, T
232228

233229
def lsim(sys, U=0., T=None, X0=0.):
234230
'''
@@ -273,5 +269,7 @@ def lsim(sys, U=0., T=None, X0=0.):
273269
>>> yout, T, xout = lsim(sys, U, T, X0)
274270
'''
275271
from ..timeresp import forced_response
276-
T, yout, xout = forced_response(sys, T, U, X0, transpose = True)
277-
return yout, T, xout
272+
273+
# Switch output argument order and transpose outputs (and always return x)
274+
out = forced_response(sys, T, U, X0, return_x=True, transpose=True)
275+
return out[1], out[0], out[2]

control/tests/config_test.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,15 @@ def test_legacy_defaults(self):
211211
ct.use_legacy_defaults('0.8.3')
212212
assert(isinstance(ct.ss(0, 0, 0, 1).D, np.matrix))
213213
ct.reset_defaults()
214-
assert(isinstance(ct.ss(0, 0, 0, 1).D, np.ndarray))
215-
assert(not isinstance(ct.ss(0, 0, 0, 1).D, np.matrix))
214+
assert isinstance(ct.ss(0, 0, 0, 1).D, np.ndarray)
215+
assert not isinstance(ct.ss(0, 0, 0, 1).D, np.matrix)
216+
217+
ct.use_legacy_defaults('0.8.4')
218+
assert ct.config.defaults['forced_response.return_x'] is True
216219

217220
ct.use_legacy_defaults('0.9.0')
218-
assert(isinstance(ct.ss(0, 0, 0, 1).D, np.ndarray))
219-
assert(not isinstance(ct.ss(0, 0, 0, 1).D, np.matrix))
221+
assert isinstance(ct.ss(0, 0, 0, 1).D, np.ndarray)
222+
assert not isinstance(ct.ss(0, 0, 0, 1).D, np.matrix)
220223

221224
# test that old versions don't raise a problem
222225
ct.use_legacy_defaults('REL-0.1')

control/tests/discrete_test.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,9 +353,11 @@ def testSimulation(self, tsys):
353353
tout, yout = step_response(tsys.siso_ss1d, T)
354354
tout, yout = impulse_response(tsys.siso_ss1d)
355355
tout, yout = impulse_response(tsys.siso_ss1d, T)
356-
tout, yout, xout = forced_response(tsys.siso_ss1d, T, U, 0)
357-
tout, yout, xout = forced_response(tsys.siso_ss2d, T, U, 0)
358-
tout, yout, xout = forced_response(tsys.siso_ss3d, T, U, 0)
356+
tout, yout = forced_response(tsys.siso_ss1d, T, U, 0)
357+
tout, yout = forced_response(tsys.siso_ss2d, T, U, 0)
358+
tout, yout = forced_response(tsys.siso_ss3d, T, U, 0)
359+
tout, yout, xout = forced_response(tsys.siso_ss1d, T, U, 0,
360+
return_x=True)
359361

360362
def test_sample_system(self, tsys):
361363
# Make sure we can convert various types of systems

control/tests/flatsys_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def test_double_integrator(self, xf, uf, Tf):
4848
T = np.linspace(0, Tf, 100)
4949
xd, ud = traj.eval(T)
5050

51-
t, y, x = ct.forced_response(sys, T, ud, x1)
51+
t, y, x = ct.forced_response(sys, T, ud, x1, return_x=True)
5252
np.testing.assert_array_almost_equal(x, xd, decimal=3)
5353

5454
def test_kinematic_car(self):

0 commit comments

Comments
 (0)
0