Logique propositionnelle
Cours complet · informatique (MP2I/MPI), chapitre 20 · MP2I et MPI
Travailler ce chapitre sur Adloun Exercices corrigés de ce chapitre
20.1 Séparer ce qu'on écrit de ce que cela veut dire
Le programme assigne à ce chapitre un objectif qu'il faut prendre au mot : « familiariser progressivement les étudiants avec la différence entre syntaxe et sémantique ». Cette distinction est l'une des plus fécondes de l'informatique, et elle sera reprise telle quelle au chapitre chap:automates — où une expression régulière (syntaxe) dénotera un langage (sémantique) — et au chapitre chap:deduction, où l'on opposera à .
| Syntaxe | Sémantique | |
|---|---|---|
| Ce que c'est | une suite de symboles | une valeur de vérité |
| Ce qu'on manipule | un arbre | une fonction des valuations |
| Deux formules égales si | mêmes arbres | mêmes tables de vérité |
Le second usage de ce chapitre est plus concret : le programme veut « donner le vocabulaire permettant de modéliser une grande variété de situations (satisfaction de contraintes, planification, diagnostic, vérification de modèles) ». Une formule logique est un format d'entrée universel — et sat, le problème de sa satisfiabilité, sera au cœur du chapitre chap:decidabilite.
20.2 Syntaxe
On se donne un ensemble de variables propositionnelles L'ensemble des formules est le plus petit ensemble tel que :
et de même pour . Les notations sont celles que le programme fixe : . L'arité d'un connecteur est son nombre d'arguments : pour , pour les autres.
Le programme est explicite deux fois : « les formules sont des données informatiques », et « on implémente uniquement les formules propositionnelles sous forme d'arbres ».
type formule =
| Var of string
| Non of formule
| Et of formule * formule
| Ou of formule * formule
| Implique of formule * formule
C'est le type inductif du chapitre chap:induction, et tout ce qui y a été démontré s'applique : les fonctions sur les formules terminent par l'ordre induit, et se prouvent par induction structurelle. Rien n'est à réapprendre.
Les sous-formules de sont elle-même et, récursivement, celles de ses arguments — ce sont exactement les sous-arbres. La taille est le nombre de nœuds, la hauteur celle de l'arbre.
let rec taille = function
| Var _ -> 1
| Non f -> 1 + taille f
| Et (a, b) | Ou (a, b) | Implique (a, b) -> 1 + taille a + taille b
Écrire est ambigu ; l'arbre, lui, ne l'est jamais. Les parenthèses ne servent qu'à écrire une formule en une ligne — et le programme demande de « faire le lien entre les écritures d'une formule comme mot et les parcours d'arbres » : l'écriture infixe parenthésée est le parcours infixe, la notation polonaise le parcours préfixe. C'est exactement le chapitre chap:arbres, et l'analyse syntaxique du chapitre chap:grammaires fera le trajet inverse.
20.2.1 Quantificateurs, variables libres et liées
Les quantificateurs et introduisent une notion de portée : dans , la portée de est . Une occurrence de dans cette portée est liée ; une occurrence hors de toute portée d'un quantificateur sur est libre.
Le programme le dit : ce sont des « notions que l'on retrouve dans la pratique de la programmation ». Dans let f x = x + a, le est lié par fun, le est libre et va chercher sa valeur ailleurs. Renommer un lié ne change rien ; renommer un libre change tout. C'est le même phénomène, et le même piège.
« On ne soulève aucune difficulté technique sur la substitution. L'unification est hors programme. » Et la sémantique n'est présentée que pour des formules sans quantificateurs — « par souci d'éviter trop de technicité ». Les quantificateurs reviendront au chapitre chap:deduction, par leurs règles d'inférence seulement.
20.3 Sémantique
Une valuation affecte à chaque variable une valeur de vérité, notée ou . La valeur se calcule alors par induction sur la structure :
(* Valeur de f sous la valuation v. Précondition : v est définie sur toutes
les variables de f. La structure de la fonction EST celle du type. *)
let rec evalue v = function
| Var p -> v p
| Non f -> not (evalue v f)
| Et (a, b) -> evalue v a && evalue v b
| Ou (a, b) -> evalue v a || evalue v b
| Implique (a, b) -> (not (evalue v a)) || evalue v b
Les deux dernières lignes de la colonne déconcertent : une implication de prémisse fausse est vraie. C'est pourtant le seul choix cohérent. « Si est divisible par , alors est pair » doit être vraie pour tout , y compris , où prémisse et conclusion sont toutes deux fausses.
Retenez la lecture opérationnelle : n'est fausse que dans un seul cas, celui où l'on a promis en ayant , et où n'est pas là.
- est un modèle de si ;
- est satisfiable si elle admet au moins un modèle ;
- est une tautologie si toute valuation en est un modèle ; on écrit ;
- est une antilogie si aucune valuation n'en est un modèle.
Le programme autorise à noter une formule tautologique et une antilogie.
est une tautologie est une antilogie n'est pas satisfiable.
Elle transforme « prouver que est toujours vraie » en « montrer que n'a aucun modèle ». Autrement dit : tout problème de validité se ramène à sat. C'est ce qui donne à sat son statut, et c'est aussi ce qui rend le chapitre chap:deduction nécessaire — car chercher un modèle coûte .
- si les deux ont la même valeur sous toute valuation ;
- (conséquence logique) si tout modèle de est modèle de . Plus généralement, pour un ensemble de formules.
Le programme précise : « la compacité est hors programme ».
et sont équivalentes et ne sont pas égales : leurs arbres diffèrent. Un programme qui teste l'égalité structurelle de deux formules ne teste pas leur équivalence — et tester l'équivalence coûte, dans l'état des connaissances, un temps exponentiel.
Démonstration (Pour la première loi de De Morgan)
Table de vérité, quatre lignes.
Les deux dernières colonnes coïncident : les formules sont équivalentes.
20.4 Formes normales
Un littéral est une variable ou sa négation. Une formule est en :
- forme normale conjonctive (fnc) si elle est une conjonction de clauses, chacune étant une disjonction de littéraux : ;
- forme normale disjonctive (fnd) si c'est une disjonction de conjonctions de littéraux.
Le programme suggère de les représenter « comme des listes de listes de littéraux » — ce qui donne, en OCaml, le type (int * bool) list list.
Méthode : Mettre sous forme normale
Trois étapes, dans cet ordre :
- éliminer et par ;
- pousser les négations vers les variables par De Morgan et ;
- distribuer — sur pour la fnc, sur pour la fnd.
Le programme demande ce lien, et il est direct : chaque ligne de la table de vérité où la formule vaut donne une conjonction de la fnd. Pour , les lignes vraies sont et :
Toute formule admet donc une fnd — et le procédé le montre — mais cette fnd a autant de termes que la table a de lignes vraies, donc jusqu'à .
Le programme demande un « exemple de formule dont la taille des formes normales est exponentiellement plus grande ». En voici un, et il est bref :
Elle est déjà en fnd et sa taille est linéaire. Sa fnc, en revanche, s'obtient en distribuant : il faut choisir un littéral dans chacun des groupes, ce qui donne
Pour : une formule de variables devient un million de clauses. Une transformation qui préserve la sémantique peut détruire l'efficacité, et c'est pourquoi les solveurs réels emploient des transformations qui ajoutent des variables au lieu de distribuer.
20.5 Le problème sat
sat : étant donné une formule en fnc, admet-elle un modèle ? -sat : même question quand chaque clause a au plus littéraux.
-sat se résout en temps linéaire — le chapitre chap:graphes-avances le montrera, par les composantes fortement connexes. -sat est np-complet (chapitre chap:decidabilite). Le saut se fait donc entre et .
Méthode : L'algorithme de Quine
C'est un retour sur trace (chapitre chap:exploration) sur les valuations : on choisit une variable, on essaie , on simplifie, on recommence ; en cas d'échec, on essaie .
(* Vrai si f est satisfiable. Précondition : aucune. Complexité : O(2^n · |f|). *)
let rec quine f =
match variables f with
| [] -> evalue (fun _ -> false) f (* plus de variable : on évalue *)
| p :: _ ->
quine (substituer f p true) || quine (substituer f p false)
Ce qui fait toute son efficacité pratique : la simplification. Substituer dans une fnc supprime toutes les clauses contenant , et retire des autres. Une clause devenue vide est fausse : on remonte immédiatement, sans explorer les valuations restantes. C'est exactement l'élagage du chapitre chap:exploration, et c'est ce qui rend l'algorithme utilisable là où serait rédhibitoire.
Le programme demande d'« incarner sat par la modélisation d'un problème (par exemple la coloration des sommets d'un graphe) ». Colorier avec couleurs sans que deux voisins partagent la leur :
Variables : pour « le sommet porte la couleur », soit variables.
Clauses :
- chaque sommet a au moins une couleur : pour chaque ;
- chaque sommet a au plus une couleur : pour ;
- deux voisins diffèrent : pour chaque arête et chaque .
La formule est satisfiable si et seulement si le graphe est -coloriable, et un modèle donne la coloration. C'est le schéma de toute modélisation par sat : on encode les choix en variables, les contraintes en clauses, et l'on délègue la recherche.
Parce que les solveurs sat modernes traitent couramment des instances à des millions de clauses, et que l'on gagne à écrire des contraintes plutôt qu'un algorithme. C'est le paradigme déclaratif du chapitre chap:algo-prog : on dit ce qu'on veut, un moteur cherche. Le prix à payer est le pire cas exponentiel — que le chapitre chap:decidabilite justifiera.
20.6 Ce qu'il faut retenir
- Une formule est un arbre — un type inductif —, et tout le chapitre chap:induction s'y applique.
- Syntaxe (, les arbres) et sémantique (, les tables) ne se confondent jamais.
- Tautologie, satisfiabilité et antilogie se ramènent l'une à l'autre par la négation — et donc à sat.
- Toute formule a une fnd lisible dans sa table de vérité, mais la mise en forme normale peut faire passer une taille linéaire à .
- Quine est un retour sur trace, et c'est la simplification qui le rend utilisable. -sat est linéaire, -sat est np-complet.