8000 recalculate loci for sisotool and rlocus on axis scaling · python-control/python-control@7d567d6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7d567d6

Browse files
committed
recalculate loci for sisotool and rlocus on axis scaling
1 parent 632391c commit 7d567d6

File tree

3 files changed

+90
-8
lines changed

3 files changed

+90
-8
lines changed

control/pzmap.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,17 @@ def plot(self, *args, **kwargs):
124124
"""
125125
return pole_zero_plot(self, *args, **kwargs)
126126

127+
def replot(self, cplt: ControlPlot):
128+
"""Update the pole/zero loci of an existing plot
129+
130+
Parameters
131+
----------
132+
cplt: ControlPlot
133+
graphics handles of the existing plot
134+
"""
135+
pole_zero_replot(self, cplt)
136+
137+
127138

128139
# Pole/zero map
129140
def pole_zero_map(sysdata):
@@ -513,6 +524,35 @@ def _click_dispatcher(event):
513524
return ControlPlot(out, ax, fig, legend=legend)
514525

515526

527+
def pole_zero_replot(pzmap_responses, cp):
528+
"""Update the loci of a plot after zooming/panning
529+
530+
Parameters
531+
----------
532+
pzmap_responses : PoleZeroMap list
533+
Responses to update
< 8000 code>534+
cp : ControlPlot
535+
Collection of plot handles
536+
"""
537+
538+
for idx, response in enumerate(pzmap_responses):
539+
540+
# remove the old data
541+
for l in cp.lines[idx, 2]:
542+
l.set_data([], [])
543+
544+
# update the line data
545+
if response.loci is not None:
546+
547+
for il, locus in enumerate(response.loci.transpose()):
548+
try:
549+
cp.lines[idx,2][il].set_data(real(locus), imag(locus))
550+
except IndexError:
551+
# not expected, but more lines apparently needed
552+
cp.lines[idx,2].append(cp.ax[0,0].plot(
553+
real(locus), imag(locus)))
554+
555+
516556
# Utility function to find gain corresponding to a click event
517557
def _find_root_locus_gain(event, sys, ax):
518558
# Get the current axis limits to set various thresholds

control/rlocus.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434

3535
# Root locus map
36-
def root_locus_map(sysdata, gains=None):
36+
def root_locus_map(sysdata, gains=None, xlim=None, ylim=None):
3737
"""Compute the root locus map for an LTI system.
3838
3939
Calculate the root locus by finding the roots of 1 + k * G(s) where G
@@ -75,7 +75,7 @@ def root_locus_map(sysdata, gains=None):
7575
nump, denp = _systopoly1d(sys[0, 0])
7676

7777
if gains is None:
78-
kvect, root_array, _, _ = _default_gains(nump, denp, None, None)
78+
kvect, root_array, _, _ = _default_gains(nump, denp, xlim, ylim)
7979
else:
8080
kvect = np.atleast_1d(gains)
8181
root_array = _RLFindRoots(nump, denp, kvect)
@@ -205,13 +205,52 @@ def root_locus_plot(
205205
# Plot the root loci
206206
cplt = responses.plot(grid=grid, **kwargs)
207207

208+
# Add a reaction to axis scale changes, if given LTI systems, and
209+
# there is no set of pre-defined gains
210+
if gains is None:
211+
add_loci_recalculate(sysdata, cplt, cplt.axes[0,0])
212+
208213
# Legacy processing: return locations of poles and zeros as a tuple
209214
if plot is True:
210215
return responses.loci, responses.gains
211216

212217
return ControlPlot(cplt.lines, cplt.axes, cplt.figure)
213218

214219

220+
def add_loci_recalculate(sysdata, cplt, axis):
221+
""" Add a calback to re-calculate the loci data fitting a zoom action
222+
223+
Parameters
224+
----------
225+
sysdata: LTI object or list
226+
Linear input/output systems (SISO only, for now).
227+
cplt: ControlPlot
228+
Collection of plot handles
229+
axis: matplotlib.axes.Axis
230+
Axis on which callbacks are installed
231+
"""
232+
233+
# if LTI, treat everything as a list of lti
234+
if isinstance(sysdata, LTI):
235+
sysdata = [ sysdata ]
236+
237+
# check that we can actually recalculate the loci
238+
if isinstance(sysdata, list) and all(
239+
[isinstance(sys, LTI) for sys in sysdata]):
240+
241+
# callback function for axis change (zoom, pan) events
242+
# captures the sysdata object and cplt
243+
def _zoom_adapter(_ax):
244+
newresp = root_locus_map(sysdata, None,
245+
_ax.get_xlim(),
246+
_ax.get_ylim())
247+
newresp.replot(cplt)
248+
249+
# connect the callback to axis changes
250+
axis.callbacks.connect('xlim_changed', _zoom_adapter)
251+
axis.callbacks.connect('ylim_changed', _zoom_adapter)
252+
253+
215254
def _default_gains(num, den, xlim, ylim):
216255
"""Unsupervised gains calculation for root locus plot.
217256
@@ -288,7 +327,7 @@ def _default_gains(num, den, xlim, ylim):
288327
# Root locus is on imaginary axis (rare), use just y distance
289328
tolerance = y_tolerance
290329
elif y_tolerance == 0:
291-
# Root locus is on imaginary axis (common), use just x distance
330+
# Root locus is on real axis (common), use just x distance
292331
tolerance = x_tolerance
293332
else:
294333
tolerance = np.min([x_tolerance, y_tolerance])

control/sisotool.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from .statesp import ss, summing_junction
2323
from .timeresp import step_response
2424
from .xferfcn import tf
25+
from .rlocus import add_loci_recalculate
2526

2627
_sisotool_defaults = {
2728
'sisotool.initial_gain': 1
@@ -137,15 +138,18 @@ def sisotool(sys, initial_gain=None, xlim_rlocus=None, ylim_rlocus=None,
137138
# sys[0, 0], initial_gain=initial_gain, xlim=xlim_rlocus,
138139
# ylim=ylim_rlocus, plotstr=plotstr_rlocus, grid=rlocus_grid,
139140
# ax=fig.axes[1])
140-
ax_rlocus = fig.axes[1]
141-
root_locus_map(sys[0, 0]).plot(
141+
ax_rlocus = axes[0,1] #fig.axes[1]
142+
cplt = root_locus_map(sys[0, 0]).plot(
142143
xlim=xlim_rlocus, ylim=ylim_rlocus,
143144
initial_gain=initial_gain, ax=ax_rlocus)
144145
if rlocus_grid is False:
145146
# Need to generate grid manually, since root_locus_plot() won't
146147
from .grid import nogrid
147148
nogrid(sys.dt, ax=ax_rlocus)
148149

150+
# install a zoom callback on the root-locus axis
151+
add_loci_recalculate(sys, cplt, ax_rlocus)
152+
149153
# Reset the button release callback so that we can update all plots
150154
fig.canvas.mpl_connect(
151155
'button_release_event', partial(
@@ -155,9 +159,8 @@ def sisotool(sys, initial_gain=None, xlim_rlocus=None, ylim_rlocus=None,
155159

156160
def _click_dispatcher(event, sys, ax, bode_plot_params, tvect):
157161
# Zoom handled by specialized callback in rlocus, only handle gain plot
158-
if event.inaxes == ax.axes and \
159-
plt.get_current_fig_manager().toolbar.mode not in \
160-
{'zoom rect', 'pan/zoom'}:
162+
if event.inaxes == ax.axes:
163+
161164
fig = ax.figure
162165

163166
# if a point is clicked on the rootlocus plot visually emphasize it

0 commit comments

Comments
 (0)
0