GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Reimplementation of 2D phase plots by murrayrm · Pull Request #980 · python-control/python-control

Conversation

@murrayrm

@murrayrm murrayrm commented Mar 28, 2024

Copy link
Copy Markdown
Member

This PR reimplements the phase plot functionality of python-control by creating a new function phase_plane_plot and creating a module phaseplot that provides more specialized functionality. The legacy phase_plot function is still available (with a deprecation warning).

From the documentation:

The default method for generating a phase plane plot is to provide a 2D dynamical system along with a range of coordinates and time limit:

sys = ct.nlsys(
    lambda t, x, u, params: np.array([[0, 1], [-1, -1]]) @ x, 
    states=['position', 'velocity'], inputs=0, name='damped oscillator')
axis_limits = [-1, 1, -1, 1]
T = 8
ct.phase_plane_plot(sys, axis_limits, T)

phaseplot-dampedosc-default

By default, the plot includes streamlines generated from starting points on limits of the plot, with arrows showing the flow of the system, as well as any equilibrium points for the system. A variety of options are available to modify the information that is plotted, including plotting a grid of vectors instead of streamlines and turning on and off various features of the plot.

To illustrate some of these possibilities, consider a phase plane plot for an inverted pendulum system, which is created using a mesh grid:

def invpend_update(t, x, u, params):
    m, l, b, g = params['m'], params['l'], params['b'], params['g']
    return [x[1], -b/m * x[1] + (g * l / m) * np.sin(x[0]) + u[0]/m]
invpend = ct.nlsys(invpend_update, states=2, inputs=1, name='invpend')
    
ct.phase_plane_plot(
    invpend, [-2*pi, 2*pi, -2, 2], 5,
    gridtype='meshgrid', gridspec=[5, 8], arrows=3,
    plot_equilpoints={'gridspec': [12, 9]},
    params={'m': 1, 'l': 1, 'b': 0.2, 'g': 1})
plt.xlabel(r"$\theta$ [rad]")
plt.ylabel(r"$\dot\theta$ [rad/sec]")

phaseplot-invpend-meshgrid

This figure shows several features of more complex phase plane plots: multiple equilibrium points are shown, with saddle points showing separatrices, and streamlines generated along a 5x8 mesh of initial conditions. At each mesh point, a streamline is created that goes 5 time units forward and backward in time. A separate grid specification is used to find equilibrium points and separatrices (since the course grid spacing of 5x8 does not find all possible equilibrium points). Together, the multiple features in the phase plane plot give a good global picture of the topological strucrure of solutions of the dynamical system.

Phase plots can be buit up by hand using a variety of helper functions that are part of the control.phaseplot (pp) module:

import control.phaseplot as pp

def oscillator_update(t, x, u, params):
    return [x[1] + x[0] * (1 - x[0]**2 - x[1]**2),
            -x[0] + x[1] * (1 - x[0]**2 - x[1]**2)]
oscillator = ct.nlsys(
    oscillator_update, states=2, inputs=0, name='nonlinear oscillator')

ct.phase_plane_plot(oscillator, [-1.5, 1.5, -1.5, 1.5], 0.9)
pp.streamlines(
    oscillator, np.array([[0, 0]]), 1.5,
    gridtype='circlegrid', gridspec=[0.5, 6], dir='both')
pp.streamlines(
    oscillator, np.array([[1, 0]]), 2*pi, arrows=6, color='b')
plt.gca().set_aspect('equal')

phaseplot-oscillator-helpers

The following helper functions are available:

  • pp.equilpoints
  • pp.separatrices
  • pp.streamlines
  • pp.vectorfield

The phase_plane_plot function calls these helper functions based on the options it is passed.

Note that unlike other plotting function, phase plane plots do not involve computing a response and then plotting the result via a plot() method. Instead, the plot is generated directly be a call to the phase_plane_plot function (or one of the ct.phaseplot helper functions.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@murrayrm
initial reimplementation of phase plots
3ca12ff
This was linked to issues Mar 28, 2024
phase_plot ODE function signature is not compatible with solve_ivp and StateSpace.dynamics #968
Closed
Strange nondeterministic issues when using phase_plot with X, Y and X0 set #970
Closed
@murrayrm murrayrm mentioned this pull request Mar 28, 2024
Version 0.10 changes (tracking issue) #917
Closed
7 tasks done
@coveralls

coveralls commented Mar 28, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Coverage Status

coverage: 94.467% (+0.05%) from 94.422%
when pulling 748e36a on murrayrm:phaseplots_01Jan2024
into 7a70be1 on python-control:main.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@murrayrm murrayrm added this to the 0.10.0 milestone Mar 28, 2024
slivingston
slivingston reviewed Mar 28, 2024
View reviewed changes
Comment thread control/flatsys/__init__.py Outdated
# Date: 1 Jul 2019

r"""The :mod:`control.flatsys` package contains a set of classes and functions
r"""The :mod:`control.flatsys` module contains a set of classes and functions

@slivingston slivingston Mar 28, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original is correct: flatsys is a package according to standard terms: https://docs.python.org/3/glossary.html#term-package

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread control/phaseplot.py Outdated
import numpy as np
import matplotlib.pyplot as mpl
"""The :mod:`control.phaseplot` module contains functions for generating 2D
phase plots.

@slivingston slivingston Mar 28, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convention is for summary to be 1 line (https://peps.python.org/pep-0257/#multi-line-docstrings).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread examples/phase_plane_plots.py Outdated
@@ -0,0 +1,215 @@
# phase_portraits.py - phase portrait examples

@slivingston slivingston Mar 28, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# phase_portraits.py - phase portrait examples
# phase_plane_plots.py - phase portrait examples

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread doc/Makefile
PYTHONPATH=.. python $<

phaseplot-dampedosc-default.png: ../control/tests/phaseplot_test.py
PYTHONPATH=.. python $<

@slivingston slivingston Mar 28, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the other images generated by phaseplot_test.py (phaseplot-oscillator-helpers.png, phaseplot-invpend-meshgrid.png) be included here? It does not seem important because the PNG files are being committed, but I note this here in case you wanted to.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@murrayrm murrayrm Mar 30, 2024

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that they could be listed, but I didn't list them all out since triggering on the one file will update all files => if the test generation script is updated, all PNG files are updated.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@slivingston slivingston Mar 30, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I will complete review tomorrow.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
sawyerbfuller
sawyerbfuller requested changes Mar 29, 2024
View reviewed changes

@sawyerbfuller sawyerbfuller left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes some very outdated code. Very nice!

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread control/phaseplot.py Outdated
return new_kwargs

# Create array for storing outputs
out = np.empty(3, dtype=object)

@sawyerbfuller sawyerbfuller Mar 29, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring says this is a list. Seems like a list might be better/simpler here than a numpy array, e.g. out = [[], None, None]

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread control/phaseplot.py Outdated

Parameters
----------
sys : NonlinearIOSystem or callable(x, t, ...)

@sawyerbfuller sawyerbfuller Mar 29, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is motivation to maintain the old convention of (x, t, ...) argument order, rather than (t, x, ...) when called with a callable? Backwards compatibility? I am not aware of anywhere else we or scipy adheres to that convention anymore, but I might be missing something.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread control/phaseplot.py Outdated
vfdata = np.zeros((points.shape[0], 4))
for i, x in enumerate(points):
vfdata[i, :2] = x
vfdata[i, 2:] = sys.dynamics(0, x, 0, params=params)

@sawyerbfuller sawyerbfuller Mar 29, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this wouldn't work if sys is just a function - is the callable intended to be a function if it is not a system?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread control/phaseplot.py Outdated
Parameters
----------
sys : NonlinearIOSystem or callable(x, t, ...)
Function used to generate phase plane data.

@sawyerbfuller sawyerbfuller Mar 29, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relatedly, should there be a statement here that only two-state systems can be accepted? (and a test that gives a helpful error?)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@murrayrm
address @sliivingston, @sawyerbfuller comments
c41d472
@murrayrm

murrayrm commented Mar 30, 2024

Copy link
Copy Markdown
Member Author

In addition to adressing comments, functionality for handling callables (versus systems) was missing; now added.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

sawyerbfuller
sawyerbfuller reviewed Mar 30, 2024
View reviewed changes
Comment thread control/phaseplot.py Outdated
# Create reverse time system, if needed
if dir != 'forward':
revsys = NonlinearIOSystem(
lambda t, x, u, params: -np.array(sys.updfcn(t, x, u, params)),

@sawyerbfuller sawyerbfuller Mar 30, 2024

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lambda t, x, u, params: -np.array(sys.updfcn(t, x, u, params)),
lambda t, x, u, params: -np.asarray(sys.updfcn(t, x, u, params)),

This won't re-create the array if it is already one.

relatedly, and this is more of a question: I am confused about the use of each of these methods, all of which do roughly the same thing. Is this correct: _rhs fast and low-level, updfcn just a reference to the function that was passed in, and dynamics: user-friendly function that may not be as fast? Just checking that this is the right one (speed is kind of important in this application)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@murrayrm murrayrm Mar 30, 2024

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct on the various versions. In general:

  • _updfcn() should not be called directly (though OK here, because we are creating a new system).
  • _rhs() should be called inside loops (with a call to _update_params() before the loop starts, which sets the parameter values)
  • dynamics() is the user-callable function (which calls _update_params and then _rhs)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
sawyerbfuller
sawyerbfuller approved these changes Mar 30, 2024
View reviewed changes

@sawyerbfuller sawyerbfuller left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@murrayrm
small updates to address @sawyerbfuller comments on updfcn
edb444e
slivingston
slivingston approved these changes Mar 31, 2024
View reviewed changes

@slivingston slivingston left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! I found a few more misprints, but nothing critical. Locally I merged this into the current tip of main branch and successfully ran the tests and example code, and built the documentation.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread examples/phase_plane_plots.py Outdated
# RMM, 25 Mar 2024
#
# This file contains a number of examples of phase plane plots generated
# using the phaseplot module. Most of these figures lines up with examples

@slivingston slivingston Mar 31, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# using the phaseplot module. Most of these figures lines up with examples
# using the phaseplot module. Most of these figures line up with examples

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread control/phaseplot.py Outdated
phase plots. The base function for creating phase plane portraits is
:func:`~control.phase_plane_plot`, which generates a phase plane portrait
for a 2 state I/O system (with no inputs). In addition, several other
functions are available to creat ecustomize phase plane plots:

@slivingston slivingston Mar 31, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
functions are available to creat ecustomize phase plane plots:
functions are available to customize phase plane plots:

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread control/phaseplot.py Outdated
Parameters
----------
sys : NonlinearIOSystem or callable(t, x, ...)
I/O systems or function used to generate phase plane data. If a

@slivingston slivingston Mar 31, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
I/O systems or function used to generate phase plane data. If a
I/O system or function used to generate phase plane data. If a

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread doc/plotting.rst Outdated
The :func:`~control.phase_plane_plot` function calls these helper functions
based on the options it is passed.

Note that unlike other plotting function, phase plane plots do not involve

@slivingston slivingston Mar 31, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Note that unlike other plotting function, phase plane plots do not involve
Note that unlike other plotting functions, phase plane plots do not involve

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread control/phaseplot.py Outdated
plot_separatrices : bool or dict
If `True` (default) then plot separatrices starting from each
equilibrium point. If set to a dict, pass on the key-value pairs
in the dict as keywords to :func:`~control.phaseplot.streamlines`.

@slivingston slivingston Mar 31, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
in the dict as keywords to :func:`~control.phaseplot.streamlines`.
in the dict as keywords to :func:`~control.phaseplot.separatrices`.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread control/phaseplot.py Outdated

import numpy as np
import matplotlib.pyplot as mpl
# Create a system form a callable

@slivingston slivingston Mar 31, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Create a system form a callable
# Create a system from a callable

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread control/phaseplot.py Outdated
Comment on lines +792 to +793
# if not isinstance(pointdata, (list, tuple)) or len(pointdata) != 4:
# raise ValueError("invalid grid data specification")

@slivingston slivingston Mar 31, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# if not isinstance(pointdata, (list, tuple)) or len(pointdata) != 4:
# raise ValueError("invalid grid data specification")

This can be deleted, or the check in the comment can be expanded to handle case of isinstance(pointdata, np.ndarray).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@murrayrm
fix typos + other small fixes from @slivingston
748e36a
@murrayrm

murrayrm commented Mar 31, 2024

Copy link
Copy Markdown
Member Author

Thanks for the careful reviews, @sawyerbfuller and @slivingston! Will merge as soon as checks finish up (just in case).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@murrayrm
murrayrm merged commit 8983e39 into python-control:main Mar 31, 2024
@murrayrm
murrayrm deleted the phaseplots_01Jan2024 branch March 31, 2024 05:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@slivingston slivingston slivingston approved these changes
@sawyerbfuller sawyerbfuller sawyerbfuller approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

0.10.0

Development

Successfully merging this pull request may close these issues.

Strange nondeterministic issues when using phase_plot with X, Y and X0 set phase_plot ODE function signature is not compatible with solve_ivp and StateSpace.dynamics

Uh oh!

There was an error while loading. Please reload this page.

4 participants

@murrayrm @coveralls @slivingston @sawyerbfuller
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.