Utilisation d'automate pour simuler une foule

Fermé
jautomate - 2 déc. 2022 à 22:57
mamiemando Messages postés 33363 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 16 novembre 2024 - 5 déc. 2022 à 17:25

Bonjour,

Dans le cadre d'un travail d'initiative personnelle étant étudiant en classe préparatoire je suis amené à simuler une évacuation de foule sur python (avec interface graphique ).J'ai eu l'idée d'introduire un modèle probabiliste axé sur les automates cellulaires ( basé sur les probabilités de transition d'un piéton vers une cellule donnée, le sol étant discrétisé en grille de n cellules) . Je souhaite m'inspirer du Jeu de la vie de Conway pour implémenter une interface graphique avec n cases similaire au jeu avec des cases colorées pour indiquer la présence d'un piéton et j'aimerai savoir comment m'y prendre.

Merci d'avance.


Windows / Chrome 107.0.0.0

3 réponses

Je ne suis pas expert en graphique, mais je pense intuitivement aux modules turtle ou pygame.
Le jeu de la vie comme modèle peut être intéressant, mais il n'est pas directionel et ne préserve pas les individus.

0
mamiemando Messages postés 33363 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 16 novembre 2024 7 801
Modifié le 5 déc. 2022 à 15:47

Bonjour,

Je distinguerais deux étapes, la première étant optionnelle :

Étape 1 : voir l'état de ton graphe

Tu peux définir ta structure de graphe avec networkx (ou une librairie équivalente) et ensuite en faire le rendu graphique avec graphviz (plus précisément, le moteur neato) en lui passant en paramètre la chaîne dot que tu auras généré.

Voici par exemple à quoi ressemble la chaîne dot d'une grille de 2x2 sommets.

digraph G {
  graph [fontcolor="black"; bgcolor="transparent"; rankdir="LR"]; node [color="black"; fontcolor="black"; fillcolor="transparent"]; edge [color="black"; fontcolor="black"];
  node [shape="rectangle"; style="rounded, filled"];
  vertex_0_0 [label=<(0,0)>; pos="0,0!"];
  vertex_0_1 [label=<(0,1)>; pos="1,0!"];
  vertex_1_0 [label=<(1,0)>; pos="0,-1!"];
  vertex_1_1 [label=<(1,1)>; pos="1,-1!"];
  vertex_0_0 -> vertex_1_0 [label=0.1];
  vertex_0_0 -> vertex_0_1 [label=0.9];
  vertex_0_1 -> vertex_0_0 [label=0.2];
  vertex_0_1 -> vertex_1_1 [label=0.8];
  vertex_1_0 -> vertex_0_0 [label=0.3];
  vertex_1_0 -> vertex_1_1 [label=0.7];
  vertex_1_1 -> vertex_0_1 [label=0.4];
  vertex_1_1 -> vertex_1_0 [label=0.6];
}

L'avantage de graphviz c'est que tu peux même directement annoter les transitions avec les probabilité de tes arcs ou adapter des attributs (comme la couleur) en fonction de la probabilité.

Si le logiciel (pas le module python) est installé et fournit notamment la commande neato (paquet graphviz sous Linux, pas python3-graphviz), tu peux y faire appel en python avec la fonction dot_to_svg :

def error(*cls):
    print(*cls, file=sys.stderr)

def run_graphviz(s_dot: str, layout_engine: str = "dot", format: str = "svg") -> bytes:
    """
    Convert a dot string (graphviz format) into a graphic file.
    Args:
        s_dot: A string in the graphviz format.
        layout_engine: The graphviz engine used for the rendering.
            See "man dot" for more details.
        format: The output graphviz terminal.
            See "man dot" for more details.
    Returns:
        The bytes/str of the output image iff successful, None otherwise.
    """
    cmd = ['dot', '-T' + format, '-K', layout_engine]
    dot = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
    stdout_data, stderr_data = dot.communicate(s_dot.encode("utf-8"))
    status = dot.wait()
    if status == 0:
        if format == "svg":
            return stdout_data.decode("utf-8")
        else:
            return stdout_data
    else:
        error(f"dot returned {status}\n[==== stderr ====]\n{stderr_data.decode('utf-8')}")
        error("Input dot string was:\n%s\n" % s_dot)
        raise RuntimeError("run_graphviz: invalid string")

def dot_to_svg(s_dot :str, layout_engine = "dot", format = "svg") -> str:
    bytes_img = run_graphviz(s_dot, layout_engine, format)
    if isinstance(bytes_img, bytes): # This should no more occur
        return bytes_img.decode("utf-8")
    elif isinstance(bytes_img, str):
        i = bytes_img.find("<svg")
        return bytes_img[i:]
    return None

Si tu travailles dans Jupyter, tu peux ensuite afficher le svg avec :

def html(s :str):
    """
    Evaluate HTML code in a Jupyter Notebook.
    Args:
        s: A str containing HTML code.
    """
    from IPython.display import display, HTML
    chart = HTML(s)
    # or chart = charts.plot(...)
    display(chart)

Bon je n'entre pas plus dans les détails car peut être que ton modèle probabiliste est suffisamment simple pour que tu n'aies pas besoin de donner ta chaîne de Markov.

Étape 2 : faire l'animation

Dans ce cas il existe plein de solutions techniques, comme le dit Pierrot, si tu te contentes de représenter chaque cellule par un carré coloré, cela se fait en turtle ou effectivement pygame. L'avantage de turtle c'est qu'il est installé de base avec python et il me paraît suffisant dans ton cas.

Voici un exemple de code que j'ai retapé à partir d'une discussion que j'ai vu passé il y a quelques semaines :

import numpy as np

MAP = """1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 0 0 0 1 0 0 0 1 0 1 0 0 0 1 0 0 0 1
1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 0 1
1 0 0 0 0 0 1 0 0 0 1 0 1 0 0 0 0 0 1
1 0 1 1 1 1 1 0 1 0 1 0 1 1 1 1 1 1 1
1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 1
1 1 1 0 1 0 1 1 1 1 1 1 1 0 1 1 1 0 1
1 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 1
1 0 1 0 1 1 1 0 1 0 1 1 1 0 1 1 1 1 1
1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 1
1 1 1 1 1 0 1 1 1 0 1 1 1 1 1 0 1 0 1
1 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1
1 0 1 1 1 1 1 1 1 1 1 0 1 0 1 1 1 1 1
1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 1
1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1 0 1
1 0 1 0 1 0 1 0 1 0 0 0 0 0 1 0 0 0 1
1 1 1 0 1 0 1 0 1 1 1 1 1 0 1 0 1 1 1
1 0 0 0 1 0 1 0 1 0 0 0 0 0 1 0 0 0 1
1 0 1 1 1 1 1 0 1 1 1 0 1 1 1 1 1 0 1
1 0 0 0 1 0 0 0 1 0 1 0 0 0 0 0 1 0 1
1 1 1 0 1 0 1 1 1 0 1 1 1 0 1 0 1 0 1
1 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 1 0 1
1 0 1 1 1 1 1 1 1 0 1 0 1 1 1 1 1 0 1
1 0 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 1
1 1 1 0 1 1 1 0 1 0 1 0 1 1 1 0 1 1 1
1 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1"""

def load_matrix(s: str) -> np.array:
    return np.array([
        [int(i) for i in line.split()]
        for line in s.splitlines()
    ])

load_matrix(MAP)


import turtle
import numpy as np
from pprint import pprint
import sys, traceback

class Agent:
    def __init__(self, color: str, position: tuple, destination :tuple = None, planner = None):
        self.color = color
        self.position = position
        self.destination = destination
        self.planner = planner
        if self.planner:
            self.planner.update_position(position)
            self.planner.update_destination(destination)
    def update_position(self, position: tuple):
        self.position = position
        if self.planner:
            self.planner.update_position(position)
    def update_destination(self, destination: tuple):
        self.destination = destination
        if self.planner:
            self.planner.update_destination(destination)
    def query(self, ij_default: tuple = None) -> tuple:
        return self.planner.query() if self.planner else ij_default
    def is_done(self) -> bool:
        return self.position == self.destination

class Maze:
    def __init__(self, matrix: np.array, agents: list):
        # Map
        self.xmin = -300
        self.ymin = -300
        self.xmax = 300
        self.ymax = 300
        canvas = turtle.getcanvas()
        screen = turtle.TurtleScreen(canvas)
        screen.setworldcoordinates(self.xmin, self.ymax, self.xmax, self.ymin)
        
        self.color_map = {
            i : color
            for (i, color) in enumerate(
                ["white", "grey", "yellow", "orange", "green"]
            )
        }
        self.load_map(matrix)
        self.agents = agents
        self.map_destination_color = {
            agent.destination : agent.color
            for agent in agents
        }
                
        # Draw
        #turtle.exitonclick()
        self.draw_map()

    def compute_cell_step(self) -> int:
        (m, n) = self.matrix.shape
        w = (self.xmax - self.xmin) / n
        h = (self.ymax - self.ymin) / m
        return min(w, h)

    def load_map(self, matrix: np.array):
        self.matrix = matrix
        self.step = self.compute_cell_step()
    
    def to_xy(self, ij) -> tuple:
        (i, j) = ij
        return (self.xmin + j * self.step, (self.ymax - self.step) - i * self.step)

    @staticmethod
    def draw_square(xy: tuple, color: str, size: int, filled :bool =True):
        turtle.speed('fastest')
        turtle.setheading(0)
        turtle.tracer(0)
        turtle.up()
        turtle.goto(xy)
        turtle.down()
        turtle.pencolor(color)
        if filled:
            turtle.fillcolor(color)
            turtle.begin_fill()
        for _ in range(4):
            turtle.forward(size)
            turtle.left(90)
        if filled:
            turtle.end_fill()
        turtle.up()

    def cell_color(self, ij) -> str:
        return self.color_map.get(self.matrix[ij], "black")

    def draw_cell(self, ij: tuple):
        size = self.step
        xy = self.to_xy(ij)
        cell_color = self.cell_color(ij)
        Maze.draw_square(xy, cell_color, size, filled=True)
        for agent in self.agents:
            if agent.destination is not None and agent.destination == ij:
                Maze.draw_square(xy, agent.color, size, filled=False)

    def draw_cells(self):
        (m, n) = self.matrix.shape
        for i in range(m):
            for j in range(n):
                self.draw_cell((i, j))

    def draw_destinations(self):
        size = self.step
        for (ij, color) in self.map_destination_color.items():
            xy = self.to_xy(ij)
            Maze.draw_square(xy, color, size, filled=False)

    @staticmethod
    def draw_dot(xy: tuple, color: str, size: int):
        turtle.speed('fastest')
        turtle.setheading(0)
        turtle.tracer(0)
        turtle.up()
        turtle.goto(xy)
        turtle.forward(size / 2)
        turtle.left(90)
        turtle.forward(size / 2)
        turtle.down()
        turtle.dot(size, color)
        
    def draw_agent(self, agent: Agent):
        ij = agent.position
        color = agent.color
        size = 0.9 * self.step
        xy = self.to_xy(ij)
        Maze.draw_dot(xy, color, size)
    
    def draw_agents(self):
        for agent in self.agents:
            self.draw_agent(agent)

    def draw_map(self):
        self.draw_cells()
        self.draw_destinations()
        self.draw_agents()
        
    def move(self, agent: Agent, new_ij: tuple) -> tuple:
        ij = agent.position
        if (
            not (0 <= new_ij[0] < self.matrix.shape[0]) or
            not (0 <= new_ij[1] < self.matrix.shape[1])
        ):
            new_ij = ij
        elif self.matrix[new_ij] == 1:
            new_ij = ij
        else:
            xy = self.to_xy(ij)
            xy_new = self.to_xy(new_ij)
        if ij != new_ij:
            agent.update_position(new_ij)
            self.draw_cell(ij)
            self.draw_agent(agent)
        return new_ij
    
    def end(self, message = None):
        turtle.bye()
        if message is not None:
            print(message)
        
    def moves(self, dij_default: tuple = None) -> tuple:
        for agent in self.agents:
            ij_default = (
                agent.position[0] + dij_default[0],
                agent.position[1] + dij_default[1]
            ) if dij_default else None
            new_ij = agent.query(ij_default)
            if new_ij is not None:
                self.move(agent, new_ij)
                if self.check_collision(agent):
                    self.end("BOOOM")
        if self.check_victory():
            self.end("BRAVO")
    
    def check_collision(self, agent) -> bool:
        ijs = [agent.position for agent in self.agents]
        if len(ijs) != len(set(ijs)):
            return True
        if any(self.matrix[ij] == 1 for ij in ijs):
            return True
        return False
    
    def check_victory(self) -> bool:
        return all(agent.is_done() for agent in self.agents)

matrix = load_matrix(MAP)
agents = [
    Agent("red",  (17, 11), (17, 10), None),
    Agent("blue", (3, 5),   (26, 17), None),
]

maze = Maze(matrix, agents)

try:
    turtle.listen() # Listen for keyboard events
    turtle.onkeypress(lambda: maze.moves(( 0, -1)), "Left")
    turtle.onkeypress(lambda: maze.moves(( 0,  1)), "Right")
    turtle.onkeypress(lambda: maze.moves((-1,  0)), "Up")
    turtle.onkeypress(lambda: maze.moves(( 1,  0)), "Down")
    turtle.mainloop()
except:
    turtle.bye()
    print(traceback.format_exc(), file=sys.stderr)

Ici le jeu dessine un labyrinthe et deux agent (rouge et bleu) dont le but est d'atteindre la case encadrée par leur couleur. À moins qu'un agent ait un planner, il se déplace (si possible) conformément à la flèche du clavier sur laquelle tu appuies. Ainsi appuyer sur la flèche droite déplace l'un des deux agents d'une case vers la droite (si possible), vérifie s'il est entré en collision avec un autre personnage, et ainsi de suite jusqu'à avoir déplacer tous les personnages.

Programmer une intelligence pour un agent consiste juste à implémenter son planner. Dans ton cas il serait basé sur les probabilité des transitions de ton graphe.

Bonne chance

0
mariam-j Messages postés 1344 Date d'inscription mercredi 9 mars 2022 Statut Membre Dernière intervention 16 novembre 2024 10
5 déc. 2022 à 17:01

Bonjour,
Tu doit pouvoir trouver sur le net des simulations de mouvement brownien.
Il y en a même un d'intégré dans un vieux Python 27.
tu n'aura alors qu'à agir sur les paramètres de déplacement en créant un attracteur.

0
mamiemando Messages postés 33363 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 16 novembre 2024 7 801
5 déc. 2022 à 17:13

Merci pour ta réponse, mais je ne pense pas que le mouvement brownien soit le bon modèle mathématiques, car un mouvement brownien est dans le plan (pas dans un espace discret comme un graphe) conditionné par des collisions (pas selon les probabilités assignées aux arcs du graphe).

0
mariam-j Messages postés 1344 Date d'inscription mercredi 9 mars 2022 Statut Membre Dernière intervention 16 novembre 2024 10 > mamiemando Messages postés 33363 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 16 novembre 2024
5 déc. 2022 à 17:17

Une foule se déplace à priori dans un plan (en attendant les piétons volants).

0
mamiemando Messages postés 33363 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 16 novembre 2024 7 801 > mariam-j Messages postés 1344 Date d'inscription mercredi 9 mars 2022 Statut Membre Dernière intervention 16 novembre 2024
5 déc. 2022 à 17:25

Je crois que tu as mal lu le modèle mathématique que jautomate veut utiliser. Je cite :

J'ai eu l'idée d'introduire un modèle probabiliste axé sur les automates cellulaires ( basé sur les probabilités de transition d'un piéton vers une cellule donnée, le sol étant discrétisé en grille de n cellules)

Comment appliquerais-tu le modèle du mouvement brownien dans ce contexte ?

0