2021-11-12 12:16:31 +01:00

458 lines
14 KiB
Coq
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(*** TP7 : Coinduction et induction bien fondée ***)
(*** Partie 1 : Divergence et coinduction ***)
(* Rappel de certaines tactiques de Coq :
La tactique [constructor.] en Coq permet d'utiliser directement un constructeur
d'une définition inductive sans redonner son nom (Coq le cherchera de lui même)
[apply H with v.]
Parfois, en utilisant la tactique [apply] en Coq, vous aurez le message d'erreur
suivant: "Error: Unable to find an instance for the variable x."
Dans ce cas là, Coq n'arrive pas à deviner tout seul quel choix il doit faire
pour la variable x de la proposition H. En utilisant [apply H with v], vous
pouvez indiquer à Coq qu'il faut prendre "v" comme valeur pour la variable "x".
[assumption.]
Si vous devez prouver "H" et que "H" est en hypothèse, assumption termine
automatiquement la preuve.
[inversion H.]
Demande à Coq de trouver les conditions nécéssaires pour que l'hypothèse H soit
vraie. Dans le cas ou H est fausse, inversion termine directement la preuve.
[unfold f.]
Lorsque "f" est une définition, unfold permet de remplacer le nom "f" par sa
définition. La variante [unfold f in H] permet de remplacer "f" par sa
définition dans l'hypothèse H.
[simpl.]
Demande à Coq de simplifier le but, en appliquant des fonctions par exemple.
[case a.]
Quand "a" est un objet défini par une définition inductive, "case a" permet de
traiter les différents cas possibles de la définition.
[subst.] Lorsque vous utilisez cette tactique, Coq simplifie vos hypothèse en
utilisant des substitutions intelligentes, par exemple "a=b", "b = c", "c = d",
et "a + b = c + d" seront remplacés par "a + a = a + a".
Très pratique lorsque vous avez utiliseé la tactique [inversion] juste avant,
qui introduit souvent des noms de variables pas très utiles.
*)
(* En cours, vous aviez vu que pour définir la divergence des programmes de IMP,
une définition inductive ne fonctionnait pas. Vous avez alors utilisé
Knaster-Tarski en version duale, en prenant un plus grand post-point fixe
au lieu du plus petit pré-point fixe. *)
(* On va illustrer ce principe en Coq, pour cela on va travailler sur la
divergence dans un graphe orienté infini. Le graphe que l'on considère
est le suivant :
Ensemble des sommets :
{ a ; b ; c ; d } { p n | n ∈ }
Arêtes :
{ (a,b) ; (b,c) ; (b,d) ; (d,b) ; (c,p 0) } { (p n,p (n+1)) | n ∈ }
Tout d'abord, formalisez en Coq ce graphe. *)
Inductive state : Set :=
| a : state
| b : state
| c : state
| d : state
| p : nat -> state
.
Inductive step : state -> state -> Prop :=
| step_ab : step a b
| step_bc : step b c
| step_bd : step b d
| step_db : step d b
| step_cp : step c (p 0)
| step_pp : forall n, step (p n) (p (S n))
.
(* On définit la divergence comme un prédicat **coinductif** :
on applique le théorème de Knaster-Tarski, pour raisonner sur le plus grand
post-point fixe d'un opérateur f. On rappelle qu'un post-point fixe pour f
est un ensemble X tel que X ⊆ f(X). *)
(* Pour rappel, l'utilisation "classique" de Knaster-Tarski consiste à travailler
sur le plus petit point fixe, qui est un ensemble X tel que f(X) ⊆ X. *)
CoInductive diverges : state -> Prop :=
| div : forall s1 s2, step s1 s2 -> diverges s2 -> diverges s1.
(* Tout d'abord, des questions sur Knaster-Tarski:
1] Quelle est la définition de l'opérateur f correspondant à la définition
de "diverges" ci-dessus ?
f(X) = {x tq exists y€X step x y}
2] Que donnerait cette définition si on remplaçait CoInductive par Inductive ?
f(X) = {x tq exists y€X tq x --> y}
*)
(* On veut d'abord montrer qu'il existe une divergence à partir de a,
en suivant la boucle b ⟳ d. Essayez de prouver ce lemme en utilisant
les tactiques que vous connaissez en Coq. Quel est le problème ? *)
Lemma div_abd_fail : diverges a.
cut (diverges b).
apply (div a b).
apply step_ab.
repeat
((apply (div b d));
(apply (step_bd));
(apply (div d b));
(apply (step_db))).
Abort.
(* Pour résoudre ce problème, il vous faudra utiliser la "tactique miraculeuse" [cofix.]
ou [cofix H.], dont vous pourrez découvrir l'effet.
L'idée de "cofix" est d'ajouter en hypothèse le but que vous souhaitez prouvez...
Mais ne croyez pas trop aux miracles : [cofix.] suivi de [assumption.] engendrera une
erreur au moment de sauvegarder la preuve avec [Qed.] (ce que vous êtes invité.e.s à
vérifier).
*)
Lemma div_abd : diverges a.
apply (div a b).
apply step_ab.
cofix H.
apply (div b d).
apply step_bd.
apply (div d b).
apply step_db.
assumption.
Qed.
(*******************)
(* Vous pouvez ensuite inspecter la preuve qui a été engendrée *)
Print div_abd.
(* Principe de preuve par coinduction *)
(* Un ensemble d'états est représenté par une fonction :
f : state → bool
correspondant à sa fonction indicatrice.
On définit un prédicat sur les ensembles d'états, pour dire qu'un
tel ensemble est un post-point fixe (pour la fonction f donnée plus haut) *)
(* Donnez cette définition *)
Definition post_fixpoint (X : state -> bool) : Prop :=
forall y, X y = true -> (exists x, step y x /\ X x = true).
(* Le théorème important exprime le principe de preuve par coinduction :
tout post-point fixe est inclus dans le plus grand post-point fixe.
Prouvez-le, en ayant à l'esprit qu'il faudra utiliser un cofix quelque part.
Post point fixe : X € f(X)*)
Theorem coinduction : forall X : state -> bool, post_fixpoint X -> forall s, X s = true -> diverges s.
intro.
intro H.
cofix Hc.
unfold post_fixpoint in H.
intro s.
intro Hh.
apply H in Hh. destruct Hh. destruct H0.
apply (div s x). assumption.
apply Hc. assumption.
Qed.
(*******************)
(* On souhaite maintenant prouver que tous les sommets de la forme (p n) sont
aussi divergents. Pourquoi la preuve précédente avec la boucle b ⟳ d pour a
ne peut-elle pas marcher ?
Parce que y a un nombre arbitraire d'étapes *)
(* Il faut donc utiliser plus en détail le principe de preuve par coinduction,
et notamment s'aider de post-point fixes. *)
(* Pour commencer, définissez l'ensemble Xp des sommets de la forme "p n". *)
Definition Xp (s : state) : bool :=
match s with
| a|b|c|d => false
| p n => true
end.
(* Prouvez ensuite le lemme suivant *)
(* Si vous voulez, vous pouvez essayer ici la notation ";" de Coq.
Lorsque vous lancez une preuve avec différents cas, le ";" permet d'appliquer
les même tactiques à tous les cas sans copier/coller. Vous pouvez alors
utiliser "try" pour essayer d'appliquer une tactique sans que Coq ne s'arrête
s'il n'y arrive pas.
Observez par exemple la preuve suivante : *)
Lemma example_step : forall s, s = a \/ s = d -> step s b.
intro s. case s;intros ;destruct H;try constructor; try inversion H.
Qed.
(* Tous les cas ont été traités d'un coup, on commence par un intros,
on fait une analyse de cas sur le "ou", puis si il y a un step
vers b ("try constructor") on le fait, sinon c'est qu'on n'est ni a ni d,
donc "try inversion H" doit finir la preuve *)
(* Comprendre cette syntaxe n'est pas obligatoire, mais elle peut parfois
accélerer les preuves, ce qui peut vous être utile. Vous pouvez trouver
plus d'infos en ligne si vous voulez. *)
Lemma inv_Xp : forall s, Xp s = true -> exists n, s = p n.
induction s; intro H; try simpl in H; inversion H.
exists n. reflexivity.
Qed.
(* Vous pouvez maintenant prouver la divergence des "p n" en vous aidant
du théoreme de coinduction *)
(* Vous aurez sûrement besoin de la tactique suivante :
[destruct f.]
si "f" est le nom d'un lemme f : A -> B, "destruct f" vous permet de
récuperer "B" dans vos hypothèses, mais Coq vous demandera de prouver
également "A". C'est utile pour utiliser un lemme que vous avez prouvé
précedemment mais pour lequel vous ne pouvez pas utiliser directement [apply].
Remarque : Comme [apply with ...], on peut aussi utiliser
[destruct with ...]
quand Coq n'arrive pas à deviner tout seul la valeur de certaines variables. *)
Lemma div_p : forall n, diverges (p n).
intro.
destruct coinduction with Xp (p n).
unfold post_fixpoint. intro. intro.
apply inv_Xp in H.
destruct H. subst.
exists (p (S x)).
split.
apply step_pp.
simpl. reflexivity.
simpl. reflexivity.
apply (div s1 s2).
assumption.
assumption.
Qed.
(*******************)
(*** Partie 2 : Bonne fondation, accessibilité, définition de fonctions ***)
(* Nous allons travailler ici sur les relations bien fondées *)
Print well_founded.
(* En Coq, une relation de A*A est bien fondée si tous les éléments de A
sont accessibles pour cette relation *)
Print Acc.
(* Un élément est dit accessible si tous ses prédécesseurs sont accessibles *)
(* Notez que la définition d'Acc est similaire au PIBF.
Cependant, par rapport au cours, la relation considérée est la relation réciproque.
C'est-à-dire que la relation (nat, >) est terminante, mais pour Coq,
c'est < qui est bien-fondée. *)
(*Remarque : on peut avoir l'impression ici qu'il n'y a pas de cas de base,
et que donc Acc est toujours vide, mais il faut remarquer qu'un élément minimal
pour R satisfait toujours Acc_intro. *)
(* Pour résumer, une relation "R" est bien fondée, pour cette définition Coq de
bonne fondation, quand tous les élements sont atteignables à partir des éléments
minimaux, de façon inductive. *)
(* Pour commencer, montrez que la relation < est bien fondée *)
Require Import Lt.
(* Indice : La preuve suivante est valide: *)
Lemma trivialité : forall x y, S x <= y -> x < y.
intros. apply H.
Qed.
Lemma lemme_nul : forall n y, y <= n -> y < n \/ y = n.
induction n.
intros.
right. inversion H. reflexivity.
induction y.
intros. left. apply PeanoNat.Nat.lt_0_succ.
intro H.
simpl in H.
Search le.
apply PeanoNat.Nat.le_S_n.
Theorem well_founded_lt : well_founded lt.
Proof.
intro n.
induction n.
apply Acc_intro.
intros.
inversion H.
apply Acc_intro.
inversion IHn.
intro y.
intro Hh.
cut (y<n \/ y=n).
intro.
inversion H0.
apply H in H1. assumption.
subst. assumption.
cut (y<=n).
intro.
Search le.
apply H0.
cut (y <= n).
intro.
apply H0.
assumption.
Qed.
(*******************)
(*** Accessibilité et divergence ***)
Require Import Lists.Streams.
(* le type des listes infinies -- **uniquement** infinies : pas de constructeur Nil. *)
Print Stream.
(* On souhaite donner la définition usuelle de relation bien fondée :
"il n'existe pas de chaine infinie décroissante".
Pour faire ça, il faut d'abord définir la notion de chaine infinie décroissante en Coq,
et surprise, on a besoin de coinduction pour ça. *)
CoInductive infiniteDecreasingChain A (R : A -> A -> Prop) : Stream A -> Prop :=
| ChainCons : forall x y s, infiniteDecreasingChain A R (Cons y s) (*Si on a une chaine infinie décroissante de la forme y::s, avec s infinie décroissante *)
-> R y x (* Si en plus on sait que x est plus grand que y *)
-> infiniteDecreasingChain A R (Cons x (Cons y s)). (* Alors x::y::s est aussi ine chaine infinie décroissante *)
(* Définissez maintenant une notion alternative de "être bien fondée" en utilisant les chaines *)
Definition wf2 A (R : A -> A -> Prop) := [...]
(* Maintenant, prouvez que si un élément est accessible, il ne peux pas commencer de chaine infinie décroissante *)
Lemma noBadChains : forall A (R : A -> A -> Prop) x, Acc R x
-> forall s, ~infiniteDecreasingChain A R (Cons x s).
[...]
Qed.
(* Prouvez maintenant le lemme suivant *)
Lemma well_founded_bad_chains : forall A (R: A -> A -> Prop), well_founded R -> wf2 A R.
[...]
Qed.
(*******************)
(*** Donner des arguments de terminaison à Coq ***)
Require Import ZArith.
Require Import Recdef.
Require Import List.
(* Dans Coq, toutes les fonctions que vous donnez doivent terminer. Sur des
fonctions récursives simples, Coq va trouver automatiquement qu'elles
terminent en cherchant un objet qui décroit strictement à chaque appel
récursif. Cependant, Coq n'arrive parfois pas à inférer ce qui décroit
strictement, et il faut l'aider. *)
(* Essayez d'écrire une fonction "merge", qui prend en entrée deux listes
d'entiers triées et retourne la liste triée correspondant à la fusion
de ces deux listes. Vous pouvez utiliser la fonction "leb".
*)
Check leb.
Fixpoint merge_fail1 (p: list nat) (q: list nat) : list nat :=
[...]
(* Essayons également en utilisant la forme non curryfiée... *)
Fixpoint merge_fail2 (p:list nat * list nat) : list nat :=
[...]
(* Coq devrait vous donner une erreur dans les deux cas. On va alors
l'aider à comprendre pourquoi cette fonction termine. *)
(* Trouvez une fonction:
mesure : (list nat * list nat) → nat
telle que "mesure" décroit strictement dans les appels récursifs de merge. *)
Definition mesure (p : list nat *list nat) :=
[...]
(* On va maintenant pouvoir définir la fonction merge. On utilise alors une autre syntaxe :
[Function ... ]
généralise Fixpoint en autorisant à faire autre chose qu'une récurrence sur
la structure du premier argument de la fonction, on peut donc lui donner
explicitement la mesure qui va décroitre.
Coq va alors nous demander de prouver que cette mesure décroit effectivement
Allez-y, definissez merge et prouvez que "mesure" décroit.
*)
Function merge (p:list nat * list nat) { measure mesure p } : list nat :=
[...]
.
[...]
Qed.
(*******************)
(*** Retour aux relations bien fondées ***)
(*Donnez la définition de l'ordre lexicographique sur le type bool*nat,
en prenant false est plus petit que true *)
Inductive lexico : bool*nat -> bool*nat -> Prop :=
[...]
(* Prouvez ensuite que cet ordre est bien fondé. *)
Theorem well_founded_lex : well_founded lexico.
[...]
Qed.
(*******************)