retour aux mémos     retour au modèle     back to SimMasto home page   retour à la page d'accueil

Gestion des agents d'une simulation
(ajout, mouvement, perception)


Objectif : Organiser les interactions et les déplacements des agents sur le support.
Entrée : Une simulation Repast Simphony qui intègre les fonctions de traitement du module 17 (relations agents-espace)
Sortie : Une simulation qui prend en charge des agents capables de se déplacer et de percevoir leur environnement proche.
Outils: Repast-Simphony

G   Les procédures et méthodes présentées dans ce mémo présentent le principe de base retenu pour implémenter la création, le déplacement et la perception des agents.
Ces procédures mises en place en 2009 sont en permanence actualisées (refactoring, etc.) mais le principe général et fondamental reste le même

SOMMAIRE :

Création des agents (classe ContextCreator)

Rendre les agents dynamiques (build, init, step, move)

Donner une perception de leur environnement aux agents (méthode findObject)


Création des agents

La création d'un agent s'effectue simplement par l'appel au constructeur de la classe correspondante,  mais pour l'intégrer dans la simulation il faut l'ajouter dans le contexte puis dans une projection.

On va donc faire un appel depuis la classe ContextCreator à la méthode « add_agent » du ground_manager. (il est logique d'ajouter les premiers agents de la simulation lors de la définition du contexte, mais on peut aussi bien créer une classe indépendante qui va créer des générations d'agents à des moments donnés).

Dans cet exemple on envoie en paramètre le contexte dans lequel on va ajouter l'agent, puis le nombre d'agents à ajouter.

public class ContextCreator implements ContextBuilder {
      //on peut définir le nombre d'agent en variable de classe pour  la modifier rapidement.  
      public final int number_of_agents = 20;  
      @Override
      public Context build(Context context)
      {

      //on crée un ground_manager de type gis
      GIS_Manager gm = new GIS_Manager();
              //on charge le terrain
          gm.create_ground(context,"Geography","data_sig/shapefile.shp");
      //on ajoute les agents  
      gm.add_agents(context, number_of_agents);  
      return context
      }
}
 

Il faut ensuite implémenter la méthode « add_agent » dans la classe implémentant l'interface Ground_Manager que l'on a choisi (ici SIG_Manager). Puis il faut configurer un affichage en fonction de la représentation choisie.

G        Si vous voulez disposer de plusieurs types d'affichage prêts, vous pouvez récupérer le contenu du fichier qui contient la configuration de l'affichage. Il s'agit d'un fichier XML situé à coté du model. Score. Son nom doit ressembler à « repast.simphony.action.display_1.xml ». Ce fichier est écrasé à chaque fois que vous enregistrez un affichage, mais vous pouvez copier coller le contenu pour conserver différentes configurations d'affichage. Il suffit ensuite de le remplacer.

haut

A. Méthode addAgent() propre au SIG_Manager

  Dans le cas  d'un terrain de type geography, on va créer une géométrie de type point pour représenter chaque agent. On place ensuite ces agents de façon aléatoire mais à l'intérieur du shapefile.

@override
public
void add_agents(Context context, int nb_agent)
      {
      //on créé un générateur de nombre aléatoire   
      Random rand =  new Random();
      //on crée une « usine à géométrie »
      GeometryFactory gf = new GeometryFactory();

          //on récupère la projection concerné par le GIS (celle  déclarée dans le //fichier « model.score »

          Geography projection = (Geography)context.getProjection("Geography");
//we get the boundaries from the envelope
      int x_min = (int) envelope.getMinX();
      int y_min = (int) envelope.getMinY();
      int x_max = (int) envelope.getMaxX();
      int y_max = (int) envelope.getMaxY();
//finally we add agents
            for (int i = 0 ; i< nb_agent ; i++)
            {
            Mobile_Agent agent = new Mobile_Agent();
            context.add(agent);
//we add one agent in the context
            //we take a couple of random coordinate inside the SIG
            int x = rand.nextInt(x_max-x_min) + x_min;
            int y = rand.nextInt(y_max-y_min) + y_min;
            Coordinate c = new Coordinate(x,y);            
//then we build a geometry where we can put the agent
            com.vividsolutions.jts.geom.Point p = gf.createPoint(c);
            projection.move(agent, p);
          }
      }

G        Attention la  dernière ligne ne déplace pas l'agent mais lui associe une géométrie.

Il faut voir le résultat de cette méthode comme une entité composée d'une géométrie et d'un agent.

La géométrie est en fait la représentation de l'agent (ici un point) et c'est elle qui possède des coordonnées (sans géométrie, un agent n'a pas de représentation et sans agent une géométrie n'est pas représentée).

L'agent existe en tant qu'acteur et possède des attributs, la géométrie n'est qu'une forme possédant  des coordonnées.

haut

B. Méthode addAgent() propre au raster Manager

 Pour ajouter des agents dans le raster_manager, on va ajouter les agents dans la  projection de type espace continu selon des coordonnées aléatoires.

On va également ajouter la référence de l'agent dans la liste d'agent_mobile associée à l'agent parcelle qui correspond aux coordonnées où on a ajouté l'agent.

@Override
public
void add_agents(Context context, int nb_agent)
{ 
// Pour ajouter des agents, on crée un objet random qui va générer des coordonnées aléatoires dans la grille
      Random rand= new Random();
      int x;
      int y;
      double[] new_location = new double[2];
      Dimensions dim = grille.getDimensions();
      int grid_width = (int) dim.getWidth();
      int grid_height = (int) dim.getHeight();
     
      for (int i = 0 ; i < nb_agent; i++)
      {
            x = rand.nextInt(grid_width);
            y = rand.nextInt(grid_height);
            new_location[0] =x;x;
            new_location[1] =y;
            Mobile_Agent a = new  Mobile_Agent();
            //on place les agents
            matrice_terrain[x][y].agent_incoming(a);

//lors de la création d'un agent, on ajoute celui ci à  la liste des agents contenus dans la grille et on lui donne des coordonnées de déplacement dans la case de 0.
            context.add(a);
            space.moveTo(a, new_location);    
      }
}

haut

Rendre les agents dynamiques

A ce stade, les agents apparaissent dans le simulateur, mais ils ne font rien. Pour apporter un peu de vie à cette simulation, nous allons permettre aux agents de se déplacer sur leur terrain.

Puisque les agents ne connaissent pas à l'avance le support sur lequel ils vont être placés, il faut leur envoyer une instance du Ground_Manager qui va se charger de fournir les méthodes qu'ils vont utiliser.

Cette référence étant commune pour tous les agents, on va utiliser une méthode statique pour renseigner les attributs communs à toutes les instances de la classe. Cette méthode sera utilisée par le constructeur du contexte.

L'appel à cette méthode doit se faire avant que les agents commencent à interagir avec le terrain. Dans  le cas d'attributs statiques on peut très bien les renseigner avant la création des agents. Par la même occasion on pourra configurer les attributs de classe des agents en fonction des paramètres choisis pour la simulation :

Il faut donc modifier la méthode « build » de la classe ContextCreator pour faire appel à une méthode « init » permettant aux agents de connaître les références qu'ils vont utiliser lors de la simulation.

@Override
      public Context build(Context context)
      {
      //on crée un ground_manager de type gis
      GIS_Manager gm = new GIS_Manager();
      //on charge le terrain
      gm.create_ground(context,"Geography","data_sig/shapefile.shp");
      //on va initialiser les agents pour leur donner une référence du //Ground_Manager à appeler lors de la simulation
         Field_Agent.init(gm);
      Mobile_Agent.init(gm);
      //on  ajoute les agents
      gm.add_agents(context, number_of_agents);
      return context
      }

On va ensuite ajouter cette méthode dans  les classes de nos agents. Dans le code ci dessous, on initialise les agents de la classe agent dynamique.

public static void init( Ground_Manager m)
    {

        //on récupère les paramètres de la simulation
        Parameters p = RunEnvironment.getInstance().getParameters();
         //puis on récupère les valeurs enregistrées dans les différents champs
        vision = (Double)p.getValue("agent_Vision");
                // on peut ainsi récupérer les attributs d'un agent
       
speed = (Double)p.getValue("agentspeed");
                //et les paramètres de l'environnement dont il faut tenir compte
       
double mn_in_tick =  (Double)p.getValue("minutes_by_tick");
       
// Dans cet exemple on adapte le déplacement par tick en fonction de la vitesse et du temps représenté par un tick
        speed = (speed /60)*mn_in_tick;
        //enfin, on renseigne la variable « ground_manager » de type Ground_Manager qui sera très utile dans les méthodes en relation avec le terrain et l'environnement de l'agent.
        ground_manager = m;
}

Maintenant que la structure de base permettant aux agents de réaliser différentes actions est définie, nous pouvons commencer à leur donner un comportement global.
A chaque pas de temps, les agents vont réaliser certaines actions.

§         Dans la classe des agents mobiles, on peut ajouter la méthode suivante avec la ligne qui la précède.

      @ScheduledMethod( start =1 , interval = 1)
      public void step()
      {       }

 

G        Cette méthode est appelée par Repast Simphony à chaque pas de temps et pour chaque agent possédant cette méthode. (on peut préciser le tick à partir duquel cette méthode est appelée et l'intervalle)

On peut alors y ajouter les appels aux méthodes que l'agent doit effectuer à chaque tour.

Par exemple, pour se déplacer, on ajoute dans la méthode step,  l'appel à la méthode « move() » comme par exemple: 
 

public void move()
      {
      //La variable dep est une variable propre à l'agent, de type coordinate. Elle représente le vecteur de déplacement de l'agent      //en un tic
      //si l'agent ne bouge pas, on va lui donner des valeurs de déplacements aléatoires en fonction de sa vitesse.
            if (dep.x == 0.0  && dep.y == 0.0)
            {
                  dep.x = rand.nextDouble()*speed*2-speed;
                  dep.y = rand.nextDouble()*speed*2-speed;
            }
            //on demande ensuite au ground _manager de le déplacer.
            ground_manager.move_Object(this,dep);
      }

Ce code est réutilisable quel que soit le terrain, mais il faut adapter la méthode « move_objet » selon le Ground_Manager.

haut

C. Méthode “moveObject” dans le GIS_Manager

@Override
public
void move_Object(Agent agent,Coordinate dep)
{
//on commence par récupérer le contexte dans lequel on travaille

Context context = ContextUtils.getContext(agent);
//on peut ensuite récupérer la projection que l'on utilise
Geography
projection = (Geography) context.getProjection("Geography");
//on récupère la géométrie de l'agent présent dans cette projection.
Geometry geom = projection.getGeometry(agent);
//on peut alors récupérer les coordonnées de la géométrie(donc de l'agent)
Coordinate coord = geom.getCoordinate();
     
/*puisque l'agent ne connait pas son support, c'est au Ground_Manager d'effectuer les tests pour vérifier que l'agent ne sorte pas. On ne se contente pas de modifier son emplacement, on va modifier la référence de déplacement que l'agent à envoyer pour que celui ci ne reste pas bloqué sur un bord.*/

if ((coord.x < envelope.getMinX() && dep.x < 0) ||(coord.x > envelope.getMaxX() && dep.x >0))

{
            dep.x = - dep.x;
      }

if ((coord.y < envelope.getMinY() && dep.y < 0) || (coord.y > envelope.getMaxY() && dep.y > 0 ))
      {
            dep.y = - dep.y;
      }
/*une fois que les vérifications ont été faites,on modifie les coordonnées de la géométrie, puis on doit ré associer l'agent avec cette géométrie modifiée*/      
            coord.x += dep.x;
            coord.y += dep.y;
            projection.move(this,geom );
           
      }
 

haut

D. Méthode  “moveObjet” dans Raster_Manager

  @Override

public void move_Object(Agent a, Coordinate c)
{

                //on récupère les coordonnées de l'agent dans l'espace continu:
        NdPoint p =
space.getLocation(a);
        //on récupère la référence sur l'agent parcelle qui correspond à
 ces coordonnnées
      Field_Agent ap1 = matrice_terrain[(int)p.getX()][(int) p.getY()];
           

/*On fait appel à une fonction pour vérifier que le déplacement est correct (l'agent ne sort pas de la projection). En cas de besoin, la méthode va modifier la référence de déplacement de l'agent pour le renvoyer dans le bon sens: dans tous les cas, la fonction renvoi le déplacement adapté au support (et non pas en mètre). */
      Coordinate dep =  check_position(p ,c);
      double[] displacement = {dep.x,dep.y};
      //on effectue le déplacement
      space.moveByDisplacement(a, displacement);

//après le déplacement, on récupère l'agent parcelle associé à ces

//nouvelles coordonnées.
      p = space.getLocation(a);
      Field_Agent ap2 = matrice_terrain[(int)p.getX()][(int) p.getY()];

//si il a changé, il faut déplacer la référence de l'agent de

//l'ancien agent terrain au nouveau       
      if ( ap1 != ap2)
      {
            ap1.agent_leaving(a);
            ap2.agent_incoming(a);
      }
}

 

public Coordinate check_position(NdPoint p, Coordinate c)
{
      Coordinate dep = new Coordinate();

//on convertit le déplacement en mètre pour le faire correspondre à un déplacement à l'intérieur du terrain défini par la grille.
      dep.x = (double)c.x / (double)size_of_one_box;
      dep.y = (double)c.y / (double)size_of_one_box;
     
      //on crée un point pour estimer l'endroit où l'agent va se  déplacer
      NdPoint p_s = new NdPoint(p.getX()+dep.x, p.getY()+dep.y );
           

//on vérifie que ce point n'est pas en dehors des limites, si c'est le cas, on modifie le déplacement jusqu'à ce que le point d'arrivée soit dans les limites         
      if ( p_s.getX() < 0 && c.x < 0 )
      {
            c.x = -c.x;
            while ( p.getX() + dep.x < 0)
            {
                  dep.x += 0.1;
            }
      }
      else if ( p_s.getX()> space.getDimensions().getWidth() && c.x > 0)
      {
            c.x = -c.x;
            while (p.getX()+dep.x>space.getDimensions().getWidth())
            {
                  dep.x -= 0.1;
            }
      }
      if ( p_s.getY() < 0 && c.y < 0 )
      {
            c.y = -c.y;
            while ( p.getY() + dep.y < 0)
            {
                  dep.y += 0.1;
            }
      }
      else if (p_s.getY()>space.getDimensions().getHeight() && c.y > 0)
      {
            c.y = -c.y;
            while(p.getY()+dep.y>space.getDimensions().getHeight())
            {
                  dep.y -= 0.1;
            }
      }
      return dep;
}

 

Ce déplacement n'est pas très original, mais l'intérêt réside dans le fait que l'agent se déplace selon des valeurs de déplacement qu'il décide lui même,on pourra donc plus tard faire des méthodes pour modifier les valeurs de déplacement pour le faire se diriger vers un point précis.

haut

Donner une perception de leur environnement aux agents

A présent que nos agents sont capables de se déplacer, il peut être utile de leur permettre de savoir où ils sont et sur quel type de terrain ils évoluent. Le comportement d'un agent  ne sera pas le même si il arrive dans une zone « forêt » ou une zone « étang ».

Dans la méthode step, on va créer une liste d'objet qui va contenir les éléments visibles par l'agent en fonction de son champ de vision.

ArrayList<Object>visibles_objects=ground_manager.find_object(this,vision);

 

On va à nouveau utiliser une fonction de l'interface « Ground_Manager », et on va l'implémenter dans la classe SIG_Manager.

haut

E. Méthode la méthode “findObject” dans le GIS_Manager

Dans un traitement SIG, pour retrouver les objets dans une certaine zone, on va utiliser une fonction d'intersection. On va ainsi récupérer toutes les géométries (et donc les objets associés à ces géométries) qui sont en intersection avec une géométrie donnée. Pour représenter le champs de vision d'un agent, on va dessiner une géométrie en forme de cercler ayant pour centre la position de l'agent et pour rayon son champs de vision.

@Override
public
ArrayList<Object> find_object(Agent a, double radius)
{
//on créé une nouvelle liste d'objet

ArrayList<Object> list =
new ArrayList<Object>();
Context
context = ContextUtils.getContext(a);
Geography
projection=(Geography)context.getProjection("Geography");
//si le rayon demandé est de 0 , on va utiliser la géométrie de l'agent (c'est à dire un point )

if
(radius == 0.0)
      {

      Geometry agent_geom = projection.getGeometry(a);
      for ( Object o : projection.getAllObjects())
            {
            if ( projection.getGeometry(o) != null &&projection.getGeometry(o).intersects(agent_geom))
                  {
                  list.add(o);
                  }
            }
      }

else /*sinon, on va faire appel à une fonction pour dessiner un cercle , puis on va récupérer les objets dont la géométrie est en intersection avec ce cercle.*/
      {
      Coordinate c = a.getcoord();
      Geometry g = createCircle(c.x,c.y,radius);
            for ( Object o : projection.getAllObjects())
            {
            if ( projection.getGeometry(o) != null && projection.getGeometry(o).intersects(g))
                  {
                  list.add(o);
                  }
            }
      }    
            return list;
      }

 

Création dune fonction permettant de créer une forme géométrique ronde représentant le champ de vision d'un agent

public static Geometry createCircle(double x, double y, final double RADIUS) {  

final int SIDES = 32;

GeometryFactory factory = new GeometryFactory();    
//on crée un tableau de 32 points

Coordinate coords[] = new Coordinate[SIDES+1];
//pour chaque point on va associer des coordonnées du cercle de centre  (x,y) on renseigne les 31 premiers points du cercle en suivant un ordre d'angle croissant pour que le tracé soit régulier

for( int i = 0; i < SIDES; i++)

{

      double angle = ((double) i / (double) SIDES) * Math.PI * 2.0;

      double dx = Math.cos( angle ) * RADIUS; //décalage x par rapport au centre

      double dy = Math.sin( angle ) * RADIUS; //décalage y par rapport au centre

coords[i] = new Coordinate( (double) x + dx, (double) y + dy ); 

}
// le dernier point doit être identique au premier pour compléter le polygone

coords[SIDES] = coords[0];
//une fois les points crées, on les relie dans un objet LinearRing    

LinearRing ring = factory.createLinearRing( coords );
//objet que l'ont transforme ensuite en polygone

Polygon polygon = factory.createPolygon( ring, null );
//pour que ce polygone soit accépté parmis les agents_parcelles,  on le transforme en multi_polygone

Polygon[] tab_p = {polygon};

Geometry mp = factory.createMultiPolygon(tab_p);
//puis on renvoie le cercle crée en tant que géometry

return mp;
      }

 

G        Attention: Dans ce cas précis, lorsque l'on cherche à récupérer la géométrie en tant que polygone, Repast Simphony provoque une erreur, il faut donc rajouter des étapes pour en faire un multipolygone. En effet, il faut adapter le type de la géométrie pour faire en sorte qu'il corresponde au type de géométrie contenu dans le shapefile.

Lors de l'exécution, la méthode step va donc faire bouger l'agent et lui envoyer tous les éléments qu'il peut voir.

G        Il faut faire attention à la façon dont le logiciel procède, en effet lors d'un parcours les éléments en intersection ne sont pas testés aléatoirement. Ainsi, si on demande à un agent de se déplacer vers le terrain avec lequel il a la plus  grande affinité, en cas de conflits ( deux terrains à porté avec la même affinité) il choisira le premier testé. Lors d'une simulation, par exemple, on peut observer que tous les agents ont tendances à se déplacer dans une même direction. Ce problème peut venir ou d'un mauvais choix dans la partie qui donnée des vecteurs de déplacement ou d'un problème de priorité dans les choix de destination.

 

haut

F. Méthode la méthode “findObject” dans le raster_Manager :

  @Override

public ArrayList<Object> find_object(Agent a, double radius)
{
      ArrayList<Object> liste = new ArrayList<Object>();
      int vision = (int) (radius / size_of_one_box);
      NdPoint emp = space.getLocation(a);      
            //on récupère la case qui contient l'agent
      GridPoint gp = new GridPoint((int)emp.getX(),(int)emp.getY());      
            //on transforme la vision de l'agent en mètre en vision en case, on effectue ensuite un parcours de toutes les cases visibles  et on récupères les objets contenus
      for (int i = gp.getX()- vision; i <= gp.getX() + vision ; i++)
      {

     for (int j=gp.getY()-vision; j <= gp.getY()+vision; j++)
            {
            if ( i>=0 && j>=0 && i<grille.getDimensions().getWidth()
                  && j < grille.getDimensions().getHeight() )
                  {
                  liste.add(matrice_terrain[i][j]);
                  liste.addAll(matrice_terrain[i][j].getliste_agent());
                  }
            }
      }
      return liste;
}

Puisque l'on ne sait pas dans quel ordre arrivent les objets vus par l'agent, on considère que tous les objets sont vus simultanément, on va donc effectuer des parcours sur la liste pour trouver les éléments qui nous intéressent et faire des choix aléatoires.

On poursuit donc la méthode step:

for ( Object o : visibles_objects )
{//si l'objet récupéré est un agent parcelle et qu'il n'est pas celui sur lequel l'agent se trouve actuellement
if
(o instanceof Field_Agent && (Field_Agent)o != agp )
      {

//si on n'a pas encore prévu de destination ou si on en trouve une avec une plus grande affinité
      if(dest==null||(((Field_Agent)o).getaffinite()> dest.getaffinite()))
      {
//on crée une liste qui va contenir toutes les parcelles de  la plus haute affinité à proximité
      dest_possible = new ArrayList<Field_Agent>();
      dest_possible.add((Field_Agent)o);
      dest = (Field_Agent)o;
      }
      else if ((((Field_Agent)o).getaffinite() == dest.getaffinite()))
      {

//si plusieurs parcelles sont à égalité, on les ajoute dans  la liste
      dest_possible.add((Field_Agent)o);
      }
            }
      }
      if (!dest_possible.isEmpty())
            {
     
//si la liste n'est pas vide, on choisi une destination au hasard  parmi celles possibles
      dest=dest_possible.get(rand.nextInt(dest_possible.size()));
            Coordinate p = dest.getcoord();
            if (objectif == null)
                  {
                  objectif = p;
                  }

 

A chaque step, on peut alors appeler une méthode qui modifie le vecteur de déplacement de l'agent pour que celui ci se dirige vers son objectif

public void move_to_aim()
      {
      Coordinate coord = this.getcoord();
     
//if the agent has a goal
      if (objectif != null)
      {

//compare its coordinate with its goal to make it go to its objective
            if ( coord.x - objectif.x > 0)
                  {
                  dep.x = - speed ;
                  }
                  else
                  {
                  dep.x = speed;
                  }
            if ( coord.y - objectif.y > 0)
                  {
                  dep.y = - speed ;
                  }
                  else
                  {
                  dep.y = speed;
                  }
                 


//watch if the agent is or not near to its goal

                                 if (Math.abs(coord.x - objectif.x) <=  speed && Math.abs(coord.y - objectif.y) <= speed)
                  {


//if is travel is achieved, he looses its goal and can own a new destination
                        objectif = null;

dep.x = rand.nextInt(11)/10*speed - 0.5*speed;

dep.y = rand.nextInt(11)/10*speed - 0.5*speed;
                  }
            }
      }

 


Mémo 18 - Auteur Q.Baduel, adaptation  06.02.13 par jlefur
retour aux mémos     retour au modèle     back to SimMasto home page   retour à la page d'accueil