In this very first tutorial, we will introduce the basic setups for computation and visualization of the Lagrangian descriptor for the dynamics of 1DoF continuous dynamical systems: autonomous and nonautonomous (time-dependent).
First, we will look at the Hamilton centre system, a well-known autonomous system, enumerating every step.
Then, additional examples of other Hamiltonian systems are added to reinforce the logic and for you to play around with them!
In this first example, we look at the centre system: a Hamiltonian autnomous system described by the following Hamiltonian function, identified as the energy of the system, and its vector field given by Hamilton's equations of motion.
Energy
\begin{equation*} H(x, p_x) = \frac{\omega}{2} (p_x^2 + x^2) \;, \;\; \omega > 0 \end{equation*}Vector field
\begin{align*} \dot{x} &= \frac{\partial H}{\partial p_x} = f_1(x, p_x) = \omega p_x \\ \dot{p}_x &= -\frac{\partial H}{\partial x} = f_2(x, p_x) = -\omega x \end{align*}The origin of this system is a stable equilibrium point, which unlike other example systems provided in ldds does not have stable and unstable invariant manifolds.
FIRST
We begin by importing the basic functions from the ldds module to calculate and plot the Lagrangian descriptors (LDs) of the dynamics of the system, and also the function that define its vector field.
from ldds.base import compute_lagrangian_descriptor
from ldds.tools import draw_all_lds
from ldds.vector_fields import HamCenter1D
NOTE When running this notebook, if you haven't installed
lddsthen, you might get an import error from Python. Try out the following lines to fix that, restart the notebook, and then try importing the above functions again.
import os, sys
import numpy as np
sys.path.insert(1, os.pardir)
SECOND
Define the paramaters for a 2D rectangular grid for visualization of the Lagrangian descriptor in the plane $x-p_x$, from which initial conditions to solve the dynamics are taken too.
x_min,x_max = [-1.6, 1.6] # x axis limits
y_min,y_max = [-1.6, 1.6] # p_x axis limits
Nx, Ny = [100, 100] # Number of points per axis
grid_parameters = [(x_min, x_max, Nx), (y_min, y_max, Ny)]
NOTE We recomend starting calculations with a coarse grid first to prevent overloading your computer, for example:
Nx, Ny = [100,100]or less, and subsequently increasing the resolution or recentering the grid.
THIRD
Define the paramaters for integration of the dynamics: the integration time, $\tau$, and the $p$-value, determining the deifnition of the Lagrangian descriptor function to use.
# Integration parameters
tau = 12
# LDp, p-value
p_value = 0.5
NOTE For further info on different versions of the Lagrangian descriptor function. Look at this online reference. Here.
FOURTH
Define the vector field function describing the dynamics.
vector_field = HamCenter1D
LDDS provides a thorough description of every vector field function, which can be displayed using help.
help(HamCenter1D)
Help on function HamCenter1D in module ldds.vector_fields:
HamCenter1D(t, u, PARAMETERS=[1])
Returns vector field for a 1DoF centre at time t, for an array of points in phase space.
Number of model parameters: 1 . PARAMETERS = [omega]
Functional form: v = (omega*y, - omega*x), with u = (x, y)
Parameters
----------
t : float
Time. (This vector field is independent of time.)
u : ndarray, shape(n,)
Points in phase space.
PARAMETERS : list of floats
Vector field parameters.
Returns
-------
v : ndarray, shape(n,)
Vector field at points u and time t.
This is particularly helpful if you want to change PARAMETERS of the system, which can be done constructing vector_field from HamCenter1D as below
my_omega_value = 1
vector_field = lambda t, u : HamCenter1D(t, u, PARAMETERS=[my_omega_value])
NOTE Here, we imported the function
HamCenter1Dthat lives in the moduleldds.vector_fields. LDDS can also accept user-defined functions, as it will be seen later in Tutorial 4
FIFTH
Compute the forward and backward Lagrangian descriptors passing all the above parameters to compute_lagrangian_descriptor.
Note that for the backwards LD the integration time is passed with a negative sign, since the dynamics has to be solved "backwards in time" for this LD.
LD_forward = compute_lagrangian_descriptor(grid_parameters, vector_field, tau, p_value)
LD_backward = compute_lagrangian_descriptor(grid_parameters, vector_field, -tau, p_value)
SIXTH
Visualise the Lagrangian descriptor and its gradient using the draw_all_lds function.
This will return two contour plots, one of LD values and another of its gradient magnitude over each point in the 2D grid that we defined initially.
figs = draw_all_lds(LD_forward, LD_backward, grid_parameters, tau, p_value)
SEVENTH (OPTIONAL)
The graphical output provided by draw_all_lds can be further modified, since it returns the plot handles of the figure. So, typical modifications to Python matplotlib objects apply.
Below we show how the first row of plots can be customised from the outputted subplot handles.
# choose LD forwards subplots
fig, ax = figs[0]
# set new axes labels
ax[0].set_xlabel(r"$q$", fontsize=18)
ax[0].set_ylabel(r"$p$", fontsize=18)
ax[1].set_xlabel(r"$q$", fontsize=18)
ax[1].set_ylabel(r"$p$", fontsize=18)
# set new super-title
suptitle = "My LDDS output"
fig.suptitle(suptitle)
# visualise
fig
Next, we introduce extra examples of computation of LDs for other autonomous and nonautonomous dynamical systems.
For simplification we just present the executable cells with all the necessary input parameters and variables.
Feel free to change all the different parameters as an exercise to see their influence on the graphical output of the LDs.
Energy
\begin{equation*} H(x, p_x) = \frac{\lambda}{2}( p_x^2 - x^2 ) \; , \;\; \lambda > 0 \end{equation*}Vector field
\begin{align*} \dot{x} &= \frac{\partial H}{\partial p_x} = f_1(x, p_x) = \lambda p_x \\ \dot{p}_x &= -\frac{\partial H}{\partial x} = f_2(x, p_x) = \lambda x \end{align*}from ldds.vector_fields import HamSaddle1D
# Integration parameters
tau = 12
# LDp, p-value
p_value = 0.5
# Mesh parameters
x_min,x_max = [-1.6, 1.6]
y_min,y_max = [-1.6, 1.6]
Nx, Ny = [100, 100]
grid_parameters = [(x_min, x_max, Nx), (y_min, y_max, Ny)]
vector_field = HamSaddle1D
LD_forward = compute_lagrangian_descriptor(grid_parameters, vector_field, tau, p_value)
LD_backward = compute_lagrangian_descriptor(grid_parameters, vector_field, -tau, p_value)
figs = draw_all_lds(LD_forward, LD_backward, grid_parameters, tau, p_value)
Energy
\begin{equation*} H(x, p_x) = \dfrac{1}{2}p_x^2 - \dfrac{1}{2}x^2 + \dfrac{1}{4}x^4 \end{equation*}Vector field
\begin{align*} \dot{x} &= \frac{\partial H}{\partial p_x} = f_1(x, p_x) = p_x \\ \dot{p}_x &= -\frac{\partial H}{\partial x} = f_2(x, p_x) = x - x^3 \end{align*}from ldds.vector_fields import Duffing1D
# Integration parameters
tau = 12
# LDp, p-value
p_value = 0.5
# Mesh parameters
x_min,x_max = [-1.6, 1.6]
y_min,y_max = [-1, 1]
Nx, Ny = [300, 300]
grid_parameters = [(x_min, x_max, Nx), (y_min, y_max, Ny)]
vector_field = Duffing1D
LD_forward = compute_lagrangian_descriptor(grid_parameters, vector_field, tau, p_value)
LD_backward = compute_lagrangian_descriptor(grid_parameters, vector_field, -tau, p_value)
figs = draw_all_lds(LD_forward, LD_backward, grid_parameters, tau, p_value)
For nonautnomous dynamical systems their vector field is time-dependent.
Since the Lagrangian descriptor is a function of the initial conditions of trajectories at time t0, then if t0 changes, then computed LD values will change too.
By default, LDDS assumes t0 = 0, however, this can be easily changed as we will see later.
NEXT, we introduce a basic example of a 2D nonautonomous system: The Double Gyre.
For this example, the steps to set up the computation of the LDs follow as before. The only fundamental difference is the definition of the vector field, which must be a time-dependent Python function.
Vector field
\begin{align*} \dot{x} &= -\pi A sin(\pi f(t, x)/s) cos(\pi y / s) - \mu x \\ \dot{y} &= \pi A cos(\pi f(t, x)/s) sin(\pi y / s) \frac{df(t,x)}{dx} - \mu y \end{align*}where
\begin{align*} f(t, x) &= \epsilon \cdot sin(\phi t + \psi) x^2 + (1 - 2 \epsilon \cdot sin(\phi t + \psi)) x \\ \frac{df(t,x)}{dx} &= 2 \epsilon \cdot sin(\phi t + \psi) x + (1 - 2 \epsilon \cdot sin(\phi t + \psi)) \end{align*}with $A, \phi, \psi, \mu, s, \epsilon$ constant parameters.
from ldds.vector_fields import DoubleGyre
vector_field = DoubleGyre
# Integration parameters
tau = 15
# LDp, p-value
p_value = 1/2
# Mesh parameters
x_min,x_max = [0, 2]
y_min,y_max = [0, 1]
Nx, Ny = [300, 300]
grid_parameters = [(x_min, x_max, Nx), (y_min, y_max, Ny)]
LD_forward = compute_lagrangian_descriptor(grid_parameters, vector_field, tau, p_value)
LD_backward = compute_lagrangian_descriptor(grid_parameters, vector_field, -tau, p_value)
figs = draw_all_lds(LD_forward, LD_backward, grid_parameters, tau, p_value)
As mentioned above, LDDS takes t0 = 0 by default.
However, this can be changes via the parameter called phase_shift, which is set as equal to zero by default, as seen using Python help.
In general, phase_shift appear for any time-dependent vector field in the vector_fields module.
help(DoubleGyre)
Help on function DoubleGyre in module ldds.vector_fields:
DoubleGyre(t, u, PARAMETERS=[0, 0.25, 6.283185307179586, 0, 0, 1, 0.25])
Returns 2D Double Gyre vector field at time t, for an array of points in phase space.
Number of model parameters: 6 . PARAMETERS = [phase_shift, A, phi, psi, mu, s, epsilon]
Functional form:
vx = -pi*A*sin(pi*f(t + phase_shift, x)/s)*cos(pi*y/s) - mu*x
vy = pi*A*cos(pi*f(t + phase_shift, x)/s)*sin(pi*y/s)*df(t + phase_shift,x)/dx - mu*y
with
f(t, x) = epsilon*sin(phi*t + psi)*x**2 + (1 - 2*epsilon*sin(phi*t + psi))*x
df/dx(t,x) = 2*epsilon*sin(phi*t + psi)*x + (1 - 2*epsilon*sin(phi*t + psi))
u = (x, y)
Parameters
----------
t : float
fixed time-point of vector field, for all points in phase space.
u : array_like, shape(n,)
points in phase space to determine vector field at time t.
PARAMETERS : list of floats
vector field parameters
Returns
-------
v : array_like, shape(n,)
vector field corresponding to points u, in phase space at time t
To change phase_shift we need the redefine the PARAMETERS of the DoubleGyre vector field, as below
phase_shift = 0.5
vector_field = lambda t, u: DoubleGyre(t, u, PARAMETERS=[phase_shift, 0.25, 2*np.pi, 0, 0, 1, 0.25])
Then, all the remaining steps of the set up are done as before
# Integration parameters
tau = 15
# LDp, p-value
p_value = 1/2
# Mesh parameters
x_min,x_max = [0, 2]
y_min,y_max = [0, 1]
Nx, Ny = [300, 300]
grid_parameters = [(x_min, x_max, Nx), (y_min, y_max, Ny)]
LD_forward = compute_lagrangian_descriptor(grid_parameters, vector_field, tau, p_value)
LD_backward = compute_lagrangian_descriptor(grid_parameters, vector_field, -tau, p_value)
figs = draw_all_lds(LD_forward, LD_backward, grid_parameters, tau, p_value)
See the difference between the above output and the one generated using default parameters (when phase_shift = 0).