Sujet
Le projet est à réaliser individuellement et sera terminé le 7/12/2021 à 23h59.
Vous travaillerez avec git
, donc rendez-vous ici https://gitlab.ensimag.fr dans votre dépôt projet_bpi_xxx
une fois que vous aurez lu attentivement cette page jusqu'au bout.
Description haut niveau
Le programme que vous allez écrire, qui se décomposera en deux fichiers Python, doit :
- calculer une valeur approximative de π à l'aide d'une simulation de Monte-Carlo ;
- générer une image animée représentant la simulation comme ci-dessous.
Pour calculer une approximation de π à l'aide d'une simulation de Monte-Carlo en tirant n
points aléatoires (comme au casino), l'algorithme simple que vous utiliserez est le suivant :
- initialiser un compteur à 0 ;
- pour
i
allant de1
àn
:- générer un point aléatoire
(x,y)
dans un carré de longueur 2 et de centre(0,0)
. Pour cela il suffit de générerx
dans[-1,1]
ety
dans[-1,1]
; - si le point est dans le cercle de centre
(0,0)
et de rayon1
, ajouter 1 au compteur ;
- générer un point aléatoire
- l'estimation de π/4 est la valeur du compteur divisée par le nombre
n
.
Pour en savoir plus sur le sujet, nous nous référerons au très clair billet de Bruno Tuffin sur le site interstices.info.
Pour la création de l'image animée, nous générerons des images au format PPM
puis nous les agrégerons comme expliqué dans la prochaine section.
Travail à réaliser
Le travail à réaliser se décompose en deux parties.
Première partie : cœur de la simulation
La première partie du projet consiste à écrire un module Python approximate_pi.py
qui sera le cœur de notre simulation.
Autrement dit, ce module se charge uniquement du travail en lien avec le calcul d'une approximation de π sans se préoccuper des questions de visualisation sous forme d'image.
Le module approximate_pi.py
doit fournir au programme draw.py
décrit ci-dessous :
- les points aléatoires tirés avec
x
appartenant à[-1,1]
ety
appartenant à[-1,1]
pour tous les points ; - l'information indiquant si un point aléatoire est dans le cercle unitaire ou non.
Il est demandé que le module approximate_pi.py
soit également un programme exécutable permettant de donner une approximation de π en utilisant un nombre de points donné sur la ligne de commande tel qu'illustré ci-dessous :
1 2 |
|
Deuxième partie : génération d'images PPM
puis d'un GIF
Nous allons maintenant utiliser approximate_pi.py
comme un module pour passer aux choses sérieuses, c'est à dire à la génération de notre image animée représentant une simulation.
Le travail demandé consiste à écrire un programme Python draw.py
qui :
- reçoit 3 arguments dans l'ordre suivant depuis la ligne de commande :
- la taille de l'image en pixels, qui est carrée donc un seul entier qui devra être supérieur ou égale à 100 ;
- le nombre de point
n
à utiliser dans la simulation, qui devra être supérieur à 100 ; - le nombre de chiffres après la virgule à utiliser dans l'affichage de la valeur approximative de π, qui devra être compris entre 1 et 5.
- implémente une fonction
generate_ppm_file(...)
qui génère une image au format PPM. L'image doit montrer les points qui sont à l'intérieur du cercle d'une certaine couleur, les points qui sont à l'extérieur du cercle d'une autre couleur et la valeur approchée de π comme l'illustre l'image animée ci-dessus. La couleur de fond de l'image, blanc sur l'image ci-dessus, peut également être n'importe quoi d'autre. Le nom de l'image doit être au formatimg0_3-13776.ppm
:0
indique le numéro de l'image dans la simulation totale (0
en l'occurrence, donc la première image) et3-13776
correspond à l'approximation courante de π en utilisant le nombre de chiffres après la virgule spécifié sur la ligne de commande (5 en l'occurrence). La signature de la fonction n'est volontairement pas donnée pour que chacun puisse réaliser sa propre solution ; - génère une image
PPM
, en utilisant la fonctiongenerate_ppm_file
, représentant l'état courant de la simulation à chaque fois qu'un dixième du nombre total de points a été tiré. Dix images seront donc générées. On le répète, les points doivent êtres fournis par le moduleapproximate.py
. Comme ce dernier s'occupe uniquement du cœur de la simulation et n'a donc pas connaissance de la taille de l'image, la conversion des points avecx
appartenant à[-1,1]
ety
appartenant à[-1,1]
vers des pixels dans l'image doit se faire dansdraw.py
; - utilise le programme
convert
pour créer une image animée au formatGIF
à partir des dix imagesPPM
.
Si plusieurs point tirés par approximate_pi.py
"tombent" sur le même pixel alors que certains points sont dans le cercle et d'autres non (ce peut arriver pour les points à la frontière du cercle), alors draw.py
doit dessiner le pixel de la couleur associée au dernier point tiré correspondant au pixel.
Consignes à respecter
Il est demandé de respecter scrupuleusement les consignes suivantes :
- respecter précisément les noms des fichiers Python et images indiqués ci-dessus ;
- votre programme
draw.py
doit lancer une exception sans l'attraper si une ou plusieurs entrées incorrectes sont données par l'utilisateur sur la ligne de commande. Autrement dit, si l'utilisateur lance le programme avec./draw.py tutu 10000 4
ou avec./draw.py -800 100000 4
il doit voir dans son terminal une erreur indiquant l'exception qui a été lancée ainsi que la pile d'appels. L'interpréteur se charge de tout cet affichage pour nous quand on lance une exception et que celle-ci n'est jamais attrapée et donc remonte jusqu'au sommet de la pile d'appels ; - utiliser des
f-string
quand cela est pertinent ; - ne PAS lancer
eog
automatiquement depuis votre programme python ; - votre programme
draw.py
doit créer les images dans le répertoire courant et non pas dans un sous dossier ; - utiliser le module
subprocess
pour générer l'image animéeGIF
à partir des imagesPPM
; - il est interdit d'utiliser
numpy
; - il est interdit d'utiliser un module externe pour rajouter le texte de π par dessus votre image. Autrement dit l'objectif est de coder cet affichage à la main en pensant à l'afficheur 7 segments de votre gadget électronique préféré ;
- idéalement, la taille du texte de π par dessus votre image s'adapte à la taille de l'image.
Tests automatiques
Des tests automatiques seront lancés régulièrement sur la dernière version de votre code afin d'évaluer les critères ci-dessous. Les résultats de ces tests sont disponibles ici.
Critères :
- justesse du code : le programme réalise bien ce qui est demandé
- respect des consignes ci-dessus ;
- qualité du code : utilisation d'au moins deux modules tel que demandé ci-dessus et utilisation de
pylint
. Le fichier de configuration pourpylint
est disponible ici. Ce fichier doit être placé à la racine de votre répertoire personnel et doit être nommé.pylintrc
; - performance (temps et utilisation mémoire) du programme ;
- complexité en calcul du programme en fonction des entrées ;
- complexité en mémoire du programme en fonction des entrées ;
- le poids des images
PPM
générées (c'est à dire le nombre d'octets sur le disque dur) ; - respect des consignes ci-dessous.
Rendu
À la fin du projet, dans votre dépôt git vous devez avoir :
- votre code, c'est à dire uniquement vos fichiers Python (attention aux fichiers cachés dont le nom commence par
.
) ; - un rapport de deux pages maximum.
Ne pas mettre votre nom ni dans le rapport ni dans les fichiers Python.
Dans le rapport vous présenterez, en argumentant, l'état de votre programme vis à vis des critères ci-dessus. Comme le rapport est limité à deux pages, il ne faut surtout pas reprendre le sujet mais présenter directement vos arguments.
Correction
Cliquez ici pour révéler la correction.
approximate_pi.py
Les points clés sont les suivants :
-
la fonction génératrice
get_points(nb_points)
renvoie un itérateur permettant de tirer aléatoirementnb_point
points(x, y)
avec x dans[-1, 1]
et y dans[-1, 1]
. En plus du point courant, l'itérateur renvoie un booléen indiquant si le point courant est dans le cercle ou non ainsi que le nombre de points déjà tirés appartenant au cercle ; -
lorsque
approximate_pi.py
est utilisé comme programme principal, on crée simplement un itérateur avec la fonction génératriceget_points(nb_points)
puis on le parcours entièrement sans rien faire juste pour récupérer le nombre total de points dans le cercle. Avec cette valeur on calcul l'approximation de π. La complexité spatiale du programme est donc constante et la complexité temporelle est une fonction linéaire du nombre de points.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
|
draw.py
Voici ci-dessous trois propositions de correction. D'autres solutions faisant des choix différents impliquant éventuellement des complexité mémoire et temps de calcul différentes, néanmoins toutes aussi raisonnables sont possibles. N'hésitez pas à contacter vos enseignants si vous voulez discuter d'une solution particulière.
Les trois solutions proposées génère des images PPM
au format binaire, qui est le format le plus compact.
Solution 1 : utilisation d'un bytearray
La première solution stocke toute l'image en mémoire sous la forme d'un bytearray
et écrit directement les pixels au format binaire dans ce bytearray
.
La complexité temporelle est quadratique en fonction de la largeur de l'image et linéaire en fonction du nombre de points.
La complexité spatiale est quadratique en fonction de la largeur de l'image et constante en fonction du nombre de points.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 |
|
Solution 2 : utilisation d'un dictionnaire
La second solution stocke les pixels associés aux points tirés aléatoirement dans un dictionnaire. La complexité temporelle est quadratique en fonction de la largeur de l'image et linéaire en fonction du nombre de points. La complexité spatiale est constante en fonction de la largeur de l'image et linéaire en fonction du nombre de points.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
|
Solution 3 : utilisation de la méthode seek
La seconde solution ne stocke rien du tout car les pixels sont directement écrit au bon endroit dans le fichier PPM
à l'aide de la méthode seek
(dont le coût est constant).
La complexité temporelle est constante (en théorie mais la pratique semble dire le contraire, peut-être que truncate
est linéaire) en fonction de la largeur de l'image et linéaire en fonction du nombre de points.
La complexité spatiale est constante en fonction de la largeur de l'image et constante en fonction du nombre de points.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
|