Manipuler les unités physiques avec pint
Ce notebook présente les fonctionnalités essentielles du paquet pint pour la manipulation des unités physiques, avec un accent sur les grandeurs courantes en hydrologie :
Longueurs (m, mm, km)
Surfaces (m², ha, km²)
Volumes (m³, L)
Débits (m³/s, L/s, L/s/ha)
Intensités de pluie (mm/h)
Vitesses (m/s, mm/h — conductivité hydraulique)
Sommaire
Le registre d’unités (``UnitRegistry`) <#1.-Le-registre-d’unités>`__
Créer des grandeurs (``Quantity`) <#2.-Créer-des-grandeurs>`__
1. Le registre d’unités
Toutes les unités sont gérées par un registre (UnitRegistry). Il est important d’utiliser un seul registre dans tout le programme, sinon les grandeurs issues de registres différents sont incompatibles.
Dans wolfhece, le registre partagé est UNITS :
[1]:
import pint
# Création d'un registre — en pratique, wolfhece en expose
# un unique dans son __init__ :
# from wolfhece import UNITS
U = pint.UnitRegistry()
# Exemples d'unités disponibles
print(U.meter)
print(U.hectare)
print(U.liter)
print(U.second)
print(U.hour)
meter
hectare
liter
second
hour
2. Créer des grandeurs
Une grandeur (Quantity) est un couple (valeur, unité). On la crée par multiplication d’un nombre par une unité :
[2]:
# Longueurs
longueur = 150 * U.meter
print(f"Longueur : {longueur}")
# Surfaces
surface = 0.5 * U.hectare
print(f"Surface : {surface}")
# Volumes
volume = 42.5 * U.meter**3
print(f"Volume : {volume}")
# Débits
debit = 3.2 * U.liter / U.second
print(f"Débit : {debit}")
# Intensité de pluie
intensite = 25.0 * U.mm / U.hour
print(f"Pluie : {intensite}")
Longueur : 150 meter
Surface : 0.5 hectare
Volume : 42.5 meter ** 3
Débit : 3.2 liter / second
Pluie : 25.0 millimeter / hour
Les unités composées se construisent avec *, / et ** :
[3]:
# Conductivité hydraulique de Darcy
K = 1e-5 * U.meter / U.second
print(f"K = {K}")
# Débit spécifique (L/s par hectare)
q_spec = 5.0 * U.liter / U.second / U.hectare
print(f"q = {q_spec}")
K = 1e-05 meter / second
q = 5.0 liter / hectare / second
3. Conversions
La méthode .to() convertit vers une autre unité compatible :
[4]:
# Surface : hectare ↔ m²
s = 2.5 * U.hectare
print(f"{s} = {s.to(U.meter**2)} = {s.to('km^2')}")
# Débit : L/s ↔ m³/s ↔ m³/h
q = 12.0 * U.liter / U.second
print(f"{q} = {q.to('m^3/s')} = {q.to('m^3/hour')}")
# Conductivité hydraulique : m/s ↔ mm/h
K = 1e-5 * U.meter / U.second
print(f"{K:.2e} = {K.to('mm/hour'):.2f}")
# Intensité de pluie : mm/h ↔ m/s
i = 50 * U.mm / U.hour
print(f"{i} = {i.to('m/s'):.4e}")
2.5 hectare = 25000.0 meter ** 2 = 0.025 kilometer ** 2
12.0 liter / second = 0.012000000000000004 meter ** 3 / second = 43.20000000000001 meter ** 3 / hour
1.00e-05 meter / second = 36.00 millimeter / hour
50.0 millimeter / hour = 1.3889e-05 meter / second
Extraire la valeur numérique
L’attribut .magnitude donne la valeur brute (sans unité) :
[5]:
q = 12.0 * U.liter / U.second
print(f"Magnitude en L/s : {q.magnitude}")
print(f"Magnitude en m³/s : {q.to('m^3/s').magnitude}")
print(f"Unité : {q.units}")
Magnitude en L/s : 12.0
Magnitude en m³/s : 0.012000000000000004
Unité : liter / second
4. Opérations arithmétiques
pint gère automatiquement les unités dans les calculs :
[6]:
# Somme de surfaces (unités différentes mais compatibles)
s1 = 800 * U.meter**2
s2 = 0.15 * U.hectare
s_tot = s1 + s2
print(f"{s1} + {s2} = {s_tot.to('m^2')}")
# Surface × intensité de pluie → débit
A = 1.0 * U.hectare
i = 50 * U.mm / U.hour
Q = (A * i).to('liter/second')
print(f"\nSurface × intensité : {A} × {i} = {Q:.1f}")
# Débit × temps → volume
Q = 5 * U.liter / U.second
t = 2 * U.hour
V = (Q * t).to('m^3')
print(f"\nDébit × temps : {Q} × {t} = {V}")
800 meter ** 2 + 0.15 hectare = 2300.0 meter ** 2
Surface × intensité : 1.0 hectare × 50.0 millimeter / hour = 138.9 liter / second
Débit × temps : 5.0 liter / second × 2 hour = 36.00000000000001 meter ** 3
Multiplication par un coefficient sans dimension
Un nombre pur (coefficient de ruissellement, facteur de sécurité, …) n’a pas d’unité et s’utilise naturellement :
[7]:
C = 0.9 # coefficient de ruissellement (sans dimension)
A = 800 * U.meter**2
i = 42.0 * U.mm / U.hour
Q_net = (C * A * i).to('liter/second')
print(f"Débit net = C × A × i = {C} × {A} × {i} = {Q_net:.2f}")
Débit net = C × A × i = 0.9 × 800 meter ** 2 × 42.0 millimeter / hour = 8.40 liter / second
5. Comparaisons
Les comparaisons entre grandeurs de même dimension fonctionnent directement, même si les unités diffèrent :
[8]:
s1 = 5000 * U.meter**2
s2 = 0.5 * U.hectare
print(f"{s1} == {s2} ? {s1 == s2}")
print(f"{s1} > {s2} ? {s1 > s2}")
s3 = 0.6 * U.hectare
print(f"{s1} < {s3} ? {s1 < s3}")
5000 meter ** 2 == 0.5 hectare ? True
5000 meter ** 2 > 0.5 hectare ? False
5000 meter ** 2 < 0.6 hectare ? True
6. Formatage de l’affichage
On peut formater les grandeurs avec les spécificateurs habituels :
[9]:
V = 42.567 * U.meter**3
K = 1e-5 * U.meter / U.second
print(f"Volume : {V:.1f}") # 1 décimale
print(f"Volume : {V:.0f}") # pas de décimale
print(f"Darcy : {K:.2e}") # notation scientifique
# Convertir avant d'afficher
print(f"Darcy : {K.to('mm/hour'):.2f}")
Volume : 42.6 meter ** 3
Volume : 43 meter ** 3
Darcy : 1.00e-05 meter / second
Darcy : 36.00 millimeter / hour
Affichage compact (~)
Le format ~ utilise les abréviations des unités :
[10]:
q = 12.0 * U.liter / U.second
print(f"Normal : {q}") # liter / second
print(f"Compact: {q:~}") # l / s
print(f"Compact: {q:~.1f}") # l / s avec 1 décimale
Normal : 12.0 liter / second
Compact: 12.0 l / s
Compact: 12.0 l / s
7. Application hydrologique
Bilan simplifié sur un bassin versant
Calculons le volume ruisselé pour une pluie de projet sur un bassin composé de plusieurs types d’occupation du sol :
[11]:
# Sous-bassins : (nom, coefficient de ruissellement, superficie)
sous_bassins = [
("Parking", 0.9, 800 * U.meter**2),
("Toitures", 1.0, 350 * U.meter**2),
("Trottoirs", 0.7, 200 * U.meter**2),
("Jardin", 0.15, 0.15 * U.hectare),
("Forêt", 0.05, 0.2 * U.hectare),
]
# Pluie de projet
intensite = 42.0 * U.mm / U.hour
duree = 1.5 * U.hour
print(f"{'Sous-bassin':<12} {'C':>4} {'Surface':>12} {'Q_net':>14} {'V_ruisselé':>12}")
print("-" * 60)
V_total = 0 * U.meter**3
for nom, C, A in sous_bassins:
Q = (C * A * intensite).to('liter/second')
V = (Q * duree).to('m^3')
V_total += V
print(f"{nom:<12} {C:>4.2f} {A.to('m^2'):>12.0f} {Q:>14.2f} {V:>12.2f}")
print("-" * 60)
print(f"{'TOTAL':>32} {V_total:>28.2f}")
Sous-bassin C Surface Q_net V_ruisselé
------------------------------------------------------------
Parking 0.90 800 meter ** 2 8.40 liter / second 45.36 meter ** 3
Toitures 1.00 350 meter ** 2 4.08 liter / second 22.05 meter ** 3
Trottoirs 0.70 200 meter ** 2 1.63 liter / second 8.82 meter ** 3
Jardin 0.15 1500 meter ** 2 2.62 liter / second 14.18 meter ** 3
Forêt 0.05 2000 meter ** 2 1.17 liter / second 6.30 meter ** 3
------------------------------------------------------------
TOTAL 96.71 meter ** 3
Débit de fuite (infiltration + réseau)
Calcul du débit évacué par infiltration (loi de Darcy) et par le réseau (débit spécifique admissible) :
[12]:
# Paramètres d'infiltration
A_infil = 150 * U.meter**2
K_darcy = 1e-5 * U.meter / U.second
facteur_secu = 2.0
Q_infil = (A_infil * K_darcy / facteur_secu).to('liter/second')
print(f"Débit d'infiltration : {Q_infil:.4f}")
# Débit admissible vers le réseau
q_adm = 5.0 * U.liter / U.second / U.hectare
A_tot = sum(A for __, __, A in sous_bassins).to('hectare')
Q_reseau = (q_adm * A_tot).to('liter/second')
print(f"Débit réseau : {Q_reseau:.4f}")
Q_fuite = Q_infil + Q_reseau
print(f"Débit de fuite total : {Q_fuite:.4f}")
# Temps de vidange d'un bassin de 40 m³
V_bassin = 40 * U.meter**3
t_vidange = (V_bassin / Q_fuite).to('hour')
print(f"\nTemps de vidange de {V_bassin} : {t_vidange:.1f}")
Débit d'infiltration : 0.7500 liter / second
Débit réseau : 2.4250 liter / second
Débit de fuite total : 3.1750 liter / second
Temps de vidange de 40 meter ** 3 : 3.5 hour
8. Pièges courants
Incompatibilité dimensionnelle
Additionner des grandeurs de dimensions différentes lève une erreur :
[13]:
try:
resultat = 10 * U.meter + 5 * U.second
except pint.DimensionalityError as e:
print(f"Erreur attendue : {e}")
Erreur attendue : Cannot convert from 'meter' ([length]) to 'second' ([time])
Registres différents
Des grandeurs créées à partir de registres différents sont incompatibles, même si les unités ont le même nom :
[14]:
U1 = pint.UnitRegistry()
U2 = pint.UnitRegistry()
a = 10 * U1.meter
b = 5 * U2.meter
try:
c = a + b
except Exception as e:
print(f"Erreur : {type(e).__name__}")
print("→ Toujours utiliser le même registre (UNITS dans wolfhece) !")
Erreur : ValueError
→ Toujours utiliser le même registre (UNITS dans wolfhece) !
Passage à NumPy
Les Quantity fonctionnent avec les tableaux NumPy. Attention toutefois à extraire .magnitude avant de passer les valeurs à des fonctions qui n’acceptent pas les unités (matplotlib, fichiers, …) :
[15]:
import numpy as np
debits = np.array([1.2, 3.5, 5.0, 2.1]) * U.liter / U.second
print(f"Somme : {debits.sum()}")
print(f"Moyenne : {debits.mean()}")
print(f"Max : {debits.max()}")
# Conversion en m³/s et extraction pour matplotlib
debits_m3s = debits.to('m^3/s').magnitude
print(f"\nTableau brut (m³/s) : {debits_m3s}")
Somme : 11.799999999999999 liter / second
Moyenne : 2.9499999999999997 liter / second
Max : 5.0 liter / second
Tableau brut (m³/s) : [0.0012 0.0035 0.005 0.0021]
Chaîne de caractères comme unité
.to() accepte soit un objet unité, soit une chaîne :
[16]:
s = 5000 * U.meter**2
# Objet unité
print(s.to(U.hectare))
# Chaîne de caractères (syntaxe pint)
print(s.to('hectare'))
print(s.to('km^2'))
0.5 hectare
0.5 hectare
0.005 kilometer ** 2