Méthodes d'Analyse Numérique en Python
Méthodes d'Analyse Numérique en Python
Ce cours est consacré à la présentation de quelques méthodes usuelles d’analyse numérique. Il aborde
des techniques de dérivation (intégration) numériques, la résolution des équations non linéaires et la
résolution des équations différentielles ordinaires.
1. Préliminaires
Les nombres décimaux sont représentés en Python par une mantisse (partie fractionnaire) et un exposant.
Considérons la représentation de 2100 en entier et en notation scientifique :
[1]: x = 2**100
y = 2.**100
print("x={} et y={}".format(x,y))
x=1267650600228229401496703205376 et y=1.2676506002282294e+30
La mantisse de 2100 est alors 1.2676506002282294 et son exposant est +30. Pour éviter de manipuler
des représentations différentes d’un même nombre, on convient de placer la virgule décimale après le
premier chiffre non nul, d’où le nom notation scientifique. Les nombres réels sont représentés en Python
avec le type double précision de la norme IEEE754. Les valeurs possibles pour l’exposant sont limités ;
approximativement, en deçà de 10−323 le nombre est arrondi à 0 ; au delà de 10308 , il y a dépassement de
capacité : le nombre est considéré comme infini (inf = ∞).
[2]: x = 1e-324
y = 1e-323
z = 1e308
w = 1e309
print("x={} ; y={} ; z={} et w={}".format(x, y, z, w))
x=2.220446049250313e-16
Calcul Numérique 2. Résolution d’équation non linéaire 2
Notons que cette constante peut-être utilisée pour écrire une fonction qui teste l’égalité entre deux flottants
dans la limite de la précision de l’interpréteur. En plus, un nombre réel x sera donc stocké en mémoire
sous forme arrondie x̃ vérifiant :
x̃ = x(1 + εx ) avec |εx | 6 ε
Le nombre εx est alors appelé erreur relative et le nombre xεx erreur absolue.
En conclusion, les problèmes d’arrondis sont à prendre en compte dès que l’on travaille avec des nombres
à virgule flottante. En général, pour éviter les pertes de précision, on essayera autant que peut se faire de
respecter les règles suivantes : éviter d’additionner ou de soustraire deux nombres d’ordres de grandeur
très différente ; éviter de soustraire deux nombres presque égaux.
De plus, pour calculer une somme de termes ayant des ordres de grandeur très différents, on applique le
principe dit photo de classe : les petits devant, les grands derrière. Par exemple, si on souhaite donner
+∞
X 1 π4
une approximation décimale de ζ(4) = dont la valeur exacte est , on constate qu’on obtient
n=1
n 4 90
une meilleure précision en commençant par sommer les termes les plus petits :
def zeta(n,p):
return sum(1/i**p for i in range(1, n+1))
def zeta_inverse(n,p):
return sum(1/i**p for i in range(n, 0, -1))
n, p = 2**18, 4
print('L'erreur du plus petit au plus grand :',
"{: .16f}".format(abs(zeta_inverse(n, p) - pi**4/90)))
Théorème 1.
Soit f : [a, b] → R une fonction continue sur un segment.
Si f (a) · f (b) 6 0, alors il existe ` ∈ [a, b] tel que f (`) = 0.
On suppose dans la suite que cette étude est déjà réalisée et que l’on dispose d’un intervalle [a, b] sur
lequel la fonction est strictement monotone, et tel que l’on ait f (a) ∗ f (b) < 0. D’après le théorème de la
bijection monotone, on en déduit que f admet un unique zéro dans ]a, b[. Pour programmer les différentes
méthodes numériques de résolution, nous allons utiliser la classe parente suivante :
[5]: # *******************************************
# Classe Parente
# *******************************************
Calcul Numérique 2. Résolution d’équation non linéaire 3
class Nonlineaire:
def __init__(self, approx, eps=1e-14, maxIter=10**7):
self.approx = approx
self.eps = eps
self.maxIter = maxIter # borne max pour limiter les itérations
• Si f (a) · f ( a+b
2 ) > 0, cela implique que f ( 2 ) · f (b) 6 0, et alors il existe c ∈ [ 2 , b] tel que
a+b a+b
f (c) = 0.
y
y
a+b
a 2
b x
f ( a+b )>0
2
f ( a+b
2
)<0
a
a+b b x
2
Nous avons obtenu un intervalle de longueur moitié dans lequel l’équation (f (x) = 0) admet une solution.
On itère alors le procédé pour diviser de nouveau l’intervalle en deux.
Voici le processus complet :
• Au rang 0 : On pose a0 = a, b0 = b. Il existe une solution x0 de l’équation (f (x) = 0) dans l’intervalle
[a0 , b0 ].
• Au rang 1 :
— Si f (a0 ) · f ( a0 +b
2 ) 6 0, alors on pose a1 = a0 et b1 =
0
2 ,
a0 +b0
[6]: # *******************************************
# Méthode de dichotomie
# *******************************************
class Dichotomie(Nonlineaire):
def subdivise(self, a, b, f):
return 0.5 * (a+b)
[7]: # *******************************************
# Méthode regula falsi
# *******************************************
class RegulaFalsi(Dichotomie):
def subdivise(self, a, b, f):
return a - f(a) * (b - a) / (f(b) - f(a))
Proposition 1.
Soit I un intervalle fermé stable pour une application contractante f de rapport de Lipschitz k ∈ [0, 1[ ;
alors
Calcul Numérique 2. Résolution d’équation non linéaire 5
Les points fixes peuvent être classés en différentes catégories suivant la valeur de f 0 (l) en points fixes
attractifs (|f 0 (l)| < 1), répulsifs (|f 0 (l)| > 1), super-attractifs (|f 0 (l)| = 0), indifférents (|f 0 (l)| = 1).
[8]: # *******************************************
# Méthode du point fixe
# *******************************************
class PointFixe(Nonlineaire):
def resolution(self, f):
eps, maxIter = self.eps, self.maxIter
x0, x1 = self.approx, f(self.approx)
for i in range(maxIter):
if abs(x0 - x1) < eps:
return x1
x0, x1 = x1, f(x1)
return x1
f (un )
un
un+1
On réitère alors l’opération tant que deux valeurs consécutives sont distantes d’au moins la valeur de
seuil epsilon (ε). Ceci revient à appliquer la méthode du point fixe avec la fonction
f (x)
ϕ(x) = x − .
f 0 (x)
Calcul Numérique 2. Résolution d’équation non linéaire 6
[9]: # *******************************************
# Méthode de Newton
# *******************************************
class Newton(PointFixe):
def resolution(self, f, fp):
return PointFixe.resolution(self, lambda x:x-f(x)/fp(x))
a a0 a00 b
x
A00
A0
A
Proposition 2.
Soit f : [a, b] → R une fonction continue, strictement croissante et convexe telle que f (a) 6 0, f (b) > 0.
Alors la suite définie par
b − an
a0 = a et an+1 = an − f (an )
f (b) − f (an )
est croissante et converge vers la solution ` de (f (x) = 0).
L’hypothèse f convexe signifie exactement que pour tout x, x0 dans [a, b] la sécante (ou corde) entre
(x, f (x)) et (x0 , f (x0 )) est au-dessus du graphe de f .
y
(x0 , f (x0 ))
x
x0 x
(x, f (x))
Dans ce cas, la convergence est moins rapide que dans le cas de la méthode de Newton, mais le résultat
peut être obtenu plus rapidement si le calcul de f 0 est très long.
Calcul Numérique 2. Résolution d’équation non linéaire 7
[10]: # *******************************************
# Méthode de la sécante
# *******************************************
class Secante(Nonlineaire):
def resolution(self, f):
eps, maxIter = self.eps, self.maxIter
[x0, x1] = self.approx
for i in range(maxIter):
x2 = x1 - f(x1) * (x1 - x0) / (f(x1) - f(x0))
if abs(x0 - x2) < eps:
return x2
x0, x1 = x1, x2
return x2
[11]: # *******************************************
# Comparatifs des différentes méthodes
# *******************************************
from math import *
print('-'*62)
print("{:>7}|{:^10}|{:^10}|{:^10}|{:^10}|{:^10}"
.format("MaxIter", "Dichotomie", "RegulaFalsi",
"Secante", "PointFixe", "Newton"))
for i in range(1, 9):
di = Dichotomie([1, 2], maxIter=i)
rg = RegulaFalsi([1, 2], maxIter=i)
se = Secante([1, 2], maxIter=i)
pf = PointFixe(2, maxIter=i)
nw = Newton(2, maxIter=i)
comp = [eval(m).resolution(lambda x:x**2-2) for m in ['di', 'rg', 'se']]
comp.append(pf.resolution(lambda x:0.5*(x+2/x)))
comp.append(nw.resolution(lambda x:x**2-2, lambda x:2*x))
comp = [abs(sqrt(2) - c) for c in comp]
print(' '*5, '-'*56)
print("{:>7d}|{: .3e}|{: .4e}|{: .3e}|{: .3e}|{: .3e}"
.format(i, comp[0], comp[1], comp[2], comp[3], comp[4]))
print(' '*5, '-'*56)
--------------------------------------------------------------
MaxIter|Dichotomie|RegulaFalsi| Secante |PointFixe | Newton
--------------------------------------------------------
1| 8.579e-02| 8.0880e-02| 8.088e-02| 2.453e-03| 2.453e-03
--------------------------------------------------------
2| 1.642e-01| 1.4358e-02| 1.421e-02| 2.124e-06| 2.124e-06
--------------------------------------------------------
Calcul Numérique 3. Dérivation numérique 8
3. Dérivation numérique
Lorsqu’une fonction est trop compliquée pour que le calcul analytique de sa dérivée soit pratique, on peut
obtenir une valeur approchée de la dérivée en calculant un taux d’accroissement τx (h) :
f (x + h) − f (x)
f 0 (x) ≈ := τx (h)
h
En théorie, plus h est petit, plus τx (h) est proche de f 0 (x). Mais, eu égard aux problèmes d’arrondi, il ne
faut pas prendre h trop petit. Pour une fonction suffisamment régulière, il est donc judicieux de choisir
√
une valeur de h qui soit de l’ordre de ε, c’est-à-dire de l’ordre de 10−8 .
Calcul Numérique 3. Dérivation numérique 9
[12]: # ***********************************
# Classe Parente
# ***********************************
class Diff:
def __init__(self, f, h=1e-8):
self.f = f
self.h = h
# ***********************************
# Approx. en avant d'ordre 1
# ***********************************
class DiffAvant1(Diff):
def __call__(self, x):
f, h = self.f, self.h
return (f(x+h)-f(x))/(h)
Comparons pour différentes valeurs de h les valeurs données pour la dérivée de la fonction sinus en 0 :
[13]: # ***********************************
# Exemple : dérivée de sinus en zéro
# ***********************************
from math import *
print('- '*15); print('{: ^8} | {: ^20}'.format(' h ', 'cos(0)')); print('- '*15)
for n in range(1, 13):
x, h = 0, 10**(-n); cos = DiffAvant1(sin, h)
print('{: 7.1e} | {: .15f}'.format(h, cos(x)))
- - - - - - - - - - - - - - -
h | cos(0)
- - - - - - - - - - - - - - -
1.0e-01 | 0.998334166468282
1.0e-02 | 0.999983333416666
1.0e-03 | 0.999999833333342
1.0e-04 | 0.999999998333333
1.0e-05 | 0.999999999983333
1.0e-06 | 0.999999999999833
1.0e-07 | 0.999999999999998
1.0e-08 | 1.000000000000000
1.0e-09 | 1.000000000000000
1.0e-10 | 1.000000000000000
1.0e-11 | 1.000000000000000
1.0e-12 | 1.000000000000000
On constate qu’à partir de h = 10−8 la valeur donnée est exacte. Notons qu’il existe d’autres formules
d’approximation de la dérivée, basées sur des calcul de différences finies et qui permettent d’obtenir un
ordre d’approximation plus élevé :
f (x) − f (x − h)
f 0 (x) ≈ approximation en arrière d’ordre 1
h
f (x + h) − f (x − h)
f 0 (x) ≈ approximation centrée d’ordre 2
2h
4f (x + h) − f (x + 2h) − 3f (x)
f 0 (x) ≈ approximation en avant d’ordre 2
2h
−4f (x − h) + f (x − 2h) + 3f (x)
f 0 (x) ≈ approximation en arrière d’ordre 2
2h
Calcul Numérique 3. Dérivation numérique 10
etc.
Pour les programmer, il suffit d’ajouter une classe pour chacune dans le fichier parent.py :
[14]: # ***********************************
# Approx. centré d'ordre 2
# ***********************************
class DiffCentre2(Diff):
def __call__(self, x):
f, h = self.f, self.h
return (f(x+h)-f(x-h))/(2*h)
Comparons, par exemple, l’efficacité de la formule de la différence centrée d’ordre 2 pour le calcul de la
dérivée de la fonction racine carrée :
[15]: # ***********************************
# Exemple : comparaison
# ***********************************
def f(x): return sqrt(1+x)
print('- '*27)
print('{: ^8} | {: ^20} | {: ^20}'
.format(' h ', 'DiffAvant1', 'DiffCentre2'))
print('- '*27)
for n in range(1, 13):
x, h = 0, 10**(-n)
g1 = DiffAvant1(f, h)
g2 = DiffCentre2(f, h)
print('{: 7.1e} | {: .17f} | {: .17f}'.format(h, g1(x), g2(x)))
- - - - - - - - - - - - - - - - - - - - - - - - - - -
h | DiffAvant1 | DiffCentre2
- - - - - - - - - - - - - - - - - - - - - - - - - - -
1.0e-01 | 0.48808848170151631 | 0.50062775059818931
1.0e-02 | 0.49875621120889502 | 0.50000625027344925
1.0e-03 | 0.49987506246096380 | 0.50000006250000562
1.0e-04 | 0.49998750062396624 | 0.50000000062444538
1.0e-05 | 0.49999875000317212 | 0.50000000000327560
1.0e-06 | 0.49999987505877641 | 0.50000000001437783
1.0e-07 | 0.49999998807948032 | 0.50000000029193359
1.0e-08 | 0.49999999696126451 | 0.50000000251237964
1.0e-09 | 0.50000004137018550 | 0.50000004137018550
1.0e-10 | 0.50000004137018550 | 0.50000004137018550
1.0e-11 | 0.50000004137018550 | 0.50000004137018550
1.0e-12 | 0.50004445029117051 | 0.50004445029117051
Cette fois-ci, on voit apparaître très nettement la perte de précision lorsque h est trop petit.
Travaux pratiques 2.
1. Évaluer la dérivée de f (x) = ex en x = 0 à l’aide des différences avant et arrière d’ordre 2. Prendre h = 0.05 et
h = 0.025 et calculer le rapport des erreurs commises.
2. À partir des données suivantes :
Fonction tabulée
x 1.00 1.01 1.02
f (x) 1.27 1.32 1.38
calculer les approximations de f 0 (1.005), f 0 (1.015) et f 00 (1.01) à l’aide des formules de différences centrées. Si les
Calcul Numérique 4. Intégration numérique 11
valeurs de f (x) sont précises à ±0.005, quelle est l’erreur maximale sur chacune des approximations ?
√
3. Soit f (x) = 12 x 1 − x2 + arcsin(x) . Pour x0 = 0.5, calculer des approximations de f 0 (x0 ) par la formule de
différence arrière d’ordre 2 avec h = 0.1, h = 0.05, h = 0.025, h = 0.0125 et h = 0, 00625. Dans chacun des cas,
calculer l’erreur exacte en valeur absolue. Cette erreur se comporte-telle en O(h2 ) ?
4. Obtenir la formule de différence centrée d’ordre 4 permettant de faire l’approximation de f 00 (x).
4. Intégration numérique
Dans cette section, nous nous intéresserons aux algorithmes permettant le calcul numérique d’intégrale.
Le principe général commun aux méthodes que nous allons présenter est simple : découper l’intervalle
d’intégration en petits morceaux, et approcher sur chacun de ces petits intervalles la courbe représentative
de la fonction f par une courbe très simple pour laquelle le calcul d’aire est facile. Avant de décrire
quelques méthodes d’intégration numérique, on peut commencer par écrire une super-classe (la classe
Integration).
[16]: # ************************************
# Classe Parente
# ************************************
class Integration:
def __init__(self, a, b, n):
self.a, self.b, self.n = a, b, n
self.poids_pivots = self.quadrature()
def quadrature(self):
raise NotlmplementedError
y y = ex
x
0 n = 10 1
En théorie plus n est grand plus l’approximation est précise (mais plus le pas est petit !).
Calcul Numérique 4. Intégration numérique 12
[17]: # ************************************
# Méthode des rectangles
# ************************************
class Rectangles(Integration):
def quadrature(self):
a , b, n = self.a, self.b, self.n
h = (b - a) / n
return [(h, a + k * h) for k in range(n)]
Exercice : Montrer qu’on peut utiliser des rectangles pour calculer l’aire du trapèze.
[18]: # ************************************
# Méthode des trapèzes
# ************************************
class Trapezes(Integration):
def quadrature(self):
a , b, n = self.a, self.b, self.n
h = (b - a) / n
return [(h, a + (k + 0.5)* h) for k in range(n)]
[19]: # ************************************
# Méthode de Gauss-Legendre
# ************************************
class GaussLegendre(Integration):
def gaussPivots(self, m, tol=1e-14):
def legendre(t, m): # calcule le m-ième polynôme de Legendre et son dérivé
p0, p1 = 1, t
for k in range(1, m):
p = ((2*k+1)*t*p1 - k*p0) / (1+k)
p0, p1 = p1, p
dp = m*(p0 - t*p1) / (1 - t**2)
return p, dp
def quadrature(self):
a , b, n = self.a, self.b, self.n
return [[(b - a)*w*0.5, (a + b)*0.5 + (a - b)*0.5*x]
for w, x in self.gaussPivots(n)]
[20]: # ************************************
# Comparatif des trois méthodes
# ************************************
from math import *
print('- '*27)
print("{:>3} | {:^15} | {:^14} | {:^10}".format("n",
- - - - - - - - - - - - - - - - - - - - - - - - - - -
n | rectangles | trapèzes | Gauss-Legendre
- - - - - - - - - - - - - - - - - - - - - - - - - - -
2 | 1.65068645e-01 | 1.78757927e-02| 4.34283e-04
4 | 9.14722187e-02 | 4.49974053e-03| 1.08247e-07
8 | 4.79859796e-02 | 1.12695453e-03| 7.99361e-15
16 | 2.45564671e-02 | 2.81866525e-04| 3.19744e-14
32 | 1.24191668e-02 | 7.04746518e-05| 1.55431e-14
64 | 6.24482072e-03 | 1.76191647e-05| 2.66454e-14
128 | 3.13121994e-03 | 4.40482253e-06| 3.64153e-14
On remarque également que la précision de la méthode de Gauss-Legendre est excellente avec seulement
8 points de quadrature, mais c’est au prix d’une programmation plus ardue. Au-delà de 8 points, les
erreurs d’arrondi dans la détermination des pivots et des points en gâtent la précision.
Travaux pratiques 3.
Z 2
1. Utiliser une méthode numérique pour évaluer ln(x)dx.
0
Z π
2
2. À l’aide d’une certaine méthode d’intégration numérique, on a évalué sin xdx en utilisant 3 valeurs différentes
0
de h. On a obtenu les résultats suivants.
Valeurs de I
h I
0.1 1.001 235
0.2 1.009 872
0.3 1.078 979
Compte tenu de la valeur exacte de I, déduire l’ordre de convergence de la quadrature employée.
3. Soit une fonction aléatoire X suivant une loi normale. La probabilité que X soit inférieure ou égale à x (notée
P(X 6 x)) est donnée par la fonction :
Z x
1 t2
P X6x = √ e− 2 dt
2π −∞
t2
Comme la fonction e− 2 n’a pas de primitive, on calcule cette probabilité pour différentes valeurs de x et l’on
garde les résultats dans des tables comme celle-ci :
Valeurs de P(X 6 x)
x P(X 6 x)
1.0 0.841 3447
1.1 0.864 3339
1.2 0.884 9303
1.3 0.903 1995
1.4 0.919 2433
(a) Calculer, toujours en vous servant de la table fournie, la dérivée P0 (X 6 1.2) avec une précision d’ordre 4.
Comparer avec la valeur exacte de cette dérivée.
(b) Calculer, toujours en vous servant de la table fournie, la dérivée P00 (X 6 1.2) avec une précision d’ordre 2
Calcul Numérique 5. Résolution numérique d’équations différentielle 15
[21]: # ************************************
# Fonction auxiliaire
# ************************************
def terminate(u, t, step_no):
eps = 1.0E-6 # nombre assez petit
return abs(u[step_no]) < eps # assez proche de zero?
# ************************************
# Classe parente
# ************************************
class ODESolver:
def __init__(self, f):
self.f = lambda u, t: np.asarray(f(u, t), float)
def advance(self):
raise NotImplementedError
self.t = np.asarray(time_points)
n = self.t.size
if self.neq == 1: # EDO scalaire
self.u = np.zeros(n)
else: # Système EDO
self.u = np.zeros((n,self.neq))
# La boucle temporelle
for k in range(n-1):
self.k = k
self.u[k+1] = self.advance()
if terminate(self.u, self.t, self.k+1):
break # terminer la boucle sur k
return self.u[:k+2], self.t[:k+2]
suivante :
∀k ∈ N, tk+1 = tk + h et xk+1 = xk + f (t, xk ) ∗ h
Voici une manière de programmer ce schéma numérique :
[22]: # ***********************************
# Euler Forward
# ***********************************
class ForwardEuler(ODESolver):
def advance(self):
u, f, k, t = self.u, self.f, self.k, self.t
h = t[k+1] - t[k]
u_new = u[k] + h*f(u[k], t[k])
return u_new
Testons l’efficacité de cette méthode pour le problème : x0 = x avec x(0) = 1 dont la solution est ex .
[23]: # ***********************************
# Test via y'=y avec y(0)=1 sur [0,3]
# ***********************************
import numpy as np
import matplotlib.pyplot as plt
from math import *
Calcul Numérique 5. Résolution numérique d’équations différentielle 17
T = 3
for h in 0.8, 0.5, 0.2, 0.1: # Pour différents dt (pas)
n = int(round(T/h))
solver = ForwardEuler(f)
solver.set_initial_condition(1)
u, t = solver.solve(np.linspace(0, T, n+1))
plt.plot(t, u, label = 'pas=%g' % h)
plt.legend(loc="upper left")
x = np.linspace(0, 3, 31)
plt.plot(x, np.exp(x), color='black',label='exacte')
plt.legend(loc="upper left")
plt.grid()
Il faut bien comprendre qu’à chaque étape, on repart dans la direction d’une solution exacte de l’équation
différentielle, mais qui n’est pas celle qui est du problème Cauchy initial. Sur le graphe ci-dessous, on trace
la solution exacte pour la condition initiale x(0) = 1 et les courbes intégrales qui passent par (tk , xk ).
[24]: # ***********************************
# Courbes intégrales
# ***********************************
T = 3
h = 0.1 # pas fixe
n = int(round(T/h))
solver = ForwardEuler(f)
for j in [0.2, 0.4, 0.6, 0.8, 1.2, 1.4, 1.6, 1.8]: # points variables
solver.set_initial_condition(j)
u, t = solver.solve(np.linspace(0, T, n+1))
plt.plot(t, u, color = 'red')
x = np.linspace(0, 3, 31)
Calcul Numérique 5. Résolution numérique d’équations différentielle 18
Pour juger de la qualité d’une méthode (ou schéma) numérique de résolution d’équations différentielles, il
faut prendre en compte plusieurs critères.
L’erreur de consistance donne l’ordre de grandeur de l’erreur effectué à chaque pas. Elle mesure l’erreur
qu’entraîne le fait d’approcher le nombre dérivé par un taux d’accroissement. À l’aide de la formule de
Taylor-Lagrange, on peut montrer que dans le cas de la méthode d’Euler, l’erreur de consistance est
dominée par h2 ; on dit alors que cette méthode est d’ordre 1. Ainsi, d’un point de vue théorique, plus le
pas est petit, meilleure sera l’approximation. Un schéma numérique est dit consistant si la somme des
erreurs de consistance tend 0 quand h → 0 (ce qui est le cas ici).
La stabilité contrôle la différence entre deux solutions approchées correspondant à deux conditions initiales
voisines : un schéma est dit stable si un petit écart entre les conditions initiales x(t0 ) = x0 et x̃(t0 ) = x0 +ε
et de petites erreurs d’arrondi dans le calcul récurrent des provoquent une erreur finale |x(t0 ) − x̃(t0 )|
contrôlable . La méthode d’Euler est stable.
Un schéma est dit convergent lorsque l’erreur globale (qui est le maximum des écarts entre la solution
exacte et la solution approchée) tend vers 0 lorsque le pas tend vers 0. En fait, pour que le schéma soit
convergent, il suffit que la méthode soit consistante et stable.
Enfin pour la mise en application du schéma, il faut aussi prendre en compte l’influence des erreurs
d’arrondi. En effet, afin de minimiser l’erreur globale théorique, on pourrait être tenté d’appliquer la
méthode d’Euler avec un pas très petit, mais ce faisant, outre que le temps de calcul deviendrait irréaliste,
les erreurs d’arrondi feraient diverger la solution approchée très rapidement. La question de trouver de
manière théorique le pas optimal peut s’avérer un problème épineux.
Dans le script suivant, on applique la méthode d’Euler sur l’intervalle [0, 1] avec la condition initiale
x(0) = 1. Pour minimiser le temps de calcul, l’algorithme est réécrit sous la forme suivante.
[25]: # ***********************************
# Exemple
# ***********************************
from math import exp
Calcul Numérique 5. Résolution numérique d’équations différentielle 19
print('-'*54)
print("{: ^8} | {: ^12} | {: ^12} | {: ^13}".format('h', 'x(t)', 'exp(t)',␣
,→'erreur'))
print('-'*54)
for k in range(1, 10): # Pour k>=6, l'algo est trop lent
h, t, x = 10**(-k), 0, 1
while t < 1:
t, x = t + h, x * (h + 1)
print("{0:>8g} | {1:>.10f} | {2:>.10f} | {3:>.10f}".format(h, x, exp(t), exp(t)␣
,→- x))
------------------------------------------------------
h | x(t) | exp(t) | erreur
------------------------------------------------------
0.1 | 2.8531167061 | 3.0041660239 | 0.1510493178
0.01 | 2.7048138294 | 2.7182818285 | 0.0134679990
0.001 | 2.7169239322 | 2.7182818285 | 0.0013578962
0.0001 | 2.7184177414 | 2.7185536702 | 0.0001359288
1e-05 | 2.7182954199 | 2.7183090114 | 0.0000135915
1e-06 | 2.7182804691 | 2.7182818285 | 0.0000013594
1e-07 | 2.7182819660 | 2.7182820996 | 0.0000001336
1e-08 | 2.7182817983 | 2.7182818347 | 0.0000000363
1e-09 | 2.7182820738 | 2.7182818299 | -0.0000002438
Pour un pas h 6 10−9 le temps de calcul devient trop important, de plus, on voit poindre l’effet des
erreurs d’arrondis, puisque la solution approchée est devenu supérieure à l’exponentielle, ce qui est
mathématiquement faux, en vertu de l’inégalité :
1 n
∀n ∈ N, 1+ 6e
n
En général, il ne suffit pas qu’un schéma numérique soit convergent pour qu’il donne de bons résultats
sur n’importe quelle équation différentielle. Encore faut-il que le problème soit bien posé (hypothèses de
Cauchy-Lipschitz doivent être vérifiées !) et bien conditionné (une petite erreur sur l’un des des termes
aura des répercussions allant en s’amplifiant).
= h ∗ f (tn , xn )
k1
tk+1 = tk + h = h ∗ f tn + h2 , xn + k21
k2
où
xk+1 = xk + 16 (k1 + 2k2 + 2k3 + k4 ) = h ∗ f tn + h2 , xn + k22
k3
= h ∗ f (tn + h, xn + k3 )
k4
Sans plus attendre, écrivons ce schéma.
[26]: # ***********************************
# Méthode de Runge-Kutta d'ordre 4
# ***********************************
class RungeKutta4(ODESolver):
def advance(self):
Calcul Numérique 5. Résolution numérique d’équations différentielle 20
Effectuons une comparaison des méthodes d’Euler et de Runge-Kutta en les appliquant au problème de
Cauchy : x0 = x et x(0) = 1 sur l’intervalle [0, 1].
[27]: # *******************************************************
# Testons graphiquement ForwardEuler vs RungeKutta4
# *******************************************************
# figure()
# legends = []
T = 3
dt = 0.5
n = int(round(T/dt))
t_points = np.linspace(0, T, n+1)
for solver_class in RungeKutta4, ForwardEuler:
solver = solver_class(f)
solver.set_initial_condition(1)
u, t = solver.solve(t_points)
plt.plot(t, u, label = '%s' % solver_class.__name__)
plt.legend(loc="upper left")
# hold('on')
x = np.linspace(0, 3, 31)
plt.plot(x, np.exp(x), color='black',label='exacte')
plt.legend(loc="upper left")
plt.show()
Calcul Numérique 5. Résolution numérique d’équations différentielle 21
[28]: # **************************************
# Comparaison pour différents pas
# *************************************
from math import exp
print('-'*51)
print("{: ^10} | {: ^18} | {: ^17}".format('h', 'erreur Euler', 'erreur RK4'))
print('-'*51)
T = 1
for i in range(1, 6):
h = 10**(-i)
n = int(round(T/h))
t_points = np.linspace(0, T, n+1)
solverEuler = ForwardEuler(f)
solverRK4 = RungeKutta4(f)
solverEuler.set_initial_condition(1)
solverRK4.set_initial_condition(1)
u_e, t_r = solverEuler.solve(t_points)
u_r, t_r = solverRK4.solve(t_points)
print("{0:>10g} | {1:>.12e} | {2:>.10e}"
.format(h, exp(1) - u_e[-1], exp(1) - u_r[-1]))
---------------------------------------------------
h | erreur Euler | erreur RK4
---------------------------------------------------
0.1 | 1.245393683590e-01 | 2.0843238793e-06
0.01 | 1.346799903752e-02 | 2.2464297089e-10
0.001 | 1.357896223148e-03 | 2.1760371283e-14
0.0001 | 1.359016338212e-04 | -8.4376949872e-15
1e-05 | 1.359128448319e-05 | -1.2878587086e-14
On constate que pour h = 0.001, la méthode d’Euler ne donne que trois décimales significatives du nombre
e, alors que la méthode de Runge-Kutta en donne quatorze ! On remarque également qu’il est inutile
d’appliquer la méthode de Runge-Kutta avec un pas h > 0.001, puisque dans ce cas les erreurs d’arrondi
prennent le dessus par rapport au gain théorique de précision.