Skip to content

Commit ff7d827

Browse files
murrayrmbnavigator
authored andcommitted
fix rlocus timeout due to inefficient _default_wn calculation
1 parent 73f65df commit ff7d827

File tree

2 files changed

+47
-10
lines changed

2 files changed

+47
-10
lines changed

control/rlocus.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -646,8 +646,9 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
646646
else:
647647
ax = fig.axes[1]
648648

649-
# Get locator function for x-axis tick marks
649+
# Get locator function for x-axis, y-axis tick marks
650650
xlocator = ax.get_xaxis().get_major_locator()
651+
ylocator = ax.get_yaxis().get_major_locator()
651652

652653
# Decide on the location for the labels (?)
653654
ylim = ax.get_ylim()
@@ -690,7 +691,7 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
690691
# omega-constant lines
691692
angles = np.linspace(-90, 90, 20) * np.pi/180
692693
if wn is None:
693-
wn = _default_wn(xlocator(), ylim)
694+
wn = _default_wn(xlocator(), ylocator())
694695

695696
for om in wn:
696697
if om < 0:
@@ -746,7 +747,7 @@ def _default_zetas(xlim, ylim):
746747
return zeta.tolist()
747748

748749

749-
def _default_wn(xloc, ylim):
750+
def _default_wn(xloc, yloc, max_lines=7):
750751
"""Return default wn for root locus plot
751752
752753
This function computes a list of natural frequencies based on the grid
@@ -758,23 +759,30 @@ def _default_wn(xloc, ylim):
758759
List of x-axis tick values
759760
ylim : array_like
760761
List of y-axis limits [min, max]
762+
max_lines : int, optional
763+
Maximum number of frequencies to generate (default = 7)
761764
762765
Returns
763766
-------
764767
wn : list
765768
List of default natural frequencies for the plot
766769
767770
"""
771+
sep = xloc[1]-xloc[0] # separation between x-ticks
772+
773+
# Decide whether to use the x or y axis for determining wn
774+
if yloc[-1] / sep > max_lines*10:
775+
# y-axis scale >> x-axis scale
776+
wn = yloc # one frequency per y-axis tick mark
777+
else:
778+
wn = xloc # one frequency per x-axis tick mark
768779

769-
wn = xloc # one frequency per x-axis tick mark
770-
sep = xloc[1]-xloc[0] # separation between ticks
771-
772-
# Insert additional frequencies to span the y-axis
773-
while np.abs(wn[0]) < ylim[1]:
774-
wn = np.insert(wn, 0, wn[0]-sep)
780+
# Insert additional frequencies to span the y-axis
781+
while np.abs(wn[0]) < yloc[-1]:
782+
wn = np.insert(wn, 0, wn[0]-sep)
775783

776784
# If there are too many values, cut them in half
777-
while len(wn) > 7:
785+
while len(wn) > max_lines:
778786
wn = wn[0:-1:2]
779787

780788
return wn

control/tests/rlocus_test.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from numpy.testing import assert_array_almost_equal
99
import pytest
1010

11+
import control as ct
1112
from control.rlocus import root_locus, _RLClickDispatcher
1213
from control.xferfcn import TransferFunction
1314
from control.statesp import StateSpace
@@ -74,3 +75,31 @@ def test_root_locus_zoom(self):
7475

7576
assert_array_almost_equal(zoom_x, zoom_x_valid)
7677
assert_array_almost_equal(zoom_y, zoom_y_valid)
78+
79+
def test_rlocus_default_wn(self):
80+
"""Check that default wn calculation works properly"""
81+
#
82+
# System that triggers use of y-axis as basis for wn (for coverage)
83+
#
84+
# This system generates a root locus plot that used to cause the
85+
# creation (and subsequent deletion) of a large number of natural
86+
# frequency contours within the `_default_wn` function in `rlocus.py`.
87+
# This unit test makes sure that is fixed by generating a test case
88+
# that will take a long time to do the calculation (minutes).
89+
#
90+
import scipy as sp
91+
import signal
92+
93+
# Define a system that exhibits this behavior
94+
sys = ct.tf(*sp.signal.zpk2tf(
95+
[-1e-2, 1-1e7j, 1+1e7j], [0, -1e7j, 1e7j], 1))
96+
97+
# Set up a timer to catch execution time
98+
def signal_handler(signum, frame):
99+
raise Exception("rlocus took too long to complete")
100+
signal.signal(signal.SIGALRM, signal_handler)
101+
102+
# Run the command and reset the alarm
103+
signal.alarm(2) # 2 second timeout
104+
ct.root_locus(sys)
105+
signal.alarm(0) # reset the alarm

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy