Utilisation d'automate pour simuler une foule

jautomate -  
mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   -

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

  1. PierrotLeFou
     

    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
  2. mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   7 944
     

    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
  3. mariam-j Messages postés 1657 Date d'inscription   Statut Membre Dernière intervention   35
     

    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
    1. mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   7 944
       

      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
      1. mariam-j Messages postés 1657 Date d'inscription   Statut Membre Dernière intervention   35 > mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention  
         

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

        0
      2. mamiemando Messages postés 33228 Date d'inscription   Statut Modérateur Dernière intervention   7 944 > mariam-j Messages postés 1657 Date d'inscription   Statut Membre Dernière intervention  
         

        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