DPLL : chercher, et savoir revenir en arrière
Ce que ce chapitre apporte
- Énoncer les trois opérations de DPLL et dire laquelle est un pari.
- Dérouler une trace complète sur une petite formule, décision par décision.
- Justifier que la propagation unitaire ne fait jamais perdre de modèle.
- Appliquer la règle du littéral pur et dire pourquoi elle est sûre.
- Reconnaître le retour arrière chronologique et voir ce qu'il gaspille.
- Comparer deux heuristiques de branchement sur la même formule.
- Situer DPLL par rapport à la résolution : ce qu'il gagne, ce qu'il perd.
Trois opérations, dont une seule est un pari
Pour décider si une formule est satisfiable sous une affectation partielle :
- Propager toutes les clauses unitaires jusqu'au point fixe.
- Si une clause est falsifiée : échec, remonter.
- Si toutes les clauses sont satisfaites : succès, rendre l'affectation.
- Sinon, choisir une variable libre, lui donner une valeur, et recommencer. Si cette branche échoue, essayer l'autre valeur.
Propager est une déduction : si une clause n'a plus qu'un littéral libre et que tous les autres sont faux, ce littéral doit être vrai. Aucun modèle n'est perdu, il n'y aura jamais à revenir dessus.
Décider est un pari : rien n'imposait cette valeur, et si la branche échoue il faudra essayer l'autre.
D'où la conséquence pratique qui gouverne toute l'ingénierie du domaine : seules les décisions coûtent. Une propagation, aussi longue soit-elle, ne double jamais le travail ; une décision, si.
Un modèle qui prolonge l'affectation courante doit satisfaire cette clause, donc satisfaire l'un de ses littéraux. Les autres étant faux, il ne reste que . Tout modèle atteignable met donc à vrai.
Fixer n'élimine ainsi aucune possibilité : c'est une déduction, et c'est ce qui autorise à la faire sans jamais prévoir de retour arrière.
Une trace, déroulée
Prenons quatre clauses et suivons la recherche pas à pas.
, , ,
Aucune clause n'est unitaire : la propagation ne peut rien faire, et il faut décider.
L'arbre complet. Dérouler pas à pas montre l'ordre dans lequel le solveur le parcourt, et le moment où il revient en arrière.
Décision . Un pari, rien ne l'imposait. La variable a été choisie parce qu'elle vient en premier ; un autre choix aurait donné un autre arbre.
Propagation . La clause voit son premier littéral devenir faux : elle est unitaire, et impose . Ce n'est pas un choix.
Conflit sur . La clause a maintenant ses deux littéraux faux. La branche est morte, et il faut défaire la dernière décision.
Décision . L'autre valeur du pari. C'est le retour arrière, et il ne coûte rien de plus qu'un changement de valeur.
Propagation . La clause devient unitaire.
Modèle. Il reste libre, et toutes les clauses sont satisfaites : par , et par , par . La formule est satisfiable, par , , au choix.
Un solveur rendra pourtant une affectation complète, en donnant une valeur arbitraire aux variables restées libres. Les deux comportements sont corrects, et il vaut la peine de savoir que le solveur ne les a pas toutes décidées.
Et quand tout échoue
Voici l'autre issue, sur la formule la plus petite qui soit insatisfiable sans clause unitaire.
, , ,
L'arbre complet. Dérouler pas à pas montre l'ordre dans lequel le solveur le parcourt, et le moment où il revient en arrière.
Répété jusqu'à la racine, l'argument donne une preuve d'insatisfiabilité. Elle est même exactement une preuve par résolution, comme le chapitre précédent l'a suggéré : chaque conflit correspond à une clause dérivée, et l'arbre entier se traduit en une réfutation.
Autrement dit, DPLL est un moteur de résolution, qui choisit l'ordre des étapes par une recherche au lieu de les produire toutes. C'est ce qui lui rend la mémoire, et c'est aussi ce qui lui laisse la borne de Haken : sur le principe des tiroirs, son arbre est exponentiel.
Le littéral pur
Un littéral est pur si son opposé n'apparaît dans aucune clause de la formule.
La justification tient en une phrase. Poser à vrai satisfait toutes les clauses qui contiennent , et n'en gêne aucune autre, puisque n'apparaît nulle part. Si un modèle existait avec , le même modèle avec en est encore un.
Ce n'est donc pas une décision : c'est une simplification qui ne coûte aucun retour arrière.
Détecter les littéraux purs demande de parcourir toute la formule après chaque affectation, puisque la pureté dépend des clauses encore actives. Sur un million de clauses, cela coûte plus cher que ce que la règle fait gagner.
Elle survit là où elle est bon marché : dans le prétraitement, où on la calcule une fois, et dans les solveurs de comptage de modèles, où elle joue un rôle différent. C'est un bon exemple d'une règle correcte et utile devenue non rentable à grande échelle.
Le choix de la variable
C'est le seul endroit où DPLL a une liberté, et c'est donc là que tout se joue.
, , , , ,
Cette formule est insatisfiable, et la contradiction se joue entièrement entre et . Les variables et n'y sont pour rien.
En commençant par , la contradiction apparaît en deux décisions, comme dans la figure précédente.
En commençant par , le solveur explore d'abord les valeurs de et de , qui ne mènent nulle part, et refait ensuite le raisonnement sur et dans chaque branche. Le travail est multiplié, sans qu'aucune information nouvelle ait été obtenue.
L'arbre complet. Dérouler pas à pas montre l'ordre dans lequel le solveur le parcourt, et le moment où il revient en arrière.
La plus fréquente. Choisir la variable qui apparaît dans le plus de clauses : elle en simplifie beaucoup d'un coup.
Les clauses les plus courtes d'abord. Une clause binaire est presque unitaire ; y toucher déclenche des propagations. C'est l'idée de l'heuristique de Jeroslow-Wang, qui pondère chaque littéral par sur les clauses où il figure, donnant plus de poids aux clauses courtes.
Ce qui a récemment posé problème. C'est l'idée qui a tout changé, mais elle demande de garder une mémoire des conflits, ce que DPLL ne fait pas. Elle appartient au chapitre suivant.
Ce qui se mesure, en revanche, est le comportement en moyenne sur les instances qui intéressent. C'est pourquoi les heuristiques des solveurs actuels ne sont pas issues d'un théorème mais de trente ans de compétitions, et pourquoi elles changent encore.
Le retour arrière chronologique, et son gaspillage
Quand une branche échoue, DPLL défait la dernière décision prise et essaie l'autre valeur. S'il l'a déjà essayée, il remonte encore d'un cran.
DPLL l'ignore. Il défait , puis , puis remonte, et refait le même raisonnement dans l'autre branche de . Il redécouvre une contradiction qu'il avait déjà rencontrée, parce qu'il n'a rien gardé de sa première rencontre.
Deux idées manquent, et elles arrivent ensemble au chapitre suivant : analyser le conflit pour savoir qui l'a causé, et retenir le résultat sous forme d'une clause. C'est le passage de DPLL à CDCL, et le gain se compte en ordres de grandeur.
L'implémentation, en trente lignes
Voici DPLL au complet, propagation comprise. Le solveur du chapitre 4 était déjà celui-ci.
Elle cache les propagations, qui se font à l'intérieur de
propage sans laisser de trace. C'est représentatif : sur une instance réelle, le nombre de propagations dépasse celui des décisions d'un facteur de dix à mille, et c'est précisément pour cela que la propagation est la partie qu'on optimise en premier. Le chapitre 9 y consacre l'essentiel de son propos.
Exercices type
Sur , combien DPLL prend-il de décisions ?
Aucune.
La clause est unitaire dès le départ : elle impose . La clause devient alors unitaire et impose , puis impose .
Toutes les clauses sont satisfaites, et le solveur n'a jamais eu à parier. C'est le cas idéal, et c'est exactement ce qui se produit sur une formule de Horn avec des faits, comme au chapitre 5.
DPLL peut-il rendre un modèle où certaines variables n'ont pas de valeur ?
Oui, et c'est correct. Il s'arrête dès que toutes les clauses sont satisfaites, ce qui peut arriver avant d'avoir touché à toutes les variables.
Une variable non affectée est une variable dont aucune clause n'a plus besoin : toutes celles où elle figure sont déjà satisfaites par d'autres littéraux. N'importe quelle valeur convient.
En pratique, un solveur complète l'affectation avant de la rendre, pour respecter le format attendu. Il vaut mieux le savoir quand on compare deux sorties : deux solveurs peuvent rendre des modèles différents sur la même instance sans qu'aucun des deux ne se trompe.
Dans , quels littéraux sont purs ?
Cherchons pour chaque littéral si son opposé apparaît.
figure dans et ; n'apparaît nulle part : est pur. figure dans , dans : ni l'un ni l'autre n'est pur. figure dans et ; n'apparaît nulle part : est pur.
Poser et satisfait les trois clauses d'un coup, sans aucune décision et sans risque. La variable reste libre.
Pourquoi le retour arrière chronologique refait-il deux fois le même travail ?
Parce qu'il défait la dernière décision, qui n'est pas forcément la coupable.
Si le conflit vient d'une clause où la dernière décision n'apparaît même pas, la défaire ne corrige rien : le solveur va rencontrer la même contradiction dans l'autre branche, puis remonter encore, et ainsi de suite.
Le remède demande deux choses que DPLL n'a pas. Analyser le conflit pour identifier les décisions réellement en cause, ce qui permet de remonter directement au bon niveau. Et retenir une clause qui résume l'impasse, pour que la même contradiction ne soit pas redécouverte ailleurs.
C'est CDCL, et c'est le chapitre suivant.
Une propagation peut-elle faire perdre l'unique modèle d'une formule ?
Non, jamais, et c'est ce qui autorise à propager sans prévoir de retour arrière.
Une clause unitaire n'a plus qu'un littéral libre, tous les autres étant faux. Or tout modèle prolongeant l'affectation courante doit satisfaire cette clause, donc satisfaire l'un de ses littéraux, et il ne reste que celui-là.
La valeur imposée figure donc dans tous les modèles atteignables. Fixer cette valeur n'en élimine aucun.
C'est exactement l'inverse d'une décision, qui coupe la moitié des possibilités et doit pouvoir être défaite.
1.Dans DPLL, laquelle de ces opérations est un pari qu'il faudra peut-être défaire ?
2.DPLL s'arrête et rend un modèle où z n'a pas de valeur. Que conclure ?
3.Le littéral x est pur. Que peut-on faire ?
4.Pourquoi les solveurs modernes n'appliquent-ils presque plus la règle du littéral pur ?
5.Un conflit survient dans une branche, et la dernière décision n'apparaît dans aucune clause en cause. Que fait DPLL ?
6.Sur une instance réelle, le rapport entre propagations et décisions est de l'ordre de…
La méthode
- Propager d'abord, toujours, avant de songer à décider.
- Vérifier les deux arrêts après chaque propagation : une clause falsifiée, ou toutes satisfaites.
- Ne décider que sur une variable libre, et se souvenir que c'est le seul geste coûteux.
- Chercher les littéraux purs sur les petites formules traitées à la main : ils suppriment des branches entières.
- Choisir la variable la plus contrainte plutôt que la première venue.
- Compter les décisions, jamais les propagations, pour estimer le coût d'une recherche.
- Lire un échec des deux branches comme une preuve portant sur ce qui précède, pas sur la variable branchée.
Synthèse
- DPLL fait trois choses : propager, détecter conflit ou succès, et décider.
- Propager ne perd aucun modèle : le littéral forcé figure dans tous les modèles atteignables.
- Décider est le seul geste qui coupe des possibilités, donc le seul qui coûte.
- Un littéral pur se pose sans brancher, mais sa détection est trop chère à grande échelle.
- Le retour arrière chronologique défait la dernière décision, qui n'est pas forcément la coupable.
- Un mauvais ordre de branchement fait refaire le même sous-arbre plusieurs fois.
- Aucune heuristique n'est optimale partout ; elles se mesurent sur les instances qui intéressent.
- DPLL est un moteur de résolution : son arbre se traduit en réfutation, et la borne de Haken s'applique.
- Sur une instance réelle, il y a dix à mille propagations par décision.