Skip to content

Commit 7188a9c

Browse files
icam0murrayrm
authored andcommitted
Adaptive gain click criterion and zoom bug fix (#302)
* added adaptive gain * changed zoom parameter * Further refine David's sisotool clicking and zooming * removed diagnostic messages * removed code for manually settings the axes
1 parent ace1683 commit 7188a9c

File tree

5 files changed

+47
-20
lines changed

5 files changed

+47
-20
lines changed

control/matlab/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
from ..margins import margin
8282
from ..rlocus import rlocus
8383
from ..dtime import c2d
84+
from ..sisotool import sisotool
8485

8586
# Import functions specific to Matlab compatibility package
8687
from .timeresp import *
@@ -241,6 +242,7 @@
241242
242243
== ========================== ============================================
243244
\* :func:`rlocus` evans root locus
245+
\* :func:`sisotool` SISO controller design
244246
\* :func:`~control.place` pole placement
245247
\ estim form estimator given estimator gain
246248
\ reg form regulator given state-feedback and

control/rlocus.py

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='b' if int(matplot
138138
f.canvas.mpl_connect(
139139
'button_release_event',partial(_RLClickDispatcher,sys=sys, fig=f,ax_rlocus=f.axes[1],plotstr=plotstr, sisotool=sisotool, bode_plot_params=kwargs['bode_plot_params'],tvect=kwargs['tvect']))
140140

141+
# zoom update on xlim/ylim changed, only then data on new limits
142+
# is available, i.e., cannot combine with _RLClickDispatcher
143+
dpfun = partial(
144+
_RLZoomDispatcher, sys=sys, ax_rlocus=ax, plotstr=plotstr)
145+
ax.callbacks.connect('xlim_changed', dpfun)
146+
ax.callbacks.connect('ylim_changed', dpfun)
147+
141148
# plot open loop poles
142149
poles = array(denp.r)
143150
ax.plot(real(poles), imag(poles), 'x')
@@ -156,6 +163,7 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None, plotstr='b' if int(matplot
156163
ax.set_xlim(xlim)
157164
if ylim:
158165
ax.set_ylim(ylim)
166+
159167
ax.set_xlabel('Real')
160168
ax.set_ylabel('Imaginary')
161169
if grid and sisotool:
@@ -263,7 +271,7 @@ def _indexes_filt(mymat,tolerance,zoom_xlim=None,zoom_ylim=None):
263271
break
264272

265273
# Check if the zoom box is not overshot and insert points where neccessary
266-
if len(indexes_too_far_filtered) == 0 and len(mymat) <300:
274+
if len(indexes_too_far_filtered) == 0 and len(mymat) <500:
267275
limits = [zoom_xlim[0],zoom_xlim[1],zoom_ylim[0],zoom_ylim[1]]
268276
for index,limit in enumerate(limits):
269277
if index <= 1:
@@ -411,23 +419,30 @@ def _RLSortRoots(mymat):
411419
prevrow = sorted[n, :]
412420
return sorted
413421

414-
def _RLClickDispatcher(event,sys,fig,ax_rlocus,plotstr,sisotool=False,bode_plot_params=None,tvect=None):
415-
"""Rootlocus plot click dispatcher"""
422+
def _RLZoomDispatcher(event, sys, ax_rlocus, plotstr):
423+
"""Rootlocus plot zoom dispatcher"""
416424

417-
# If zoom is used on the rootlocus plot smooth and update it
418-
if plt.get_current_fig_manager().toolbar.mode in ['zoom rect','pan/zoom'] and event.inaxes == ax_rlocus.axes:
419-
(nump, denp) = _systopoly1d(sys)
420-
xlim,ylim = ax_rlocus.get_xlim(),ax_rlocus.get_ylim()
425+
nump, denp = _systopoly1d(sys)
426+
xlim, ylim = ax_rlocus.get_xlim(), ax_rlocus.get_ylim()
427+
428+
kvect, mymat, xlim, ylim = _default_gains(
429+
nump, denp, xlim=None, ylim=None, zoom_xlim=xlim, zoom_ylim=ylim)
430+
_removeLine('rootlocus', ax_rlocus)
431+
432+
for i, col in enumerate(mymat.T):
433+
ax_rlocus.plot(real(col), imag(col), plotstr, label='rootlocus',
434+
scalex=False, scaley=False)
421435

422-
kvect,mymat, xlim,ylim = _default_gains(nump, denp,xlim=None,ylim=None, zoom_xlim=xlim,zoom_ylim=ylim)
423-
_removeLine('rootlocus', ax_rlocus)
436+
def _RLClickDispatcher(event,sys,fig,ax_rlocus,plotstr,sisotool=False,bode_plot_params=None,tvect=None):
437+
"""Rootlocus plot click dispatcher"""
424438

425-
for i,col in enumerate(mymat.T):
426-
ax_rlocus.plot(real(col), imag(col), plotstr,label='rootlocus')
439+
# Zoom is handled by specialized callback above, only do gain plot
440+
if event.inaxes == ax_rlocus.axes and \
441+
plt.get_current_fig_manager().toolbar.mode not in \
442+
{'zoom rect','pan/zoom'}:
427443

428-
# if a point is clicked on the rootlocus plot visually emphasize it
429-
else:
430-
K = _RLFeedbackClicksPoint(event, sys, fig,ax_rlocus,sisotool)
444+
# if a point is clicked on the rootlocus plot visually emphasize it
445+
K = _RLFeedbackClicksPoint(event, sys, fig, ax_rlocus, sisotool)
431446
if sisotool and K is not None:
432447
_SisotoolUpdate(sys, fig, K, bode_plot_params, tvect)
433448

@@ -440,21 +455,27 @@ def _RLFeedbackClicksPoint(event,sys,fig,ax_rlocus,sisotool=False):
440455

441456
(nump, denp) = _systopoly1d(sys)
442457

458+
xlim = ax_rlocus.get_xlim()
459+
ylim = ax_rlocus.get_ylim()
460+
x_tolerance = 0.05 * abs((xlim[1] - xlim[0]))
461+
y_tolerance = 0.05 * abs((ylim[1] - ylim[0]))
462+
gain_tolerance = np.mean([x_tolerance, y_tolerance])*0.1
463+
443464
# Catch type error when event click is in the figure but not in an axis
444465
try:
445466
s = complex(event.xdata, event.ydata)
446467
K = -1. / sys.horner(s)
468+
K_xlim = -1. / sys.horner(complex(event.xdata + 0.05 * abs(xlim[1] - xlim[0]), event.ydata))
469+
K_ylim = -1. / sys.horner(complex(event.xdata, event.ydata + 0.05 * abs(ylim[1] - ylim[0])))
447470

448471
except TypeError:
449472
K = float('inf')
473+
K_xlim = float('inf')
474+
K_ylim = float('inf')
450475

451-
xlim = ax_rlocus.get_xlim()
452-
ylim = ax_rlocus.get_ylim()
453-
x_tolerance = 0.05 * (xlim[1] - xlim[0])
454-
y_tolerance = 0.05 * (ylim[1] - ylim[0])
455-
gain_tolerance = np.min([x_tolerance, y_tolerance])*1e-1
476+
gain_tolerance += 0.1*max([abs(K_ylim.imag/K_ylim.real),abs(K_xlim.imag/K_xlim.real)])
456477

457-
if abs(K.real) > 1e-8 and abs(K.imag / K.real) < gain_tolerance and event.inaxes == ax_rlocus.axes:
478+
if abs(K.real) > 1e-8 and abs(K.imag / K.real) < gain_tolerance and event.inaxes == ax_rlocus.axes and K.real > 0.:
458479

459480
# Display the parameters in the output window and figure
460481
print("Clicked at %10.4g%+10.4gj gain %10.4g damp %10.4g" %

control/sisotool.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ def _SisotoolUpdate(sys,fig,K,bode_plot_params,tvect=None):
131131
ax_rlocus.get_xaxis().set_label_coords(0.5, -0.15)
132132
ax_rlocus.get_yaxis().set_label_coords(-0.15, 0.5)
133133

134+
135+
134136
# Generate the step response and plot it
135137
sys_closed = (K*sys).feedback(1)
136138
if tvect is None:

doc/control.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ Control system analysis
8989
zero
9090
pzmap
9191
root_locus
92+
sisotool
9293

9394
Matrix computations
9495
===================

doc/matlab.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Compensator design
8282
:toctree: generated/
8383

8484
rlocus
85+
sisotool
8586
place
8687
lqr
8788

0 commit comments

Comments
 (0)