Skip to content

Commit 3fb2c86

Browse files
committed
Merge remote-tracking branch 'origin/unsup_rlocus_gain_selection' into unsup_rlocus_gain_selection
2 parents 95d6908 + ebd3904 commit 3fb2c86

File tree

1 file changed

+71
-6
lines changed

1 file changed

+71
-6
lines changed

control/rlocus.py

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959

6060
# Main function: compute a root locus diagram
6161
def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
62-
PrintGain=True):
62+
PrintGain=True, grid=False):
6363
"""Root locus plot
6464
6565
Calculate the root locus by finding the roots of 1+k*TF(s)
@@ -77,10 +77,12 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
7777
ylim : tuple or list, optional
7878
control of y-axis range
7979
Plot : boolean, optional (default = True)
80-
If True, plot magnitude and phase
80+
If True, plot root locus diagram.
8181
PrintGain: boolean (default = True)
8282
If True, report mouse clicks when close to the root-locus branches,
8383
calculate gain, damping and print
84+
grid: boolean (default = False)
85+
If True plot s-plane grid.
8486
8587
Returns
8688
-------
@@ -99,9 +101,17 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
99101
mymat = _RLFindRoots(nump, denp, kvect)
100102
mymat = _RLSortRoots(mymat)
101103

102-
# Create the plot
103-
if (Plot):
104-
f = pylab.figure()
104+
# Create the Plot
105+
if Plot:
106+
figure_number = pylab.get_fignums()
107+
figure_title = [pylab.figure(numb).canvas.get_window_title() for numb in figure_number]
108+
new_figure_name = "Root Locus"
109+
rloc_num = 1
110+
while new_figure_name in figure_title:
111+
new_figure_name = "Root Locus " + str(rloc_num)
112+
rloc_num += 1
113+
f = pylab.figure(new_figure_name)
114+
105115
if PrintGain:
106116
f.canvas.mpl_connect(
107117
'button_release_event', partial(_RLFeedbackClicks, sys=sys))
@@ -127,6 +137,8 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
127137
ax.set_ylim(ylim)
128138
ax.set_xlabel('Real')
129139
ax.set_ylabel('Imaginary')
140+
if grid:
141+
sgrid_func(f)
130142

131143
return mymat, kvect
132144

@@ -150,6 +162,7 @@ def _default_gains(num, den, xlim, ylim):
150162
mymat_xl = mymat
151163
singular_points = np.concatenate((num.roots, den.roots), axis=0)
152164
important_points = np.concatenate((singular_points, real_break), axis=0)
165+
important_points = np.concatenate((singular_points, np.zeros(2)), axis=0)
153166
mymat_xl = np.append(mymat_xl, important_points)
154167

155168
if xlim is None:
@@ -305,6 +318,58 @@ def _RLFeedbackClicks(event, sys):
305318
K = -1./sys.horner(s)
306319
if abs(K.real) > 1e-8 and abs(K.imag/K.real) < 0.04:
307320
print("Clicked at %10.4g%+10.4gj gain %10.4g damp %10.4g" %
308-
(s.real, s.imag, K.real, -1*s.real/abs(s)))
321+
(s.real, s.imag, K.real, -1 * s.real / abs(s)))
322+
323+
324+
def sgrid_func(fig):
325+
ax = fig.gca()
326+
ylocator = ax.get_yaxis().get_major_locator()
327+
xlocator = ax.get_yaxis().get_major_locator()
328+
329+
angules = np.arange(-90, 80, 15)*np.pi/180
330+
331+
# zeta-constant lines
332+
y_over_x = np.tan(angules[1::])*ylocator()[-1]/xlocator()[-1]
333+
ylim = ax.get_ylim()
334+
ytext_pos_lim = ylim[1]-(ylim[1]-ylim[0])*0.03
335+
xlim = ax.get_xlim()
336+
xtext_pos_lim = xlim[0]+(xlim[1]-xlim[0])*0.0
337+
index = 0
338+
zeta = np.sin(np.pi/2-angules[1::])
339+
340+
for yp in y_over_x:
341+
ax.plot([0, xlocator()[0]], [0, yp*xlocator()[0]], color='gray',
342+
linestyle='dashed', linewidth=0.5)
343+
an = "%.2f" % zeta[index]
344+
if yp > 0:
345+
xtext_pos = -1/yp * ylim[1]
346+
ytext_pos = -yp * xtext_pos_lim
347+
if np.abs(xtext_pos) > np.abs(xtext_pos_lim):
348+
xtext_pos = xtext_pos_lim
349+
else:
350+
ytext_pos = ytext_pos_lim
351+
ax.annotate(an, textcoords='data', xy=[xtext_pos, ytext_pos], fontsize=8)
352+
elif yp < 0:
353+
xtext_pos = -1/yp * ylim[1]
354+
ytext_pos = yp * xtext_pos_lim
355+
if np.abs(xtext_pos) > np.abs(xtext_pos_lim):
356+
xtext_pos = xtext_pos_lim
357+
ytext_pos = - ytext_pos
358+
else:
359+
ytext_pos = ylim[0]
360+
xtext_pos = -xtext_pos
361+
ax.annotate(an, textcoords='data', xy=[xtext_pos, ytext_pos], fontsize=8)
362+
index += 1
363+
ax.plot([0, 0], [ylim[0], ylim[1]], color='gray', linestyle='dashed', linewidth=0.5)
364+
365+
angules = np.linspace(-90, 90, 20)*np.pi/180
366+
for xt in xlocator():
367+
if xt < 0:
368+
yp = np.sin(angules)*np.abs(xt)
369+
xp = -np.cos(angules)*np.abs(xt)
370+
ax.plot(xp, yp, color='gray',
371+
linestyle='dashed', linewidth=0.5)
372+
an = "%.2f" % -xt
373+
ax.annotate(an, textcoords='data', xy=[xt, 0], fontsize=8)
309374

310375
rlocus = root_locus

0 commit comments

Comments
 (0)