Poly
Poly
LicenceSciences et Applications
Mention Informatique
Outils Logiques
Ralf Treinen
Version 6.0 du 25 septembre 2012
Adresse de l'auteur :
Ralf Treinen
Laboratoire Preuves, Programmes et Systèmes (PPS)
Université Paris Diderot - Paris 7
Case 7014
75205 PARIS Cedex 13
France
Email : treinen@[Link]
WWW : [Link]
Cet ÷uvre est protégé sous une licence creative commons Paternité - Pas d'Utilisation Com-
merciale - Pas de Modication 2.0 France. Vous êtes libres :
de reproduire, distribuer et communiquer cette création au public
Selon les conditions suivantes :
Paternité : Vous devez citer le nom de l'auteur original de la manière indiquée par l'auteur
de l'÷uvre ou le titulaire des droits qui vous confère cette autorisation (mais pas d'une
manière qui suggérerait qu'ils vous soutiennent ou approuvent votre utilisation de l'oeuvre).
Pas d'Utilisation Commerciale : Vous n'avez pas le droit d'utiliser cette création à des ns
commerciales.
Pas de Modication. Vous n'avez pas le droit de modier, de transformer ou d'adapter cette
création.
À chaque réutilisation ou distribution de cette création, vous devez faire apparaître clairement
au public les conditions contractuelles de sa mise à disposition. Chacune de ces conditions peut
être levée si vous obtenez l'autorisation du titulaire des droits sur cette ÷uvre. Rien dans ce
contrat ne diminue ou ne restreint le droit moral de l'auteur ou des auteurs.
Le text intégral de la licence est disponible à l'adresse [Link]
by-nc-nd/2.0/fr/legalcode.
Table des matières
1 Introduction 7
I Logique Propositionnelle 13
2 Dénition de la logique propositionnelle 15
2.1 Syntaxe des formules propositionnelles . . . . . . . . . . . . . . . . . . . . . . . 15
2.2 Preuves par induction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.3 Dénition de fonctions par récurrence . . . . . . . . . . . . . . . . . . . . . . . 18
2.4 Sémantique de la logique propositionnelle . . . . . . . . . . . . . . . . . . . . . 21
2.5 Raccourcis syntaxiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.6 Références et remarques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
7 Les expressions 71
7.1 Syntaxe des expressions arithmétiques et booléennes . . . . . . . . . . . . . . . 71
7.2 Sémantique des expressions arithmétiques et booléennes . . . . . . . . . . . . . 72
7.3 Validité d'expressions booléennes . . . . . . . . . . . . . . . . . . . . . . . . . . 74
7.4 Références et remarques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
8 Les programmes 77
8.1 Syntaxe des programmes IMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
8.2 Sémantique des programmes IMP . . . . . . . . . . . . . . . . . . . . . . . . . . 78
8.3 Références et remarques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Introduction
La logique peut être dénie comme l'étude des règles formelles que doit respecter toute déduc-
tion correcte [?]. La logique était à l'origine (c'est-à-dire, dans l'antiquité) une sous-discipline
de la philosophie. La logique a continué à intéresser les philosophes, mais elle a aussi trouvé
des utilisations dans des autres domaines scientiques : Dans le 19-ème et le 20-ème siècle
elle était utilisée comme base pour une nouvelle fondation de la Mathématique, et depuis le
20-ème siècle comme fondation de l'informatique. Aujourd'hui on peut dire que l'inuence de
la logique dans les mathématiques est très limitée, tandis que la logique s'est avérée comme
absolument indispensable pour l'informatique [?]. Dans ce chapitre introductif nous essayons
d'expliquer cette importance de la logique pour l'informatique. Certaines des notions logiques
et des domaines d'application dans l'informatique mentionnés dans ce chapitre seront abordés
dans ce cours d'outils logiques, d'autres seront présentés dans des cours qui interviendront plus
tard dans un cursus d'informatique.
Avant de parler des applications il faut se faire une idée de ce qu'est la logique. La Logique en
soit est un domaine scientique, mais il y a plusieurs systèmes logiques qui sont étudiés par les
logiciens (en fait on parle souvent simplement d'une logique au lieu de dire système logique ).
Dans une première approche on peut dire qu'un système logique consiste en les éléments
suivants :
Syntaxe il s'agit d'une dénition formelle (c'est-à-dire, mathématique) de l'ensemble des
énoncés qui sont considérés dans ce système logique. Ces énoncés sont normalement
exprimés dans un langage symbolique (donc, en particulier pas en langue naturelle),
et on appelle aussi un énoncé une formule. Voici deux exemples de telles formules, ces
exemples sont pris des systèmes logiques que nous allons étudier dans ce cours :
(x ∧ (y ∧ ¬z))
est une formule du premier système logique que nous allons étudier, la logique proposi-
tionnelle. Les formules de cette logique sont composées de variables propositionnelles, ici
x, y et z , et d'opérateurs logiques, ici ∧ et ¬. Nous reviendrons plus tard à ce que cette
formule veut dire, mais pour le moment nous pouvons déjà dire qu'une telle formule
ressemble beaucoup à un circuit. La logique propositionnelle est le sujet de la première
partie du cours.
Un deuxième exemple d'une formule est
¬x = 0 ∧ x ∗ y = 0
8 CHAPITRE 1. INTRODUCTION
Il s'agit ici d'une formule du système de la logique du premier ordre. Dans cette logique
il y a des variables qui dénotent des valeurs entières (comme x, y ), des opérations arith-
métiques comme la multiplication ∗, et aussi des opérateurs logiques comme ∧. Cette
logique est en fait bien adaptée pour raisonner sur le comportement d'un programme
et pour exprimer par exemples des invariants de boucles. Cela sera le sujet de la deux-
ième moitié du cours ; en fait nous n'allons pas étudier la logique du premier ordre dans
toute sa généralité mais seulement la partie de cette logique que nous allons utiliser pour
raisonner sur des programmes.
Il est important que la syntaxe soit rigoureusement dénie. Nous allons dans ce cours
dénir les formules à l'aide de dénitions inductives (cette notation sera expliquée à
l'aide d'exemples). Par dénition rigoureuse nous entendons en particulier le fait
que la dénition doit permettre de décider, sans aucun doute, si un texte donné présente
une formule et pas.
Sémantique La sémantique dénit quand une formule est vraie et quand une formule est
fausse. Elle est donnée, au moins pour les systèmes logiques qui nous intéressent ici, par
une dénition rigoureuse qui fait bien sûr référence à la dénition formelle de la syntaxe.
Très souvent, la véracité d'une formule dépend d'un contexte. La nature de ce contexte
varie d'un systèmes logique à un autre. Reprenons les deux exemples de la logique
propositionnelle et de la logique du premier ordre.
Dans la logique propositionnelle, le contexte spécie la véracité des variables proposi-
tionnelles. Ainsi, la véracité de la formule (x ∧ (y ∧ ¬z)) dépend des valeurs des variables
propositionnelles x, y , et z . La dénition de la sémantique de la logique propositionnelle
(que nous allons dénir dans le chapitre suivant) dit que cette formule est vraie dans
tout contexte dans lequel x et y sont vraies et z est fausse, et que la formule est fausse
dans tous les autres contextes.
Dans la logique du premier ordre, le contexte spécie les valeurs (qui sont des valeurs
entières) des variables. La sémantique de la logique du premier ordre (que nous allons
dénir précisément plus tard) dira que la formule ¬x = 0∧x∗y = 0 est vraie exactement
dans les contextes dans lesquelles la valeur de x est diérente de 0, est dans lesquelles
l'expression x ∗ y s'évalue à 0. On voit facilement que cela implique que y vaut 0. On dira
aussi que la formule y = 0 est une conséquence de la formule ¬x = 0 ∧ x ∗ y = 0 c'est-
à-dire que la formule y = 0 est vraie (au moins) dans tous les contextes dans lesquelles
¬x = 0 ∧ x ∗ y = 0 est vraie. La notation de conséquence est fondamentale en logique,
elle existe dans tous les systèmes logiques (pas seulement celui du premier ordre), et
nous en parlerons souvent dans ce cours.
Une autre notion importante est la validité d'un formule. Tandis que la véracité d'une
formule dépend en général du contexte, nous dirons qu'une formule est valide si elle vraie
dans tous les contextes possibles. Les deux formules données au-dessus, par exemple, ne
sont pas valides car il y a pour les deux des contextes qui les rendent vraies, mais aussi
des contextes qui les rendent fausses. Par contre, la formule
¬x = 0 ∨ x ∗ y = 0
où le symbole ∨ dénote ou , est vraie comme tous les contextes comme on peut
facilement voir en considérant les deux cas x = 0 et x 6= 0, elle donc valide.
Inférence Il s'agit des règles formelles de raisonnement. Les règles d'inférence disent comment
conclure la véracité d'un énoncé si on suppose que certaines hypothèses sont vraies. Plus
9
Cette présentation des éléments d'un système logique est volontairement simpliste, mais elle
sut pour donner une première idée des systèmes logiques que vous allez rencontrer pendant
vos études. Les spécialistes de la logique peuvent nous reprocher qu'il existe des systèmes
logiques qui ne répondent pas à cette caractérisation. En particulier, il existe des systèmes
logiques pour lesquels la sémantique n'a pas de dénition indépendante d'un système d'in-
férence, mais pour lesquels les règles d'inférence elles-mêmes dénissent la sémantique. Notre
point de vue est celui de la logique classique qui admet une notion de véracité qui vient avant
la notion de déduction ; en opposition au point de vue de la logique intuitionniste qui met la
notion de preuve à la base d'une sémantique.
Revenons à la question : quelle est l'importance de tout ça pour l'informatique ? Voici quelques
applications des systèmes logiques :
Expressions Booléennes Ces expressions existent dans tous les langages de programma-
tion. L'écriture de ces expressions suit bien sur les règles syntaxiques propres au langage de
programmation. Par exemple
Ces expressions Booléennes sont par exemple utilisées comme gardes dans des instructions
conditionnelles ou des boucles.
Questions qui nous intéressent dans ce contexte :
Est-ce qu'on peut remplacer une formule par une formule équivalente qui est moins coûteuse
à évaluer ? Cela permettra à un compilateur de simplier la formule avant d'engendrer le
code, et ainsi d'optimiser le temps de calcul.
Est-ce qu'une formule donnée est toujours vraie ou toujours fausse ? Cela permettra à un
compilateur d'omettre un test inutile, et ainsi d'optimiser le temps de calcul et aussi la
taille du programme. Un compilateur pourrait aussi utiliser un tel test pour détecter des
erreurs de programmation probables : une conditionnelle avec une expression de garde qui
est toujours vraie ou toujours fausse est souvent signe d'une erreur de programmation.
Circuits Les circuits sont une réalisation matérielle des formules de la logique proposition-
nelle, dans le sens que les variables propositionnelles correspondent aux entrées d'un circuit, les
opérateurs logiques correspondent aux portes logiques, et la véracité de la formule correspond
à la sortie du circuit. Ainsi, la formule (x ∧ (y ∧ ¬z)) correspond au circuit suivant :
10 CHAPITRE 1. INTRODUCTION
z := 0;
i := 0;
while i 6= y do
z := z + x;
i := i + 1;
done
z := 0;
i := 0;
while i 6= y do
{ z = i * x }
z := z + x;
i := i + 1;
done
Dans cet exemple, l'invariant est z = i ∗ x, et nous prétendons qu'il est vrai chaque fois que le
programme passe par le début du corps de la boucle. Nous le montrons par une induction sur
le nombre n d'itérations de la boucle :
11
z 0 = i0 ∗ x0
z = z 0 + x0
x = x0
i = i0 + 1
En d'autres mots, pendant cette exécution du corps de la boucle on incrément z par x (=x0 )
et i par 1, donc on a augmenté chacun des deux côtés de l'équation par la même valeur x.
L'équation z = i ∗ x en suit par un calcul facile.
Finalement, quand on sort de la boucle la condition de garde de la boucle est forcement fausse,
c'est-à-dire on a que i = y . L'énoncé en suit car z = y ∗ x est une conséquence de z = i ∗ x et
i = y.
Les assertions dans les programmes sont extrêmement utiles car elles permettent de com-
prendre, ou d'expliquer à un tiers le fonctionnement d'un programme ; elles ouvrent la voie
à la vérication automatique des programmes, et ils apportent même une méthode pour le
développement de programmes. Quelques questions pertinentes dans ce contexte sont :
Comment trouver les bonnes assertions pour un programme donné ?
Étant donné un programme annoté avec des assertions, comment vérier que les assertions
sont vraies ?
Bases de données C'est la première application qui ne sera pas discutée dans le cadre de
ce cours mais que vous allez rencontrer plus tard pendant vos études. Les bases de données
modernes (par exemple les systèmes MySQL et Postgres qui sont aujourd'hui omniprésents
dans les applications Web) utilisent un langage de requêtes appelé SQL. Dans ce langage, les
requêtes sont spéciées dans une variante notationnelle de la logique du premier ordre, Par
exemple, la requête
SELECT nom FROM persons WHERE age > 18 AND ( children > 0 OR married = yes)
revient à évaluer la formule logique age > 0 ∧ (children > 0 ∨ married = yes) sur tous les
éléments de la table avec le nom persons. Les questions qui nous intéressent dans ce contexte
sont :
Comment évaluer ecacement une requête (formule logique) ?
Comment transformer une requête pour rendre son évaluation plus ecace ? Cela est une
question importante pour des bases de données de très grande taille.
12 CHAPITRE 1. INTRODUCTION
Le cours va clore sur une question fondamentale de l'Informatique : Est-ce qu'il existe pour
tous les problèmes mathématiques (c'est à dire des problèmes bien posés, pas du genre de
la Grande Question sur la Vie, l'Univers et le Reste [?]), un programme qui peut répondre
correctement à une question posée ? Un exemple d'un tel problème est celui de la validité
d'une formule : étant donnée une formule (propositionnelle ou de la logique du premier ordre),
est-elle valide ? Nous allons montrer qu'il y a des problèmes mathématiques pour lesquels un
tel programme ne peut pas exister par principe, et nous allons exhiber un exemple concret
d'un tel problème.
Première partie
Logique Propositionnelle
Chapitre 2
Dénition de la logique
propositionnelle
V := {x, x1 , x2 , x3 , . . . , y, y1 , y2 , . . . , z, z1 , z2 , z3 , . . .}
Nous admettons également les décorations habituelles des variables propositionnelles comme
par exemple x0 , y 00 .
Faisons un premier essai d'une dénition des formules propositionnelles :
Tentative de dénition : L'ensemble Form des formules propositionnelles est déni comme
l'ensemble de chaînes de caractères tel que :
1. V ⊆ Form
2. Si p ∈ Form alors ¬p ∈ Form
16 CHAPITRE 2. DÉFINITION DE LA LOGIQUE PROPOSITIONNELLE
Dénition 1 L'ensemble Form des formules propositionnelles est le plus petit ensemble de
chaînes de caractères tel que :
1. V ⊆ Form
2. Si p ∈ Form alors ¬p ∈ Form
3. Si p, q ∈ Form alors (p ∧ q) ∈ Form
4. Si p, q ∈ Form alors (p ∨ q) ∈ Form
x ∨
y ¬
Pour montrer ce théorème il convient d'introduire une petite notation : |w|( dénote le nombre
de parenthèses ouvrantes de w, et |w|) dénote le nombre de parenthèses fermantes de w. Avec
cette notation nous pouvons reformuler l'énoncé comme : pour tout w ∈ Form, |w|( = |w|) .
Démonstration: Soit X l'ensemble des chaînes de caractères qui ont le même nombre de
parenthèses ouvrantes que de parenthèses fermantes :
X = {w | |w|( = |w|) }
Nous montrons d'abord que cet ensemble X satisfait les propriétés de clôture des formules
propositionnelles :
18 CHAPITRE 2. DÉFINITION DE LA LOGIQUE PROPOSITIONNELLE
Donc, X est un ensemble qui satisfait les propriétés de clôture des formules propositionnelles.
Or, Form est le plus petit ensemble qui satisfait les propriétés de clôtures des formules propo-
sitionnelles selon la dénition de Form. Par conséquent, Form ⊆ X . 2
On peut maintenant dénir plus généralement ce principe de preuve pour une propriété, ici
appelée P , des formules propositionnelles :
Théorème 2 Soit P une propriété des chaînes de caractères, Si les énoncés suivants sont
vrais :
tout élément de V a la propriété P ,
si p satisfait P alors ¬p satisfait P ,
si p et q satisfont P alors (p ∧ q) satisfait P ,
si p et q satisfont P alors (p ∨ q) satisfait P ,
alors tous les éléments de Form satisfont P .
Dans ce cas, l'expression close est x + 1. Pour évaluer la fonction f sur un argument donné,
par exemple 17, on remplace dans l'expression de la dénition de la fonction f (c.-à-d. x + 1)
la variable qui sert comme paramètre (c.-à-d. x) par sa valeur (17), et on évalue l'expression
qui en résulte :
17 + 1 = 18
On peut aussi dénir de la même façon certaines fonctions sur les formules propositionnelles.
Par exemple, la fonction qui envoie son argument, qui est une formule propositionnelle, vers
sa négation peut être dénie par
g(x) := ¬x
Il y a peu de fonctions intéressantes sur les formules propositionnelles qui peuvent être dénies
de cette façon. En particulier il nous faudrait la possibilité de prendre en compte la façon dont
une formule est construite (par ∧, par ∨, . . .), de décomposer l'argument en sous-formules, et
éventuellement de faire un calcul sur les sous-formules pour obtenir la valeur envoyée par la
fonction. Le principe de dénition d'une fonction par récurrence nous donne cette possibilité.
Ce principe peut être énoncé comme suit :
Dénition d'une fonction sur Form par récurrence On peut dénir une fonction avec
le domaine Form comme suit :
1. on donne le résultat de la fonction appliquée à un élément quelconque de V ,
2. on donne le résultat de la fonction appliquée à une formule de la forme ¬p, en supposant
le résultat de la fonction appliquée à p connu,
3. on donne le résultat de la fonction appliquée à une formule de la forme (p ∧ q), en
supposant le résultat de la fonction appliquée à p et le résultat de la fonction appliquée
à q connus.
4. on donne le résultat de la fonction appliquée à une formule de la forme (p ∨ q), en
supposant le résultat de la fonction appliquée à p et le résultat de la fonction appliquée
à q connus.
Illustrons d'abord ce principe par deux exemples. La fonction length est dénie comme suit :
1. length(x) = 1 si x ∈ V
2. length(¬p) = 1 + length(p)
3. length((p ∧ q)) = 3 + length(p) + length(q)
4. length((p ∨ q)) = 3 + length(p) + length(q)
Intuitivement, la fonction length calcule le nombre de caractères dans une formule, où on
pose que la longueur d'une variable propositionnelle est 1. Pour évaluer cette fonction sur un
argument donné on déplie la dénition de la fonctions selon la structure de l'argument
donné. Dans l'exemple suivant nous donnons dans chaque ligne le numéro de la clause (de la
dénition de la fonction length) que nous avons utilisée pour le dépliage :
length((x1 ∧ ¬x2 )) = 3 + length(x1 ) + length(¬x2 ) (3)
= 3 + 1 + length(¬x2 ) (1)
= 3 + 1 + 1 + length(x2 ) (2)
= 3+1+1+1 (1)
= 6
20 CHAPITRE 2. DÉFINITION DE LA LOGIQUE PROPOSITIONNELLE
Exercice 1 Montrer par induction structurelle que pour toute formule p ∈ Form :
card(V(p)) ≤ length(p)
Il y a une subtilité derrière ce principe de dénition. Une fonction doit toujours associer à
un argument donné un seul résultat unique. On doit donc assurer qu'une dénition récursive
d'une fonction garantie bien cette unicité du résultat. Le problème est que, en général, la
même formule pourrait être construite de deux façon diérentes, et que la dénition de la
fonction donne deux valeurs diérentes selon la construction considérée. Heureusement ce
risque n'existe pas pour les formules de la logique propositionnelle car toute formule peut être
construite d'une seule façon, c'est-à-dire il y a un seul arbre syntaxique pour toute formule
(il y a derrière cette assertion le théorème de lecture unique que nous n'allons pas montrer
ici). Sachez que quand on essaye de généraliser les dénitions par induction et récurrence il
faut donc assurer cette unicité de la construction, sinon on aura le souci supplémentaire de
montrer qu'une fonction dénie par récurrence est eectivement bien dénie.
2.4. SÉMANTIQUE DE LA LOGIQUE PROPOSITIONNELLE 21
v : V → {0, 1}
supp(v) = {x ∈ V | v(x) = 1}
Nous précisons qu'une aectation est toujours une fonction totale, c'est-à-dire une fonction qui
à chaque variable associe une valeur. Par exemple, la fonction v1 qui associe à x la valeur 1,
à y la valeur 1, et qui associe à toute autre variable la valeur 0 est une aectation, et son
support est {x, y}. Sont également des aectations la fonction qui associe à chaque variable la
valeur 0, ou la fonction qui associe à chaque variable la valeur 1, leur support est respectivement
l'ensemble vide et l'ensemble V .
Nous utilisons la notation suivante pour noter des aectations :
[x1 7→ 1, x2 7→ 1, x3 7→ 1, . . . , xn 7→ 1]
est l'aectation qui aux variables x1 , . . . , xn associe la valeur 1, et qui associe à toute autre
variable la valeur 0. Par exemple, [x 7→ 1, y 7→ 1] est l'aectation qui associe à x la valeur 1,
à y la valeur 1, et qui associe à toute autre variable la valeur 0 ; et [] est l'aectation qui
associe à toute variable la valeur 0. On peut dans cette notation aussi ajouter des valeurs 0
pour certaines variables, comme dans [x 7→ 0, y 7→ 1] mais cela n'est pas très utile car 0 est de
toute façon la valeur de défaut .
Une aectation avec un support inni, comme par exemple la fonction qui associe à toute
variable la valeur 1, ne peut évidemment pas être représentée de cette façon. Cela ne nous
dérange pas car dans les applications on s'intéresse seulement à un nombre ni de variables.
Par ailleurs, on peut toujours faire recours à une notation mathématique si on en a besoin.
Dénition 3 L'interprétation [[p]]v d'une formule p par rapport à l'aectation v est dénie
22 CHAPITRE 2. DÉFINITION DE LA LOGIQUE PROPOSITIONNELLE
si [[p]]v = 1
1
[[(p ∨ q)]]v =
[[q]]v si [[p]]v = 0
Exercice 2 Le démontrer.
Pour savoir si une formule propositionnelle donnée est satisfaisable ou valide il faut donc
en principe évaluer la formule sur toutes les aectations possibles, ce qui pose une petit
problème : il y a un nombre inni d'aectations possibles car il y a un nombre inni de
variables propositionnelles ! Heureusement, il y a un théorème qui nous dit que seulement les
variables qui paraissent dans une formule sont pertinentes.
Proposition 3 Soit p une formule propositionnelle et v1 , v2 des aectations telles que v1 (x) =
v2 (x) pour toute variable x ∈ V(p). Alors [[p]]v1 = [[p]]v2 .
Démonstration: Nous montrons ici seulement le premier énoncé, le deuxième est laissé
comme exercice.
Si v |= p avec supp(v) ⊆ V(p) alors p est, par dénition satisfaisable.
Si p est satisfaisable il y a une aectation w telle que w |= p. Nous construisons une nouvelle
aectation v comme suit :
w(x) si x ∈ V(p)
v(x) =
0 si x 6∈ V(p)
On a que supp(v) ⊆ V(p), et v |= p par proposition 3. 2
Il sut donc de tester les aectations qui ont pour domaine V(p). L'avantage est qu'il y en un
nombre ni, plus exactement il y a 2n telles aectations quand p contient n variables. C'est
donc à priori faisable mais potentiellement coûteux.
Une méthode pour déterminer si une formule p est satisfaisable est donc :
1. Calculer V(p)
2. Engendrer l'ensemble A des aectations v avec supp(v) ⊆ V(p)
3. Évaluer [[p]]v pour toute aectation v ∈ A. Dès qu'on tombe sur un v tel que [[p]]v = 1
on sait que p est satisfaisable, si on n'en trouve pas alors p n'est pas satisfaisable.
Une méthode pour déterminer si une formule p est valide est :
1. Calculer V(p)
2. Engendrer l'ensemble A des aectations v avec supp(v) ⊆ V(p)
3. Évaluer [[p]]v pour tout v ∈ A. Dès qu'on tombe sur un v tel que [[p]]v = 0 on sait que p
n'est pas valide, si on n'en trouve pas alors p est valide.
En pratique on utilise souvent des tables de vérité pour déterminer la satisfaisabilité ou la
validité d'une formule p. Les colonnes d'une telle table de vérité sont étiquetées par des sous-
formules de p : au début (dans les colonnes à gauche) on a toutes les variables propositionnelles
qui paraissent dans p, puis les sous-formules de p dans un ordre croissant de complexité, et
nalement la formule p elle-même. La table a 2n lignes où n est le nombre de variables proposi-
tionnelles qui paraissent dans la formule. Maintenant, on remplit toutes les cases des colonnes
qui correspondent à des variables propositionnelles par toutes les combinaisons possibles de 0
et 1. Il convient de choisir un système pour ne pas oublier une combinaison, par exemple on fait
dans la première colonne une suite alternante 01010101 . . ., dans la deuxième colonne une suite
alternante à deux pas 00110011 . . ., dans la troisième 00001111 . . ., etc. De cette façon toute
ligne correspond à une aectation diérente v avec supp(v) ⊆ V(p). Puis, on rempli toutes
les autres colonnes de la table, de gauche vers la droite. Puisqu'on a choisi les étiquettes des
colonnes dans un ordre croissant de complexité, on trouve toujours les interprétations des
sous-formules d'une formule composée dans des colonnes qui sont déjà remplies.
Exemple : Soit p la formule ((x1 ∧ x2 ) ∨ (x3 ∧ ¬x2 )). La table de vérité est comme suit. Nous
indiquons dans cet exemple sur la ligne Calcul comment les entrées sont calculées, normalement
2.5. RACCOURCIS SYNTAXIQUES 25
les lignes Numéro et Calcul sont omises dans une table de vérité.
Formule x1 x2 x3 ¬x2 (x1 ∧ x2 ) (x3 ∧ ¬x2 ) ((x1 ∧ x2 ) ∨ (x3 ∧ ¬x2 ))
Numéro (1) (2) (3) (4) (5) (6) (7)
Calcul ¬(2) (1) ∧ (2) (3) ∧ (4) (5) ∨ (6)
0 0 0 1 0 0 0
1 0 0 1 0 0 0
0 1 0 0 0 0 0
1 1 0 0 1 0 1
0 0 1 1 0 1 1
1 0 1 1 0 1 1
0 1 1 0 0 0 0
1 1 1 0 1 0 1
où nous avons agrandi un peu les parenthèses des applications de ∨ pour mieux faire paraître
la structure.
On peut aussi se poser la question de savoir pourquoi nous avons déni les formules propo-
sitionnelles si strictement avec toutes ces parenthèses. La raison est qu'une dénition stricte
est avantageuse quand on fait des preuves de propriétés des formules, ou quand on dénit
des fonctions par récurrence sur les formules. Dans la syntaxe stricte il n'y a que quatre cas
assez simples (variable, ¬p, (p ∧ q), (p ∨ q)). Si on avait choisi la syntaxe libérale comme
dénition de la syntaxe on aurait des cas plus compliqués à considérer, comme par exemple
(p1 ∧ p2 ∧ . . . ∧ pn ), ce qui risque de nécessiter une deuxième induction sur le nombre n. Notre
façon de dénir les formules propositionnelles combine les deux avantages : d'une part des cas
simples dans les preuves par induction et dénitions des fonctions par récurrence, d'autre part
une certaine souplesse dans l'écriture des formules en pratique.
En fait on pourrait être encore plus libéral et aussi permettre l'utilisation des priorités entre
opérateurs, comme c'est souvent le cas dans les langages de programmation. Les priorités
permettent encore plus d'économie dans l'utilisation des parenthèses. Dans ce cours nous
avons choisi de ne pas considérer les priorités entre opérateurs, une raison pour ce choix
est que l'utilisation des priorités rende la tâche d'analyse des formules par un programme
beaucoup plus dicile. Ce problème, et sa solution, est un sujet du cours Analyse Syntaxique
de l'année L3.
Est-ce qu'il y a un algorithme qui est plus rapide que la méthode des tables de vérité, par
exemple un algorithme qui peut déterminer la satisfaisabilité d'une formule avec n variables
dans un temps de calcul qui est un polynôme en n (par exemple n3 ) ? Il s'agit ici de la
question ouverte la plus célèbre de l'informatique ; les informaticiens cherchent une réponse à
cette question depuis plus que 30 ans. Il y a des algorithmes qui trouvent des réponses avec
une étonnante rapidité pour des classes de formules particulières, mais la question est s'il y a
un algorithme qui peut répondre rapidement pour n'importe quelle formule qu'on lui présente.
La majorité des informaticiens pensent aujourd'hui qu'un tel algorithme ne peut pas exister,
mais à ce jour personne n'a réussi à le démontrer. Les questions de ce type sont l'objet d'étude
d'un domaine important de l'Informatique : la théorie de la Complexité.
Chapitre 3
Donc, deux formules p et q sont équivalentes si et seulement si [[p]]v = [[q]]v pour toute aec-
tation v . Parfois on s'intéresse aussi à la notion d'une conséquence de tout en ensemble de
formules :
On utilise les tables de vérité pour savoir si une formule est conséquence d'une autre ou si
deux formules sont équivalentes. La formule q est une conséquence de p si pour toute ligne de
la table de vérité où il y a 1 dans la colonne de p il y aussi 1 dans la colonne de q ; les formules
p et q sont équivalentes quand le contenu de la colonne de p est le même que le contenu de la
colonne de q .
Par exemple, x ∧ (¬x ∨ y) |= y car
x y ¬x ¬x ∨ y x ∧ (¬x ∨ y) y
0 0 1 1 0 0
1 0 0 0 0 0
0 1 1 1 0 1
1 1 0 1 1 1
28 CHAPITRE 3. LOIS DE LA LOGIQUE PROPOSITIONNELLE
On dit aussi que les opérateurs ∧ et ∨ sont idempotents (1 et 4), commutatifs (2 et 5), et asso-
ciatifs (3 et 6). Il s'agit ici de propriétés importantes d'opérations mathématiques en général.
Par exemple, les opérations d'addition et de multiplication des entiers sont commutatifs et
associatifs, mais pas idempotent.
Exercice 6 Dire pour chacun des opérateurs (sur les entiers) suivants s'il est idempotent,
commutatif, associatif :
1. le maximum de deux entiers, déni par x Z y = maximum(x, y)
2. la moyenne de deux entiers, déni par x 1 y = x+y 2
3. l'opérateur de projection gauche, déni par x / y = x
Démonstration: Par table de vérité pour les trois premier équivalences, les trois équivalences
pour ∨ et la dernière équivalence pour la double négation se montrent de la même façon.
1. x ∧ x |=| x :
x x∧x
0 0
1 1
2. x ∧ y |=| y ∧ x :
x y x∧y y∧x
0 0 0 0
1 0 0 0
0 1 0 0
1 1 1 1
3.3. OBTENIR DES TAUTOLOGIES ET DES ÉQUIVALENCES PAR INSTANCIATION
29
3. x ∧ (y ∧ z) |=| (x ∧ y) ∧ z :
x y z y ∧ z x ∧ (y ∧ z) x ∧ y (x ∧ y) ∧ z
0 0 0 0 0 0 0
1 0 0 0 0 0 0
0 1 0 0 0 0 0
1 1 0 0 0 1 0
0 0 1 0 0 0 0
1 0 1 0 0 0 0
0 1 1 1 0 0 0
1 1 1 1 1 1 1
2
Par exemple, si
p = z ∨ ¬y
alors
(x ∧ ¬x ∧ y)[x/p] = (z ∨ ¬y) ∧ ¬(z ∨ ¬y) ∧ y
Cette dénition se généralise facilement à une substitution simultanée q[x1 /p1 , . . . , xn /pn ] pour
le cas où les x1 , . . . , xn sont toutes des variables diérentes. Par exemple, si
p1 = (y1 ∧ ¬y2 )
p2 = (z1 ∨ (z2 ∧ z3 ))
alors on a que
(x1 ∧ x2 )[x1 /p1 , x2 /p2 ] = (y1 ∧ ¬y2 ) ∧ (z1 ∨ (z2 ∧ z3 ))
30 CHAPITRE 3. LOIS DE LA LOGIQUE PROPOSITIONNELLE
Attention, on n'a pas toujours que q[x1 /p1 , x2 /p2 ] = (q[x1 /p1 ])[x2 /p2 ]. Un contre-exemple est
obtenu on choissisant q = x1 , p1 = (x2 ∧ x2 ), et p2 = (x3 ∨ x3 ) :
Dénition 8 Soit v une aectation, x une variable propositionnelle, et b ∈ {0, 1}. Alors
v[x/b] est l'aectation dénie comme suit :
si x = y
b
v[x/b](y) =
v(y) si x 6= y
Cette dénition se généralise aussi à une substitution simultanée v[x1 /b1 , . . . , xn /bn ] pour le
cas où les x1 , . . . , xn sont toutes des variables diérentes. Par exemple,
[x 7→ 0, y 7→ 1][y/0, z/1] = [x 7→ 0, y 7→ 0, z 7→ 1]
Théorème 6 Soient q1 , q2 deux formules telles que q1 |=| q2 , x1 , . . . , xn des variables proposi-
tionnelles diérentes, et p1 , . . . , pn des formules propositionnelles. Alors
q1 [x1 /p1 , . . . , xn /pn ] |=| q2 [x1 /p1 , . . . , xn /pn ]
32 CHAPITRE 3. LOIS DE LA LOGIQUE PROPOSITIONNELLE
Démonstration: Comme la preuve du théorème 7. Les équivalences à la base des deux lois
de distributivité seront montrées en TD. L'équivalence utilisée pour la preuve de la première
loi de de Morgan était montré sur la page 28. 2
Puisque pi |=| qi pour tout i on a aussi que [[pi ]]v = [[qi ]]v pour tout i. On obtient la chaîne
d'égalités suivante :
3.5. AUTRES OPÉRATEURS BOOLÉENS 33
Nand et Nor sont des noms anglais (contraction de Not-And et Not-Or), et on dit
parfois xor à la place de Ou exclusif . On pourrait aussi imaginer des opérateurs
booléens avec plus que deux arguments.
Par exemple,
(x ⊕ y) → (x ∨ y)
est une abréviation pour
¬((x ∧ ¬y) ∨ (¬x ∧ y)) ∨ (x ∨ y)
Ces nouveaux opérateurs nous permettent d'écrire des nouvelles lois de la logique proposition-
nelle, comme par exemple :
34 CHAPITRE 3. LOIS DE LA LOGIQUE PROPOSITIONNELLE
Cette formule est vraie dans une aectation v si et seulement si v(x1 ) = 1, v(x2 ) = 0, v(x3 ) = 0,
et v(x4 ) = 1. Maintenant on peut construire la formule p comme la disjonction de toutes les
formules pc pour les n-uplets c pour lesquelles f donne le résultat 1.
Exemple : Soit f la fonction à trois arguments qui envoie 1 si et seulement si un des ses
arguments est 0 et deux de ses arguments sont 1. Autrement dit, f envoie 1 exactement pour
les arguments (0, 1, 1), (1, 0, 1), et (1, 1, 0). La formule correspondante est alors
On dit que l'ensemble d'opérateurs {¬, ∧, ∨} est fonctionnellement complet . D'après la discus-
sion de cette section, les ensembles {¬, ∧}, {¬, ∨} et {¬, →} sont également fonctionnellement
complets.
Proposition 6 Les deux énoncés suivants sont équivalents pour toutes formules proposition-
nelles p et q :
1. p |= q
2. |= p → q
Dénition 9 Une formule est en forme normale de négation si elle ne contient pas d'appli-
cations de l'opérateur ¬ sauf des applications à des variables propositionnelles.
φ(¬p) = (φ(p))2 + 1
Alors pour toute formule p ∈ F orm : Si p se transforme en q par une application d'une des
règles 4.1, 4.2 or 4.3 alors φ(p) > φ(q).
Exercice 9 Montrer par induction pour la fonction φ de la proposition 7 que φ(p) ≥ 1 pour
toute formule p.
φ(¬(p2 ∧ p3 ))
= φ((p2 ∧ p3 ))2 + 1 par dénition de φ
= (φ(p2 ) + φ(p3 ))2 + 1 par dénition de φ
= (φ(p2 ))2 + 2φ(p2 )φ(p3 ) + (φ(p3 ))2 + 1
> (φ(p2 ))2 + 1 + (φ(p3 ))2 + 1 car φ(p2 ) ≥ 1 et φ(p3 ) ≥ 1
= φ(¬p2 ) + φ(¬p3 ) par dénition de φ
= φ((¬p2 ∨ ¬p3 )) par dénition de φ
4. Il s'agit d'une application de la règle 4.3 à la racine : analogue au cas précédent. 2
Le théorème suivant dit qu'on obtient toujours à la n de ce processus une formule équivalente
en forme normale de négation. Remarquez que cette description du processus laisse souvent
des libertés de choix car nous n'avons pas spécié quoi faire quand il y a plusieurs des règles
qui peuvent s'appliquer à une formule, comme par exemple pour la formule
¬¬(x1 ∨ ¬(x2 ∧ x3 ))
Le théorème ne fait pas d'hypothèse sur la stratégie d'application des règles dans de tels cas.
L'avantage d'une description si générale d'une méthode de transformation est qu'un program-
meur a le choix d'implanter la stratégie qui lui semble la meilleure (parce qu'elle est la plus
facile à mettre en ÷uvre, ou parce qu'une certaine stratégie mène plus rapidement au résultat
qu'une autre).
Théorème 10 Pour toute formule il existe une formule équivalente en forme normale de
négation.
donc en p une occurrence du symbole de négation qui n'est pas appliquée à une variable
propositionnelle. Ils restent trois possibilités pour la sous-formule qui commence sur cette
négation :
1. C'est une formule de la forme ¬¬q : la règle (4.1) s'applique, contradiction
2. C'est une formule de la forme ¬(q1 ∧ q2 ) : la règle (4.2) s'applique, contradiction
3. C'est une formule de la forme ¬(q1 ∨ q2 ) : la règle (4.3) s'applique, contradiction 2
Dénition 10 1. Un littéral est soit une variable propositionnelle, soit la négation d'une
variable propositionnelle.
2. Une clause conjonctive est soit la constante True, soit un littéral, soit une conjonction
d'aux moins deux littéraux.
3. Une formule est en forme disjonctive normale si elle est soit la constante False, soit
une clause conjonctive, soit une disjonction d'aux moins deux clauses conjonctives.
On écrit parfois plus brièvement DNF, c'est l'abréviation du nom anglais disjonctive normal
form.
Par une conjonction d'aux moins deux littéraux nous entendons une formule de la forme
(l1 ∧ (l2 ∧ (l3 ∧ . . .) . . .))
où chacun des li est un littéral, et par une disjonction d'aux moins deux clauses conjonctives
une formule de la forme
(c1 ∨ (c2 ∨ (c3 ∨ . . .) . . .))
où chacun des ci est une clause conjonctive. Quand on parle de clauses conjonctives et de
formes disjonctives normales on utilise la syntaxe libérale introduite en section 2.5, et on
inclut les constantes True et False dans la syntaxe. En fait on peut simplier la dénition si
on admet que la disjonction (la conjonction) d'une seule formule est la formule elle même, et
que la disjonction de zéro formules est False et la conjonction de zéro formules est True. La
justication pour cette dernière convention est que la conjonction d'une formule w avec zéro
d'autre formules doit donner une formule équivalente à w. Par conséquent, la conjonction de
zéro formules est True car w ∧ True est équivalent à w. De la même façon, la disjonction d'une
formule w avec une disjonction de zéro d'autre formules doit donner une formule équivalente
à w, la disjonction de zéro formules est donc False car w ∨ False est équivalent à w. Ou
autrement dit, la conjonction de zéro formules est l'élément neutre de la conjonction (True)
et la disjonction de zéro formules est l'élément neutre de la disjonction (False). On trouve
des conventions similaires dans les Mathématiques, en arithmétique par exemple on dit que
la somme de zéro nombres est 0 (l'élément neutre de l'addition) et que le produit de zéros
nombres est 1 (l'élément neutre de la multiplication).
Avec cette convention, on peut donc dire :
42 CHAPITRE 4. FORMES NORMALES DANS LA LOGIQUE PROPOSITIONNELLE
X ∧ (Y ∨ Z) → (X ∧ Y ) ∨ (X ∧ Z) (4.4)
(X ∨ Y ) ∧ Z → (X ∧ Z) ∨ (Y ∧ Z) (4.5)
(X ∧ Y ) ∧ Z → X ∧ (Y ∧ Z) (4.6)
(X ∨ Y ) ∨ Z → X ∨ (Y ∨ Z) (4.7)
Par exemple,
x1 ∧ (¬(y1 ∨ y2 ) ∧ ¬¬(z1 ∨ z2 ))
se transforme en x1 ∧ (¬(y1 ∨ y2 ) ∧ (z1 ∨ z2 )) par règle 4.1
se transforme en x1 ∧ ((¬y1 ∧ ¬y2 ) ∧ (z1 ∨ z2 )) par règle 4.3
se transforme en x1 ∧ (((¬y1 ∧ ¬y2 ) ∧ z1 ) ∨ ((¬y1 ∧ ¬y2 ) ∧ z2 )) par règle 4.4
se transforme en x1 ∧ ((¬y1 ∧ ¬y2 ∧ z1 ) ∨ (¬y1 ∧ ¬y2 ∧ z2 )) par règle 4.6 (2 fois)
se transforme en (x1 ∧ ¬y1 ∧ ¬y2 ∧ z1 ) ∨ (x1 ∧ ¬y1 ∧ ¬y2 ∧ z2 ) par règle 4.5
Théorème 11 Pour toute formule il existe une formule équivalente en forme disjonctive nor-
male.
Démonstration: Grâce au théorème 10 il existe pour toute formule une formule équivalente
en forme normale de négation. Nous appliquons les règles de transformation 4.4 à 4.7 autant
que possible à cette formule en forme normale de négation. Le reste de la preuve suit le même
schéma que la preuve du théorème 10 : Nous devrons montrer que :
1. Le processus de transformation s'arrête toujours : pour une formule p donnée on arrive
toujours après un nombre ni de transformations sur une formule qui ne peut plus être
transformée.
4.3. FORME DISJONCTIVE NORMALE 43
donne une formule avec 2n clauses conjonctives, chaque clause consistant en n littéraux.
Un avantage d'une forme disjonctive normale est qu'on peut voir facilement si la formule est
satisfaisable ou pas.
est satisfaisable si et seulement s'il y a un i tel que la clause ci ne contient pas à la fois une
variable x et aussi sa négation.
Nous avons expliqué à la n de la section 2.4 qu'on connaît à ce jour aucun algorithme
ecace , c'est-à-dire dont le temps d'exécution ne croit pas de façon exponentielle avec
la taille de la formule, pour décider la satisfaisabilité d'une formule. Or, le critère énoncé
dans le théorème 12 est très simple à vérier par un seul passage de la formule de gauche
à droite. Est-ce qu'il n'y a pas une contradiction avec la diculté perçue du problème de
décider la satisfaisabilité d'une formule ? L'explication de se paradoxe apparent est très simple :
Étant donnée une formule propositionnelle quelconque il faut d'abord la transformer en forme
disjonctive normale, avant d'appliquer le critère du théorème 12 à la formule obtenue. Or, cette
transformation implique un gonement exponentiel de la taille de la formule, et l'exécution
d'un algorithme ecace sur la formule de taille exponentielle se traduit alors en n de compte
toujours en un algorithme qui est exponentiel par rapport à la taille de la formule de départ.
La réponse à la question 1 est oui car les deux formules peuvent déjà diérer dans l'ordre
des littéraux dans les clauses conjonctives, ou dans l'ordre des clauses conjonctives dans la
formule entière. Par exemple, les trois formules suivantes sont toutes en forme disjonctive
normale, elles sont équivalentes mais elles sont toutes syntaxiquement diérentes :
(x ∧ y) ∨ (¬z ∧ ¬y)
(y ∧ x) ∨ (¬z ∧ ¬y)
(¬z ∧ ¬y) ∨ (y ∧ x)
On pourrait dire que les diérences entre ces trois formules ne sont pas vraiment essentielles,
et on peut raner la question :
Question 2 : Est-il possible que deux formules en forme disjonctive normale avec
plus de diérence que simplement l'ordre des littéraux ou l'ordre des clauses, soient
équivalentes ?
Dénition 11 Une clause conjonctive l1 ∧. . .∧ln subsume une clause conjonctive k1 ∧. . .∧km
si pour tout i avec 1 ≤ i ≤ n il existe un j avec 1 ≤ j ≤ m tel que kj = li .
Autrement dit, une clause c subsume une clause d si c est incluse en d à l'ordre des
littéraux et à des éventuelles répétitions près. Par exemple, x ∧ ¬y subsume z ∧ x ∧ ¬y , et
aussi z ∧ ¬y ∧ z ∧ x. On a évidemment que
Autrement dit, on obtient une formule équivalente quand on supprime dans une forme dis-
jonctive normale une clause qui est subsumée par une autre clause. Les clauses subsumées sont
redondantes. La preuve est une simple application de la proposition 9. Par exemple,
La réponse à la question 2 est que c'est toujours possible, comme on a vu sur l'exemple
précédent. On peut donc essayer de raner encore la question :
Question 3 : Est-il possible que deux formules en forme disjonctive normale avec
plus de diérence que simplement l'ordre des littéraux ou l'ordre des clauses, et où
aucune clause subsume une autre dans la même formule, soient équivalentes ?
46 CHAPITRE 4. FORMES NORMALES DANS LA LOGIQUE PROPOSITIONNELLE
La réponse est toujours oui . Par exemple, les deux formules suivantes sont en forme dis-
jonctive normale, équivalentes, mais leur diérence syntaxique dépasse l'ordre des littéraux ou
des clauses, et on ne peut pas supprimer une clause qui subsume une autre :
¬x ∨ (x ∧ ¬y) (4.8)
¬y ∨ (y ∧ ¬x) (4.9)
En fait, les deux formules sont équivalentes à
(¬x ∧ y) ∨ (¬x ∧ ¬y) ∨ (x ∧ ¬y) (4.10)
ce qui est donc encore une formule en forme disjonctive normale et qui est équivalente aux
formules (4.8) et (4.10), mais complètement diérente. On voit par exemple l'équivalence de
(4.8) et (4.10) comme suit :
¬x ∨ (x ∧ ¬y)
|=| (¬x ∧ (y ∨ ¬y)) ∨ (x ∧ ¬y) True est l'élément neutre de ∧
|=| (¬x ∧ y) ∨ (¬x ∧ ¬y) ∨ (x ∧ ¬y) de Morgan
En fait il y a encore une autre formule en forme disjonctive normale qui est équivalente a (4.8)
à (4.10) et qui est encore plus petite :
¬x ∨ ¬y (4.11)
Pour montrer cette équivalence nous avons besoin du critère de subsomption (théorème 13) :
¬x ∨ ¬y
|=| ¬x ∨ (¬y ∧ (x ∨ ¬x)) True est l'élément neutre de ∧
|=| ¬x ∨ (¬y ∧ x) ∨ (¬y ∧ ¬x) de Morgan
|=| ¬x ∨ (x ∧ ¬y) théorème 13, car ¬x subsume ¬y ∧ ¬x
Il faut bien sûr préciser qu'est-ce qu'on entend par plus petit : Dans le contexte de
cette section, c'est-à-dire pour la comparaison des formules en forme disjonctive normale, en
compare les formules simplement par le nombre de littéraux qui paraissent dedans (si le même
littéral parait plusieurs fois on compte toutes ces occurrences). Par exemple, les formules (4.8)
et (4.9) ont chacune 3 littéraux, et la formule (4.10) a 6 littéraux.
On vois facilement qu'il n'y a pas de formule équivalente en forme disjonctive normale qui est
encore plus petite, sauf ¬y ∨ ¬x qui est simplement une variante de (4.11) par commutativité.
Une formule en forme disjonctive normale est dite minimale quand il n'y a pas d'autre formule
en forme disjonctive normale qui est plus petite et qui lui est équivalente. Dans notre exemple
on obtient donc que la forme disjonctive normale minimale est unique à des applications de
commutativité et associativité près.
Le calcul d'une forme disjonctive normale minimale est d'une grande importance dans le
domaine d'architecture d'ordinateurs où on s'intéresse au problème de réaliser un circuit d'une
certaine fonctionnalité à moindre coût. L'algorithme le plus connu pour le calcul d'une forme
disjonctive normale minimale porte le nom de ses deux inventeurs Quine et McCluskey.
On peut donc encore raner notre question :
Question 4 : est-il possible que deux formules minimales en forme disjonctive
normale avec plus de diérence que simplement l'ordre des littéraux ou l'ordre des
clauses, sont équivalentes ?
4.5. FORME CONJONCTIVE NORMALE 47
La réponse est toujours oui comme on peut voir sur l'exemple de la formule
Cette formule est équivalente aux deux formules plus petites en forme disjonctive normale
Ces deux formules ne sont pas de simples variantes par application de commutativité et d'as-
sociativité. De plus, il n'y a pas de formule équivalente en forme disjonctive normale qui soit
plus petite.
Dénition 12 1. Une clause disjonctive est une disjonction de zéro ou plus littéraux.
2. Une formule est en forme conjonctive normale (abrégé CNF) si elle est la conjonction
de zéro ou plus clauses disjonctives.
Par exemple, (x ∨ y) ∧ (¬z ∨ y ∨ ¬z) est en forme conjonctive normale. Remarquez qu'il y a des
formules qui sont à la fois en forme disjonctive normale et en forme conjonctive normale, par
exemple (x ∨ ¬y ∨ z) qu'on peut voir comme une seule clause disjonctive et donc une formule
en forme conjonctive normale ; ou comme une disjonction de trois clauses conjonctives qui
chacune consiste en un seul littéral, et donc comme une formule en forme disjonctive normale.
Théorème 14 Pour toute formule il existe une formule équivalente en forme conjonctive
normale.
La procédure est analogue à celle de la mise en forme disjonctive normale, sauf que les deux
premières règles de transformation (4.4) et (4.5) sont à remplacer par
X ∨ (Y ∧ Z) → (X ∨ Y ) ∧ (X ∨ Z) (4.15)
(X ∧ Y ) ∨ Z → (X ∨ Z) ∧ (Y ∨ Z) (4.16)
Nous allons développer l'algorithme DPLL en plusieurs étapes en partant de l'algorithme des
tables de vérité. L'algorithme complet est résumé en section 5.2.
Hypothèses sur la forme des clauses. Nous dirons qu'une variable x paraît avec polarité
positive dans une clause disjonctive quand la clause est de la forme
l1 ∨ . . . ∨ x ∨ . . . ∨ ln
A priori une variable peut paraître à la fois avec polarité positive et avec polarité négative
dans une clause, comme par exemple la variable x dans la clause
y1 ∨ x ∨ y2 ∨ ¬x ∨ y3
Une telle clause disjonctive qui contient à la fois un littéral x et sa négation ¬x est évidemment
une tautologie, elle n'importe pas pour la satisfaisabilité de la formule entière et on peut
la supprimer. De même, si une clause contient deux occurrences du même littéral, on peut
supprimer une des deux occurrences, la formule résultante est équivalente à la formule de
départ grâce à la loi d'idempotence de la disjonction (théorème 7). Exemple : on peut remplacer
x1 ∨ ¬x2 ∨ x3 ∨ ¬x2
On peut donc dans la suite supposer que dans chacune des clauses disjonctives aucune variable
apparaît deux fois car si une variable apparaît deux fois dans une clause alors soit les deux
occurrences sont d'une polarité diérente et on peut ignorer la clause car elle est une tautologie,
soit les deux occurrences sont de la même polarité et on peut simplier la clause en supprimant
le littéral qui paraît en deux copies dans la clause.
Ici nous avons utilisé la notation d \ l, où l est un littéral de la clause disjonctive l, pour la
clause obtenue à partir de l en supprimant le littéral l. Par exemple, si d = (x ∨ ¬y ∨ z) alors
d \ {¬y} = (x ∨ z). Voici des exemples pour chacun des cinq cas qui apparaissent dans la
dénition d'une évaluation partielle d'une formule :
(x ∨ ¬y ∨ z)[x/1] = True
(¬x ∨ ¬y ∨ z)[x/0] = True
(¬x ∨ ¬y ∨ z)[x/1] = (¬y ∨ z)
(x ∨ ¬y ∨ z)[x/0] = (¬y ∨ z)
(y1 ∨ ¬y2 ∨ z)[x/1] = (y1 ∨ ¬y2 ∨ z)
Nous rappelons que False est une abréviation pour la clause disjonctive vide, si on supprime
de la clause x le littéral x on obtient la clause vide notée False.
La propriété de cette notion d'évaluation partielle est que
[[c[x/b]]]v = [[c]](v[x/b])
52 CHAPITRE 5. SATISFAISABILITÉ DE FORMES CONJONCTIVES NORMALES
Donc, c est satisfaisable si c[x/0] est satisfaisable ou si c[x/1] est satisfaisable. Cela donne lieu
à un arbre de recherche car il faut a priori exploiter les deux possibilités :
c[x/0] c[x/1]
Notez que l'arbre de recherche est une représentation du déroulement de l'algorithme, il est
normalement pas représenté directement en mémoire par le programme qui réalise cet algo-
rithme. Une réalisation de l'algorithme dans un langage de programmation (disons, en Java)
va plutôt utiliser des appels récursifs de fonctions, ou une pile (voir un cours d'algorithmique).
L'intérêt de cet algorithme repose sur le fait qu'on peut donner des critères simples qui nous
permettent de dire dans certains cas si une formule obtenue pendant ce calcul est satisfaisable
ou pas :
1. Si la formule est la conjonction vide, notée True, alors la formule est une tautologie, et
donc satisfaisable.
2. Si la formule contient une clause qui est la disjonction vide, notée False, alors la formule
n'est pas satisfaisable.
3. Sinon on ne peut rien dire, il faut continuer à choisir des valeurs des variables.
On appelle la variable x pour laquelle on évalue la formule une fois pour la valeur [x/1],
une fois pour [x/0], la variable pivot . Sur l'exemple (5.1), les deux choix possibles pour les
valeurs de la variable pivot x nous donnent les deux alternatives qui sont toutes les deux non
satisfaisables. Par conséquent, la formule c de (5.1) n'est pas satisfaisable. Dans ce cas on a un
arbre de recherche très simple qui consiste seulement en trois n÷uds c, c[x/0] et c[x/1]. Bien
sûr on ne va pas toujours trouver une réponse après une seule évaluation partielle, en général il
faut continuer le parcours de l'arbre de recherche en choisissant une nouvelle variable pivot et
d'évaluer partiellement la formule obtenue par rapport au choix fait pour la nouvelle variable
pivot. Cela donne lieu à un arbre de recherche plus profond, comme par exemple
c[x/0] c[x/1]
Remarquez qu'on n'est pas obligé de choisir la même variable pivot pour le n÷ud c[x/0] que
pour le n÷ud c[x/1]. L'ecacité d'une réalisation de l'algorithme DPLL dépend énormément
de la stratégie pour trouver une variable pivot.
choix répété de la variable pivot va résulter en un arbre de recherche très grand, et va donner
lieu à un calcul qui est aussi long que la construction de la table de vérité. Un bon choix de
la variable pivot par contre peut mener beaucoup plus rapidement à une réponse.
Si on prend par exemple la formule suivante :
(x1 ∨ x2 ∨ x3 ) ∧ (¬x1 ∨ x2 ∨ x3 ) ∧ x3
∧ (x1 ∨ x2 ∨ ¬x3 ) ∧ (¬x1 ∨ x2 ∨ ¬x3 ) ∧ ¬x3
∧ (x1 ∨ ¬x2 ∨ x3 ) ∧ (¬x1 ∨ ¬x2 ∨ x3 )
∧ (x1 ∨ ¬x2 ∨ ¬x3 ) ∧ (¬x1 ∨ ¬x2 ∨ ¬x3 )
Si on choisi les variables de pivot dans l'ordre x1 − x2 − x3 alors on trouvera que la formule est
non satisfaisable seulement quand on aura choisi les valeurs des trois variables. Dans ce cas on
parcourt un arbre de recherche d'hauteur 3 avec 23 = 8 feuilles, c'est-à-dire on engendre les
mêmes aectations que dans la méthode des tables vérités, et on a très peu gagné par notre
nouvelle méthode. En fait il y a un petit gain dû au fait qu'on simplie la formule dès qu'on a
fait un choix, par exemple x1 = 1 De cette façon il y a plus de réutilisation de calculs entre les
choix possibles des aectations que dans la méthode des tables de vérité, mais ce gain n'est
pas très important.
Un meilleur choix comme variable de pivot est bien sûr la variable x3 , on trouve dans ce cas
que la formule n'est pas satisfaisable pour les deux choix des valeurs de x3 et on a trouvé la
réponse avec un arbre de recherche d'hauteur 1.
Une bonne stratégie est donc : S'il y a une clause qui consiste en un seul littéral x ou ¬x alors
on choisit la variable x.
Dénition 14 Une clause (conjonctive ou disjonctive) qui consiste en un seul littéral est
appelé unitaire.
Pour ce cas on pourra même optimiser un peu le calcul : Si la formule c contient la clause
unitaire x alors le choix x = 0 donne forcement une formule c qui contient la clause False.
Puisqu'on sait déjà que c[x/0] va être non satisfaisable il n'est même pas nécessaire de con-
struire c[x/0], et la formule c est satisfaisable si et seulement si c[x/1] est satisfaisable. On
peut donc passer de la formule c directement à la formule c[x/1] qui est une formule plus
petite, et cela sans avoir fait un choix. Le cas d'une formule c qui contient une clause unitaire
¬x est analogue, dans ce cas c est satisfaisable si et seulement si c[x/0] est satisfaisable.
Exemple :
c = (x ∨ y) ∧ ¬y ∧ (¬x ∨ y ∨ ¬z) est satisfaisable
ssi c[y/0] = x ∧ (¬x ∨ ¬z) est satisfaisable
ssi c[y/0][x/1] = ¬z est satisfaisable
ssi c[y/0][x/1][z/0] = True est satisfaisable
La formule True est satisfaisable (elle est même une tautologie), la formule de départ est
alors également satisfaisable. Par contre on ne peut pas conclure que la formule de départ
est une tautologie, notez que les étapes de raisonnement sont relatives à la satisfaisabilité et
ne sont pas des équivalences logiques. Observez que l'évaluation partielle c[y/0] fait paraître
une nouvelle clause unitaire (la clause x) qui n'existait pas dans cette forme en c, et que
l'évaluation partielle c[y/0][x/1] fait paraître encore une nouvelle clause unitaire. Il est donc
54 CHAPITRE 5. SATISFAISABILITÉ DE FORMES CONJONCTIVES NORMALES
possible que cette stratégie nous permette dans certains cas d'enchaîner des simplications
sans nécessiter de vrais choix des valeurs de variables pivot.
Cela laisse toujours la question ouverte quoi faire quand toutes les clauses contiennent au
moins deux littéraux.
Variables qui apparaissent avec une seule polarité. Une deuxième optimisation con-
cerne le cas des variables qui apparaissent avec une seule polarité dans la formule, comme par
exemple
(x ∨ ¬y ∨ z) ∧ (x ∨ ¬z) ∧ (y ∨ z) ∧ (x ∨ ¬y) (5.2)
Dans ce cas, x apparait seulement avec polarité positive.
Un exemple complet :
(x1 ∨ ¬x2 ∨ y1 ∨ ¬y2 ∨ ¬z2 ∨ ¬z4 )
∧(x2 ∨ y1 ) ∧ (x2 ∨ y1 ∨ y2 ∨ z1 ∨ z4 ) ∧ (x2 ∨ ¬y2 ∨ z1 ∨ ¬z2 )
∧(x2 ∨ ¬y1 ∨ z3 ∨ ¬z4 ) ∧ (x2 ∨ ¬y2 ∨ z2 ∨ ¬z3 )
∧(¬x2 ∨ ¬y1 ) ∧ (¬x2 ∨ ¬y1 ∨ ¬y2 ) ∧ (¬x2 ∨ y1 ∨ y2 ) ∧ (¬x2 ∨ ¬y2 ∨ z1 ) ∧ (¬x2 ∨ ¬z1 ∨ z2 )
∧(¬z3 ∨ ¬z4 ) ∧ (z3 ∨ z4 ) ∧ (¬z3 ∨ z4 )
Cette formule contient 8 variables, il y a donc 256 aectations à tester avec la méthode des
tables de vérité.
56 CHAPITRE 5. SATISFAISABILITÉ DE FORMES CONJONCTIVES NORMALES
Le cas (5) de l'algorithme DPLL s'applique avec la variable x1 qui n'apparait qu'avec polarité
positive dans la formule (dans la première clause). On obtient, en supprimant la première
clause :
Dans cette formule il n'y aucune clause unitaire et toute variable apparait avec les deux polar-
ités. On est donc obligé d'appliquer (7). Nous choisissons ici la variable x2 , il y a maintenant
deux cas :
Premier cas obtenu pour x2 = 0
y1 ∧ (y1 ∨ y2 ∨ z1 ∨ z4 ) ∧ (¬y2 ∨ z1 ∨ ¬z2 ) ∧ (¬y1 ∨ z3 ∨ ¬z4 ) ∧ (¬y2 ∨ z2 ∨ ¬z3 )
∧(¬z3 ∨ ¬z4 ) ∧ (z3 ∨ z4 ) ∧ (¬z3 ∨ z4 )
(¬y2 ∨ z1 ∨ ¬z2 ) ∧ (z3 ∨ ¬z4 ) ∧ (¬y2 ∨ z2 ∨ ¬z3 ) ∧ (¬z3 ∨ ¬z4 ) ∧ (z3 ∨ z4 ) ∧ (¬z3 ∨ z4 )
Dans cette formule, la variable y2 n'apparait qu'avec polarité négative, on peut donc
appliquer (6) et supprimer les deux clauses qui contiennent le littéral ¬y2 :
Encore une fois ce n'est que (7) qui s'applique, si on choisit la variable pivot z3 on obtient
encore deux sous-cas :
Premier sous-cas obtenu pour z3 = 0
¬z4 ∧ z4
qui donne false par application de (3) (ou de (4)). On doit donc considérer le second
sous-cas :
Second sous-cas obtenu pour z3 = 1. Il se trouve qu'on obtient la même formule que
dans le premier sous-cas :
¬z4 ∧ z4
Modélisation en logique
propositionnelle
Nous avons vu dans le chapitre précédent qu'il existe des outils pour décider de la satis-
faisabilité de formules propositionnelles en forme conjonctive normale, et que ces outils sont
souvent en pratique très ecaces. Dans ce chapitre nous allons montrer comment ces outils
peuvent être utilisés pour construire des programmes qui résolvent ecacement des problèmes
combinatoires (c'est-à-dire, des problèmes ou il faut trouver une solution parmi toutes les
combinaisons possibles de certains choix élémentaires).
une frontière commune ; une île n'est adjacente à aucun autre état) n'ont jamais la même
couleur ?
Toute coloration de la carte de l'Australie par les trois couleurs peut être vue comme une
aectation qui associe à chaque état (parmi WA, NT, SA, QLD, NSW, VIC, et TAS) une
couleur parmi r, b, et v. Or, les variables propositionnelles permettent seulement un choix
binaire pour leur valeur : 0 ou 1. La première étape de la modélisation de notre problème en
logique propositionnelle consiste à exprimer les colorations possibles comme des aectations
à des variable propositionnelles. Dans cette première étape on ne s'intéresse pas encore à
la modélisation de la contrainte qui consiste à interdire la même couleur pour deux états
adjacent ; cette contrainte sera mise en place dans la deuxième étape de la modélisation.
Nous créons une variable propositionnelle pour chaque état et pour chaque couleur, cela fait
donc en total 7 ∗ 3 = 21 variables. Nous notons une telle variable par exemple comme [WA,r],
l'idée est que cette variable vaut 1 quand l'état WA est coloré en rouge, et 0 sinon (s'il est
coloré dans une autre couleur, ou pas coloré du tout). Puis, nous allons construire une formule
propositionnelle en forme conjonctive normale sur ces 21 variables qui est vraie par rapport
à une aectation si et seulement si l'aectation correspond à une coloration de la carte telle
que deux états adjacent soient colorés diéremment.
Première étape : caractériser les colorations. A priori, il est possible qu'une aectation
des variables propositionnelles ne corresponde pas à une coloration de la carte, par exemple
quand toutes les trois variables [WA,r], [WA,b] et [WA,v] ont la valeur 0 (c'est-à-dire, l'état
WA aurait aucune couleur), ou quand à la fois [WA,r] et [WA,b] ont la valeur 1 (l'état WA
aurait à la fois la couleur rouge et la couleur bleue). La première partie de la formule sert
donc à exclure toutes les aectations qui ne correspondent pas à une coloration. En fait, il
faut exprimer que tout état a une couleur, et exactement une couleur. Cette formule consiste
elle-même en deux parties. La première partie exprime que tout état a au moins une couleur :
P1 := ([WA,r] ∨ [WA,b] ∨ [WA,v])
∧ ([NT,r] ∨ [NT,b] ∨ [NT,v])
∧ ([SA,r] ∨ [SA,b] ∨ [SA,v])
∧ ([QLD,r] ∨ [QLD,b] ∨ [QLD,v])
∧ ([NSW,r] ∨ [NSW,b] ∨ [NSW,v])
∧ ([VIC,r] ∨ [VIC,b] ∨ [VIC,v])
∧ ([TAS,r] ∨ [TAS,b] ∨ [TAS,v])
La deuxième partie de la première formule exprime le fait que tout état ne peut pas avoir deux
couleurs diérentes à la fois :
P2 := (¬[WA,r] ∨ ¬[WA,b]) ∧ (¬[WA,r] ∨ ¬[WA,v]) ∧ (¬[WA,b] ∨ ¬[WA,v])
∧ (¬[NT,r] ∨ ¬[NT,b]) ∧ (¬[NT,r] ∨ ¬[NT,v]) ∧ (¬[NT,b] ∨ ¬[NT,v])
∧ (¬[SA,r] ∨ ¬[SA,b]) ∧ (¬[SA,r] ∨ ¬[SA,v]) ∧ (¬[SA,b] ∨ ¬[SA,v])
∧ (¬[QLD,r] ∨ ¬[QLD,b]) ∧ (¬[QLD,r] ∨ ¬[QLD,v]) ∧ (¬[QLD,b] ∨ ¬[QLD,v])
∧ (¬[NSW,r] ∨ ¬[NSW,b]) ∧ (¬[NSW,r] ∨ ¬[NSW,v]) ∧ (¬[NSW,b] ∨ ¬[NSW,v])
∧ (¬[VIC,r] ∨ ¬[VIC,b]) ∧ (¬[VIC,r] ∨ ¬[VIC,v]) ∧ (¬[VIC,b] ∨ ¬[VIC,v])
∧ (¬[TAS,r] ∨ ¬[TAS,b]) ∧ (¬[TAS,r] ∨ ¬[TAS,v]) ∧ (¬[TAS,b] ∨ ¬[TAS,v])
La formule construite jusqu'à maintenant, P1 ∧ P2 , a la propriété que toute solution de cette
formule correspond à une coloration unique de la carte, et inversement. Exprimé plus formelle-
6.1. EXEMPLE : COLORER UNE CARTE 61
ment, il existe une fonction bijective entre l'ensemble de toutes les solutions, et l'ensemble de
toutes les colorations possibles de la carte. Soit
rep: {WA, NT, SA, QLD, NSW, VIC, TAS} → {r, b, v} → {v ∈ X → {0, 1} | v |= P1 ∧ P2 }
1 si f (state) = color
rep(f )([state, color ]) =
0 si f (state) 6= color
On constate que la dernière formule ne contient pas de variable pour l'état de Tasmanie, ce
qui est normal puisque la Tasmanie est une île et donc adjacente à aucun autre état.
Finalement, la formule complète est P1 ∧P2 ∧P3 . Cette formule est satisfaisable si et seulement
s'il existe une solution au problème de départ. Si v |= P1 ∧ P2 ∧ P3 , alors la coloration cherchée
est la fonction f telle que rep(f ) = v . Une telle fonction f existe car la fonction rep est
surjective, et elle est uniquement déterminée pour une aection v donnée car la fonction rep
est injective.
On voit que la première étape était plus au moins indépendante du fait qu'il s'agit ici de
l'Australie : Cette étape aurait été la même pour l'Afrique ou l'Europe, sauf que pour ces
continents il nous aurait fallu plus de variables (car ils ont plus de pays que l'Australie a
d'états). La deuxième étape, par contre, était très spécique à l'Australie, bien que le principe
de construction sera le même pour des autres cartes. Dans la deuxième étape, la forme de la
formule obtenue dépend beaucoup de la carte considérée. Par exemple, une carte des régions du
Chili donnerait une formule complètement diérente (consultez un atlas pour savoir pourquoi).
62 CHAPITRE 6. MODÉLISATION EN LOGIQUE PROPOSITIONNELLE
^ _
[i, j]
1≤i≤n 1≤j≤m
^ ^ ^
∧ (¬[i, j] ∨ ¬[i, k])
1≤i≤n 1≤j≤m j<k≤m
^ ^ ^
∧ (¬[i, k] ∨ ¬[j, k])
1≤i≤n i<j≤n 1≤k≤m
A(i,j)=true
Première étape : caractériser les mariages Évidemment tout mariage entre ces per-
sonnes correspond à une aectation des variables. Le contraire n'est pas vrai : D'un côté, une
aectation peut par exemple aecter 1 aux deux variables [A,X] et [A,Y], ce qui fait vivre
Adam en bigamie, ou aecter 0 aux trois variables [C,X], [C,Y] et [C,Z], ce qui laisse Chen
célibataire. On construit donc des formules qui expriment l'absence du célibat : chaque garçon
trouve une lle (formule P1 ), et chaque lle trouve un garçon (formule P10 ).
Puis, on construit des formules qui expriment l'absence de bigamie : Aucun garçon ne se marie
avec deux lles (formule P2 ), et aucune lle ne se marie avec deux garçons (formule P20 ).
La formule P1 ∧ P10 ∧ P2 ∧ P20 est satisfaite par tout les mariages légaux (monogames) entre
toutes les personnes, et exclut toutes les aectations qui ne sont pas des mariages légaux entre
64 CHAPITRE 6. MODÉLISATION EN LOGIQUE PROPOSITIONNELLE
toutes les personnes du groupe. On pourrait remarquer qu'il aurait en principe sut de choisir
la formule P1 ∧ P20 (ou P10 ∧ P2 ).
Exercice 14 Est-ce que P2 ∧P3 serait une solution correcte ? En général, dire exactement pour
quels choix des formules parmi P1 , P10 , P2 , P20 , P3 leur conjonction est une solution correcte du
problème.
On parle alors d'une contrainte de comptage. Dans la coloration de la carte d'Australie étudiée
dans la section 6.1, par exemple, on pourrait ajouter la contrainte que au moins 2 (ou au plus
2, ou précisément 2) des régions sont colorées en rouge. Dans ce cas, n = 2 et l'ensemble Y
est l'ensemble des variables qui correspondent à une coloration rouge d'une des régions :
Comment exprimer une contrainte de comptage ? On voit d'abord que la troisième forme, qui
demande que exactement n parmi les variables de Y sont vraies, peut être exprimée à l'aide
des deux premières : il faut que au moins n des variables dans Y , et aussi au plus n des
variables dans Y sont vraies. Regardons d'abord la contrainte : au moins n des variables dans
Y sont vraies. Supposons, pour simplier la notation, que Y = {y1 , . . . , ym }. L'ensemble Y
contient alors m variables.
Si n = 1 la tãche est facile :
y1 ∨ y2 ∨ . . . ∨ ym
Si n = 2 on peut engendrer toutes les paires de deux variables diérentes de Y , et exprimer
le fait que au moins une de ces paires est entièrement colorée en rouge :
Plus précisément : _
(yi ∧ yj )
i=1,...,m−1
j=i+1,...,m
Dans cette forme générale, on construit une grande disjonction sur tous les n-uplets d'indices
entre 1 et m, où en plus on impose que les indices dans chacun de ces n-uplets sont arrangés
dans un ordre croissant. Pour chacun de ces n-uplets on exprime par la grande conjonction
que les variables indiquées par ces indices sont vraies.
Malheureusement, la formule ainsi obtenue est on forme disjonctive normale et pas en forme
conjonctive normale. On pourrait utiliser l'algorithme présenté en section 4.5 pour transformer
cette formule en forme conjonctive normale. Une solution alternative est de construire la
formule directement en forme conjonctive normale, en utilisant l'idée suivante : Dire que au
moins n parmi les variables de Y sont vraies est équivalent à dire que pour n'importe quelle
sélection de m − n + 1 variables de Y , au moins une dans cette sélection est vraie.
L'avantage de cette formulation est qu'elle s'exprime naturellement en forme conjonctive nor-
male : ^ _
yij
i1 ,...,im−n+1 j=1,...,m−n+1
i1 <...<im−n+1
Sur notre exemple Australien : on a au plus deux régions rouge si dans toute sélection de trois
régions il y a au moins une qui n'est pas rouge :
Pour écrire une formule en format DIMACS on suppose d'abord que les variables proposition-
nelles soient numérotées à partir de 1. La première ligne d'un chier rédigé dans ce format
contient l'entête qui consiste en le mot p, puis le mot cnf, puis le numéro maximal des vari-
ables utilisées dans la formule, puis le nombre de clauses disjonctives de la formules. Chacune
des lignes suivantes décrit une clause disjonctive de la formule : pour décrire une clause on
écrit simplement la liste des numéros de toutes les variables contenues dans la clause, avec un
signe positif quand il s'agit d'un littéral positif, et avec un signe négatif quand il s'agit d'un
littéral négatif. Toute ligne décrivant une clause (mais pas l'entête) se termine sur 0.
Par exemple, la formule
p cnf 5 2
1 -5 0
2 5 -1 0
Les expressions
Nous allons au chapitre 8 dénir un petit langage de programmation. Les expressions arith-
métiques sont un composant important de ce langage, et méritent que nous leur consacrons
un chapitre à part.
X := {x, x1 , x2 , x3 , . . . , y, y1 , y2 , . . . , z, z1 , z2 , z3 , . . .}
Nous admettons également les décorations des variables comme par exemple x0 , y 00 .
Nous dénissons l'ensemble des expressions arithmétiques et l'ensemble des expressions booléennes
par induction, suivant le même principe que dans la dénition de la syntaxe des formules propo-
sitionnelles (dénition 1). Cette fois nous donnons des dénitions avec un nombre minimal de
cas, et nous dénissons toutes les autres notions que nous souhaitons en pratique comme des
abréviations.
Dénition 15 L'ensemble des expressions arithmétiques AExpr est le plus petit ensemble de
chaînes de caractères tel que :
1. X ⊆ AExpr
2. N ⊆ AExpr
3. Si e ∈ AExpr alors −e ∈ AExpr
4. Si e1 , e2 ∈ AExpr alors (e1 + e2 ) ∈ AExpr
5. Si e1 , e2 ∈ AExpr alors (e1 ∗ e2 ) ∈ AExpr
72 CHAPITRE 7. LES EXPRESSIONS
Dénition 16 L'ensemble des expressions booléennes BExpr est le plus petit ensemble de
chaînes de caractères tel que :
1. Si e1 , e2 ∈ AExpr alors (e1 ≤ e2 ) ∈ BExpr
2. Si f ∈ BExpr alors ¬f ∈ BExpr
3. Si f1 , f2 ∈ BExpr alors (f1 ∨ f2 ) ∈ BExpr
On appelle une expression booléenne atomique si elle est de la forme e1 ≤ e2 .
Remarquez qu'on utilise pour un cas de la dénition inductive des expressions booléennes les
expressions arithmétiques, un ensemble qui est lui-même déni par induction.
Nous permettons aussi les abréviations habituelles suivantes :
Abbréviation Dénition
True 0≤0
False 1≤0
f1 ∧ f2 ¬(¬f1 ∨ ¬f2 )
e1 = e2 e1 ≤ e2 ∧ e2 ≤ e1
e1 ≥ e2 e2 ≤ e1
e1 < e2 e1 + 1 ≤ e2
e1 > e2 e2 + 1 ≤ e1
e1 6= e2 e1 < e2 ∨ e1 > e2
ainsi que les abréviations de la logique propositionnelle vues à la section 3.5. La dénition de
l'abréviation e1 < e2 est justiée par le fait que les expressions arithmétiques sont interprétées
comme des entiers, et pas comme des réels.
L'ensemble des aectations est noté A. Le support d'une aectation v est dénie comme
supp(σ) = {x ∈ X | σ(x) 6= 0}
7.2. SÉMANTIQUE DES EXPRESSIONS ARITHMÉTIQUES ET BOOLÉENNES 73
Une aectation est donc toujours une fonction totale. Nous utilisons une notation pour les
aectations :
x1 7→ n1 , x2 7→ n2 , . . . , xm 7→ nm
est l'aectation qui associe à toute variable xi (avec 1 ≤ i ≤ m) la valeur ni , et qui associe 0
à toute autre variable.
[[x]]σ = σ(x)
[[n]]σ = n
[[−e]]σ = −[[e]]σ
[[(e1 + e2 )]]σ = [[e1 ]]σ + [[e2 ]]σ
[[(e1 ∗ e2 )]]σ = [[e1 ]]σ ∗ [[e2 ]]σ
[[((x + y) ∗ (x ∗ z))]]σ
= [[(x + y)]]σ ∗ [[(x ∗ z)]]σ
= ([[x]]σ + [[y]]σ) ∗ ([[x]]σ ∗ [[z]]σ)
= (σ(x) + σ(y)) ∗ (σ(x) ∗ σ(z))
= (1 + 2) ∗ (1 ∗ 3)
= 9
Dénition 19 L'évaluation [[f ]]σ d'une expression booléenne f par rapport à l'aectation σ
est dénie par récurrence sur la structure de f :
si
1 [[e1 ]]σ ≤ [[e2 ]]σ
[[(e1 ≤ e2 )]]σ =
0 si [[e1 ]]σ > [[e2 ]]σ
0 si [[f ]]σ = 1
[[¬f ]]σ =
1 si [[f ]]σ = 0
si [[f1 ]]σ = 0 et [[f2 ]]σ = 0
0
[[(f1 ∨ f2 )]]σ =
1 si [[f1 ]]σ = 1 ou [[f2 ]]σ = 1
Dénition 21 Une tautologie est une expression booléenne de la forme p[x1 /f1 , . . . , xn /fn ]
où p est une tautologie propositionnelle, V(p) = {x1 , . . . , xn } et f1 , . . . , fn sont des expressions
booléennes.
En d'autres mots, une tautologie (dans le sens des expressions booléennes) est obtenue en rem-
plaçant dans une tautologie propositionnelle (c'est-à-dire une formule propositionnelle valide)
toutes les variables propositionnelles par des expressions booléennes. Par exemple :
(x ≤ y) ∨ ¬(x ≤ y)
est une tautologie, car cette expression booléenne est obtenue en remplaçant dans la tautologie
propositionnelle z ∨ ¬z la variable propositionnelle z par l'expression arithmétique x ≤ y . Un
autre exemple est
car cette formule est obtenue en remplaçant dans la tautologie ¬(x0 ∨ y 0 ) ↔ (¬x0 ∧ ¬y 0 ) la
variable propositionnelle x0 par (x ≤ y ∗ z) et la variable propositionnelle y 0 par (z + y ≤ x ∗ x).
Une remarque concernant notre notation : Nous avons choisi d'utiliser le même répertoire X
de variables à la fois pour les variables propositionnelles et pour les variables arithmétiques.
Ce choix a l'avantage d'éviter l'introduction d'une nouvelle notation. Normalement, quand
on étudie soit la logique propositionnelle, soit la logique de Hoare il n'y a pas de risque de
confusion, c'est seulement dans cette section qu'on a les deux au même temps et qu'il faut
bien préciser quand on parle de variables propositionnelles et quand on parle d'expressions
arithmétiques.
Or, quand nous appliquons la méthode décrite ci-dessus nous obtiendrons pas exactement
la même structure : On commence par remplacer dans la formule toute expression booléenne
atomique par une nouvelle variable (si on a plusieurs occurrences de la même expression atom-
ique on les remplace toutes par la même variable). Ici nous avons deux expressions atomiques
diérentes, (y > z ∗ z) et (z ∗ z > y). Si on choisit pour la première expression la variable
propositionnelle x1 et pour la deuxième la variable propositionnelle x2 on obtient donc comme
formule propositionnelle
(x1 ∨ x2 ) ∨ ¬(x1 ∨ x2 )
qui est aussi une tautologie (propositionnelle) ! La question est : Est-ce qu'on tombe avec notre
méthode de décomposition toujours sur une tautologie quand l'expression de départ est une
tautologie ? La réponse est oui , et la raison est le théorème 5 ! Sur notre exemple ce théorème
nous dit que puisque x∨¬x est une tautologie propositionnelle, la formule (x1 ∨x2 )∨¬(x1 ∨x2 )
l'est aussi. On ne peut donc rien perdre par notre méthode de décomposition, et notre méthode
est complète.
Tandis que toute tautologie est valide, l'inverse n'est pas vrai : il y a des expressions valides
qui ne sont pas des tautologies. Voici quelques exemples :
((x > y) ∧ (y > z)) → (x > z)
((x > y) → (x + z > y + z)
(x ∗ y = 0) → (x = 0 ∨ y = 0)
((x1 = y1 ) ∧ (x2 = y2 )) → (x1 + x2 = y1 + y2 )
y 6= 0 → x ∗ x 6= 2 ∗ (y ∗ y)
La première expression exprime une propriété de la relation > (la transitivité), la deuxième et
la troisième expriment des propriétés des opérateurs arithmétiques. La quatrième expression
est aussi valide mais pour des raisons qui sont complètement indépendantes de l'arithmétique,
cette expression est simplement valide à cause des propriétés de l'égalité. La dernière expression
exprime le fait que le nombre rationnel xy n'est pas la racine de 2, cette expression est donc
valide car aucun nombre rationnel est la racine de 2.
On voit sur ces exemples qu'il faut en général une bonne connaissance de l'arithmétique pour
savoir si une expression booléenne est valide ou pas. Une question intéressante est s'il y a un
algorithme qui peut, analogue au cas de la logique propositionnelle, décider pour n'importe
quelle expression booléenne si elle valide ou pas. Il est a priori concevable qu'un tel algorithme
n'existe pas, nous verrons à la n de cet ouvrage (au chapitre 12) quelques exemples de
problèmes pour lesquels un algorithme ne peut pas exister.
Pour donner une idée de la diculté du problème, l'expression booléenne
3 ∗ x ∗ x ∗ x ∗ y ∗ y − 2 ∗ x ∗ x ∗ z + 7 ∗ z ∗ z ∗ z 6= 3
est valide si est seulement le polynôme 3x3 y 2 − 2x2 z + 7z 3 − 3 n'a pas de racine dans les
entiers. Cet exemple montre comment il est dicile en général de décider de la validité d'une
expression booléenne.
Heureusement, on ne rencontre normalement pas d'expressions booléennes si diciles que ça
quand on s'intéresse à la correction de programmes. Les expressions booléennes pour lesquelles
nous aurions à décider de la validité sont souvent des tautologies, ou elles sont valides pour
des raisons assez simples.
76 CHAPITRE 7. LES EXPRESSIONS
Les résultats de la logique propositionnelle peuvent être généralisés pour obtenir des résultats
analogues pour les expressions booléennes. Nous citons par exemple ici la proposition de
substitution car nous en aurions besoin plus tard :
[[f [x1 /e1 , . . . , xn /en ]]]v = [[f ]](v[x1 /[[e1 ]]v, . . . , xn /[[en ]]v])
Les programmes
Dans ce chapitre nous allons dénir formellement la syntaxe et la sémantique d'un petit langage
de programmation appelé IMP. Ce langage de programmation nous servira pour étudier la
logique de Hoare au chapitre 9, il est simplié au maximum et ne contient que les éléments
les plus importants.
La longueur d'une liste vide est 0, et la longueur d'une liste composée de n éléments est n.
Par exemple,
[(x + y); (17 + z1 ); (42 ∗ (y + 5))]
est une liste d'expression arithmétiques, et sa longueur est 3. Pour être exact il faut assurer
que la notation n'est pas ambiguë, ce qui est assuré quand les éléments d'une liste ne peuvent
pas se terminer sur le symbole ; . Une notion importante est l'opération de concaténation
de deux listes : Si l est une liste [e1 ; . . . ; en ] et l0 une liste [e01 ; . . . ; e0m ] alors leur concaténation
est la liste
[e1 ; . . . , en ; e01 , . . . ; e0m ]
Nous écrivons l; l0 pour cette liste. Il s'agit en principe d'un abus de notation car de cette
façon nous utilisons le symbole ; pour deux choses diérentes, d'un part pour combiner deux
éléments d'une liste, et d'autre part pour combiner deux listes. Puisque nous considérons ici
pas des listes dont les éléments sont eux-mêmes des listes cela ne risque pas d'introduire une
ambiguïté, et nous pouvons autoriser cette notation.
En particulier, la liste vide est l'élément neutre de l'opération de concaténation, c'est-à-dire
; l = l et l; = l.
Dénition 22 L'ensemble Inst des instructions est le plus petit ensemble de chaînes de car-
actères tel que :
78 CHAPITRE 8. LES PROGRAMMES
if x ≤ 0 then y := y − x else y := y + x; y := y + y
car on ne saurait pas si la dernière aectation fait partie de la deuxième branche de l'instruction
conditionnelle, ou si elle suit après l'instruction conditionnelle. Avec notre dénition de la
syntaxe on a un théorème de lecture unique comme nous l'avons déjà vu pour la logique
propositionnelle.
Un exemple est le programme dont nous avons déjà parlé dans l'introduction :
z := 0;
y1 := 0;
while y1 6= y do
z := z + x;
y1 := y1 + 1;
od
Dénition 24 La relation ⇒ entre congurations est la plus petite relation telle que
hx := e; S, σi ⇒ hS, σ[x/[[e]]σ]i
hS2 ; S, σi si [[f ]]σ = 0
hif f then S1 else S2 ; S, σi ⇒
hS1 ; S, σi si [[f ]]σ = 1
8.2. SÉMANTIQUE DES PROGRAMMES IMP 79
si [[f ]]σ = 0
hS, σi
hwhile f do S0 od; S, σi ⇒ hS 0 ; while f do S0 od; S, σi si [[f ]]σ = 1
Dans cette dénition nous permettons aussi que le bout de programme p soit la liste vide.
Intuitivement, la relation ⇒ exprime une étape dans l'avancement de l'exécution d'un pro-
gramme. Remarquez qu'il n'y a aucune conguration c telle que h, σi ⇒ c, l'idée est qu'un
programme vide dans une conguration représente le fait que l'exécution du programme a
terminé (il ne reste plus rien à faire).
La transition pour le cas d'une aectation se lit comme suit : on évalue l'expression e par
rapport à l'aectation actuelle σ , puis on met à jour l'aectation (l'état de la mémoire) et on
passe à la suite. Dans le cas d'une conditionnelle on évalue la condition f pour savoir laquelle
des deux branches S1 ou S2 est à exécuter, et après avoir exécuté cette branche on continue
avec ce qui suit après la conditionnelle, c'est-à-dire S . Dans le cas d'une boucle on évalue la
condition, si la condition est fausse on passe à ce qui suit après la boucle, si la condition est
vraie on exécute d'abord le corps S 0 de la boucle, et puis on exécute de nouveau la boucle.
Voici un exemple avec une instruction conditionnelle :
hx := 2; y := 5; x := x ∗ y; if x > y + 1 then z := 0 else z := 1 ; z := z + 10, []i
⇒ hy := 5; x := x ∗ y; if x > y + 1 then z := 0 else z := 1 ; z := z + 10, [x 7→ 2]i
⇒ hx := x ∗ y; if x > y + 1 then z := 0 else z := 1 ; z := z + 10, [x 7→ 2; y 7→ 5]i
⇒ hif x > y + 1 then z := 0 else z := 1 ; z := z + 10, [x 7→ 10; y 7→ 5]i
⇒ hz := 0; z := z + 10, [x 7→ 10; y 7→ 5]i
⇒ hz := z + 10, [x 7→ 10; y 7→ 5; z 7→ 0]i
⇒ h, [x 7→ 10; y 7→ 5; z 7→ 10]i
Un deuxième exemple avec une boucle :
hx := 3; y := 2; while x > 1 do x := x − 1; y := y ∗ y od, []i
⇒ hy := 2; while x > 1 do x := x − 1; y := y ∗ y od, [x 7→ 3]i
⇒ hwhile x > 1 do x := x − 1; y := y ∗ y od, [x 7→ 3; y 7→ 2]i
⇒ hx := x − 1; y := y ∗ y; while x > 1 do x := x − 1; y := y ∗ y od, [x 7→ 3; y 7→ 2]i
⇒ hy := y ∗ y; while x > 1 do x := x − 1; y := y ∗ y od, [x 7→ 2; y 7→ 2]i
⇒ hwhile x > 1 do x := x − 1; y := y ∗ y od, [x 7→ 2; y 7→ 4]i
⇒ hx := x − 1; y := y ∗ y; while x > 1 do x := x − 1; y := y ∗ y od, [x 7→ 2; y 7→ 4]i
⇒ hy := y ∗ y; while x > 1 do x := x − 1; y := y ∗ y od, [x 7→ 1; y 7→ 4]i
⇒ hwhile x > 1 do x := x − 1; y := y ∗ y od, [x 7→ 1; y 7→ 16]i
⇒ h, [x 7→ 1; y 7→ 16]i
Il faut bien sûr montrer que cette dénition de la sémantique n'est pas ambiguë :
avec hS2 , σ2 i =
6 hS20 , σ20 i.
Ce qui est absurde. 2
Le cas de la boucle dans la dénition de la sémantique est remarquable car on passe d'un
programme à un programme qui est plus grand, on a même que le programme qui parait sur la
gauche de la relation ⇒ est contenu dans le programme qui parait sur la droite ! Cela explique
pourquoi nous n'avons pas simplement déni la sémantique des programmes par récurrence,
comme nous l'avons fait pour les formules propositionnelles et les expressions. En fait il est
possible de dénir la sémantique aussi des programmes IMP par récurrence structurelle, on
parle alors d'une sémantique dénotationnelle tandis que nous avons déni ici une sémantique
opérationnelle. Mais cela nécessite quelque résultats mathématiques de la théorie des ordres
partiels, voir un cours de Sémantique.
Proposition 15 Si on a que
[[f ]]σ = 1
[[S]]σ = σ1
8.3. RÉFÉRENCES ET REMARQUES 81
[[while f do S od]]σ1 = σ2
alors on a aussi que
[[while f do S od]]σ = σ2
Démonstration: Nous notons pb = [while f do S od]. Puisque [[S]]σ = σ1 il existe une
séquence
hS, σi ⇒ . . . ⇒ h, σ1 i
Par proposition 14, on a aussi que
hS; Sb , σi ⇒ . . . ⇒ hSb , σ1 i
Finalement, on obtient en enchaînant ces deux séquences et on utilisant le fait que [[f ]]σ = 1 :
des noms de variables vers des cases mémoire, puis des cases mémoire vers des valeurs. Un
autre exemple un peu moins évident et celui des langages de programmation avec des procé-
dures et passage des paramètres par référence. Dans ce cas on observe des eets de partage (la
même case mémoire peut être accessible par plusieurs variables diérentes) qui sont le mieux
modélisés par une liaison en deux étapes.
Ce chapitre suit largement le chapitre 2 du livre [?], dont nous avons aussi emprunté le
nom IMP pour le langage de programmation, et du chapitre 3 du livre [?]. Imp, qui ici
est un acronyme pour Imperative Programming Language (langage de programmation
impérative) est aussi le nom anglais pour une espèce de petits démons mythologiques [?].
Chapitre 9
où p, q ∈ BExpr sont des des expressions booléennes et S ∈ Imp est un programme. L'ensemble
des formules de Hoare est noté Hoare.
Dénition 27 Soit σ ∈ A une aectation et {p} S {q} ∈ Hoare une formule de Hoare.
L'aectation σ satisfait la formule de Hoare {p} S {q}, noté
σ |= {p} S {q}
si
Si σ |= p
et si [[S]]σ = σ 0 6= ⊥
alors σ 0 |= q .
La formule {p} S {q} est valide, noté |= {p} S {q}, si on a σ |= {p} S {q} pour toute aectation
σ ∈ A.
Il y a donc deux hypothèses dans la dénition de σ |= {p} S {q} : il faut que σ |= p, et il faut
aussi que [[S]]σ 6= ⊥. Si une de ces deux hypothèses n'est pas satisfaite alors on a trivialement
que σ |= {p} S {q} (voir gure 9.1). On dit aussi que les formules de Hoare expriment des
84 CHAPITRE 9. LES FORMULES DE HOARE
oui σ0
σ |= p ? [[S]]σ = ? σ 0 |= q ?
non
non
Figure 9.1: Évaluation d'une formule de Hoare {p} S {q} par rapport à une aectation σ
énoncés de correction partielle . On dit partiel car une telle formule n'énonce pas la ter-
minaison, contrairement à un énoncé de correction totale qui énonce aussi la terminaison du
programme.
Pourquoi ces deux hypothèses dans les formules de Hoare ? La première hypothèse est assez
naturelle car on ne s'intéresse souvent pas à l'exécution d'un morceaux de programme dans
un état quelconque, mais seulement dans des états qui peuvent se produire à un certain point
d'un programme. La raison pour la deuxième hypothèse, et la raison pourquoi on s'intéresse
ici à des énoncés de correction partielle et pas de correction totale, est plus délicate. Nous
reviendrons à cette question à la n du chapitre (section 9.3).
9.2 Exemples
Exemple 1 On a
[x 7→ 1] |= {x = 1} x := x + 1 {x = 2}
car
La pre-condition est satisfaite : [x 7→ 1] |= x = 1
L'exécution du programme dans l'état initial [x 7→ 1] termine et donne [[x := x + 1]][x 7→
1] = [x 7→ 2]
Finalement, [x 7→ 2] |= x = 2.
car [x 7→ 42] 6|= x = 1, la formule de Hoare est donc aussi satisfaite par l'aectation [x 7→ 42].
Exemple 4 On a que
[x 7→ 2, y 7→ 3] |= {T rue} if y > x then z := 1 else z := 0 {z > 0}
et [x 7→ 5, y 7→ 3, z 7→ 0] 6|= z > 0.
est valide. Soit σ ∈ A une aectation quelconque. On a évidemment que σ |= True, il faut
donc vérier que la post-condition est satisfaite par le résultat de l'exécution du programme
avec état initial σ . Il y a deux cas, soit σ satisfait la condition x > y , soit σ ne la satisfait pas.
Cas σ(x) > σ(y) On a alors
hif x > y then z := x else z := y , σi
⇒ hz := x, σi
⇒ h, σ[z/σ(x)]i
| {z }
σ0
Exemple 7 L'exemple suivant montre une autre raison pour laquelle une formule de Hoare
avec une boucle peut être vraie :
[x 7→ 20] |= {True} while x > 0 do x := x − 1 od {x ≤ 0}
On a bien sûr que [x 7→ 20] |= True. L'exécution du programme donne
hwhile x > 0 do x := x − 1 od, x 7→ 20i
⇒ hx := x − 1; while x > 0 do x := x − 1 od, x 7→ 20i
⇒ hwhile x > 0 do x := x − 1 od, x 7→ 19i
.. ..
. .
⇒ hx := x − 1; while x > 0 do x := x − 1 od, x 7→ 1i
⇒ hwhile x > 0 do x := x − 1 od, x 7→ 0i
⇒ h, x 7→ 0i
9.2. EXEMPLES 87
arriver sur une conguration h, σ 0 i seulement quand σ 0 |= ¬x > 0, car la dernière transition
était forcement pour le cas d'une boucle où la condition n'est pas vraie. Ce qui montre qu'on
a même que
|= {True} while x > 0 do x := x − 1 od {x ≤ 0}
Exemple 8 Finalement il y a une troisième raison pour laquelle une formule de Hoare avec
une boucle dans le programme peut être valide, et cette raison est liée à la nature d'une boucle.
Nous prétendons que
|= {x > 0} while z > 0 do z := z − 1; x := x + 1 od {x > 0}
Cette fois l'argument utilise une induction sur la longueur de la séquence d'exécution. Nous
montrons d'abord que (*) si σ |= x > 0 et
hwhile z > 0 do z := z − 1; x := x + 1 od, σi
⇒ ...
⇒ hwhile z > 0 do z := z − 1; x := x + 1 od, σ 0 i
alors on a aussi que σ 0 |= x > 0. Nous le montrons par induction sur le nombre n de congu-
rations dans cette séquence où le programme est while z > 0 do z := z − 1; x := x + 1 od.
Remarquez que n est simplement le nombre de fois que le corps de la boucle est exécutée dans
cette séquence, plus 1.
Cas : n = 1 . Dans ce cas on a une séquence d'exécution de longueur 1 qui consiste en une
seule conguration :
hwhile z > 0 do z := z − 1; x := x + 1 od, σi
Nous avons donc que σ 0 = σ1 [z/σ1 (z) − 1, x/σ1 (x) + 1]. Application de l'hypothèse
d'induction nous donne que σ1 (x) > 0. On a que σ 0 (x) = σ1 (x) + 1, donc
σ 0 (x) = σ1 (x) + 1 > σ1 (x) > 0
88 CHAPITRE 9. LES FORMULES DE HOARE
avec σ 0 |= ¬z > 0. Nous concluons avec la propriété (*) montrée ci-dessus que σ 0 |= x > 0.
Notez que σ 0 |= ¬z > 0, le fait que la condition de la boucle est fausse à la n de la séquence
d'exécution, n'est pas du tout utilisé dans cette preuve. Le c÷ur de l'argument est le fait qu'il
y a un invariant, ici x > y , qui est vrai chaque fois quand on est au début de la boucle. Nous
avons déjà vu un argument de ce genre dans l'introduction.
Exemple 9 Notre dernier exemple montre une astuce souvent utilisée. Parfois on souhaite
exprimer dans un énoncé de correction partielle que la valeur nale (après exécution du pro-
gramme) d'une variable est dans une certaine relation avec la valeur initiale de cette variable,
c'est-à-dire sa valeur avant l'exécution du programme. Pour avoir dans la post-condition un
accès à la valeur initiale d'une variable on peut la sauvegarder dans une nouvelle variable
qui n'est pas touchée par le programme. Par exemple,
|= {x0 = x} x := x + 1 {x = x0 + 1}
exprime le fait trivial que, après l'exécution de l'instruction d'aectation, la valeur nale de la
variable x est 1 plus sa valeur initiale. C'est une conséquence de la dénition 27 : Soit σ une
aectation quelconque. Si σ(x) = σ(x0 ), alors on a après exécution du programme que pour
l'aectation résultante σ 0 : σ 0 (x0 ) = σ(x0 ) car le programme ne touche pas la variable x0 , et
par conséquent on a que σ 0 (x0 ) = σ(x0 ) = σ(x).
La logique présentée dans ce chapitre est nommé après Tony Hoare , chercheur britannique qui
a reçu en 1980 le très prestigieux prix Turing Award (une sorte de Nobel pour l'informatique)
pour ses nombreuses contributions à l'informatique.
90 CHAPITRE 9. LES FORMULES DE HOARE
Chapitre 10
1. L'axiome d'aectation
A où p ∈ BExpr, t ∈ AExpr, x ∈ X
{p[x/t]} x := t {p}
2. La règle de composition
{p} S1 {q} {q} S2 {r}
C où p, q, r ∈ BExpr, S1 , S2 ∈ Imp
{p} S1 ; S2 {r}
3. La règle de la conditionnelle
{p ∧ f } S1 {q} {p ∧ ¬f } S2 {q}
I où p, q, f ∈ BExpr, S1 , S2 ∈ Imp
{p} if f then S1 else S2 {q}
4. La règle de la boucle
{p ∧ f } S {p}
W où p, f ∈ BExpr et S ∈ Imp
{p} while f do S od {p ∧ ¬f }
5. La règle de la conséquence
{p} S {q}
P où p, q, p0 , q 0 ∈ BExpr, S ∈ Imp, p0 |= p, q |= q 0
{p0 } S {q 0 }
{x = 1} x := x + 1 {x = 2} {x = 2} x := x ∗ x {x = 4}
C
{x = 1} x := x + 1; x := x ∗ x {x = 4}
{x = 1} x := x + 1; x := x + 2 {x = 17} {x = 17} x := x − 1 {x = 16}
C
{x = 1} x := x + 1; x := x + 2; x := x − 1 {x = 16}
Si on dit la règle de composition est correcte on veut en fait dire que toutes ses instances
(comme par exemple les deux données ci-dessus, mais aussi toutes les autres) sont correctes.
La bonne dénition est donc :
10.1. LE CALCUL DE HOARE 93
J1 ... Jn
Dénition 29 Une règle d'inférence est correcte quand on a pour toutes
J
p1 ... pn
ses instances R :
p
Si toutes ses hypothèses sont valides, |= p1 , . . . , |= pn ,
alors sa conclusion est aussi valide, |= p.
Sur l'exemple donné on s'aperçoit qu'au moins les deux instances données ci-dessus sont cor-
rectes : Pour la première c'est le cas parce que la conclusion est valide, pour la deuxième
instance c'est le cas parce qu'il y a une hypothèse (la première) qui n'est pas valide.
Dans le cas particulier d'une règle d'inférence qui est un axiome, la dénition 29 dit que
Un axiome est correct quand on a pour toutes ses instances : p est valide, c'est-
J p
à-dire que |= p.
Regardons maintenant les règles une par une.
L'axiome de l'aectation À première vue cet axiome peut étonner car on pourrait at-
tendre (à tort) une substitution dans la post-condition au lieu d'une substitution dans la
pre-condition. Intuitivement le raisonnement est comme suit : On veut que, après exécution
de l'aectation, la post-condition q soit vraie pour la nouvelle valeur de x. Or, la valeur de x
après l'aectation est la valeur de l'expression t avant l'aectation. Donc, toute propriété de x
après l'aectation correspond à la même propriété de t avant l'aectation. Plus formellement :
σ[x/[[t]]σ] |= p
σ |= p[x/t]
A
{(x = t)[x/t]} x := t {x = t}
Maintenant si x 6∈ V(t) alors on a que (x = t)[x/t] donne la formule t = t qui est trivialement
vraie. Par contre, si la variable x parait en t alors t[x/t] donne quelque chose diérent de t, et
la formule n'est en général pas vraie.
94 CHAPITRE 10. UN CALCUL POUR LES FORMULES DE HOARE
La règle de composition dit simplement que pour démontrer une assertion de correction
partielle {p} S {r}, où S est une séquence d'instructions, il sut de couper la séquence S en
deux morceaux S1 et S2 et de montrer une assertion de correction partielle pour S1 et une autre
pour S2 . Dans ces deux assertions on utilise un lemme q qui exprime une propriété qui
doit être vraie après avoir exécuté S1 , et que nous pouvons donc utiliser comme pre-condition
dans l'assertion de correction partielle de S2 .
Proposition 18 La règle de composition est correcte.
Démonstration: Soient {p} S1 {q} et {q} S2 {r} valides. Il faut montrer que {p} S1 ; S2 {r}
est valide.
Soit σ une aectation avec σ |= p et [[S1 ; S2 ]]σ = σ 0 . Il y a donc σ 00 avec [[S1 ]]σ = σ 00 et
[[S2 ]]σ 00 = σ 0 . Puisque {p} S1 {q} est valide on a que σ 00 |= q , et puisque {q} S2 {r} est valide
on a donc aussi que σ 0 |= q . 2
La règle de la boucle est la règle la plus intéressante. L'expression p dans cette règle est
l'invariant. La règle dit que si l'exécution du corps S de la boucle conserve la véracité de
l'invariant p (où en plus on a le droit d'utiliser le fait que la garde f de la boucle est vraie
quand on commence l'exécution du corps), alors on a aussi que la véracité de p est conservée
par l'exécution de la boucle entière. De plus on sait qu'après l'exécution de la boucle la garde
de la boucle est fausse.
Par exemple, une instance de cette règle est
{x = y ∧ x > 42} x := x − 1; y := y − 1 {x = y}
W
{x = y} while x > 42 do x := x − 1; y := y − 1 od {x = y ∧ x ≤ 42}
10.1. LE CALCUL DE HOARE 95
An de démontrer la correction de la règle de la boucle nous avons besoin d'une petite propo-
sition :
Dans cette proposition, σi est l'état de la mémoire au début de la i-ème itération de la boucle,
et σi+1 est l'état de la mémoire à la n de la i-ème itération de la boucle.
Exercice 19 Montrer la proposition 20 par induction sur le nombre d'itération dans l'exécu-
tion de la boucle.
Exercice 20 Dire pour chacune des règles suivantes si elle est correcte ou pas.
1.
{p1 } S {q1 } {p2 } S {q2 }
{p1 ∧ p2 } S {q1 ∧ q2 }
2.
{p} S {q}
{True} if p then S else x := 0 {q}
96 CHAPITRE 10. UN CALCUL POUR LES FORMULES DE HOARE
p3
p1 p2 p4
p5 p6
p7
est un arbre dont la racine est étiquetée par p7 , et pour qu'il soit une preuve il faut que ,
p1
98 CHAPITRE 11. PREUVES DE CORRECTION PARTIELLE
p3 p1 p2 p4 p5 p6
, et soient des instances d'axiomes, et que , et soient
p2 p3 p6 p4 p5 p7
des instances de règles d'inférence.
La dénition formelle des preuves utilise le principe de la dénition inductive :
Dénition 30 L'ensemble des preuves dans le calcul de Hoare est le plus petit ensemble tel
que :
Si R est instance d'un axiome alors R est une preuve (de la formule p).
p p
p1 ... pn
Si Π1 , . . . , Πn sont des preuves des formules p1 , . . . , pn respectivement, et si R
p
Π1 ... Πn
est instance d'une règle d'inférence, alors R est une preuve (de la formule
p
p).
une preuve de pi , et R
p1 ...
p
pn
est une instance de la règle d'inférence R. Par
hypothèse de récurrence, les pi sont valides, donc p est aussi valide car toutes les règles
d'inférence du calcul de Hoare sont correctes (propositions 18 à 21). 2
Des exemples de preuves sont donnés en gure 11.1 et en gure 11.2. Dans ces preuves nous
permettons des simplications évidentes dans les expressions booléennes, comme par exemple
de remplacer une formule True ∧ p par p, etc. Remarquez qu'il existe en général plusieurs
preuves diérentes pour la même formule (quand elle est valide). Un premier exemple pour
cela est donné page 99 avec une programme consistant en deux aectations.
A
{x + 1 ≥ 1} x := x + 1 {x ≥ 1}
P A
{x ≥ 0} x := x + 1 {x ∗ x ≥ 1} {x ∗ x ≥ 1} x := x ∗ x {x ≥ 1}
C
{x ≥ 0} x := x + 1; x := x ∗ x {x ≥ 1}
A A
{x + 1 ≥ 1} x := x + 1 {x ≥ 1} {x ∗ x ≥ 1} x := x ∗ x {x ≥ 1}
P P
{x ≥ 0} x := x + 1 {x ≥ 1} {x ≥ 1} x := x ∗ x {x ≥ 1}
C
{x ≥ 0} x := x + 1; x := x ∗ x {x ≥ 1}
A A
{x ≥ y} z := x {z ≥ y} {y ≥ y} z := y {z ≥ y}
P P
{x > y} z := x {z ≥ y} {x ≤ y} z := y {z ≥ y}
I
{True} if x > y then z := x else z := y {z ≥ y}
A
{x + 1 > 5} x := x + 1 {x > 5}
P
{x > 5 ∧ x > 5} x := x + 1 {x > 5}
W
{x > 5}while x > 5 do x := x + 1 od {x > 5 ∧ ¬x > 5}
P
{x > 5} while x > 5 do x := x + 1 od {x = 52}
L'exemple 7 de la section 9.2 (la garde d'une boucle n'est pas vraie directement après la
boucle)
A
{True} x := x − 1 {True}
P
{True ∧ x > 0} x := x − 1 {True}
W
{True} while x > 0 do x := x − 1 od {True ∧ ¬x > 0}
P
{True} while x > 0 do x := x − 1 od {x ≤ 0}
Exemple d'une preuve avec une boucle utilisant un raisonnement avec un invariant :
A
{x − 1 = y − 1} x := x − 1 {x = y − 1}
P A
{x = y ∧ x > 5} x := x − 1 {x = y − 1} {x = y − 1} y := y − 1 {x = y}
C
{x = y ∧ x > 5} x := x − 1; y := y − 1 {x = y}
W
{x = y}while x > 5 do x := x − 1; y := y − 1 {x = y ∧ ¬x > 5}
P
{x = 42 ∧ x = y} while x > 5 do x := x − 1; y := y − 1 od {y ≤ 5}
qu'il y a des cas dans lesquels une plus faible pre-condition n'existe pas. Si une plus faible
pre-condition existe, est-elle unique ? Évidemment elle ne peut pas être unique dans le sens
stricte car elle est dénie seulement par sa sémantique (c'est-à-dire, par caractérisation des
aectations pour lesquelles elle est vraie). Donc, si p1 est une plus faible pre-condition de S
par rapport à q , et si p2 est logiquement équivalent à p1 , alors p2 est aussi une plus faible
pre-condition de S par rapport à q . La proposition suivante en fait énonce que la notion de
pre-condition la plus faible est unique à équivalence logique près :
Proposition 22 Soit p1 et p2 sont des plus faibles pre-conditions de S par rapport à q alors
p1 |=| p2 .
Démonstration: Soit σ ∈ A une aectation. Alors, nous avons selon la dénition 31 que
σ |= p1
ssi [[S]]σ = ⊥ ou [[S]]σ |= q
ssi σ |= p2
Donc, p1 |=| p2 . 2
Pour cette raison, nous parlons aussi souvent de la pre-condition la plus faible.
La proposition suivante donne quelques propriétés importantes de cette notion, et justie son
nom.
Démonstration:
1. Soit σ ∈ A. Si σ 6|= p alors σ |= {p} S {q}, et si σ |= p alors nous avons après la
dénition 31 que [[S]]σ = ⊥ ou [[S]]σ |= q , donc σ |= {p} S {q}.
2. Soit σ ∈ Af f avec σ |= p0 , nous devrons montrer que σ |= p. Puisque σ |= {p0 } S {q} et
σ 0 |= p, nous avons que [[S]]σ = ⊥ ou [[S]]σ |= q , et donc après dénition 31 que σ |= p. 2
Une fois la notion de la plus faible pre-condition établie, il nous faut maintenant étudier com-
ment on peut eectivement la calculer pour un programme donné et une post-condition donnée.
La façon utilisée pour construire cette pre-condition, si elle existe, va certainement dépendre
de la structure du programme. Puisque l'ensemble des programmes est déni inductivement,
il faudra répondre aux questions suivantes :
Comment calculer une plus faible pre-condition pour une seule aectation ?
Comment calculer une plus faible pre-condition pour une séquence d'instructions, sous l'hy-
pothèse qu'on sait calculer des plus faibles pre-conditions pour chaque instructions dans la
liste ?
Comment calculer une plus faible pre-condition pour une instruction conditionnelle, sous
l'hypothèse qu'on sait calculer de plus faibles pre-conditions pour les deux branches ?
Comment calculer une plus faible pre-condition pour une boucle, sous l'hypothèse qu'on
sait calculer une plus faible pre-condition pour son corps ?
Ici, calculer une plus faible pre-condition pour un programme S veut dire avoir une
méthode pour calculer wp(S, q) pour n'importe quelle post-condition q . Commençons avec une
aectation :
102 CHAPITRE 11. PREUVES DE CORRECTION PARTIELLE
while x > 0 do x := x − y od
et q la formule x = 0. Quelle pourrait être la plus faible pre-condition de S par rapport à q ?
Il faudrait quelle exprime d'abord le fait que x ≥ 0 (car, si x < 0), le programme termine et
la post-condition n'est pas satisfaite), et en plus il faudrait que si y ≥ 0 alors que x soit un
multiple de y . Mais comment exprimer le fait que x soit un multiple de y ? Nous ne pouvons
pas l'exprimer dans notre logique car pour cela il nous faudrait dire il existe une valeur
z telle que x = y ∗ z , sans connaître cette valeur ! Cela est seulement possible dans une
extension de notre logique qui dépasse le cadre de ce cours.
En fait il se trouve que la question de la construction d'une plus faible pre-condition dans
le cas d'une boucle est liée à la question de trouver le bon invariant pour une boucle (voir
l'exercice 22. Malheureusement il n'y a pas de bonne méthode pour trouver un bon invariant.
Il est en principe possible de construire un tel invariant à partir de la post-condition de la
boucle mais cela nécessite une logique plus expressive (la logique du premier ordre que nous
11.2. VERS UNE AUTOMATISATION DES PREUVES ? 103
avons déjà plusieurs fois évoquée). La justication de la construction repose sur un théorème
de l'arithmétique modulaire, et l'invariant obtenu n'est pas un invariant naturel .
La seule méthode praticable est de connaître le bon invariant. La meilleure personne à
connaître l'invariant est le programmeur qui a écrit le programme. Il est donc crucial que le
programmeur donne les invariants des boucles avec le programme. Ça peut être simplement
en forme d'un commentaire dans le programme, mais certains langages de programmation
permettent d'écrire un invariant directement dans le programme, comme par exemple avec
l'instruction assert de Objective CAML. L'utilisation de cette instruction n'est pas restreinte
à des invariants de boucles, le programmeur peut mettre une telle assertion à n'importe quel
point de contrôle dans le programme. Les assertions servent d'une part à la documentation du
programme (et en particulier à la vérication de sa correction partielle, comme nous avons vu),
mais elles sont aussi utiles pour mieux tester le programme. Dans le cas du langage Objective
CAML, le compilateur permet de générer du code qui évalue les assertions chaque fois qu'une
instruction assert est rencontrée, et qui signale quand une assertion n'est pas satisfaite. Il est
aussi possible de générer du code qui ignore les assertion, et dans ce cas on a aucune perte de
performance dans le code engendré. Une instruction similaire est disponible dans le langage
C++ où elle est dénie par le pre-processeur (au lieu d'être dénie dans le noyeau du langage,
ou dans une bibliothèque).
Exercice 22 Soit S =while e do S1 od, et supposons que r = wp(S, q). Montrer que
1. |= {r ∧ e} S1 {r}
2. r ∧ ¬e |= q
Indication : Pour la première question, utiliser le fait que le programme S est équivalent au
programme if e then S1 ; S .
Un exemple de la construction d'une preuve par la méthode des plus faibles pre-conditions est
donné page 104.
Malheureusement la page n'est pas susamment large pour dessiner l'arbre en un seul morceaux :
104
A
{(x + y) ∗ (x + y) − (x + y + x + y) = y ∗ y − 1)} x := x + y {x ∗ x − (x + x) = y ∗ y − 1}
P
{x = 1} x := x + y {x ∗ x − (x + x) = y ∗ y − 1}
{x = 1} x := x + y; z := x + x {x ∗ x − z = y ∗ y − 1} A
{x ∗ x − z = y ∗ y − 1} x := x ∗ x {x − z = y ∗ y − 1}
C A
{x = 1} x := x + y; z := x + x; x := x ∗ x {x − z = y ∗ y − 1} {x − z = y ∗ y − 1} x := x − z {x = y ∗ y − 1}
C
{x = 1} x := x + y; z := x + x; x := x ∗ x; x := x − z {x = y ∗ y − 1}
11.3. RÉFÉRENCES ET REMARQUES 105
La règle de calcul pour la plus faible pre-condition est la raison pourquoi nous essayons toujours
de construire la preuve dans le sens qui commence avec la n du programme, c'est-à-dire
avec la post-condition. Une approche duale qui commence au début du programme avec la
pre-condition, et qui avance par construction successive d'une plus forte post-condition est
également possible, le problème est simplement que la règle de construction d'une plus forte
post-condition dans le cas d'une aectation est plus compliquée.
oui si m rase m0
pour tout m0 ∈ V : m(m ) =
0
non si m ne rase pas m0
Notre barbier b est un habitant de notre village. Nous reformulons la spécication du com-
108 CHAPITRE 12. PROBLÈMES NON DÉCIDABLES
entrée un programme source, par exemple en Java, et sort un autre programme, par exemple
en langage machine.
On considère des programmes qui prennent en argument un programme ou plusieurs pro-
grammes. On peut supposer qu'avec chaque programme le nombre des arguments est indiqué.
Si P est un programme qui attend n arguments alors nous notons P (P1 , . . . , Pn ) le résultat
de l'exécution du programme P avec les n arguments P1 , . . . , Pn . Nous supposons que tout
programme envoie un résultat en forme d'une chaîne de caractères quand il a terminé. Il y a
bien sûr la possibilité que l'exécution d'un programme sur des arguments donnés ne termine
pas. Nous écrivons P (P1 , . . . , Pn ) = s quand l'exécution termine et envoie le résultat s, et
P (P1 , . . . , Pn ) = ⊥ quand l'exécution ne termine pas. Le fait qu'un programme peut ne pas
terminer pose une petite diculté supplémentaire par rapport à la section 12.1.
Nous pouvons maintenant donner une spécication de la fonction pour laquelle nous montrons
dans la suite qu'il n'y a a pas de programme :
oui si P1 (P2 ) 6= ⊥
Pour tout programme P1 , P2 : H(P1 , P2 ) =
non si P1 (P2 ) = ⊥
On appelle H le problème d'arrêt. Il s'agit évidemment d'un problème fondamentale de l'infor-
matique, et on serait bien content d'avoir un programme qui répond à cette spécication. Cela
permettrait par exemple d'écrire un compilateur qui rejette les programmes qui ne terminent
pas. Une solution naïve qui consiste simplement en exécutant P1 avec entrée P2 et de voir
si ça termine ne marchera pas : Si l'exécution termine alors on peut certainement répondre
oui , mais qu'est-ce qu'on peut conclure si le programme tourne toujours après une heure,
ou après un jour, ou après deux mois ?
Nous allons dans la suite montrer qu'un tel programme H ne peut pas exister. Supposons par
l'absurde qu'un tel programme H existe. On ne peut pas appliquer tout de suite l'argument de
la section 12.1 à cette fonction H , nous allons plutôt construire d'abord un autre programme
B en utilisant le programme hypothétique H . On construit le programme B suivant :
Du coup, les exemples des preuves dans les gures 11.1 et 11.2 deviennent beaucoup plus
digestes.
Les règles d'inférences portent un nom, le nom doit être utilisé dans l'écriture d'une preuve.
Dans les preuves, on demande de mettre un trait aussi au-dessus d'une feuille.
La dénition de la plus faible pre-condition a changé : maintenant elle est dénie séman-
tiquement, avant elle était dénie comme la plus faible (par rapport à l'implication logique)
parmi toutes les pre-conditions valides. Cela a fait possible d'énoncer (et de montrer) la
proposition 26. Le fait que la plus faible pre-condition est en fait la plus faible parmi toutes
les pre-conditions valides est maintenant simplement une observation (proposition 23).
Ajouté le nouveau chapitre 6 sur la modélisation en logique propositionnelle.