8000 Merge pull request #503 from murrayrm/fix_rlocus_jupyter · python-control/python-control@8194b1d · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 8194b1d

Browse files
authored
Merge pull request #503 from murrayrm/fix_rlocus_jupyter
fix rlocus plotting problem in Jupyter notebooks + comments, PEP8
2 parents 7f5d40e + 4b88452 commit 8194b1d

File tree

1 file changed

+82
-26
lines changed

1 file changed

+82
-26
lines changed

control/rlocus.py

Lines changed: 82 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,14 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
222222
ax.set_xlabel('Real')
223223
ax.set_ylabel('Imaginary')
224224

225+
# Set up the limits for the plot
226+
# Note: need to do this before computing grid lines
227+
if xlim:
228+
ax.set_xlim(xlim)
229+
if ylim:
230+
ax.set_ylim(ylim)
231+
232+
# Draw the grid
225233
if grid and sisotool:
226234
if isdtime(sys, strict=True):
227235
zgrid(ax=ax)
@@ -236,14 +244,9 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
236244
ax.axhline(0., linestyle=':', color='k', zorder=-20)
237245
ax.axvline(0., linestyle=':', color='k', zorder=-20)
238246
if isdtime(sys, strict=True):
239-
ax.add_patch(plt.Circle((0,0), radius=1.0,
240-
linestyle=':', edgecolor='k', linewidth=1.5,
241-
fill=False, zorder=-20))
242-
243-
if xlim:
244-
ax.set_xlim(xlim)
245-
if ylim:
246-
ax.set_ylim(ylim)
247+
ax.add_patch(plt.Circle(
248+
(0, 0), radius=1.0, linestyle=':', edgecolor='k',
249+
linewidth=1.5, fill=False, zorder=-20))
247250

248251
return mymat, kvect
249252

@@ -642,16 +645,21 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
642645
ax = fig.gca()
643646
else:
644647
ax = fig.axes[1]
648+
649+
# Get locator function for x-axis tick marks
645650
xlocator = ax.get_xaxis().get_major_locator()
646651

652+
# Decide on the location for the labels (?)
647653
ylim = ax.get_ylim()
648654
ytext_pos_lim = ylim[1] - (ylim[1] - ylim[0]) * 0.03
649655
xlim = ax.get_xlim()
650656
xtext_pos_lim = xlim[0] + (xlim[1] - xlim[0]) * 0.0
651657

658+
# Create a list of damping ratios, if needed
652659
if zeta is None:
653660
zeta = _default_zetas(xlim, ylim)
654661

662+
# Figure out the angles for the different damping ratios
655663
angles = []
656664
for z in zeta:
657665
if (z >= 1e-4) and (z <= 1):
@@ -661,11 +669,8 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
661669
y_over_x = np.tan(angles)
662670

663671
# zeta-constant lines
664-
665-
index = 0
666-
667-
for yp in y_over_x:
668-
ax.plot([0, xlocator()[0]], [0, yp*xlocator()[0]], color='gray',
672+
for index, yp in enumerate(y_over_x):
673+
ax.plot([0, xlocator()[0]], [0, yp * xlocator()[0]], color='gray',
669674
linestyle='dashed', linewidth=0.5)
670675
ax.plot([0, xlocator()[0]], [0, -yp * xlocator()[0]], color='gray',
671676
linestyle='dashed', linewidth=0.5)
@@ -679,45 +684,96 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
679684
ytext_pos = ytext_pos_lim
680685
ax.annotate(an, textcoords='data', xy=[xtext_pos, ytext_pos],
681686
fontsize=8)
682-
index += 1
683687
ax.plot([0, 0], [ylim[0], ylim[1]],
684688
color='gray', linestyle='dashed', linewidth=0.5)
685689

686-
angles = np.linspace(-90, 90, 20)*np.pi/180
690+
# omega-constant lines
691+
angles = np.linspace(-90, 90, 20) * np.pi/180
687692
if wn is None:
688693
wn = _default_wn(xlocator(), ylim)
689694

690695
for om in wn:
691696
if om < 0:
692-
yp = np.sin(angles)*np.abs(om)
693-
xp = -np.cos(angles)*np.abs(om)
694-
ax.plot(xp, yp, color='gray',
695-
linestyle='dashed', linewidth=0.5)
696-
an = "%.2f" % -om
697-
ax.annotate(an, textcoords='data', xy=[om, 0], fontsize=8)
697+
# Generate the lines for natural frequency curves
698+
yp = np.sin(angles) * np.abs(om)
699+
xp = -np.cos(angles) * np.abs(om)
700+
701+
# Plot the natural frequency contours
702+
ax.plot(xp, yp, color='gray', linestyle='dashed', linewidth=0.5)
703+
704+
# Annotate the natural frequencies by listing on x-axis
705+
# Note: need to filter values for proper plotting in Jupyter
706+
if (om > xlim[0]):
707+
an = "%.2f" % -om
708+
ax.annotate(an, textcoords='data', xy=[om, 0], fontsize=8)
698709

699710

700711
def _default_zetas(xlim, ylim):
701-
"""Return default list of damping coefficients"""
702-
sep1 = -xlim[0]/4
712+
"""Return default list of damping coefficients
713+
714+
This function computes a list of damping coefficients based on the limits
715+
of the graph. A set of 4 damping coefficients are computed for the x-axis
716+
and a set of three damping coefficients are computed for the y-axis
717+
(corresponding to the normal 4:3 plot aspect ratio in `matplotlib`?).
718+
719+
Parameters
720+
----------
721+
xlim : array_like
722+
List of x-axis limits [min, max]
723+
ylim : array_like
724+
List of y-axis limits [min, max]
725+
726+
Returns
727+
-------
728+
zeta : list
729+
List of default damping coefficients for the plot
730+
731+
"""
732+
# Damping coefficient lines that intersect the x-axis
733+
sep1 = -xlim[0] / 4
703734
ang1 = [np.arctan((sep1*i)/ylim[1]) for i in np.arange(1, 4, 1)]
735+
736+
# Damping coefficient lines that intersection the y-axis
704737
sep2 = ylim[1] / 3
705738
ang2 = [np.arctan(-xlim[0]/(ylim[1]-sep2*i)) for i in np.arange(1, 3, 1)]
706739

740+
# Put the lines together and add one at -pi/2 (negative real axis)
707741
angles = np.concatenate((ang1, ang2))
708742
angles = np.insert(angles, len(angles), np.pi/2)
743+
744+
# Return the damping coefficients corresponding to these angles
709745
zeta = np.sin(angles)
710746
return zeta.tolist()
711747

712748

713749
def _default_wn(xloc, ylim):
714-
"""Return default wn for root locus plot"""
750+
"""Return default wn for root locus plot
751+
752+
This function computes a list of natural frequencies based on the grid
753+
parameters of the graph.
754+
755+
Parameters
756+
----------
757+
xloc : array_like
758+
List of x-axis tick values
759+
ylim : array_like
760+
List of y-axis limits [min, max]
761+
762+
Returns
763+
-------
764+
wn : list
765+
List of default natural frequencies for the plot
766+
767+
"""
768+
769+
wn = xloc # one frequency per x-axis tick mark
770+
sep = xloc[1]-xloc[0] # separation between ticks
715771

716-
wn = xloc
717-
sep = xloc[1]-xloc[0]
772+
# Insert additional frequencies to span the y-axis
718773
while np.abs(wn[0]) < ylim[1]:
719774
wn = np.insert(wn, 0, wn[0]-sep)
720775

776+
# If there are too many values, cut them in half
721777
while len(wn) > 7:
722778
wn = wn[0:-1:2]
723779

0 commit comments

Comments
 (0)
0