diff --git a/control/freqplot.py b/control/freqplot.py index 475467147..35a5ee503 100644 --- a/control/freqplot.py +++ b/control/freqplot.py @@ -1312,6 +1312,8 @@ def nyquist_response( # Use existing dictionary, to keep track of processed keywords _kwargs |= kwargs + indent_direction_arg = 'indent_direction' in _kwargs + # Get values for params omega_num_given = omega_num is not None omega_num = config._get_param('freqplot', 'number_of_samples', omega_num) @@ -1469,8 +1471,21 @@ def nyquist_response( - (s - p).real # Figure out which way to offset the contour point - if p.real < 0 or (p.real == 0 and - indent_direction == 'right'): + if indent_direction_arg: + if indent_direction == 'right': + # Indent to the right + splane_contour[i] += offset + + elif indent_direction == 'left': + # Indent to the left + splane_contour[i] -= offset + + else: + raise ValueError( + "unknown value for indent_direction") + + elif p.real < 0 or (p.real == 0 and + indent_direction == 'right'): # Indent to the right splane_contour[i] += offset diff --git a/control/tests/nyquist_test.py b/control/tests/nyquist_test.py index 243a291d2..8f42e7430 100644 --- a/control/tests/nyquist_test.py +++ b/control/tests/nyquist_test.py @@ -372,6 +372,29 @@ def test_nyquist_indent_im(): assert _Z(sys) == response.count + _P(sys) +def test_nyquist_indent_near_imaginary_axis(): + """Test indent direction for poles near the imaginary axis.""" + sys = ct.tf([1, 11, 10], [0.01, 1, 0.01, 1]) + omega = np.linspace(0, 2, 21) + + _, contour_default = ct.nyquist_response( + sys, omega, indent_radius=0.1, return_contour=True, + warn_encirclements=False) + _, contour_right = ct.nyquist_response( + sys, omega, indent_radius=0.1, indent_direction='right', + return_contour=True, warn_encirclements=False) + _, contour_left = ct.nyquist_response( + sys, omega, indent_radius=0.1, indent_direction='left', + return_contour=True, warn_encirclements=False) + + # The pole near +1j has a small positive real part, so the default + # behavior indents to the left. Explicit directions override this. + pole_index = np.argmin(np.abs(contour_default.imag - 1)) + assert contour_default[pole_index].real < 0 + assert contour_right[pole_index].real > 0 + assert contour_left[pole_index].real < 0 + + def test_nyquist_exceptions(): # MIMO not implemented sys = ct.rss(2, 2, 2)