Changes between Version 1 and Version 2 of CaoCourseTme8


Ignore:
Timestamp:
Apr 4, 2007, 5:46:12 PM (18 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • CaoCourseTme8

    v1 v2  
    99On souhaite implanter en langage C un algorithme de partitionnement de graphe utilisant
    1010la méthode Min-Cut, dont le principe a été présenté en cours.
     11
     12{{{
     13#!html
     14<h1>TME 8 : Partitionnement de Graphe/MinCut</h1>
     15}}}
     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
     27Pour ce qui nous concerne, on s'intéresse à 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 le signal de sortie d'une
     40porte logique Cx attaque n portes C1 à Cn, 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
     47On défini comme suit la fonction de coût du problème d'optimisation MinCut:
     48
     49{{{
     50FC = Somme  Aij * Kij
     51          i,j
     52
     53avec Kij = 1 si les cellules Ci et Cj sont de part et d'autre de la frontière
     54     Kij = 0 sinon
     55}}}     
     56
     57= B. Algorithme MinCut glouton =
     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 forcément
     61rechercher le 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
     72avec Dij = +1 si les cellules Ci et Cj sont de part et d'autre de la frontière
     73         Dij = -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 GAIN_L
     77pour les cellules appartenant à L et une liste GAIN_R pour les cellules
     78appartenant à R).
     79
     80On transfère la première cellule Ci de la liste ordonnée GAIN_L vers R,
     81on met à jour les gains des cellules adjacentes à Ci, ainsi que l'ordre des
     82listes GAIN_L et GAIN_R. La cellule Ci transférée dans R n'est pas rangée
     83dans GAIN_R, car elle n'est plus autorisée à bouger.
     84
     85On transfère la première cellule Ci de la liste ordonnée GAIN_R vers L,
     86on met à jour les gains des cellules adjacentes à Ci, ainsi que l'ordre des
     87listes GAIN_L et GAIN_R. La cellule Ci transférée dans L n'est pas rangée
     88dans GAIN_L, car elle n'est plus autorisée à bouger.
     89
     90On recommence les deux étapes ci-dessus tant que cet échange entraîne une
     91diminution de la fonction de coût.
     92
     93= C. Structures de données =
     94
     95On a besoin de deux structures de données séparées pour représenter d'une part
     96le graphe à partitionner, et d'autre part les listes GAIN_L et GAIN_R. Cette
     97seconde structure de donnée étant utilisée pour calculer les mouvements de
     98cellules à effectuer, est  appelée «gestionnaire de mouvements».
     99
     100== C.1. représentation du graphe ==
     101
     102Les noeuds du graphe sont identifiés par un index compris entre 0 et (NBNODE-1).
     103
     104La structure du graphe est représentée par un tableau NODE[ ] indexé par
     105le numéro du noeud (la taille du tableau est donc égale à NBNODE). Chaque
     106case du tableau NODE[i] contient un pointeur sur une liste de bi-pointeurs
     107représentant l'ensemble des noeuds adjacents au noeud [i]. Il y a donc un
     108bipointeur pour chaque arête attachée au noeud[i].
     109
     110Un second tableau PART[ ] est lui aussi indexé par le numéro du noeud. Chaque
     111case  PART[i] contient un entier définissant à quel sous-ensemble appartient
     112le noeud [i]: (valeur 0 pour l'ensemble L, et valeur 1 pour l'ensemble R).
     113
     114Le graphe est donc représenté par la structure suivante:
     115{{{
     116typedef struct graph_t {
     117  unsigned      NBNODE; // nombre de noeuds du graphe
     118  bip_t *       * NODE; // pointeur sur le tableau NODE[NBNODE]
     119  unsigned      * PART; // pointeur sur le tableau PART[NBNODE]
     120} graph_t
     121}}}
     122
     123== C2. représentation du gestionnaire des mouvements ==
     124
     125On appelle arité d'un noeud du graphe, le nombre d'arêtes attachées à ce
     126noeud. Le gain maximum G(i) associé au mouvement d'un noeud [i] est au plus
     127égal à l'arité du noeud [i]: Ce gain peut être positif ou négatif, mais sa
     128valeur absolue ne peut pas être supérieure au nombre des noeuds auquel il
     129est connecté.
     130Soit GMAX la valeur maximale de l'arité (en considérant tous les noeuds du graphe).
     131Pour tout noeud [i] du graphe, on a donc GMAX < G(i) < GMAX
     132
     133Pour représenter la liste ordonnée GAIN_L, on effectue une partition de
     134l'ensemble des cellules appartenant à L, en regroupant dans un même sous
     135ensemble tous les noeuds possédant  le même gain. Ce sous ensemble est
     136représenté par une liste doublement chaînée, dont chaque élément de type
     137bichain_t contient un index de noeud. On définit un tableau de pointeurs
     138GAIN_L[ ] possèdant 2 * GMAX + 1 cases, et indexé par une valeur de gain
     139comprise entre GMAX et GMAX. Chaque case correspond à une valeur de gain, et
     140contient un pointeur sur une des listes doublement chaînées définies ci-dessus.
     141
     142On fait la même chose pour représenter la liste GAIN_R.
     143
     144Au début  de l'algorithme, chaque noeud du graphe est inséré dans la  liste
     145chaînée qui correspond à sa valeur de gain. Au fur et à mesure que les noeuds
     146sont transférés, les listes se vident, car un noeud qui a subi un mouvement
     147n'est plus autorisé à  bouger. Puisque le gain des noeuds changent au cours
     148de l'algorithme, un même noeud peut être retiré d'une liste et inséré dans
     149une autre, ce qui justifie l'utilisation de listes doublement chaînées.
     150
     151Dans la structure de donnée «graphe_t»  représentant le graphe, les noeuds
     152du graphe sont identifiés par leur index. On a donc besoin d'introduire dans
     153la structure «move_manager_t» représentant le gestionnaire de mouvements,
     154un tableau de pointeurs NODES [ ], indexé par le numéro du noeud, et contenant
     155un pointeur sur la structure bichain_t représentant un  les noeuds du graphe
     156dans les listes doublement chaînées GAIN_L et GAIN_R.
     157 
     158On utilise donc les structures suivantes:
     159
     160{{{
     161typedef struct bichain_t {
     162  bichain_t     *NEXT;  // pointeur sur le noeud suivant de même gain
     163  bichain_t     *PREV;  // pointeur sur le noeud précédent de même gain
     164  unsigned      INODE;  // index du nSud
     165  int           IGAIN;  // index dans le tableau des gains
     166  unsigned  PART;   // numero de la partie 0 pour R, 1 pour L
     167} bichain_t
     168}}}
     169{{{
     170typedef struct move_manager_t {
     171  int       GMAX;           // la taille des tableaux GAIN_R et GAIN_L est égale à 2*GMAX+1
     172  bichain_t **GAIN_L;   // pointeur sur le tableau de pointeurs pour la partie gauche
     173  bichain_t **GAIN_R;   // pointeur sur le tableau de pointeurs  pour la partie droite
     174  int       LX;                 // index de la première case non vide de GAIN_L (init à -1)
     175  int       RX;                 // index de la première case non vide de GAIN_R (init à -1)
     176  bichain_t **NODES;    // pointeur sur le tableau de pointeurs sur les noeuds
     177} move_manager_t
     178}}}
     179
     180= D. Fonctions d'accès aux structures de données =
     181
     182On dispose d'un ensemble de fonctions permettant de construire et de manipuler les deux structures de données graph_t et move_manager_t.
     183
     184== D.1. graph_t * parse_graph(char *filename) ==
     185
     186Cette fonction prend pour argument un nom de fichier au format.dot
     187représentant le graphe à partitionner, et construit en mémoire la structure
     188graph_t correspondante, et renvoie un pointeur sur cette structure. Si aucune
     189partition initiale n'est définie dans le fichier .dot, les noeuds d'index
     190inférieur à NBNODE/2 sont arbitrairement rangés dans le sous-ensemble L,
     191et les noeuds d'index supérieur à NBNODE/2 sont rangés dans le sous-ensemble R.
     192
     193Un exemple de fichier au format .dot est donné ci-dessous. Les noeuds du graphe
     194sont identifiés par un index, il y a une ligne par arête, les deux sous-graphes
     195cluster0 et cluster1 définissent la partition initiale et sont optionnels.
     196
     197{{{
     198graph
     199{
     200  // les clusters sont optionnels
     201  subgraph cluster0 {0 1 2 3}
     202  subgraph cluster1 {4 5 6 7}
     203  0--4
     204  0--5
     205  0--7
     206  1--5
     207  3--7
     208  1--2
     209  5--6
     210}
     211}}}
     212
     213== D.2. void drive_graph(graph_t *p, char *filename) ==
     214
     215Cette fonction prend en paramètre un pointeur p sur une structure graph_t,
     216ainsi que le nom du fichier de sortie, et écrit sur disque un fichier au
     217format .dot, en conservant dans le fichier les informations de partition
     218(sous-ensemble L et R). Ce format est affichable sous forme graphique et
     219permet de visualiser la partition.
     220
     221== D.3. bichain_t * cons_bichain (bichain_t *prev, bichain_t *next, unsigned inode, int igain, unsigned part) ==
     222
     223Cette fonction alloue la mémoire nécessaire pour créer un élément d'une liste
     224doublement chaînée, elle initialise les différents champs de cette structure,
     225et renvoie un pointeur sur cette structure.
     226
     227== D.4. void free_bichain (bichain_t *p) ==
     228
     229Cette fonction libère la mémoire allouée lors de la construction de l'objet
     230bichain_t par la donction cons_bichain().
     231
     232== D.5. move_manager_t * cons_move_manager(graph_t *p) ==
     233
     234Cette fonction pend pour argument un pointeur sur la structure représentant
     235le graphe partitionné, et construit en mémoire la structure move_manager_t
     236correspondant à cette partition.
     237
     238== D.6. void add_node (move_manager_t *mm, bichain_t *node) ==
     239
     240Cette fonction prend pour arguments un pointeur sur la structure représentant
     241les gestionnaire de mouvements, un pointeur sur un élément de liste doublement
     242chaînée (représentant un noeud), contenant l'index du nSud dans le graphe,
     243un entier représentant l'index dans le tableau GAIN_R ou GAIN_L, et un entier
     244définissant le sous-ensemble d'appartenance (0 pour R, 1 pour L). Elle range
     245ce noeud dans la liste doublement chaînée correspondante, elle met à jour
     246le tableau NODES et les index LX ou LR. Les champs NEXT et PREV du nSud ne
     247sont pas utilisés, ils sont initialisés par la fonction add_node elle-même.
     248
     249== D.7. bichain_t * del_node(move_manager_t *mm, bichain_t *node) ==
     250
     251cette fonction prend pour arguments un pointeur sur la structure représentant
     252les gestionnaire de mouvements, et un pointeur sur un élément de liste
     253doublement chaînée (représentant un noeud). Elle retire cet élément de la
     254liste doublement chaînée, et renvoie un pointeur sur la structure bichain_t
     255pour une éventuelle libération de la mémoire.
     256
     257== D.8. int compute_gain (graph_t *g, unsigned index) ==
     258
     259Cette fonction prend pour argument un pointeur sur la structure représentant le
     260graphe, un index désignant un noeud particulier, et calcule le gain associé
     261à ce noeud, pour la partition courante.
     262
     263== D.9. int global_cost (graph_t *g) ==
     264
     265Cette fonction prend pour argument un pointeur sur la structure représentant
     266le graphe, calcule la fonction de coût globale pour la partition courante,
     267et renvoie la valeur calculée.
     268
     269= E. Travail à réaliser =
     270
     271Nous vous proposons un programme à trous. Vous allez pouvoir remplacer
     272progressivement les fichiers objets fournis par vos propres fichiers objet.
     273
     274== E.1. Construction et initialisation du gestionnaire de mouvements ==
     275
     276C'est un point important puisque que c'est la structure de base de
     277l'algorithme. Vous disposez pour vous aidez d'une fonction dump_move_manager()
     278qui affiche le contenu de la structure.
     279
     280== E.2. Ecriture de l'algorithme MinCut glouton ==
     281
     282Ecrivez la boucle réalisant les transferts de cellules tant que le transfert
     283d'une paire de cellules améliore la fonction de coût FC.
     284
     285== E.3. Exécution et évaluation ==
     286
     287Exécutez cet algorithme sur différents graphes que vous définirez
     288vous-mêmes. La partition résultante est-elle toujours la partition
     289optimale? Pourquoi? Donnez un contre-exemple.
     290
     291== E.4. Ecriture des fonctions ==
     292
     293Ecrire les différentes fonctions d'accès aux structures de données définies
     294dans la partie D, et validez ces fonctions en les intégrant peu à peu dans
     295votre programme.
     296
     297== E.5. construction du graphe en mémoire ==
     298
     299L'objectif est ici d'écrire la fonction parse_graph(), en utilisant lex et
     300yacc. Nous vous suggérons de supposer qu'il n'y a pas de clusters dans le
     301fichier, puis de les introduire. Si vous n'avez pas le temps de construire
     302le graphe, étudiez au moins la grammaire de ce petit langage.