Partie IV - Calcul scientifique · Chapitre 18

Bibliothèque matplotlib

3 sections de cours · fiche d'exercices corrigee

1Premiers traces et echelles logarithmiques

Un graphique n'est pas un ornement : c'est un outil de verification. Tracer une solution numerique a cote de la solution exacte, ou l'erreur en fonction du pas en echelle logarithmique, revele immediatement une erreur de code ou un ordre de convergence incorrect.

Le module pyplot

matplotlib.pyplot fournit une interface a etats : les commandes s'appliquent a la figure « courante ». C'est l'interface utilisee en CPGE.

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2*np.pi, 200)
plt.plot(x, np.sin(x))
plt.show()                      # affiche la fenetre (bloquant)
Le squelette d'un trace complet
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2*np.pi, 300)

plt.figure(figsize=(8, 5))                      # 1. creer la figure
plt.plot(x, np.sin(x), label="sin", color="teal", linewidth=2)
plt.plot(x, np.cos(x), label="cos", linestyle="--", color="orange")
plt.xlabel("x (radians)")                       # 2. TOUJOURS legender les axes
plt.ylabel("y")
plt.title("Fonctions trigonometriques")
plt.legend()                                    # 3. afficher la legende
plt.grid(True, alpha=0.3)
plt.axhline(0, color="black", linewidth=0.8)    # axe des abscisses
plt.savefig("trigo.png", dpi=150, bbox_inches="tight")   # 4. enregistrer
plt.show()                                      # 5. afficher

Un graphique sans axes legendes est inexploitable : c'est la premiere chose que verifie un correcteur.

Les principaux traces

FonctionUsage
plot(x, y)courbe (points relies)
scatter(x, y)nuage de points
bar(x, h)diagramme en barres
hist(donnees, bins=k)histogramme
errorbar(x, y, yerr)barres d'erreur
imshow(M)image ou matrice en couleurs
contour(X, Y, Z)lignes de niveau
quiver(X, Y, U, V)champ de vecteurs

Style des courbes

plt.plot(x, y, "r--o")        # forme abregee : rouge, tirets, marqueurs ronds
plt.plot(x, y, color="#026573", linestyle=":", marker="s",
         markersize=4, linewidth=2, alpha=0.8, label="mesures")
CouleursStyles de ligneMarqueurs
b bleu, r rouge, g vert, k noir - plein, -- tirets, : pointilles, -. mixte o rond, s carre, ^ triangle, +, x, .

Echelles logarithmiques

plt.semilogy(x, y)        # ordonnee logarithmique  : detecte les lois exponentielles
plt.semilogx(x, y)        # abscisse logarithmique
plt.loglog(x, y)          # les deux                : detecte les lois de puissance
Pourquoi c'est essentiel
  • Si \(y = C a^x\) (loi exponentielle), alors \(\ln y = \ln C + x\ln a\) : le trace semilog-y donne une droite de pente \(\ln a\).
  • Si \(y = C x^p\) (loi de puissance), alors \(\ln y = \ln C + p\ln x\) : le trace log-log donne une droite de pente \(p\).

C'est la methode standard pour mesurer experimentalement l'ordre d'une methode numerique : on trace l'erreur en fonction du pas \(h\) en echelle log-log, et la pente de la droite obtenue est l'ordre \(p\).

import numpy as np, matplotlib.pyplot as plt

h = np.logspace(-4, -1, 20)              # pas de 1e-4 a 1e-1
erreur_ordre1 = 0.5 * h                  # methode d'ordre 1
erreur_ordre2 = 0.5 * h**2               # methode d'ordre 2

plt.loglog(h, erreur_ordre1, "o-", label="ordre 1 : pente 1")
plt.loglog(h, erreur_ordre2, "s-", label="ordre 2 : pente 2")
plt.xlabel("pas h"); plt.ylabel("erreur")
plt.legend(); plt.grid(True, which="both", alpha=0.3)
plt.show()

# mesure de la pente par regression lineaire sur les logarithmes
p = np.polyfit(np.log(h), np.log(erreur_ordre2), 1)[0]
print(f"ordre mesure : {p:.3f}")         # 2.000

2Sous-graphiques, courbes implicites et pieges

Sous-graphiques

fig, axes = plt.subplots(2, 2, figsize=(10, 8))

axes[0, 0].plot(x, np.sin(x));  axes[0, 0].set_title("sin")
axes[0, 1].plot(x, np.cos(x));  axes[0, 1].set_title("cos")
axes[1, 0].plot(x, np.tan(x));  axes[1, 0].set_ylim(-5, 5)
axes[1, 1].hist(np.random.randn(1000), bins=30)

for ax in axes.flat:
    ax.grid(True, alpha=0.3)
fig.suptitle("Quatre traces")
fig.tight_layout()                        # evite le chevauchement des titres
plt.show()
Interface objet

Avec subplots, on manipule des objets Axes : les methodes prennent le prefixe set_ (ax.set_xlabel au lieu de plt.xlabel). Cette interface est plus verbeuse mais explicite ; elle est indispensable des qu'il y a plusieurs graphiques.

Traces parametriques et implicites

# courbe parametree : la lemniscate
t = np.linspace(0, 2*np.pi, 500)
plt.plot(np.sin(t) / (1 + np.cos(t)**2),
         np.sin(t)*np.cos(t) / (1 + np.cos(t)**2))
plt.axis("equal")                # REPERE ORTHONORME : indispensable en geometrie
plt.show()
# courbe implicite f(x,y) = 0 : lignes de niveau
x = np.linspace(-2, 2, 400)
y = np.linspace(-2, 2, 400)
X, Y = np.meshgrid(x, y)                  # grille 2D
Z = X**3 + Y**3 - 3*X*Y                   # folium de Descartes

plt.contour(X, Y, Z, levels=[0], colors="teal")
plt.contourf(X, Y, Z, levels=20, cmap="RdBu", alpha=0.5)
plt.colorbar(label="valeur de f")
plt.axis("equal"); plt.grid(True, alpha=0.3)
plt.show()
Toujours axis("equal") en geometrie

Par defaut, Matplotlib ajuste independamment les deux axes : un cercle apparait comme une ellipse. plt.axis("equal") impose la meme echelle sur les deux axes.

Champs de vecteurs et surfaces

x = np.linspace(-2, 2, 20)
X, Y = np.meshgrid(x, x)
U, V = -Y, X                              # champ de rotation
plt.quiver(X, Y, U, V, np.hypot(U, V), cmap="viridis")
plt.axis("equal"); plt.title("Champ de vecteurs (-y, x)")
plt.show()
from mpl_toolkits.mplot3d import Axes3D    # noqa : necessaire pour projection='3d'

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection="3d")
x = np.linspace(-3, 3, 60)
X, Y = np.meshgrid(x, x)
Z = np.sin(np.sqrt(X**2 + Y**2))
ax.plot_surface(X, Y, Z, cmap="viridis", edgecolor="none")
ax.set_xlabel("x"); ax.set_ylabel("y"); ax.set_zlabel("z")
plt.show()

Pieges courants

Cinq erreurs a eviter
  1. Trop peu de points : np.linspace(0, 10, 20) sur \(\sin(10x)\) produit une courbe fausse (repliement de spectre). Prendre au moins 200 a 500 points.
  2. Discontinuites reliees : plt.plot(x, np.tan(x)) trace des traits verticaux aux asymptotes. Utiliser np.ma.masked_where ou limiter l'ordonnee.
  3. Oublier plt.show() : rien ne s'affiche (hors notebook).
  4. Superposer des figures : sans plt.figure() ni plt.clf(), tous les traces s'accumulent dans la meme figure.
  5. Axes non legendes : un graphique sans xlabel/ylabel ne vaut rien.
# traiter une discontinuite proprement
x = np.linspace(-2*np.pi, 2*np.pi, 1000)
y = np.tan(x)
y = np.ma.masked_where(np.abs(y) > 10, y)     # on masque les valeurs extremes
plt.plot(x, y)
plt.ylim(-10, 10)
plt.show()

3Traceur interactif et applications

Traceur de fonctions
Reduire le nombre de points pour observer le repliement.

Application 1 — verifier une methode numerique

import numpy as np, matplotlib.pyplot as plt

def euler(f, y0, t0, tf, n):
    """Methode d'Euler explicite. Renvoie (t, y)."""
    t = np.linspace(t0, tf, n + 1)
    h = (tf - t0) / n
    y = np.empty(n + 1)
    y[0] = y0
    for k in range(n):
        y[k+1] = y[k] + h * f(t[k], y[k])
    return t, y

# y' = -2y, y(0) = 1  ->  solution exacte y(t) = exp(-2t)
plt.figure(figsize=(11, 4))

plt.subplot(1, 2, 1)
for n in (5, 10, 40):
    t, y = euler(lambda t, y: -2*y, 1, 0, 2, n)
    plt.plot(t, y, "o-", markersize=3, label=f"Euler, n = {n}")
te = np.linspace(0, 2, 300)
plt.plot(te, np.exp(-2*te), "k--", linewidth=2, label="solution exacte")
plt.xlabel("t"); plt.ylabel("y"); plt.legend(); plt.grid(alpha=0.3)

plt.subplot(1, 2, 2)
ns = np.array([5, 10, 20, 40, 80, 160, 320, 640])
erreurs = []
for n in ns:
    t, y = euler(lambda t, y: -2*y, 1, 0, 2, n)
    erreurs.append(abs(y[-1] - np.exp(-4)))
h = 2 / ns
plt.loglog(h, erreurs, "o-", label="erreur mesuree")
plt.loglog(h, h, "k--", label="pente 1 (ordre 1)")
plt.xlabel("pas h"); plt.ylabel("erreur en t = 2")
plt.legend(); plt.grid(True, which="both", alpha=0.3)
plt.tight_layout(); plt.show()

pente = np.polyfit(np.log(h), np.log(erreurs), 1)[0]
print(f"ordre mesure : {pente:.3f}")        # ~ 1.00 : la methode d'Euler est d'ordre 1
La demarche a retenir

Le trace de gauche montre que la methode converge ; celui de droite, en log-log, mesure a quelle vitesse. La pente 1 confirme que la methode d'Euler est d'ordre 1. Si l'on obtenait une pente de 0,5 ou de 2, ce serait le signe d'un bug ou d'une methode differente.

Application 2 — visualiser des donnees

import numpy as np, matplotlib.pyplot as plt

rng = np.random.default_rng(0)
X = rng.normal(170, 10, 2000)

fig, ax = plt.subplots(1, 2, figsize=(11, 4))

ax[0].hist(X, bins=40, density=True, color="teal", alpha=0.7, edgecolor="white")
x = np.linspace(X.min(), X.max(), 300)
ax[0].plot(x, np.exp(-(x-170)**2/200) / np.sqrt(200*np.pi), "r-", linewidth=2,
           label="densite theorique")
ax[0].set_xlabel("taille (cm)"); ax[0].set_ylabel("densite")
ax[0].legend(); ax[0].grid(alpha=0.3)

ax[1].boxplot([X, rng.normal(160, 8, 2000)], labels=["groupe A", "groupe B"])
ax[1].set_ylabel("taille (cm)"); ax[1].grid(alpha=0.3)

fig.tight_layout(); plt.show()

L'essentiel du chapitre

  • Toujours legender : xlabel, ylabel, legend, title.
  • Utiliser np.linspace avec au moins 200 a 500 points pour une courbe lisse.
  • plt.axis("equal") en geometrie, sinon les cercles deviennent des ellipses.
  • Echelle log-log pour mesurer un ordre de convergence : la pente est l'ordre.
  • Echelle semilog-y pour reveler une croissance exponentielle.
  • subplots pour plusieurs graphiques ; tight_layout pour eviter les chevauchements.
  • Tracer la solution numerique a cote de la solution exacte est la meilleure verification d'un code.
Fiche d'exercices — chapitre 18Enonces gradues, du calcul d'application au niveau concours, avec corriges detailles.
Ouvrir la fiche →