sandbox/easystab/easyPYstab/PhasePortrait.py
# -*- coding: utf-8 -*-Created on Nov 28 2025
@author: D. Fabre (adapted from a previous Matlab program)
- How to use this program :
- Launch the program (in spyder, after switching graphical mode to “automatic”)
- Specify the “problem type” you want to consider, among a list of predefined cases
- Set the parameters of the problem (type enter to use default value)
- Click on the figures to draw trajectories in phase space (left-click = forward trajectory, right-click = backward trajectroy)
- Remarque importante : Pour utiliser ce programme de manière
optimale il faut modifier le mode graphique de Python. Pour cela (avec
Spyder) :
- Ouvrir le menu “Preferences / Ipython Console / Graphics”
- Dans le menu “graphics backend” sélectionner “automatic”’
import numpy as np
import math
from scipy.integrate import odeint
import matplotlib.pyplot as plt
plt.close("all")
def myinput(question,default):
# Fonction pour selection de parametre avec valeur par defaut
answer = input(f"{question} [default : {default}] : " )
if not answer:
return default
else:
return answerSelection of problem type
print("#####################################################\n")
print(" Phase portrait generator for 2D dynamical systems\n")
print("#####################################################\n")
print("Select a dynamical system among the following list :")
print(' "Pendulum" \n "Brusselator" \n "VanDerPol" ')
print(' (or add you own case in the program) \n')
problemtype = str(myinput("Your case : ","Pendulum"))
print(">>> Your choice : ",problemtype)
print(">>> Selection of parameters :")
Problem = {"type": problemtype} ;Big function defining the available problems
def F_PortraitdePhase(P,X):this function defines the function F for the RHS of the dynamical
system Usage of this function : At first call, Problem =
F_PortraitdePhase(P,0) the first argument is expected as a dictionary ,
the second is unused and the output is a dictionary containing the
parameters of the problem
At next calls, : F = F_PortraitdePhase(Problem,X) the first argument is
the dictionary, the second is vector X (dimension 2) and the output is
the dynamical function F
x1 = X[0]; x2 = X[1];Selection according to the problem type
if (P["type"]=="Pendulum"):Case “Pendulum” : basic damped pendulum
if not "Init" in P.keys():
# at first call : define parameters and ranges for figure
P["mu"] = float(myinput("damping parameter mu = ",.3)); # damping parameter
P["omega0"] = float(myinput("frequency omega0",1)); # frequency
P["xmin"] = float(myinput("xmin",-3*np.pi));
P["xmax"] = float(myinput("xmax",3*np.pi));
P["ymin"] = float(myinput("ymin",-2*np.pi));
P["ymax"] = float(myinput("ymax",2*np.pi));
P["Init"] = "OK";
return P
else:
# Equation for the motion of a damped pendulum.
#
# m L^2 \ddot \theta = - \mu_f \dot \theta - m g \sin \theta
#
# Considering this problem as a dynamical system of order 2 for
# $X = [x_1 ; x_2] = [\theta, \dot \theta]$ leads to :
#
# d/dt [x_1 ; x_2] = [ x_2 ; - \mu x_2 - \omega_0^2 \sin(x_1) ]
#
# with $\omega_0^2 = \sqrt{\frac{g}{L}}$ ; $\mu = \frac{\mu_f}{M L^2}$.
F = [x2 , -P["omega0"]**2*np.sin(x1)-P["mu"]*x2];
return F
elif (P["type"]=="VanDerPol"): # VanDerPol is well-known model of self-sustained oscillatorCase “VanDerPol” : a classical model of nonlinear oscillator
if not "Init" in P.keys():
# at first call : define parameters and ranges for figure
P["omega0"] = float(myinput("frequency omega0",1)); # frequency
P["r"] = float(myinput("damping rate r ",0.3)); # coef of linear term
P["delta"] = float(myinput("nonlinear coeff delta",1)); # coef of nonlinear term
print("Range for figure:")
P["xmin"] = float(myinput("xmin",-3)) ;
P["xmax"] = float(myinput("xmax",3));
P["ymin"] = float(myinput("ymin",-3));
P["ymax"] = float(myinput("ymax",3));
P["Init"] = "OK";
return P
else:
# The VanDerPol oscillator is a well-known model of self-sustained
# oscillator. It is defined as follows :
#
# $$ m \ddot x +\omega_0^2 x = (r - \delta x^2) \dot x$$
#
F = [x2 , -P["omega0"]**2*x1+(P["r"]-P["delta"]*x1**2)*x2];
return F
elif (P["type"]=="Brusselator"):Case “Brusselator”, a model for funny chemical reactions
if not "Init" in P.keys():
P["alpha"] = float(myinput("parameter alpha",1));
P["beta"] = float(myinput("parameter beta",1));
print("Range for figure:")
P["xmin"] = float(myinput("xmin",0)) ;
P["xmax"] = float(myinput("xmax",6));
P["ymin"] = float(myinput("ymin",0));
P["ymax"] = float(myinput("ymax",6));
P["Init"] ="OK";
return P
else:
#
# The 'Brusselator' (more precisely the homogeneous brusselator) is a simple model
# for a chemical reaction displaying unsteadiness. It is defined as follows :
#
# $$ \dot x_1 = -(\beta + 1) x_1 + x_1^2 x_2 + \alpha ;$$
# $$\dot x_2 = \beta x_1 - x_1^2 x_2 ;$$
#
F = [-(P["beta"]+1)*x1+x1**2*x2+P["alpha"], P["beta"]*x1-x1**2*x2] ;
return FMain program
Trace de la figure et du champ de vecteur “flow”
# Initialisation, to set the parameters of the problem
Problem = F_PortraitdePhase( {"type": problemtype} ,[0,0])
# Construction d'une grille de 21x21 points dans ces limites
xP = np.linspace(Problem["xmin"], Problem["xmax"], 21)
yP = np.linspace(Problem["ymin"], Problem["ymax"], 21)
[xG, yG] = np.meshgrid(xP,yP);
ux = np.zeros((21,21)); uy = np.zeros((21,21));
# Calcul du champ de vecteur sur cette grille pour visualisation sur figure
for i in range(len(xP)):
for j in range(len(yP)):
F = F_PortraitdePhase(Problem,[xP[i],yP[j]]);
ux[j,i] = F[0];
uy[j,i] = F[1];
# Affichage du champ de vecteurs
fig,ax = plt.subplots()
ax.quiver(xG, yG, ux, uy, color='b')
ax.set_xlim(Problem["xmin"], Problem["xmax"])
ax.set_ylim(Problem["ymin"], Problem["ymax"])
# Ajout d'un titre et d'une legende
plt.title('Phase portrait of the model problem "'+Problem["type"]+ '"')
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()
# Definition de certains parametres
Tmax = 100; # temps maximal pour l'integration de la trajectoire
Tmaxplot = 5 ;# temps maximal pour l'affichage dans la figure 2
Nstep = 10000 ; # number of steps in integration
button = 1
compteur = 0Fonction qui sera appelee lorsqu’on clique sur la figure
def on_click(event):
if event.button == 1:
# Left-click: compute a forward (future) trajectory and draw with plain line
xinit = np.array([event.xdata, event.ydata])
plt.figure(1)
t = np.linspace(0, Tmax, Nstep)
xtraj = np.zeros((2,Nstep))
xtraj = odeint(lambda x, t: F_PortraitdePhase(Problem,x), xinit, t)
plt.plot(xtraj[:,0], xtraj[:,1], 'r', xtraj[0,0], xtraj[0,1], 'ro')
plt.pause(0.01);
plt.show()
if event.button == 3:
# Right-click: compute a backwards (past) trajectory and draw with dotted line
t = np.linspace(0, -Tmax, Nstep)
xinit = np.array([event.xdata, event.ydata])
plt.figure(1)
xtrajB = np.zeros((2,Nstep))
xtrajB = odeint(lambda x, t: F_PortraitdePhase(Problem,x), xinit, t)
plt.plot(xtrajB[:,0], xtrajB[:,1], 'r:')
plt.pause(0.01);
plt.show()
## Association de la fonction on_click a la figure
fig.canvas.mpl_connect('button_press_event', on_click)
plt.savefig('PhasePortrait.png', dpi=80)
plt.show()APPENDIX: the original function in Matlab with lots of other cases to reintroduce…
function F = F_PortraitdePhase(X)
% Fonction pour tracer le portrait de phase d’un systeme 2-2 de la forme % d X /d T = F(X). % global typeproblem xmin xmax ymin ymax; x1 = X(1); x2 = X(2);
switch (typeproblem)
%{
Case “Pendulum” :
Equation for the motion of a damped pendulum.
m L^2 \ddot \theta = - \mu_f \dot \theta - m g \sin \theta
Considering this problem as a dynamical system of order 2 for X = [x_1 ; x_2] = [\theta, \dot \theta] leads to :
\frac{ d}{dt} [x_1 ; x_2] = [ x_2 ; - \mu x_2 - \omega_0^2 \sin(x_1) ]
with \omega_0^2 = \sqrt{\frac{g}{L}} ; \mu = \frac{\mu_f}{M L^2}.
Exercice : Observe the structure of the phase portrait for various values of the damping parameter mu. What do we observe for mu=0 ?
case('Pendulum')
mu = 0;
omega0 = 1; % omega_0 = sqrt(g/L)
F = [x2;-omega0^2*sin(x1)-mu*x2];
% adjust the figure axes
xmin = -3*pi ; xmax = 3*pi; ymin = -2*pi; ymax = 2*pi;
Case “RotatingPendulum” :
Equation for the motion of a pendulum with imposed precession
m L^2 \ddot \theta = - \mu_f \dot \theta - m g L \sin \theta + m L^2 \Omega^2 \sin \theta \cos \theta
Considering this problem as a dynamical system of order 2 for X = [x_1 ; x_2] = [\theta, \dot \theta] leads to :
\frac{ d}{dt} [x_1 ; x_2] = [ x_2 ; - \mu x_2 - \omega_0^2 \sin x_1 (1 - R \cos x_1 ) ]
with \omega_0^2 = \sqrt{\frac{g}{L}} ; \mu = \frac{\mu_f}{M L^2} ; R = \frac{\Omega^2}{\omega^2}.
Exercice : Observe the structure of the phase portrait for various values of the rotation parameter R. Draw the corresponding bifurcation diagram.
case('RotatingPendulum')
omega0 = 1; mu = .5;
R = 1.1;
F = [x2;-omega0^2*sin(x1)*(1-R*cos(x1))-mu*x2];
% adjust the figure axes
xmin = -pi/2 ; xmax = pi/2; ymin = -.5; ymax =.5;
Case “InvertedPendulum” :
Equation for the motion of an inverted pendulum with a spring (offset angle alpha)
m L^2 \ddot \theta = - \mu_f \dot \theta + m g L \sin \theta - K (\theta-\alpha)
Considering this problem as a dynamical system of order 2 for X = [x_1 ; x_2] = [\theta, \dot \theta] leads to :
\frac{ d}{dt} [x_1 ; x_2] = [ x_2 ; - \mu x_2 + \omega_0^2 \sin x_1 - k ( x_1- \alpha) ]
with \omega_0^2 = \sqrt{\frac{g}{L}} ; \mu = \frac{\mu_f}{m L^2} ; k = \frac{K}{m L^2}.
Exercice : Observe the structure of the phase portrait for various values of the offset angle alpha. Draw the corresponding bifurcation diagram.
case('InvertedPendulum')
omega0 = 1; mu = .5;k = .9;
alpha = -0.025;
F = [x2;+omega0^2*sin(x1)-k*(x1-alpha)-mu*x2];
% adjust the figure axes
xmin = -pi/2 ; xmax = pi/2; ymin = -.5; ymax =.5;
Case ‘VanDerPol’ :
The VanDerPol oscillator is a well-known model of self-sustained oscillator. It is defined as follows :
m \ddot x +\omega_0^2 x = (r - \delta x^2) \dot x
Exercice : Observe the structure of the phase portrait for various choices of the parameters. Reconstruct the corresponding bifurcation diagram in terms of the bifurcation parameter r.
case('VanDerPol');
omega0 = 1;
r = 0.5;
delta = -1;
F = [x2 ; -omega0^2*x1+(r-delta*x1^2)*x2];
% adjust the figure axes
xmin = -3 ; xmax = 3; ymin = -3; ymax = 3;
Case ‘Brusselator’ :
The ‘Brusselator’ (more precisely the homogeneous brusselator) is a simple model for a chemical reaction displaying unsteadiness. It is defined as follows :
\dot x_1 = -(\beta + 1) x_1 + x_1^2 x_2 + \alpha ; \dot x_2 = \beta x_1 - x_1^2 x_2 ;
Exercice : Observe the structure of the phase portrait for various choices of the parameters. Construct the bifurcation diagram as function of the bifurcation parameter beta.
case('Brusselator')
alpha = 1;
beta = 2.2;
F = [-(beta+1)*x1+x1^2*x2+alpha ; beta*x1-x1^2*x2] ;
% adjust the figure axes
xmin = 0 ; xmax = 6; ymin = 0; ymax = 6;
Case ‘LotkaVolterra’ :
The Lotka-Volterra is a simple system representing the competition of two animal species (for instance hares and lynxes). It is defined as follows :
\dot x_1 = x_1( \alpha - \beta x_2) ; \dot x_2 = x_2( -\gamma + \delta x_1) ;
Exercice : Observe the structure of the phase portrait for various choices of the parameters. Is this problem conservative or not ?
case('LotkaVolterra')
% we take the same values as wikipedia...
alpha = 2/3;
beta = 4/3;
gamma = 1;
delta = 1;
F = [(alpha-beta*x2)*x1 ; (delta*x1-gamma)*x2];
% adjust the figure axes
xmin = 0 ; xmax = 2.5; ymin = 0; ymax = 2.5;
Case ‘BuffaloWolf’ :
This problem is a
\dot x_1 = r x_1 - A x_1 (x_2+ E x_2^2) - B x_1^2; \dot x_2 = -C x_2 + D x_1 (x_2+ E x_2^2) ) ;
Exercice : Observe the structure of the phase portrait for various choices of the parameters. Reconstruct the corresponding bifurcation diagram as the parameter r is varied
case('BuffaloWolf')
r = .4;
A = .3;
B = 0.1;
C = 1;
D = .2;
E = 0.15;
F = [(r*x1 -A*x1*(x2+E*x2^2) - B*x1^2) ; (-C*x2 + D*x1*(x2+E*x2^2)) ];
% adjust the figure axes
xmin = -5 ; xmax = 20; ymin = -5; ymax = 10;
Case ‘Trefethen’ :
This is a simple model which is asymptotically stable but may lead to sucritical transition with very small initial conditions thanks to transient growth.
\dot x_1 = -(1/R) x_1 + x_2 -\sqrt{x_1^2+x_2^2} x_2; \dot x_2 = -(2/R) x_2 + \sqrt{x_1^2+x_2^2} x_1;
Exercice : observe the behaviour of the system for very small values of the initial condition (try with various values of R?)
case('Trefethen')
R = 10;
NL = 1;% select 0 or 1
normX = sqrt(x1^2+x2^2) ;
F1 = -(1/R)*x1 + x2 - NL*normX*x2;
F2 = -(2/R)*x2 + NL*normX*x1;
F = [F1;F2];
% adjust the figure axes
xmin = -1 ; xmax = 1; ymin = -1; ymax = 1;
Case ‘SH_5.1’ :
This case is a variant of Trefethen’s original model, and is taken from Schmid & Henningson (Exercice 5.1, p. 525)
case('SH_5.1')
R = 10;
NL = 0;% select 0 or 1
normX = sqrt(x1^2+x2^2) ;
F1 = -(1/R)*x1 + NL*normX*x2;
F2 = -(2/R)*x2 + x1 - NL*normX*x1;
F = [F1;F2];
% adjust the figure axes
xmin = -1 ; xmax = 1; ymin = -1; ymax = 1;
Case ‘SaddleNode’ :
We investigate a saddle-node bifurcation in dimension 2 by considering the following model:
m \ddot x + \mu \dot x = r - x^2
If the friction is dominant over the inertia this equation reduces to the classical one-dimensional saddle-node bifurcation.
Exercice : Observe the structure of the phase portrait for various choices of the parameters. Reconstruct the corresponding bifurcation diagram.
case('SaddleNode')
mass = .5; friction = 1; gravity = 1;
r= -.1;
F = [x2 ; -friction*x2/mass + (r-x1^2)*gravity];
% adjust the figure axes
xmin = -3 ; xmax = 3; ymin = -1; ymax = 1;Case ‘Pitchfork’ :
We investigate a transcritical bifurcation in dimension 2 by considering the following model:
m \ddot x + \mu \dot x = r x - \delta x^3
If the friction is dominant over the inertia this equation reduces to the classical one-dimensional pitchfork bifurcation.
Exercice : Observe the structure of the phase portrait for various choices of the parameters. Reconstruct the corresponding bifurcation diagram.
case('Pitchfork')
mass = .5; friction = 2;
r= .5; delta = 1;
F = [x2 ; -friction*x2/mass+(r*x1-delta*x1^3)/mass];
% adjust the figure axes
xmin = -1 ; xmax = 1; ymin = -1; ymax = 1;
Case ‘TransCritical’ :
We investigate a transcritical bifurcation in dimension 2 by considering the following model:
m \ddot x + \mu \dot x = r x - x^2
If the friction is dominant over the inertia this equation reduces to the classical one-dimensional transcritical bifurcation.
Exercice : Observe the structure of the phase portrait for various choices of the parameters. Reconstruct the corresponding bifurcation diagram.
case('TransCritical')
mass = .5; friction = 1;
r= .5;
F = [x2 ; -friction*x2/mass+(r*x1-x1^2)/mass];
% adjust the figure axes
xmin = -1 ; xmax = 1; ymin = -.5; ymax = .5;
Case ‘Exo3.1’ :
case('Exo3.1')
F = [x1*(1-x1) ; x2*(2-4*x1)];
% adjust the figure axes
xmin = -.5 ; xmax = 1.5; ymin = -.5; ymax = 1.5;
Case ‘Exam2021’ :
case('Exam2021')
r = -.9; A = 100; B = 1;
F = [x1*(r+1-(x1-1)^2)+A*x2 ; -B*x2];
% adjust the figure axes
xmin = -.5 ; xmax = 1.6; ymin = -.01; ymax = .02;
case('Exam2025')
r = .5; A = 0;
F = [x1*(r-x1^2)*(1-x1^2)+A*x2 ; -x2];
% adjust the figure axes
xmin = -1.5 ; xmax = 1.5; ymin = -1; ymax = 1;
case('Custom')
% add your own case here !
otherwise
error(['Error in PhasePortrait_NonLinear : unknown type ', typeproblem]);
end%swith
end%function