Probleme – Quine sur les \textsc{fnc} : ce que la simplification fait gagner
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 20 — Logique propositionnelle
Énoncé
On représente une fnc par une liste de listes de littéraux, un littéral étant un couple (string * bool) — le booléen étant le signe.
- Écrire
simplifier : clause list -> string -> bool -> clause list, qui substitue dans une fnc. - En déduire
quine, qui rend un modèle ouNone. - Prouver la terminaison, et énoncer les deux conditions d'arrêt.
- Mesurer le gain sur le pigeonnier de l'exercice 20.9.
Corrigé
1. La simplification, et c'est elle qui porte tout l'algorithme.
type litteral = string * bool (* (nom, signe) : (p, false) est ¬p *)
type clause = litteral list (* disjonction *)
type fnc = clause list (* conjonction *)
(* La FNC f où p vaut b. Deux effets, et ils sont différents :
- une clause contenant le littéral SATISFAIT disparaît (elle est vraie) ;
- dans les autres, le littéral contraire est RETIRÉ (il est faux).
Précondition : aucune. Complexité : Theta(taille de f). *)
let simplifier f p b =
f
|> List.filter (fun c -> not (List.mem (p, b) c))
|> List.map (List.filter (fun (q, _) -> q <> p))
La dissymétrie des deux lignes est le point délicat. La clause qui contient sous le bon signe est effacée — elle est vraie, elle ne contraint plus rien. La clause qui contient sous le mauvais signe maigrit — ce littéral-là ne peut plus la sauver. Confondre les deux, c'est écrire un algorithme qui rend « satisfiable » sur une formule qui ne l'est pas.
2. Quine.
(* Un modèle de f, ou None. Le modèle est partiel : les variables absentes
du résultat peuvent prendre n'importe quelle valeur.
Précondition : aucune. *)
let rec quine f =
if f = [] then Some [] (* plus de clause : VRAI *)
else if List.exists (fun c -> c = []) f then None (* clause vide : FAUX *)
else
let (p, _) = List.hd (List.hd f) in
match quine (simplifier f p true) with
| Some m -> Some ((p, true) :: m)
| None ->
match quine (simplifier f p false) with
| Some m -> Some ((p, false) :: m)
| None -> None
3. Terminaison et arrêts.
Variant : le nombre de variables distinctes de f. La deuxième ligne de simplifier retire toute occurrence de ; aucune variable n'est ajoutée. Le variant décroît donc strictement de à chaque appel récursif, et il est entier positif : la récursion a profondeur au plus .
Les deux conditions d'arrêt sont duales, et il faut les tester dans cet ordre.
| `f = []` | aucune clause | la conjonction vide est vraie |
|---|---|---|
| `List.mem [] f` | une clause vide | la disjonction vide est fausse |
Ces deux conventions ne sont pas des choix arbitraires : ce sont les éléments neutres. Une conjonction sans facteur vaut , une disjonction sans terme vaut — exactement comme une somme vide vaut et un produit vide vaut .
Correction : par récurrence sur le variant. Si est vide, toute valuation convient. Si une clause est vide, aucune ne convient. Sinon, est satisfiable si et seulement si l'est ou l'est — car toute valuation donne à l'une des deux valeurs —, et simplifier f p b est bien d'après la spécification ci-dessus.
Complexité : appels dans le pire cas, chacun en ; mais la clause vide interrompt une branche avant sa profondeur maximale, et c'est ce qui fait toute la différence en pratique.
4. Les mesures.
| Instance | variables | appels de Quine | valuations |
|---|---|---|---|
| pigeonnier | |||
| pigeonnier | |||
| pigeonnier | |||
La dernière ligne est la plus parlante : la même formule, traitée par le quine sans simplification de l'exercice 20.5, demande appels — soit l'arbre binaire complet. Avec simplification : trois. Le facteur ne tient qu'à ceci : dès que est fixée, la clause ou la clause devient vide, et les valuations des autres variables ne sont jamais visitées.
Le retour sur trace ne devient utilisable que si l'on détecte l'échec tôt. Ici, la détection est la clause vide, et la propagation est la simplification. Les solveurs sat modernes ajoutent à cela deux idées — la propagation unitaire (forcer une clause d'un seul littéral au lieu de la deviner) et l'apprentissage de clauses (retenir la cause d'un échec pour ne pas le refaire). Aucune ne change la complexité au pire cas ; toutes changent le temps d'exécution réel de plusieurs ordres de grandeur.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.