Un binder qui peut faire tourner le notebook qui contient les exercices se trouve ici :
https://mybinder.org/v2/gh/nickcollins-craft/USMA1Q-Methodes-Numeriques/HEAD
Plan du cours¶
| Jour | Sujet |
|---|---|
| 03 mars | Fondamentaux de la programmation en Python |
| 04 mars (matin) | Fonctions, listes approfondies, fichiers et graphiques |
| 04 mars (après-midi) | Calcul numérique avec NumPy |
| 11 mars | Tableaux en mémoire, matrices et précision numérique |
| 18 mars | Interpolation, recherche des racines et systèmes non linéaires |
| 25 mars | Intégration numérique |
| 02 avril | Équations différentielles ordinaires et partielles |
| 08 avril | Examen |
Plan de la séance (indicatif)¶
| Horaire | Sujet |
|---|---|
| 0:00 – 0:05 | Récapitulatif & motivation : pourquoi NumPy ? |
| 0:05 – 1:00 | Tableaux NumPy (ndarray) |
| 1:00 – 1:50 | Opérations vectorisées & diffusion (broadcasting) |
| 1:50 – 2:00 | ☕ Pause |
| 2:00 – 2:30 | Indexation, découpage et masques booléens |
| 2:30 – 3:00 | Lecture de fichiers avec NumPy + graphiques |
| 3:00 – 3:45 | Tableaux 2D, maillages et tracés de surface |
0 · Récapitulatif & motivation¶
Les boucles Python sont lentes¶
La séance 2 nous a appris à calculer une moyenne avec une boucle for :
def moyenne(valeurs):
total = 0
for v in valeurs:
total += v
return total / len(valeurs)
Cela fonctionne parfaitement pour de petites listes. Mais que se passe-t-il avec 1 million de valeurs ?
En Python, chaque itération de boucle a un coût fixe élevé (vérification de type, allocation mémoire…).
NumPy délègue les calculs à du code C compilé — il n'y a presque plus de surcoût d'itération.
Démonstration — boucle Python vs NumPy¶
import time
import numpy as np # convention universelle : alias np
# Générer des données avec NumPy (np.random.uniform = uniforme aléatoire)
N = int(1E6) # 1 million de valeurs
donnees_np = np.random.uniform(200, 300, N) # tableau NumPy
donnees_py = list(donnees_np) # liste Python équivalente
# ── Boucle Python ──────────────────────────────────────────────────────────
t0 = time.perf_counter() # perf_counter() = horloge haute précision
total = 0
for v in donnees_py:
total += v
moy_boucle = total / len(donnees_py)
dt_boucle = time.perf_counter() - t0
# ── NumPy ──────────────────────────────────────────────────────────────────
t0 = time.perf_counter()
moy_numpy = np.mean(donnees_np) # mean() = moyenne
dt_numpy = time.perf_counter() - t0
print("Boucle Python :", round(moy_boucle, 4), " temps :", round(dt_boucle * 1000, 1), "ms")
print("NumPy :", round(moy_numpy, 4), " temps :", round(dt_numpy * 1000, 1), "ms")
print("Facteur d'accélération :", round(dt_boucle / dt_numpy, 0), "x")
1 · Tableaux NumPy — ndarray¶
Qu'est-ce qu'un tableau NumPy ?¶
Un tableau NumPy (ndarray — n-dimensional array, tableau à n dimensions) est comme une liste Python, mais :
| Caractéristique | Liste Python | Tableau NumPy |
|---|---|---|
| Type des éléments | peut varier (int, str, …) |
fixe (tous identiques) |
| Opérations mathématiques | élément par élément avec boucle | automatiquement vectorisées |
| Taille | variable (append) |
fixe à la création |
| Mémoire | dispersée | bloc contigu (rapide) |
En ingénierie, un tableau représente une mesure tensorielle : déformations, contraintes, températures, etc.
Penser à un tableau NumPy comme à un vecteur ou une matrice mathématique en un ou deux dimensions, mais ils peuvent avoir un nombre arbitraire des dimensions.
Créer un tableau — les fonctions essentielles¶
import numpy as np
# À partir d'une liste Python — np.array()
a = np.array([1.0, 2.0, 3.0, 4.0]) # array = tableau
# Tableau de zéros — np.zeros(n)
z = np.zeros(5) # → [0. 0. 0. 0. 0.]
# Tableau de uns — np.ones(n)
u = np.ones(4) # → [1. 1. 1. 1.]
# N valeurs régulièrement espacées entre start et stop — np.linspace()
# linspace = linear space (espace linéaire)
T = np.linspace(20, 1200, 7) # → [ 20. 216.67 … 1200.]
# Valeurs de start à stop avec un pas — np.arange()
# arange = array range (intervalle tabulé)
pas = np.arange(0, 1.0, 0.2) # → [0. 0.2 0.4 0.6 0.8]
⚠️
linspacevsarange:
linspace(a, b, n)— vous choisissez le nombre de points ; la bornebest incluse.arange(a, b, pas)— vous choisissez le pas ; la bornebest exclue.
Attributs d'un tableau — shape, dtype, ndim¶
Chaque tableau possède des attributs (attributes) — des informations accessibles avec le point :
a = np.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
a.shape # shape = forme → (2, 3) : 2 lignes, 3 colonnes
a.dtype # dtype = type de données → float64
a.ndim # ndim = nombre de dimensions → 2
| Attribut | Signification | Exemple |
|---|---|---|
shape |
dimensions du tableau | (5,) pour 1D, (3, 4) pour 2D |
dtype |
type de chaque élément | float64, int32, bool |
ndim |
nombre de dimensions | 1, 2, 3… |
⚠️
shapeest un tuple, pas un entier. Pour un tableau 1D de 5 éléments,shape = (5,)— notez la virgule.
Pour obtenir le nombre total d'éléments, utiliseza.sizeoua.shape[0](pour la première dimension).
# ── Démonstration : création de tableaux ──────────────────────────────────
import numpy as np
# Rampe de température pour un traitement thermique
T_rampe = np.linspace(20.0, 1000.0, 6) # 6 points de 20 à 1000 °C
print("Rampe T :", T_rampe)
print("shape :", T_rampe.shape)
print("dtype :", T_rampe.dtype)
# Tableau de déformations
eps = np.arange(0.0, 0.025, 0.005) # pas de 0.5 %
print("Déformations :", eps)
# Tableau 2D — une ligne par éprouvette, colonnes : [E_MPa, Rm_MPa, A_%]
eprouvettes = np.array([
[210000, 510, 22], # éprouvette 1
[208000, 495, 24], # éprouvette 2
[211000, 525, 19], # éprouvette 3
])
print("Tableau 2D shape :", eprouvettes.shape) # → (3, 3)
print("dtype :", eprouvettes.dtype)
Tableaux 1D et 2D — analogie avec les vecteurs et matrices¶
| Objet mathématique | NumPy | Exemple |
|---|---|---|
| Vecteur ligne $[x_1, x_2, x_3]$ | Tableau 1D | np.array([1, 2, 3]) — shape (3,) |
| Matrice $m \times n$ | Tableau 2D | shape (m, n) |
| Cube de données | Tableau 3D | shape (p, m, n) |
Convention rows/columns pour les tableaux 2D :
A = np.zeros((3, 4)) # 3 lignes, 4 colonnes
# ↑ noter le tuple (3, 4) — UNE paire de parenthèses extra
A[1, 2] # élément à la ligne 1, colonne 2 (indices à partir de 0)
A[0, :] # première ligne entière
A[:, 2] # troisième colonne entière
⚠️
np.zeros(3, 4)lève uneTypeError— il faut écrirenp.zeros((3, 4))avec un tuple en argument.
À vous de faire les exercices 1.1 et 1.2 !¶
2 · Opérations vectorisées¶
Calcul élément par élément — sans boucle¶
Avec NumPy, les opérations arithmétiques s'appliquent simultanément à tous les éléments :
import numpy as np
forces = np.array([12500, 13200, 11800, 14000]) # N
diametres = np.array([10.0, 10.0, 9.5, 11.0]) # mm
# Calcul vectorisé : une seule ligne, pas de boucle
aires = np.pi * (diametres / 2) ** 2 # mm²
contraintes = forces / aires # MPa
print(contraintes) # → [159.15 167.93 166.55 147.26]
En Python pur, il faudrait une boucle
forou une compréhension de liste.
NumPy est 10–100× plus rapide et plus lisible.
Fonctions universelles (ufuncs)¶
NumPy fournit des fonctions universelles (universal functions, abrégées ufuncs) qui s'appliquent élément par élément à un tableau :
| Fonction | Signification | Exemple |
|---|---|---|
np.sqrt(a) |
racine carrée (square root) | np.sqrt(np.array([4, 9])) → [2. 3.] |
np.exp(a) |
exponentielle | np.exp(0) → 1.0 |
np.log(a) |
logarithme naturel | np.log(np.e) → 1.0 |
np.log10(a) |
logarithme décimal | np.log10(1000) → 3.0 |
np.abs(a) |
valeur absolue | np.abs(-3.5) → 3.5 |
np.sin(a) |
sinus (angle en radians) | np.sin(np.pi / 2) → 1.0 |
np.cos(a) |
cosinus (angle en radians) | np.cos(np.pi/2) → 0.0 |
np.tan(a) |
tangent (angle en radians) | np.tan(np.pi/4) → 1.0 |
temperatures = np.linspace(300, 1200, 5) # K
Ea = 150e3 # J/mol — énergie d'activation
R = 8.314 # J/(mol·K)
A0 = 1e12 # facteur pré-exponentiel
# Arrhenius vectorisé — calcule k pour chaque T en une ligne
k = A0 * np.exp(-Ea / (R * temperatures))
⚠️ Utilisez toujours
np.exp,np.log,np.sqrtetc. sur des tableaux NumPy.
math.expne fonctionne que sur des scalaires et lève uneTypeErrorsur un tableau.
Agrégations — np.mean, np.std, np.max, np.min¶
Les agrégations (aggregations) condensent un tableau en une seule valeur (ou un vecteur si on précise l'axe) :
durete = np.array([245, 251, 238, 260, 249, 253, 247, 255, 241, 258])
np.mean(durete) # mean = moyenne → 249.7
np.std(durete) # std = écart-type (population) → 6.97
np.max(durete) # → 260
np.min(durete) # → 238
np.sum(durete) # → 2497
Argument axis= — pour les tableaux 2D :
# Tableau 2D : lignes = éprouvettes, colonnes = [E, Rm, A]
data = np.array([[210000, 510, 22],
[208000, 495, 24],
[211000, 525, 19]])
np.mean(data, axis=0) # moyenne de chaque colonne → [209667, 510, 21.67]
np.mean(data, axis=1) # moyenne de chaque ligne → [70177, 69173, 70181]
Mémorisez ainsi :
axis=0réduit la première dimension (les lignes disparaissent) ;axis=1réduit la deuxième (les colonnes disparaissent).
Diffusion (broadcasting)¶
La diffusion (broadcasting) est la règle qui détermine comment NumPy combine des tableaux de formes différentes.
Cas le plus courant — scalaire + tableau :
T = np.array([20, 200, 400, 600, 800]) # °C
T_kelvin = T + 273.15 # → [293.15, 473.15, …]
# Le scalaire 273.15 est "diffusé" sur tous les éléments
Deuxième cas — vecteur 1D + tableau 2D :
data = np.array([[500, 22, 245],
[510, 20, 251],
[495, 24, 238]]) # shape (3, 3)
moyennes = np.mean(data, axis=0) # shape (3,) → [501.67, 22.0, 244.67]
# "Centrer" chaque colonne (voir sa position rélatif à la moyenne) en une ligne :
data_centree = data - moyennes # (3,3) - (3,) → broadcasting automatique
La règle générale : deux dimensions sont compatibles si elles sont égales, ou si l'une d'elles vaut 1.
NumPy étend (diffuse) la dimension de taille 1 pour la rendre égale à l'autre.
# ── Démonstration : opérations vectorisées & agrégations ──────────────────
import numpy as np
# Données de traction — 5 éprouvettes cylindriques
forces = np.array([12500, 13600, 11900, 14200, 12800]) # N
diametres = np.array([10.0, 10.5, 9.8, 11.2, 10.1]) # mm
# Calcul vectorisé des contraintes
aires = np.pi * (diametres / 2) ** 2 # mm²
contraintes = forces / aires # MPa
print("Contraintes (MPa) :", np.round(contraintes, 1))
# Agrégations
print("Moyenne :", round(np.mean(contraintes), 2), "MPa")
print("Écart-type:", round(np.std(contraintes), 2), "MPa")
print("Maximum :", round(np.max(contraintes), 2), "MPa")
print("Minimum :", round(np.min(contraintes), 2), "MPa")
# Arrhenius — vectorisé sur un tableau de températures
T = np.linspace(400, 1200, 5) # K
Ea = 120e3 # J/mol
R = 8.314 # J/(mol·K)
A0 = 1e10 # 1/s
k = A0 * np.exp(-Ea / (R * T))
print("\nConstante cinétique k :", np.round(k, 3))
⚠️ Erreurs de forme fréquentes avec broadcasting¶
NumPy vérifie la compatibilité des formes avant tout calcul.
Une incompatibilité lève immédiatement une ValueError — c'est utile car elle signale un bug logique plutôt que de produire un résultat silencieusement erroné.
Règle de compatibilité (rappel) :
Deux dimensions sont compatibles si elles sont égales, ou si l'une d'elles vaut 1.
La vérification se fait dimension par dimension en partant de la droite.
Cas 1 — longueurs incompatibles (erreur la plus courante)
import numpy as np
forces = np.array([12500, 13600, 11900, 14200]) # shape (4,)
diametres = np.array([10.0, 10.5, 9.8]) # shape (3,)
# MAUVAIS — (4,) et (3,) sont incompatibles
aires = np.pi * (diametres / 2) ** 2
contraintes = forces / aires
# → ValueError: operands could not be broadcast together with shapes (4,) (3,)
Cas 2 — vecteur 1D soustrait d'un tableau 2D dans le mauvais sens
data = np.array([[500, 22], [510, 20], [495, 24]]) # shape (3, 2)
moyenne = np.array([501.67, 22.0, 24.0]) # shape (3,) ← mauvaise forme
centree = data - moyenne
# → ValueError: operands could not be broadcast together with shapes (3,2) (3,)
# NumPy essaie d'aligner par la droite : (3, 2) vs (3,) → (3, 2) vs (1, 3) → incompatible
Corrections pour le cas 2
# Soustraire la moyenne de chaque colonne — shape (2,) compatible avec (3, 2)
moy_col = np.mean(data, axis=0) # shape (2,)
centree = data - moy_col # (3, 2) - (2,) → NumPy aligne par la droite → OK
# Soustraire la moyenne de chaque ligne — il faut shape (3, 1)
moy_lig = np.mean(data, axis=1, keepdims=True) # keepdims=True conserve la dimension → shape (3, 1)
centree = data - moy_lig # (3, 2) - (3, 1) → NumPy diffuse la colonne → OK
Réflexe de débogage : avant toute opération entre tableaux, imprimez
a.shapeetb.shape.
keepdims=Trueest l'argument qui évite de perdre une dimension lors d'une agrégation.
À vous de faire les exercices 2.1 et 2.2 !¶
☕ Pause — 10 minutes¶
3 · Indexation, découpage et masques booléens¶
Indexation et découpage (slicing) — pareil que les listes, mais plus puissant¶
a = np.array([10, 20, 30, 40, 50, 60, 70, 80])
a[2] # → 30 (un seul élément)
a[-1] # → 80 (dernier élément — indice négatif)
a[2:5] # → [30, 40, 50] (indices 2, 3, 4 — borne haute exclue)
a[::2] # → [10, 30, 50, 70] (un sur deux)
a[::-1] # → [80, 70, … 10] (ordre inversé)
B = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
B[1, 2] # → 6 (ligne 1, colonne 2)
B[0, :] # → [1, 2, 3] (première ligne)
B[:, 1] # → [2, 5, 8] (deuxième colonne)
B[0:2, 1:] # → [[2,3],[5,6]] (sous-matrice)
⚠️ Le découpage NumPy renvoie une vue (view), pas une copie. Modifier la sous-matrice modifie aussi le tableau original !
Pour une copie indépendante :B[0:2, :].copy(). Ça donne une copie "profonde" où les manipulations sur les copies ne changent pas l'original.
Masques booléens (boolean masks)¶
Un masque booléen est un tableau de True/False obtenu en appliquant une condition à un tableau.
On l'utilise pour filtrer les éléments qui satisfont la condition.
UTS = np.array([480, 530, 510, 470, 560, 490, 615, 505])
# Étape 1 — créer le masque
masque = UTS > 500 # → [False True True False True False True True]
# Étape 2 — appliquer le masque pour sélectionner
au_dessus = UTS[masque] # → [530, 510, 560, 615, 505]
# En une ligne
au_dessus = UTS[UTS > 500]
# Compter → np.sum compte les True
nb = np.sum(UTS > 500) # → 5
Pourquoi cela fonctionne ? Python traite
Truecomme1etFalsecomme0.
Ainsinp.sum(masque)compte exactement le nombre deTrue.
Sélection conditionnelle — np.where()¶
np.where(condition, valeur_si_vrai, valeur_si_faux) (où) construit un nouveau tableau en choisissant entre deux valeurs selon la condition :
UTS = np.array([480, 530, 510, 470, 560, 490])
# Classer : "conforme" si UTS >= 500, sinon "non conforme"
labels = np.where(UTS >= 500, "conforme", "non conforme")
# → ['non conforme' 'conforme' 'conforme' 'non conforme' 'conforme' 'non conforme']
# Remplacer les valeurs hors-spec par NaN
filtre = np.where(UTS >= 500, UTS, np.nan)
# → [nan 530. 510. nan 560. nan]
Avec un seul argument, np.where(condition) retourne les indices des éléments True :
indices = np.where(UTS >= 500) # → (array([1, 2, 4]),)
# Utile pour récupérer la position des éléments intéressants
premier_conforme = UTS[np.where(UTS >= 500)[0][0]] # → 530
# ── Démonstration : masques booléens & np.where ────────────────────────────
import numpy as np
# Tableau 2D : 8 éprouvettes × 2 propriétés [UTS (MPa), allongement (%)]
specimens = np.array([[480, 14], [530, 11], [510, 16], [470, 9],
[560, 18], [490, 13], [615, 8], [505, 15]])
UTS = specimens[:, 0] # première colonne
allongements = specimens[:, 1] # deuxième colonne
# Filtrer : UTS > 500 MPa
print("UTS > 500 MPa :", UTS[UTS > 500])
print("Nombre de specimens :", np.sum(UTS > 500))
print("UTS moyen (>500 seul.):", round(np.mean(UTS[UTS > 500]), 1), "MPa")
# Filtrer sur les deux colonnes : UTS ≥ 495 ET allongement ≥ 12 %
masque_double = (UTS >= 495) & (allongements >= 12) # & = ET logique sur tableaux
print("Conformes (UTS≥495 ET A≥12%) :")
print(specimens[masque_double])
# Lignes où allongement > 15 %
print("Lignes A > 15% :", np.where(allongements > 15))
⚠️ Pièges avec les masques booléens¶
1. Utiliser and/or Python au lieu de &/| NumPy
# MAUVAIS — lève une ValueError ambiguë
masque = (UTS >= 500) and (allongements >= 12)
# BON — opérateurs bit-à-bit pour les tableaux NumPy
masque = (UTS >= 500) & (allongements >= 12) # & = ET
masque = (UTS >= 500) | (allongements >= 12) # | = OU
masque = ~(UTS >= 500) # ~ = NON (négation)
2. Oublier les parenthèses
# MAUVAIS — priorité des opérateurs erronée
masque = UTS >= 500 & allongements >= 12
# Python évalue : UTS >= (500 & allongements) >= 12 ← incorrect !
# BON
masque = (UTS >= 500) & (allongements >= 12)
À vous de faire les exercices 3.1 et 3.2 !¶
4 · Lecture de fichiers avec NumPy¶
np.loadtxt() — lire un CSV simple¶
Pour les fichiers CSV sans en-tête compliqué, np.loadtxt() (charger texte) est le moyen le plus direct :
import numpy as np
data = np.loadtxt("donnees_traction.csv",
delimiter=",", # delimiter = séparateur (virgule)
skiprows=3, # skiprows = ignorer les 3 premières lignes
comments="#") # comments = ignorer les lignes commençant par #
# data est un tableau 2D : une ligne par point, une colonne par variable
deformations = data[:, 0] # première colonne
contraintes = data[:, 1] # deuxième colonne
np.genfromtxt() — fichiers avec données manquantes¶
data = np.genfromtxt("mesures.csv",
delimiter=",",
skip_header=1, # skip_header = ignorer la ligne d'en-tête
filling_values=np.nan) # remplacer les manquants par NaN
loadtxtvsgenfromtxt:
loadtxt— plus rapide, mais échoue si une valeur est manquantegenfromtxt— plus souple, gère les valeurs manquantes avecfilling_values. Il faut bien spécifier tels valeurs pour ne pas les confondre avec des données réelles (donc utilisez NaN et pas zéro)
import numpy as np
import matplotlib.pyplot as plt
# 1. Charger
data = np.loadtxt("essai_traction_316L.csv", delimiter=",", comments="#")
# Colonnes du fichier : force (N), déplacement (mm)
force = data[:, 0]
deplacement = data[:, 1]
# Dimensions de l'éprouvette
d0 = 10.0 # mm — diamètre initial
L0 = 50.0 # mm — longueur de jauge initiale
# 2. Calculer — contrainte et déformation d'ingénierie
A0 = np.pi * (d0 / 2) ** 2 # mm²
contrainte = force / A0 # MPa
deformation = deplacement / L0 # sans unité
# 3. Tracer
plt.figure(figsize=(8, 5))
plt.plot(deformation, contrainte, color='steelblue', linewidth=2, label='316L')
plt.xlabel("Deformation e (sans unite)", fontsize=13)
plt.ylabel("Contrainte s (MPa)", fontsize=13)
plt.title("Courbe contrainte-deformation — acier 316L", fontsize=14)
plt.legend(fontsize=12)
plt.grid(True)
plt.tight_layout()
plt.show()
# ── Démonstration : np.loadtxt + calcul + tracé ────────────────────────────
import numpy as np
import matplotlib.pyplot as plt
# Charger le fichier CSV de la séance 2
data = np.loadtxt("donnees_traction.csv", delimiter=",", comments="#", skiprows=1)
# skiprows=1 — ignorer la ligne d'en-tête "deformation,contrainte"
deformations = data[:, 0]
contraintes = data[:, 1]
print("Points chargés :", len(deformations))
print("Déformation max :", round(np.max(deformations), 4))
print("Contrainte max :", round(np.max(contraintes), 1), "MPa")
print("Contrainte moy :", round(np.mean(contraintes), 1), "MPa")
# Tracé
plt.figure(figsize=(8, 5))
plt.plot(deformations, contraintes, color='steelblue', linewidth=2, marker='o', markersize=4, label='Acier 316L')
plt.xlabel("Deformation ", fontsize=13)
plt.ylabel("Contrainte (MPa)", fontsize=13)
plt.title("Courbe contrainte-deformation — acier 316L", fontsize=14)
plt.legend(fontsize=12)
plt.grid(True)
plt.tight_layout()
plt.show()
À vous de faire les exercices 4.1 et 4.2 !¶
5 · Tableaux 2D, maillages et tracés de surface¶
Motivation — visualiser une propriété sur deux variables¶
En ingénierie, de nombreuses propriétés dépendent de deux paramètres simultanément :
- La constante cinétique $k$ dépend de la température $T$ et de l'énergie d'activation $E_a$ (Arrhenius)
- La résistance dépend de la composition et du temps de traitement thermique
- La déflexion d'une plaque dépend des coordonnées $x$ et $y$
Pour visualiser cela, on crée une grille de points $(T_i, E_{a,j})$ et on calcule la propriété en chaque point.
NumPy fournit np.meshgrid() (maillage) pour créer cette grille automatiquement.
np.meshgrid() — créer une grille 2D¶
import numpy as np
# Deux vecteurs 1D définissant les axes
x = np.array([1, 2, 3]) # 3 valeurs
y = np.array([10, 20]) # 2 valeurs
# meshgrid produit deux tableaux 2D de forme (ny, nx)
X, Y = np.meshgrid(x, y)
#
# X → [[1, 2, 3], Y → [[10, 10, 10],
# [1, 2, 3]] [20, 20, 20]]
#
# En chaque point (X[i,j], Y[i,j]) on a les coordonnées du nœud de la grille.
# Calculer une propriété sur la grille — pas de boucle !
Z = X ** 2 + Y
# Z[i,j] = X[i,j]² + Y[i,j] pour toute la grille
Pourquoi deux tableaux 2D ?
Parce que pour calculerZ(x, y), on a besoin des valeurs dexetyen chaque nœud.meshgrid"étire" chaque vecteur pour couvrir toute la grille.
Tracés 2D — contours et cartes de chaleur¶
Matplotlib offre plusieurs tracés pour visualiser Z(x, y) :
import matplotlib.pyplot as plt
# 1. Contours colorés — plt.contourf()
# contourf = contour filled (contours remplis)
plt.figure(figsize=(7, 5))
cf = plt.contourf(X, Y, Z, levels=20, cmap='viridis')
plt.colorbar(cf, label='Valeur de Z') # colorbar = barre de couleurs
plt.xlabel("x", fontsize=12)
plt.ylabel("y", fontsize=12)
plt.title("Carte de Z(x, y)", fontsize=13)
plt.tight_layout()
plt.show()
# 2. Carte de chaleur (*heatmap*) — plt.pcolormesh()
# pcolormesh = pseudocolor mesh (maille pseudocouleur)
plt.figure(figsize=(7, 5))
pc = plt.pcolormesh(X, Y, Z, cmap='plasma', shading='auto')
plt.colorbar(pc)
plt.tight_layout()
plt.show()
| Tracé | Usage |
|---|---|
contourf |
Mettre en évidence des iso-valeurs (ex. contours de température) |
pcolormesh |
Vue rapide de la distribution sur une grille régulière |
plot_surface (3D) |
Perspective tridimensionnelle pour les présentations "marketing" |
Surface 3D — Axes3D et plot_surface()¶
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # Axes3D = axes tridimensionnel
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d') # projection = '3d'
ax.plot_surface(X, Y, Z,
cmap='viridis', # colormap (palette de couleurs)
edgecolor='none', # edgecolor = couleur des arêtes
alpha=0.9) # alpha = transparence
ax.set_xlabel("x", fontsize=11)
ax.set_ylabel("y", fontsize=11)
ax.set_zlabel("Z", fontsize=11) # set_zlabel = étiquette de l'axe z
ax.set_title("Surface Z(x,y)", fontsize=13)
plt.tight_layout()
plt.show()
⚠️ La surface 3D est utile pour les présentations, mais un
contourfoupcolormeshest souvent plus lisible pour lire des valeurs précises.
# ── Démonstration : Arrhenius sur une grille T × Ea ──
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# ── Données physiques ───
R = 8.314 # J/(mol·K)
A0 = 1e13 # facteur pré-exponentiel
T_vec = np.linspace(300, 1200, 80) # températures : 300–1200 K
Ea_vec = np.linspace(50e3, 250e3, 80) # E_a : 50–250 kJ/mol
T_grid, Ea_grid = np.meshgrid(T_vec, Ea_vec) # grilles 2D
# Constante cinétique d'Arrhenius — vectorisé sur toute la grille
k_grid = A0 * np.exp(-Ea_grid / (R * T_grid))
# ── Tracé : contours remplis ─
plt.figure(figsize=(8, 5))
cf = plt.contourf(T_grid, Ea_grid / 1e3, np.log10(k_grid),
levels=20, cmap='viridis')
plt.colorbar(cf, label='log10(k)')
plt.xlabel("Temperature T (K)", fontsize=12)
plt.ylabel("Energie d activation Ea (kJ/mol)", fontsize=12)
plt.title("Constante cinematique d Arrhenius log10(k)", fontsize=13)
plt.tight_layout()
plt.show()
# ── Surface 3D ──
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(T_grid, Ea_grid / 1e3, np.log10(k_grid),
cmap='viridis', edgecolor='none', alpha=0.85)
ax.set_xlabel("T (K)", fontsize=10)
ax.set_ylabel("Ea (kJ)", fontsize=10)
ax.set_zlabel("log10(k)", fontsize=10)
ax.set_title("Arrhenius — surface 3D", fontsize=12)
plt.tight_layout()
plt.show()
Choisir une palette de couleurs (colormap)¶
Le choix de la palette influence fortement la lisibilité et l'accessibilité d'un graphique :
| Palette | Type | Usage recommandé |
|---|---|---|
viridis |
séquentielle | valeurs de 0 jusqu'à un max — par défaut |
plasma |
séquentielle | similaire à viridis, contraste élevé |
coolwarm |
divergente | valeurs positives et négatives (ex. écarts) |
RdBu |
divergente | rouge–blanc–bleu, fréquent en météo/matériaux |
hot |
séquentielle | températures |
Règle d'or : évitez
jet(arc-en-ciel) — il crée de fausses perceptions des gradients et est inaccessible aux daltoniens.
viridisetplasmasont perceptivement uniformes et lisibles en noir-et-blanc.
À vous de faire l'exercice 5 !¶
Points clés de la séance¶
| Concept | Ce qu'il faut retenir |
|---|---|
np.array() |
Convertit une liste en tableau ; tous les éléments ont le même dtype |
np.linspace(a,b,n) |
n points entre a et b inclus |
shape, dtype, ndim |
Attributs fondamentaux — à vérifier avant tout calcul |
| Opérations vectorisées | +, -, *, /, ** opèrent élément par élément — aucune boucle |
np.exp, np.sqrt… |
Utiliser les ufuncs NumPy sur les tableaux |
Agrégations + axis= |
np.mean(A, axis=0) = moyenne de chaque colonne |
| Masques booléens | a[a > seuil] — extraire sans boucle ; combiner avec & et \| |
| Vue vs copie | Le découpage NumPy renvoie une vue ; utilisez .copy() si besoin |
np.loadtxt() |
Lire un CSV propre en une ligne ; skiprows pour l'en-tête |
np.meshgrid() |
Créer une grille 2D à partir de deux vecteurs 1D |
contourf / pcolormesh |
Visualiser Z(x,y) sur une grille — toujours ajouter colorbar |