Skip to content

Commit 4b4d9cd

Browse files
A simple step_info and stepinfo (for matlab compatibility) was implemented.
1 parent e4e7a0d commit 4b4d9cd

3 files changed

Lines changed: 194 additions & 2 deletions

File tree

control/matlab/timeresp.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Note that the return arguments are different than in the standard control package.
55
"""
66

7-
__all__ = ['step', 'impulse', 'initial', 'lsim']
7+
__all__ = ['step', 'stepinfo', 'impulse', 'initial', 'lsim']
88

99
def step(sys, T=None, X0=0., input=0, output=None, return_x=False):
1010
'''
@@ -66,6 +66,51 @@ def step(sys, T=None, X0=0., input=0, output=None, return_x=False):
6666

6767
return yout, T
6868

69+
def stepinfo(sys, T=None, SettlingTimeThreshold=0.02, RiseTimeLimits=(0.1,0.9)):
70+
'''
71+
Step response characteristics (Rise time, Settling Time, Peak and others).
72+
73+
Parameters
74+
----------
75+
sys: StateSpace, or TransferFunction
76+
LTI system to simulate
77+
78+
T: array-like object, optional
79+
Time vector (argument is autocomputed if not given)
80+
81+
SettlingTimeThreshold: float value, optional
82+
Defines the error to compute settling time (default = 0.02)
83+
84+
RiseTimeLimits: tuple (lower_threshold, upper_theshold)
85+
Defines the lower and upper threshold for RiseTime computation
86+
87+
Returns
88+
-------
89+
S: a dictionary containing:
90+
RiseTime: Time from 10% to 90% of the steady-state value.
91+
SettlingTime: Time to enter inside a default error of 2%
92+
SettlingMin: Minimum value after RiseTime
93+
SettlingMax: Maximum value after RiseTime
94+
Overshoot: Percentage of the Peak relative to steady value
95+
Undershoot: Percentage of undershoot
96+
Peak: Absolute peak value
97+
PeakTime: time of the Peak
98+
99+
100+
See Also
101+
--------
102+
step, lsim, initial, impulse
103+
104+
Examples
105+
--------
106+
>>> S = stepinfo(sys, T)
107+
'''
108+
from ..timeresp import step_info
109+
110+
S = step_info(sys, T, SettlingTimeThreshold, RiseTimeLimits)
111+
112+
return S
113+
69114
def impulse(sys, T=None, X0=0., input=0, output=None, return_x=False):
70115
'''
71116
Impulse response of a linear system

control/tests/timeresp_test.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,61 @@ def test_step_response(self):
8787
np.testing.assert_array_equal(Tc.shape, Td.shape)
8888
np.testing.assert_array_equal(youtc.shape, youtd.shape)
8989

90+
def test_step_info(self):
91+
# From matlab docs:
92+
sys = TransferFunction([1,5,5],[1,1.65,5,6.5,2])
93+
Strue = {
94+
'RiseTime': 3.8456,
95+
'SettlingTime': 27.9762,
96+
'SettlingMin': 2.0689,
97+
'SettlingMax': 2.6873,
98+
'Overshoot': 7.4915,
99+
'Undershoot': 0,
100+
'Peak': 2.6873,
101+
'PeakTime': 8.0530
102+
}
103+
104+
S = step_info(sys)
105+
106+
# Very arbitrary tolerance because I don't know if the
107+
# response from the MATLAB is really that accurate.
108+
# maybe it is a good idea to change the Strue to match
109+
# but I didn't do it because I don't know if it is
110+
# accurate either...
111+
rtol = 2e-2
112+
np.testing.assert_allclose(
113+
S.get('RiseTime'),
114+
Strue.get('RiseTime'),
115+
rtol=rtol)
116+
np.testing.assert_allclose(
117+
S.get('SettlingTime'),
118+
Strue.get('SettlingTime'),
119+
rtol=rtol)
120+
np.testing.assert_allclose(
121+
S.get('SettlingMin'),
122+
Strue.get('SettlingMin'),
123+
rtol=rtol)
124+
np.testing.assert_allclose(
125+
S.get('SettlingMax'),
126+
Strue.get('SettlingMax'),
127+
rtol=rtol)
128+
np.testing.assert_allclose(
129+
S.get('Overshoot'),
130+
Strue.get('Overshoot'),
131+
rtol=rtol)
132+
np.testing.assert_allclose(
133+
S.get('Undershoot'),
134+
Strue.get('Undershoot'),
135+
rtol=rtol)
136+
np.testing.assert_allclose(
137+
S.get('Peak'),
138+
Strue.get('Peak'),
139+
rtol=rtol)
140+
np.testing.assert_allclose(
141+
S.get('PeakTime'),
142+
Strue.get('PeakTime'),
143+
rtol=rtol)
144+
90145
def test_impulse_response(self):
91146
# Test SISO system
92147
sys = self.siso_ss1

control/timeresp.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
from .statesp import _convertToStateSpace, _mimo2simo, _mimo2siso
5959
from .lti import isdtime, isctime
6060

61-
__all__ = ['forced_response', 'step_response', 'initial_response',
61+
__all__ = ['forced_response', 'step_response', 'step_info', 'initial_response',
6262
'impulse_response']
6363

6464
# Helper function for checking array-like parameters
@@ -432,6 +432,98 @@ def step_response(sys, T=None, X0=0., input=None, output=None,
432432

433433
return T, yout
434434

435+
def step_info(sys, T=None, SettlingTimeThreshold=0.02, RiseTimeLimits=(0.1,0.9)):
436+
'''
437+
Step response characteristics (Rise time, Settling Time, Peak and others).
438+
439+
Parameters
440+
----------
441+
sys: StateSpace, or TransferFunction
442+
LTI system to simulate
443+
444+
T: array-like object, optional
445+
Time vector (argument is autocomputed if not given)
446+
447+
SettlingTimeThreshold: float value, optional
448+
Defines the error to compute settling time (default = 0.02)
449+
450+
RiseTimeLimits: tuple (lower_threshold, upper_theshold)
451+
Defines the lower and upper threshold for RiseTime computation
452+
453+
Returns
454+
-------
455+
S: a dictionary containing:
456+
RiseTime: Time from 10% to 90% of the steady-state value.
457+
SettlingTime: Time to enter inside a default error of 2%
458+
SettlingMin: Minimum value after RiseTime
459+
SettlingMax: Maximum value after RiseTime
460+
Overshoot: Percentage of the Peak relative to steady value
461+
Undershoot: Percentage of undershoot
462+
Peak: Absolute peak value
463+
PeakTime: time of the Peak
464+
SteadyStateValue: Steady-state value
465+
466+
467+
See Also
468+
--------
469+
step, lsim, initial, impulse
470+
471+
Examples
472+
--------
473+
>>> info = step_info(sys, T)
474+
'''
475+
sys = _get_ss_simo(sys)
476+
if T is None:
477+
if isctime(sys):
478+
T = _default_response_times(sys.A, 1000)
479+
else:
480+
# For discrete time, use integers
481+
tvec = _default_response_times(sys.A, 1000)
482+
T = range(int(np.ceil(max(tvec))))
483+
484+
T, yout = step_response(sys, T)
485+
486+
# Steady state value
487+
InfValue = yout[-1]
488+
489+
# RiseTime
490+
tr_lower_index = (np.where(yout >= RiseTimeLimits[0] * InfValue)[0])[0]
491+
tr_upper_index = (np.where(yout >= RiseTimeLimits[1] * InfValue)[0])[0]
492+
RiseTime = T[tr_upper_index] - T[tr_lower_index]
493+
494+
# SettlingTime
495+
sup_margin = (1. + SettlingTimeThreshold) * InfValue
496+
inf_margin = (1. - SettlingTimeThreshold) * InfValue
497+
# find Steady State looking for the first point out of specified limits
498+
for i in reversed(range(T.size)):
499+
if((yout[i] <= inf_margin) | (yout[i] >= sup_margin)):
500+
SettlingTime = T[i + 1]
501+
break
502+
503+
# Peak
504+
PeakIndex = np.abs(yout).argmax()
505+
PeakValue = yout[PeakIndex]
506+
PeakTime = T[PeakIndex]
507+
SettlingMax = (yout).max()
508+
SettlingMin = (yout[tr_upper_index:]).min()
509+
# I'm really not very confident about UnderShoot:
510+
UnderShoot = yout.min()
511+
OverShoot = 100. * (yout.max() - InfValue) / (InfValue - yout[0])
512+
513+
# Return as a dictionary
514+
S = {
515+
'RiseTime': RiseTime,
516+
'SettlingTime': SettlingTime,
517+
'SettlingMin': SettlingMin,
518+
'SettlingMax': SettlingMax,
519+
'Overshoot': OverShoot,
520+
'Undershoot': UnderShoot,
521+
'Peak': PeakValue,
522+
'PeakTime': PeakTime,
523+
'SteadyStateValue': InfValue
524+
}
525+
526+
return S
435527

436528
def initial_response(sys, T=None, X0=0., input=0, output=None,
437529
transpose=False, return_x=False):

0 commit comments

Comments
 (0)