Réétiquetages de graphes dynamiques avec JBotSim 1

publicité
Réétiquetages de graphes dynamiques avec JBotSim
Université de Bordeaux
25 novembre 2014
1
Installation de JBotSim
Téléchargez la dernière version de
JBotSim
(wget
http://jbotsim.sf.net/jbotsim.jar).
Si vous utilisez Eclipse, créez un nouveau projet Java (choisissez un nom puis cliquez sur
propriétés, rendez-vous dans
Java Build Path > Librairies,
et ajouter
jbotsim.jar
Finish).
Dans les
comme JAR externe.
Si vous n'utilisez un autre IDE, il doit avoir des fonctionalités équivalentes pour ajouter une archive jar dans
le classpath.
Si vous n'utilisez pas d'IDE, vous pourrez compiler votre programme en tapant
et l'exécuter en tapant
2
java -cp .:jbotsim.jar MaClasse.
javac -cp jbotsim.jar MaClasse.java
Exemple de programme basique
Créez une classe principale (appelons-la
MainClass)
dont la méthode
main()
a le code suivant :
Topology tp = new Topology();
new JViewer(tp);
Ce programme créé une nouvelle topologie (objet qui gère les sommets et les arêtes), et la passe comme argument
à un visualisateur dont le but est de la dessiner tout en permettant à l'utilisateur d'interagir directement avec elle.
En particulier, vous pouvez ajouter des sommets (clique gauche), les supprimer (clique droit), ou encore les déplacer
(glisser/déposer). Lancez votre programme et familiarisez-vous quelques instants avec ces possibilités d'interaction.
3
Objectifs du TD
L'objectif du TD est de créer un support d'exécution pour algorithmes à base de réétiquetages de graphes, fonc-
tionnant en contexte hautement dynamique, c'est à dire en considérant que les changements de connexité dans le
graphe sont très fréquents et tout à fait normaux. Comme vu en cours, les réétiquetages permettent d'abstraire la
communication entre voisins : au lieu de spécier les messages que les n÷uds s'envoient, on spécie directement les
changements d'états résultant d'une interaction.
Nous allons tout d'abord créer un ordonnanceur (appelé adversaire dans le cours) pour sélectionner les interactions
au fur et à mesure. L'ordonnanceur que nous allons créer est un processus centralisé, qui pioche au hasard les arêtes
sur lesquelles l'algorithme travaillera. Nous pourrions tout aussi bien (ou peut-être juste un peu moins bien) utiliser
un ordonnanceur distribué, mais ce n'est pas l'objet de cette séance.
Une fois l'ordonnanceur créé, nous l'utiliserons pour tester quelques algorithmes à base de réétiquetages de
graphes dynamiques, parmi lesquels un algorithme de propagation d'information, trois algorithmes de comptage
et un algorithme de maintien de forêt couvrante.
4
Création d'un ordonnanceur
Commencez par recopier le squelette d'ordonnanceur ci-dessous. Lors de la construction, cet ordonnanceur prend
une topologie en paramètre et s'en copie la référence pour un usage ultérieur. Il souscrit ensuite aux évènements de
l'horloge globale de JBotSim (Clock), ce qui aura pour conséquence l'invocation de sa méthode
onClock() à intervalles
réguliers (ici, toutes les 10 unités de temps, une unité valant 10ms par défaut).
→ Travail
à faire : remplir la méthode
onClock()
de sorte à sélectionner une arête aléatoire parmi toutes les arêtes de
la topologie (tp.getLinks()). À ce stade, nous nous bornerons à mettre cette arête en gras (par exemple
setWidth(3)
pour 3 pixels de large). Pensez aussi à remettre l'épaisseur de la dernière arête sélectionnée à 1. Enn, n'oubliez pas
1
public class Scheduler implements ClockListener{
Topology tp;
public Scheduler(Topology tp){
this.tp = tp;
Clock.addClockListener(this, 10);
}
public void onClock() {
}
}
// TODO : remplir cette fonction pour selectionner une interaction au hasard
qu'il est possible que le graphe soit sans arête !
Testez votre programme. Si les arêtes clignotent comme prévu et que cela vous convainc que l'ordonnanceur
fonctionne, vous pouvez passer à l'exercice suivant.
5
Algorithme 1 : propagation d'information
Notre premier algorithme est extrêmement basique : si un n÷ud informé (état
informé (état
N),
il lui transmet l'information (réétiquetage du second n÷ud en
I).
I)
interagit avec un n÷ud non-
Ce n÷ud pourra ensuite, à son
tour, la transmettre aux autres n÷uds.
Algorithm 1 Propagation d'une information dans le réseau
Etats initiaux :
I
pour le sommet source ;
Règle de réétiquetage :
I
N
N
pour tous les autres.
I
I
5.1 Implémentation générique des algorithmes
An de ne pas réinventer la roue pour chaque algorithme que l'on codera, nous allons dénir une
interface
générique qui permettra à l'ordonnanceur de manipuler un algorithme. Chaque algorithme devra implémenter cette
interface en en redénissant les méthodes.
Algorithm 2 L'interface
Algorithm
import jbotsim.Link;
import jbotsim.Node;
public interface Algorithm {
public void setDefaultState(Node node); // Initialise les noeuds ``normaux''
public void setDistinguishedState(Node node); // Initialise le noeud ``distingue'' (si applicable)
public void applyNormalRule(Link link); // Realise l'interaction sur l'arete selectionnee.
}
Via cet interface, notre algorithme pourra dénir l'état à donner aux n÷uds par défaut, par exemple, pour l'Algorithme 1, vous redénirez la méthode
setDefaultState(Node node), qui consistera en l'instruction node.setState("N").
La seconde méthode est utilisée pour aecter un état spécial à l'un des n÷uds, par exemple ici, l'état d'émetteur
initial représenté par l'étiquette
"I".
La troisième méthode encapsulera l'exécution de la règle à proprement parler,
par exemple ici, tester si l'une des extrémités est à
"N"
et l'autre à
"I",
et si tel est le cas, modier le
"N"
en
"I".
Reste maintenant à modier l'ordonnanceur pour qu'il invoque ces trois méthodes au bon moment.
Remarque sur l'usage des couleurs :
A la place des étiquettes "N" et "I", vous pouvez utiliser la coloration
des n÷uds pour indiquer leur état. Cela se fait en utilisant
setColor(),
forme de chaîne de caractère, par exemple blue, green, etc.
2
avec pour argument le nom de la couleur sous
5.2 Modication de l'ordonnanceur
Jusqu'à présent nous n'avions qu'un argument au constructeur de la classe
Scheduler.
En plus de la topologie,
nous voulons maintenant recevoir une référence vers l'algorithme à exécuter. An d'être générique, nous allons
donc recevoir un argument de type
Algorithm
et conserver sa référence dans une variable globale
algo
pour un usage
ultérieur (comme pour la topologie). Nous ajouterons également à la n du constructeur le bloc d'instruction suivant :
Node model = new Node(); // Declare un noeud (qui ne sera PAS ajoute a la topologie)
algo.setDefaultState(model); // Donner l'etat par defaut a ce noeud
Node.setModel("default", model); // Dire a JBotSim que ce noeud servira de modele pour les nouveaux noeuds
Ainsi, tout noeud créé pendant l'exécution (par exemple, lorsque vous faites un clique gauche sur la topologie)
recevra l'état de ce noeud ctif, en l'occurrence, l'état par défaut de l'algorithme considéré. Prenez quelques secondes
pour bien comprendre l'enchainement de ces appels.
Nous voulons maintenant permettre à l'utilisateur de désigner le noeud émetteur, celui qui a l'information ini-
JBotSim
tialement.
permet à l'utilisateur de sélectionner un n÷ud en cliquant dessus avec la molette (clique
Scheduler implémente l'interface SelectionListener
tp.addSelectionListener(this) dans le constructeur. Il ne restera plus qu'à implémenter la méthode
nodeSelected() an de récupérer la référence du noeud cliqué pour lui aecter l'état distingué de l'algorithme
(algo.setDistinguishedState()). Vériez que ces initialisations fonctionnent avant de passer à la suite.
milieu). Pour récupérer cet évènement, il faut que notre classe
et rajouter
On y est presque, encore un petit eort ! Nous sommes en train de construire des briques génériques qui nous
feront ensuite gagner beaucoup de temps. Jusqu'à présent, nous avons fait en sorte que les nouveaux noeuds créés
recoivent l'état par défaut de l'algorithme (quel que soit cet algorithme !) et que les noeuds cliqués avec la molette
en recoivent l'état distingué. Il ne nous reste plus qu'à déclencher la règle de réétiquetage à chaque fois qu'une arête
est sélectionnée. Je vous laisse deviner quelle ligne il faut rajouter et où il faut la mettre.
5.3 Implémenter un algorithme.
Grâce à la généricité de notre code, l'implémentation d'un algorithme particulier devient une tâche facile. Créez
une classe
→
AlgoPropagation
qui implémente l'interface
Algorithm
et remplissez comme il faut ses trois méthodes.
Indice : on peut récupérer les références vers les deux n÷uds d'un lien à l'aide de la méthode
Link. Cette méthode renvoie une liste qui n'a
endpoints().get(0), et l'autre via endpoints().get(1).
la classe
endpoints()
de
que deux éléments. L'un des n÷uds sera donc accédé via
Faîtes fonctionner votre algorithme et vériez bien que tout fonctionne comme vous l'attendiez. Bouger les n÷uds
pendant l'exécution en rééchissant à la condition nécessaire pour que tous les n÷uds recoivent l'information.
6
Algorithmes de comptage
On veut maintenant que les n÷uds parviennent à se compter mutuellement. On s'intéressera d'abord au cas où
un n÷ud désigné se charge de compter tous les autres, puis on regardera des solutions de comptage où tous les n÷uds
démarrent avec le même état (initialisation uniforme).
→ Travail
à faire : implémenter les trois algorithmes de comptage suivants. Pour chaque algorithme, on s'interrogera,
lors de l'exécution, sur les conditions nécessaires à son succès en terme de propriétés temporelles sur la topologie
(existence de trajets, etc.).
Algorithm 3 Comptage du nombre de sommets (avec compteur distingué)
Etats initiaux :
C,1
pour le sommet source ;
Règle de réétiquetage :
C, i
N
N
pour tous les autres.
C, i + 1
F
Par exemple ici, quelle propriété minimale doit avoir le graphe dynamique pour permettre le succès de cet
algorithme, 1) en supposant un choix heureux de compteur ? ou 2) en supposant n'importe quel choix de compteur ?
Algorithm 4 Comptage du nombre de sommets (avec compteurs fusionnants)
Etats initiaux :
1
pour tous les sommets.
Règle de réétiquetage :
i 6= 0
j 6= 0
i+j
0
3
Donner une condition nécessaire au succès de cet algorithme. Existe-t-il une condition susante ? Si vous deviez
choisir entre cet algorithme et le précédent, lequel utiliseriez-vous ?
Algorithm 5 Comptage du nombre de sommets (avec compteurs fusionnants et circulants)
Etats initiaux :
1
Règle de fusion :
pour tous les sommets.
i 6= 0
Règle de circulation :
j 6= 0
i+j
i 6= 0
0
0
0
i
De manière générale, lorsque qu'un algorithme est composé de plusieurs règles, on choisira le comportement
suivant (cette stratégie vaudra également pour le dernier algorithme du TD : celui du maintien d'une forêt d'arbres
couvrants). Une fois l'arête sélectionnée par l'ordonnanceur, on tente d'abord d'exécuter la première règle de la liste
(p.ex. ici, la règle de fusion). Si cette règle est applicable, alors elle est appliquée et l'ordonnanceur reprend la main.
Sinon, on tente d'appliquer la seconde règle sur cette même arête, etc.
7
Règles de réparation (exemple de la forêt couvrante)
Nous considérons maintenant l'exemple d'un algorithme capable de réagir à la disparition d'une arête inci-
dente. L'algorithme consiste à maintenir une forêt d'arbres couvrants qui supporte n'importe quel type d'événement
ajout ou suppression de sommets ou d'arêtes et n'importe quelle fréquence de ces événements. L'objectif est de
rester cohérent (i.e. pas de cycle et une seule racine par arbre) quitte à mettre plus longtemps à converger vers un
état optimal (un arbre par composante connexe). Cet algorithme est constitué de trois règles de réétiquetage, deux
desquelles sont du genre habituel :
une règle de fusion, s'appliquant entre deux racines d'arbres et ayant pour eet de fusionner les deux arbres
sur l'arête correspondante. Cette fusion crée également une orientation sur l'arête pour indiquer la relation
parent/enfant dans cette nouvelle partie de l'arbre. (On représentera cette orientation par une référence du
noeud parent sur le noeud enfant.)
une règle de circulation, consistant à faire circuler une racine au sein de son propre arbre, en inversant la
relation parent/enfant locale à chaque mouvement.
Ces règles sont d'un genre habituel dans le sens où elles impliquent deux sommets voisins suite à une sélection d'arête.
On ajoute maintenant une troisième règle qui ne dépend pas de l'ordonnanceur. Cette règle spécie un traitement
à eectuer immédiatement après qu'une arête ait disparue, sur les sommets impliqués. Au niveau sémantique, un
sommet qui perd l'arête vers son parent, se reconvertit instantanément en racine.
Algorithm 6 Maintien d'une forêt d'arbres couvrants
Etats initiaux :
R
Règle de fusion :
pour tous les sommets.
R
Règle de circulation :
R
R
N
Règle de réparation :
R
N
×
N
N
R
R
ConnectivityListener. Concrètement, l'algorithme se mettra
addConnectivityListener sur la topologie. Le code corlinkRemoved(). Il consistera à tester si l'arête perdue menait
La troisième règle sera codée en utilisant l'interface
à l'écoute de l'apparition/disparition d'arêtes en invoquant
respondant à la troisième règle sera alors inséré dans
localement vers son parent, et si oui, à régénérer une racine. Encore une fois, n'hésitez pas à vous référer à la javadoc
ou à m'appeler si vous avez des questions.
→
Note : En guise d'arête orientée, vous pouvez simplement mémoriser la référence du n÷ud parent sur le n÷ud
enfant. JBotSim ne permet pas de dessiner une arête orientée (à ce jour), mais vous pouvez toutefois épaissir les
arêtes qui font partie de l'arbre pour les distinguer des autres, ce qui combiné à une couleur distincte pour la racine,
permet de suivre l'exécution de l'algorithme. Il faudra pour y voir plus clair désactiver l'épaississement des arêtes
sélectionnées par l'ordonnanceur.
4
Téléchargement