Skip to content

Commit 66a3593

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

File tree

3 files changed

+94
-8
lines changed

3 files changed

+94
-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
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: 46 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
@@ -46,6 +46,10 @@ def root_locus_map(sysdata, gains=None):
4646
gains : array_like, optional
4747
Gains to use in computing plot of closed-loop poles. If not given,
4848
gains are chosen to include the main features of the root locus map.
49+
xlim : array_like, 2 elements, optional
50+
Plot range
51+
ylim : array_like, 2 elements, optional
52+
Plot range
4953
5054
Returns
5155
-------
@@ -75,7 +79,7 @@ def root_locus_map(sysdata, gains=None):
7579
nump, denp = _systopoly1d(sys[0, 0])
7680

7781
if gains is None:
78-
kvect, root_array, _, _ = _default_gains(nump, denp, None, None)
82+
kvect, root_array, _, _ = _default_gains(nump, denp, xlim, ylim)
7983
else:
8084
kvect = np.atleast_1d(gains)
8185
root_array = _RLFindRoots(nump, denp, kvect)
@@ -205,13 +209,52 @@ def root_locus_plot(
205209
# Plot the root loci
206210
cplt = responses.plot(grid=grid, **kwargs)
207211

212+
# Add a reaction to axis scale changes, if given LTI systems, and
213+
# there is no set of pre-defined gains
214+
if gains is None:
215+
add_loci_recalculate(sysdata, cplt, cplt.axes[0,0])
216+
208217
# Legacy processing: return locations of poles and zeros as a tuple
209218
if plot is True:
210219
return responses.loci, responses.gains
211220

212221
return ControlPlot(cplt.lines, cplt.axes, cplt.figure)
213222

214223

224+
def add_loci_recalculate(sysdata, cplt, axis):
225+
""" Add a calback to re-calculate the loci data fitting a zoom action
226+
227+
Parameters
228+
----------
229+
sysdata: LTI object or list
230+
Linear input/output systems (SISO only, for now).
231+
cplt: ControlPlot
232+
Collection of plot handles
233+
axis: matplotlib.axes.Axis
234+
Axis on which callbacks are installed
235+
"""
236+
237+
# if LTI, treat everything as a list of lti
238+
if isinstance(sysdata, LTI):
239+
sysdata = [ sysdata ]
240+
241+
# check that we can actually recalculate the loci
242+
if isinstance(sysdata, list) and all(
243+
[isinstance(sys, LTI) for sys in sysdata]):
244+
245+
# callback function for axis change (zoom, pan) events
246+
# captures the sysdata object and cplt
247+
def _zoom_adapter(_ax):
248+
newresp = root_locus_map(sysdata, None,
249+
_ax.get_xlim(),
250+
_ax.get_ylim())
251+
newresp.replot(cplt)
252+
253+
# connect the callback to axis changes
254+
axis.callbacks.connect('xlim_changed', _zoom_adapter)
255+
axis.callbacks.connect('ylim_changed', _zoom_adapter)
256+
257+
215258
def _default_gains(num, den, xlim, ylim):
216259
"""Unsupervised gains calculation for root locus plot.
217260
@@ -288,7 +331,7 @@ def _default_gains(num, den, xlim, ylim):
288331
# Root locus is on imaginary axis (rare), use just y distance
289332
tolerance = y_tolerance
290333
elif y_tolerance == 0:
291-
# Root locus is on imaginary axis (common), use just x distance
334+
# Root locus is on real axis (common), use just x distance
292335
tolerance = x_tolerance
293336
else:
294337
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy