Aller au contenu principal
satLa résolution, ou comment prouver qu'il n'y a rien

La résolution, ou comment prouver qu'il n'y a rien

Ce que ce chapitre apporte

  • Appliquer la règle de résolution et calculer une résolvante sans faute.
  • Justifier qu'elle est correcte : la résolvante est conséquence de ses deux parents.
  • Énoncer la complétude réfutationnelle et savoir ce qu'elle promet, et ce qu'elle ne promet pas.
  • Construire une réfutation complète sur une petite formule.
  • Reconnaître la résolution unitaire comme un cas particulier déjà rencontré.
  • Comprendre pourquoi l'algorithme de Davis-Putnam a été abandonné.
  • Citer les deux familles dont on sait que toute preuve par résolution est exponentielle.
  • Dire ce qu'est un certificat DRAT et à quoi il sert.
Trouver un modèle est facile à justifier : on le montre, l'autre vérifie. Démontrer qu'il n'en existe aucun demande autre chose, et cette autre chose tient dans une seule règle, découverte en 1965 et jamais remplacée depuis. La résolution combine deux clauses en une troisième, et rien de plus ; répétée assez longtemps, elle finit par produire la clause vide dès que la formule est contradictoire. Tous les solveurs de ce cours raisonnent avec elle, ce qui leur donne leur puissance et, comme on le verra, leur limite indépassable.

La règle

Résolution

Soient deux clauses dont l'une contient le littéral xx et l'autre le littéral ¬x\neg x :

C=(xa1ak)C = (x \vee a_1 \vee \cdots \vee a_k) et D=(¬xb1bm)D = (\neg x \vee b_1 \vee \cdots \vee b_m)

Leur résolvante sur le pivot xx est la clause

(a1akb1bm)(a_1 \vee \cdots \vee a_k \vee b_1 \vee \cdots \vee b_m)

c'est-à-dire la réunion des deux clauses privée de xx et de ¬x\neg x.

Trois résolutions, dont deux pièges

Le cas ordinaire. (xy)(x \vee y) et (¬xz)(\neg x \vee z) donnent (yz)(y \vee z).

Le littéral commun. (yz)(y \vee z) et (¬yz)(\neg y \vee z) donnent (z)(z), et non (zz)(z \vee z). Une clause est un ensemble de littéraux : le doublon disparaît, et c'est ce qui permet aux clauses de raccourcir.

Le pivot unique. (xy)(x \vee y) et (¬x¬y)(\neg x \vee \neg y) donnent (y¬y)(y \vee \neg y), qui est une tautologie donc inutile. On ne peut pas résoudre sur xx et sur yy en même temps : la règle élimine une seule paire de littéraux opposés à la fois. Résoudre sur les deux produirait la clause vide à partir de deux clauses parfaitement satisfiables, ce qui serait faux.

Une résolution, un pivot
C'est l'erreur de calcul la plus fréquente, et elle est fatale : elle permet de « démontrer » n'importe quoi.
(xy)(x \vee y) et (¬x¬y)(\neg x \vee \neg y) sont toutes deux satisfaites par x=1x = 1, y=0y = 0. Si l'on éliminait les deux paires d'un coup, on obtiendrait la clause vide, donc l'insatisfiabilité, sur une formule qui a un modèle.
La règle ne le permet pas : un pivot, un seul, à chaque étape.
Pourquoi la règle est correcte
La résolvante est conséquence logique de ses deux parents, et la démonstration tient en deux lignes.
Prenons une affectation qui satisfait CC et DD. Le littéral xx vaut 1 ou 0.
S'il vaut 1, alors ¬x\neg x vaut 0, donc DD est satisfaite par l'un de ses bjb_j, qui figure dans la résolvante.
S'il vaut 0, alors CC est satisfaite par l'un de ses aia_i, qui figure aussi dans la résolvante.
Dans les deux cas la résolvante est satisfaite. Ajouter une résolvante à une formule ne change donc jamais ses modèles : la formule reste équivalente à elle-même, en un peu plus explicite.

Une réfutation complète

Réfutation par résolution

Une réfutation d'une formule FF est une suite de clauses où chacune est soit une clause de FF, soit la résolvante de deux clauses précédentes, et dont la dernière est la clause vide \square.

Puisque chaque résolvante est conséquence de FF, dériver la clause vide signifie que FF entraîne le faux. Une formule qui entraîne le faux n'a aucun modèle : la réfutation est une preuve d'insatisfiabilité.

Réfuter quatre clauses

F=(xy)(¬xz)(¬yz)(¬z)F = (x \vee y) \wedge (\neg x \vee z) \wedge (\neg y \vee z) \wedge (\neg z)

Que cette formule soit insatisfiable se vérifie à la main : (¬z)(\neg z) impose z=0z = 0, ce qui force x=0x = 0 par la deuxième clause et y=0y = 0 par la troisième, et la première tombe.

La réfutation ci-dessous établit la même chose mécaniquement, sans raisonner sur les valeurs, en trois applications de la règle.

sur xsur ysur zx ∨ yc1¬x ∨ zc2¬y ∨ zc3¬zc4y ∨ zr1zr2r3
La réfutation de (x ∨ y) ∧ (¬x ∨ z) ∧ (¬y ∨ z) ∧ (¬z). Chaque résolvante est calculée à partir de ses deux parents, jamais recopiée. Noter r2 : la résolvante de (y ∨ z) et (¬y ∨ z) est z, et non « z ∨ z ». La dérivation aboutit à la clause vide : la formule est démontrée insatisfiable.
Lire une réfutation à l'envers
Une preuve se construit rarement dans le sens où elle se lit. En pratique on part de la fin : pour obtenir \square, il faut résoudre deux clauses unitaires opposées, donc trouver (z)(z) et (¬z)(\neg z).
(¬z)(\neg z) est donnée. Reste à produire (z)(z), ce qui demande deux clauses ne contenant plus que zz et un littéral opposé, et ainsi de suite. Chercher à rebours depuis la contradiction est ce que fait un solveur, et le chapitre 8 montrera qu'il en tire ses meilleures clauses.

La complétude réfutationnelle

Théorème, Robinson 1965

Si une formule CNF est insatisfiable, alors la clause vide est dérivable par résolution à partir de ses clauses.

Ce que ce théorème ne dit pas
Il ne dit pas que la résolution dérive toute conséquence d'une formule. La formule (x)(x) entraîne (xy)(x \vee y), et aucune résolution ne produira jamais cette clause : la règle ne sait qu'enlever des littéraux, jamais en ajouter.
Le théorème est réfutationnel : il ne promet que la clause vide, et seulement quand elle est due. C'est suffisant, parce que toute question de logique propositionnelle se ramène à une insatisfiabilité, comme le chapitre 1 l'a montré pour la validité et la conséquence logique.
Il ne dit pas non plus que la preuve est courte. C'est la fin de ce chapitre, et c'est là que tout se joue.
La propagation unitaire est de la résolution
La résolution où l'un des deux parents est une clause unitaire porte un nom, la résolution unitaire, et c'est exactement le mécanisme rencontré depuis le chapitre 2.
Résoudre (¬xyz)(\neg x \vee y \vee z) avec la clause unitaire (x)(x) donne (yz)(y \vee z) : la clause a raccourci, précisément parce que xx a été affecté. Répéter jusqu'à obtenir une clause unitaire, c'est propager ; obtenir la clause vide, c'est le conflit.
Autrement dit, un solveur qui propage résout déjà, sans le dire. Le chapitre 8 montrera que CDCL ne fait rien d'autre que de la résolution, appliquée dans un ordre choisi par la recherche.
La résolution unitaire seule n'est pas complète
Elle suffit sur les formules de Horn, ce qui est tout le contenu du chapitre 5. Elle ne suffit pas en général.
Sur (xy)(x¬y)(¬xy)(¬x¬y)(x \vee y) \wedge (x \vee \neg y) \wedge (\neg x \vee y) \wedge (\neg x \vee \neg y), qui est insatisfiable, aucune clause n'est unitaire et la propagation ne démarre pas. Il faut donc résoudre deux clauses binaires entre elles, ce qui est une décision de plus qu'un solveur doit prendre.
C'est exactement pourquoi un solveur a besoin de décider en plus de propager, et c'est le sujet du chapitre suivant.

Davis-Putnam, et pourquoi il a été abandonné

Élimination de variable

Pour éliminer la variable xx d'une formule : produire toutes les résolvantes sur xx, entre chaque clause contenant xx et chaque clause contenant ¬x\neg x, puis supprimer toutes les clauses où xx apparaît.

La formule obtenue ne parle plus de xx, et elle est équisatisfiable à la précédente.

L'algorithme de Davis et Putnam, publié en 1960, applique cette élimination variable après variable. Quand il ne reste plus rien, la formule était satisfiable ; si la clause vide apparaît en chemin, elle ne l'était pas. C'est correct, complet, et parfaitement déterministe.

Le coût est en mémoire, et il explose
Éliminer une variable présente dans pp clauses positivement et qq négativement produit jusqu'à p×qp \times q résolvantes, et en supprime p+qp + q.
Une variable apparaissant dans 50 clauses de chaque côté remplace donc 100 clauses par 2 500. La formule enfle à chaque élimination, et la mémoire cède bien avant que le calcul n'aboutisse.
Deux ans plus tard, en 1962, Davis, Logemann et Loveland remplaçaient l'élimination par un parcours en profondeur : au lieu de produire toutes les résolvantes d'une variable, on lui donne une valeur et l'on descend, quitte à revenir. La mémoire redevient linéaire, au prix d'un temps qui peut exploser. C'est DPLL, et c'est le chapitre suivant.
L'élimination de variable n'a pas disparu
Elle est revenue, mais en petit. Les solveurs modernes l'appliquent en prétraitement, et seulement aux variables où elle fait diminuer le nombre de clauses : typiquement celles qui n'apparaissent que deux ou trois fois.
Sur une instance industrielle, cela retire souvent la moitié des variables pour un coût négligeable. Ce qui était un algorithme complet est devenu une optimisation locale, et c'est un bon exemple de la façon dont une idée abandonnée revient à l'échelle où elle est rentable. Le chapitre 9 y revient.

Le talon d'Achille : la longueur des preuves

C'est ici que la résolution montre sa limite, et le résultat est définitif.

Théorème de Haken, 1985

Toute réfutation par résolution du principe des tiroirs PHPn\mathrm{PHP}_n compte un nombre exponentiel de clauses.

Ce que cela veut dire pour un solveur
Ce n'est pas un défaut d'implémentation, ni une question de réglage, ni un problème que le prochain solveur résoudra. C'est une propriété de la règle elle-même.
Tout solveur qui raisonne par résolution, ce qui inclut DPLL, CDCL et tous les outils de ce cours, doit produire une preuve par résolution pour répondre UNSAT. Si toutes ces preuves sont exponentiellement longues, alors le solveur y passera un temps exponentiel, quelle que soit son ingéniosité.
PHP14\mathrm{PHP}_{14} compte 210 variables, et met en échec des solveurs qui traitent par ailleurs des millions de variables. Un humain le réfute en une phrase : il y a plus de pigeons que de casiers. Cette phrase, la résolution ne sait pas l'écrire.
Le principe des tiroirs n'est pas seul
Urquhart a démontré en 1987 un résultat du même genre pour une famille construite sur des systèmes XOR, ceux du chapitre 5 : leurs réfutations par résolution sont elles aussi exponentielles.
Les deux familles ont un point commun instructif. Dans les deux cas, la raison de l'insatisfiabilité est un argument de comptage : plus de pigeons que de casiers, ou une somme de parités qui vaut 1 alors qu'elle devrait valoir 0. La résolution ne sait pas compter ; elle ne sait qu'enlever des littéraux un par un.
D'où l'existence de systèmes de preuve plus puissants, qui savent additionner des contraintes au lieu de les résoudre, et qui réfutent le principe des tiroirs en quelques lignes. Aucun n'a encore donné de solveur compétitif sur les instances réelles, ce qui est l'un des écarts les plus intrigants du domaine.

Les preuves qu'un solveur rend

Certificat DRAT

Un solveur qui répond UNSAT peut produire un fichier DRAT : la liste, dans l'ordre, des clauses qu'il a ajoutées pendant la recherche, et de celles qu'il a supprimées.

Un programme indépendant, de quelques milliers de lignes, relit ce fichier et vérifie que chaque ajout est licite. S'il valide jusqu'à la clause vide, la réponse UNSAT est démontrée.

Pourquoi c'est devenu obligatoire
Un solveur moderne compte des dizaines de milliers de lignes, plusieurs heuristiques et beaucoup d'optimisations fines. Personne ne peut garantir qu'il n'a pas de bug, et un bug qui fait dire UNSAT à tort est silencieux : il n'y a rien à regarder.
La parade est de ne pas faire confiance au solveur, mais à un vérificateur minuscule, simple au point d'être relisible, et lui-même parfois démontré correct. Depuis 2013, les compétitions internationales exigent un certificat DRAT pour toute réponse UNSAT.
C'est la même logique qu'au chapitre 4, poussée jusqu'au bout : une réponse « oui » se certifie par un modèle, une réponse « non » par une preuve, et dans les deux cas c'est le vérificateur, pas le chercheur, qui fonde la confiance.
L'ordre de grandeur, qui surprend
Ces fichiers sont énormes. La preuve de la conjecture de Pythagore booléenne, produite en 2016, occupe environ 200 téraoctets : c'est la plus grosse démonstration mathématique jamais écrite.
Elle a pourtant été vérifiée intégralement par un programme, ce qui est le seul sens dans lequel un objet pareil peut être « lu ». Une preuve qu'aucun humain ne lira reste une preuve, à condition que le vérificateur, lui, soit lisible.

Exercices type

Calculer la résolvante de (ab¬c)(a \vee b \vee \neg c) et (¬bcd)(\neg b \vee c \vee d) sur le pivot bb

On retire bb et ¬b\neg b, puis on réunit le reste :

(a¬ccd)(a \vee \neg c \vee c \vee d)

Cette clause contient cc et ¬c\neg c : elle est tautologique, donc toujours vraie et parfaitement inutile. Un solveur la jetterait immédiatement.

C'est un cas fréquent, et il vaut la peine de le repérer avant de calculer : dès que les deux clauses contiennent une seconde variable avec des signes opposés, la résolvante sera tautologique.

Résoudre les deux mêmes clauses sur le pivot cc donnerait en revanche (ab¬bd)(a \vee b \vee \neg b \vee d), tautologique elle aussi.

Réfuter (x)(¬xy)(¬y)(x) \wedge (\neg x \vee y) \wedge (\neg y)

Trois clauses, deux résolutions.

Première étape. (x)(x) et (¬xy)(\neg x \vee y), pivot xx, donnent (y)(y).

Deuxième étape. (y)(y) et (¬y)(\neg y), pivot yy, donnent \square.

La formule est insatisfiable. Noter que les deux étapes emploient une clause unitaire : c'est de la résolution unitaire, donc exactement ce que la propagation aurait fait toute seule.

Peut-on dériver (xy)(x \vee y) à partir de (x)(x) par résolution ?

Non, et c'est structurel. La résolution produit toujours une clause dont les littéraux viennent des deux parents. Le littéral yy n'apparaît nulle part dans (x)(x), donc aucune suite de résolutions ne le fera surgir.

Pourtant (x)(x) entraîne bien (xy)(x \vee y) : toute affectation qui satisfait la première satisfait la seconde.

C'est la différence entre complétude et complétude réfutationnelle. La résolution ne dérive pas toutes les conséquences ; elle dérive la clause vide quand elle est due, et c'est tout ce dont on a besoin.

Éliminer la variable xx de (xa)(xb)(¬xc)(¬xd)(x \vee a) \wedge (x \vee b) \wedge (\neg x \vee c) \wedge (\neg x \vee d)

Deux clauses contiennent xx, deux contiennent ¬x\neg x : il y a 2×2=42 \times 2 = 4 résolvantes à produire.

(ac)(a \vee c), (ad)(a \vee d), (bc)(b \vee c), (bd)(b \vee d).

On supprime ensuite les quatre clauses d'origine. Bilan : 4 clauses remplacées par 4, ce qui est le cas neutre.

Avec 10 clauses de chaque côté, ce serait 20 remplacées par 100. C'est cette croissance qui a condamné Davis-Putnam, et c'est aussi pourquoi les solveurs modernes n'éliminent que les variables où p×qp+qp \times q \leq p + q, c'est-à-dire presque uniquement celles qui apparaissent deux ou trois fois.

Un solveur répond UNSAT sur une instance critique. Comment s'en assurer ?

Ne pas le croire sur parole. Lui demander un certificat DRAT, puis le passer à un vérificateur indépendant.

Le raisonnement est le même que pour une réponse SAT, où l'on vérifie le modèle en un parcours plutôt que de faire confiance à la recherche. La différence est que le certificat d'un UNSAT est beaucoup plus gros, et que le vérificateur fait un vrai travail.

Ce qu'on gagne : la confiance ne repose plus sur les dizaines de milliers de lignes du solveur, mais sur les quelques milliers du vérificateur, qui est simple, stable, et parfois démontré correct.

Pourquoi PHPn\mathrm{PHP}_n est-il difficile pour la résolution alors qu'il est évident pour un humain ?

Parce que l'argument humain est un comptage : il y a n+1n+1 pigeons pour nn casiers, donc deux pigeons partagent un casier.

La résolution ne sait pas compter. Elle ne dispose que d'une opération, éliminer une paire de littéraux opposés, et pour atteindre la contradiction elle doit en quelque sorte parcourir toutes les façons de placer les pigeons. Haken a démontré que ce parcours est nécessairement exponentiel.

La leçon est générale : un système de preuve n'est puissant que sur les arguments qu'il sait exprimer. Il existe des systèmes qui additionnent des contraintes et réfutent PHPn\mathrm{PHP}_n en quelques lignes ; aucun n'a encore donné de solveur qui batte CDCL sur les instances réelles.

Vérification rapideon peut se reprendre

1.La résolvante de (y ∨ z) et (¬y ∨ z) sur y vaut…

2.Peut-on résoudre (x ∨ y) et (¬x ∨ ¬y) en éliminant x et y à la fois ?

3.Dériver la clause vide à partir des clauses de F prouve que…

4.La résolution peut-elle dériver (x ∨ y) à partir de (x) ?

5.Pourquoi Davis-Putnam a-t-il été remplacé par DPLL dès 1962 ?

6.Un solveur produit un certificat DRAT. À quoi sert-il ?

La méthode

  1. Chercher une paire de littéraux opposés avant tout : sans elle, aucune résolution n'est possible.
  2. Ne résoudre que sur un seul pivot à la fois, quelle que soit la tentation.
  3. Dédoublonner la résolvante, c'est ce qui la fait raccourcir.
  4. Jeter les résolvantes tautologiques : elles sont licites et sans usage.
  5. Construire une réfutation à rebours, en partant des deux clauses unitaires opposées qu'il faudra obtenir.
  6. Reconnaître la propagation unitaire comme un cas de résolution, et non comme un mécanisme distinct.
  7. Demander un certificat pour toute réponse UNSAT dont dépend une décision.

Synthèse

  • La résolvante de deux clauses sur un pivot est leur réunion privée du pivot, sans doublon.
  • Un seul pivot par étape : en éliminer deux permettrait de démontrer n'importe quoi.
  • La résolvante est conséquence de ses parents : l'ajouter ne change aucun modèle.
  • Une réfutation est une dérivation aboutissant à la clause vide, donc une preuve d'insatisfiabilité.
  • Robinson : toute formule insatisfiable admet une réfutation par résolution.
  • La complétude est réfutationnelle : la règle ne dérive pas toutes les conséquences, seulement \square.
  • La propagation unitaire est de la résolution, avec une clause unitaire pour parent.
  • La résolution unitaire seule suffit sur Horn, pas en général : d'où le besoin de décider.
  • Davis-Putnam élimine les variables par résolution ; p×qp \times q résolvantes font exploser la mémoire.
  • Haken : toute réfutation par résolution de PHPn\mathrm{PHP}_n est exponentielle, et Urquhart en dit autant des systèmes XOR.
  • La résolution ne sait pas compter, et c'est exactement ce qui manque à ces deux familles.
  • Un certificat DRAT déplace la confiance du solveur vers un vérificateur minuscule.