• Aucun résultat trouvé

Propagation de contraintes et identification des nogoods

3. Tabu-NG et affectation de fréquences

3.3. Méthode Tabu-NG pour le problème AFD classique

3.3.2. Propagation de contraintes et identification des nogoods

3.3.2.1. Propagation de l’extension d’une configuration partielle consistante

Dans cette partie, nous allons détailler la procédure de propagation de contraintes. Cette procédure intervient suite à chaque extension d’une configuration partielle courante consistante. Cet algorithme aura comme mission l’élimination de toutes les valeurs inconsistantes qui résulte de la propagation de la dernière affectation ajoutée à la configuration partielle courante. Cet algorithme est responsable aussi de l’identification d’un nogood lorsqu’un deadend a lieu.

Etant donné que les instances du projet CALMA que nous traitons dans cette section portent sur des contraintes binaires et ne contiennent pas de contraintes n-aires, un algorithme de propagation de contraintes par arc-consistance suffit pour éliminer toutes les valeurs inconsistantes dans les domaines des variables libres. Plusieurs algorithmes de propagation de contraintes ont été proposés dans la littérature tels que : AC-3, AC-4, AC-6, AC-2001, etc. Parmi ces algorithmes, seul AC-4 permet d’identifier un nogood de bonne qualité en modifiant l’algorithme ; nous en donnons les raisons ci-après. Les travaux expliqués dans cette partie se basent principalement sur l’algorithme MAC -DBT de Jussien [45], l’algorithme AC-4 ainsi que sur les travaux menés sur les explications d’élimination de Jussien [139] [140] [141].

Par définition un nogood est une configuration partielle qui ne peut pas faire partie d’une solution réalisable. Après la procédure de propagation de contraintes, si le domaine d’une variable devient vide, un sous ensemble de la configuration partielle courante est considéré comme nogood. La question qui se pose est alors comment identifier les explications d’élimination ? Nous passons par des structures de données spécifiques pour marquer les éliminations de données dans les domaines de définition des variables.

Etape 1 : enregistrement des supports par contraintes

Dans le domaine de chaque variable, nous associons à chaque valeur, un ensemble noté explications d’élimination qui contient les affectations responsables de l’élimination de la valeur. Le stockage des explications d’élimination pour toutes les valeurs des variables est d’une complexité spatiale de l’ordre de O(n*n*d). Cette complexité est déterminée en associant à chaque variable d valeurs possibles donc n*d valeurs totales. A chaque association (variable, valeur) on pourra, dans le pire des cas, avoir une explication qui inclut toutes les variables du problème.

L’élimination d’une valeur a d’une variable i se fait selon une contrainte donnée cij. Cependant la cause de l’élimination de a n’est pas toujours la dernière affectation ajoutée à la configuration partielle courante mais la cause de l’élimination de tous les supports de cette valeur dans le domaine de la variable opposée j selon la contrainte cij. Pour cela, nous avons besoin de savoir pour chaque contrainte cij tous les supports de

chaque valeur de i dans le domaine de la variable j. L’algorithme de filtrage AC-4 est le seul algorithme qui permet d’utiliser une telle structure.

Un algorithme d’initialisation est nécessaire afin de remplir les structures de données nécessaires pour identifier les nogoods et pour faciliter le filtrage, ces structures sont les mêmes que celles utilisées par AC-4 [11]. L’algorithme d’initialisation des structures est donné dans Algorithme 15. Le but est de stocker pour chaque valeur a d’une variable i tous les supports et leurs nombres dans le domaine de la variable j selon une contrainte

cij. A noter que la complexité spatiale de cet algorithme est de l’ordre de O(|C|*d2) avec |C| le nombre de contraintes du problème.

Function initialisation()

1. foreach cijC do

2. foreach a in Domi do

3. foreach b in Domj do 4. if c.verify(a,b) then

5. supports[cij,a] = supports[cij,a] U {(j, b)};

6. nbSupports[(i,j),a] += 1;

7. endif;

8. endforeach; 9. endforeach;

Algorithme 15 : Initialisation et remplissage des structures

Etape 2 : enregistrement de l’explication d’élimination par valeur

Notons par la suite ElExpl la matrice qui contient l’explication d’élimination de chaque couple (variable, valeur). Par exemple, ElExpl(i, a) contiendra l’explication d’élimination de la valeur a dans le domaine de la variable i.

L’algorithme de propagation est appelé après l’extension d’une configuration partielle courante. Il est donc responsable de l’élimination des valeurs inconsistantes dans les domaines des variables libres en propageant seulement l’effet de la nouvelle affectation ajoutée à la configuration partielle. Cet algorithme est responsable aussi de l’identification d’un nogood si un deadend a lieu.

La procédure de propagation est donnée dans Algorithme 16. Suite à l’assignation de la valeur a à la variable i, le domaine de la variable i est alors restreint à la valeur a tout en désignant l’affectation (i, a) comme explication d’élimination pour toutes les autres valeurs de i, et en propageant ainsi l’effet des valeurs supprimées sur la totalité du graphe de contraintes en appelant la fonction PropagationSuppression.

Function AlgoFiltrageApresExtension (D, S, C, i, a)

1. foreach bDi and b#a do

2. ElExpl(i,b) = {(i, a)}; //L’explication est la nouvelle affectation

3. QQ

  

i,a ; //Ajout de valeurs supprimées à la liste Q

4. Di =Di\{b} ; //Supprimer la valeur b de Di

5. endforeach

6. SS

  

i,a //Extension de la configuration partielle

7. E = PropagationSuppression(Q); //Propagation de la suppression des valeurs

8. return E;

L’Algorithme 17 décrit la fonction appelée PropagationSuppression qui vise à propager les valeurs que l’on vient de supprimer dans le domaine de la nouvelle variable instanciée i. L’idée est d’établir l’arc-consistance dans la totalité du graphe suite à l’ajout de la nouvelle affectation. Si la propagation de cette nouvelle affectation vide un domaine d’une variable, cette fonction retourne le nogood responsable de cette situation.

La fonction PropagationSuppression prend chaque couple (i, a) de la liste Q, puis elle propage le retrait de la valeur a du domaine de la variable i sur chaque contrainte de

i. Pour chaque contrainte cij, pour chaque support b de a dans le domaine de la variable

j, on retranche de 1 le nombre de supports de j=b dans le domaine de la variable i. Dans

le cas où le nombre de supports de j=b dans i devient 0, on supprime b du domaine de la variable j et son explication d’élimination sera l’union des explications d’élimination de tous les supports a’ de b dans le domaine de la variable opposée i. On ajoute à Q le couple (j,b) afin de propager l’effet du retrait de b du domaine de la variable j. Dans le cas ou le domaine de la variable j devient vide, un deadend est alors atteint et un nogood est identifié qui est l’union des explications d’élimination de toutes les valeurs dans le domaine de la variable j.

Function PropagationSuppression (Q)

1. while Q not Empty do

2. (i, a) = Un couple dans Q;

3. foreach cijC do // toutes les contraintes de la variable i

4. foreach bDj and bSupports(cij, a) do

5. nbSupports((j,i), b)--; //le nbre de supports de j=b dans i

6. if nbSupports(j, i, b) == 0 then 7. Dj = Dj\{b} ; 8. ElExpl(j, b) =

) , ( sup ' ) ' , ( b C ports a ji a i ElExpl 9. QQ

 

j,b

10. if Dj== then return

j Dom a a i ElExpl ' ) ' , ( //nogood 11. endif ; 12. endforeach; 13. endforeach; 14. endwhile; 15. return NULL

Algorithme 17 : Propagation de l'effet d'une valeur supprimée d'un domaine d'une variable

Les algorithmes de propagation que nous venons de donner sont appelés régulièrement par la méthode Tabu-NG, leur complexité spatiale et temporelle ont donc une incidence directe sur les performances de la méthode. Ils doivent être implémentés avec soin.

3.3.2.2. Propagation de la réparation d’une configuration partielle

Dans cette partie, nous détaillons la procédure de propagation appliquée à la suite de la réparation d’une configuration partielle courante. Une réparation est nécessaire lorsque l’algorithme aboutit à un deadend.

La réparation d’une configuration partielle S consiste à annuler une affectation parmi l’ensemble des affectations appartenant à S. L’algorithme de propagation lancé après cette phase consiste à rendre arc-consistant le graphe de contraintes du problème en propageant l’effet de l’annulation d’une affectation.

La solution la plus simple du point de vue algorithmique, est de restaurer les domaines de toutes les variables et d’appliquer à nouveau la propagation de chaque affectation restante dans la configuration partielle courante. Cette solution est cependant très coûteuse en termes de temps. Nous avons donc préféré utiliser des structures de données spécifiques pour rendre le graphe de contraintes arc-consistant en propageant l’annulation d’une affectation. La procédure de propagation après réparation est évoquée en détail ci-dessous, elle se base sur la procédure implémentée pour l’algorithme MAC-DBT [45].

La fonction AlgoFiltrageApresReparation donnée dans l’Algorithme 18 propage l’effet de l’annulation d’une affectation (i,a) de la configuration partielle courante S.

Function AlgoFiltrageApresReparation(D, S, C, i, a, ng, Δ)

1. //Mise à jour des domaines

2. ng’ = MiseAJourDomain({(k,c)}/ (i,a)ElExpl(k,c));

3. //Annulation de l’affectation dans S

4. S=S\{(i,a)}

5. //Parfois il existe plusieurs nogoods dans la même solution, ou le nogood

6. //repéré n’est pas minimal, d’où le lancement de cette phase

7. if ng’ is not NULL then

8. (j, b) = ReparerSolution(S, Δ, ng’)

9. AlgoFiltrageApresReparation(D, S, C, j, b, ng’, Δ) 10. endif ;

11. if ng\(i,a) fait encore partie de la configuration partielle courante then

12. //La cause de l’élimination de l’affectation annulée est le reste du nogood

13. ElExpl(i,a) = ng\{(i,a)}

14. //Mise à jour du domaine de la variable i

15. Di=Di\a 16. QQ

  

i,a

17. //Propagation de l’affectation annulée

18. ng’ = PropagationSuppression(Q)

19. if ng’ is not NULL then

20. (j, b) = ReparerSolution(S, Δ, ng’)

21. AlgoFiltrageApresReparation(D, S, C, j, b, ng’, Δ) 22. endif ;

23. endif;

Algorithme 18 : Filtrage après la réparation d'une configuration partielle courante

Tout d’abord, cette fonction (Algorithme 18) met à jour les domaines en appelant la fonction MiseAJourDomain et en passant en paramètre pour cette fonction tous les couples (k,c) dont l’affectation (i,a) fait partie de son explication d’élimination.

ng’ contient un éventuel nouveau nogood malgré la mise à jour des domaines. Ensuite,

l’affectation (i,a) est enlevée de la configuration partielle courante S. Si après la mise à jour des domaines, un deadend est à nouveau détecté (ligne 7), cela signifie que la réparation faite n’est pas suffisante et on a besoin de lancer une nouvelle phase de

réparation, d’où le lancement récursif de la phase de réparation conduisant à une nouvelle réparation à faire sur un nouveau couple (j,b) (lignes 8 et 9 de l’algorithme). Le test de la ligne 11 de l’algorithme vérifie si le nogood récupéré sans l’instanciation annulée fait toujours partie de la configuration partielle courante ; si c’est le cas on interdit à la variable i qui vient d’être désaffectée de reprendre la valeur a puisqu’on sait que le couple (i,a) avec le reste du nogood existant conduit à un deadend. Le reste du

nogood est donc l’explication d’élimination de (i,a) ; a est supprimée du domaine de la

variable i et une phase de propagation du retrait d’une valeur est lancée. Suite à cette phase on peut tomber à nouveau sur un deadend qui demande une nouvelle réparation récursive sur une autre affectation (j,b).

Suite à la réparation d’une configuration partielle courante en annulant une affectation, une mise à jour des domaines est nécessaire. La fonction

AlgoFiltrageApresReparation met à jour les domaines en appelant la fonction MiseAJourDomain et en passant en paramètre pour cette fonction tous les couples dont

l’affectation annulée fait partie de leurs explications d’élimination. Ces couples sont dans la liste listeAffectation. Pour chaque couple (i, a) de la liste listeAffectation, la valeur a est réinsérée dans le domaine de la variable i (ligne 2 de l’algorithme) puis le nombre de supports dans la variable opposée est mis à jour (lignes 4, 5 et 6 ). A partir de la ligne 11, l’algorithme parcourt pour chaque couple (i, a) de la liste listeAffectation toutes les contraintes cij et vérifie si ce couple possède un support dans le domaine de la variable j ; si ce n’est pas le cas le couple (i,a) est inséré dans une liste Q (ligne 13) afin que la valeur a soit supprimée du domaine de la variable i (ligne 16).

Function MiseAJourDomain(listeAffectation)

1. foreach (i, a) listeAffectation do

2. DiDi

 

a //Mise à jour du domaine de la variable i

3. //Mise à jour des nombres de supports

4. foreach cijC do

5. foreach bDj / bsupports(cij, a) do nbsupports(j, i, b)++;

6. endforeach; 7. endforeach;

8. //Si une valeur dans le domaine d’une variable ne possède pas de support

9. //dans le domaine de la variable opposée alors cette valeur doit être supprimée

10. //et il faut propager l’effet de sa suppression

11. foreach (i,a)listeAffectation do

12. if  cij C/(i, a) n’a pas de support dans Dj pour cij then

13. QQ

  

i,a

14. endif; 15. endforeach,

16. return PropagationSuppression(Q)

Algorithme 19 : Mise à jour des domaines après la réparation de la configuration partielle courante