Comment déterminer l’aire d’un étang dont la forme n’a rien de régulier ? Lorsqu’un contour est trop complexe pour être décrit par une formule simple, une méthode efficace consiste à utiliser… le hasard.
Cette approche, appelée méthode de Monte-Carlo, permet d’estimer une surface en réalisant des tirages aléatoires dans une zone connue.
Dans cette simulation, l’étang se trouve dans un champ rectangulaire de 60 mètres sur 35 mètres, soit 2100 m². L’étang lui-même est formé par la réunion de quatre ellipses, ce qui lui donne une forme irrégulière mais permet de tester facilement si un point tombe dans l’eau.
Principe de l’estimation
Le programme réalise des tirs aléatoires dans tout le champ.
Chaque tir tombe soit dans l’eau, soit dans l’herbe.
En comptant le nombre de tirs dans l’eau, on obtient une estimation de l’aire de l’étang.
Voici les calculs utilisés :
Aire totale du champ :
- 2100 m²
Estimation de l’aire de l’étang :
aire estimée = 2100 × (tirs dans l’eau / tirs totaux)
Pourcentage de tirs dans l’eau :
- pourcentage = (tirs dans l’eau × 100) / tirs totaux
Écart avec l’aire exacte :
- écart = |aire estimée - aire exacte|
- pourcentage d’erreur = (écart × 100) / aire exacte
Plus le nombre de tirs augmente, plus l’estimation se rapproche de la valeur réelle.
Un étang généré aléatoirement
Les quatre ellipses qui composent l’étang sont générées avec des valeurs aléatoires pour leurs centres et leurs rayons.
Par exemple :
- « ecx[0] vaut hasard(18, 26) »
- « erx[1] vaut hasard(8, 12) »
Chaque nouvelle simulation produit donc un étang différent.
Calcul de l’aire exacte
Pour disposer d’une référence, le programme calcule l’aire exacte en balayant tout le champ avec un pas de 0,5 mètre.
Chaque point du quadrillage est testé pour savoir s’il se trouve dans l’une des ellipses.
Ce calcul est long mais fournit une valeur précise, utilisée pour comparer la qualité de l’estimation.
Visualisation et convergence
La simulation affiche :
- le champ et l’étang,
- les impacts des tirs (jaune = eau, rouge = herbe),
- le comptage en temps réel,
- l’estimation actuelle,
- l’écart avec l’aire exacte,
une courbe de convergence montrant comment l’estimation se stabilise au fil des tirs.
Les boutons permettent d’ajouter 1, 10, 100 ou 1000 tirs, de réinitialiser le comptage ou de générer un nouvel étang.
Une méthode simple pour un problème complexe
Cette simulation montre comment une méthode probabiliste peut résoudre un problème géométrique difficile.
Avec quelques dizaines de tirs, l’estimation reste approximative.
Avec plusieurs centaines, elle devient fiable.
Avec plusieurs milliers, elle se stabilise autour de l’aire réelle.
La méthode de Monte-Carlo devient ici visuelle, intuitive et accessible.
Programme :
// ===========================================================
// MESURER UNE AIRE À PARTIR DE TIRS ALÉATOIRES
//
// Un étang aux contours irréguliers occupe une partie d'un
// champ rectangulaire de 60 m sur 35 m, soit 2100 m².
// On tire au hasard sur tout le rectangle. Chaque tir tombe
// dans l'eau ou dans l'herbe.
//
// aire de l'étang ˜ 2100 x (tirs dans l'eau / tirs totaux)
//
// Plus on tire, plus l'estimation se rapproche de la vérité.
// ===========================================================
@ CONFIGURATION
vue est une toile
dimension(vue, 700, 520)
LARGM est un nombre
HAUTM est un nombre
LARGM vaut 60
HAUTM vaut 35
AIRETOT est un nombre
AIRETOT vaut 2100
// Le champ à l'écran : 8 pixels pour 1 mètre
ECH est un nombre
CHAMPX est un nombre
CHAMPY est un nombre
ECH vaut 8
CHAMPX vaut 22
CHAMPY vaut 92
MAXPTS est un nombre
MAXPTS vaut 1500
nbTirs est un nombre
nbEau est un nombre
nbTirs vaut 0
nbEau vaut 0
estim est un nombre
estim vaut 0
aireVraie est un nombre
aireVraie vaut 0
message est un texte
message vaut "Tire quelques obus au hasard sur le champ"
@ L'ÉTANG
// Il est décrit par la réunion de quatre ellipses : le contour
// paraît irrégulier, et le test « suis-je dans l'eau ? » reste
// une simple inégalité.
ecx est un tableau
ecy est un tableau
erx est un tableau
ery est un tableau
pour e de 0 à 3
ecx ajoute 0
ecy ajoute 0
erx ajoute 0
ery ajoute 0
fin pour
@ LES TIRS MÉMORISÉS
tirX est un tableau
tirY est un tableau
tirEau est un tableau
pour k de 0 à MAXPTS
tirX ajoute 0
tirY ajoute 0
tirEau ajoute faux
fin pour
nbPts est un nombre
nbPts vaut 0
@ HISTORIQUE DES ESTIMATIONS
histo est un tableau
histoN est un tableau
pour k de 0 à 79
histo ajoute 0
histoN ajoute 0
fin pour
nbHisto est un nombre
nbHisto vaut 0
nouvelEtang est un booléen
nouvelEtang vaut vrai
@ UN BOUTON
procédure bouton(t, x, y, l, txt, coul)
rectangle_arrondi(t, x, y + 2, l, 34, 8, rgba(15, 23, 42, 0.2))
rectangle_arrondi(t, x, y, l, 34, 8, coul)
label(t, x + 10, y + 23, txt, #FFFFFF, 13)
fin procédure
@ BOUCLE PRINCIPALE
tant que vrai
// =========================================================
// CRÉATION DE L'ÉTANG ET CALCUL DE SON AIRE EXACTE
// L'aire vraie est obtenue en balayant tout le champ avec
// un pas fin. C'est long, mais c'est la référence qui
// permet de juger la qualité de l'estimation.
// =========================================================
si nouvelEtang = vrai alors
nouvelEtang vaut faux
ecx[0] vaut hasard(18, 26)
ecy[0] vaut hasard(14, 20)
erx[0] vaut hasard(11, 15)
ery[0] vaut hasard(7, 10)
ecx[1] vaut hasard(30, 38)
ecy[1] vaut hasard(10, 16)
erx[1] vaut hasard(8, 12)
ery[1] vaut hasard(5, 8)
ecx[2] vaut hasard(26, 34)
ecy[2] vaut hasard(20, 26)
erx[2] vaut hasard(7, 11)
ery[2] vaut hasard(4, 7)
ecx[3] vaut hasard(10, 16)
ecy[3] vaut hasard(18, 24)
erx[3] vaut hasard(5, 9)
ery[3] vaut hasard(4, 6)
// Balayage fin : un point tous les 0,5 mètre
dedansTot est un nombre
totalPts est un nombre
dedansTot vaut 0
totalPts vaut 0
// On avance par demi-mètres, en comptant en demi-mètres entiers :
// aucun nombre à virgule n'apparaît dans le programme.
pour ix de 0 à LARGM * 2 - 1
pour iy de 0 à HAUTM * 2 - 1
px est un nombre
py est un nombre
px vaut ix / 2
py vaut iy / 2
totalPts ajoute 1
dedans est un booléen
dedans vaut faux
pour e de 0 à 3
ddx est un nombre
ddy est un nombre
ddx vaut (px - ecx[e]) / erx[e]
ddy vaut (py - ecy[e]) / ery[e]
si ddx * ddx + ddy * ddy <= 1 alors
dedans vaut vrai
fin si
fin pour
si dedans = vrai alors
dedansTot ajoute 1
fin si
fin pour
fin pour
aireVraie vaut arrondi(AIRETOT * dedansTot / totalPts)
nbTirs vaut 0
nbEau vaut 0
nbPts vaut 0
nbHisto vaut 0
estim vaut 0
message vaut "Nouvel étang : son aire exacte est de " + aireVraie + " m²"
fin si
// =========================================================
// AFFICHAGE
// =========================================================
effacer(vue)
contour(vue, #0)
remplir(vue, #EEF2F7)
rectangle_arrondi(vue, 12, 10, 676, 70, 14, #FFFFFF)
label(vue, 26, 38, "MESURER UNE AIRE AVEC DES TIRS AU HASARD", #B45309, 18)
label(vue, 26, 60, "Champ de " + LARGM + " m sur " + HAUTM + " m, soit " + AIRETOT + " m². On tire, on compte, on estime.", #64748B, 12)
// ---------- LE CHAMP ET L'ÉTANG ----------
rectangle_arrondi(vue, 12, 84, 500, 308, 14, #FFFFFF)
rectangle(vue, CHAMPX, CHAMPY, LARGM * ECH, HAUTM * ECH, #86C46A)
pour e de 0 à 3
ellipse(vue, CHAMPX + ecx[e] * ECH, CHAMPY + ecy[e] * ECH, erx[e] * ECH, ery[e] * ECH, #2D7FD4)
fin pour
contour(vue, #334155, 2)
rectangle(vue, CHAMPX, CHAMPY, LARGM * ECH, HAUTM * ECH, rgba(0, 0, 0, 0))
contour(vue, #0)
// Les impacts
pour k de 0 à nbPts - 1
si tirEau[k] = vrai alors
cercle(vue, tirX[k], tirY[k], 2, #FDE68A)
sinon
cercle(vue, tirX[k], tirY[k], 2, #B91C1C)
fin si
fin pour
label(vue, 24, 386, "jaune : dans l'eau - rouge : dans l'herbe", #94A3B8, 10)
// ---------- LE COMPTAGE ----------
rectangle_arrondi(vue, 520, 84, 168, 308, 14, #FFFFFF)
label(vue, 536, 114, "COMPTAGE", #0F172A, 14)
label(vue, 536, 144, "tirs : " + nbTirs, #334155, 13)
label(vue, 536, 168, "dans l'eau : " + nbEau, #2563EB, 13)
si nbTirs > 0 alors
pct est un nombre
pct vaut arrondi(nbEau * 1000 / nbTirs) / 10
label(vue, 536, 192, "soit " + pct + " %", #64748B, 12)
fin si
label(vue, 536, 232, "ESTIMATION", #0F172A, 14)
label(vue, 536, 256, "" + AIRETOT + " x " + nbEau, #94A3B8, 11)
rectangle(vue, 536, 264, 96, 1, #94A3B8)
si nbTirs > 0 alors
label(vue, 578, 280, "" + nbTirs, #94A3B8, 11)
sinon
label(vue, 578, 280, "0", #94A3B8, 11)
fin si
label(vue, 536, 314, "" + estim + " m²", #16A34A, 20)
label(vue, 536, 346, "aire exacte", #94A3B8, 11)
label(vue, 536, 366, "" + aireVraie + " m²", #334155, 14)
// ---------- BOUTONS ET COURBE ----------
rectangle_arrondi(vue, 12, 394, 676, 114, 14, #FFFFFF)
appelle bouton(vue, 24, 404, 62, "+ 1", #2563EB)
appelle bouton(vue, 94, 404, 62, "+ 10", #2563EB)
appelle bouton(vue, 164, 404, 70, "+ 100", #2563EB)
appelle bouton(vue, 242, 404, 78, "+ 1000", #2563EB)
appelle bouton(vue, 328, 404, 74, "Effacer", #64748B)
appelle bouton(vue, 410, 404, 84, "Autre étang", #7C3AED)
label(vue, 24, 470, message, #64748B, 11)
si nbTirs > 0 alors
ecart est un nombre
ecart vaut absolue(estim - aireVraie)
pctE est un nombre
pctE vaut arrondi(ecart * 1000 / aireVraie) / 10
label(vue, 24, 494, "écart avec l'aire exacte : " + ecart + " m², soit " + pctE + " %", #B45309, 11)
fin si
// Courbe de convergence
rectangle(vue, 510, 400, 168, 100, #F8FAFC)
label(vue, 512, 412, "convergence", #94A3B8, 9)
// la ligne de l'aire exacte, au milieu du cadre
rectangle(vue, 512, 452, 164, 1, #16A34A)
// Échelle verticale : un écart de 300 m² occupe toute la demi-hauteur.
// Sans cela, une erreur de 60 m² se traduirait par trois pixels.
pour k de 0 à nbHisto - 2
x1 est un nombre
x2 est un nombre
y1 est un nombre
y2 est un nombre
x1 vaut 512 + arrondi(k * 164 / 79)
x2 vaut 512 + arrondi((k + 1) * 164 / 79)
y1 vaut 452 - arrondi((histo[k] - aireVraie) * 46 / 300)
y2 vaut 452 - arrondi((histo[k + 1] - aireVraie) * 46 / 300)
y1 vaut limiter(y1, 404, 498)
y2 vaut limiter(y2, 404, 498)
p est un tableau
p ajoute x1
p ajoute y1 - 1
p ajoute x2
p ajoute y2 - 1
p ajoute x2
p ajoute y2 + 1
p ajoute x1
p ajoute y1 + 1
polygone(vue, p, #DC2626)
fin pour
// Chaque estimation est aussi marquée d'un point : la courbe existe
// dès le premier tir, avant même d'avoir un segment à tracer.
pour k de 0 à nbHisto - 1
xp est un nombre
yp est un nombre
xp vaut 512 + arrondi(k * 164 / 79)
yp vaut 452 - arrondi((histo[k] - aireVraie) * 46 / 300)
yp vaut limiter(yp, 404, 498)
cercle(vue, xp, yp, 2, #DC2626)
fin pour
label(vue, 512, 500, "vert : aire exacte", #94A3B8, 9)
affiche vue
// =========================================================
// LA SOURIS
// =========================================================
xc est un nombre
yc est un nombre
cliquer vue dans xc, yc
combien est un nombre
combien vaut 0
si yc >= 404 et yc <= 438 alors
si xc >= 24 et xc <= 86 alors
combien vaut 1
sinon si xc >= 94 et xc <= 156 alors
combien vaut 10
sinon si xc >= 164 et xc <= 234 alors
combien vaut 100
sinon si xc >= 242 et xc <= 320 alors
combien vaut 1000
sinon si xc >= 328 et xc <= 402 alors
nbTirs vaut 0
nbEau vaut 0
nbPts vaut 0
nbHisto vaut 0
estim vaut 0
message vaut "Comptage remis à zéro"
sinon si xc >= 410 et xc <= 494 alors
nouvelEtang vaut vrai
fin si
fin si
// =========================================================
// LES TIRS
// Un tir au hasard sur tout le rectangle, puis une seule
// question : ce point est-il dans l'eau ?
// =========================================================
n est un nombre
n vaut 0
tant que n < combien
n ajoute 1
// Coordonnées en mètres, au dixième près
tx est un nombre
ty est un nombre
tx vaut hasard(0, LARGM * 10) / 10
ty vaut hasard(0, HAUTM * 10) / 10
dedans vaut faux
pour e de 0 à 3
ddx vaut (tx - ecx[e]) / erx[e]
ddy vaut (ty - ecy[e]) / ery[e]
si ddx * ddx + ddy * ddy <= 1 alors
dedans vaut vrai
fin si
fin pour
nbTirs ajoute 1
si dedans = vrai alors
nbEau ajoute 1
fin si
// On ne mémorise l'impact que si l'écran peut encore l'afficher
si nbPts < MAXPTS alors
tirX[nbPts] vaut CHAMPX + tx * ECH
tirY[nbPts] vaut CHAMPY + ty * ECH
tirEau[nbPts] vaut dedans
nbPts ajoute 1
fin si
fin tant que
si combien > 0 alors
// *** LA FORMULE ***
estim vaut arrondi(AIRETOT * nbEau / nbTirs)
// On garde la trace de l'estimation pour la courbe
si nbHisto < 80 alors
histo[nbHisto] vaut estim
histoN[nbHisto] vaut nbTirs
nbHisto ajoute 1
sinon
pour k de 0 à 78
histo[k] vaut histo[k + 1]
histoN[k] vaut histoN[k + 1]
fin pour
histo[79] vaut estim
histoN[79] vaut nbTirs
fin si
message vaut "Sur " + nbTirs + " tirs, " + nbEau + " sont tombés dans l'eau"
fin si
fin tant que





