Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions control/tests/timeresp_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""timeresp_test.py - test time response functions"""

import warnings
from copy import copy
from math import isclose

Expand Down Expand Up @@ -582,6 +583,17 @@ def test_discrete_time_impulse_input(self):

np.testing.assert_array_equal(response.inputs,Uexpected)

def test_discrete_time_impulse_negative_poles_no_warning(self):
# A negative pole on the unit circle should be treated as oscillatory.
sysd = ct.TransferFunction([1, 3, 0], [1, 3, 2], dt=True)

with warnings.catch_warnings():
warnings.simplefilter("error", RuntimeWarning)
response = ct.impulse_response(sysd, 5)

np.testing.assert_allclose(response.time, np.arange(6))
assert np.all(np.isfinite(response.outputs))

@pytest.mark.parametrize("tsystem", ["siso_ss1"], indirect=True)
def test_impulse_response_warnD(self, tsystem):
"""Test warning about direct feedthrough"""
Expand Down Expand Up @@ -827,6 +839,15 @@ def test_auto_generated_time_vector_tfinal(self, tfsys, tfinal):
T = _default_time_vector(tfsys)
np.testing.assert_allclose(T[-1], tfinal, atol=0.5*ideal_dt)

@pytest.mark.parametrize(
"tfsys, tfinal",
[(TransferFunction(1, [1, .5], dt=True), 9.96578), # pole at -0.5
(TransferFunction(1, [1, 1], dt=True), 10)]) # pole at -1
def test_discrete_negative_real_pole_tfinal(self, tfsys, tfinal):
"""Confirm discrete negative real pole time horizon estimates."""
ideal_tfinal, _ = _ideal_tfinal_and_dt(tfsys)
np.testing.assert_allclose(ideal_tfinal, tfinal, rtol=1e-4)

@pytest.mark.parametrize("wn, zeta", [(10, 0), (100, 0), (100, .1)])
def test_auto_generated_time_vector_dt_cont1(self, wn, zeta):
"""Confirm a TF with a natural frequency of wn rad/s gets a
Expand Down
8 changes: 6 additions & 2 deletions control/timeresp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2172,13 +2172,17 @@ def _ideal_tfinal_and_dt(sys, is_step=True):
m_z = np.abs(p) < sqrt_eps
p = p[~m_z]
# Negative reals- treated as oscillatory mode
m_nr = (p.real < 0) & (np.abs(p.imag) < sqrt_eps)
m_nr = (
(p.real < 0) & (np.abs(p.imag) < sqrt_eps) &
(np.abs(np.abs(p) - 1) >= sqrt_eps))
p_nr, p = p[m_nr], p[~m_nr]
if p_nr.size > 0:
t_emp = np.max(log_decay_percent / np.abs((np.log(p_nr)/dt).real))
tfinal = max(tfinal, t_emp)
# discrete integrators
m_int = (p.real - 1 < sqrt_eps) & (np.abs(p.imag) < sqrt_eps)
m_int = (
(p.real > 0) & (p.real - 1 < sqrt_eps) &
(np.abs(p.imag) < sqrt_eps))
p_int, p = p[m_int], p[~m_int]
# pure oscillatory modes
m_w = (np.abs(np.abs(p) - 1) < sqrt_eps)
Expand Down