|
45 | 45 | 'phaseplot.separatrices_radius': 0.1 # initial radius for separatrices |
46 | 46 | } |
47 | 47 |
|
| 48 | + |
48 | 49 | def phase_plane_plot( |
49 | 50 | sys, pointdata=None, timedata=None, gridtype=None, gridspec=None, |
50 | 51 | plot_streamlines=None, plot_vectorfield=None, plot_streamplot=None, |
@@ -131,17 +132,17 @@ def phase_plane_plot( |
131 | 132 | 'both' to flow both forward and backward. The amount of time to |
132 | 133 | simulate in each direction is given by the `timedata` argument. |
133 | 134 | plot_streamlines : bool or dict, optional |
134 | | - If then plot streamlines based on the pointdata and gridtype. If set |
135 | | - to a dict, pass on the key-value pairs in the dict as keywords to |
136 | | - `streamlines`. |
| 135 | + If True then plot streamlines based on the pointdata and gridtype. |
| 136 | + If set to a dict, pass on the key-value pairs in the dict as |
| 137 | + keywords to `streamlines`. |
137 | 138 | plot_vectorfield : bool or dict, optional |
138 | | - If then plot the vector field based on the pointdata and gridtype. |
139 | | - If set to a dict, pass on the key-value pairs in the dict as keywords |
140 | | - to `phaseplot.vectorfield`. |
| 139 | + If True then plot the vector field based on the pointdata and |
| 140 | + gridtype. If set to a dict, pass on the key-value pairs in the |
| 141 | + dict as keywords to `phaseplot.vectorfield`. |
141 | 142 | plot_streamplot : bool or dict, optional |
142 | | - If then use :func:`matplotlib.axes.Axes.streamplot` function |
| 143 | + If True then use `matplotlib.axes.Axes.streamplot` function |
143 | 144 | to plot the streamlines. If set to a dict, pass on the key-value |
144 | | - pairs in the dict as keywords to :func:`~control.phaseplot.streamplot`. |
| 145 | + pairs in the dict as keywords to `phaseplot.streamplot`. |
145 | 146 | plot_equilpoints : bool or dict, optional |
146 | 147 | If True (default) then plot equilibrium points based in the phase |
147 | 148 | plot boundary. If set to a dict, pass on the key-value pairs in the |
@@ -158,6 +159,16 @@ def phase_plane_plot( |
158 | 159 | title : str, optional |
159 | 160 | Set the title of the plot. Defaults to plot type and system name(s). |
160 | 161 |
|
| 162 | + Notes |
| 163 | + ----- |
| 164 | + The default method for producing streamlines is determined based on which |
| 165 | + keywords are specified, with `plot_streamplot` serving as the generic |
| 166 | + default. If any of the `arrows`, `arrow_size`, `arrow_style`, or `dir` |
| 167 | + keywords are used and neither `plot_streamlines` nor `plot_streamplot` is |
| 168 | + set, then `plot_streamlines` will be set to True. If neither |
| 169 | + `plot_streamlines` nor `plot_vectorfield` set set to True, then |
| 170 | + `plot_streamplot` will be set to True. |
| 171 | +
|
161 | 172 | """ |
162 | 173 | # Check for legacy usage of plot_streamlines |
163 | 174 | streamline_keywords = [ |
@@ -174,11 +185,8 @@ def phase_plane_plot( |
174 | 185 | "falling back to streamlines") |
175 | 186 | plot_streamlines = True |
176 | 187 |
|
177 | | - if ( |
178 | | - plot_streamlines is None |
179 | | - and plot_vectorfield is None |
180 | | - and plot_streamplot is None |
181 | | - ): |
| 188 | + if plot_streamlines is None and plot_vectorfield is None \ |
| 189 | + and plot_streamplot is None: |
182 | 190 | plot_streamplot = True |
183 | 191 |
|
184 | 192 | if plot_streamplot and not plot_streamlines and not plot_vectorfield: |
@@ -219,9 +227,10 @@ def _create_kwargs(global_kwargs, local_kwargs, **other_kwargs): |
219 | 227 | out[0] += streamlines( |
220 | 228 | sys, pointdata, timedata, _check_kwargs=False, |
221 | 229 | suppress_warnings=suppress_warnings, **kwargs_local) |
222 | | - |
| 230 | + |
223 | 231 | new_zorder = max(elem.get_zorder() for elem in out[0]) |
224 | | - flow_zorder = max(flow_zorder, new_zorder) if flow_zorder else new_zorder |
| 232 | + flow_zorder = max(flow_zorder, new_zorder) if flow_zorder \ |
| 233 | + else new_zorder |
225 | 234 |
|
226 | 235 | # Get rid of keyword arguments handled by streamlines |
227 | 236 | for kw in ['arrows', 'arrow_size', 'arrow_style', 'color', |
@@ -237,25 +246,28 @@ def _create_kwargs(global_kwargs, local_kwargs, **other_kwargs): |
237 | 246 | kwargs, plot_vectorfield, gridspec=gridspec, ax=ax) |
238 | 247 | out[1] = vectorfield( |
239 | 248 | sys, pointdata, _check_kwargs=False, **kwargs_local) |
240 | | - |
| 249 | + |
241 | 250 | new_zorder = out[1].get_zorder() |
242 | | - flow_zorder = max(flow_zorder, new_zorder) if flow_zorder else new_zorder |
| 251 | + flow_zorder = max(flow_zorder, new_zorder) if flow_zorder \ |
| 252 | + else new_zorder |
243 | 253 |
|
244 | 254 | # Get rid of keyword arguments handled by vectorfield |
245 | 255 | for kw in ['color', 'params']: |
246 | 256 | initial_kwargs.pop(kw, None) |
247 | 257 |
|
248 | 258 | if plot_streamplot: |
249 | 259 | if gridtype not in [None, 'meshgrid']: |
250 | | - raise ValueError("gridtype must be 'meshgrid' when using streamplot") |
| 260 | + raise ValueError( |
| 261 | + "gridtype must be 'meshgrid' when using streamplot") |
251 | 262 |
|
252 | 263 | kwargs_local = _create_kwargs( |
253 | 264 | kwargs, plot_streamplot, gridspec=gridspec, ax=ax) |
254 | 265 | out[3] = streamplot( |
255 | 266 | sys, pointdata, _check_kwargs=False, **kwargs_local) |
256 | | - |
| 267 | + |
257 | 268 | new_zorder = max(out[3].lines.get_zorder(), out[3].arrows.get_zorder()) |
258 | | - flow_zorder = max(flow_zorder, new_zorder) if flow_zorder else new_zorder |
| 269 | + flow_zorder = max(flow_zorder, new_zorder) if flow_zorder \ |
| 270 | + else new_zorder |
259 | 271 |
|
260 | 272 | # Get rid of keyword arguments handled by streamplot |
261 | 273 | for kw in ['color', 'params']: |
@@ -269,8 +281,9 @@ def _create_kwargs(global_kwargs, local_kwargs, **other_kwargs): |
269 | 281 | kwargs_local['zorder'] = kwargs_local.get('zorder', sep_zorder) |
270 | 282 | out[0] += separatrices( |
271 | 283 | sys, pointdata, _check_kwargs=False, **kwargs_local) |
272 | | - |
273 | | - sep_zorder = max(elem.get_zorder() for elem in out[0]) if out[0] else None |
| 284 | + |
| 285 | + sep_zorder = max(elem.get_zorder() for elem in out[0]) if out[0] \ |
| 286 | + else None |
274 | 287 |
|
275 | 288 | # Get rid of keyword arguments handled by separatrices |
276 | 289 | for kw in ['arrows', 'arrow_size', 'arrow_style', 'params']: |
@@ -340,9 +353,6 @@ def vectorfield( |
340 | 353 | dict with key 'args' and value given by a tuple (passed to callable). |
341 | 354 | color : matplotlib color spec, optional |
342 | 355 | Plot the vector field in the given color. |
343 | | - zorder : float, optional |
344 | | - Set the zorder for the separatrices. In not specified, it will be |
345 | | - automatically chosen by `matplotlib.axes.Axes.quiver`. |
346 | 356 | ax : `matplotlib.axes.Axes`, optional |
347 | 357 | Use the given axes for the plot, otherwise use the current axes. |
348 | 358 |
|
@@ -357,6 +367,9 @@ def vectorfield( |
357 | 367 | Default is set by `config.defaults['ctrlplot.rcParams']`. |
358 | 368 | suppress_warnings : bool, optional |
359 | 369 | If set to True, suppress warning messages in generating trajectories. |
| 370 | + zorder : float, optional |
| 371 | + Set the zorder for the separatrices. In not specified, it will be |
| 372 | + automatically chosen by `matplotlib.axes.Axes.quiver`. |
360 | 373 |
|
361 | 374 | """ |
362 | 375 | # Process keywords |
@@ -427,31 +440,35 @@ def streamplot( |
427 | 440 | dict with key 'args' and value given by a tuple (passed to callable). |
428 | 441 | color : matplotlib color spec, optional |
429 | 442 | Plot the vector field in the given color. |
430 | | - vary_color : bool, optional |
431 | | - If set to True, vary the color of the streamlines based on the magnitude |
432 | | - vary_linewidth : bool, optional. |
433 | | - If set to True, vary the linewidth of the streamlines based on the magnitude. |
434 | | - cmap : str or Colormap, optional |
435 | | - Colormap to use for varying the color of the streamlines. |
436 | | - norm : `matplotlib.colors.Normalize`, optional |
437 | | - An instance of Normalize to use for scaling the colormap and linewidths. |
438 | | - zorder : float, optional |
439 | | - Set the zorder for the separatrices. In not specified, it will be |
440 | | - automatically chosen by `matplotlib.axes.Axes.streamplot`. |
441 | 443 | ax : `matplotlib.axes.Axes`, optional |
442 | 444 | Use the given axes for the plot, otherwise use the current axes. |
443 | 445 |
|
444 | 446 | Returns |
445 | 447 | ------- |
446 | 448 | out : StreamplotSet |
| 449 | + Containter object with lines and arrows contained in the |
| 450 | + streamplot. See `matplotlib.axes.Axes.streamplot` for details. |
447 | 451 |
|
448 | 452 | Other Parameters |
449 | 453 | ---------------- |
| 454 | + cmap : str or Colormap, optional |
| 455 | + Colormap to use for varying the color of the streamlines. |
| 456 | + norm : `matplotlib.colors.Normalize`, optional |
| 457 | + Normalization map to use for scaling the colormap and linewidths. |
450 | 458 | rcParams : dict |
451 | 459 | Override the default parameters used for generating plots. |
452 | 460 | Default is set by `config.default['ctrlplot.rcParams']`. |
453 | 461 | suppress_warnings : bool, optional |
454 | 462 | If set to True, suppress warning messages in generating trajectories. |
| 463 | + vary_color : bool, optional |
| 464 | + If set to True, vary the color of the streamlines based on the |
| 465 | + magnitude of the vector field. |
| 466 | + vary_linewidth : bool, optional. |
| 467 | + If set to True, vary the linewidth of the streamlines based on the |
| 468 | + magnitude of the vector field. |
| 469 | + zorder : float, optional |
| 470 | + Set the zorder for the separatrices. In not specified, it will be |
| 471 | + automatically chosen by `matplotlib.axes.Axes.streamplot`. |
455 | 472 |
|
456 | 473 | """ |
457 | 474 | # Process keywords |
@@ -466,7 +483,8 @@ def streamplot( |
466 | 483 | # Determine the points on which to generate the streamplot field |
467 | 484 | points, gridspec = _make_points(pointdata, gridspec, 'meshgrid') |
468 | 485 | grid_arr_shape = gridspec[::-1] |
469 | | - xs, ys = points[:, 0].reshape(grid_arr_shape), points[:, 1].reshape(grid_arr_shape) |
| 486 | + xs = points[:, 0].reshape(grid_arr_shape) |
| 487 | + ys = points[:, 1].reshape(grid_arr_shape) |
470 | 488 |
|
471 | 489 | # Create axis if needed |
472 | 490 | if ax is None: |
@@ -484,25 +502,29 @@ def streamplot( |
484 | 502 |
|
485 | 503 | # Generate phase plane (quiver) data |
486 | 504 | sys._update_params(params) |
487 | | - us_flat, vs_flat = np.transpose([sys._rhs(0, x, np.zeros(sys.ninputs)) for x in points]) |
| 505 | + us_flat, vs_flat = np.transpose( |
| 506 | + [sys._rhs(0, x, np.zeros(sys.ninputs)) for x in points]) |
488 | 507 | us, vs = us_flat.reshape(grid_arr_shape), vs_flat.reshape(grid_arr_shape) |
489 | 508 |
|
490 | 509 | magnitudes = np.linalg.norm([us, vs], axis=0) |
491 | 510 | norm = norm or mpl.colors.Normalize() |
492 | 511 | normalized = norm(magnitudes) |
493 | | - cmap = plt.get_cmap(cmap) |
| 512 | + cmap = plt.get_cmap(cmap) |
494 | 513 |
|
495 | 514 | with plt.rc_context(rcParams): |
496 | 515 | default_lw = plt.rcParams['lines.linewidth'] |
497 | 516 | min_lw, max_lw = 0.25*default_lw, 2*default_lw |
498 | | - linewidths = normalized * (max_lw - min_lw) + min_lw if vary_linewidth else None |
| 517 | + linewidths = normalized * (max_lw - min_lw) + min_lw \ |
| 518 | + if vary_linewidth else None |
499 | 519 | color = magnitudes if vary_color else color |
500 | 520 |
|
501 | | - out = ax.streamplot(xs, ys, us, vs, color=color, linewidth=linewidths, |
502 | | - cmap=cmap, norm=norm, zorder=zorder) |
| 521 | + out = ax.streamplot( |
| 522 | + xs, ys, us, vs, color=color, linewidth=linewidths, cmap=cmap, |
| 523 | + norm=norm, zorder=zorder) |
503 | 524 |
|
504 | 525 | return out |
505 | 526 |
|
| 527 | + |
506 | 528 | def streamlines( |
507 | 529 | sys, pointdata, timedata=1, gridspec=None, gridtype=None, dir=None, |
508 | 530 | zorder=None, ax=None, _check_kwargs=True, suppress_warnings=False, |
@@ -548,9 +570,6 @@ def streamlines( |
548 | 570 | dict with key 'args' and value given by a tuple (passed to callable). |
549 | 571 | color : str |
550 | 572 | Plot the streamlines in the given color. |
551 | | - zorder : float, optional |
552 | | - Set the zorder for the separatrices. In not specified, it will be |
553 | | - automatically chosen by `matplotlib.axes.Axes.plot`. |
554 | 573 | ax : `matplotlib.axes.Axes`, optional |
555 | 574 | Use the given axes for the plot, otherwise use the current axes. |
556 | 575 |
|
@@ -574,6 +593,9 @@ def streamlines( |
574 | 593 | Default is set by `config.defaults['ctrlplot.rcParams']`. |
575 | 594 | suppress_warnings : bool, optional |
576 | 595 | If set to True, suppress warning messages in generating trajectories. |
| 596 | + zorder : float, optional |
| 597 | + Set the zorder for the separatrices. In not specified, it will be |
| 598 | + automatically chosen by `matplotlib.axes.Axes.plot`. |
577 | 599 |
|
578 | 600 | """ |
579 | 601 | # Process keywords |
@@ -672,9 +694,6 @@ def equilpoints( |
672 | 694 | dict with key 'args' and value given by a tuple (passed to callable). |
673 | 695 | color : str |
674 | 696 | Plot the equilibrium points in the given color. |
675 | | - zorder : float, optional |
676 | | - Set the zorder for the separatrices. In not specified, it will be |
677 | | - automatically chosen by `matplotlib.axes.Axes.plot`. |
678 | 697 | ax : `matplotlib.axes.Axes`, optional |
679 | 698 | Use the given axes for the plot, otherwise use the current axes. |
680 | 699 |
|
@@ -687,6 +706,9 @@ def equilpoints( |
687 | 706 | rcParams : dict |
688 | 707 | Override the default parameters used for generating plots. |
689 | 708 | Default is set by `config.defaults['ctrlplot.rcParams']`. |
| 709 | + zorder : float, optional |
| 710 | + Set the zorder for the separatrices. In not specified, it will be |
| 711 | + automatically chosen by `matplotlib.axes.Axes.plot`. |
690 | 712 |
|
691 | 713 | """ |
692 | 714 | # Process keywords |
@@ -720,7 +742,8 @@ def equilpoints( |
720 | 742 | out = [] |
721 | 743 | for xeq in equilpts: |
722 | 744 | with plt.rc_context(rcParams): |
723 | | - out += ax.plot(xeq[0], xeq[1], marker='o', color=color, zorder=zorder) |
| 745 | + out += ax.plot( |
| 746 | + xeq[0], xeq[1], marker='o', color=color, zorder=zorder) |
724 | 747 | return out |
725 | 748 |
|
726 | 749 |
|
@@ -767,9 +790,6 @@ def separatrices( |
767 | 790 | separatrices. If a tuple is given, the first element is used as |
768 | 791 | the color specification for stable separatrices and the second |
769 | 792 | element for unstable separatrices. |
770 | | - zorder : float, optional |
771 | | - Set the zorder for the separatrices. In not specified, it will be |
772 | | - automatically chosen by `matplotlib.axes.Axes.plot`. |
773 | 793 | ax : `matplotlib.axes.Axes`, optional |
774 | 794 | Use the given axes for the plot, otherwise use the current axes. |
775 | 795 |
|
@@ -784,6 +804,9 @@ def separatrices( |
784 | 804 | Default is set by `config.defaults['ctrlplot.rcParams']`. |
785 | 805 | suppress_warnings : bool, optional |
786 | 806 | If set to True, suppress warning messages in generating trajectories. |
| 807 | + zorder : float, optional |
| 808 | + Set the zorder for the separatrices. In not specified, it will be |
| 809 | + automatically chosen by `matplotlib.axes.Axes.plot`. |
787 | 810 |
|
788 | 811 | Notes |
789 | 812 | ----- |
@@ -884,7 +907,8 @@ def separatrices( |
884 | 907 | if traj.shape[1] > 1: |
885 | 908 | with plt.rc_context(rcParams): |
886 | 909 | out += ax.plot( |
887 | | - traj[0], traj[1], color=color, linestyle=linestyle, zorder=zorder) |
| 910 | + traj[0], traj[1], color=color, |
| 911 | + linestyle=linestyle, zorder=zorder) |
888 | 912 |
|
889 | 913 | # Add arrows to the lines at specified intervals |
890 | 914 | with plt.rc_context(rcParams): |
@@ -984,6 +1008,7 @@ def circlegrid(centers, radius, num): |
984 | 1008 | theta in np.linspace(0, 2 * math.pi, num, endpoint=False)]) |
985 | 1009 | return grid |
986 | 1010 |
|
| 1011 | + |
987 | 1012 | # |
988 | 1013 | # Internal utility functions |
989 | 1014 | # |
@@ -1004,6 +1029,7 @@ def _create_system(sys, params): |
1004 | 1029 | return NonlinearIOSystem( |
1005 | 1030 | _update, _output, states=2, inputs=0, outputs=0, name="_callable") |
1006 | 1031 |
|
| 1032 | + |
1007 | 1033 | # Set axis limits for the plot |
1008 | 1034 | def _set_axis_limits(ax, pointdata): |
1009 | 1035 | # Get the current axis limits |
|