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êmeSOMMAIRE :
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)
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.
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.
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);
}
}
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.
@Override
public void move_Object(Agent agent,Coordinate dep)
{
//on commence par récupérer le contexte dans lequel on travailleContext 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 );
}
@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.
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.
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 d’une 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 pointsCoordinate 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égulierfor( 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 polygonecoords[SIDES] = coords[0];
//une fois les points crées, on les relie dans un objet LinearRingLinearRing ring = factory.createLinearRing( coords );
//objet que l'ont transforme ensuite en polygonePolygon polygon = factory.createPolygon( ring, null );
//pour que ce polygone soit accépté parmis les agents_parcelles, on le transforme en multi_polygonePolygon[] tab_p = {polygon};
Geometry mp = factory.createMultiPolygon(tab_p);
//puis on renvoie le cercle crée en tant que géometryreturn 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.
@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 goalif (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;
}
}
}