From 813c93799197d3e0e4ab37dfadf0844fc8b569d5 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Fri, 12 Jul 2024 20:32:06 -0700 Subject: [PATCH] fix step_info settling time calculation for constant signals --- control/tests/timeresp_test.py | 51 +++++++++++++++++++++++++++++++--- control/timeresp.py | 10 ++++--- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/control/tests/timeresp_test.py b/control/tests/timeresp_test.py index 73032c0a8..e2d93be0e 100644 --- a/control/tests/timeresp_test.py +++ b/control/tests/timeresp_test.py @@ -1,6 +1,7 @@ """timeresp_test.py - test time response functions""" from copy import copy +from math import isclose import numpy as np import pytest @@ -8,11 +9,11 @@ import control as ct from control import StateSpace, TransferFunction, c2d, isctime, ss2tf, tf2ss -from control.exception import slycot_check, pandas_check +from control.exception import pandas_check, slycot_check from control.tests.conftest import slycotonly -from control.timeresp import (_default_time_vector, _ideal_tfinal_and_dt, - forced_response, impulse_response, - initial_response, step_info, step_response) +from control.timeresp import _default_time_vector, _ideal_tfinal_and_dt, \ + forced_response, impulse_response, initial_response, step_info, \ + step_response class TSys: @@ -1275,3 +1276,45 @@ def test_no_pandas(): # Convert to pandas with pytest.raises(ImportError, match="pandas"): df = resp.to_pandas() + + +# https://github.com/python-control/python-control/issues/1014 +def test_step_info_nonstep(): + # Pass a constant input + timepts = np.linspace(0, 10, endpoint=False) + y_const = np.ones_like(timepts) + + # Constant value of 1 + step_info = ct.step_info(y_const, timepts) + assert step_info['RiseTime'] == 0 + assert step_info['SettlingTime'] == 0 + assert step_info['SettlingMin'] == 1 + assert step_info['SettlingMax'] == 1 + assert step_info['Overshoot'] == 0 + assert step_info['Undershoot'] == 0 + assert step_info['Peak'] == 1 + assert step_info['PeakTime'] == 0 + assert step_info['SteadyStateValue'] == 1 + + # Constant value of -1 + step_info = ct.step_info(-y_const, timepts) + assert step_info['RiseTime'] == 0 + assert step_info['SettlingTime'] == 0 + assert step_info['SettlingMin'] == -1 + assert step_info['SettlingMax'] == -1 + assert step_info['Overshoot'] == 0 + assert step_info['Undershoot'] == 0 + assert step_info['Peak'] == 1 + assert step_info['PeakTime'] == 0 + assert step_info['SteadyStateValue'] == -1 + + # Ramp from -1 to 1 + step_info = ct.step_info(-1 + 2 * timepts/10, timepts) + assert step_info['RiseTime'] == 3.8 + assert step_info['SettlingTime'] == 9.8 + assert isclose(step_info['SettlingMin'], 0.88) + assert isclose(step_info['SettlingMax'], 0.96) + assert step_info['Overshoot'] == 0 + assert step_info['Peak'] == 1 + assert step_info['PeakTime'] == 0 + assert isclose(step_info['SteadyStateValue'], 0.96) diff --git a/control/timeresp.py b/control/timeresp.py index 244d90c23..f844b1df4 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -1674,17 +1674,19 @@ def step_info(sysdata, T=None, T_num=None, yfinal=None, params=None, if not np.isnan(InfValue) and not np.isinf(InfValue): # RiseTime - tr_lower_index = np.where( + tr_lower_index = np.nonzero( sgnInf * (yout - RiseTimeLimits[0] * InfValue) >= 0 )[0][0] - tr_upper_index = np.where( + tr_upper_index = np.nonzero( sgnInf * (yout - RiseTimeLimits[1] * InfValue) >= 0 )[0][0] rise_time = T[tr_upper_index] - T[tr_lower_index] # SettlingTime - settled = np.where( - np.abs(yout/InfValue-1) >= SettlingTimeThreshold)[0][-1]+1 + outside_threshold = np.nonzero( + np.abs(yout/InfValue - 1) >= SettlingTimeThreshold)[0] + settled = 0 if outside_threshold.size == 0 \ + else outside_threshold[-1] + 1 # MIMO systems can have unsettled channels without infinite # InfValue if settled < len(T):