Skip to content

Commit 155d8eb

Browse files
Improved default time vector and handling for time response functions step, impulse, and initial (#420)
* fixed default response time for time response of discrete-time functions step, impulse, and initial * to pass tests, added convenient ability to specify simulation time and number of steps rather than complete time vector in timeresponse functions * eliminated deprecation warnings by importing certain functions from numpy instead of scipy * eliminated deprecation warnings by importing certain functions from numpy instead of scipy * small fix to pass unit tests * adjusted sisotool test so tests pass with new default step response time window * added functionality to automatically choose dt in timeresp.py based on system poles. and unit tests. * removed some leftover code and comments * explanation in docstrings for how time vector T is auto-computed in time response functions
1 parent ce3a231 commit 155d8eb

File tree

7 files changed

+220
-93
lines changed

7 files changed

+220
-93
lines changed

control/freqplot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -822,10 +822,10 @@ def default_frequency_range(syslist, Hz=None, number_of_samples=None,
822822

823823
# Set the range to be an order of magnitude beyond any features
824824
if number_of_samples:
825-
omega = sp.logspace(
825+
omega = np.logspace(
826826
lsp_min, lsp_max, num=number_of_samples, endpoint=True)
827827
else:
828-
omega = sp.logspace(lsp_min, lsp_max, endpoint=True)
828+
omega = np.logspace(lsp_min, lsp_max, endpoint=True)
829829
return omega
830830

831831

control/matlab/timeresp.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ def step(sys, T=None, X0=0., input=0, output=None, return_x=False):
2121
sys: StateSpace, or TransferFunction
2222
LTI system to simulate
2323
24-
T: array-like object, optional
25-
Time vector (argument is autocomputed if not given)
24+
T: array-like or number, optional
25+
Time vector, or simulation time duration if a number (time vector is
26+
autocomputed if not given)
2627
2728
X0: array-like or number, optional
2829
Initial condition (default = 0)
@@ -59,7 +60,7 @@ def step(sys, T=None, X0=0., input=0, output=None, return_x=False):
5960
from ..timeresp import step_response
6061

6162
T, yout, xout = step_response(sys, T, X0, input, output,
62-
transpose = True, return_x=True)
63+
transpose=True, return_x=True)
6364

6465
if return_x:
6566
return yout, T, xout
@@ -75,8 +76,9 @@ def stepinfo(sys, T=None, SettlingTimeThreshold=0.02, RiseTimeLimits=(0.1,0.9)):
7576
sys: StateSpace, or TransferFunction
7677
LTI system to simulate
7778
78-
T: array-like object, optional
79-
Time vector (argument is autocomputed if not given)
79+
T: array-like or number, optional
80+
Time vector, or simulation time duration if a number (time vector is
81+
autocomputed if not given)
8082
8183
SettlingTimeThreshold: float value, optional
8284
Defines the error to compute settling time (default = 0.02)
@@ -127,9 +129,10 @@ def impulse(sys, T=None, X0=0., input=0, output=None, return_x=False):
127129
sys: StateSpace, TransferFunction
128130
LTI system to simulate
129131
130-
T: array-like object, optional
131-
Time vector (argument is autocomputed if not given)
132-
132+
T: array-like or number, optional
133+
Time vector, or simulation time duration if a number (time vector is
134+
autocomputed if not given)
135+
133136
X0: array-like or number, optional
134137
Initial condition (default = 0)
135138
@@ -182,9 +185,10 @@ def initial(sys, T=None, X0=0., input=None, output=None, return_x=False):
182185
sys: StateSpace, or TransferFunction
183186
LTI system to simulate
184187
185-
T: array-like object, optional
186-
Time vector (argument is autocomputed if not given)
187-
188+
T: array-like or number, optional
189+
Time vector, or simulation time duration if a number (time vector is
190+
autocomputed if not given)
191+
188192
X0: array-like object or number, optional
189193
Initial condition (default = 0)
190194
@@ -245,9 +249,8 @@ def lsim(sys, U=0., T=None, X0=0.):
245249
If `U` is ``None`` or ``0``, a special algorithm is used. This special
246250
algorithm is faster than the general algorithm, which is used otherwise.
247251
248-
T: array-like
249-
Time steps at which the input is defined, numbers must be (strictly
250-
monotonic) increasing.
252+
T: array-like, optional for discrete LTI `sys`
253+
Time steps at which the input is defined; values must be evenly spaced.
251254
252255
X0: array-like or number, optional
253256
Initial condition (default = 0).

control/rlocus.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
import numpy as np
5151
import matplotlib
5252
import matplotlib.pyplot as plt
53-
from scipy import array, poly1d, row_stack, zeros_like, real, imag
53+
from numpy import array, poly1d, row_stack, zeros_like, real, imag
5454
import scipy.signal # signal processing toolbox
5555
import pylab # plotting routines
5656
from .xferfcn import _convert_to_transfer_function

control/sisotool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def _SisotoolUpdate(sys,fig,K,bode_plot_params,tvect=None):
136136
# Generate the step response and plot it
137137
sys_closed = (K*sys).feedback(1)
138138
if tvect is None:
139-
tvect, yout = step_response(sys_closed)
139+
tvect, yout = step_response(sys_closed, T_num=100)
140140
else:
141141
tvect, yout = step_response(sys_closed,tvect)
142142
ax_step.plot(tvect, yout)

control/tests/sisotool_test.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ def test_sisotool(self):
3333

3434
# Check the step response before moving the point
3535
step_response_original = np.array(
36-
[0., 0.02233651, 0.13118374, 0.33078542, 0.5907113, 0.87041549,
37-
1.13038536, 1.33851053, 1.47374666, 1.52757114])
38-
assert_array_almost_equal(ax_step.lines[0].get_data()[1][:10],
39-
step_response_original, 4)
36+
[0., 0.0217, 0.1281, 0.3237, 0.5797, 0.8566, 1.116,
37+
1.3261, 1.4659, 1.526])
38+
assert_array_almost_equal(
39+
ax_step.lines[0].get_data()[1][:10], step_response_original, 4)
4040

4141
bode_plot_params = {
4242
'omega': None,
@@ -78,10 +78,10 @@ def test_sisotool(self):
7878

7979
# Check if the step response has changed
8080
step_response_moved = np.array(
81-
[0., 0.02458187, 0.16529784, 0.46602716, 0.91012035, 1.43364313,
82-
1.93996334, 2.3190105, 2.47041552, 2.32724853])
83-
assert_array_almost_equal(ax_step.lines[0].get_data()[1][:10],
84-
step_response_moved, 4)
81+
[0., 0.0239, 0.161 , 0.4547, 0.8903, 1.407,
82+
1.9121, 2.2989, 2.4686, 2.353])
83+
assert_array_almost_equal(
84+
ax_step.lines[0].get_data()[1][:10], step_response_moved, 4)
8585

8686

8787
if __name__ == "__main__":

control/tests/timeresp_test.py

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import unittest
1212
import numpy as np
1313
from control.timeresp import *
14+
from control.timeresp import _ideal_tfinal_and_dt, _default_time_vector
1415
from control.statesp import *
1516
from control.xferfcn import TransferFunction, _convert_to_transfer_function
1617
from control.dtime import c2d
@@ -94,6 +95,7 @@ def test_step_response(self):
9495
np.testing.assert_array_equal(Tc.shape, Td.shape)
9596
np.testing.assert_array_equal(youtc.shape, youtd.shape)
9697

98+
9799
# Recreate issue #374 ("Bug in step_response()")
98100
def test_step_nostates(self):
99101
# Continuous time, constant system
@@ -346,10 +348,75 @@ def test_step_robustness(self):
346348
sys2 = TransferFunction(num, den2)
347349

348350
# Compute step response from input 1 to output 1, 2
349-
t1, y1 = step_response(sys1, input=0)
350-
t2, y2 = step_response(sys2, input=0)
351+
t1, y1 = step_response(sys1, input=0, T_num=100)
352+
t2, y2 = step_response(sys2, input=0, T_num=100)
351353
np.testing.assert_array_almost_equal(y1, y2)
352354

355+
def test_auto_generated_time_vector(self):
356+
# confirm a TF with a pole at p simulates for 7.0/p seconds
357+
p = 0.5
358+
np.testing.assert_array_almost_equal(
359+
_ideal_tfinal_and_dt(TransferFunction(1, [1, .5]))[0],
360+
(7/p))
361+
np.testing.assert_array_almost_equal(
362+
_ideal_tfinal_and_dt(TransferFunction(1, [1, .5]).sample(.1))[0],
363+
(7/p))
364+
# confirm a TF with poles at 0 and p simulates for 7.0/p seconds
365+
np.testing.assert_array_almost_equal(
366+
_ideal_tfinal_and_dt(TransferFunction(1, [1, .5, 0]))[0],
367+
(7/p))
368+
# confirm a TF with a natural frequency of wn rad/s gets a
369+
# dt of 1/(7.0*wn)
370+
wn = 10
371+
np.testing.assert_array_almost_equal(
372+
_ideal_tfinal_and_dt(TransferFunction(1, [1, 0, wn**2]))[1],
373+
1/(7.0*wn))
374+
zeta = .1
375+
np.testing.assert_array_almost_equal(
376+
_ideal_tfinal_and_dt(TransferFunction(1, [1, 2*zeta*wn, wn**2]))[1],
377+
1/(7.0*wn))
378+
# but a smapled one keeps its dt
379+
np.testing.assert_array_almost_equal(
380+
_ideal_tfinal_and_dt(TransferFunction(1, [1, 2*zeta*wn, wn**2]).sample(.1))[1],
381+
.1)
382+
np.testing.assert_array_almost_equal(
383+
np.diff(initial_response(TransferFunction(1, [1, 2*zeta*wn, wn**2]).sample(.1))[0][0:2]),
384+
.1)
385+
np.testing.assert_array_almost_equal(
386+
_ideal_tfinal_and_dt(TransferFunction(1, [1, 2*zeta*wn, wn**2]))[1],
387+
1/(7.0*wn))
388+
# TF with fast oscillations simulates only 5000 time steps even with long tfinal
389+
self.assertEqual(5000,
390+
len(_default_time_vector(TransferFunction(1, [1, 0, wn**2]),tfinal=100)))
391+
# and simulates for 7.0/dt time steps
392+
self.assertEqual(
393+
len(_default_time_vector(TransferFunction(1, [1, 0, wn**2]))),
394+
int(7.0/(1/(7.0*wn))))
395+
396+
sys = TransferFunction(1, [1, .5, 0])
397+
sysdt = TransferFunction(1, [1, .5, 0], .1)
398+
# test impose number of time steps
399+
self.assertEqual(10, len(step_response(sys, T_num=10)[0]))
400+
self.assertEqual(10, len(step_response(sysdt, T_num=10)[0]))
401+
# test impose final time
402+
np.testing.assert_array_almost_equal(
403+
100,
404+
step_response(sys, 100)[0][-1],
405+
decimal=.5)
406+
np.testing.assert_array_almost_equal(
407+
100,
408+
step_response(sysdt, 100)[0][-1],
409+
decimal=.5)
410+
np.testing.assert_array_almost_equal(
411+
100,
412+
impulse_response(sys, 100)[0][-1],
413+
decimal=.5)
414+
np.testing.assert_array_almost_equal(
415+
100,
416+
initial_response(sys, 100)[0][-1],
417+
decimal=.5)
418+
419+
353420
def test_time_vector(self):
354421
"Unit test: https://github.com/python-control/python-control/issues/239"
355422
# Discrete time simulations with specified time vectors

0 commit comments

Comments
 (0)