Skip to content

Commit fe13ae1

Browse files
committed
Fixed zooming on plot. Now displays smoother root locus plot.
1 parent c0e533e commit fe13ae1

1 file changed

Lines changed: 72 additions & 110 deletions

File tree

control/rlocus.py

Lines changed: 72 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,10 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='b' if int(matplot
165165
ax.axvline(0., linestyle=':', color='k')
166166
return mymat, kvect
167167

168-
def _default_gains(num, den, xlim, ylim, point_tolerance=5e-2,zoom_xlim=None,zoom_ylim=None):
169-
"""Unsupervised gains calculation for root locus plot.
170-
168+
169+
def _default_gains(num, den, xlim, ylim,zoom_xlim=None,zoom_ylim=None):
170+
"""Unsupervised gains calculation for root locus plot.
171+
171172
References:
172173
Ogata, K. (2002). Modern control engineering (4th ed.). Upper Saddle River, NJ : New Delhi: Prentice Hall.."""
173174

@@ -197,126 +198,92 @@ def _default_gains(num, den, xlim, ylim, point_tolerance=5e-2,zoom_xlim=None,zoo
197198
"locus with equal order of numerator and denominator.")
198199

199200
if xlim is None and false_gain > 0:
200-
x_tolerance = point_tolerance * (np.max(np.real(mymat_xl)) - np.min(np.real(mymat_xl)))
201+
x_tolerance = 0.05 * (np.max(np.real(mymat_xl)) - np.min(np.real(mymat_xl)))
201202
xlim = _ax_lim(mymat_xl)
202203
elif xlim is None and false_gain < 0:
203-
axmin = np.min(np.real(important_points))-(np.max(np.real(important_points))-np.min(np.real(important_points)))
204+
axmin = np.min(np.real(important_points)) - (
205+
np.max(np.real(important_points)) - np.min(np.real(important_points)))
204206
axmin = np.min(np.array([axmin, np.min(np.real(mymat_xl))]))
205-
axmax = np.max(np.real(important_points))+np.max(np.real(important_points))-np.min(np.real(important_points))
207+
axmax = np.max(np.real(important_points)) + np.max(np.real(important_points)) - np.min(
208+
np.real(important_points))
206209
axmax = np.max(np.array([axmax, np.max(np.real(mymat_xl))]))
207210
xlim = [axmin, axmax]
208-
x_tolerance = 5e-2 * (axmax - axmin)
211+
x_tolerance = 0.05 * (axmax - axmin)
209212
else:
210-
x_tolerance = 5e-2 * (xlim[1] - xlim[0])
213+
x_tolerance = 0.05 * (xlim[1] - xlim[0])
211214

212215
if ylim is None:
213-
y_tolerance = 5e-2 * (np.max(np.imag(mymat_xl)) - np.min(np.imag(mymat_xl)))
216+
y_tolerance = 0.05 * (np.max(np.imag(mymat_xl)) - np.min(np.imag(mymat_xl)))
214217
ylim = _ax_lim(mymat_xl * 1j)
215218
else:
216-
y_tolerance = 5e-2 * (ylim[1] - ylim[0])
217-
print(len(mymat))
218-
tolerance = np.max([x_tolerance, y_tolerance])
219-
# print('normal smoothing start')
220-
# mymat,kvect =_smooth_rootlocus(num, den, tolerance,mymat,kvect, xlim=None, ylim=None)
221-
# print('normal smoothing end')
222-
mymat = _RLSortRoots(mymat)
223-
print(len(mymat))
219+
y_tolerance = 0.05 * (ylim[1] - ylim[0])
220+
221+
tolerance = np.min([x_tolerance, y_tolerance])
222+
indexes_too_far = _indexes_filt(mymat,tolerance,zoom_xlim,zoom_ylim)
223+
224+
while (len(indexes_too_far) > 0) and (kvect.size < 5000):
225+
for counter,index in enumerate(indexes_too_far):
226+
index = index + counter*3
227+
new_gains = np.linspace(kvect[index], kvect[index + 1], 5)
228+
new_points = _RLFindRoots(num, den, new_gains[1:4])
229+
kvect = np.insert(kvect, index + 1, new_gains[1:4])
230+
mymat = np.insert(mymat, index + 1, new_points, axis=0)
224231

225-
# If a zoom on the plot is used insert points on this interval and use a smaller tolerance
226-
if zoom_xlim != None and zoom_ylim != None:
227-
print('zoom smoothing start')
228-
y_tolerance = 5e-2 * (zoom_ylim[1] - zoom_ylim[0])
229-
x_tolerance = 5e-2 * (zoom_xlim[1] - zoom_xlim[0])
230-
tolerance = np.max([x_tolerance, y_tolerance])
231-
tolerance = np.max([x_tolerance, y_tolerance])
232-
#print(mymat)
233-
mymat,kvect = _smooth_rootlocus(num,den,tolerance,mymat,kvect,zoom_xlim,zoom_ylim)
234232
mymat = _RLSortRoots(mymat)
235-
#print(mymat)
236-
print('zoom smoothing end')
237-
print(len(mymat))
238-
kvect = np.sort(kvect)
233+
indexes_too_far = _indexes_filt(mymat,tolerance,zoom_xlim,zoom_ylim)
239234

240-
mymat = _RLFindRoots(num, den, kvect)
235+
new_gains = kvect[-1] * np.hstack((np.logspace(0, 3, 4)))
236+
new_points = _RLFindRoots(num, den, new_gains[1:4])
237+
kvect = np.append(kvect, new_gains[1:4])
238+
mymat = np.concatenate((mymat, new_points), axis=0)
239+
mymat = _RLSortRoots(mymat)
241240
return kvect, mymat, xlim, ylim
242241

243-
244-
def _smooth_rootlocus(num,den,tolerance,mymat,kvect, xlim,ylim):
245-
"""Smooth the rootlocus plot by inserting points at locations where the distance between
246-
two points exceeds a certain tolerance."""
242+
def _indexes_filt(mymat,tolerance,zoom_xlim=None,zoom_ylim=None):
243+
"""Calculate the distance between points and return the indexes.
244+
Filter the indexes so only the resolution of points within the xlim and ylim is improved when zoom is used"""
247245

248246
distance_points = np.abs(np.diff(mymat, axis=0))
249-
indexes_too_far = np.where(distance_points > tolerance)
250-
indexes_too_far_filtered = _indexes_filter(indexes_too_far,mymat,xlim,ylim)
251-
252-
print('length of indexes too_far_filtered')
253-
print(len(indexes_too_far_filtered))
254-
print(indexes_too_far_filtered)
255-
256-
if len(indexes_too_far_filtered) > 0:
257-
print('points are added')
258-
# if indexes_too_far_filtered[0] != 0:
259-
# point_before_start = indexes_too_far_filtered[0] -1
260-
# indexes_too_far_filtered.insert(0,point_before_start)
261-
#
262-
# if indexes_too_far_filtered[-1] != len(mymat):
263-
# point_at_end = indexes_too_far_filtered[-1]+1
264-
# indexes_too_far_filtered.append(point_at_end)
265-
266-
print('before while loop')
267-
print(len(indexes_too_far_filtered))
268-
print(indexes_too_far_filtered)
269-
while (len(indexes_too_far_filtered) > 0) and (kvect.size < 5000):
270-
counter = 0
271-
for list_index, index in enumerate(indexes_too_far_filtered):
272-
index+= counter*3
273-
new_gains = np.linspace(kvect[index], kvect[index+1], 5)
274-
new_points = _RLFindRoots(num, den, new_gains[1:4])
275-
276-
print('inside while loop')
277-
print(index)
278-
print(kvect[index],kvect[index+1])
279-
print(new_gains[1:4])
280-
281-
kvect = np.insert(kvect, index+1, new_gains[1:4])
282-
283-
mymat = np.insert(mymat, index+1, new_points, axis=0)
284-
counter +=1
285-
286-
287-
288-
mymat = _RLSortRoots(mymat)
289-
distance_points = np.abs(np.diff(mymat, axis=0)) > tolerance
290-
indexes_too_far = np.where(distance_points)
291-
indexes_too_far_filtered = _indexes_filter(indexes_too_far, mymat, xlim, ylim)
292-
print('new indexes too far')
293-
print(indexes_too_far_filtered)
294-
print('K SORTED?')
295-
print(all(kvect[i] <= kvect[i+1] for i in range(len(kvect)-1)))
296-
297-
#print(kvect)
298-
299-
new_gains = kvect[-1] * np.hstack((np.logspace(0, 3, 4)))
300-
new_points = _RLFindRoots(num, den, new_gains[1:4])
301-
kvect = np.append(kvect, new_gains[1:4])
302-
mymat = np.concatenate((mymat, new_points), axis=0)
303-
304-
305-
return mymat,kvect
306-
307-
def _indexes_filter(indexes_too_far,mymat,xlim,ylim):
308-
"""Filter the indexes so only the resolution of points within the xlim and ylim is improved"""
309-
if xlim== None and ylim == None:
310-
indexes_too_far_filtered = list(np.unique(indexes_too_far[0]))
311-
else:
247+
indexes_too_far = list(np.unique(np.where(distance_points > tolerance)[0]))
248+
249+
if zoom_xlim != None and zoom_ylim != None:
250+
x_tolerance_zoom = 0.05 * (zoom_xlim[1] - zoom_xlim[0])
251+
y_tolerance_zoom = 0.05 * (zoom_ylim[1] - zoom_ylim[0])
252+
tolerance_zoom = np.min([x_tolerance_zoom, y_tolerance_zoom])
253+
distance_points_zoom_ = np.abs(np.diff(mymat, axis=0))
254+
indexes_too_far_zoom = list(np.unique(np.where(distance_points_zoom_ > tolerance_zoom)[0]))
312255
indexes_too_far_filtered = []
313-
for index in np.unique(indexes_too_far[0]):
256+
257+
for index in indexes_too_far_zoom:
314258
for point in mymat[index]:
315-
if (xlim[0] <= point.real <= xlim[1]) and (ylim[0] <= point.imag <=ylim[1]):
259+
if (zoom_xlim[0] <= point.real <= zoom_xlim[1]) and (zoom_ylim[0] <= point.imag <= zoom_ylim[1]):
316260
indexes_too_far_filtered.append(index)
317261
break
318262

319-
return indexes_too_far_filtered
263+
# Check if the zoom box is not overshot and insert points where neccessary
264+
if len(indexes_too_far_filtered) == 0 and len(mymat) <300:
265+
limits = [zoom_xlim[0],zoom_xlim[1],zoom_ylim[0],zoom_ylim[1]]
266+
for index,limit in enumerate(limits):
267+
if index <= 1:
268+
asign = np.sign(real(mymat)-limit)
269+
else:
270+
asign = np.sign(imag(mymat) - limit)
271+
signchange = ((np.roll(asign, 1, axis=0) - asign) != 0).astype(int)
272+
signchange[0] = np.zeros((len(mymat[0])))
273+
if len(np.where(signchange ==1)) > 0:
274+
indexes_too_far_filtered.append(np.where(signchange == 1)[0][0])
275+
276+
if len(indexes_too_far_filtered) > 0 :
277+
if indexes_too_far_filtered[0] != 0:
278+
indexes_too_far_filtered.insert(0,indexes_too_far_filtered[0]-1)
279+
if not indexes_too_far_filtered[-1] +1 >= len(mymat)-2:
280+
indexes_too_far_filtered.append(indexes_too_far_filtered[-1]+1)
281+
282+
indexes_too_far.extend(indexes_too_far_filtered)
283+
284+
indexes_too_far = list(np.unique(indexes_too_far))
285+
indexes_too_far.sort()
286+
return indexes_too_far
320287

321288
def _break_points(num, den):
322289
"""Extract break points over real axis and the gains give these location"""
@@ -405,7 +372,6 @@ def _systopoly1d(sys):
405372

406373
def _RLFindRoots(nump, denp, kvect):
407374
"""Find the roots for the root locus."""
408-
409375
# Convert numerator and denominator to polynomials if they aren't
410376
roots = []
411377
for k in kvect:
@@ -421,7 +387,6 @@ def _RLFindRoots(nump, denp, kvect):
421387
mymat = row_stack(roots)
422388
return mymat
423389

424-
425390
def _RLSortRoots(mymat):
426391
"""Sort the roots from sys._RLFindRoots, so that the root
427392
locus doesn't show weird pseudo-branches as roots jump from
@@ -446,20 +411,17 @@ def _RLSortRoots(mymat):
446411

447412
def _RLClickDispatcher(event,sys,fig,ax_rlocus,plotstr,sisotool=False,bode_plot_params=None,tvect=None):
448413
"""Rootlocus plot click dispatcher"""
449-
450414
# If zoom is used on the rootlocus plot smooth and update it
451415
if plt.get_current_fig_manager().toolbar.mode == 'zoom rect' and event.inaxes == ax_rlocus.axes:
452416

453417
(nump, denp) = _systopoly1d(sys)
454-
xlim = ax_rlocus.get_xlim()
455-
ylim = ax_rlocus.get_ylim()
418+
xlim,ylim = ax_rlocus.get_xlim(),ax_rlocus.get_ylim()
419+
456420
kvect,mymat, xlim,ylim = _default_gains(nump, denp,xlim=None,ylim=None, zoom_xlim=xlim,zoom_ylim=ylim)
457421
_removeLine('rootlocus', ax_rlocus)
458422

459-
for index,col in enumerate(mymat.T):
460-
ax_rlocus.plot(real(col), imag(col), plotstr,label=index)
461-
462-
fig.canvas.draw()
423+
for i,col in enumerate(mymat.T):
424+
ax_rlocus.plot(real(col), imag(col), plotstr,label='rootlocus')
463425

464426
# if a point is clicked on the rootlocus plot visually emphasize it
465427
else:

0 commit comments

Comments
 (0)