Logique propositionnelle
Cours complet · OCaml (option informatique), chapitre 17 · prépas MPSI et MP, option informatique
Travailler ce chapitre sur Adloun Exercices corrigés de ce chapitre
<i class="fa-solid fa-compass mr-2" style="color:#9A563B"></i>17.1 Introduction et motivation
La logique propositionnelle manipule des formules construites sur des variables booléennes à l'aide des connecteurs non, et, ou. Savoir si une formule est satisfiable (rendue vraie par un choix de valeurs) est le problème SAT, central en informatique théorique et omniprésent en pratique (vérification, planification, circuits).
Ce chapitre est aussi une synthèse du cours : une formule se modélise par un type somme récursif (chapitre 3), s'évalue par récursion sur sa structure, et sa satisfiabilité se décide par retour sur trace sur les valuations (chapitre 11), à l'aide d'un tableau de booléens (chapitre 5). La logique propositionnelle est le terrain de jeu idéal de la programmation fonctionnelle.
17.2 Représenter une formule
Une formule est : une variable, la négation d'une formule, la conjonction (et) ou la disjonction (ou) de deux formules. C'est un type récursif, comme les arbres du chapitre 3 — d'ailleurs une formule est un arbre.
type formule =
| Var of int (* variable numérotée *)
| Non of formule
| Et of formule * formule
| Ou of formule * formule
La formule s'écrit :
let f = Et (Ou (Var 0, Non (Var 1)), Var 2)
Les feuilles sont les variables Var i, les nœuds internes les connecteurs. La taille de la formule est le nombre de nœuds de cet arbre.
17.3 Évaluer une formule
Une valuation affecte une valeur de vérité à chaque variable ; on la code par un tableau de booléens, v.(i) étant la valeur de la variable i. L'évaluation suit la structure de la formule.
let rec evalue v f =
match f with
| Var i -> v.(i)
| Non g -> not (evalue v g)
| Et (g, h) -> evalue v g && evalue v h
| Ou (g, h) -> evalue v g || evalue v h
C'est une récursion sur l'arbre de la formule (chapitre 3) : chaque connecteur se traduit par l'opérateur booléen correspondant. && et || héritent au passage de l'évaluation paresseuse (chapitre 1).
17.4 Variables et valuations
Pour énumérer les valuations, il faut connaître le nombre de variables. On suppose les variables numérotées 0..n-1 ; n est le plus grand indice plus un.
let rec var_max f =
match f with
| Var i -> i
| Non g -> var_max g
| Et (g, h) | Ou (g, h) ->
let a = var_max g and b = var_max h in
if a >= b then a else b
let nb_variables f = var_max f + 1
Le motif Et (g, h) | Ou (g, h) regroupe les deux connecteurs binaires, traités pareillement (chapitre 3). Avec n variables, il y a valuations possibles.
17.5 Satisfiabilité et tautologie
Une formule est satisfiable s'il existe au moins une valuation qui la rend vraie ; c'est une tautologie si toute valuation la rend vraie. Une formule est satisfiable si et seulement si sa négation n'est pas une tautologie.
On décide ces propriétés en énumérant les valuations par retour sur trace : on essaie false puis true pour chaque variable.
let satisfiable f =
let n = nb_variables f in
let v = Array.make n false in
let rec essaie i =
if i = n then evalue v f (* valuation complète : on teste *)
else begin
v.(i) <- false;
if essaie (i + 1) then true (* une solution suffit *)
else begin v.(i) <- true; essaie (i + 1) end
end
in
essaie 0
Complexité : Coût de SAT
On explore jusqu'à valuations : coût exponentiel dans le pire cas. Aucun algorithme connu ne fait essentiellement mieux en général — SAT est le problème NP-complet emblématique. Le retour sur trace, avec un peu d'élagage (le || paresseux s'arrête à la première solution), reste l'approche de base.
<i class="fa-solid fa-dumbbell mr-2" style="color:#2E7559"></i>17.6 Exercices résolus
Niveau (application directe du cours)
Avec f = Et (Ou (Var 0, Non (Var 1)), Var 2), évaluer f sous la valuation x0 = false, x1 = false, x2 = true.
Démonstration
let v = [| false; false; true |]
(* evalue v f : Ou (false, not false) = Ou(false, true) = true ; Et(true, true) = true *)
evalue v f vaut true : Var 0 est false, Non (Var 1) est true, leur ou est true ; Var 2 est true ; le et final est true.
Écrire nb_variables et l'appliquer à f ci-dessus.
Démonstration
let rec var_max f =
match f with
| Var i -> i
| Non g -> var_max g
| Et (g, h) | Ou (g, h) ->
let a = var_max g and b = var_max h in if a >= b then a else b
let nb_variables f = var_max f + 1
La plus grande variable de f est Var 2, donc nb_variables f = 3. On suppose les variables numérotées sans trou de 0 à n-1.
Écrire taille f : le nombre de nœuds (variables et connecteurs) de la formule.
Démonstration
let rec taille f =
match f with
| Var _ -> 1
| Non g -> 1 + taille g
| Et (g, h) | Ou (g, h) -> 1 + taille g + taille h
Récursion sur l'arbre : une feuille compte 1, un connecteur unaire 1 + sa sous-formule, un binaire 1 + les deux. C'est la fonction taille des arbres du chapitre 3, adaptée.
Niveau (raisonnement intermédiaire)
Écrire satisfiable f et l'appliquer à Et (Var 0, Non (Var 0)).
Démonstration
let satisfiable f =
let n = nb_variables f in
let v = Array.make n false in
let rec essaie i =
if i = n then evalue v f
else begin
v.(i) <- false;
if essaie (i + 1) then true
else begin v.(i) <- true; essaie (i + 1) end
end
in
essaie 0
satisfiable (Et (Var 0, Non (Var 0))) vaut false : aucune valuation ne rend x0 et not x0 simultanément vrais — c'est une contradiction.
Écrire tautologie f et vérifier que Ou (Var 0, Non (Var 0)) (le tiers exclu) en est une.
Démonstration
let tautologie f =
let n = nb_variables f in
let v = Array.make n false in
let rec essaie i =
if i = n then evalue v f
else begin
v.(i) <- false;
essaie (i + 1) && (v.(i) <- true; essaie (i + 1))
end
in
essaie 0
Toute valuation doit rendre la formule vraie : le && exige les deux branches (x_i faux et vrai) et s'arrête au premier échec. tautologie (Ou (Var 0, Non (Var 0))) vaut true. On a aussi tautologie f = not (satisfiable (Non f)).
Écrire nb_modeles f : le nombre de valuations qui satisfont f.
Démonstration
let nb_modeles f =
let n = nb_variables f in
let v = Array.make n false in
let rec compte i =
if i = n then (if evalue v f then 1 else 0)
else begin
v.(i) <- false;
let a = compte (i + 1) in
v.(i) <- true;
a + compte (i + 1)
end
in
compte 0
On parcourt l'arbre des valuations en additionnant les contributions (comme le comptage de sous-ensembles du chapitre 11), au lieu de s'arrêter à la première. Une tautologie a modèles, une contradiction .
Niveau (approfondissement)
Écrire equivalentes f g : f et g ont-elles la même valeur sous toute valuation ?
Démonstration
let equivalentes f g =
let n =
let a = var_max f and b = var_max g in (if a >= b then a else b) + 1
in
let v = Array.make n false in
let rec verifie i =
if i = n then evalue v f = evalue v g
else begin
v.(i) <- false;
verifie (i + 1) && (v.(i) <- true; verifie (i + 1))
end
in
verifie 0
On prend le nombre de variables couvrant les deux formules, puis on vérifie l'égalité evalue v f = evalue v g sur chaque valuation. C'est une tautologie déguisée (celle de f <-> g).
On définit l'implication par implique a b = Ou (Non a, b). Vérifier que a => b et sa contraposée (not b) => (not a) sont équivalentes.
Démonstration
let implique a b = Ou (Non a, b)
let f = implique (Var 0) (Var 1) (* x0 => x1 *)
let g = implique (Non (Var 1)) (Non (Var 0)) (* (non x1) => (non x0) *)
(* equivalentes f g vaut true *)
On construit les deux formules avec implique, puis equivalentes f g confirme par énumération qu'elles ont la même table de vérité : une implication équivaut toujours à sa contraposée. Représenter les connecteurs dérivés (=>, <->) par des combinaisons des trois de base est une technique récurrente.
Écrire substitue f x g : la formule obtenue en remplaçant chaque occurrence de Var x par la formule g dans f.
Démonstration
let rec substitue f x g =
match f with
| Var i -> if i = x then g else Var i
| Non h -> Non (substitue h x g)
| Et (a, b) -> Et (substitue a x g, substitue b x g)
| Ou (a, b) -> Ou (substitue a x g, substitue b x g)
Récursion sur la structure : on descend dans la formule et l'on remplace les feuilles Var x par g, en reconstruisant un nouvel arbre (les formules sont immuables). La substitution est l'opération de base de la réécriture de formules.
Écrire trouve_modele f : bool array option qui renvoie une valuation satisfaisant f (Some v), ou None si f est insatisfiable.
Démonstration
let trouve_modele f =
let n = nb_variables f in
let v = Array.make n false in
let rec essaie i =
if i = n then (if evalue v f then Some (Array.copy v) else None)
else begin
v.(i) <- false;
match essaie (i + 1) with
| Some m -> Some m
| None -> v.(i) <- true; essaie (i + 1)
end
in
essaie 0
C'est satisfiable qui, au lieu de renvoyer true, rapporte la valuation trouvée — d'où le Array.copy v (chapitre 5) pour figer une copie indépendante de v (qui, mutable, continue d'évoluer). C'est le squelette d'un solveur SAT : explorer, et reconstruire la solution le long des choix réussis (chapitre 11).
- Une formule est un type somme récursif (
Var,Non,Et,Ou) : un arbre, traité par récursion (chapitre 3). - Évaluer sous une valuation (
bool array) : chaque connecteur devient l'opérateur booléen correspondant. - Satisfiable (existe une valuation vraie) / tautologie (toutes vraies) ; lien :
tautologie f = not (satisfiable (Non f)). - On décide par retour sur trace sur les valuations (chapitre 11) :
||paresseux pour SAT,&&pour la tautologie, addition pour compter les modèles. - SAT est exponentiel dans le pire cas (NP-complet).
- Connecteurs dérivés (
=>,<->) par combinaison des trois de base ; substitution et équivalence par récursion / énumération.
17.7 Exercices d'entraînement
Légende : application directe, raisonnement intermédiaire, approfondissement ; signale un classique incontournable. La numérotation prolonge celle des dix exercices résolus.
Thème A — Manipuler les formules.
- [11.] Écrire
profondeur f(hauteur de l'arbre de la formule). - [12.] Écrire
nb_connecteurs f(nombre deNon,Et,Ou). - [13.] Définir
equiv a b() à partir deimpliqueetEt.
Thème B — Sémantique.
- [14.] Écrire
table_verite fqui affiche (print_string) la table de vérité. - [15.]
contradiction f:fest-elle fausse sous toute valuation ? - [16.] Vérifier les lois de De Morgan :
Non (Et (a, b))équivaut àOu (Non a, Non b).
Thème C — Transformations.
- [17.]
pousse_negations f: faire « descendre » lesNonjusqu'aux variables (De Morgan, double négation). - [18.]
simplifie f: éliminer les doubles négationsNon (Non g) -> g. - [19.] Évaluer une formule sous une valuation partielle (certaines variables inconnues) renvoyant
bool option.
Thème D — Vers un solveur.
- [20.] Compter les modèles sans les énumérer tous lorsque la formule est une simple conjonction de variables.
- [21.]
toutes_solutions f: la liste de toutes les valuations satisfaisantes. - [22.] Discuter : pourquoi l'énumération est-elle exponentielle, et qu'apporterait un élagage plus malin (propagation unitaire) ?