Skip to content

Commit 23b3e8c

Browse files
committed
new indent code that adds a consistent number of points at every indent if omega_range_given is false
1 parent 372efb4 commit 23b3e8c

File tree

2 files changed

+60
-37
lines changed

2 files changed

+60
-37
lines changed

control/freqplot.py

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ def gen_zero_centered_series(val_min, val_max, period):
522522
'nyquist.mirror_style': '--',
523523
'nyquist.arrows': 2,
524524
'nyquist.arrow_size': 8,
525-
'nyquist.indent_radius': 1e-2,
525+
'nyquist.indent_radius': 1e-6,
526526
'nyquist.maximum_magnitude': 5,
527527
'nyquist.indent_direction': 'right',
528528
}
@@ -552,7 +552,9 @@ def nyquist_plot(syslist, omega=None, plot=True, omega_limits=None,
552552
If True, plot magnitude
553553
554554
omega : array_like
555-
Set of frequencies to be evaluated, in rad/sec.
555+
Set of frequencies to be evaluated, in rad/sec. Remark: if omega is
556+
specified, you may need to specify specify a larger `indent_radius`
557+
than the default to get reasonable contours.
556558
557559
omega_limits : array_like of two values
558560
Limits to the range of frequencies. Ignored if omega is provided, and
@@ -725,7 +727,7 @@ def nyquist_plot(syslist, omega=None, plot=True, omega_limits=None,
725727
# do indentations in s-plane where it is more convenient
726728
splane_contour = 1j * omega_sys
727729

728-
# Bend the contour around any poles on/near the imaginary axis
730+
# indent the contour around any poles on/near the imaginary axis
729731
if isinstance(sys, (StateSpace, TransferFunction)) \
730732
and indent_direction != 'none':
731733
if sys.isctime():
@@ -738,30 +740,49 @@ def nyquist_plot(syslist, omega=None, plot=True, omega_limits=None,
738740
zplane_poles = zplane_poles[~np.isclose(abs(zplane_poles), 0.)]
739741
splane_poles = np.log(zplane_poles)/sys.dt
740742

741-
if splane_contour[1].imag > indent_radius \
742-
and np.any(np.isclose(abs(splane_poles), 0)) \
743-
and not omega_range_given:
744-
# add some points for quarter circle around poles at origin
745-
splane_contour = np.concatenate(
746-
(1j * np.linspace(0., indent_radius, 50),
747-
splane_contour[1:]))
748-
for i, s in enumerate(splane_contour):
749-
# Find the nearest pole
750-
p = splane_poles[(np.abs(splane_poles - s)).argmin()]
751-
# See if we need to indent around it
752-
if abs(s - p) < indent_radius:
753-
if p.real < 0 or (np.isclose(p.real, 0) \
754-
and indent_direction == 'right'):
755-
# Indent to the right
756-
splane_contour[i] += \
757-
np.sqrt(indent_radius ** 2 - (s-p).imag ** 2)
758-
elif p.real > 0 or (np.isclose(p.real, 0) \
759-
and indent_direction == 'left'):
760-
# Indent to the left
761-
splane_contour[i] -= \
762-
np.sqrt(indent_radius ** 2 - (s-p).imag ** 2)
763-
else:
764-
ValueError("unknown value for indent_direction")
743+
if omega_range_given:
744+
# indent given contour
745+
for i, s in enumerate(splane_contour):
746+
# Find the nearest pole
747+
p = splane_poles[(np.abs(splane_poles - s)).argmin()]
748+
# See if we need to indent around it
749+
if abs(s - p) < indent_radius:
750+
if p.real < 0 or (np.isclose(p.real, 0) \
751+
and indent_direction == 'right'):
752+
# Indent to the right
753+
splane_contour[i] += \
754+
np.sqrt(indent_radius ** 2 - (s-p).imag ** 2)
755+
elif p.real > 0 or (np.isclose(p.real, 0) \
756+
and indent_direction == 'left'):
757+
# Indent to the left
758+
splane_contour[i] -= \
759+
np.sqrt(indent_radius ** 2 - (s-p).imag ** 2)
760+
else:
761+
ValueError("unknown value for indent_direction")
762+
else:
763+
# find poles that are near imag axis and replace contour
764+
# near there with indented contour
765+
if indent_direction == 'right':
766+
indent = indent_radius * \
767+
np.exp(1j * np.linspace(-np.pi/2, np.pi/2, 51))
768+
elif indent_direction == 'left':
769+
indent = -indent_radius * \
770+
np.exp(1j * np.linspace(np.pi/2, -np.pi/2, 51))
771+
else:
772+
ValueError("unknown value for indent_direction")
773+
for p in splane_poles[np.isclose(splane_poles.real, 0) \
774+
& (splane_poles.imag >= 0)]:
775+
start = np.searchsorted(
776+
splane_contour.imag, p.imag - indent_radius)
777+
end = np.searchsorted(
778+
splane_contour.imag, p.imag + indent_radius)
779+
# only keep indent parts that are > 0 and within contour
780+
indent_mask = ((indent + p).imag >= 0) \
781+
& ((indent + p).imag <= splane_contour[-1].imag)
782+
splane_contour = np.concatenate((
783+
splane_contour[:start],
784+
indent[indent_mask] + p,
785+
splane_contour[end:]))
765786

766787
# map contour to z-plane if necessary
767788
if sys.isctime():
@@ -816,7 +837,7 @@ def nyquist_plot(syslist, omega=None, plot=True, omega_limits=None,
816837
_add_arrows_to_line2D(
817838
ax, p[0], arrow_pos, arrowstyle=arrow_style, dir=1)
818839
# plot large magnitude contour
819-
p = plt.plot(x_lg, y_lg, ':', color='red', *args, **kwargs)
840+
p = plt.plot(x_lg, y_lg, color='red', *args, **kwargs)
820841
_add_arrows_to_line2D(
821842
ax, p[0], arrow_pos, arrowstyle=arrow_style, dir=1)
822843

@@ -825,7 +846,8 @@ def nyquist_plot(syslist, omega=None, plot=True, omega_limits=None,
825846
p = plt.plot(x, -y, mirror_style, color=c, *args, **kwargs)
826847
_add_arrows_to_line2D(
827848
ax, p[0], arrow_pos, arrowstyle=arrow_style, dir=-1)
828-
p = plt.plot(x_lg, -y_lg, ':', color='red', *args, **kwargs)
849+
p = plt.plot(x_lg, -y_lg, mirror_style,
850+
color='red', *args, **kwargs)
829851
_add_arrows_to_line2D(
830852
ax, p[0], arrow_pos, arrowstyle=arrow_style, dir=-1)
831853

control/tests/nyquist_test.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ def test_nyquist_basic():
6969

7070
# Make sure that we can turn off frequency modification
7171
count, contour_indented = ct.nyquist_plot(
72-
sys, np.linspace(1e-4, 1e2, 100), return_contour=True)
72+
sys, np.linspace(1e-4, 1e2, 100), indent_radius=0.01,
73+
return_contour=True)
7374
assert not all(contour_indented.real == 0)
7475
count, contour = ct.nyquist_plot(
7576
sys, np.linspace(1e-4, 1e2, 100), return_contour=True,
@@ -87,14 +88,17 @@ def test_nyquist_basic():
8788
assert _Z(sys) == count + _P(sys)
8889

8990
# Nyquist plot with poles on imaginary axis, omega specified
91+
# when specifying omega, usually need to specify larger indent radius
9092
sys = ct.tf([1], [1, 3, 2]) * ct.tf([1], [1, 0, 1])
91-
count = ct.nyquist_plot(sys, np.linspace(1e-3, 1e1, 1000))
93+
count = ct.nyquist_plot(sys, np.linspace(1e-3, 1e1, 1000),
94+
indent_radius=0.1)
9295
assert _Z(sys) == count + _P(sys)
9396

9497
# Nyquist plot with poles on imaginary axis, omega specified, with contour
9598
sys = ct.tf([1], [1, 3, 2]) * ct.tf([1], [1, 0, 1])
9699
count, contour = ct.nyquist_plot(
97-
sys, np.linspace(1e-3, 1e1, 1000), return_contour=True)
100+
sys, np.linspace(1e-3, 1e1, 1000), indent_radius=0.1,
101+
return_contour=True)
98102
assert _Z(sys) == count + _P(sys)
99103

100104
# Nyquist plot with poles on imaginary axis, return contour
@@ -193,13 +197,10 @@ def test_nyquist_indent():
193197
plt.title("Pole at origin; indent_radius=default")
194198
assert _Z(sys) == count + _P(sys)
195199

196-
# first value of default omega vector was 0.1, replaced by 0. for contour
197-
# indent_radius is larger than 0.1 -> no extra quater circle around origin
200+
# confirm that first element of indent is properly indented
198201
count, contour = ct.nyquist_plot(sys, plot=False, indent_radius=.1007,
199202
return_contour=True)
200203
np.testing.assert_allclose(contour[0], .1007+0.j)
201-
# second value of omega_vector is larger than indent_radius: not indented
202-
assert np.all(contour.real[2:] == 0.)
203204

204205
plt.figure();
205206
count, contour = ct.nyquist_plot(sys, indent_radius=0.01,
@@ -208,7 +209,7 @@ def test_nyquist_indent():
208209
assert _Z(sys) == count + _P(sys)
209210
# indent radius is smaller than the start of the default omega vector
210211
# check that a quarter circle around the pole at origin has been added.
211-
np.testing.assert_allclose(contour[:50].real**2 + contour[:50].imag**2,
212+
np.testing.assert_allclose(contour[:25].real**2 + contour[:25].imag**2,
212213
0.01**2)
213214

214215
plt.figure();

0 commit comments

Comments
 (0)