|
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 |
@@ -75,7 +75,7 @@ def root_locus_map(sysdata, gains=None): |
75 | 75 | nump, denp = _systopoly1d(sys[0, 0]) |
76 | 76 |
|
77 | 77 | if gains is None: |
78 | | - kvect, root_array, _, _ = _default_gains(nump, denp, None, None) |
| 78 | + kvect, root_array, _, _ = _default_gains(nump, denp, xlim, ylim) |
79 | 79 | else: |
80 | 80 | kvect = np.atleast_1d(gains) |
81 | 81 | root_array = _RLFindRoots(nump, denp, kvect) |
@@ -205,13 +205,52 @@ def root_locus_plot( |
205 | 205 | # Plot the root loci |
206 | 206 | cplt = responses.plot(grid=grid, **kwargs) |
207 | 207 |
|
| 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 | + |
208 | 213 | # Legacy processing: return locations of poles and zeros as a tuple |
209 | 214 | if plot is True: |
210 | 215 | return responses.loci, responses.gains |
211 | 216 |
|
212 | 217 | return ControlPlot(cplt.lines, cplt.axes, cplt.figure) |
213 | 218 |
|
214 | 219 |
|
| 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 | + |
215 | 254 | def _default_gains(num, den, xlim, ylim): |
216 | 255 | """Unsupervised gains calculation for root locus plot. |
217 | 256 |
|
@@ -288,7 +327,7 @@ def _default_gains(num, den, xlim, ylim): |
288 | 327 | # Root locus is on imaginary axis (rare), use just y distance |
289 | 328 | tolerance = y_tolerance |
290 | 329 | 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 |
292 | 331 | tolerance = x_tolerance |
293 | 332 | else: |
294 | 333 | tolerance = np.min([x_tolerance, y_tolerance]) |
|
0 commit comments