From 920649b393dc739f6a9dd3302946bf26e279f4bd Mon Sep 17 00:00:00 2001 From: bnavigator Date: Tue, 18 Aug 2020 13:08:35 +0200 Subject: [PATCH] no log(0) in automatic timevector calculation --- control/timeresp.py | 83 ++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/control/timeresp.py b/control/timeresp.py index b472cb2fd..fa4ced2bd 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -61,7 +61,7 @@ Initial Author: Eike Welk Date: 12 May 2011 -Modified: Sawyer B. Fuller (minster@uw.edu) to add discrete-time +Modified: Sawyer B. Fuller (minster@uw.edu) to add discrete-time capability and better automatic time vector creation Date: June 2020 @@ -463,14 +463,14 @@ def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None, LTI system to simulate T: array-like or number, optional - Time vector, or simulation time duration if a number. If T is not - provided, an attempt is made to create it automatically from the - dynamics of sys. If sys is continuous-time, the time increment dt - is chosen small enough to show the fastest mode, and the simulation - time period tfinal long enough to show the slowest mode, excluding + Time vector, or simulation time duration if a number. If T is not + provided, an attempt is made to create it automatically from the + dynamics of sys. If sys is continuous-time, the time increment dt + is chosen small enough to show the fastest mode, and the simulation + time period tfinal long enough to show the slowest mode, excluding poles at the origin. If this results in too many time steps (>5000), - dt is reduced. If sys is discrete-time, only tfinal is computed, and - tfinal is reduced if it requires too many simulation steps. + dt is reduced. If sys is discrete-time, only tfinal is computed, and + tfinal is reduced if it requires too many simulation steps. X0: array-like or number, optional Initial condition (default = 0) @@ -483,10 +483,10 @@ def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None, output: int Index of the output that will be used in this simulation. Set to None to not trim outputs - + T_num: number, optional - Number of time steps to use in simulation if T is not provided as an - array (autocomputed if not given); ignored if sys is discrete-time. + Number of time steps to use in simulation if T is not provided as an + array (autocomputed if not given); ignored if sys is discrete-time. transpose: bool If True, transpose all input and output arrays (for backward @@ -550,12 +550,12 @@ def step_info(sys, T=None, T_num=None, SettlingTimeThreshold=0.02, LTI system to simulate T: array-like or number, optional - Time vector, or simulation time duration if a number (time vector is + Time vector, or simulation time duration if a number (time vector is autocomputed if not given, see :func:`step_response` for more detail) T_num: number, optional - Number of time steps to use in simulation if T is not provided as an - array (autocomputed if not given); ignored if sys is discrete-time. + Number of time steps to use in simulation if T is not provided as an + array (autocomputed if not given); ignored if sys is discrete-time. SettlingTimeThreshold: float value, optional Defines the error to compute settling time (default = 0.02) @@ -588,7 +588,7 @@ def step_info(sys, T=None, T_num=None, SettlingTimeThreshold=0.02, sys = _get_ss_simo(sys) if T is None or np.asarray(T).size == 1: T = _default_time_vector(sys, N=T_num, tfinal=T) - + T, yout = step_response(sys, T) # Steady state value @@ -640,7 +640,7 @@ def initial_response(sys, T=None, X0=0., input=0, output=None, T_num=None, LTI system to simulate T: array-like or number, optional - Time vector, or simulation time duration if a number (time vector is + Time vector, or simulation time duration if a number (time vector is autocomputed if not given; see :func:`step_response` for more detail) X0: array-like or number, optional @@ -655,10 +655,10 @@ def initial_response(sys, T=None, X0=0., input=0, output=None, T_num=None, output: int Index of the output that will be used in this simulation. Set to None to not trim outputs - + T_num: number, optional - Number of time steps to use in simulation if T is not provided as an - array (autocomputed if not given); ignored if sys is discrete-time. + Number of time steps to use in simulation if T is not provided as an + array (autocomputed if not given); ignored if sys is discrete-time. transpose: bool If True, transpose all input and output arrays (for backward @@ -730,7 +730,7 @@ def impulse_response(sys, T=None, X0=0., input=0, output=None, T_num=None, LTI system to simulate T: array-like or number, optional - Time vector, or simulation time duration if a number (time vector is + Time vector, or simulation time duration if a number (time vector is autocomputed if not given; see :func:`step_response` for more detail) X0: array-like or number, optional @@ -746,8 +746,8 @@ def impulse_response(sys, T=None, X0=0., input=0, output=None, T_num=None, to not trim outputs T_num: number, optional - Number of time steps to use in simulation if T is not provided as an - array (autocomputed if not given); ignored if sys is discrete-time. + Number of time steps to use in simulation if T is not provided as an + array (autocomputed if not given); ignored if sys is discrete-time. transpose: bool If True, transpose all input and output arrays (for backward @@ -830,56 +830,55 @@ def _ideal_tfinal_and_dt(sys): constant = 7.0 tolerance = 1e-10 A = ssdata(sys)[0] - if A.shape == (0,0): + if A.shape == (0,0): # no dynamics - tfinal = constant * 1.0 + tfinal = constant * 1.0 dt = sys.dt if isdtime(sys, strict=True) else 1.0 else: poles = sp.linalg.eigvals(A) - if isdtime(sys, strict=True): - poles = np.log(poles)/sys.dt # z-poles to s-plane using s=(lnz)/dt - # calculate ideal dt if isdtime(sys, strict=True): + # z-poles to s-plane using s=(lnz)/dt, no ln(0) + poles = np.log(poles[abs(poles) > 0])/sys.dt dt = sys.dt else: fastest_natural_frequency = max(abs(poles)) dt = 1/constant / fastest_natural_frequency - + # calculate ideal tfinal poles = poles[abs(poles.real) > tolerance] # ignore poles near im axis - if poles.size == 0: + if poles.size == 0: slowest_decay_rate = 1.0 else: slowest_decay_rate = min(abs(poles.real)) - tfinal = constant / slowest_decay_rate + tfinal = constant / slowest_decay_rate return tfinal, dt -# test below: ct with pole at the origin is 7 seconds, ct with pole at .5 is 14 s long, +# test below: ct with pole at the origin is 7 seconds, ct with pole at .5 is 14 s long, def _default_time_vector(sys, N=None, tfinal=None): - """Returns a time vector suitable for observing the response of the - both the slowest poles and fastest resonant modes. if system is + """Returns a time vector suitable for observing the response of the + both the slowest poles and fastest resonant modes. if system is discrete-time, N is ignored """ - + N_max = 5000 N_min_ct = 100 N_min_dt = 7 # more common to see just a few samples in discrete-time - + ideal_tfinal, ideal_dt = _ideal_tfinal_and_dt(sys) - + if isdtime(sys, strict=True): - if tfinal is None: + if tfinal is None: # for discrete time, change from ideal_tfinal if N too large/small N = int(np.clip(ideal_tfinal/sys.dt, N_min_dt, N_max))# [N_min, N_max] tfinal = sys.dt * N - else: + else: N = int(tfinal/sys.dt) - else: - if tfinal is None: + else: + if tfinal is None: # for continuous time, simulate to ideal_tfinal but limit N tfinal = ideal_tfinal - if N is None: + if N is None: N = int(np.clip(tfinal/ideal_dt, N_min_ct, N_max)) # N<-[N_min, N_max] - + return np.linspace(0, tfinal, N, endpoint=False)