59
59
60
60
# Main function: compute a root locus diagram
61
61
def root_locus (sys , kvect = None , xlim = None , ylim = None , plotstr = '-' , Plot = True ,
62
- PrintGain = True ):
62
+ PrintGain = True , grid = False ):
63
63
"""Root locus plot
64
64
65
65
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,
77
77
ylim : tuple or list, optional
78
78
control of y-axis range
79
79
Plot : boolean, optional (default = True)
80
- If True, plot magnitude and phase
80
+ If True, plot root locus diagram.
81
81
PrintGain: boolean (default = True)
82
82
If True, report mouse clicks when close to the root-locus branches,
83
83
calculate gain, damping and print
84
+ grid: boolean (default = False)
85
+ If True plot s-plane grid.
84
86
85
87
Returns
86
88
-------
@@ -99,9 +101,17 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
99
101
mymat = _RLFindRoots (nump , denp , kvect )
100
102
mymat = _RLSortRoots (mymat )
101
103
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
+
105
115
if PrintGain :
106
116
f .canvas .mpl_connect (
107
117
'button_release_event' , partial (_RLFeedbackClicks , sys = sys ))
@@ -127,6 +137,8 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
127
137
ax .set_ylim (ylim )
128
138
ax .set_xlabel ('Real' )
129
139
ax .set_ylabel ('Imaginary' )
140
+ if grid :
141
+ sgrid_func (f )
130
142
131
143
return mymat , kvect
132
144
@@ -150,6 +162,7 @@ def _default_gains(num, den, xlim, ylim):
150
162
mymat_xl = mymat
151
163
singular_points = np .concatenate ((num .roots , den .roots ), axis = 0 )
152
164
important_points = np .concatenate ((singular_points , real_break ), axis = 0 )
165
+ important_points = np .concatenate ((singular_points , np .zeros (2 )), axis = 0 )
153
166
mymat_xl = np .append (mymat_xl , important_points )
154
167
155
168
if xlim is None :
@@ -305,6 +318,58 @@ def _RLFeedbackClicks(event, sys):
305
318
K = - 1. / sys .horner (s )
306
319
if abs (K .real ) > 1e-8 and abs (K .imag / K .real ) < 0.04 :
307
320
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 )
309
374
310
375
rlocus = root_locus
0 commit comments