Un correcteur orthographique maison, mesuré avec CodeCarbon. Même énergie en France et en Allemagne, mais 6,8 fois plus de CO2 en Allemagne. Puis un défi : faire tomber la consommation en optimisant l'algorithme, puis en changeant de technologie.
CodeCarbon estime l'énergie d'un programme (puissance du matériel, durée) et la multiplie par l'intensité carbone de l'électricité. En offline, l'intensité est la moyenne annuelle du pays ; en online, elle est instantanée (Electricity Maps) ; on peut aussi la forcer. La distance entre deux mots est la distance de Damerau-Levenshtein (une transposition compte 1). Un spellchecker naïf compare chaque mot inconnu à tout le dictionnaire (235 976 mots) : trop lent pour être mesuré. On l'améliore par des buckets de longueur et une DP plafonnée, puis par du C (rapidfuzz), seul à finir en 90 s.
01 · L'outil
CodeCarbon (version 3.3.1 requise : la 2.2.2 n'a ni detect ni --offline sur monitor) enveloppe un programme, estime la puissance du CPU, de la RAM et du GPU, la multiplie par la durée, puis par l'intensité carbone. codecarbon detect décrit la machine : ici un Apple M4 Pro, 12 threads, 24 Go.
codecarbon monitor --offline --country-iso-code FRA -- python3 wdistance.py
Puissance estimée par composant, durée, et en mode process la part d'utilisation du processus. Dans le lab : environ 4,6e-8 kWh pour wdistance.py.
Énergie x intensité carbone. C'est la colonne qu'on compare entre pays.
Une ligne par exécution : durée, puissances, énergie, pays, version, matériel. Base des graphiques du lab.
02 · L'intensité carbone
Dix exécutions en France, dix en Allemagne, avec le même programme. Bascule entre l'énergie et les émissions :
--offline --country-iso-code FRA : pas de réseau, intensité moyenne de l'année. FRA 56, DEU 381 g/kWh.
Sans --offline et avec une clé API : l'intensité au moment du run. Exige un experiment_id obtenu par codecarbon config. Mesuré : 22 g/kWh en France ce jour-là.
Une valeur imposée dans le fichier de config, sans clé ni compte. Le 6 janvier 2026 en France : 81 g/kWh. Vérifié : émissions / énergie = 81,0 exactement.
Hiver : forte demande de chauffage, recours au gaz et aux imports carbonés, peu de solaire. Le carbone d'un même kWh varie selon la saison et le jour.
Pour l'offline, le fichier ~/.codecarbon.config doit être [codecarbon] nu. Si le token Electricity Maps y traîne, CodeCarbon interroge l'intensité en direct de ta vraie position et ignore --country-iso-code : FRA et DEU renvoient alors la même valeur (22 g/kWh). Le token ne sert qu'aux exercices online.
Même énergie, quatre intensités : ce que ça change pour le spellchecker rapidfuzz du lab (0,251 Wh) ou pour un petit script.
Pour 0,251 Wh (0,000251 kWh) en France offline : 0,000251 x 56 = 14 mg, ce que le lab mesure (14 mgCO2eq).
03 · Le programme
Le lab demande le nombre minimal de modifications pour passer d'un mot à l'autre. Test imposé : hievr contre hiver doit valoir 1. Or c'est une transposition de deux lettres adjacentes (e et v), que Levenshtein simple compte pour 2. Il faut la variante Damerau-Levenshtein (OSA) où une transposition adjacente est une opération.
d[i][j] = min( d[i-1][j] + 1, // suppression d[i][j-1] + 1, // insertion d[i-1][j-1] + (a[i]!=b[j]) ) // substitution ou égalité si a[i]==b[j-1] et a[i-1]==b[j] : // transposition adjacente d[i][j] = min( d[i][j], d[i-2][j-2] + 1 )
Regarde la matrice se remplir ligne par ligne. Les cases marquées * sont celles où la transposition fait gagner un point. Choisis un des quatre tests du sujet, ou tape tes propres mots (9 lettres max) :
04 · Le spellchecker
Le programme charge words (235 976 mots) dans un set (test d'appartenance en O(1)), lit Tom Sawyer, nettoie chaque mot et, s'il n'est pas dans le dictionnaire, cherche jusqu'à 10 remplacements à distance 1 et 10 à distance 2. Sur le livre : 3136 mots inconnus (dialecte, en-tête Gutenberg). Chacun déclenche un scan du dictionnaire.
Chaque mot inconnu contre les 235 976 mots. Au plus 740 millions de calculs de distance. Non terminé après plus de 10 minutes.
Buckets par longueur et DP plafonnée à 2. Exact à 100 %. Toujours plus de 5 minutes en Python pur.
rapidfuzz (distance en C) avec score_cutoff=2. 90 s, 0,251 Wh, 14 mgCO2eq. Seul à finir vite.
Une distance au plus 2 impose un écart de longueur au plus 2 : pour un mot de L lettres, seuls les mots de longueur L-2 à L+2 sont candidats. Voici l'effet, calculé sur le vrai fichier words :
Pour les longueurs courantes (5 à 12 lettres), la fenêtre garde encore 25 à 57 % du dictionnaire : le bucket seul ne gagne qu'un facteur 2 à 4. C'est la DP plafonnée (abandon dès qu'une ligne dépasse 2) et surtout le C qui changent l'ordre de grandeur.
def suggest(word, by_len): d1, d2 = [], [] for length in range(len(word) - 2, len(word) + 3): for cand in by_len.get(length, ()): dist = DamerauLevenshtein.distance(word, cand, score_cutoff=2) # rapidfuzz, en C if dist == 1 and len(d1) < 10: d1.append(cand) elif dist == 2 and len(d2) < 10: d2.append(cand) return d1, d2
C'est exactement la démarche du Lab 1 : éviter du travail (buckets, plafond), puis changer la technologie (C). L'ordre attendu d'énergie est naïf, puis Python optimisé, puis rapidfuzz.
05 · Le score
Le lab note un spellchecker sur deux axes : l'énergie moyenne consommée (Wh) et la précision.
score = 0,1 / ( avenergy x ( 1 + somme |distance_optimale - distance_trouvée| ) )
Un spellchecker exact (OSA) a une somme d'écarts nulle : seul compte l'énergie. Avec rapidfuzz (0,251 Wh) : 0,1 / 0,251 = 0,40. Joue avec les deux termes :
Diviser l'énergie par deux double le score. Une seule erreur de distance (somme = 1) le divise par deux : un algorithme approximatif doit être deux fois plus sobre pour compenser une seule erreur. C'est pourquoi le challenge 1 exige un résultat 100 % exact.
06 · Questions du lab corrigées
Damerau-Levenshtein, tests imposés.
def wdistance(a, b): m, n = len(a), len(b) d = [[0] * (n + 1) for _ in range(m + 1)] for i in range(m + 1): d[i][0] = i for j in range(n + 1): d[0][j] = j for i in range(1, m + 1): for j in range(1, n + 1): cost = 0 if a[i-1] == b[j-1] else 1 d[i][j] = min(d[i-1][j] + 1, d[i][j-1] + 1, d[i-1][j-1] + cost) if i > 1 and j > 1 and a[i-1] == b[j-2] and a[i-2] == b[j-1]: d[i][j] = min(d[i][j], d[i-2][j-2] + 1) # transposition return d[m][n]
| Paire | Distance | Pourquoi |
|---|---|---|
| hievr / hiver | 1 | une transposition (e et v) |
| hiv / hiver | 2 | insérer e et r |
| ver / hiver | 2 | insérer h et i |
| vre / hiver | 3 | insérer h, i puis une transposition (r et e) |
On teste aussi les mots échangés : la distance est symétrique (les insertions d'un sens sont les suppressions de l'autre).
Levenshtein simple donne 2 pour hievr / hiver et fait échouer le test. C'est la variante OSA (une transposition adjacente = 1) qui est demandée.
pip, fichier de configuration, detect.
pip install --user --break-system-packages "codecarbon[carbonboard]" rapidfuzz echo "[codecarbon]" > ~/.codecarbon.config codecarbon detect
Sur un Python système géré (PEP 668), --break-system-packages est nécessaire (ou un environnement virtuel). Il faut codecarbon 3.3.1 : la 2.2.2 livrée avec Anaconda n'a ni detect ni --offline sur monitor. detect affiche le matériel : Apple M4 Pro, 12 threads, 24 Go.
Dix runs chacun, comparer énergie et émissions.
for i in $(seq 10); do codecarbon monitor --offline --country-iso-code FRA -- python3 wdistance.py; done for i in $(seq 10); do codecarbon monitor --offline --country-iso-code DEU -- python3 wdistance.py; done
| Pays | energy_consumed | emissions | intensité implicite |
|---|---|---|---|
| France | 4,6e-8 kWh | 2,6e-9 kgCO2eq | 56 g/kWh |
| Allemagne | 4,5e-8 kWh | 1,7e-8 kgCO2eq | 381 g/kWh |
Identique dans les deux pays : elle dépend du matériel et du code, pas de la localisation.
Environ 6,7 fois plus en Allemagne (le rapport des intensités : 381 / 56 = 6,8). Le mix allemand est plus carboné (charbon, gaz) que le français (nucléaire). Même énergie, plus de CO2.
Note : wdistance.py ne dure que des microsecondes, l'énergie est minuscule. La charge utile à mesurer est le spellchecker.
Naïf, format de sortie, mesure.
Un set pour le dictionnaire : le test « le mot existe-t-il ? » est fait pour chaque mot du livre, donc il doit être en O(1).
def clean(token): kept = [c for c in token.lower() if c.isalpha() or c == "'"] return "".join(kept).strip("'") # "I'd" et "You'll" gardés, "'abc'" devient "abc"
"abc" does not exist. Did you mean ['...distance 1...'] ['...distance 2...']
for i in $(seq 5); do codecarbon monitor --offline --country-iso-code FRA -- python3 spellchecker.py; done
La version naïve n'a pas terminé après plus de 10 minutes (3136 mots inconnus x 235 976 mots) : point de départ à optimiser, non mesurable telle quelle.
Intensité instantanée, et pourquoi elle est plus basse.
credentials.json et la clé Electricity Maps sont des secrets : ne jamais les committer ni les coller dans un compte rendu.
Émission plus haute, égale ou plus basse que sans Electricity Maps ? Ici plus basse : l'API renvoie 22 g/kWh pour la France ce jour-là, contre 56 g/kWh de moyenne annuelle en offline. Beaucoup de nucléaire et de renouvelable à cet instant. Le résultat peut aussi être plus haut un autre jour (exercice 7).
Les étapes de connexion sont interactives (invites et navigateur) : elles se font à la main dans un terminal. Le jeton Electricity Maps a été validé auprès de l'API (GET /v3/carbon-intensity/latest?zone=FR, en-tête auth-token).
Le 6 janvier 2026, intensité forcée.
Developer Hub d'Electricity Maps, France, temporalité past, granularité daily, ou l'API carbon-intensity/past?zone=FR&datetime=2026-01-06T12:00:00Z : 81 g/kWh.
[codecarbon] force_carbon_intensity_g_co2e_kwh = 81
Après les runs, émissions / énergie = 81,0 g/kWh exactement : l'intensité imposée est bien appliquée.
Hiver et froid : forte demande de chauffage électrique, recours au gaz et aux importations carbonées pour couvrir la pointe, et peu de solaire. L'intensité d'un kWh n'est pas une constante du pays : elle varie avec la saison, le jour et l'heure.
Précision et énergie.
Score = 0,1 / (avenergy x (1 + somme |distance optimale - distance trouvée|)), avec avenergy l'énergie moyenne en Wh de tous les runs (colonne energy_consumed x 1000).
Le spellchecker renvoie la distance exacte donc la somme des écarts vaut 0. Avec la version rapidfuzz mesurée (0,251 Wh) : score = 0,1 / 0,251 = 0,40. À faire valider par l'encadrant.
Algorithme puis technologie, sans perdre en précision.
Précision 100 % conservée, deux optimisations : buckets par longueur (distance <= 2 implique un écart de longueur <= 2, donc on ne compare qu'aux mots de longueur L-2 à L+2) et DP plafonnée (dès qu'une ligne de la matrice dépasse le seuil 2, on abandonne car la distance ne pourra plus redescendre). Vérifié identique au naïf sur des mots de test.
Même structure en buckets, mais la distance vient de rapidfuzz (DamerauLevenshtein.distance(..., score_cutoff=2), en C). Résultats identiques au Python pur, bien moins d'énergie CPU par appel.
| Version | Durée | Énergie | CO2 (FRA) |
|---|---|---|---|
| Naïf | plus de 10 min, non terminé | non mesuré | non mesuré |
| Challenge 1 (Python) | plus de 5 min | non mesuré | non mesuré |
| Challenge 2 (rapidfuzz) | 90 s | 0,251 Wh | 14 mgCO2eq |
Seule la version rapidfuzz a été mesurée jusqu'au bout. Les deux autres sont trop lentes en Python pur pour une campagne complète : on ne les compare donc qu'en ordre attendu (naïf plus coûteux que fast, plus coûteux que rapidfuzz), pas en chiffres.
07 · Réviser
Le QCM tire un échantillon au hasard dans le pool ; chaque réponse est corrigée et expliquée.
Entraîneur Pour un QCM qui s'adapte à ton niveau, revient sur tes erreurs et explique chaque réponse avec une fiche illustrée : s'entraîner sur ce chapitre.
Pool de 22 questions. Tire un échantillon au hasard, correction immédiate et explication à chaque réponse.