33
33
34
34
35
35
# Root locus map
36
- def root_locus_map (sysdata , gains = None ):
36
+ def root_locus_map (sysdata , gains = None , xlim = None , ylim = None ):
37
37
"""Compute the root locus map for an LTI system.
38
38
39
39
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):
46
46
gains : array_like, optional
47
47
Gains to use in computing plot of closed-loop poles. If not given,
48
48
gains are chosen to include the main features of the root locus map.
49
+ xlim : tuple or list, optional
50
+ Set limits of x axis (see `matplotlib.axes.Axes.set_xlim`).
51
+ ylim : tuple or list, optional
52
+ Set limits of y axis (see `matplotlib.axes.Axes.set_ylim`).
49
53
50
54
Returns
51
55
-------
@@ -75,7 +79,7 @@ def root_locus_map(sysdata, gains=None):
75
79
nump , denp = _systopoly1d (sys [0 , 0 ])
76
80
77
81
if gains is None :
78
- kvect , root_array , _ , _ = _default_gains (nump , denp , None , None )
82
+ kvect , root_array , _ , _ = _default_gains (nump , denp , xlim , ylim )
79
83
else :
80
84
kvect = np .atleast_1d (gains )
81
85
root_array = _RLFindRoots (nump , denp , kvect )
@@ -205,13 +209,52 @@ def root_locus_plot(
205
209
# Plot the root loci
206
210
cplt = responses .plot (grid = grid , ** kwargs )
207
211
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
+
208
217
# Legacy processing: return locations of poles and zeros as a tuple
209
218
if plot is True :
210
219
return responses .loci , responses .gains
211
220
212
221
return ControlPlot (cplt .lines , cplt .axes , cplt .figure )
213
222
214
223
224
+ def add_loci_recalculate (sysdata , cplt , axis ):
225
+ """Add a callback 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
+
215
258
def _default_gains (num , den , xlim , ylim ):
216
259
"""Unsupervised gains calculation for root locus plot.
217
260
@@ -288,7 +331,7 @@ def _default_gains(num, den, xlim, ylim):
288
331
# Root locus is on imaginary axis (rare), use just y distance
289
332
tolerance = y_tolerance
290
333
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
292
335
tolerance = x_tolerance
293
336
else :
294
337
tolerance = np .min ([x_tolerance , y_tolerance ])
0 commit comments