A
la recherche de la solution perdue...
Pour
choisir entre plusieurs modèles, il peut être
utile de comprendre comment fonctionne un solveur.
La PPC se base sur lidée simple dun parcours
de larbre des affectations partielles :
- Generate-and-test
:
on essaie tous les nuds de l'arbre.
- Backtracking
(ou retour-arrière) : on vérifie
les contraintes dont les variables sont déjà
affectées.
- Look
Ahead (ou anticipation) : on raisonne au fur et à
mesure de la recherche dans larbre.
Generate
& Test (ou Générer
Tester)
Pour
chaque variable, on essaye de lui affecter une des valeurs
de son domaine, puis on affecte la deuxième variable,
etc... Quand toutes les variables ont une valeur, on vérifie
la faisabilité de l'affectation. On stoppe la recherche
dès qu'une affectation est consistante. Dans le cas
d'un problème d'optimisation, on est obligé
de parcourir tout l'arbre afin de trouver l'affectation
générant une valeur de la fonction objective
la plus intéressante. Pour un problème à
N variables ayant chacune des domaines de tailles M, on
va ainsi tester M puissance N solutions potentielles.
Pour des problèmes de grandes tailles, cette stratégie
est bien évidement inadaptée.
Backtracking
(ou retour-arrière)
Cet
algorithme améliore l'algorithme précédement
décrit et permet de ne développer que les
affectations partielles consistantes. A chaque affectation
partielle du problème, on teste le jeu de contrainte
et dès qu'une affectation partielle est inconsistante,
on stoppe la recherche dans cette branche et on revient
en arrière sans même tester les autres variables.
En effet, il est inutile de continuer à l'étendre
en une affectation totale car elle sera nécessairement
inconsistante.
Look
Ahead (ou anticipation)
Cet
algorithme améliore l'algorithme "retour-arrière"
en anticipant les conséquences de l'affectation partielle
en cours de construction sur les domaines des variables
qui ne sont pas encore affectées. Il existe 2 méthodes
principales : le forward checking et le maintien
de l'arc consistance (encore appelé filtrage)
.
Forward
Checking (ou Vérification en Avant)
Avant
d'affecter une variable x à une valeur v, on vérifie
que toutes les autres variables non affectées et
liées par au moins une contrainte à x possède
au moins une valeur de leur domaine compatible avec v.
Arc
Consistency (ou Filtrage)
Filtrer
les domaines des variables non affectées va consister
à retirer les valeurs "localement inconsistantes".
Différents types de filtrage peuvent être
réalisés, qui seront plus ou moins coûteux
et qui réduiront plus ou moins le domaine des autres
variables.
La
complexité* de cet algorithme est O(ned3)
avec n : nb variables, e : nb contraintes, d = |domaine|.
- L'algorithme
AC3
Cet algorithme est une version améliorée
du précédent.
Atester
= {Liste des contraintes à vérifier}.
Pour chaque contrainte C de
Atester Faire
Soit
x et y les variables
de C.
Si
il existe une valeur du domaine de x
sans support dans y (ou inv.) Alors
Retirer
la valeur sans support.
Ajouter
les contraintes sur la variable modifiée
dans Atester.
Cette
fois-ci on sait quelle variable a été
modifiée.
La complexité* de cet algorithme est O(ed3)
avec e : nb contraintes, d = |domaine|.
- L'algorithme
AC4
Cet
algorithme est une version améliorée du
précédent.
Pour
chaque valeur (x, v)
Supports(
x, v ) = { ( y, v ) | il existe Cxy,
telle que Cxy( v, v ) est vraie
}
Tant
quil existe ( x, v ) et y tels que Supports(
x, v ) Dy
est vide Faire
Retirer
la valeur ( x, v )
Retirer
( x, v ) des supports de ( z, v ) contenus
dans Supports( x, v )
Cette
fois on connaît le variable modifiée
mais aussi la valeur concernée.
La complexité* de cet algorithme est O(ed2)
avec e : nb contraintes, d = |domaine|.
- Les
autres algorithmes AC
Il
existe dautres améliorations : AC6, AC7,
AC2001
mais sont spécifiques à lutilisation
de contraintes en extension. La
plupart de solveurs utilisant des contraintes en intension
exploite lalgorithme AC4. LAC
peut paraître puissant, mais il ne suffit pas
pour détecter toutes les valeurs inconsistantes.
Conclusion
On
appelera Propagation, l'algorithme qui consiste donc
à appliquer des filtrages jusqu'à l'obtention
d'un point fixe. On utilisera des « mondes »
qui stockent létat courant dune résolution
à chaque fois quun choix est pris pour optimiser
la remontée dans larbre de recherche.
La PPC est donc bien lunion dun algorithme
de recherche (profondeur dabord) avec des algorithmes
de filtrage. Une contrainte réagit à lenvoi
dun événement.
Les événements classiques sont :
- Modification
dune des bornes : awakeOnInf et awakeOnSup
(Choco),
- Retrait
dune valeur
(en plein milieu du domaine) : awakeOnRem (Choco),
- Instanciation
de la variable, il ne reste quune seule valeur
: awakeOnInst (Choco).
La
recherche seffectue comme suit :
Tant
quon na pas de contradiction Alors
On
propage
Quand on ne peut plus rien déduire (pile dévénements
vide) on fait un choix
On ajoute la contrainte (ou on fait les modifications
directement sur la variable)
Et on recommence
En
cas de contradiction, on remonte dans larbre
*
Ici les complexités sont exprimés si on considère
qu'on a que des contraintes binaires.
Voici
le plan de cette présentation sur la programmation par contraintes
- Les
notions de base
:
- Quelques
notions avancées :
- Quelques
exemples pour utiliser la P.P.C. :
Cette page
a été co-réalisée avec Guillaume
Rochart, docteur en mathématiques dans mon équipe de
recherche au Bouygues
e-lab.10/03/2009.