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 | Fonctions, listes approfondies, fichiers et graphiques |
| 11 mars | Matrices, précision et la résolution numérique des équations linéaires |
| 18 mars | Interpolation, la recherche des racines des fonctions et la résolution numérique des équations non linéaires |
| 25 mars | Intégration numérique |
| 02 avril | La résolution numérique des équations différentielles ordinaires et partielles |
| 08 avril | Examen |
Plan de la séance (indicatif)¶
| Horaire | Sujet |
|---|---|
| 0:00 – 1:00 | Fonctions (def, return, docstrings, erreurs) |
| 1:00 – 1:50 | Listes approfondies (découpage, compréhensions) |
| 1:50 – 2:00 | ☕ Pause |
| 2:00 – 2:40 | Lecture de fichiers (open, split, CSV) |
| 2:40 – 3:45 | Premiers graphiques avec Matplotlib |
1 · Fonctions¶
Pourquoi écrire des fonctions ?¶
Imaginez que vous calculez la moyenne d'une liste à cinq endroits différents dans votre script.
Si vous corrigez une erreur dans le calcul, vous devez la corriger cinq fois — et oublier l'une d'elles crée un bug silencieux.
Une fonction (function) résout ce problème :
- Réutilisation — écrire une fois, appeler partout
- Lisibilité — un nom explicite remplace dix lignes de calcul
- Testabilité — on peut vérifier une fonction seule, indépendamment du reste
Règle d'or : si vous copiez-collez le même bloc de code deux fois, il est temps d'écrire une fonction.
Définir une fonction — def et return¶
def nom_de_la_fonction(parametre1, parametre2):
# corps de la fonction — indenté de 4 espaces
resultat = parametre1 + parametre2
return resultat
def(définir) — mot-clé qui annonce la définition d'une fonction- Les paramètres (parameters) sont les variables d'entrée, listées entre parenthèses. Ils peuvent être de n'importe quel type
return(retourner) — envoie la valeur calculée à l'appelant et termine la fonction- Le corps (body) est indenté — comme pour
ifetfor
def contrainte(force, aire):
"""Calcule la contrainte en MPa."""
return force / aire
sigma = contrainte(10000, 78.5) # appel avec les arguments
print(sigma) # → 127.3885…
La définition décrit ce que fait la fonction. L'appel (call) l'exécute.
Démonstration — fonctions simples¶
# ── Fonctions simples ──────────────────────────────────────────────────────
def celsius_vers_kelvin(t_celsius):
"""Convertit une température de Celsius en Kelvin."""
return t_celsius + 273.15
def taux_allongement(L0, Lf):
"""Calcule le taux d'allongement en pourcentage."""
return ((Lf - L0) / L0) * 100
# Appels
print(celsius_vers_kelvin(850)) # → 1123.15 K
print(taux_allongement(50.0, 63.5)) # → 27.0 %
Retourner plusieurs valeurs¶
Une fonction peut renvoyer plusieurs valeurs séparées par une virgule — Python les regroupe dans un tuple (n-uplet).
def min_max(valeurs):
"""Retourne le minimum et le maximum d'une liste."""
mini = valeurs[0]
maxi = valeurs[0]
for v in valeurs:
if v < mini:
mini = v
if v > maxi:
maxi = v
return mini, maxi # deux valeurs séparées par une virgule
m, M = min_max([245, 251, 238, 260, 249])
print("Min =", m, "Max =", M) # → Min = 238 Max = 260
On peut récupérer chaque valeur dans sa propre variable en les séparant par une virgule à gauche du
=.
Les docstrings (chaînes de documentation)¶
Une docstring est une chaîne de caractères placée juste après def, entre triples guillemets. Elle décrit ce que fait la fonction : ce qu'elle attend en entrée, ce qu'elle retourne. Ce n'est pas une partie obligatoire, mais c'est un bon pratique.
def moyenne(valeurs):
"""
Calcule la moyenne arithmétique d'une liste de nombres.
Paramètres
----------
valeurs : list
Liste de nombres (int ou float).
Retourne
--------
float
La moyenne de la liste.
"""
total = 0
for v in valeurs:
total += v
return total / len(valeurs)
La docstring est accessible avec help(moyenne) — votre IDE l'affiche aussi automatiquement.
Bonne pratique : une fonction sans docstring est une fonction à moitié finie si on veut faire un code durable.
⚠️ Oublier return — la fonction retourne None¶
Si une fonction ne contient pas de return, Python retourne silencieusement None (rien) — sans erreur.
C'est une source fréquente de bugs difficiles à repérer.
def double(x):
resultat = x * 2
# oubli du return !
y = double(5)
print(y) # → None ← pas 10 !
print(y + 1) # → TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
Règle : si vous voyez None là où vous attendez un nombre, cherchez un return manquant.
Lever une erreur — raise ValueError¶
Une bonne fonction vérifie ses entrées et signale clairement si quelque chose ne va pas.
raise(lever) — déclenche une exception (exception) et interrompt l'exécutionValueError(erreur de valeur) — exception standard pour une valeur incorrecte
def racine_carree(x):
"""Calcule √x. Lève ValueError si x est négatif."""
if x < 0:
raise ValueError("x doit être positif, reçu : " + str(x))
return x ** 0.5
print(racine_carree(9)) # → 3.0
print(racine_carree(-4)) # → ValueError: x doit être positif, reçu : -4
Un message d'erreur explicite vaut mieux qu'un résultat silencieusement faux (
NaNou un nombre aberrant).
# ── Démonstration : fonction robuste ──────────────────────────────────────
import math
def contrainte_engineering(force, diametre):
"""
Calcule la contrainte d'ingénierie (MPa) pour un cylindre.
Paramètres
----------
force : float Force en N
diametre : float Diamètre en mm
Retourne
--------
float Contrainte en MPa
"""
if diametre <= 0:
raise ValueError("Le diamètre doit être strictement positif, reçu : " + str(diametre))
if force < 0:
raise ValueError("La force ne peut pas être négative, reçu : " + str(force))
aire = math.pi * (diametre / 2) ** 2 # mm²
return force / aire # N/mm² = MPa
print(contrainte_engineering(15000, 10.0)) # → 190.99 MPa
# print(contrainte_engineering(15000, -5)) # → ValueError (décommenter pour tester)
À vous de faire les exercices 1.1, 1.2 et 1.3 !¶
2 · Listes approfondies¶
Découpage (slicing) — liste[début:fin:pas]¶
Le découpage extrait une sous-liste sans modifier l'originale.
mesures = [245, 251, 238, 260, 249, 253, 247]
# 0 1 2 3 4 5 6
mesures[1:4] # → [251, 238, 260] indice 1 inclus, 4 exclus
mesures[:3] # → [245, 251, 238] du début jusqu'à 3 (exclus)
mesures[4:] # → [249, 253, 247] de 4 jusqu'à la fin
mesures[::2] # → [245, 238, 249, 247] un élément sur deux (pas=2)
mesures[::-1] # → [247, 253, 249, 260, 238, 251, 245] ordre inversé
⚠️ Le découpage crée une copie indépendante. Modifier la sous-liste ne modifie pas l'originale.
Compréhension de liste (list comprehension)¶
La compréhension de liste est une façon concise de construire une nouvelle liste à partir d'une liste existante.
Forme générale :
nouvelle_liste = [expression for element in liste_source]
Avec filtre (filter) optionnel :
nouvelle_liste = [expression for element in liste_source if condition]
Exemples :
mesures = [245, 251, 238, 260, 249]
# Convertir chaque valeur HB en MPa (approximation : HB × 3.45)
en_mpa = [v * 3.45 for v in mesures]
# → [845.25, 866.95, 821.1, 897.0, 860.05]
# Ne garder que les valeurs supérieures à 248
elevees = [v for v in mesures if v > 248]
# → [251, 260, 249]
Compréhension vs boucle for¶
Les deux formes produisent le même résultat :
# Avec boucle — explicite, facile à lire pour les débutants
carres = []
for v in mesures:
carres.append(v ** 2)
# Avec compréhension — plus concis, idiomatique en Python
carres = [v ** 2 for v in mesures]
Quand utiliser quoi ?
| Situation | Forme recommandée |
|---|---|
| Transformation simple d'une liste | Compréhension |
| Logique complexe, plusieurs étapes | Boucle for |
| Accumulation avec état (total, max…) | Boucle for |
# ── Démonstration : compréhensions de listes ──────────────────────────────
limites_elastiques = [320, 415, 285, 510, 370, 295, 480, 440, 350, 395] # MPa
# Conversion en kN/cm² (1 MPa = 0.1 kN/cm²)
en_kn_cm2 = [v * 0.1 for v in limites_elastiques]
print("En kN/cm² :", en_kn_cm2)
# Filtrer : valeurs ≥ 400 MPa (aciers haute résistance)
haute_resistance = [v for v in limites_elastiques if v >= 400]
print("≥ 400 MPa :", haute_resistance)
# Pourcentage qui passe
taux = len(haute_resistance) / len(limites_elastiques) * 100
print("Taux de conformité :", round(taux), "%")
À vous de faire les exercices 2.1 et 2.2 !¶
☕ Pause — 10 minutes¶
3 · Lecture de fichiers¶
Ouvrir un fichier — open() et le gestionnaire with¶
open(chemin, mode) (ouvrir) ouvre un fichier et retourne un objet fichier (file object).
# Sans gestionnaire — il FAUT fermer le fichier manuellement
f = open("donnees.csv", "r") # "r" = lecture (read)
# ... lire ...
f.close() # close() = fermer — si on oublie, le fichier reste verrouillé !
Mieux : utiliser le gestionnaire de contexte with (avec) — le fichier est automatiquement fermé à la sortie du bloc, même en cas d'erreur.
with open("donnees.csv", "r") as f:
# tout ce qui lit le fichier va ici
contenu = f.read() # read() = lire tout le fichier d'un coup
# f est automatiquement fermé ici
Bonne pratique : utilisez toujours
with open(...)— c'est la forme idiomatique et sûre.
Lire ligne par ligne — readline(), strip(), split()¶
Pour un CSV, on lit une ligne à la fois et on la découpe :
with open("donnees.csv", "r") as f:
for ligne in f: # itérer sur les lignes
ligne = ligne.strip() # strip() — supprime \n et espaces en bout
if ligne.startswith("#"): # startswith() — ignorer les commentaires
continue
morceaux = ligne.split(",") # split(",") — découpe sur la virgule
# morceaux est une liste de chaînes : ["0.002", "380.0"]
deformation = float(morceaux[0]) # float() — convertir chaîne en nombre
contrainte = float(morceaux[1])
| Méthode | Signification | Exemple |
|---|---|---|
strip() |
supprimer espace/\n en bout de chaîne |
"0.002\n" → "0.002" |
split(",") |
découper la chaîne sur la virgule | "0.002,380" → ["0.002", "380"] |
float() |
convertir une chaîne en décimal | "380.0" → 380.0 |
startswith() |
vérifier si la chaîne commence par… | "# commentaire" → True |
⚠️ Pièges fréquents lors de la lecture de fichiers¶
1. Oublier strip() avant split()
ligne = "0.002,380.0\n"
morceaux = ligne.split(",") # → ["0.002", "380.0\n"] ← \n dans le dernier !
float(morceaux[1]) # → ValueError (impossible de convertir "380.0\n")
# Correction :
morceaux = ligne.strip().split(",") # → ["0.002", "380.0"] ✓
2. Ne pas sauter l'en-tête
# La première ligne du CSV est : "deformation,contrainte"
# float("deformation") → ValueError !
# Correction : ignorer les lignes qui ne commencent pas par un chiffre
if ligne[0].isdigit():
# traiter la ligne
3. Ouvrir sans with et oublier close()
Le fichier reste verrouillé — impossible de le modifier ou le supprimer tant que Python tourne.
À vous de faire les exercices 3.1 et 3.2 !¶
4 · Premiers graphiques avec Matplotlib¶
Importer et tracer une première courbe¶
import matplotlib.pyplot as plt # convention universelle : alias plt
Structure minimale d'un graphique :
plt.figure(figsize=(8, 5)) # figure() — créer une figure ; figsize = taille en pouces
plt.plot(x, y) # plot() — tracer une courbe
plt.xlabel("...") # xlabel() — étiquette de l'axe x
plt.ylabel("...") # ylabel() — étiquette de l'axe y
plt.title("...") # title() — titre du graphique
plt.grid(True) # grid() — afficher la grille
plt.tight_layout() # ajuster les marges automatiquement
plt.show() # show() — afficher le graphique
plt.plot(x, y)relie les points dans l'ordre — c'est une courbe.
Sixn'est pas croissant, la courbe aura des zigzags inattendus.
Démonstration — courbe contrainte–déformation¶
# ── Courbe contrainte–déformation — caoutchouc naturel souple ────────────
import matplotlib.pyplot as plt
# Données — caoutchouc naturel souple
deformations = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.5, 1.8, 2.0, 2.5, 3.0, 3.5, 4.0]
contraintes = [0.000, 0.152, 0.267, 0.363, 0.447, 0.525, 0.598, 0.702,
0.802, 0.867, 1.025, 1.181, 1.335, 1.488] # MPa
plt.figure(figsize=(8, 5))
plt.plot(deformations, contraintes,
color='steelblue', # couleur de la courbe
linewidth=2, # épaisseur du trait
label='Caoutchouc naturel') # label = étiquette pour la légende
plt.xlabel("Déformation ε (sans unité)", fontsize=13)
plt.ylabel("Contrainte σ (MPa)", fontsize=13)
plt.title("Courbe contrainte–déformation — caoutchouc naturel souple", fontsize=14)
plt.legend(fontsize=12) # legend() — afficher la légende
plt.grid(True)
plt.tight_layout()
plt.show()
Paramètres de mise en forme¶
| Paramètre | Valeurs exemples | Effet |
|---|---|---|
color |
'steelblue', '#E07B39', 'k' |
couleur de la courbe |
linewidth |
1, 2, 3 |
épaisseur |
linestyle |
'-', '--', ':', '-.' |
style du trait |
marker |
'o', 's', '^', '+' |
marqueur sur chaque point |
label |
'Acier' |
texte dans la légende |
alpha |
0.0–1.0 |
transparence |
plt.plot(x, y, color='k', linestyle='--', marker='o', markersize=4, label='Essai 1')
plt.plot(x, z, color='steelblue', linestyle='-', label='Essai 2')
plt.legend()
Pour comparer plusieurs courbes, variez à la fois la couleur et le style de trait — les lecteurs daltoniensne verront peut-être pas la différence de couleur seule.
Nuage de points — plt.scatter()¶
plt.scatter(x, y) (nuage de points) affiche chaque paire (x, y) comme un point isolé, sans les relier.
Utile pour visualiser des données expérimentales sans supposer de tendance entre les points.
# Dureté vs résistance à la traction pour différents alliages
durete = [170, 210, 240, 275, 310, 350, 390]
Rm = [530, 650, 750, 860, 980, 1100, 1220]
plt.figure(figsize=(7, 5))
plt.scatter(durete, Rm,
color='tomato', # tomato = rouge-orangé
s=60, # s = taille des marqueurs
edgecolors='k', # edgecolors = couleur du contour
linewidths=0.8)
plt.xlabel("Dureté HB", fontsize=12)
plt.ylabel("Résistance Rm (MPa)", fontsize=12)
plt.title("Corrélation dureté–Rm", fontsize=13)
plt.grid(True)
plt.tight_layout()
plt.show()
Bonnes pratiques de visualisation¶
Un graphique publiable respecte ces règles :
- Axes étiquetés avec les unités —
"σ (MPa)", jamais juste"sigma" - Texte lisible —
fontsize=10(ou plus) minimum ; essayez d'avoir au moins la même taille que le texte du document - Légende si plusieurs courbes — avec
label=dansplot()etplt.legend() - Palette daltonisme-amicale — évitez les rouges et verts autant que possible
- Différencier les courbes par couleur et style de trait (
'-','--',':') — pas couleur seule - Titre informatif —
"Courbe σ–ε — acier 316L", si un titre est démandé plt.tight_layout()— évite les étiquettes coupées en export
# ── Nuage de points : dureté vs résistance ────────────────────────────────
import matplotlib.pyplot as plt
durete = [170, 210, 240, 275, 310, 350, 390]
Rm = [530, 650, 750, 860, 980, 1100, 1220]
noms = ['E24', 'E28', 'S355', 'S460', 'HLE 690', 'HLE 890', 'HLE 1100']
plt.figure(figsize=(7, 5))
plt.scatter(durete, Rm, color='tomato', s=70, edgecolors='k', linewidths=0.8, zorder=3)
# Étiqueter chaque point
for i, nom in enumerate(noms):
plt.annotate(nom, (durete[i], Rm[i]),
textcoords="offset points", xytext=(6, 4), fontsize=9)
plt.xlabel("Dureté HB", fontsize=12)
plt.ylabel("Résistance Rm (MPa)", fontsize=12)
plt.title("Corrélation dureté–Rm — aciers structuraux", fontsize=13)
plt.grid(True)
plt.tight_layout()
plt.show()
À vous de faire les exercices 4.1 et 4.2 !¶
Points clés de la séance¶
| Concept | Ce qu'il faut retenir |
|---|---|
Fonctions (def/return) |
Écrire une fois, appeler partout ; toujours inclure une docstring |
return manquant |
La fonction retourne silencieusement None — source de bugs |
raise ValueError |
Signaler explicitement une entrée invalide |
Découpage (liste[a:b:pas]) |
Extrait une sous-liste indépendante |
| Compréhension de liste | [expr for x in liste if cond] — concis pour transformer/filtrer |
with open(...) |
Fermeture automatique du fichier — toujours utiliser cette forme |
strip() → split() |
Supprimer \n avant de découper — ordre important |
plt.plot() / plt.scatter() |
Courbe reliée vs points isolés ; étiqueter axes avec unités |