In [1]:
from IPython.display import display, Latex
from IPython.core.display import HTML
css_file = './custom.css'
HTML(open(css_file, "r").read()) 
Out[1]:
In [2]:
import sys #only needed to determine Python version number
print('Python version ' + sys.version)
Python version 3.8.5 (default, Jan 27 2021, 15:41:15) 
[GCC 9.3.0]

M62 TP 2 - Implémentation des schémas "classiques" et étude de la convergence

Rappel de CM : implémentation des schémas d'Euler explicite et implicite

Considérons le problème de Cauchy

trouver la fonction $y \colon I\subset \mathbb{R} \to \mathbb{R}$ définie sur l'intervalle $I=[0,1]$ telle que $$ \begin{cases} y'(t) = -4y(t)+t^2, &\forall t \in I=[0,1],\\ y(0) = 1 \end{cases} $$

  1. Calculer la solution exacte en utilisant le module sympy.
  2. Calculer la solution approchée obtenue avec la méthode d'Euler explicite avec $h=1/N$ et $N=8$ (pour bien visualiser les erreurs);
  3. Même exercice pour la méthode d'Euler implicite.

Correction 1
Calculons la solution exacte en utilisant le module sympy:

In [3]:
%reset -f
%matplotlib inline

import sympy as sym
sym.init_printing()

t  = sym.Symbol('t')
y  = sym.Function('y')
edo= sym.Eq( sym.diff(y(t),t) , -4*y(t)+t**2 )
display(edo)
solgen = sym.dsolve(edo)
display(solgen)

t0=0
y0=1
consts = sym.solve( sym.Eq( y0, solgen.rhs.subs(t,t0)) , dict=True)[0]
display(consts)
solpar=solgen.subs(consts).simplify()
display(solpar)
$\displaystyle \frac{d}{d t} y{\left(t \right)} = t^{2} - 4 y{\left(t \right)}$
$\displaystyle y{\left(t \right)} = \left(C_{1} + \frac{\left(8 t^{2} - 4 t + 1\right) e^{4 t}}{32}\right) e^{- 4 t}$
$\displaystyle \left\{ C_{1} : \frac{31}{32}\right\}$
$\displaystyle y{\left(t \right)} = \frac{t^{2}}{4} - \frac{t}{8} + \frac{1}{32} + \frac{31 e^{- 4 t}}{32}$

On définit la solution exacte à utiliser pour estimer les erreurs:

In [4]:
sol_exacte = sym.lambdify(t,solpar.rhs,'numpy')

Correction 2 et 3
On commence par importer le module matplotlibet la fonction fsolve du module scipy.optimize pour résoudre l'équation implicite présente dans le schéma implicite:

In [5]:
from matplotlib.pylab import *
from scipy.optimize import fsolve

On initialise le problème de Cauchy

In [6]:
# variables globales
t0     = 0
tfinal = 1
y0     = 1

On définit l'équation différentielle : phi est une lambda function qui contient la fonction mathématique $\varphi(t, y)=-4y+t^2$ dépendant des variables $t$ et $y$.

In [7]:
phi = lambda t,y : -4*y+t**2

On écrit les schémas numériques : les valeurs $[u_0,u_1,\dots,u_{N}]$ pour chaque méthode sont contenues dans le vecteur uu.

Schéma d'Euler progressif : $$ \begin{cases} u_0=y_0,\\ u_{n+1}=u_n+h\varphi(t_n,u_n)& n=0,1,2,\dots N-1 \end{cases} $$

In [8]:
def EE(phi, tt, y0):
    h  = tt[1] - tt[0]
    uu = [y0]
    for i in range(len(tt) - 1):
        uu.append(uu[i] + h * phi(tt[i], uu[i]))
    return uu

Schéma d'Euler régressif : $$ \begin{cases} u_0=y_0,\\ u_{n+1}=u_n+h\varphi(t_{n+1},u_{n+1})& n=0,1,2,\dots N-1 \end{cases} $$

Attention : $u_{n+1}$ est solution de l'équation $x=u_n+h\varphi(t_{n+1},x)$, c'est-à-dire un zéro de la fonction (en générale non linéaire) $$x\mapsto -x+u_n+h\varphi(t_{n+1},x)$$

In [9]:
def EI(phi, tt, y0):
    h = tt[1] - tt[0]
    uu = [y0]
    for i in range(len(tt) - 1):
        temp = fsolve(lambda x: -x + uu[i] + h * phi(tt[i + 1], x), uu[i])
        uu.append(temp)
    return uu

On introduit la discrétisation: les nœuds d'intégration $[t_0,t_1,\dots,t_{N}]$ sont contenus dans le vecteur tt.
On a $N+1$ points espacés de $h=\frac{t_N-t_0}{N}$.

On calcule les solutions exacte et approchées en ces points:

In [10]:
N = 8 
tt=linspace(t0,tfinal,N+1)
# print(tt)

yy    = [sol_exacte(t) for t in tt]
# yy    = sol_exacte(tt)  # si vectorisée
uu_ep = EE(phi, tt, y0)
uu_er = EI(phi, tt, y0)

On compare les graphes des solutions exacte et approchées et on affiche le maximum de l'erreur: $$ \max_n \left| y(t_n)-u_n \right| $$

In [11]:
figure(figsize=(18, 7))

subplot(1, 2, 1)
plot(tt, yy, 'b-', tt, uu_ep, 'r-D')
err_ep = max( [abs(uu_ep[i] - yy[i]) for i in range(N)] )
# title(f'Euler explicite - max(|erreur|)= {err_ep:1.10f}') #syntaxe si python >=3.6
title('Euler explicite - max(|erreur|)=%1.10f' %(err_ep)) #"OLD" syntaxe
grid()

subplot(1, 2, 2)
plot(tt, yy, 'b-', tt, uu_er, 'r-D')
err_er = max( [abs(uu_er[i] - yy[i]) for i in range(N)] )
title('Euler implicite - max(|erreur|)=%1.10f' %(err_er)) #"OLD" syntaxe
grid();

Rappel de CM : étude empirique de la convergence des schémas d'Euler explicite et implicite

Considérons le même problème de Cauchy.

  1. On se propose d'estimer empiriquement l'ordre de convergence de la méthode d'Euler explicite.

    • On calcule d'abord la solution approchée avec différentes valeurs de $h_k=1/N_k$
    • Pour chaque valeur de $h_k$, on calcule le maximum de la valeur absolue de l'erreur et on la sauvegarde dans le vecteur err_ep de sort que err_ep[k] contient $e_k=\max_{i=0,\dots,N_k}|y(t_i)-u_{i}|$.
    • Pour estimer l'ordre de convergence on affiche les points (h[k],err_ep[k]) en echèlle logarithmique. On trouve ainsi une droite qui relie l'erreur au pas $k$ à l'erreur au pas $k+1$. Pour estimer la pente globale de cette droite (par des moindres carrés) on utilisers la fonction polyfit avec une régression linéaire.
  2. Même exercice pour estimer l'ordre de convergence de la méthode d'Euler implicite.

Correction

Pour chaque schéma, on calcule la solution approchée avec différentes valeurs de $h_k=1/N_k$ et on sauvegarde les valeurs de $h_k$ dans le vecteur H.

Pour chaque valeur de $h_k$, on calcule le maximum de la valeur absolue de l'erreur et on sauvegarde toutes ces erreurs dans le vecteur err_schema de sort que err_schema[k] contient $e_k=\max_{i=0,\dots,N_k}|y(t_i)-u_{i}|$.

In [12]:
H = []
err_ep = []
err_er = []

N = 10
for k in range(7):
#     N = 2**(k + 3)
    N+=20
    tt = linspace(t0, tfinal, N + 1)
    h = tt[1] - tt[0]
    yy = [sol_exacte(t) for t in tt]
    uu_ep = EE(phi, tt, y0)
    uu_er = EI(phi, tt, y0)
    H.append(h)
    err_ep.append(max([abs(uu_ep[i] - yy[i]) for i in range(len(yy))]))
    err_er.append(max([abs(uu_er[i] - yy[i]) for i in range(len(yy))]))

Pour afficher l'ordre de convergence on utilise une échelle logarithmique, i.e. on représente $\ln(h)$ sur l'axe des abscisses et $\ln(\text{err})$ sur l'axe des ordonnées.
En effet, si $\text{err}=Ch^p$ alors $\ln(\text{err})=\ln(C)+p\ln(h)$.
En échelle logarithmique, $p$ représente donc la pente de la ligne droite $\ln(\text{err})$.

In [13]:
figure(figsize=(10,7))
plot(log(H), log(err_ep), 'r-o', label='Euler Explicite')
plot(log(H), log(err_er), 'g-+', label='Euler Implicite')
# loglog(H, err_ep, 'r-o', label='Euler Explicite')
# loglog(H, err_er, 'g-+', label='Euler Implicite')
xlabel('$\ln(h)$')
ylabel('$\ln(e)$')
legend(bbox_to_anchor=(1.04, 1), loc='upper left')
grid(True)
show()

Pour estimer l'ordre de convergence on doit estimer la pente de la droite qui relie l'erreur au pas $k$ à l'erreur au pas $k+1$ en echelle logarithmique. Pour estimer la pente globale de cette droite (par des moindres carrés) on peut utiliser la fonction polyfit (du module numpy que nous avons déjà importé avec matplotlib.pylab).

In [14]:
print ('Euler progressif %1.2f' %(polyfit(log(H),log(err_ep), 1)[0]))
print ('Euler regressif  %1.2f' %(polyfit(log(H),log(err_er), 1)[0]))
Euler progressif 1.03
Euler regressif  0.98

Exercice : implémentation et étude empirique de la convergence des méthodes d'Euler modifié, RK1_m, de Crank-Nicolson et de Heun

Ajouter l'implémentation et l'étude empirique de la convergence des méthodes d'Euler modifié, RK1_M, de Crank-Nicolson et de Heun .
Pour cela modifier directement le code ci-dessus : les endroits où il faut écrire du code sont indiqués par les commentaire commençant par # !!!!!!.

Correction

On écrit les trois schémas:

Schéma d'Euler modifié: $$ \begin{cases} u_0=y_0,\\ \tilde u = u_n+\frac{h}{2}\varphi(t_n,u_n),\\ u_{n+1}=u_n+h\varphi\left(t_n+\frac{h}{2},\tilde u\right)& n=0,1,2,\dots N-1 \end{cases} $$ qu'on peut réécrire sous la forme $$ \begin{cases} u_0=y_0,\\ k_1 = h\varphi(t_n,u_n),\\ u_{n+1}=u_n+h\varphi\left(t_n+\frac{h}{2}, u_n+\frac{k_1}{2}\right)& n=0,1,2,\dots N-1 \end{cases} $$

In [15]:
def EM(phi,tt, y0):
    h = tt[1]-tt[0]
    uu = [y0]
    for i in range(len(tt)-1):
        k1 = h * phi( tt[i], uu[i] )
        uu.append( uu[i]+h*phi( tt[i]+h/2 , uu[i]+k1/2 ) )
    return uu

Schéma RK1_M: $$ \begin{cases} u_0=y_0,\\ u_{n+1}=u_n+h\varphi\left(\frac{t_n+t_{n+1}}{2},\frac{u_n+u_{n+1}}{2}\right)& n=0,1,2,\dots N-1 \end{cases} $$

In [16]:
def RK1_M(phi,tt,y0):
    h  = tt[1]-tt[0]
    uu = [y0]
    for i in range(len(tt)-1):
        uu.append( fsolve(lambda x: -x+uu[i]+h*phi( (tt[i]+tt[i+1])/2,(uu[i]+x)/2 ), uu[i]) )
    return uu

Schéma de Crank-Nicolson : $$ \begin{cases} u_0=y_0,\\ u_{n+1}=u_n+\frac{h}{2}\Bigl(\varphi(t_n,u_n)+\varphi(t_{n+1},u_{n+1})\Bigr)& n=0,1,2,\dots N-1 \end{cases} $$ avec $u_{n+1}$ zéro de la fonction $x\mapsto -x+u_n+\frac{h}{2}(\varphi(t_n,u_n)+\varphi(t_{n+1},x))$.

In [17]:
def CN(phi,tt, y0):
    h = tt[1]-tt[0]
    uu = [y0]
    for i in range(len(tt)-1):
        temp = fsolve(lambda x: -x+uu[i]+0.5*h*( phi(tt[i+1],x)+phi(tt[i],uu[i]) ), uu[i])
        uu.append(temp)
    return uu

Schéma de Heun: $$ \begin{cases} u_0=y_0,\\ \tilde u = u_n+h\varphi(t_n,u_n)\\ u_{n+1}=u_n+\frac{h}{2}\Bigl(\varphi(t_n,u_n)+\varphi(t_{n+1},\tilde u)\Bigr)& n=0,1,2,\dots N-1 \end{cases} $$ qu'on peut réécrire sous la forme $$ \begin{cases} u_0=y_0,\\ k_1 = h\varphi(t_n,u_n),\\ k_2 = h\varphi(t_{n+1},u_n+k_1),\\ u_{n+1}=u_n+\frac{k_1+k_2}{2}& n=0,1,2,\dots N-1 \end{cases} $$

In [18]:
def heun(phi,tt, y0):
    h = tt[1]-tt[0]
    uu = [y0]
    for i in range(len(tt)-1):
        k1 = h * phi( tt[i], uu[i] )
        k2 = h * phi( tt[i+1], uu[i] + k1  )
        uu.append( uu[i] + (k1+k2) /2 )
    return uu
In [19]:
N = 8 
tt=linspace(t0,tfinal,N+1)

yy    = [sol_exacte(t) for t in tt]
uu_ep = EE(phi, tt, y0)
uu_er = EI(phi, tt, y0)
uu_em = EM(phi, tt, y0)
uu_rk1m = RK1_M(phi, tt, y0)
uu_cn = CN(phi, tt, y0)
uu_heun = heun(phi, tt, y0)

figure(1, figsize=(18, 10))

subplot(2, 3, 1)
plot(tt, yy, 'b-', tt, uu_ep, 'r-D')
err_ep = max( [abs(uu_ep[i] - yy[i]) for i in range(N)] )
# title(f'Euler explicite - max(|erreur|)= {err_ep:1.10f}')
title('Euler explicite - max(|erreur|)= %1.5f'%(err_ep))
grid()

subplot(2, 3, 2)
plot(tt, yy, 'b-', tt, uu_er, 'r-D')
err_er = max( [abs(uu_er[i] - yy[i]) for i in range(N)] )
title('Euler implicite - max(|erreur|)=%1.5f' %(err_er))
grid();


subplot(2, 3, 3)
plot(tt, yy, 'b-', tt, uu_em, 'r-D')
err_ep = max( [abs(uu_em[i] - yy[i]) for i in range(N)] )
title('Euler modifié - max(|erreur|)= %1.5f'%(err_ep))
grid()

subplot(2, 3, 4)
plot(tt, yy, 'b-', tt, uu_rk1m, 'r-D')
err_rk1m = max( [abs(uu_rk1m[i] - yy[i]) for i in range(N)] )
title('RK1_m - max(|erreur|)= %1.5f'%(err_rk1m))
grid()

subplot(2, 3, 5)
plot(tt, yy, 'b-', tt, uu_cn, 'r-D')
err_cn = max( [abs(uu_cn[i] - yy[i]) for i in range(N)] )
title('Crank Nicolson - max(|erreur|)=%1.5f' %(err_cn))
grid()

subplot(2, 3, 6)
plot(tt, yy, 'b-', tt, uu_heun, 'r-D')
err_heun = max( [abs(uu_heun[i] - yy[i]) for i in range(N)] )
title('Heun - max(|erreur|)=%1.5f' %(err_heun))
grid();
In [20]:
H = []
err_ep = []
err_er = []
err_em = []
err_rk1m = []
err_cn = []
err_heun = []

for k in range(7):
    N = 2**(k + 3)
    tt = linspace(t0, tfinal, N + 1)
    h = tt[1] - tt[0]
    yy = [sol_exacte(t) for t in tt]
    uu_ep = EE(phi, tt, y0)
    uu_er = EI(phi, tt, y0)
    uu_em = EM(phi, tt, y0)
    uu_rk1m = RK1_M(phi, tt, y0)
    uu_cn = CN(phi, tt, y0)
    uu_heun = heun(phi, tt, y0)
    H.append(h)
    err_ep.append(max([abs(uu_ep[i] - yy[i]) for i in range(len(yy))]))
    err_er.append(max([abs(uu_er[i] - yy[i]) for i in range(len(yy))]))
    err_em.append(max([abs(uu_em[i] - yy[i]) for i in range(len(yy))]))
    err_rk1m.append(max([abs(uu_rk1m[i] - yy[i]) for i in range(len(yy))]))
    err_cn.append(max([abs(uu_cn[i] - yy[i]) for i in range(len(yy))]))
    err_heun.append(max([abs(uu_heun[i] - yy[i]) for i in range(len(yy))]))

figure(figsize=(10,7))
loglog(H, err_ep, 'r-o', label='Euler Explicite')
loglog(H, err_er, 'g-+', label='Euler Implicite')
loglog(H, err_em, 'm-x', label='Euler Modife')
loglog(H, err_rk1m, 'b-^', label='RK1_m')
loglog(H, err_cn, 'y-d', label='Crank Nicolson')
loglog(H, err_heun, 'c-v', label='Heun')
xlabel('$\ln(h)$')
ylabel('$\ln(e)$')
legend(bbox_to_anchor=(1.04, 1), loc='upper left')
grid(True);

print ('Euler progressif %1.2f' %(polyfit(log(H),log(err_ep), 1)[0]))
print ('Euler regressif %1.2f' %(polyfit(log(H),log(err_er), 1)[0]))
print ('Euler Modifié %1.2f' %(polyfit(log(H),log(err_em), 1)[0]))
print ('RK1_M %1.2f' %(polyfit(log(H),log(err_rk1m), 1)[0]))
print ('Cranck Nicolson %1.2f' %(polyfit(log(H),log(err_cn), 1)[0]))
print ('Heun %1.2f' %(polyfit(log(H),log(err_heun), 1)[0]))
Euler progressif 1.05
Euler regressif 0.96
Euler Modifié 2.08
RK1_M 2.00
Cranck Nicolson 2.01
Heun 2.08

Exercice : Fonction intégrale

Il existe des fonctions définies par des intégrales qu'on ne peut pas calculer explicitement. Il est pourtant possible de calculer des valeurs approchées de ces fonctions.

Pour $x\in\left[0;\frac{\pi}{2}\right]$ calculer et afficher la table des valeurs et tracer le graphe de la fonction $$ x \mapsto f(x)=\int_0^x \sqrt{1-\frac{1}{4}\sin^2(t)}\mathrm{d}t $$ en approchant numériquement un problème de Cauchy (lequel?) avec la méthode d'Euler explicite.

Vérifier que $f\left(\frac{\pi}{6}\right)\simeq0.51788193$ et $f\left(\frac{\pi}{2}\right)\simeq1.46746221$.

Correction

La fonction $f$ est solution du problème de Cauchy $$ \begin{cases} y'(t)= \sqrt{1-\frac{1}{4}\sin^2(t)},\\ y(0)=0. \end{cases} $$ En effet, $f(0)=0$ et si on intègre l'EDO entre $0$ et $x$ on a $$ f(x)=\int_0^x \sqrt{1-\frac{1}{4}\sin^2(t)}\mathrm{d}t=\int_0^x y'(t)\mathrm{d}t=y(x)-y(0)=y(x). $$

Dans le code ci-dessous:

  1. On définit l'équation différentielle : phi est une lambda function qui contient la fonction mathématique $\varphi(t, y)$ dépendant des variables $t$ et $y$ (même si $y$ n'apparaît pas explicitement).
  2. On définit la condition initiale $t_0=0$ et $y_0=0$.
  3. On introduit la discrétisation: les nœuds d'intégration $[t_0,t_1,\dots,t_{N}]$ sont contenus dans le vecteur tt.

Comme nous devons éstimer ensuite $f$ en $\frac{\pi}{6}$ et $\frac{\pi}{2}$, faisons en sort davoir ces deux valeurs dans nos noeuds de discrétisation.
Étant donné que

  • $t_0=0$,
  • $t_N=\frac{\pi}{2}$ et
  • $t_i=t_0+ih$ avec $h=\frac{t_N-t_0}{N}=\frac{\pi}{2N}$,

on a $\frac{\pi}{6}=i\frac{\pi}{2N}$ ssi $i=\frac{2N}{6}=\frac{N}{3}$ et bien sûr $\frac{\pi}{2}=t_{N}$. Pour que $i\in\mathbb{N}$ on choisira $N$ multiple de $3$.

On calcule enfin la solution approchée et on vérifie que $f(\pi/6)\simeq0.51788193$ et $f(\pi/2)\simeq1.46746221$ et on affiche le graphe de la fonction approchée.

In [21]:
%reset -f
%matplotlib inline

from matplotlib.pylab import *

# schéma
def EE(phi, tt, y0):
    h  = tt[1] - tt[0]
    uu = [y0]
    for i in range(len(tt) - 1):
        uu.append(uu[i] + h * phi(tt[i], uu[i]))
    return uu

# EDO
phi = lambda t,y : sqrt(1-0.25*(sin(t))**2)

# CI
t0, y0 = 0, 0

# DISCRETISATION
tfinal = pi/2
k = 100
N = 3*k # ainsi t_N=pi/2=3pi/6 et t_k=pi/6
tt=linspace(t0,tfinal,N+1)

# CALCUL DE LA SOLUTION APPROCHÉE
uu = EE(phi,tt, y0)

# print(f"f(pi/{pi/tt[k]})={uu[k]}") # NEW syntaxe
# print(f"f(pi/{pi/tt[-1]})={uu[-1]}") # NEW syntaxe
print("f(pi/%g)=%g" %(pi/tt[k],uu[k])) # OLD syntaxe
print("f(pi/%g)=%g" %(pi/tt[-1],uu[-1])) # OLD syntaxe

# AFFICHAGE, NON DEMANDÉ
plot(tt,uu)
plot([tt[0],tt[k],tt[k]],[uu[k],uu[k],0],'m--');
plot([tt[0],tt[-1],tt[-1]],[uu[-1],uu[-1],0],'c--');
f(pi/6)=0.517965
f(pi/2)=1.46781

Et sympy ?

In [22]:
%reset -f
%matplotlib inline

import sympy as sym
sym.init_printing()

t  = sym.Symbol('t')
y  = sym.Function('y')
edo= sym.Eq( sym.diff(y(t),t) , sym.sqrt(1-sym.Rational(1,4)*(sym.sin(t))**2) )
display(edo)
# solgen = sym.dsolve(edo)
# display(solgen)

t  = sym.Symbol('t')
f=sym.Integral(sym.sqrt(1-sym.Rational(1,4)*(sym.sin(t))**2))
display(f)
# f.doit()
$\displaystyle \frac{d}{d t} y{\left(t \right)} = \sqrt{1 - \frac{\sin^{2}{\left(t \right)}}{4}}$
$\displaystyle \int \sqrt{1 - \frac{\sin^{2}{\left(t \right)}}{4}}\, dt$

Exercice : Système de deux EDO avec invariant

Considérons le problème de Cauchy $$ \begin{cases} x'(t)= -y(t) ,\\ y'(t)= x(t), & t\in[0;4\pi]\\ x(0)=1,\\ y(0)=0. \end{cases} $$

  1. Invariant
    Vérifier que $I(t)=x^2(t) + y^2(t)$ est un invariant pour le système.
  2. Approximations
    Dans une simulation numérique on aimerait que l'invariant soit préservé aussi bien que possible. Nous allons regarder ce qui se passe avec certaines méthodes vues en cours.
    On notera $u_n\approx x_n=x(t_n)$ et $w_n\approx y_n=y(t_n)$.
    À chaque instant $t_n$, on calculera $J_n\approx I_n=I(t_n)$.
    Choisissons $N=193$ points de discrétisation ainsi $h=t_{n+1}-t_n=\pi/48$.
    1. Dans un premier temps on se propose d'appliquer la méthode d'Euler explicite à la résolution du système.
      1. Tracer $(t_n,u_n)$ et $(t_n,w_n)$ sur un même graphique;
      2. sur un autre graphique tracer $(t_n,J_n)$;
      3. tracer enfin $(u_n,w_n)$ sur un autre graphique et vérifier que la solution numérique tourne vers l’extérieur
      4. Après avoir constaté que l'invariant $J_n$ n'est pas conservé mais augment au cours du temps, montrer analytiquement que $J_n=(1+h^2)^n$.
    2. Même exercice pour le schéma d'Euler implicite (la linéarité du second membre permet de rendre ce schéma explicite). Que peut-on constater? Commenter les résultats et écrire l'expression analytique de $J_n$.
    3. Même exercice pour le schéma de Crank Nicolson (la linéarité du second membre permet de rendre ce schéma explicite). Que peut-on constater? Commenter les résultats et écrire l'expression analytique de $J_n$.
    4. Même exercice avec la fonction odeint du module scipy.optimize.
  3. Solution exacte
    Dans ce cas il est même possible de calculer analytiquement la solution exacte. Calculer donc la solution exacte avec le module sympy.

Correction

Invariant

Pour tout $t$ on a $$ I'(t)=\left(x^2(t) + y^2(t)\right)' = 2x'(t)x(t)+2y'(t)y(t)=-2x(t)y(t)+2x(t)y(t)=0 $$ donc $I(t)=I(0)=0^2+1^2=1$.

Approximations

On notera $u_n\approx x_n=x(t_n)$ et $w_n\approx y_n=y(t_n)$.

À chaque instant $t_n$, on calculera $J_n=u_n^2+w_n^2\approx I_n=I(t_n)$.

In [23]:
%reset -f
%matplotlib inline

from matplotlib.pylab import *
# from scipy.optimize import fsolve # non necessaire car on resout l'equation implicite a la main

t0 = 0
tfinal = 4*pi

#h = pi/48
tt = linspace(0,4*pi,193)  #arange(t0,tfinal+h/2,h)

x0 = 1
y0 = 0

phi1 = lambda t,x,y : -y
phi2 = lambda t,x,y : x

Euler explicite $$ \begin{cases} u_0=1,\\ w_0=0,\\ u_{n+1}=u_n+h\varphi_1(t_n,u_n,w_n)=u_n-hw_n,\\ w_{n+1}=w_n+h\varphi_2(t_n,u_n,w_n)=w_n+hu_n \end{cases} $$

In [24]:
def EE(phi1,phi2,tt,x0,y0):
	uu = [x0]
	ww = [y0]
	h = tt[1]-tt[0]
	for i in range(len(tt)-1):
		uu.append(uu[i]+h*phi1(tt[i],uu[i],ww[i]))
		ww.append(ww[i]+h*phi2(tt[i],uu[i],ww[i]))
	return [uu,ww]

On a $$ J_{n+1} =u_{n+1}^2+w_{n+1}^2 =(u_n-hw_n)^2+(w_n+hu_n)^2 =(1+h^2)(u_n^2+w_n^2) =(1+h^2)J_n $$ soit encore $$ J_n=(1+h^2)^nJ_0=(1+h^2)^n. $$ On voit que

  • l'invariant n'est conservé que si $h=0$, ce qui est impossible,
  • pour $h$ fixé, $\lim\limits_{n\to+\infty}J_n=+\infty$,
  • pour $n$ fixé, l'erreur $|J_n-I_n|$ diminue comme $h^2$.
In [25]:
[uu_ep, ww_ep] = EE(phi1,phi2,tt,x0,y0)
JJ_ep = [(uu_ep[i])**2+(ww_ep[i])**2 for i in range(len(tt))]


figure(figsize=(18,5))

subplot(1,3,1)
plot(tt,uu_ep,tt,ww_ep)
xlabel('t')
legend(['x(t)','y(t)'])
title('Euler progressif - x(t) et y(t)') 

subplot(1,3,2)
plot(tt,JJ_ep)
xlabel('t')
title('Euler progressif - Invariant')

subplot(1,3,3)
plot(uu_ep,ww_ep)
xlabel('x(t)')
ylabel('y(t)')
title('Euler progressif - y(x)'); 

Euler implicite $$ \begin{cases} u_0=1,\\ w_0=0,\\ u_{n+1}=u_n+h\varphi_1(t_{n+1},u_{n+1},w_{n+1})=u_n-hw_{n+1},\\ w_{n+1}=w_n+h\varphi_2(t_{n+1},u_{n+1},w_{n+1})=w_n+hu_{n+1} \end{cases} $$ En resolvant le système linéaire on obtient une écriture explicite $$ \begin{cases} u_0=1,\\ w_0=0,\\ u_{n+1}=\dfrac{u_n-hw_{n}}{1+h^2},\\ w_{n+1}=\dfrac{w_n+hu_{n}}{1+h^2}. \end{cases} $$

In [26]:
# Notons que ici il est inutile de passer phi1,phi2 car non utilisées explicitement dans le code
def EI(phi1,phi2,tt,x0,y0):
	uu = [x0]
	ww = [y0]
	h  = tt[1]-tt[0]
	for i in range(len(tt)-1):
		uu.append((uu[i]-h*ww[i])/(1+h**2))
		ww.append((ww[i]+h*uu[i])/(1+h**2))
	return [uu,ww]
In [27]:
#  VERSION AVEC fsolve (non demandé)
from scipy.optimize import fsolve

def EI(phi1,phi2,tt,x0,y0):
	uu = [x0]
	ww = [y0]
	h  = tt[1]-tt[0]
	for i in range(len(tt)-1):
		sys = lambda z : [ -z[0]+uu[i]+h*phi1(tt[i+1],z[0],z[1]) , -z[1]+ww[i]+h*phi2(tt[i+1],z[0],z[1]) ]
		utemp,wtemp = fsolve( sys , (uu[i],ww[i]) ) 
		uu.append(utemp)
		ww.append(wtemp)
	return [uu,ww]

On a $$ J_{n+1} =u_{n+1}^2+w_{n+1}^2 =\frac{(u_n-hw_n)^2+(w_n+hu_n)^2}{(1+h^2)^2} =(u_n^2+w_n^2)\frac{1+h^2}{(1+h^2)^2} =\frac{1+h^2}{(1+h^2)^2}J_n =\frac{1}{1+h^2}J_n $$ soit encore $$ J_n=\left(\frac{1}{1+h^2}\right)^nJ_0=\left(\frac{1}{1+h^2}\right)^n. $$ On voit que

  • l'invariant n'est conservé que si $h=0$, ce qui est impossible,
  • pour $h$ fixé, $\lim\limits_{n\to+\infty}J_n=0^+$,
  • pour $n$ fixé, l'erreur $|J_n-I_n|$ diminue comme $h^2$.
In [28]:
[uu_er, ww_er] = EI(phi1,phi2,tt,x0,y0)
JJ_er = [(uu_er[i])**2+(ww_er[i])**2 for i in range(len(tt))]

figure(figsize=(18,5))

subplot(1,3,1)
plot(tt,uu_er,tt,ww_er)
xlabel('t')
legend(['x(t)','y(t)'])
title('Euler regressif - x(t) et y(t)') 

subplot(1,3,2)
plot(tt,JJ_er)
xlabel('t')
title('Euler regressif - Invariant')

subplot(1,3,3)
plot(uu_er,ww_er)
xlabel('x(t)')
ylabel('y(t)')
title('Euler regressif - y(x)');

Crank Nicolson $$ \begin{cases} u_0=1,\\ w_0=0,\\ u_{n+1}=u_n+\frac{h}{2}\Big(\varphi_1(t_{n},u_{n},w_{n})+\varphi_1(t_{n+1},u_{n+1},w_{n+1})\big)=u_n-\frac{h}{2}w_{n}-\frac{h}{2}w_{n+1},\\ w_{n+1}=w_n+\frac{h}{2}\Big(\varphi_2(t_{n},u_{n},w_{n})+\varphi_2(t_{n+1},u_{n+1},w_{n+1})\big)=w_n+\frac{h}{2}u_{n}+\frac{h}{2}u_{n+1}. \end{cases} $$ En resolvant le système linéaire on obtient une écriture explicite:

$$ \begin{cases} u_0=1,\\ w_0=0,\\ u_{n+1}=\dfrac{(4-h^2)u_n-4hw_{n}}{4+h^2},\\ w_{n+1}=\dfrac{(4-h^2)w_n+4hu_{n}}{4+h^2}. \end{cases} $$

De plus, on a \begin{align*} J_{n+1} &=u_{n+1}^2+w_{n+1}^2 \\ &=\frac{((4-h^2)u_n-4hw_n)^2+((4-h^2)w_n+4hu_n)^2}{(4+h^2)^2} \\ &=\frac{(4-h^2)^2u_n^2+16h^2w_n^2-8(4-h^2)hu_nw_n+(4-h^2)^2w_n^2+16h^2u_n^2+8(4-h^2)hu_nw_n}{(4+h^2)^2} \\ &=\frac{\big((4-h^2)^2+16h^2\big)(u_n^2+w_n^2)}{(4+h^2)^2} \\ &=\frac{(4+h^2)^2(u_n^2+w_n^2)}{(4+h^2)^2} \\ &=(u_n^2+w_n^2)=J_n \end{align*}

On voit que l'invariant est conservé pour tout $h$

Vérifions ces calculs avec sympy:

In [29]:
import sympy as sym
sym.init_printing()

sym.var('u_n, u_np1, w_n, w_np1, dt')
eq1 = sym.Eq( u_np1 , u_n-dt/2*w_n-dt/2*w_np1 )
eq2 = sym.Eq( w_np1 , w_n+dt/2*u_n+dt/2*u_np1 )
sol=sym.solve([eq1,eq2],[u_np1,w_np1],dict=True)[0]
display(sol)

J_np1=u_np1**2+w_np1**2
J_np1.subs(sol).simplify()
$\displaystyle \left\{ u_{np1} : \frac{- dt^{2} u_{n} - 4 dt w_{n} + 4 u_{n}}{dt^{2} + 4}, \ w_{np1} : \frac{- dt^{2} w_{n} + 4 dt u_{n} + 4 w_{n}}{dt^{2} + 4}\right\}$
Out[29]:
$\displaystyle u_{n}^{2} + w_{n}^{2}$
In [30]:
# Notons que ici il est inutile de passer phi1,phi2 car non utilisées explicitement dans le code
def cn(phi1,phi2,tt,x0,y0):
	uu = [x0]
	ww = [y0]
	h  = tt[1]-tt[0]
	for i in range(len(tt)-1):
		uu.append(((4-h**2)*uu[i]-4*h*ww[i])/(4+h**2))
		ww.append(((4-h**2)*ww[i]+4*h*uu[i])/(4+h**2))
	return [uu,ww]
In [31]:
#  VERSION AVEC fsolve (non demandé)
from scipy.optimize import fsolve

def cn(phi1,phi2,tt,x0,y0):
	uu = [x0]
	ww = [y0]
	h  = tt[1]-tt[0]
	for i in range(len(tt)-1):
		sys = lambda z : [ -z[0]+uu[i]+h/2*(phi1(tt[i],uu[i],ww[i])+phi1(tt[i+1],z[0],z[1])) , 
                           -z[1]+ww[i]+h/2*(phi2(tt[i],uu[i],ww[i])+phi2(tt[i+1],z[0],z[1])) ]
		utemp,wtemp = fsolve( sys , (uu[i],ww[i]) ) 
		uu.append(utemp)
		ww.append(wtemp)
	return [uu,ww]
In [32]:
[uu_cn, ww_cn] = cn(phi1,phi2,tt,x0,y0)
JJ_cn = [(uu_cn[i])**2+(ww_cn[i])**2 for i in range(len(tt))]

figure(figsize=(18,5))

subplot(1,3,1)
plot(tt,uu_cn,tt,ww_cn)
xlabel('t')
legend(['x(t)','y(t)'])
title('Crank Nicolson - x(t) et y(t)') 

subplot(1,3,2)
plot(tt,JJ_cn)
xlabel('t')
title('Crank Nicolson - Invariant')

subplot(1,3,3)
plot(uu_cn,ww_cn)
xlabel('x(t)')
ylabel('y(t)')
title('Crank Nicolson - y(x)');

Avec odeint du module scipy.optimize:

In [33]:
from scipy.integrate import odeint

# Attention à l'ordre des paramètres dans pphi et dans odeint
pphi = lambda yy,t : [ phi1(t,yy[0],yy[1]) , phi2(t,yy[0],yy[1]) ]
sol = odeint(pphi,[x0,y0],tt)

uu_odeint = sol[:,0] 
ww_odeint = sol[:,1] 

JJ_odeint = [uu_odeint[i]**2+ww_odeint[i]**2 for i in range(len(tt))]

figure(figsize=(18,5))

subplot(1,3,1)
plot(tt,uu_odeint,tt,ww_odeint)
xlabel('t')
legend(['x(t)','y(t)'])
title('ODEINT - x(t) et y(t)') 

subplot(1,3,2)
plot(tt,JJ_odeint)
xlabel('t')
title('ODEINT - Invariant')

subplot(1,3,3)
plot(uu_odeint,ww_odeint)
xlabel('x(t)')
ylabel('y(t)')
title('ODEINT - y(x)');

Solution exacte

La solution exacte est \begin{align*} x(t)&=\cos(t),\\ y(t)&=\sin(t). \end{align*} Cette solution est périodique de période $2\pi$ et on a bien $$ I(t)=x^2(t) + y^2(t) = 1 \quad \forall t. $$ Vérifions-le avec le module sympy:

In [34]:
%reset -f
%matplotlib inline

import sympy as sym
sym.init_printing()

t  = sym.Symbol('t')
x = sym.Function('x')
y = sym.Function('y')
edo1 = sym.Eq( sym.diff(x(t),t) , -y(t) )
edo2 = sym.Eq( sym.diff(y(t),t) ,  x(t) )
display(edo1)
display(edo2)
solgen = sym.dsolve([edo1,edo2],[x(t),y(t)])
display(solgen)

t_0=0
x_0=1
y_0=0
consts = sym.solve( [ sym.Eq( x_0, solgen[0].rhs.subs(t,t_0)) , sym.Eq( y_0, solgen[1].rhs.subs(t,t_0)) ] , dict=True)[0]
display(consts)
solpar_1=solgen[0].subs(consts)
solpar_2=solgen[1].subs(consts)
display(solpar_1)
display(solpar_2)

func_1 = sym.lambdify(t,solpar_1.rhs,'numpy')
func_2 = sym.lambdify(t,solpar_2.rhs,'numpy')

from matplotlib.pylab import *
figure(figsize=(17,7))
tt=linspace(0,4*pi,193)
xx=func_1(tt)
yy=func_2(tt)
subplot(1,2,1)
plot(tt,xx,tt,yy)
legend([r'$x(t)$',r'$y(t)$'])
subplot(1,2,2)
plot(xx,yy)
xlabel(r'$x$')
ylabel(r'$y$')
axis('equal');
$\displaystyle \frac{d}{d t} x{\left(t \right)} = - y{\left(t \right)}$
$\displaystyle \frac{d}{d t} y{\left(t \right)} = x{\left(t \right)}$
$\displaystyle \left[ x{\left(t \right)} = - C_{1} \cos{\left(t \right)} - C_{2} \sin{\left(t \right)}, \ y{\left(t \right)} = - C_{1} \sin{\left(t \right)} + C_{2} \cos{\left(t \right)}\right]$
$\displaystyle \left\{ C_{1} : -1, \ C_{2} : 0\right\}$
$\displaystyle x{\left(t \right)} = \cos{\left(t \right)}$
$\displaystyle y{\left(t \right)} = \sin{\left(t \right)}$
In [ ]: