Probleme – Un évaluateur, un compteur de modèles, un testeur de tautologie
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 20 — Logique propositionnelle
Énoncé
On travaille avec le type formule étendu par Vrai et Faux (exercice 20.5).
- Écrire
modeles : formule -> (string * bool) list list, la liste des modèles. - En déduire
tautologie,satisfiableetantilogie. Combien de parcours de la table cela demande-t-il ? - Donner la spécification, la terminaison, la correction et la complexité de
modeles. - Pourquoi
tautologie fne doit-il pas s'écrireList.length (modeles f) = 1 lsl n?
Corrigé
1. Les modèles.
(* Toutes les valuations sur vs. Il y en a 2^|vs|.
Précondition : vs sans doublon. *)
let rec valuations = function
| [] -> [[]]
| p :: reste ->
List.concat_map (fun l -> [(p, true) :: l; (p, false) :: l])
(valuations reste)
(* La valeur que la liste d'association l donne à p ; false par défaut,
ce qui ne sert jamais si l porte toutes les variables de la formule. *)
let lire l p = try List.assoc p l with Not_found -> false
(* Liste des modèles de f, comme listes d'association sur variables f.
Précondition : aucune. Postcondition : le résultat contient exactement
les valuations v de variables f telles que [[f]]_v = V. *)
let modeles f =
List.filter (fun l -> evalue (lire l) f) (valuations (variables f))
2. Les trois prédicats.
let nb_variables f = List.length (variables f)
let satisfiable f = modeles f <> []
let tautologie f =
List.for_all (fun l -> evalue (lire l) f) (valuations (variables f))
let antilogie f = not (satisfiable f)
Un seul parcours suffit, et il ne faut surtout pas le faire trois fois. Chacun des trois prédicats est un quantificateur sur la même table : pour la satisfiabilité, pour la tautologie, et l'antilogie est la négation du premier. Écrits comme ci-dessus, satisfiable et tautologie s'arrêtent dès qu'ils savent — List.for_all rend false au premier contre-modèle. C'est le même bénéfice que la paresse du chapitre chap:langage-c, et c'est ce que perd la version qui construit d'abord toute la liste.
3. La grille complète pour modeles.
Spécification. Entrée : une formule . Sortie : la liste des valuations sur qui satisfont , chacune donnée comme liste d'association. Aucune précondition.
Terminaison. valuations récurre sur une liste strictement plus courte : variant la longueur de la liste. evalue récurre sur un sous-arbre strict : induction structurelle. List.filter parcourt une liste finie. Tout termine.
Correction. Par récurrence sur : valuations [] rend [[]], l'unique valuation vide ; et si valuations reste énumère les valuations sur reste sans répétition, alors préfixer chacune par puis par en donne , deux à deux distinctes, et toutes. La correction de evalue est celle du cours (induction structurelle, la fonction reproduit la définition). Le filtre garde donc exactement les modèles.
Complexité. listes de longueur , construites en . Chaque évaluation coûte nœuds, chacun consultant List.assoc en . Total : en temps, et en espace — c'est là que le bât blesse, car on matérialise la table.
Complexité : La table ne se stocke pas, elle se parcourt
Pour , la liste des valuations pèse déjà des dizaines de gigaoctets, alors que le parcours n'aurait besoin que de booléens à la fois. La leçon vaut au-delà de ce chapitre : quand une énumération est exponentielle, on l'écrit en itérateur et jamais en liste. Ici, tautologie écrite avec List.for_all sur valuations garde ce défaut ; la version correcte énumère par récursion sans construire la liste.
4. Le piège du comptage. L'écriture List.length (modeles f) = 1 lsl n est fausse pour deux raisons distinctes, et il faut les nommer toutes les deux.
D'abord, elle est coûteuse pour rien : elle calcule tous les modèles avant de conclure, là où un seul contre-modèle suffirait. Sur une formule qui échoue à la première ligne, on paie au lieu de .
Ensuite, et c'est plus grave, n y désigne quoi ? Si c'est nb_variables f, le test est correct mais fragile ; si c'est un nombre de variables fixé par le contexte — celui d'un ensemble de formules, par exemple —, le test devient faux : a modèles sur ses propres variables et sur , et elle est tautologique dans les deux cas. Le nombre de modèles n'a de sens que relativement à un ensemble de variables déclaré. La formulation par n'a pas ce défaut : elle ne mentionne aucun nombre.
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.