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,

222222

ax.set_xlabel('Real')

223223

ax.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

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))

247250248251

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()

646651652+

# 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

651657658+

# Create a list of damping ratios, if needed

652659

if zeta is None:

653660

zeta = _default_zetas(xlim, ylim)

654661662+

# 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)

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',

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)

685689686-

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)

689694690695

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)

698709699710700711

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)]

706739740+

# 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()

711747712748713749

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

715771716-

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)

720775776+

# If there are too many values, cut them in half

721777

while len(wn) > 7:

722778

wn = wn[0:-1:2]

723779