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

  1. Le registre d’unités (``UnitRegistry`) <#1.-Le-registre-d’unités>`__

  2. Créer des grandeurs (``Quantity`) <#2.-Créer-des-grandeurs>`__

  3. Conversions

  4. Opérations arithmétiques

  5. Comparaisons

  6. Formatage de l’affichage

  7. Application hydrologique : bilan sur un bassin versant

  8. Pièges courants

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