(novembre 2015) Simulation d'un feu de forêt

a marqué ce sujet comme résolu.

Plop,

Je partage une ébauche fonctionelle en C1, qui sera améliorée dès que j'aurais du temps (le we prochain à priori). Pour le moment la simulation fonctionne, j'ai des arbres qui brulent et des stats qui s'affichent à chaque tour. Pour les améliorations à venir, il y aura bientôt un retour graphique et plus d'options pour déterminer la grille de départ (changer la probabilité et ajouter des lacs facilement).

Montre moi le code !


  1. Je l'aurais bien fait en Python, mais autant essayer de diversifier les langages ! 

Bonjour,

Je n'ai pas vu tous les codes, mais il me semble que certains d'entre eux parcourent toute la carte à chaque itération (complexité en $O(nm)$, pour $n$ arbres brûlés en $m$ étapes). Algorithmiquement, on peut faire beaucoup mieux pour un peu de mémoire en plus (simulation en $O(n)$, c'est un peu ce que j'ai fait dans mon premier code en python, mais sans donner d'explications), et voici comment :

Remarquons tout d'abord que seuls les arbres qui brûlent à l'étape $i$ ont un impact à l'étape $i+1$. Par ailleurs, un arbre brûle en une seule étape. Il suffit donc de conserver une liste des arbres qui brûlent (le front de feu si vous voulez) pour réaliser la simulation. Une structure de type FIFO est donc tout indiquée ici.

S'il est souhaitable de réaliser l'affichage pas à pas, il faut encore ruser un peu afin de distinguer les différentes étapes, mais l'idée reste la même.

Démonstration en C (percolation de lien, 1024x1024 arbres, p = 0.55, approx. 10 simulations par seconde sur mon i3) :

 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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define WIDTH   1024
#define HEIGHT  1024
#define SZ      (WIDTH * HEIGHT)
#define PROBA   0.55

enum {NONE=0, TREE=1, BURN=2, ASHE=3} State;


static char mat[SZ];
static int queue[SZ];
size_t I, J;

float random() {
    return (float) rand() / RAND_MAX;
}

void step()
{
    const size_t k = J;
    while (I < k)
    {
        const int i = queue[I++];
        mat[i] = ASHE;
        if (i > 0 && mat[i-1] == TREE && random() < PROBA)
        {
            queue[J++] = i-1;
            mat[i-1] = BURN;
        }
        if (i < SZ && mat[i+1] == TREE && random() < PROBA)
        {
            queue[J++] = i+1;
            mat[i+1] = BURN;
        }
        if (i >= WIDTH && mat[i-WIDTH] == TREE && random() < PROBA)
        {
            queue[J++] = i-WIDTH;
            mat[i-WIDTH] = BURN;
        }
        if (i + WIDTH < SZ && mat[i+WIDTH] == TREE && random() < PROBA)
        {
            queue[J++] = i+WIDTH;
            mat[i+WIDTH] = BURN;
        }
    }
}

void init() {
    memset(mat, TREE, SZ);
    I = 0, J = 0;
    const int k = rand() % SZ;
    mat[k] = BURN;
    queue[J++] = k;
}

int main()
{
    srand(time(NULL));

    for (int i=1; i<=10; ++i) {
        init();

        size_t steps;
        for(steps=0; I < J; steps++) {
            step();
        }
        printf("%d) %d sur %d arbres brules, en %u etapes\n", i, J, SZ, steps);
    }

    return 0;
}

+4 -0

Salut,

Voici ma contribution en Haskell:

 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
module SimulationFeuForet where

-- Package tierce, faites `cabal install matrix` pour l'installer
import Data.Matrix
import System.Random (randomRIO)

-- Pour les prompts plus développés
import System.Console.Haskeline
import Data.Maybe (fromMaybe)

-- Représente une cellule d'arbre, d'arbre en feu, et de cendre
data Cell
    = Tree
    | Fire
    | Ash
    deriving Eq

instance Show Cell where
    show Tree = "#"
    show Fire = "§"
    show Ash  = "…"

type Forest = Matrix Cell
type Pos    = (Int, Int)

displayForest :: Forest -> IO ()
displayForest forest = putStrLn $ concatMap concat (map (("\n":) . map show) (toLists forest))

-- J'utilise cette fonction qu'une fois, j'aurai pu la mettre dans un `where` je pense
translateBy :: Pos -> (Int, Int) -> Pos
(x, y) `translateBy` (dx, dy) = (x + dx, y + dy)

generateForest :: Int -> Int -> Forest 
generateForest w h = matrix w h cellGen
    where
        cellGen (1, _) = Ash
        cellGen (_, 1) = Ash
        cellGen (x, y) =
            if x == w || y == h then
                Ash
            else
                Tree

initRandomFire :: Forest -> IO Forest
initRandomFire forest = do
    x <- randomRIO (1, nrows forest)
    y <- randomRIO (1, ncols forest)
    return $ setElem Fire (x, y) forest

getNeighbors :: Pos -> [Pos]
getNeighbors pos = map (pos `translateBy`) offsets
    where offsets = [(1, 0), (0, 1), (-1, 0), (0, -1)]

processFire :: Int -> Forest -> IO Forest
processFire prob forest =
    if Fire `elem` toList forest then do
        iteration <- sequence $ matrix (nrows forest) (ncols forest) (updateCell prob forest)
        displayForest iteration
        processFire prob iteration
    else
        return forest

updateCell :: Int -> Forest -> Pos -> IO Cell
updateCell prob forest pos =
    case forest ! pos of
        Tree -> if Just Fire `elem` map (\ (x, y) -> safeGet x y forest) (getNeighbors pos)
            then randomRIO (0, 100) >>= \ rnd ->
               return $ if rnd < prob then Fire else Tree
            else return Tree
        Fire -> return Ash
        Ash  -> return Ash

main :: IO ()
main = do
    prob    <- prompt "Probabilité (en %): "
    width   <- prompt "Largeur: "
    height  <- prompt "Hauteur: "
    burning <- initRandomFire (generateForest width height)
    final   <- processFire prob burning
    print final

prompt :: String -> IO Int
prompt msg = read <$> fromMaybe "" <$> runInputT defaultSettings (getInputLine msg)

EDIT: Le code est un peu dégueu, j'ai changé quelques trucs.

EDIT 2: Merci à Aloqsin pour sa remarque, j'ai adapté le code.

+4 -0

Hier, en jouant avec les paramètres de probabilité (par ex. probas différentes selon la direction du feu / seuil de proba choisi selon une distribution gaussienne / etc.), j'ai obtenu des rendus visuels intéressants (direction / dynamique de propagation).

J'ai réalisé le défi en Coffeescript.

J'ai utilisé la liste des arbres en feu suggéré par yoch. Merci d'ailleurs, je crois pas que j'y aurais pensé tout seul.

Le code.

Et un graphique qui montre le seuil décrit par Gabbro.

  • p est le paramètre de la simulation.
  • ratio est le nombre d'arbre restant divisé par le nombre d'arbres initial.

Le ratio est la moyenne de 100 simulation pour chaque p différent.

+3 -0

L'explication

Voici ma version en Scheme, volontairement à peu près claire pour les curieux qui ne connaissent pas le langage. Le programme devrait fonctionner sur toutes les implémentations de Scheme qui fournissent au moins sleep, random, clear et println.

Le programme affiche étape par étape un rendu du feu dans le terminal. (Le rendu utilise les couleurs avec le code d'échappement ANSI et l'utf8, et devrait donc fonctionner uniquement sur un terminal compatible.) Mon scénario est le suivant: Une personne est infectée par une maladie (feu). La maladie est assez simpliste, elle à une probabilité p d'être transmise à un voisin direct (arbre). Toute personne infectée meurt (cendre). En fait le problème reste le même, mais y'a pas de caractères utf8 qui représentent le trio arbre/feu/cendre :p . Le programme affiche enfin le pourcentage de survivants.

Le résultat

(gif animé qui représente une exécution)

Image utilisateur

Le code

 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
;;---------------------------------------------------------------------------
;; Params
(define size 10)
(define p 0.5)

(print "Grid size: ") (set! size (read))
(print "P: ")         (set! p (read))

;;---------------------------------------------------------------------------
;; Init
(define tsize (+ size 2))
(define start (+ (* (random 1 size) tsize) (random 1 size)))
(define TREE 0)
(define ASH 1)
(define FIRE 2)
(define grid (make-vector (* tsize tsize)))

;;---------------------------------------------------------------------------
;; Utils

(define (pp-pos pos)
  (cond
    ((fire? pos) (print "\033[1;31m☣ \033[0m"))   ;☣ "))
    ((tree? pos) (print "\033[1;32m☺ \033[0m"))
    ((ash?  pos) (print "\033[1;37m☠ \033[0m"))))

(define (pp-grid)
    (let loop ((i 0))
      (if (< i (* tsize tsize))
        (begin (if (not (on-edge? i))
                   (pp-pos i)
                   (if (right? i)
                       (newline)))
               (loop (+ i 1))))))

(define (left? pos)    (= (modulo pos tsize) 0))
(define (right? pos)   (= (modulo pos tsize) (- tsize 1)))
(define (top? pos)     (< pos tsize))
(define (bottom? pos)  (>= pos (- (* tsize tsize) tsize)))
(define (on-edge? pos) (or (left? pos) (right? pos) (top? pos) (bottom? pos)))

(define (tree? pos) (= (vector-ref grid pos) TREE))
(define (fire? pos) (= (vector-ref grid pos) FIRE))
(define (ash? pos)  (= (vector-ref grid pos) ASH))

;;---------------------------------------------------------------------------

(define (prob-fire)
  (<= (random 1 1000) (* p 1000)))

(define (propagate-fire pos)
  (foldr (lambda (el r)
           (if (and (tree? el) (prob-fire))
               (begin (vector-set! grid el FIRE) (cons el r))
               r))
         '()
         (list (- pos 1) (+ pos 1) (- pos tsize) (+ pos tsize))))

(define (turn trees-on-fire)
  (clear)
  (pp-grid)
  (sleep 0.2)
  (if (null? trees-on-fire)
      #f
      (begin ;; On fire -> ash
             (for-each (lambda (i) (vector-set! grid i ASH))
                         trees-on-fire)
             ;; Get new trees on fire
             (let ((new-fire (foldr (lambda (el r)
                                      (append (propagate-fire el)
                                              r))
                                    '()
                                    trees-on-fire)))
               (turn new-fire)))))

;; Init grid
(let loop ((i 0))
 (if (< i (* tsize tsize))
     (begin (if (on-edge? i)
                (vector-set! grid i ASH)
                (vector-set! grid i TREE))
            (loop (+ i 1)))))

;; Init fire
(vector-set! grid start FIRE)

;; Go
(turn (list start))

;; Print result
(let loop ((i 0)
           (r 0))
  (if (<= i (* tsize tsize))
      (if (= i (* tsize tsize))
          (println "Result: " (exact->inexact (* 100 (/ r (* size size)))) "%")
          (if (and (not (on-edge? i)) (ash? i))
              (loop (+ i 1) (+ r 1))
              (loop (+ i 1) r)))))

+11 -0

J'avais déjà lu cet article, et j'ai misérablement raté pour l'appliquer dans ce cas. Donc juste pour prévenir ceux qui seraient tentés d'aborder le problème de cette manière : c'est vraiment pas simple à comprendre et à mettre en place.

La première difficulté est de passer votre comonade en 2 dimensions. C'est l'étape la plus simple, mais il faudra cependant vraiment bien comprendre cette étape.

Ensuite, vous n'êtes pas sans savoir qu'un générateur de nombre aléatoire ne peut pas sortir de nul part et il faudra le gérer d'une certaine manière dans votre comonade. C'est là que les choses se corsent vraiment. Vous avez un article (en deux partie) qui explique toutes les misères qui peuvent vous arriver : http://productivedetour.blogspot.com.es/2012/12/evaluating-probabilistic-cellular.html

Oui, mais! Cet article ne décrit le problème que pour une comonade en 1 dimension. Le passage en deux dimension est bien plus dur que pour une comonade simple. C'est d'ailleurs à cet étape que je n'ai pas réussi à faire fonctionner la chose (boucle infini).

En tout cas, si quelqu'un arrive à faire fonctionner tout ça pour le problème posé, ça serait vraiment génial. Il ne manquera plus qu'à y ajouter une interface graphique avec de la programmation fonctionnelle réactive1.


  1. Moi, sadique? pas du tout! 

À kelepoc, je ne sais pas si c'était fait exprès ou non, mais la notion de seuil apparaît effectivement en épidémiologie. L'idée est proche de celle du feu, si une proportion faible des gens sont malades, la maladie va disparaître très vite. Si de nombreuses personnes sont malades, il y a risque d'une transmission généralisée de la maladie. Avec un seuil, c'est-à-dire qu'une faible augmentation de la proportion de malade juste en deçà du seuil augmente beaucoup le nombre de nouvelle personne infectée.

C'est de là que vient l'idée de l'immunité de groupe, où en vaccinant suffisamment de personne, on empêche le seuil d'être atteint, si bien que la propagation de la maladie reste toujours faible.

Pour donner un chiffre, le seuil épidémique est de 142 nouveau cas pour 100 000 habitants pour la grippe. Les chances de tomber malades sont beaucoup plus grandes une fois le seuil franchie (ce qui arrive tout les hiver).

+0 -0

@Grimur: Pour faire des stats sur un univers infini, ça risque d'être compliqué non ? Du coup, je propose une autre métrique : le nombre d'arbres brûlés par pas de simulation effectués.

Avec ce script simulant l'infini, j'obtiens cette courbe du ratio nombre d'arbres brûlés par pas de simulation (sur les 100 premiers pas, le ratio ayant tendance à augmenter au cours du temps pour P > 0.5).

ratio nombre d'arbres brulés par pas de simulation

 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
import random
from collections import deque
import numpy as np
import matplotlib.pyplot as plt

TREE, BURN, ASH = 0, 1, 2   # globales
MAX_STEPS = 100             # nombre maximum de pas de simulation

def simulation(p, max_steps=MAX_STEPS):
    mat = {}

    start = 0, 0  # point de départ
    mat[start] = BURN
    queue = deque([start])

    steps = 0
    n, k = 0, 1
    while steps < max_steps:
        if not queue:
            break
        i, j = queue.popleft()
        mat[i,j] = ASH
        n += 1
        for x, y in [(-1,0), (1,0), (0,-1), (0,1)]:
            npos = i + x, j + y
            if mat.get(npos, TREE) == TREE and random.random() < p:
                queue.append(npos)
                mat[npos] = BURN
        if n == k:
            steps += 1
            k += len(queue)

    return n, steps


if __name__ == '__main__':
    P = np.arange(0., 1., 0.025)
    X = []
    for p in P:
        lst = []
        for _ in range(25):
            n, s = simulation(p)
            lst.append(n / s)
        m = np.array(lst).mean()
        X.append(m)
    plt.plot(P, X)
    plt.show()

+0 -0

Le code Haskell de AlphaZeta ne compile pas (ou ne compile plus).

Il y a très probablement un oubli dans la définition de la fonction updateCell, en effet le symbole prob n'apparaît pas :

1
2
updateCell :: Forest -> Pos -> IO Cell
updateCell forest pos =

J'imagine que l'auteur voulait écrire :

1
2
updateCell :: Int -> Forest -> Pos -> IO Cell
updateCell prob forest pos =

Autre chose, le symbole computeNewState sort de nulle part, j'ai essayé de faire le lien avec le updateCell, mais je n'y arrive pas.

+1 -0

Je tiens à présenter mes excuses pour ce message que je sais hors-sujet, mais si certains d'entre vous ont envie de faire une autre simulation sur les forêts, il y a eu un article publié très récemment sur la distribution des arbres en fonction de leur taille dans une forêt.

L'algo semble assez simple et plutôt bien expliqué, mais je dois avouer ne pas l'avoir lu en détail, puisque ca ne m'intéresse pas plus que ca. Plus de détails ici.

Je vous laisse retourner à vos problèmes de percolation :)

Personne pour le faire en 3D ? La simulation est assez simple, mais visualiser proprement est plus difficile.

D'après mes tests (20 simulations sur diverses valeurs de $p$, voisinage à 6 points), l'équilibre se situerait aux alentours de $p=0.4$ (90% de l'espace atteint en moyenne).

pourcentage de l'espace atteint en fonction de p (31x31x31)

En comparaison, voici le même graphique pour une simulation 2D (voisinage 4 points) :

pourcentage de la surface atteinte en fonction de p (101x101)

+2 -0

Très fun cet atelier :)

J'ai affiné ma solution initiale en ajoutant les braises dans le cycle du feu:

Feu -> Braises -> Cendres (1 cycle par étape)

Le paramétrage de p se fait maintenant pour chaque type de terrain, le feu peut également redémarrer des braises.

J'ai ajouté un facteur multiplicateur de p causé par le vent (idée que je reprends d'un post précédent de Yoch).

Code C toujours ici.

Résultat d'une simulation avec vent de Sud-Ouest .

EDIT: mon simulateur travaillait avec 8 voisins et avec le même p pour tous les voisins, le résultat donnait une propagation de forme carrée peu réaliste. J'ai donc ajusté p pour les voisins en diagonale en divisant la probabilité de base par $\sqrt{2}$ (distance entre 2 voisins en diagonale). Cela donne à la propagation une forme plus arrondie qui me semble mieux correspondre à la réalité :)

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte