Dans ce TD, nous allons définir et manipuler des structures mutables et en particulier la structure dite « union - find » vue en cours. L'application proposée consiste à simuler des phénomènes de percolation.
On considère des grilles carrées formées de nxn
cellules.
Chaque cellule peut être fermée ou ouverte et on suppose qu'un fluide peut
circuler entre les cellules ouvertes adjacentes. On considère ici la
4-connexité où deux cellules sont dites adjacentes si et seulement si elles
ont un côté en commun.
On dit que deux cellules ouvertes communiquent s'il existe entre elles un
chemin passant par une suite de cellules ouvertes adjacentes.
On distingue des cellules ouvertes particulières que l'on va appeler
respectivement entrée et sortie (sur les illustrations ce sont
les cellules situées respectivement sur la ligne du haut et sur la ligne du bas
de la grille).
On veut déterminer si, pour une grille donnée, une circulation de fluide peut
s'établir entre l'entrée et la sortie, autrement dit s'il existe au moins une
cellule d'entrée et une cellule de sortie qui communiquent.
La figure ci-dessous illustre deux exemples de grilles possibles.
Sur cette figure, on a distingué :
Une question importante en physique est : étant donnée la probabilité p (uniforme) qu'une cellule soit ouverte (autrement dit la densité des cellules ouvertes), quelle est la probabilité d'observer une percolation ? Il n'existe pas de solution analytique à ce problème, mais on sait qu'il existe un seuil pour p, en dessous duquel la probabilité de percolation est proche de 0 et au dessus duquel la probabilité de percolation est proche de 1. Une expérimentation simple consiste, partant d'une grille dont toutes les cellules sont fermées, à itérer l'ouverture aléatoire de cellules, une à une, jusqu'à atteindre la percolation et à retenir le nombre de cellules alors ouvertes. Une moyenne sur un grand nombre de telles expériences donne une estimation de p.
Pour y parvenir, on notera deux problèmes informatiques :
Pour commencer, suivant la procédure habituelle,
Le fichier State.java contient la définition d'un type énuméré
(enum en java) pour coder les états possibles d'une cellule.
L'état d'une cellule sera mutable, mais les seules mutations autorisées seront
celles indiquées par les arcs orientés de ce graphe (notez que cela définit
une relation d'ordre partiel, on dira que A ≥ B
ssi il y a un arc de B vers A dans ce graphe) :
Écrivez dans la classe StateOperation une méthode :
public static boolean allowTransition(State from, State to)qui renvoie true si et seulement si la mutation de l'état from à l'état to est autorisée (c'est-à-dire qu'il y a un arc de from à to dans le graphe) et qui renvoie false sinon.
La méthode test1a de la classe Test permet de vérifier le bon fonctionnement de allowTransition. Son exécution doit afficher le résultat :
T T T T T F T T T T F F T F T F F F T T F F F F T
Lors de la mise en communication de deux composantes, d'état respectif
state1 et state2, l'état de la composante résultante peut
être défini comme le plus petit majorant de state1 et
state2 (on a défini un treillis).
Écrivez dans StateOperation une méthode :
public static State fusion(State state1, State state2)qui renvoie l'état résultant. La méthode test1b de la classe Test permet de vérifier le bon fonctionnement de fusion. Son exécution doit afficher le résultat :
BLOCKED OPEN INPUT OUTPUT PERCOLATING OPEN OPEN INPUT OUTPUT PERCOLATING INPUT INPUT INPUT PERCOLATING PERCOLATING OUTPUT OUTPUT PERCOLATING OUTPUT PERCOLATING PERCOLATING PERCOLATING PERCOLATING PERCOLATING PERCOLATING
Déposez StateOperation.java.
Complétez la classe Cell qui définit une cellule. Pour l'instant, une cellule est définie seulement par son état qui sera un champ mutable mais privé et accessible seulement par les 3 méthodes d'objet :
public State getState() public void setState(State newState) public void reset()À sa construction, une cellule est dans l'état BLOCKED. La méthode getState renvoie l'état courant de la cellule. La méthode setState doit vérifier que la transition de l'état courant vers newState est autorisée ; si oui setState réalise cette transition ; dans le cas contraire, elle doit lancer une IllegalArgumentException avec un message explicite. La méthode reset permet de remettre une cellule dans l'état BLOCKED (permettant ainsi ce qui est en général interdit par setState).
La méthode test2 de la classe Test permet de vérifier
quelques traits de cette spécification.
Important : Ce test utilise la construction assert de Java et
vous devez passer l'option -ea à la machine virtuelle (dans Eclipse,
via l'onglet VM Arguments de la configuration de lancement).
Le test est correct s'il s'affiche :
Test 2 OK.
Déposez Cell.java.
Pour simplifier, on va coder la structure d'union-find directement dans les objets de type Cell.
Ajoutez dans la classe Cell la méthode
public static void union(Cell c1, Cell c2) qui
réalise la fusion des composantes de c1 et c2.
Vous aurez certainement à ajouter des champs qui doivent être privés
et la méthode find qui peut être aussi privée car on ne l'utilisera
pas en dehors de Cell.
Après un appel à union(c1, c2) :
Les méthodes test3a, test3b et test3c de la classe Test permettent de vérifier quelques traits de cette spécification. La méthode test3d permet aussi de vérifier que la structure union-find qui est construite possède les bonnes propriétés du point de vue algorithmique.
Déposez Cell.java.
Complétez la classe Grid qui définit une grille comme un tableau de nxn cellules. Le constructeur prend la dimension n en paramètre et doit initialiser toute la structure. Une fois qu'une grille est construite, la seule opération permise depuis l'extérieur de la classe est la modification de l'état de ses cellules. En particulier, on ne doit pas pouvoir changer les dimensions du tableau ni remplacer une cellule par une autre. Vous devez utiliser au mieux les possibilités de déclaration en Java pour contraindre ce fonctionnement.
Écrivez les 3 méthodes d'objet :
public int size() public Cell getCell(int i, int j) public void reset()La méthode size renvoie la dimension de la grille. La méthode getCell renvoie la référence sur la cellule correspondante de la grille, permettant ainsi d'opérer sur cette cellule (on numérote naturellement à partir de 0). La méthode reset doit réinitialiser toutes les cellules de la grille.
La méthode test4 de la classe Test permet de vérifier le bon fonctionnement de ces méthodes en construisant 3 exemples de grilles et en affichant leur représentation graphique.
Déposez Grid.java.
Écrivez dans la classe Grid
la méthode public Cell open(int i, int j) qui doit réaliser
toutes les opérations requises lors de l'ouverture de la cellule située aux
coordonnées (i,j) dans la grille.
Cette méthode renvoie la référence à cette cellule car cela s'avère pratique
pour la suite.
L'état initial de cette cellule est donné par la méthode
getInitialOpenState de la grille qui permet notamment de définir où
sont les cellules d'entrée et de sortie.
Cette cellule est alors considérée comme formant une composante à elle seule
et il s'agit de réaliser les fusions avec les composantes des éventuelles
cellules adjacentes.
Si vous en voyez l'utilité, vous pouvez ajouter dans la classe Cell
la méthode suivante :
public void unionIfNotBlocked(Cell c) { if (c.getState() != State.BLOCKED) union(this, c); }
Les méthodes test5a et test5b de la classe Test permettent de vérifier le bon fonctionnement de ces méthodes sur quelques exemples. Les deux appels à test5b qui sont donnés en exemple, produisent les illustrations de l'introduction.
Déposez Grid.java.
Le déroulement d'une expérience va consister à ouvrir une à une et aléatoirement les cellules de la grille, jusqu'à atteindre un état de percolation. Pour de grandes grilles, il ne saurait être question d'utiliser une boucle du genre de celle de test5b, pour écrire :
réinitialiser la grille tant que l'on n'a pas réalisé la percolation tirer aléatoirement une cellule si cette cellule est fermée, alors l'ouvrir ...Même si un générateur pseudo-aléatoire congruentiel bien choisi permet de s'assurer de la terminaison de cette boucle, cela peut prendre un temps très long, le nombre de cellules testées pouvant être de l'ordre du carré du nombre de cellules dans la grille.
Il est préférable d'utiliser une structure auxiliaire référençant l'ensemble des cellules et d'écrire :
réinitialiser la grille effectuer une permutation aléatoire de la structure auxiliaire tant que l'on n'a pas réalisé la percolation prendre la cellule suivante dans l'ordre donné par la structure auxiliaire cette cellule est nécessairement fermée, donc l'ouvrir ...La forme la plus légère est d'utiliser un tableau contenant les entiers de 0 à n2-1 où chaque entier k code la position dans la grille qui est définie par i = k % n et j = k / n.
Écrivez un programme (une classe avec la méthode main) qui permet
de mesurer expérimentalement une estimation du seuil de percolation pour des
grilles relativement grandes, par exemple 1000x1000 (si vous voulez visualiser
de telles grilles, il faut modifier la constante MAXSIZE dans
DisplayPanel).
On doit obtenir une bonne estimation, de l'ordre de 0.593, en quelques
secondes.
Déposez Percolation.java.