Skip to content

Commit 9c26e22

Browse files
authored
Merge pull request #885 from sawyerbfuller/nyquist-z-polenum
update nyquist_plot for DT transfer functions with poles at 0 and 1
2 parents 15ed92c + d88c67c commit 9c26e22

File tree

2 files changed

+56
-39
lines changed

2 files changed

+56
-39
lines changed

control/freqplot.py

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -819,29 +819,29 @@ def _parse_linestyle(style_name, allow_false=False):
819819
splane_contour = 1j * omega_sys
820820

821821
# Bend the contour around any poles on/near the imaginary axis
822-
# TODO: smarter indent radius that depends on dcgain of system
823-
# and timebase of discrete system.
824822
if isinstance(sys, (StateSpace, TransferFunction)) \
825823
and indent_direction != 'none':
826824
if sys.isctime():
827825
splane_poles = sys.poles()
828826
splane_cl_poles = sys.feedback().poles()
829827
else:
830-
# map z-plane poles to s-plane, ignoring any at the origin
831-
# because we don't need to indent for them
828+
# map z-plane poles to s-plane. We ignore any at the origin
829+
# to avoid numerical warnings because we know we
830+
# don't need to indent for them
832831
zplane_poles = sys.poles()
833832
zplane_poles = zplane_poles[~np.isclose(abs(zplane_poles), 0.)]
834833
splane_poles = np.log(zplane_poles) / sys.dt
835834

836835
zplane_cl_poles = sys.feedback().poles()
836+
# eliminate z-plane poles at the origin to avoid warnings
837837
zplane_cl_poles = zplane_cl_poles[
838-
~np.isclose(abs(zplane_poles), 0.)]
838+
~np.isclose(abs(zplane_cl_poles), 0.)]
839839
splane_cl_poles = np.log(zplane_cl_poles) / sys.dt
840840

841841
#
842842
# Check to make sure indent radius is small enough
843843
#
844-
# If there is a closed loop pole that is near the imaginary access
844+
# If there is a closed loop pole that is near the imaginary axis
845845
# at a point that is near an open loop pole, it is possible that
846846
# indentation might skip or create an extraneous encirclement.
847847
# We check for that situation here and generate a warning if that
@@ -851,15 +851,16 @@ def _parse_linestyle(style_name, allow_false=False):
851851
# See if any closed loop poles are near the imaginary axis
852852
if abs(p_cl.real) <= indent_radius:
853853
# See if any open loop poles are close to closed loop poles
854-
p_ol = splane_poles[
855-
(np.abs(splane_poles - p_cl)).argmin()]
854+
if len(splane_poles) > 0:
855+
p_ol = splane_poles[
856+
(np.abs(splane_poles - p_cl)).argmin()]
856857

857-
if abs(p_ol - p_cl) <= indent_radius and \
858-
warn_encirclements:
859-
warnings.warn(
860-
"indented contour may miss closed loop pole; "
861-
"consider reducing indent_radius to be less than "
862-
f"{abs(p_ol - p_cl):5.2g}", stacklevel=2)
858+
if abs(p_ol - p_cl) <= indent_radius and \
859+
warn_encirclements:
860+
warnings.warn(
861+
"indented contour may miss closed loop pole; "
862+
"consider reducing indent_radius to below "
863+
f"{abs(p_ol - p_cl):5.2g}", stacklevel=2)
863864

864865
#
865866
# See if we should add some frequency points near imaginary poles
@@ -897,29 +898,30 @@ def _parse_linestyle(style_name, allow_false=False):
897898
splane_contour[last_point:]))
898899

899900
# Indent points that are too close to a pole
900-
for i, s in enumerate(splane_contour):
901-
# Find the nearest pole
902-
p = splane_poles[(np.abs(splane_poles - s)).argmin()]
903-
904-
# See if we need to indent around it
905-
if abs(s - p) < indent_radius:
906-
# Figure out how much to offset (simple trigonometry)
907-
offset = np.sqrt(indent_radius ** 2 - (s - p).imag ** 2) \
908-
- (s - p).real
909-
910-
# Figure out which way to offset the contour point
911-
if p.real < 0 or (p.real == 0 and
912-
indent_direction == 'right'):
913-
# Indent to the right
914-
splane_contour[i] += offset
915-
916-
elif p.real > 0 or (p.real == 0 and
917-
indent_direction == 'left'):
918-
# Indent to the left
919-
splane_contour[i] -= offset
901+
if len(splane_poles) > 0: # accomodate no splane poles if dtime sys
902+
for i, s in enumerate(splane_contour):
903+
# Find the nearest pole
904+
p = splane_poles[(np.abs(splane_poles - s)).argmin()]
905+
906+
# See if we need to indent around it
907+
if abs(s - p) < indent_radius:
908+
# Figure out how much to offset (simple trigonometry)
909+
offset = np.sqrt(indent_radius ** 2 - (s - p).imag ** 2) \
910+
- (s - p).real
911+
912+
# Figure out which way to offset the contour point
913+
if p.real < 0 or (p.real == 0 and
914+
indent_direction == 'right'):
915+
# Indent to the right
916+
splane_contour[i] += offset
917+
918+
elif p.real > 0 or (p.real == 0 and
919+
indent_direction == 'left'):
920+
# Indent to the left
921+
splane_contour[i] -= offset
920922

921-
else:
922-
raise ValueError("unknown value for indent_direction")
923+
else:
924+
raise ValueError("unknown value for indent_direction")
923925

924926
# change contour to z-plane if necessary
925927
if sys.isctime():

control/tests/nyquist_test.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,23 @@ def test_nyquist_legacy():
370370
def test_discrete_nyquist():
371371
# Make sure we can handle discrete time systems with negative poles
372372
sys = ct.tf(1, [1, -0.1], dt=1) * ct.tf(1, [1, 0.1], dt=1)
373-
ct.nyquist_plot(sys)
374-
373+
ct.nyquist_plot(sys, plot=False)
374+
375+
# system with a pole at the origin
376+
sys = ct.zpk([1,], [.3, 0], 1, dt=True)
377+
ct.nyquist_plot(sys, plot=False)
378+
sys = ct.zpk([1,], [0], 1, dt=True)
379+
ct.nyquist_plot(sys, plot=False)
380+
381+
# only a pole at the origin
382+
sys = ct.zpk([], [0], 2, dt=True)
383+
ct.nyquist_plot(sys, plot=False)
384+
385+
# pole at zero (pure delay)
386+
sys = ct.zpk([], [1], 1, dt=True)
387+
ct.nyquist_plot(sys, plot=False)
388+
389+
375390
if __name__ == "__main__":
376391
#
377392
# Interactive mode: generate plots for manual viewing
@@ -427,5 +442,5 @@ def test_discrete_nyquist():
427442
np.array2string(sys.poles(), precision=2, separator=','))
428443
count = ct.nyquist_plot(sys)
429444

430-
445+
431446

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