Merge pull request #503 from murrayrm/fix_rlocus_jupyter · python-control/python-control@8194b1d
@@ -222,6 +222,14 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
222222ax.set_xlabel('Real')
223223ax.set_ylabel('Imaginary')
224224225+# 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
225233if grid and sisotool:
226234if isdtime(sys, strict=True):
227235zgrid(ax=ax)
@@ -236,14 +244,9 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
236244ax.axhline(0., linestyle=':', color='k', zorder=-20)
237245ax.axvline(0., linestyle=':', color='k', zorder=-20)
238246if 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))
247250248251return mymat, kvect
249252@@ -642,16 +645,21 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
642645ax = fig.gca()
643646else:
644647ax = fig.axes[1]
648+649+# Get locator function for x-axis tick marks
645650xlocator = ax.get_xaxis().get_major_locator()
646651652+# Decide on the location for the labels (?)
647653ylim = ax.get_ylim()
648654ytext_pos_lim = ylim[1] - (ylim[1] - ylim[0]) * 0.03
649655xlim = ax.get_xlim()
650656xtext_pos_lim = xlim[0] + (xlim[1] - xlim[0]) * 0.0
651657658+# Create a list of damping ratios, if needed
652659if zeta is None:
653660zeta = _default_zetas(xlim, ylim)
654661662+# Figure out the angles for the different damping ratios
655663angles = []
656664for z in zeta:
657665if (z >= 1e-4) and (z <= 1):
@@ -661,11 +669,8 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
661669y_over_x = np.tan(angles)
662670663671# 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',
669674linestyle='dashed', linewidth=0.5)
670675ax.plot([0, xlocator()[0]], [0, -yp * xlocator()[0]], color='gray',
671676linestyle='dashed', linewidth=0.5)
@@ -679,45 +684,96 @@ def _sgrid_func(fig=None, zeta=None, wn=None):
679684ytext_pos = ytext_pos_lim
680685ax.annotate(an, textcoords='data', xy=[xtext_pos, ytext_pos],
681686fontsize=8)
682-index += 1
683687ax.plot([0, 0], [ylim[0], ylim[1]],
684688color='gray', linestyle='dashed', linewidth=0.5)
685689686-angles = np.linspace(-90, 90, 20)*np.pi/180
690+# omega-constant lines
691+angles = np.linspace(-90, 90, 20) * np.pi/180
687692if wn is None:
688693wn = _default_wn(xlocator(), ylim)
689694690695for om in wn:
691696if 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)
698709699710700711def _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
703734ang1 = [np.arctan((sep1*i)/ylim[1]) for i in np.arange(1, 4, 1)]
735+736+# Damping coefficient lines that intersection the y-axis
704737sep2 = ylim[1] / 3
705738ang2 = [np.arctan(-xlim[0]/(ylim[1]-sep2*i)) for i in np.arange(1, 3, 1)]
706739740+# Put the lines together and add one at -pi/2 (negative real axis)
707741angles = np.concatenate((ang1, ang2))
708742angles = np.insert(angles, len(angles), np.pi/2)
743+744+# Return the damping coefficients corresponding to these angles
709745zeta = np.sin(angles)
710746return zeta.tolist()
711747712748713749def _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
715771716-wn = xloc
717-sep = xloc[1]-xloc[0]
772+# Insert additional frequencies to span the y-axis
718773while np.abs(wn[0]) < ylim[1]:
719774wn = np.insert(wn, 0, wn[0]-sep)
720775776+# If there are too many values, cut them in half
721777while len(wn) > 7:
722778wn = wn[0:-1:2]
723779