56
56
57
57
__all__ = ['root_locus' , 'rlocus' ]
58
58
59
+
59
60
# Main function: compute a root locus diagram
60
61
def root_locus (sys , kvect = None , xlim = None , ylim = None , plotstr = '-' , Plot = True ,
61
62
PrintGain = True ):
@@ -93,11 +94,10 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
93
94
(nump , denp ) = _systopoly1d (sys )
94
95
95
96
if kvect is None :
96
- kvect = _default_gains (sys )
97
-
98
- # Compute out the loci
99
- mymat = _RLFindRoots (sys , kvect )
100
- mymat = _RLSortRoots (sys , mymat )
97
+ kvect , mymat , xlim , ylim = _default_gains (nump , denp , xlim , ylim )
98
+ else :
99
+ mymat = _RLFindRoots (nump , denp , kvect )
100
+ mymat = _RLSortRoots (mymat )
101
101
102
102
# Create the plot
103
103
if (Plot ):
@@ -130,11 +130,104 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='-', Plot=True,
130
130
131
131
return mymat , kvect
132
132
133
- def _default_gains (sys ):
134
- # TODO: update with a smart calculation of the gains using sys poles/zeros
135
- return np .logspace (- 3 , 3 )
136
133
137
- # Utility function to extract numerator and denominator polynomials
134
+ def _default_gains (num , den , xlim , ylim ):
135
+ """Unsupervised gains calculation for root locus plot"""
136
+ k_break , real_break = _break_points (num , den )
137
+ kmax = _k_max (num , den , real_break , k_break )
138
+ kvect = np .hstack ((np .linspace (0 , kmax , 50 ), np .real (k_break )))
139
+ kvect .sort ()
140
+ mymat = _RLFindRoots (num , den , kvect )
141
+ mymat = _RLSortRoots (mymat )
142
+ open_loop_poles = den .roots
143
+ open_loop_zeros = num .roots
144
+
145
+ if (open_loop_zeros .size != 0 ) & (open_loop_zeros .size < open_loop_poles .size ):
146
+ open_loop_zeros_xl = np .append (open_loop_zeros ,
147
+ np .ones (open_loop_poles .size - open_loop_zeros .size ) * open_loop_zeros [- 1 ])
148
+ mymat_xl = np .append (mymat , open_loop_zeros_xl )
149
+ else :
150
+ mymat_xl = mymat
151
+
152
+ if xlim is None :
153
+ x_tolerance = 0.05 * (np .max (np .max (np .real (mymat_xl ))) - np .min (np .min (np .real (mymat_xl ))))
154
+ xlim = _ax_lim (mymat_xl )
155
+ else :
156
+ x_tolerance = 0.05 * (xlim [1 ] - xlim [0 ])
157
+ if ylim is None :
158
+ y_tolerance = 0.05 * (np .max (np .max (np .imag (mymat_xl ))) - np .min (np .min (np .imag (mymat_xl ))))
159
+ ylim = _ax_lim (mymat_xl * 1j )
160
+ else :
161
+ y_tolerance = 0.05 * (ylim [1 ] - ylim [0 ])
162
+
163
+ tolerance = np .max ([x_tolerance , y_tolerance ])
164
+ distance_points = np .abs (np .diff (mymat , axis = 0 ))
165
+ indexes_too_far = np .where (distance_points > tolerance )
166
+ while (indexes_too_far [0 ].size > 0 ) & (kvect .size < 5000 ):
167
+ for index in indexes_too_far [0 ]:
168
+ new_gains = np .linspace (kvect [index ], kvect [index + 1 ], 5 )
169
+ new_points = _RLFindRoots (num , den , new_gains [1 :4 ])
170
+ kvect = np .insert (kvect , index + 1 , new_gains [1 :4 ])
171
+ mymat = np .insert (mymat , index + 1 , new_points , axis = 0 )
172
+ mymat = _RLSortRoots (mymat )
173
+ distance_points = np .abs (np .diff (mymat , axis = 0 ))
174
+ indexes_too_far = np .where (distance_points > tolerance )
175
+ new_gains = np .hstack ((np .logspace (np .log10 (kvect [- 1 ]), np .log10 (kvect [- 1 ]* 200 ), 10 )))
176
+ new_points = _RLFindRoots (num , den , new_gains [1 :10 ])
177
+ kvect = np .append (kvect , new_gains [1 :10 ])
178
+ mymat = np .concatenate ((mymat , new_points ), axis = 0 )
179
+ mymat = _RLSortRoots (mymat )
180
+ return kvect , mymat , xlim , ylim
181
+
182
+
183
+ def _break_points (num , den ):
184
+ """Extract break points over real axis and the gains give these location"""
185
+ # type: (np.poly1d, np.poly1d) -> (np.array, np.array)
186
+ dnum = num .deriv (m = 1 )
187
+ dden = den .deriv (m = 1 )
188
+ polynom = den * dnum - num * dden
189
+ real_break_pts = polynom .r
190
+ real_break_pts = real_break_pts [num (real_break_pts ) != 0 ] # don't care about infinite break points
191
+ k_break = - den (real_break_pts ) / num (real_break_pts )
192
+ idx = k_break >= 0 # only positives gains
193
+ k_break = k_break [idx ]
194
+ real_break_pts = real_break_pts [idx ]
195
+ return k_break , real_break_pts
196
+
197
+
198
+ def _ax_lim (mymat ):
199
+ """Utility to get the axis limits"""
200
+ axmin = np .min (np .min (np .real (mymat )))
201
+ axmax = np .max (np .max (np .real (mymat )))
202
+ if axmax != axmin :
203
+ deltax = (axmax - axmin ) * 0.02
204
+ else :
205
+ deltax = np .max ([1. , axmax / 2 ])
206
+ axlim = [axmin - deltax , axmax + deltax ]
207
+ return axlim
208
+
209
+
210
+ def _k_max (num , den , real_break_points , k_break_points ):
211
+ """" Calculation the maximum gain for the root locus shown in tne figure"""
212
+ asymp_number = den .order - num .order
213
+ singular_points = np .concatenate ((num .roots , den .roots ), axis = 0 )
214
+ important_points = np .concatenate ((singular_points , real_break_points ), axis = 0 )
215
+
216
+ if asymp_number > 0 :
217
+ asymp_center = (np .sum (den .roots ) - np .sum (num .roots ))/ asymp_number
218
+ distance_max = 2 * np .max (np .abs (important_points - asymp_center ))
219
+ asymp_angles = (2 * np .arange (0 , asymp_number )- 1 ) * np .pi / asymp_number
220
+ farthest_points = asymp_center + distance_max * np .exp (asymp_angles * 1j ) # farthest points over asymptotes
221
+ kmax_asymp = - den (farthest_points ) / num (farthest_points )
222
+ else :
223
+ farthest_points = 2 * np .max (np .abs (important_points ))
224
+ kmax_asymp = - den (farthest_points ) / num (farthest_points )
225
+ if kmax_asymp == 0 :
226
+ kmax_asymp = - den (1 ) / num (1 )
227
+ kmax = np .max (np .concatenate ((np .real (kmax_asymp ), k_break_points ), axis = 0 ))
228
+ return kmax
229
+
230
+
138
231
def _systopoly1d (sys ):
139
232
"""Extract numerator and denominator polynomails for a system"""
140
233
# Allow inputs from the signal processing toolbox
@@ -163,11 +256,10 @@ def _systopoly1d(sys):
163
256
return (nump , denp )
164
257
165
258
166
- def _RLFindRoots (sys , kvect ):
259
+ def _RLFindRoots (nump , denp , kvect ):
167
260
"""Find the roots for the root locus."""
168
261
169
262
# Convert numerator and denominator to polynomials if they aren't
170
- (nump , denp ) = _systopoly1d (sys )
171
263
172
264
roots = []
173
265
for k in kvect :
@@ -179,7 +271,7 @@ def _RLFindRoots(sys, kvect):
179
271
return mymat
180
272
181
273
182
- def _RLSortRoots (sys , mymat ):
274
+ def _RLSortRoots (mymat ):
183
275
"""Sort the roots from sys._RLFindRoots, so that the root
184
276
locus doesn't show weird pseudo-branches as roots jump from
185
277
one branch to another."""
0 commit comments