Exercice 1 : Preuve de programmes : triplets de Hoare, invariants et variants
🌐 Énoncé du problème / Cadre d’étude
Section titled “🌐 Énoncé du problème / Cadre d’étude”La vérification formelle de programmes est une discipline essentielle pour garantir la sûreté et la correction de systèmes critiques (aéronautique, ferroviaire, médical). Formalisée par C.A.R. Hoare, la logique des assertions permet de prouver mathématiquement qu’un programme satisfait des spécifications données sous forme de préconditions et de postconditions.
Dans ce problème, on s’intéresse à l’établissement formel de la correction partielle et totale de programmes impératifs simples manipulables en langage pivot de type OCaml/Python, en se basant sur le formalisme des triplets de Hoare : où est la précondition, la commande (le programme) et la postcondition.
On rappelle les règles de base de la logique de Hoare :
- Règle d’affectation :
- Règle de composition :
- Règle de la conséquence :
- Règle de l’itération (boucle
while) :
L’objectif de ce problème est de concevoir, annoter et prouver la correction totale (correction partielle + terminaison) d’un algorithme de calcul de division entière par soustractions successives optimisées, puis d’analyser les limites théoriques de ces méthodes automatiques.
❓ Questions progressives (incluant une analyse critique du modèle)
Section titled “❓ Questions progressives (incluant une analyse critique du modèle)”-
Règle d’affectation multiple et conditions de Floyd-Hoare Soit le bloc d’affectations simultanées suivant : En utilisant la règle de substitution arrière (calcul de la plus faible précondition ), déterminer la formule logique la plus générale telle que le triplet soit valide.
-
Correction partielle d’une boucle On considère la fonction qui calcule le produit de deux entiers naturels et par additions itérées. Voici le code annoté :
# Précondition : a >= 0 et b >= 0res = 0i = 0while i < a:res = res + bi = i + 1# Postcondition : res == a * bProposer un invariant de boucle permettant de prouver la correction partielle de ce programme. Démontrer l’étape d’initialisation, la conservation de l’invariant, et l’assertion finale.
-
Terminaison et Variant Pour le programme de la question 2, définir un variant de boucle (ou fonction de rang) à valeurs dans un ensemble bien fondé. Démontrer que ce variant décroit strictement à chaque itération et qu’il est minoré, garantissant ainsi la terminaison de la boucle.
-
Synthèse d’un algorithme de division entière On souhaite concevoir un algorithme calculant le quotient et le reste de la division euclidienne d’un entier par un entier , c’est-à-dire vérifiant et , en n’utilisant que des additions et soustractions. Rédiger le code complet avec ses assertions (précondition, invariant, variant, postcondition) et expliciter la preuve de correction totale.
-
Analyse critique du modèle et indécidabilité Dans le cadre de la vérification formelle automatisée (par exemple via des solveurs SMT ou des assistants de preuve comme Coq), on cherche à concevoir un vérificateur de programmes parfait qui prend en entrée un code source annoté et renvoie
Truesi et seulement si le programme est totalement correct. En utilisant une réduction depuis le problème de l’arrêt (Halting Problem), démontrer qu’un tel vérificateur automatique général (capable de trouver des invariants arbitraires sans aide humaine) ne peut pas exister.
💡 Pistes & Indications méthodologiques
Section titled “💡 Pistes & Indications méthodologiques”- Question 1 : Procédez par substitution simultanée. Remplacez par et par dans l’expression de la postcondition. Développez et simplifiez algébriquement.
- Question 2 : L’invariant doit lier les variables modifiées (, ) aux variables d’entrée (, ) en exprimant l’état d’avancement du calcul. Pensez à l’analogie avec la récurrence mathématique.
- Question 3 : Le variant est souvent une expression de type . Vérifiez que sa valeur entière diminue strictement et qu’elle ne peut pas descendre en dessous d’un seuil sans provoquer la sortie de boucle.
- Question 4 : Pour l’invariant de la division, maintenez la relation fondamentale tout au long de la décroissance de ou de la croissance de .
- Question 5 : Réfléchissez à la possibilité d’encoder l’arrêt d’une machine de Turing arbitraire sous la forme de la correction totale d’un programme annoté.
📋 Corrigé détaillé pas à pas
Section titled “📋 Corrigé détaillé pas à pas”Correction 1 : Règle d’affectation multiple
Section titled “Correction 1 : Règle d’affectation multiple”La règle d’affectation pour variables et une postcondition s’écrit par la plus faible précondition :
Ici, la postcondition est . On substitue par et par :
Développons l’expression :
La formule logique la plus générale est donc .
Correction 2 : Correction partielle de la multiplication itérée
Section titled “Correction 2 : Correction partielle de la multiplication itérée”Soit l’invariant de boucle :
-
Initialisation (avant l’entrée dans la boucle) : Avant la boucle, et .
- est vrai puisque la précondition garantit .
- est vrai. L’invariant est donc vérifié au démarrage.
-
Conservation (corps de boucle) : On suppose l’invariant vrai ainsi que la condition de boucle . Montrons que l’invariant est préservé après l’exécution de : Notons et les nouvelles valeurs :
- .
- Comme et que est un entier, . De plus, , donc . L’invariant est donc conservé.
-
Assertion finale : À la sortie de la boucle, on a , c’est-à-dire : Puisque et (dans ), on a nécessairement . En injectant dans l’égalité de l’invariant : La postcondition est bien établie.
Correction 3 : Terminaison de la multiplication itérée
Section titled “Correction 3 : Terminaison de la multiplication itérée”On associe à la boucle le variant (ou fonction de rang) : Vérifions les deux propriétés requises pour un variant dans (ensemble bien fondé) :
- Minoration : Dans la boucle, la condition de continuation est , ce qui implique , soit (ou selon la borne exacte). Plus précisément, tant que la boucle tourne, .
- Décroissance stricte : À chaque itération, la variable est incrémentée de (), tandis que reste invariant. La nouvelle valeur du variant est : On a bien .
Le variant décroit strictement à chaque étape tout en étant minoré par , ce qui garantit la terminaison du programme.
Correction 4 : Algorithme de division entière par soustractions
Section titled “Correction 4 : Algorithme de division entière par soustractions”Voici l’algorithme annoté calculant le quotient et le reste de par (, ) :
# Précondition : A >= 0 et B > 0q = 0r = A
# Invariant : I ≡ A == q * B + r and r >= 0# Variant : v = rwhile r >= B: r = r - B q = q + 1
# Postcondition : A == q * B + r and 0 <= r < BDémonstration de la correction totale :
- Initialisation : Avant la boucle, et .
- . L’invariant est vérifié.
- (par précondition).
- Conservation : Supposons et la condition de boucle .
Soit et .
- (par invariant). L’invariant est conservé.
- De plus, , donc .
- Terminaison : Le variant est un entier. À chaque itération, avec , donc . Le variant décroît strictement. Comme la boucle ne s’exécute que si , le variant reste positif ou nul à l’intérieur de la boucle et décroît vers une valeur strictement inférieure à , assurant la sortie.
- Postcondition : À la sortie de la boucle, on a , soit : Ce qui correspond exactement à la spécification de la division euclidienne.
Correction 5 : Analyse critique du modèle et indécidabilité
Section titled “Correction 5 : Analyse critique du modèle et indécidabilité”Supposons par l’absurde qu’il existe un algorithme (ou un vérificateur automatique total) capable de prendre n’importe quel code source annoté avec ses pré/postconditions, invariants et variants, et de décider en temps fini si le programme est totalement correct.
Considérons un problème insoluble bien connu : le problème de l’arrêt (Halting Problem). Soit une machine de Turing (ou un programme arbitraire) et une entrée . On souhaite savoir si s’arrête.
On peut construire un programme source qui simule l’exécution de sur . On associe à ce programme une postcondition triviale (par exemple, ) et on cherche à prouver la correction totale de . Pour réussir cette preuve de terminaison de manière automatique, le vérificateur devrait être capable de déterminer si se termine, ce qui résoudrait le problème de l’arrêt pour une machine arbitraire .
Or, d’après le théorème de Turing, le problème de l’arrêt est indécidable. Par conséquent, il ne peut pas exister de vérificateur automatique universel capable de prouver la correction totale de tous les programmes sans intervention humaine. C’est pourquoi les assistants de preuve modernes (comme Coq ou Why3) nécessitent des annotations interactives (fourniture manuelle d’invariants complexes, de traits de récurrence ou de preuves de lemmes auxiliaires par le ou la programmeuse).