Skip to content

Commit c25c0cb

Browse files
committed
add warning if encirclements is not near an integer
1 parent aef3b06 commit c25c0cb

File tree

4 files changed

+39
-5
lines changed

4 files changed

+39
-5
lines changed

control/freqplot.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ def gen_zero_centered_series(val_min, val_max, period):
523523
'nyquist.mirror_style': ['--', ':'], # style for mirror curve
524524
'nyquist.arrows': 2, # number of arrors around curve
525525
'nyquist.arrow_size': 8, # pixel size for arrows
526+
'nyquist.encirclement_threshold': 0.05, # warning threshold
526527
'nyquist.indent_radius': 1e-4, # indentation radius
527528
'nyquist.indent_direction': 'right', # indentation direction
528529
'nyquist.indent_points': 50, # number of points to insert
@@ -611,6 +612,11 @@ def nyquist_plot(syslist, omega=None, plot=True, omega_limits=None,
611612
arrow_style : matplotlib.patches.ArrowStyle, optional
612613
Define style used for Nyquist curve arrows (overrides `arrow_size`).
613614
615+
encirclement_threshold : float, optional
616+
Define the threshold for generating a warning if the number of net
617+
encirclements is a non-integer value. Default value is 0.05 and can
618+
be set using config.defaults['nyquist.encirclement_threshold'].
619+
614620
indent_direction : str, optional
615621
For poles on the imaginary axis, set the direction of indentation to
616622
be 'right' (default), 'left', or 'none'.
@@ -717,6 +723,9 @@ def nyquist_plot(syslist, omega=None, plot=True, omega_limits=None,
717723
arrow_style = config._get_param('nyquist', 'arrow_style', kwargs, None)
718724
indent_radius = config._get_param(
719725
'nyquist', 'indent_radius', kwargs, _nyquist_defaults, pop=True)
726+
encirclement_threshold = config._get_param(
727+
'nyquist', 'encirclement_threshold', kwargs,
728+
_nyquist_defaults, pop=True)
720729
indent_direction = config._get_param(
721730
'nyquist', 'indent_direction', kwargs, _nyquist_defaults, pop=True)
722731
indent_points = config._get_param(
@@ -750,8 +759,11 @@ def _parse_linestyle(style_name, allow_false=False):
750759
if not isinstance(syslist, (list, tuple)):
751760
syslist = (syslist,)
752761

762+
# Determine the range of frequencies to use, based on args/features
753763
omega, omega_range_given = _determine_omega_vector(
754764
syslist, omega, omega_limits, omega_num)
765+
766+
# If omega was not specified explicitly, start at omega = 0
755767
if not omega_range_given:
756768
if omega_num_given:
757769
# Just reset the starting point
@@ -852,6 +864,7 @@ def _parse_linestyle(style_name, allow_false=False):
852864
first_point = 0
853865
start_freq = 0
854866

867+
# Find the frequencies after the pole frequency
855868
above_points = np.argwhere(
856869
splane_contour.imag - abs(p.imag) > indent_radius)
857870
last_point = above_points[0].item()
@@ -900,7 +913,15 @@ def _parse_linestyle(style_name, allow_false=False):
900913

901914
# Compute CW encirclements of -1 by integrating the (unwrapped) angle
902915
phase = -unwrap(np.angle(resp + 1))
903-
count = int(np.round(np.sum(np.diff(phase)) / np.pi, 0))
916+
encirclements = np.sum(np.diff(phase)) / np.pi
917+
count = int(np.round(encirclements, 0))
918+
919+
# Let the user know if the count might not make sense
920+
if abs(encirclements - count) > encirclement_threshold:
921+
warnings.warn(
922+
"number of encirclements was a non-integer value; this can"
923+
" happen is contour is not closed, possibly based on a"
924+
" frequency range that does not include zero.")
904925

905926
#
906927
# Make sure that the enciriclements match the Nyquist criterion
@@ -1534,6 +1555,7 @@ def _determine_omega_vector(syslist, omega_in, omega_limits, omega_num,
15341555
num=omega_num, endpoint=True)
15351556
else:
15361557
omega_out = np.copy(omega_in)
1558+
15371559
return omega_out, omega_range_given
15381560

15391561

control/tests/descfcn_test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def test_describing_function(fcn, amin, amax):
140140
def test_describing_function_plot():
141141
# Simple linear system with at most 1 intersection
142142
H_simple = ct.tf([1], [1, 2, 2, 1])
143-
omega = np.logspace(-1, 2, 100)
143+
omega = np.logspace(-2, 2, 100)
144144

145145
# Saturation nonlinearity
146146
F_saturation = ct.descfcn.saturation_nonlinearity(1)
@@ -160,7 +160,7 @@ def test_describing_function_plot():
160160

161161
# Multiple intersections
162162
H_multiple = H_simple * ct.tf(*ct.pade(5, 4)) * 4
163-
omega = np.logspace(-1, 3, 50)
163+
omega = np.logspace(-2, 3, 50)
164164
F_backlash = ct.descfcn.friction_backlash_nonlinearity(1)
165165
amp = np.linspace(0.6, 5, 50)
166166
xsects = ct.describing_function_plot(H_multiple, F_backlash, amp, omega)

control/tests/freqresp_test.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ def test_nyquist_basic(ss_siso):
8181
tf_siso, plot=False, return_contour=True, omega_num=20)
8282
assert len(contour) == 20
8383

84-
count, contour = nyquist_plot(
85-
tf_siso, plot=False, omega_limits=(1, 100), return_contour=True)
84+
with pytest.warns(UserWarning, match="encirclements was a non-integer"):
85+
count, contour = nyquist_plot(
86+
tf_siso, plot=False, omega_limits=(1, 100), return_contour=True)
8687
assert_allclose(contour[0], 1j)
8788
assert_allclose(contour[-1], 100j)
8889

control/tests/nyquist_test.py

+11
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,17 @@ def test_nyquist_encirclements():
219219
plt.title("Pole at the origin; encirclements = %d" % count)
220220
assert _Z(sys) == count + _P(sys)
221221

222+
# Non-integer number of encirclements
223+
plt.figure();
224+
sys = 1 / (s**2 + s + 1)
225+
with pytest.warns(UserWarning, match="encirclements was a non-integer"):
226+
count = ct.nyquist_plot(sys, omega_limits=[0.5, 1e3])
227+
with pytest.warns(None) as records:
228+
count = ct.nyquist_plot(
229+
sys, omega_limits=[0.5, 1e3], encirclement_threshold=0.2)
230+
assert len(records) == 0
231+
plt.title("Non-integer number of encirclements [%g]" % count)
232+
222233

223234
@pytest.fixture
224235
def indentsys():

0 commit comments

Comments
 (0)