Annexe 3 — Tutoriel Python : Méthodes numériques interactives
Objectifs
Ce tutoriel vous permet de :
- Implémenter les méthodes numériques du chapitre 2 en Python
- Visualiser la convergence de manière interactive avec Plotly
- Comparer le comportement des différentes méthodes
- Expérimenter avec vos propres fonctions
Configuration de l'environnement
Option 1 : Google Colab (recommandé)
La façon la plus simple d'exécuter ce tutoriel est d'utiliser Google Colab, un environnement Jupyter gratuit dans le navigateur :
- Rendez-vous sur Google Colab
- Créez un nouveau notebook : Fichier → Nouveau notebook
- Les bibliothèques
numpyetplotlysont déjà préinstallées ! - Copiez-collez le code des sections suivantes dans les cellules
Avantages de Colab
- Aucune installation requise
- Fonctionne sur n'importe quel ordinateur avec un navigateur
- Plotly fonctionne nativement (version 4.x+)
- Sauvegarde automatique sur Google Drive
Option 2 : Installation locale (Jupyter Notebook)
Si vous préférez travailler localement :
Prérequis : Python 3.8+ installé sur votre machine
# Installation des bibliothèques (une seule fois)
pip install numpy plotly jupyter
# Ou avec conda :
conda install numpy plotly jupyter
# Lancer Jupyter Notebook
jupyter notebookBibliothèques utilisées
| Bibliothèque | Version | Description |
|---|---|---|
numpy | ≥ 1.20 | Calculs numériques (tableaux, fonctions mathématiques) |
plotly | ≥ 4.0 | Graphiques interactifs (zoom, pan, animations) |
Version de Plotly
Assurez-vous d'avoir Plotly 4.x ou plus récent. Les versions antérieures nécessitaient une configuration spéciale pour Colab. Depuis la version 4, fig.show() fonctionne automatiquement dans tous les environnements (Colab, Jupyter, Kaggle, etc.).
Imports et configuration
Copiez ce bloc au début de votre notebook :
# ============================================
# IMPORTS - Exécutez cette cellule en premier
# ============================================
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# Configuration pour de meilleurs graphiques
import plotly.io as pio
pio.templates.default = "plotly_white"
print("✓ Bibliothèques chargées avec succès!")
print(f" NumPy version: {np.__version__}")
import plotly
print(f" Plotly version: {plotly.__version__}")1. Méthode de bissection
Principe
La méthode de bissection divise successivement l'intervalle en deux, en gardant le sous-intervalle contenant la racine (changement de signe).
Implémentation
def bissection(f, a, b, tol=1e-10, max_iter=100):
"""
Méthode de bissection pour trouver une racine de f dans [a, b].
Paramètres:
-----------
f : fonction dont on cherche la racine
a, b : bornes de l'intervalle (f(a) et f(b) doivent être de signes opposés)
tol : tolérance sur la largeur de l'intervalle
max_iter : nombre maximum d'itérations
Retourne:
---------
racine : approximation de la racine
historique : liste des triplets (a, b, milieu) à chaque itération
"""
# Vérification de la condition initiale
if f(a) * f(b) > 0:
raise ValueError("f(a) et f(b) doivent être de signes opposés")
historique = []
for i in range(max_iter):
# Point milieu
c = (a + b) / 2
historique.append({'a': a, 'b': b, 'c': c, 'f_c': f(c)})
# Critère d'arrêt
if abs(b - a) < tol or abs(f(c)) < tol:
break
# Mise à jour de l'intervalle
if f(a) * f(c) <= 0:
b = c
else:
a = c
return c, historiqueExemple d'utilisation
# Fonction exemple : f(x) = x³ - x - 2
def f(x):
return x**3 - x - 2
# Recherche de la racine dans [1, 2]
racine, hist = bissection(f, 1, 2, tol=1e-8)
print(f"Racine trouvée : {racine:.10f}")
print(f"Nombre d'itérations : {len(hist)}")
print(f"Vérification : f({racine:.10f}) = {f(racine):.2e}")Visualisation interactive
def visualiser_bissection(f, a, b, n_iter=10):
"""Visualise les itérations de la méthode de bissection avec un slider."""
# Calcul des itérations
_, hist = bissection(f, a, b, tol=1e-15, max_iter=n_iter)
# Grille pour tracer la fonction
x = np.linspace(a - 0.5, b + 0.5, 500)
y = np.array([f(xi) for xi in x])
y_min, y_max = y.min() * 1.2, y.max() * 1.2
# Création de la figure
fig = go.Figure()
# Tracé de la fonction (toujours visible)
fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='f(x)',
line=dict(color='blue', width=2)))
# Axe horizontal y=0
fig.add_trace(go.Scatter(x=[a-0.5, b+0.5], y=[0, 0], mode='lines',
name='y=0', line=dict(color='black', width=1)))
# Pour chaque itération, on crée des traces (initialement invisibles sauf la première)
for i, h in enumerate(hist):
visible = (i == 0) # Seule la première itération est visible au départ
# Ligne verticale gauche (borne a)
fig.add_trace(go.Scatter(
x=[h['a'], h['a']], y=[y_min, y_max],
mode='lines', name=f"a={h['a']:.4f}",
line=dict(color='orange', width=2, dash='dash'),
visible=visible, showlegend=visible
))
# Ligne verticale droite (borne b)
fig.add_trace(go.Scatter(
x=[h['b'], h['b']], y=[y_min, y_max],
mode='lines', name=f"b={h['b']:.4f}",
line=dict(color='orange', width=2, dash='dash'),
visible=visible, showlegend=visible
))
# Point milieu sur la courbe
fig.add_trace(go.Scatter(
x=[h['c']], y=[h['f_c']],
mode='markers+text', name=f"c={h['c']:.6f}",
marker=dict(color='green', size=14, symbol='star'),
text=[f"c_{i+1}"], textposition='top center',
visible=visible, showlegend=visible
))
# Bornes sur l'axe x
fig.add_trace(go.Scatter(
x=[h['a'], h['b']], y=[0, 0],
mode='markers', name='Intervalle',
marker=dict(color='red', size=10, symbol='diamond'),
visible=visible, showlegend=False
))
# Création des steps pour le slider
# Chaque itération a 4 traces (a, b, milieu, bornes)
n_base_traces = 2 # f(x) et y=0
n_traces_per_iter = 4
steps = []
for i in range(len(hist)):
# Créer le masque de visibilité
visibility = [True, True] # f(x) et y=0 toujours visibles
for j in range(len(hist)):
if j == i:
visibility.extend([True, True, True, True])
else:
visibility.extend([False, False, False, False])
step = dict(
method='update',
args=[{'visible': visibility},
{'title': f"Bissection - Itération {i+1}/{len(hist)} | "
f"Intervalle [{hist[i]['a']:.6f}, {hist[i]['b']:.6f}] | "
f"f(c) = {hist[i]['f_c']:.2e}"}],
label=str(i+1)
)
steps.append(step)
sliders = [dict(
active=0,
currentvalue={"prefix": "Itération: ", "font": {"size": 14}},
pad={"t": 50},
steps=steps
)]
fig.update_layout(
sliders=sliders,
title=f"Bissection - Itération 1/{len(hist)} | "
f"Intervalle [{hist[0]['a']:.6f}, {hist[0]['b']:.6f}] | "
f"f(c) = {hist[0]['f_c']:.2e}",
xaxis_title="x",
yaxis_title="f(x)",
yaxis=dict(range=[y_min, y_max]),
height=500
)
fig.show()
# Exécution
visualiser_bissection(f, 1, 2, n_iter=15)2. Méthode de la sécante
Principe
La méthode de la sécante utilise une droite passant par deux points pour approximer la racine.
Implémentation
def secante(f, x0, x1, tol=1e-10, max_iter=100):
"""
Méthode de la sécante pour trouver une racine de f.
Paramètres:
-----------
f : fonction dont on cherche la racine
x0, x1 : deux approximations initiales
tol : tolérance
max_iter : nombre maximum d'itérations
Retourne:
---------
racine : approximation de la racine
historique : liste des approximations successives
"""
historique = [{'x': x0, 'f_x': f(x0)}, {'x': x1, 'f_x': f(x1)}]
for i in range(max_iter):
f_x0, f_x1 = f(x0), f(x1)
# Protection contre la division par zéro
if abs(f_x1 - f_x0) < 1e-15:
break
# Formule de la sécante
x2 = x1 - f_x1 * (x1 - x0) / (f_x1 - f_x0)
historique.append({'x': x2, 'f_x': f(x2), 'x_prev': x0, 'x_curr': x1})
# Critère d'arrêt
if abs(x2 - x1) < tol or abs(f(x2)) < tol:
break
# Mise à jour
x0, x1 = x1, x2
return x2, historiqueVisualisation
def visualiser_secante(f, x0, x1, n_iter=8):
"""Visualise les itérations de la méthode de la sécante avec un slider."""
racine, hist = secante(f, x0, x1, tol=1e-15, max_iter=n_iter)
# Déterminer les bornes du graphique
all_x = [h['x'] for h in hist]
x_min, x_max = min(all_x) - 0.5, max(all_x) + 0.5
x = np.linspace(x_min, x_max, 500)
y = np.array([f(xi) for xi in x])
y_min, y_max = min(y.min(), -1) * 1.2, max(y.max(), 1) * 1.2
fig = go.Figure()
# Traces de base (toujours visibles)
fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='f(x)',
line=dict(color='blue', width=2)))
fig.add_trace(go.Scatter(x=[x_min, x_max], y=[0, 0], mode='lines',
name='y=0', line=dict(color='black', width=1)))
# Pré-calculer les sécantes (une par itération à partir de j=2)
secantes_data = []
for j in range(2, len(hist)):
h = hist[j]
if 'x_prev' in h:
x_p, x_c = h['x_prev'], h['x_curr']
f_p, f_c = f(x_p), f(x_c)
if abs(x_c - x_p) > 1e-15:
m = (f_c - f_p) / (x_c - x_p)
x_line = np.array([x_min, x_max])
y_line = f_c + m * (x_line - x_c)
secantes_data.append((j, x_line, y_line))
# Ajouter toutes les sécantes (initialement invisibles)
for j, x_line, y_line in secantes_data:
fig.add_trace(go.Scatter(
x=x_line, y=y_line, mode='lines',
name='Sécante',
line=dict(color='red', width=2),
opacity=0.7,
visible=False
))
# Ajouter les points pour chaque step_idx avec opacité décroissante
# On crée n_points traces pour chaque step (avec opacités différentes)
n_steps = len(hist)
for step_idx in range(n_steps):
for j in range(step_idx + 1):
# Opacité : le point actuel a 1.0, les précédents diminuent de 15% par itération
age = step_idx - j # 0 pour le plus récent
opacity = max(0.15, 1.0 - age * 0.15)
h = hist[j]
fig.add_trace(go.Scatter(
x=[h['x']], y=[h['f_x']],
mode='markers+text',
name=f"x_{j}",
marker=dict(color='green', size=12, opacity=opacity),
text=[f'x_{j}'], textposition='top center',
textfont=dict(color=f'rgba(0,128,0,{opacity})'),
visible=False,
showlegend=(j == step_idx)
))
n_base = 2
n_secantes = len(secantes_data)
# Compter les traces de points par step
# step 0: 1 point, step 1: 2 points, ..., step k: k+1 points
# Total points traces = sum(1 to n_steps) = n_steps*(n_steps+1)/2
# Créer les steps du slider
steps = []
point_trace_start = n_base + n_secantes
for step_idx in range(n_steps):
visibility = [True, True] # f(x) et y=0
# Sécantes : seulement celle de l'itération actuelle
for sec_idx, (j, _, _) in enumerate(secantes_data):
visibility.append(j == step_idx)
# Points : calculer l'index des traces pour ce step
# Les traces sont organisées : step0(1pt), step1(2pts), step2(3pts), ...
for s in range(n_steps):
n_pts_this_step = s + 1
for p in range(n_pts_this_step):
visibility.append(s == step_idx)
step = dict(
method='update',
args=[{'visible': visibility}],
label=str(step_idx)
)
steps.append(step)
sliders = [dict(
active=0,
currentvalue={"prefix": "Itération: ", "font": {"size": 14}},
pad={"t": 50},
steps=steps
)]
fig.update_layout(
sliders=sliders,
title=f"Méthode de la sécante - Racine ≈ {racine:.8f}",
xaxis_title="x",
yaxis_title="f(x)",
yaxis=dict(range=[y_min, y_max]),
height=500
)
fig.show()
# Exécution
visualiser_secante(f, 1.0, 2.0, n_iter=6)3. Méthode de Newton-Raphson
Principe
La méthode de Newton utilise la tangente à la courbe pour approximer la racine.
Implémentation
def newton(f, df, x0, tol=1e-10, max_iter=100):
"""
Méthode de Newton-Raphson pour trouver une racine de f.
Paramètres:
-----------
f : fonction dont on cherche la racine
df : dérivée de f
x0 : approximation initiale
tol : tolérance
max_iter : nombre maximum d'itérations
Retourne:
---------
racine : approximation de la racine
historique : liste des approximations successives
"""
x = x0
historique = [{'x': x, 'f_x': f(x), 'df_x': df(x)}]
for i in range(max_iter):
f_x = f(x)
df_x = df(x)
# Protection contre la division par zéro
if abs(df_x) < 1e-15:
print(f"Attention : dérivée proche de zéro à x = {x}")
break
# Formule de Newton
x_new = x - f_x / df_x
historique.append({'x': x_new, 'f_x': f(x_new), 'df_x': df(x_new),
'x_prev': x})
# Critère d'arrêt
if abs(x_new - x) < tol or abs(f(x_new)) < tol:
break
x = x_new
return x, historiqueExemple avec dérivée
# Fonction : f(x) = x³ - x - 2
def f(x):
return x**3 - x - 2
# Dérivée : f'(x) = 3x² - 1
def df(x):
return 3*x**2 - 1
# Recherche de la racine
racine, hist = newton(f, df, x0=1.5, tol=1e-12)
print(f"Racine trouvée : {racine:.15f}")
print(f"Nombre d'itérations : {len(hist)-1}")
print(f"Vérification : f({racine}) = {f(racine):.2e}")
# Afficher la convergence
print("\nConvergence :")
for i, h in enumerate(hist):
print(f" x_{i} = {h['x']:.15f}, f(x_{i}) = {h['f_x']:.2e}")Visualisation des tangentes
def visualiser_newton(f, df, x0, n_iter=6):
"""Visualise les itérations de la méthode de Newton avec un slider."""
racine, hist = newton(f, df, x0, tol=1e-15, max_iter=n_iter)
# Bornes du graphique
all_x = [h['x'] for h in hist]
x_min, x_max = min(min(all_x) - 1, -1), max(max(all_x) + 1, 3)
x = np.linspace(x_min, x_max, 500)
y = np.array([f(xi) for xi in x])
y_min, y_max = min(y.min(), -1) * 1.3, max(y.max(), 1) * 1.3
fig = go.Figure()
# Traces de base (toujours visibles)
fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='f(x)',
line=dict(color='blue', width=2)))
fig.add_trace(go.Scatter(x=[x_min, x_max], y=[0, 0], mode='lines',
name='y=0', line=dict(color='black', width=1)))
# Pré-calculer les tangentes
tangentes_data = []
for j in range(1, len(hist)):
hp = hist[j]
if 'x_prev' in hp:
xp = hp['x_prev']
fp = f(xp)
dfp = df(xp)
x_tang = np.array([x_min, x_max])
y_tang = fp + dfp * (x_tang - xp)
tangentes_data.append((j, x_tang, y_tang, xp, fp))
# Ajouter toutes les tangentes (initialement invisibles) - seulement la courante sera affichée
for j, x_tang, y_tang, xp, fp in tangentes_data:
fig.add_trace(go.Scatter(
x=x_tang, y=y_tang, mode='lines',
name='Tangente',
line=dict(color='red', width=2),
opacity=0.7,
visible=False
))
n_base = 2
n_tangentes = len(tangentes_data)
n_steps = len(hist)
# Ajouter les points pour chaque step avec opacité décroissante
for step_idx in range(n_steps):
for j in range(step_idx + 1):
age = step_idx - j
opacity = max(0.15, 1.0 - age * 0.15)
h = hist[j]
# Point sur l'axe x
fig.add_trace(go.Scatter(
x=[h['x']], y=[0],
mode='markers+text',
name=f"x_{j}",
marker=dict(color='purple', size=10, symbol='diamond', opacity=opacity),
text=[f'x_{j}'], textposition='top center',
textfont=dict(color=f'rgba(128,0,128,{opacity})'),
visible=False,
showlegend=(j == step_idx)
))
# Point actuel sur la courbe (étoile verte, toujours opaque)
h = hist[step_idx]
fig.add_trace(go.Scatter(
x=[h['x']], y=[h['f_x']],
mode='markers',
name='Point actuel',
marker=dict(color='green', size=14, symbol='star'),
visible=False,
showlegend=False
))
# Créer les steps du slider
steps = []
for step_idx in range(n_steps):
visibility = [True, True] # f(x) et y=0
# Tangentes : seulement celle de l'itération actuelle
for tang_idx, (j, _, _, _, _) in enumerate(tangentes_data):
visibility.append(j == step_idx)
# Points : chaque step a ses propres traces
for s in range(n_steps):
n_pts_this_step = s + 1 # points sur l'axe x
for p in range(n_pts_this_step):
visibility.append(s == step_idx)
# Point sur la courbe
visibility.append(s == step_idx)
step = dict(
method='update',
args=[{'visible': visibility}],
label=str(step_idx)
)
steps.append(step)
sliders = [dict(
active=0,
currentvalue={"prefix": "Itération: ", "font": {"size": 14}},
pad={"t": 50},
steps=steps
)]
fig.update_layout(
sliders=sliders,
title=f"Méthode de Newton - x₀={x0} → Racine ≈ {racine:.10f}",
xaxis_title="x",
yaxis_title="f(x)",
yaxis=dict(range=[y_min, y_max]),
height=500
)
fig.show()
# Exécution
visualiser_newton(f, df, x0=2.0, n_iter=6)4. Méthode du point fixe
Principe
On transforme en et on itère.
La convergence est garantie si au voisinage de la racine.
Implémentation
def point_fixe(g, x0, tol=1e-10, max_iter=100):
"""
Méthode du point fixe : x_{n+1} = g(x_n).
Paramètres:
-----------
g : fonction d'itération (on cherche x tel que x = g(x))
x0 : approximation initiale
tol : tolérance
max_iter : nombre maximum d'itérations
Retourne:
---------
point_fixe : approximation du point fixe
historique : liste des approximations successives
"""
x = x0
historique = [{'x': x, 'g_x': g(x)}]
for i in range(max_iter):
try:
x_new = g(x)
except:
print(f"Erreur de calcul à l'itération {i}")
break
historique.append({'x': x_new, 'g_x': g(x_new) if abs(x_new) < 1e10 else float('inf')})
# Critère d'arrêt
if abs(x_new - x) < tol:
break
# Détection de divergence
if abs(x_new) > 1e10:
print(f"Divergence détectée à l'itération {i}")
break
x = x_new
return x, historiqueVisualisation : diagramme en toile d'araignée
def visualiser_point_fixe(g, x0, x_min=-2, x_max=4, n_iter=15):
"""
Visualise la méthode du point fixe avec un diagramme en toile d'araignée.
"""
_, hist = point_fixe(g, x0, tol=1e-15, max_iter=n_iter)
# Grilles
x = np.linspace(x_min, x_max, 500)
y_g = [g(xi) for xi in x]
y_id = x # y = x
fig = go.Figure()
# g(x) et y = x
fig.add_trace(go.Scatter(x=x, y=y_g, mode='lines', name='g(x)',
line=dict(color='blue', width=2)))
fig.add_trace(go.Scatter(x=x, y=y_id, mode='lines', name='y = x',
line=dict(color='black', width=1, dash='dash')))
# Toile d'araignée
cobweb_x = [hist[0]['x']]
cobweb_y = [0]
for i, h in enumerate(hist[:-1]):
xi = h['x']
gi = h['g_x']
# Vertical : (xi, ?) → (xi, g(xi))
cobweb_x.extend([xi, xi])
cobweb_y.extend([cobweb_y[-1], gi])
# Horizontal : (xi, g(xi)) → (g(xi), g(xi))
cobweb_x.extend([xi, gi])
cobweb_y.extend([gi, gi])
fig.add_trace(go.Scatter(x=cobweb_x, y=cobweb_y, mode='lines',
name='Itérations', line=dict(color='red', width=1)))
# Points d'itération
for i, h in enumerate(hist):
fig.add_trace(go.Scatter(x=[h['x']], y=[h['g_x']], mode='markers',
name=f'x_{i}', marker=dict(size=8),
showlegend=(i < 5)))
# Point fixe théorique (si convergé)
if len(hist) > 1:
pf = hist[-1]['x']
fig.add_trace(go.Scatter(x=[pf], y=[pf], mode='markers',
name='Point fixe',
marker=dict(color='green', size=15, symbol='star')))
fig.update_layout(
title=f"Méthode du point fixe - x₀ = {x0}",
xaxis_title="x",
yaxis_title="y",
xaxis=dict(range=[x_min, x_max]),
yaxis=dict(range=[x_min, x_max]),
width=600,
height=600
)
fig.show()
# Exemple : résoudre x³ - x - 2 = 0 ⟺ x = (x + 2)^(1/3)
def g(x):
return np.cbrt(x + 2) # racine cubique
visualiser_point_fixe(g, x0=1.0, x_min=0, x_max=3, n_iter=10)5. Méthode Regula Falsi (fausse position)
Principe
Combine les avantages de la bissection (intervalle borné) et de la sécante (interpolation linéaire).
Implémentation
def regula_falsi(f, a, b, tol=1e-10, max_iter=100):
"""
Méthode Regula Falsi (fausse position).
Paramètres:
-----------
f : fonction dont on cherche la racine
a, b : bornes de l'intervalle (f(a) et f(b) doivent être de signes opposés)
tol : tolérance
max_iter : nombre maximum d'itérations
Retourne:
---------
racine : approximation de la racine
historique : liste des approximations successives
"""
if f(a) * f(b) > 0:
raise ValueError("f(a) et f(b) doivent être de signes opposés")
historique = []
for i in range(max_iter):
fa, fb = f(a), f(b)
# Interpolation linéaire
c = b - fb * (b - a) / (fb - fa)
fc = f(c)
historique.append({'a': a, 'b': b, 'c': c, 'f_c': fc})
# Critère d'arrêt
if abs(fc) < tol or abs(b - a) < tol:
break
# Mise à jour de l'intervalle
if fa * fc <= 0:
b = c
else:
a = c
return c, historique6. Comparaison des méthodes
Comparaison de la vitesse de convergence
def comparer_methodes(f, df, a, b, x0, tol=1e-12):
"""Compare les différentes méthodes sur un même problème."""
# Bissection
_, hist_bis = bissection(f, a, b, tol=tol)
# Sécante
_, hist_sec = secante(f, a, b, tol=tol)
# Newton
_, hist_new = newton(f, df, x0, tol=tol)
# Regula Falsi
_, hist_rf = regula_falsi(f, a, b, tol=tol)
# Graphique de convergence
fig = go.Figure()
# Erreur en fonction des itérations
methods = [
('Bissection', hist_bis, 'blue'),
('Sécante', hist_sec, 'orange'),
('Newton', hist_new, 'green'),
('Regula Falsi', hist_rf, 'red')
]
# Trouver la vraie racine (Newton avec haute précision)
racine, _ = newton(f, df, x0, tol=1e-15, max_iter=100)
for name, hist, color in methods:
if 'c' in hist[0]: # Bissection ou Regula Falsi
erreurs = [abs(h['c'] - racine) for h in hist]
else: # Sécante ou Newton
erreurs = [abs(h['x'] - racine) for h in hist]
# Éviter log(0)
erreurs = [max(e, 1e-16) for e in erreurs]
fig.add_trace(go.Scatter(
x=list(range(len(erreurs))),
y=erreurs,
mode='lines+markers',
name=f'{name} ({len(hist)} iter)',
line=dict(color=color)
))
fig.update_layout(
title="Comparaison des méthodes - Convergence",
xaxis_title="Itération",
yaxis_title="Erreur |x_n - racine|",
yaxis_type="log",
yaxis=dict(exponentformat='e'),
legend=dict(x=0.7, y=0.95)
)
fig.show()
# Tableau récapitulatif
print("\n" + "="*60)
print("RÉCAPITULATIF")
print("="*60)
print(f"Racine exacte : {racine:.15f}")
print("-"*60)
print(f"{'Méthode':<15} {'Itérations':<12} {'Erreur finale':<20}")
print("-"*60)
for name, hist, _ in methods:
n_iter = len(hist)
if 'c' in hist[-1]:
final_x = hist[-1]['c']
else:
final_x = hist[-1]['x']
erreur = abs(final_x - racine)
print(f"{name:<15} {n_iter:<12} {erreur:.2e}")
# Exemple : f(x) = x³ - x - 2
comparer_methodes(f, df, a=1, b=2, x0=1.5)7. Cas problématiques
Newton : sensibilité au point de départ
# Fonction avec plusieurs racines : f(x) = sin(x)
def f_sin(x):
return np.sin(x)
def df_sin(x):
return np.cos(x)
def explorer_newton_sensibilite():
"""Explore comment le point de départ affecte la convergence de Newton."""
fig = make_subplots(rows=1, cols=2,
subplot_titles=["Différents points de départ",
"Racine trouvée vs x₀"])
x = np.linspace(-10, 10, 1000)
y = np.sin(x)
# Graphique 1 : Trajectoires
fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='sin(x)',
line=dict(color='blue')), row=1, col=1)
fig.add_hline(y=0, line=dict(color='black', width=1), row=1, col=1)
# Différents points de départ
x0_list = [0.5, 1.4, 2.0, 3.0, 4.5]
colors = ['red', 'green', 'orange', 'purple', 'brown']
for x0, color in zip(x0_list, colors):
racine, hist = newton(f_sin, df_sin, x0, tol=1e-10, max_iter=20)
x_vals = [h['x'] for h in hist]
y_vals = [f_sin(xi) for xi in x_vals]
fig.add_trace(go.Scatter(x=x_vals, y=y_vals, mode='lines+markers',
name=f'x₀={x0} → {racine:.2f}',
line=dict(color=color)), row=1, col=1)
# Graphique 2 : Bassin d'attraction
x0_range = np.linspace(-10, 10, 200)
racines_trouvees = []
for x0 in x0_range:
try:
racine, _ = newton(f_sin, df_sin, x0, tol=1e-8, max_iter=50)
# Arrondir à la racine la plus proche (multiple de π)
racine_norm = round(racine / np.pi) * np.pi
racines_trouvees.append(racine_norm)
except:
racines_trouvees.append(np.nan)
fig.add_trace(go.Scatter(x=x0_range, y=racines_trouvees, mode='markers',
name='Racine trouvée', marker=dict(size=3)),
row=1, col=2)
fig.update_layout(height=400, title="Newton sur sin(x) : sensibilité au point de départ")
fig.update_xaxes(title_text="x", row=1, col=1)
fig.update_xaxes(title_text="x₀", row=1, col=2)
fig.update_yaxes(title_text="y", row=1, col=1)
fig.update_yaxes(title_text="Racine trouvée", row=1, col=2)
fig.show()
explorer_newton_sensibilite()Point fixe : convergence vs divergence
def comparer_fonctions_iteration():
"""
Compare deux formulations de point fixe pour le même problème.
x³ - x - 2 = 0 peut s'écrire :
- g₁(x) = (x + 2)^(1/3) (converge car |g₁'| < 1 près de la racine)
- g₂(x) = x³ - 2 (diverge car |g₂'| > 1 près de la racine)
"""
def g1(x):
return np.cbrt(x + 2)
def g2(x):
return x**3 - 2
fig = make_subplots(rows=1, cols=2,
subplot_titles=["g₁(x) = ∛(x+2) - CONVERGE",
"g₂(x) = x³ - 2 - DIVERGE"])
x = np.linspace(0, 3, 200)
# g₁ : converge
fig.add_trace(go.Scatter(x=x, y=[g1(xi) for xi in x], name='g₁(x)',
line=dict(color='blue')), row=1, col=1)
fig.add_trace(go.Scatter(x=x, y=x, name='y=x',
line=dict(color='black', dash='dash')), row=1, col=1)
_, hist1 = point_fixe(g1, 1.0, max_iter=10)
cobweb_x1, cobweb_y1 = [hist1[0]['x']], [0]
for h in hist1[:-1]:
cobweb_x1.extend([h['x'], h['x'], h['g_x']])
cobweb_y1.extend([cobweb_y1[-1], h['g_x'], h['g_x']])
fig.add_trace(go.Scatter(x=cobweb_x1, y=cobweb_y1, name='Itérations',
line=dict(color='red')), row=1, col=1)
# g₂ : diverge
x2 = np.linspace(0.5, 2, 200)
fig.add_trace(go.Scatter(x=x2, y=[min(g2(xi), 10) for xi in x2], name='g₂(x)',
line=dict(color='blue')), row=1, col=2)
fig.add_trace(go.Scatter(x=x2, y=x2, name='y=x',
line=dict(color='black', dash='dash')), row=1, col=2)
_, hist2 = point_fixe(g2, 1.2, max_iter=5)
cobweb_x2, cobweb_y2 = [hist2[0]['x']], [0]
for h in hist2[:-1]:
xi, gi = h['x'], min(h['g_x'], 5)
cobweb_x2.extend([xi, xi, gi])
cobweb_y2.extend([cobweb_y2[-1], gi, gi])
fig.add_trace(go.Scatter(x=cobweb_x2, y=cobweb_y2, name='Itérations',
line=dict(color='red')), row=1, col=2)
fig.update_layout(height=400,
title="Point fixe : importance du choix de g(x)")
fig.update_yaxes(range=[0, 4], row=1, col=1)
fig.update_yaxes(range=[0, 5], row=1, col=2)
fig.show()
print("Dérivées au point fixe r ≈ 1.52:")
r = 1.5214
print(f" |g₁'(r)| = |1/(3∛(r+2)²)| ≈ {abs(1/(3*np.cbrt(r+2)**2)):.3f} < 1 ✓")
print(f" |g₂'(r)| = |3r²| ≈ {abs(3*r**2):.3f} > 1 ✗")
comparer_fonctions_iteration()8. Exercices pratiques
Exercice 1 : Trouver les racines de différentes fonctions
# Testez les méthodes sur ces fonctions :
# 1. f(x) = e^x - 3x (deux racines)
def f1(x):
return np.exp(x) - 3*x
def df1(x):
return np.exp(x) - 3
# 2. f(x) = cos(x) - x (une racine près de 0.74)
def f2(x):
return np.cos(x) - x
def df2(x):
return -np.sin(x) - 1
# 3. f(x) = x² - 2 (racine = √2)
def f3(x):
return x**2 - 2
def df3(x):
return 2*x
# Essayez différentes méthodes et comparez !
# Exemple :
print("Racine de x² - 2 :")
racine, hist = newton(f3, df3, 1.0)
print(f"Newton : {racine:.15f}")
print(f"√2 réel : {np.sqrt(2):.15f}")Exercice 2 : Implémenter Newton modifié pour racines multiples
def newton_modifie(f, df, d2f, x0, tol=1e-10, max_iter=100):
"""
Newton modifié pour racines multiples.
u(x) = f(x)/f'(x)
x_{n+1} = x_n - u(x_n)/u'(x_n)
où u'(x) = 1 - f(x)f''(x)/f'(x)²
"""
x = x0
historique = [{'x': x}]
for i in range(max_iter):
fx = f(x)
dfx = df(x)
d2fx = d2f(x)
if abs(dfx) < 1e-15:
break
u = fx / dfx
u_prime = 1 - fx * d2fx / dfx**2
if abs(u_prime) < 1e-15:
break
x_new = x - u / u_prime
historique.append({'x': x_new})
if abs(x_new - x) < tol:
break
x = x_new
return x, historique
# Test sur f(x) = (x-1)³ (racine triple en x=1)
def f_triple(x):
return (x - 1)**3
def df_triple(x):
return 3 * (x - 1)**2
def d2f_triple(x):
return 6 * (x - 1)
print("Racine triple de (x-1)³ :")
print("Newton classique :", end=" ")
r1, h1 = newton(f_triple, df_triple, 2.0)
print(f"{r1:.10f} en {len(h1)} itérations")
print("Newton modifié :", end=" ")
r2, h2 = newton_modifie(f_triple, df_triple, d2f_triple, 2.0)
print(f"{r2:.10f} en {len(h2)} itérations")Résumé des méthodes
| Méthode | Ordre | Avantages | Inconvénients |
|---|---|---|---|
| Bissection | 1 (linéaire) | Toujours converge, simple | Lent, nécessite intervalle |
| Regula Falsi | ~1.6 | Plus rapide que bissection | Peut stagner |
| Sécante | ~1.618 | Pas besoin de dérivée | Peut diverger |
| Newton | 2 (quadratique) | Très rapide | Nécessite f', sensible à x₀ |
| Point fixe | Dépend de g | Flexible | Choix de g crucial |
Conseil pratique
En pratique, on combine souvent les méthodes : quelques itérations de bissection pour s'approcher de la racine (phase de localisation), puis Newton pour converger rapidement (phase de raffinement).