Plotting#

mappy_ds.plot contains two helpers built on matplotlib: a static plot_trajectory() and an interactive plot2d(). Matplotlib is an optional dependency:

pip install "mappy-ds[plot]"

import mappy_ds.plot works without matplotlib; calling a plotting function then raises ImportError with the install hint.

Trajectories#

Map.trajectory (or image_detail(..., trajectory=True)) returns a Trajectory: a list of TrajectorySegment, one per visited mode whose in_trajectory flag is set. A segment knows its mode name, its kind ("continuous" or "discrete"), the sampled states y of shape (dim, n_points) and, for continuous segments, the sampling times t relative to the start of the segment. Trajectory.y concatenates all segments when they share the same dimension.

Static plots: plot_trajectory#

plot_trajectory() draws the segments of a trajectory on a matplotlib Axes (a new figure if ax is omitted) and returns the Axes. xkey and ykey select the state components; segments whose dimension is too small (for instance on a one-dimensional section) are skipped. styles is either a single LineStyle or a dictionary from mode name to style, with the optional key "_default" for modes it does not list.

import numpy as np
import matplotlib.pyplot as plt
from mappy_ds import Diffeomorphism, mapping
from mappy_ds.plot import LineStyle, plot_trajectory

@mapping(2)
def henon(y, p):
    x, u = y
    return np.array([1 - p["a"] * x**2 + u, p["b"] * x])

henon_map = Diffeomorphism(henon)
params = {"a": 1.4, "b": 0.3}

fig, ax = plt.subplots()
# Each iteration is a two-point segment of the mode "map".
traj = henon_map.trajectory([0.1, 0.1], params, n=30)
plot_trajectory(traj, ax, styles=LineStyle("tab:gray", 0.4))

# The attractor itself: the points of a long orbit.
y, points = np.array([0.1, 0.1]), []
for _ in range(2000):
    y = henon_map.image(y, params)
    points.append(y)
points = np.array(points)
ax.plot(points[100:, 0], points[100:, 1], ".", markersize=1, color="tab:red")
ax.set(xlabel="x", ylabel="y")
../../_images/plotting-1.png

Interactive plots: plot2d#

plot2d() iterates a map forever in a matplotlib animation. Each frame computes one iteration, draws the trajectory segments (unless only_map) and the image point, then continues from the image:

from mappy_ds.plot import LineStyle, PlotConfig, plot2d

config = PlotConfig(
    xlim=(-1.5, 1.5), ylim=(-0.5, 0.5), xlabel="x", ylabel="y",
    only_map=True,
    param_keys=["a", "b"], param_step=0.01,
    point_style=LineStyle("teal", 1.0),
)
anim = plot2d(henon_map, [0.1, 0.1], params, config)

It needs an interactive matplotlib backend. With show=True (the default) it calls matplotlib.pyplot.show() and blocks; with show=False it returns the FuncAnimation immediately — keep a reference to it, or the animation stops.

Controls#

Input

Action

mouse click

Restart from the clicked point. For a two-dimensional (or larger) state, components xkey and ykey are set to the click coordinates. For a one-dimensional state, the coordinate on config.scalar_axis ("x" or "y") becomes the state. A PoincareMap restarts in its start mode.

space or e

Clear the axes.

f

Toggle only_map (draw only the image points, or the trajectories too). Matplotlib’s full-screen binding of f is removed.

p

Select the next parameter of config.param_keys; its name is printed.

up / down

Increase / decrease the selected parameter by config.param_step; the parameters are printed. The map is not reset.

If an iteration fails (for example a continuous mode does not reach its border), a message is printed and the animation pauses until you click a new initial point.

For a PoincareMap, mode= starts in a mode other than initial_mode; later iterations continue from the end mode reached by each image, so maps with several end modes work as expected.

PlotConfig#

PlotConfig is a dataclass; every field has a default.

Field

Default

Meaning

only_map

False

Draw only the image points.

figsize

(6, 6)

Figure size in inches.

xlabel, ylabel

"x", "y"

Axis labels.

xlim, ylim

(-3, 3)

Axis limits.

xkey, ykey

0, 1

State components on the horizontal and vertical axes.

linewidth

1.0

Width of trajectory lines.

markersize

3.0

Size of point markers.

param_keys

[]

Parameters adjustable with the keyboard.

param_index

0

Index into param_keys of the parameter selected first.

param_step

0.1

Increment of one up/down key press.

scalar_axis

"x"

For one-dimensional states, the axis read on click.

trajectory_style

LineStyle() (black, 0.3)

Style of trajectory segments, one for all or one per mode.

point_style

LineStyle("red", 1.0)

Style of image points, chosen by the mode of the last segment.

click_style

LineStyle("blue", 1.0)

Style of the marker at a clicked initial point.

LineStyle#

LineStyle is a frozen dataclass with a matplotlib color (default "black") and an alpha (default 0.3). Per-mode styles are dictionaries; for example, to colour two regions of a switched system differently:

styles = {
    "alp_1": LineStyle("orange", 0.6),
    "alp_2": LineStyle("teal", 0.6),
    "_default": LineStyle("gray", 0.3),
}
PlotConfig(trajectory_style=styles, point_style=styles)