Skip to content

Commit 8821978

Browse files
committed
2 fixes. 1st fix: the m_nr branch which is meant to handle negative reals takes the -1 root and uses the p_nr case causes t_emp to become infinite due to ln|p|=0 causing a division by zero and a RuntimeWarning. FIX exclude |p|=1 so they use the m_w case which uses cycle counting formula which is appropriate for unit circle poles.
2nd fix: The m_int case used p.real -1 <sqrt_eps instead of np.abs (p.real-1)<sqrt_eps. Without abs any pole with Re(p) < 1 were matched rather than just poles near +1 causing stable poles like 0.95 to be discarded as discrete integrators. I updated the corresponding test to have the expected value to match the correct result. Fixes #1204.
1 parent 146ccee commit 8821978

2 files changed

Lines changed: 14 additions & 2 deletions

File tree

control/tests/timeresp_test.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
import numpy as np
77
import pytest
88

9+
910
import control as ct
1011
from control import StateSpace, TransferFunction, c2d, isctime, ss2tf, tf2ss
1112
from control.exception import pandas_check
1213
from control.timeresp import _default_time_vector, _ideal_tfinal_and_dt, \
1314
forced_response, impulse_response, initial_response, step_info, \
1415
step_response
1516

17+
import warnings
18+
1619

1720
class TSys:
1821
"""Struct of test system"""
@@ -817,7 +820,7 @@ def test_step_robustness(self):
817820

818821
@pytest.mark.parametrize(
819822
"tfsys, tfinal",
820-
[(TransferFunction(1, [1, .5]), 13.81551), # pole at 0.5
823+
[(TransferFunction(1, [1, .5]), 25), # pole at 0.5
821824
(TransferFunction(1, [1, .5]).sample(.1), 25), # discrete pole at 0.5
822825
(TransferFunction(1, [1, .5, 0]), 25)]) # poles at 0.5 and 0
823826
def test_auto_generated_time_vector_tfinal(self, tfsys, tfinal):
@@ -826,6 +829,14 @@ def test_auto_generated_time_vector_tfinal(self, tfsys, tfinal):
826829
np.testing.assert_allclose(ideal_tfinal, tfinal, rtol=1e-4)
827830
T = _default_time_vector(tfsys)
828831
np.testing.assert_allclose(T[-1], tfinal, atol=0.5*ideal_dt)
832+
833+
def test_discrete_time_negative_one_settling(self):
834+
#system with -1 pole
835+
TF = TransferFunction([1,3,0],[1,3,2], dt =True)
836+
with warnings.catch_warnings():
837+
warnings.simplefilter("error")
838+
impulse_response(TF)
839+
829840

830841
@pytest.mark.parametrize("wn, zeta", [(10, 0), (100, 0), (100, .1)])
831842
def test_auto_generated_time_vector_dt_cont1(self, wn, zeta):

control/timeresp.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2172,12 +2172,13 @@ def _ideal_tfinal_and_dt(sys, is_step=True):
21722172
m_z = np.abs(p) < sqrt_eps
21732173
p = p[~m_z]
21742174
# Negative reals- treated as oscillatory mode
2175-
m_nr = (p.real < 0) & (np.abs(p.imag) < sqrt_eps)
2175+
m_nr = (p.real < 0) & (np.abs(p.imag) < sqrt_eps) & (np.abs(p.real+1)>sqrt_eps)
21762176
p_nr, p = p[m_nr], p[~m_nr]
21772177
if p_nr.size > 0:
21782178
t_emp = np.max(log_decay_percent / np.abs((np.log(p_nr)/dt).real))
21792179
tfinal = max(tfinal, t_emp)
21802180
# discrete integrators
2181+
21812182
m_int = (p.real - 1 < sqrt_eps) & (np.abs(p.imag) < sqrt_eps)
21822183
p_int, p = p[m_int], p[~m_int]
21832184
# pure oscillatory modes

0 commit comments

Comments
 (0)