Changes between Initial Version and Version 1 of 2009/CaoCourseTme8


Ignore:
Timestamp:
Jan 11, 2010, 12:10:58 AM (15 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • 2009/CaoCourseTme8

    v1 v1  
     1{{{
     2#!html
     3<h1>TME 8 : Méthode de partitionnement de graphe : Min-Cut</h1>
     4}}}
     5[[PageOutline]]
     6
     7= Objectif =
     8
     9On souhaite implanter en langage C un algorithme de partitionnement de graphe utilisant
     10la méthode Min-Cut, dont le principe a été présenté en cours. Cet algorithme est fortement
     11inspiré de la méthode publiée par Fiduccia et Mattheyses en 1982.
     12Cet algorithme est souvent utilisé en CAO micro-électronique pour résoudre des problèmes de
     13placement : Les noeuds du graphe représentent des cellules précaractérisèes à placer dans un plan,
     14et l'existence d'une arête entre deux noeuds i et j représente l'existence d'une connection entre
     15les deux deux cellules i et j.
     16
     17= A) Introduction =
     18
     19Soit un graphe G(N, H), où N est l'ensemble des noeuds, et H l'ensemble des
     20arêtes. Une bipartition de G est formée de 2 sous-ensembles L (left) et R
     21(right) de l'ensemble des noeuds N, dont l'intersection est vide, et l'union
     22est égale à N.
     23
     24En supposant que le nombre de noeud est pair, on dit qu'une bipartition est
     25équilibrée si |L| = |R| = N/2
     26
     27Dans le contexte du placement de cellules, on part d'une net-list de cellules
     28précaractérisées, et on cherche à partitionner l'ensemble des cellules
     29en deux sous-ensembles L et R tels que le nombre de signaux traversant la
     30frontière entre les deux sous-ensembles soit minimal.
     31
     32Nous allons raisonner sur un graphe non-orienté, défini de la façon suivante:
     33
     34Les noeuds Ci représentent les cellules de la net-list `(0 < i < N-1)`.
     35Il existe une arête entre deux cellules Ci et Cj chaque fois qu'il existe au
     36moins un signal connectant un port de la cellule Ci à un port de la cellule Cj.
     37
     38Attention: avec cette définition, à un seul signal de la net-list peuvent
     39correspondre plusieurs arêtes dans le graphe: si la sortie d'une
     40cellule attaque n autres cellules, on a n arêtes dans le graphe.
     41
     42On appelle «matrice d'adjacence» {Aij} la matrice carrée N * N qui représente
     43la structure du graphe:
     44 * Aij = 1 si il existe une arête dans le graphe entre les noeuds Ci et Cj
     45 * Aij = 0 sinon
     46
     47= B) Algorithme !MinCut glouton =
     48
     49On défini comme suit la fonction de coût du problème d'optimisation !MinCut,
     50dont la valeur dépend du nombre d'arcs qui traversent la frontière :
     51{{{
     52FC = 1/2 Somme  Aij * Kij
     53          i,j
     54
     55Kij = 1 si les cellules Ci et Cj sont de part et d'autre de la frontière
     56Kij = 0 sinon
     57}}}     
     58
     59On part d'une bi-partition équilibrée initiale arbitraire en deux ensembles
     60L et R, et on cherche à optimiser la fonction de coût FC, sans garantie d'atteindre
     61le minimum absolu, mais avec un temps de calcul très court.
     62
     63L'idée de base de l'algorithme est de pré-calculer, pour chaque cellule Ci
     64un gain G(i) représentant la variation de la fonction de coût FC lorsqu'on
     65déplace la cellule Ci d'un côté de la frontière à l'autre (sans déplacer
     66aucune autre cellule).
     67
     68Le gain G(i) est défini par:
     69{{{
     70G(i) = Somme  Aij * Dij
     71         j
     72Dij = +1 si les cellules Ci et Cj sont de part et d'autre de la frontière
     73Dij = -1  sinon
     74}}}
     75Après avoir calculé le gain G(i) pour toutes les cellules Ci, on classe les
     76cellules dans deux listes ordonnées par gain décroissant (une liste ordonnée GAIN_L
     77pour les cellules appartenant à L et une liste ordonnée GAIN_R pour les cellules
     78appartenant à R).
     79
     80Un mouvement élémentaire consiste à échanger deux cellules i et j :
     81 * une cellule i appartenant à l'ensemble R passe dans l'ensemble L
     82 * une cellule j appartenant à l'ensemble L passe dans l'ensemble R
     83
     84La variation de la fonction de coût résultant du mouvement des cellules i et j
     85peut être calculée de la façon suivante:
     86{{{
     87DFC(i<->j) = G(i) + G(j) - 2 * Aij
     88}}}
     89
     90L'algorithme d'optimisation proposé est "glouton", car il n'y a pas de retour en arrière:
     91Lorsqu'un mouvement a été accepté, les deux cellules déplacées i et j ne sont plus autorisées à bouger.
     92
     93On considère la cellule i appartenant à R possédant le gain G(i) maximum, et la cellule j appartenant à L possédant le gain G(j) maximum.
     94On calcule la variation de la fonction de coût associé à l'échange i <-> j, et on accepte le mouvement
     95tant que celui-ci n'entraîne pas une augmentation de la fonction de coût.
     96
     97= C) Structures de données =
     98
     99On a besoin de deux structures de données séparées pour représenter d'une part
     100le graphe à partitionner, et d'autre part les listes GAIN_L et GAIN_R. Cette
     101seconde structure de donnée étant utilisée pour calculer les mouvements de
     102cellules à effectuer, est  appelée «gestionnaire de mouvements».
     103
     104== C1) représentation du graphe ==
     105
     106Les noeuds du graphe sont identifiés par un index compris entre 0 et (NBNODE-1).
     107
     108La structure du graphe est représentée par un tableau NODE[ ] indexé par
     109le numéro du noeud (la taille du tableau est donc égale à NBNODE). Chaque
     110case du tableau NODE[i] contient un pointeur sur une liste de bi-pointeurs
     111représentant l'ensemble des noeuds adjacents au noeud [i]. Il y a donc un
     112bipointeur pour chaque arête attachée au noeud[i].
     113
     114Un second tableau PART[ ] est lui aussi indexé par le numéro du noeud. Chaque
     115case  PART[i] contient un entier définissant à quel sous-ensemble appartient
     116le noeud [i]: (valeur 0 pour l'ensemble R, et valeur 1 pour l'ensemble L).
     117
     118Le graphe est donc représenté par la structure suivante:
     119{{{
     120typedef struct graph_t {
     121  unsigned      NBNODE; // nombre de noeuds du graphe
     122  bip_t *       * NODE; // pointeur sur le tableau NODE[NBNODE]
     123  unsigned      * PART; // pointeur sur le tableau PART[NBNODE]
     124} graph_t
     125}}}
     126
     127[[Image(graph.png, nolink)]]
     128
     129== C2) représentation du gestionnaire des mouvements ==
     130
     131On appelle arité d'un noeud du graphe, le nombre d'arêtes attachées à ce
     132noeud. Le gain maximum G(i) associé au mouvement d'un noeud [i] est au plus
     133égal à l'arité du noeud [i]: Ce gain peut être positif ou négatif, mais sa
     134valeur absolue ne peut pas être supérieure au nombre des noeuds auquel il
     135est connecté.
     136Soit GMAX la valeur maximale de l'arité (en considérant tous les noeuds du graphe).
     137Pour tout noeud [i] du graphe, on a donc -GMAX < G(i) < GMAX
     138
     139Pour représenter la liste ordonnée GAIN_L, on effectue une partition de
     140l'ensemble des cellules appartenant à L, en regroupant dans un même sous
     141ensemble tous les noeuds possédant  le même gain. Ce sous ensemble est
     142représenté par une liste doublement chaînée, dont chaque élément de type
     143bichain_t contient un index de noeud. On définit un tableau de pointeurs
     144GAIN_L[ ] possèdant 2 * GMAX + 1 cases, et indexé par une valeur de gain
     145comprise entre -GMAX et GMAX. Chaque case correspond à une valeur de gain, et
     146contient un pointeur sur une des listes doublement chaînées définies ci-dessus.
     147 * Le gain des cellules rattachées à la case d'index 0 est égal à -GMAX
     148 * Le gain des cellules rattachées à la case d'index i est égal à i - GMAX
     149 * Le gain des cellules rattachées à la case d'index 2*GMAX est égal à GMAX
     150
     151[[Image(gestionnaire.png, nolink)]]
     152
     153On fait la même chose pour représenter la liste GAIN_R.
     154
     155Au début  de l'algorithme, chaque noeud du graphe est inséré dans la  liste
     156chaînée qui correspond à sa valeur de gain. Au fur et à mesure que les noeuds
     157sont transférés, les listes se vident, car un noeud qui a subi un mouvement
     158n'est plus autorisé à  bouger. Puisque le gain des noeuds change au cours
     159de l'algorithme, un même noeud peut être retiré d'une liste et inséré dans
     160une autre, ce qui justifie l'utilisation de listes doublement chaînées.
     161
     162Dans la structure de donnée «graphe_t»  représentant le graphe, les noeuds
     163du graphe sont identifiés par leur index. On a donc besoin d'introduire dans
     164la structure «move_manager_t» représentant le gestionnaire de mouvements,
     165un moyen d'obtenir le pointeur sur la structure bichain_t représentant
     166un noeud du graphe lorsqu'on connait son index. On construit donc un
     167tableau de pointeurs NODES [ ], indexé par le numéro du noeud.
     168 
     169On utilise donc les structures suivantes:
     170
     171{{{
     172typedef struct bichain_t {
     173  bichain_t     *NEXT;  // pointeur sur le noeud suivant de même gain
     174  bichain_t     *PREV;  // pointeur sur le noeud précédent de même gain
     175  unsigned      INODE;  // index du noeud
     176  int           IGAIN;  // index dans le tableau des gains
     177  unsigned  PART;       // numero de la partie 0 pour R, 1 pour L
     178} bichain_t
     179}}}
     180{{{
     181typedef struct move_manager_t {
     182  int       GMAX;           // la taille des tableaux GAIN_R et GAIN_L est égale à 2*GMAX+1
     183  bichain_t **GAIN_L;   // pointeur sur le tableau de pointeurs pour la partie gauche
     184  bichain_t **GAIN_R;   // pointeur sur le tableau de pointeurs  pour la partie droite
     185  int       LX;         // index de la première case non vide de GAIN_L (init à -1)
     186  int       RX;         // index de la première case non vide de GAIN_R (init à -1)
     187  bichain_t **NODES;    // pointeur sur le tableau de pointeurs sur les noeuds
     188} move_manager_t
     189}}}
     190
     191= D) Fonctions d'accès aux structures de données =
     192
     193On dispose d'un ensemble de fonctions permettant de construire et de manipuler les deux structures de données graph_t et move_manager_t.
     194
     195 '''graph_t * parse_graph(char *filename)'''::
     196        Cette fonction prend pour argument un nom de fichier au format .dot
     197        représentant le graphe à partitionner, construit en mémoire la structure
     198        graph_t correspondante, et renvoie un pointeur sur cette structure. Si aucune
     199        partition initiale n'est définie dans le fichier .dot, les noeuds d'index
     200        inférieur à NBNODE/2 sont arbitrairement rangés dans le sous-ensemble L,
     201        et les noeuds d'index supérieur à NBNODE/2 sont rangés dans le sous-ensemble R.
     202
     203        Un exemple de fichier au format .dot est donné ci-dessous. Les noeuds du graphe
     204        sont identifiés par un index, il y a une ligne par arête, les deux sous-graphes
     205        cluster0 et cluster1 définissent la partition initiale et sont optionnels.
     206
     207{{{
     208graph
     209{
     210  // la définition des clusters est optionnelle
     211  subgraph cluster0 {0 1 2 3}
     212  subgraph cluster1 {4 5 6 7}
     213  0--4
     214  0--5
     215  0--7
     216  1--5
     217  3--7
     218  1--2
     219  5--6
     220}
     221}}}
     222
     223 '''void drive_graph(graph_t *p, char *filename)'''::
     224        Cette fonction prend pour argument un pointeur p sur une structure graph_t,
     225        ainsi que le nom du fichier de sortie, et écrit sur disque un fichier au
     226        format .dot, en conservant dans le fichier les informations de partition
     227        (sous-ensemble L et R). Ce format est affichable sous forme graphique et
     228        permet de visualiser la partition.
     229
     230 '''bichain_t * cons_bichain (bichain_t *prev, bichain_t *next, unsigned inode, int igain, unsigned part)'''::
     231        Cette fonction alloue la mémoire nécessaire pour créer un objet de type bichain_t appartenant à une liste
     232        doublement chaînée, elle initialise les différents champs de cette structure,
     233        et renvoie un pointeur sur cette structure.
     234
     235 '''void free_bichain (bichain_t *p)'''::
     236        Cette fonction libère la mémoire allouée lors de la construction de l'objet
     237        bichain_t par la donction cons_bichain().
     238
     239 '''move_manager_t * cons_move_manager(graph_t *p)'''::
     240        Cette fonction pend pour argument un pointeur sur la structure représentant
     241        le graphe partitionné, et construit en mémoire la structure move_manager_t
     242        correspondant à cette partition.
     243
     244 '''void add_node (move_manager_t *mm, bichain_t *node)'''::
     245        Cette fonction prend pour arguments un pointeur sur la structure représentant
     246        le gestionnaire de mouvements et un pointeur sur un élément de type bichain_t
     247        représentant un noeud) dont les champs INODE, IGAIN et PART sont définis. Elle range
     248        ce noeud dans la liste doublement chaînée correspondante, elle met à jour
     249        le tableau NODES et les index LX ou LR. Les champs NEXT et PREV sont initialisés par la fonction add_node().
     250
     251 '''bichain_t * del_node(move_manager_t *mm, bichain_t *node)'''::
     252        cette fonction prend pour arguments un pointeur sur la structure représentant
     253        le gestionnaire de mouvements, et un pointeur sur un élément de liste
     254        doublement chaînée (représentant un noeud). Elle retire cet élément de la
     255        liste doublement chaînée, met à jour le chaînage et les index LX et RX, enfin renvoie un pointeur sur la structure bichain_t
     256        pour une éventuelle libération de la mémoire.
     257
     258 '''int compute_gain (graph_t *g, unsigned index)'''::
     259        Cette fonction prend pour argument un pointeur sur la structure représentant le
     260        graphe, un index désignant un noeud particulier, et calcule le gain associé
     261        à ce noeud, pour la partition courante.
     262
     263 '''int global_cost (graph_t *g)'''::
     264        Cette fonction prend pour argument un pointeur sur la structure représentant
     265        le graphe, calcule la fonction de coût globale pour la partition courante,
     266        et renvoie la valeur calculée.
     267
     268= E) Travail à réaliser =
     269
     270Créez un répertoire de travail '''tme8''', et copiez dans ce répertoire tous les fichiers et répertoires qui se trouvent dans
     271{{{
     272/users/enseig/encadr/cao/tme8
     273}}}
     274
     275Dans un premier temps vous devez utiliser les fonctions qui vous sont fournies pour écrire le programme main()
     276qui effectue le partitionnement d'un graphe quelconque défini dans un fichier au format .dot.
     277Dans un deuxième temps vous devrez programmer vous même les différentes fonctions et remplacer
     278progressivement les fichiers objets fournis par vos propres fichiers objet.
     279
     280'''1. Construction et initialisation du gestionnaire de mouvements'''
     281       Ecrivez le programme ''main()'' qui construit en mémoire les deux structures de données
     282       ''graph_t'' et ''move_manager_t''. Vous disposez pour vous aidez d'une fonction ''dump_move_manager()''
     283       qui affiche le contenu de la structure.
     284
     285'''2. Exécution et évaluation'''
     286       Complétez le programme main() en ajoutant la fonction ''mincut(graph_t *gr, move_manager_t *mm)''.
     287       On utilisera la fonction ''global_cost(graph_t *g)'' pour calculer et afficher le coût de la partition avant et après optimisation.
     288       Exécutez cet algorithme sur différents graphes que vous définirez vous-mêmes.
     289       La partition résultante est-elle toujours la partition optimale? Pourquoi? Donnez un contre-exemple.
     290
     291'''3. Ecriture de la fonction mincut() '''
     292       Ecrire en langage C la fonction mincut() qui réalise l'algorithme !MinCut glouton.
     293       Cette fonction contient la boucle réalisant les transferts de cellules tant que le transfert fait décroitre
     294       la fonction de coût. A chaque tour dans cette boucle, une cellule du sous-ensemble L passe dans R,
     295       et une cellule du sous-ensemble R passe dans L. Les deux cellules i et j qui on bougé doivent être retirées
     296       de la structure de donnée ''move_manager'', et les gains de toutes les cellules adjacentes à i et j doivent
     297       être mis à jour.
     298
     299'''4. Ecriture des autres fonctions d'accès'''
     300       Ecrire les différentes fonctions d'accès aux structures de données définies
     301       dans la partie D, et validez ces fonctions en les intégrant peu à peu dans
     302       votre programme.
     303
     304'''5. construction du graphe en mémoire'''
     305       L'objectif est ici d'écrire la fonction ''parse_graph()'', en utilisant lex et
     306       yacc. Nous vous suggérons de commencer par faire l'hypothèse qu'il n'y a pas de clusters
     307       dans le fichier, puis d'introduire les clusters dans la grammaire de ce petit langage.
     308
     309= Compte-Rendu =
     310
     311Il ne vous est pas demandé de compte-rendu écrit pour ce TME, mais vous devrez faire une démonstration
     312de votre code au début du prochain TME.