(** Remark. Some material in this lecture is from << Software Foundation >>
volume 2. *)
(** Like our last lecture, we need to import [Imp] first. *)
Require Import PL.Imp.
Import Assertion_S.
Import Concrete_Pretty_Printing.
(* ################################################################# *)
(** * Examples about [hoare_if] *)
(** Recall that this is our proof rule for if commands. We will now use it in
some examples. *)
Axiom hoare_if : forall P Q b c1 c2,
{{ P AND {[b]} }} c1 {{ Q }} ->
{{ P AND NOT {[b]} }} c2 {{ Q }} ->
{{ P }} If b Then c1 Else c2 EndIf {{ Q }}.
(* ================================================================= *)
(** ** Computing difference *)
(** Here's a simple program using conditionals, with a possible specification:
*)
(**
{{ True }}
If A <= B
Then C ::= B - A
Else C ::= A - B
EndIf
{{ 0 <= {[C]} AND ( {[C]} + {[A]} = {[B]} OR {[C]} + {[B]} = {[A]} ) }}.
*)
(** We can prove it by [hoare_if] and the following two facts:
1. {{ True AND {[A <= B]} }}
C ::= B - A
{{ 0 <= {[C]} AND ( {[C]} + {[A]} = {[B]} OR {[C]} + {[B]} = {[A]} ) }}.
2. {{ True AND NOT {[A <= B]} }}
C ::= A - B
{{ 0 <= {[C]} AND ( {[C]} + {[A]} = {[B]} OR {[C]} + {[B]} = {[A]} ) }}.
*)
(* ================================================================= *)
(** ** Counter example revisited *)
(** In order to prove:
{{ True }}
If X == 0
Then Y ::= 2
Else Y ::= X + 1
EndIf
{{ {[X]} <= {[Y]} }}
we can use [hoare_if]. Then, we need to prove:
{{ True AND {[X == 0]} }}
Y ::= 2
{{ {[X]} <= {[Y]} }}
and
{{ True AND NOT {[X == 0]} }}
Y ::= X + 1
{{ {[X]} <= {[Y]} }},
which are both valid *)
(** It is worth mentioning that [hoare_if_first_try] is not strong enough here.
If we try to use [hoare_if_first_try], we have to show that
{{ True }}
Y ::= 2
{{ {[X]} <= {[Y]} }}
and
{{ True }}
Y ::= X + 1
{{ {[X]} <= {[Y]} }}.
They correspond to two assumptions of [hoare_if_first_try]. Now, it is obvious
that the first triple here is not true. *)
(* ################################################################# *)
(** * Axiomatic semantics for loops *)
(** Axiomatic semantics of loops: *)
Axiom hoare_while : forall P b c,
{{ P AND {[b]} }} c {{P}} ->
{{P}} While b Do c EndWhile {{ P AND NOT {[b]} }}.
(** Here, the assertion [P] is called a _loop invariant_ (循环不变量). *)
(* ================================================================= *)
(** ** Example: Reduce to Zero *)
(** This program will terminate until [X] is zero.
{{ True }}
While !(X == 0) Do
X ::= X - 1
EndWhile
{{ {[ X ]} = 0 }}
*)
(** Using [True] as loop invariant! [{[ X ]} = 0] is equivalent to
[NOT {[ !(X == 0) ]}]. *)
(* ================================================================= *)
(** ** Example: Division *)
(** For fixed positive integer [m] and [n], the following program calculates the
integer quotient and remainder:
{{ 0 <= m AND 0 < n }}
X ::= m;;
Y ::= 0;;
While n <= X Do
X ::= X - n;;
Y ::= Y + 1
EndWhile
{{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} AND NOT {[n <= X]} }}.
*)
(** We write [NOT {[n <= X]}] instead of [{[X]} < n] in order to fit the format
of [hoare_while]. We can prove this triple using the following loop invariant:
n * {[Y]} + {[X]} = m AND 0 <= {[X]}.
Only two things are critical:
1. {{ 0 <= m AND 0 < n }}
X ::= m;;
Y ::= 0
{{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} }}.
2. {{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} AND {[n <= X]} }}
X ::= X - n;;
Y ::= Y + 1
{{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} }}.
*)
(* ================================================================= *)
(** ** How to find a good loop invariant? *)
(** The most critical steps in our examples above is to find a good loop
invariant. Is there a generic method of doing that? *)
(** Let's consider a specific case in our slow division example above. Suppose
[n = 3] and [m = 10]. Then the loop body is executed for 3 times:
1. {{ {[X]} = 10 AND {[Y]} = 0 }}
X ::= X - 3;;
Y ::= Y + 1
{{ {[X]} = 7 AND {[Y]} = 1 }};
2. {{ {[X]} = 7 AND {[Y]} = 1 }}
X ::= X - 3;;
Y ::= Y + 1
{{ {[X]} = 4 AND {[Y]} = 2 }};
3. {{ {[X]} = 4 AND {[Y]} = 2 }}
X ::= X - 3;;
Y ::= Y + 1
{{ {[X]} = 1 AND {[Y]} = 3 }}.
Loop invariant should be preserved by any execution of loop body (suppose the
loop condition is true at the beginning). Thus, our loop invariant [P] in this
case should not be stronger than
({[X]} = 10 AND {[Y]} = 0) OR
({[X]} = 7 AND {[Y]} = 1) OR
({[X]} = 4 AND {[Y]} = 2) OR
({[X]} = 1 AND {[Y]} = 3),
or else
{{ P }} LOOP_BODY {{ P }}
LOOP SPECIFICATIONS's PRECONDITION |-- P
cannot be true at the same time. *)
(** Also, our loop invariant should not be too weak, or else [ P AND NOT {[b]} ]
is not strong enough to describe an ideal property. In this specific case, let's
first try setting our loop invariant as strong as possible. In other words, we
let [P] be
({[X]} = 10 AND {[Y]} = 0) OR
({[X]} = 7 AND {[Y]} = 1) OR
({[X]} = 4 AND {[Y]} = 2) OR
({[X]} = 1 AND {[Y]} = 3).
Thus,
P AND NOT {[3 <= X]} |-- {[X]} = 1 AND {[Y]} = 3.
This is already good enough. We can also restate this [P] in a more concise way:
n * {[Y]} + {[X]} = 10 AND 0 <= {[X]} AND 0 <= {[Y]}.
BTW, a weaker loop invariant may also work (the one we used previously):
n * {[Y]} + {[X]} = 10 AND 0 <= {[X]}.
Here are some general principles. *)
(** (1) A loop invariant should not be too strong. The program states before and
after every single loop body's iteration should satisfy the invariant. *)
(** (2) A loop invariant [P] should not be too weak to derive meaningful
conclusion from the conjunction [ P AND NOT {[b]} ]. *)
(* ================================================================= *)
(** ** Example: Remainder Only *)
(** The following program calculates the remainder only.
X ::= m;;
While n <= X Do
X ::= X - n
EndWhile
What specification shall we write? How to prove it? *)
(**
We need to use existential quantifiers for help. We may write:
{{ 0 <= m }}
X ::= m;;
While n <= X Do
X ::= X - n
EndWhile
{{ (EXISTS q, n * q + {[X]} = m) AND 0 <= {[X]} AND NOT {[n <= X]} }}.
and we may use the following loop invariant:
(EXISTS q, n * q + {[X]} = m) AND 0 <= {[X]}
*)
(* ================================================================= *)
(** ** Example: Slow subtraction *)
(** How to prove that the following program calculates the subtraction?
X ::= m;;
Y ::= p;;
While !(X == 0) Do
Y ::= Y - 1;;
X ::= X - 1
EndWhile
*)
(** Solution: using invariant
{[Y]} - {[X]} = p - m.
*)
(* ================================================================= *)
(** ** Example: Squaring *)
(** The following program squares [X] by repeated addition. How to prove its
correctness?
I ::= 0;;
RES ::= 0;;
While !(I == X) Do
RES ::= RES + X;;
I ::= I + 1
EndWhile
*)
(** Solution: using invariant
{[RES]} = {[I]} * m AND {[X]} = m.
*)
(* ================================================================= *)
(** ** Example: Finding Square Roots *)
(** The following program computes the (integer) square root of [X]
by naive iteration. How to prove its correctness?
I ::= 0;;
While (I+1)*(I+1) <= X Do
I ::= I+1
EndWhile
*)
(**
Solution. Using invariant
{[I]} * {[I]} <= m AND {[X]} = m.
*)
(* ################################################################# *)
(** * Program correctness proof in Coq: more examples *)
(** So far, we've introduced four axioms for four program constructor. They are: *)
Module Axiomatic_semantics.
Axiom hoare_seq : forall (P Q R: Assertion) (c1 c2: com),
{{P}} c1 {{Q}} ->
{{Q}} c2 {{R}} ->
{{P}} c1;;c2 {{R}}.
Axiom hoare_skip : forall P,
{{P}} Skip {{P}}.
Axiom hoare_if : forall P Q b c1 c2,
{{ P AND {[b]} }} c1 {{ Q }} ->
{{ P AND NOT {[b]} }} c2 {{ Q }} ->
{{ P }} If b Then c1 Else c2 EndIf {{ Q }}.
Axiom hoare_while : forall P b c,
{{ P AND {[b]} }} c {{P}} ->
{{P}} While b Do c EndWhile {{ P AND NOT {[b]} }}.
End Axiomatic_semantics.
(** Because we use these axioms about Hoare triples to define program semantics,
such definition is called an _axiomatic semantics_ (公理化语义). We also say that
these axioms form a _proof system_ (推理系统), or a _Hoare logic_ (霍尔逻辑).
Here we show how to use this logic to prove program correctness in Coq. *)
(* ================================================================= *)
(** ** Example: Simple Conditionals *)
Module if_minus_dec.
Import Axiomatic_semantics.
Local Instance A: var := new_var().
Local Instance B: var := new_var().
Local Instance C: var := new_var().
Hypothesis triple1:
{{ True AND {[A <= B]} }}
C ::= B - A
{{ 0 <= {[C]} AND ( {[C]} + {[A]} = {[B]} OR {[C]} + {[B]} = {[A]} ) }}.
Hypothesis triple2:
{{ True AND NOT {[A <= B]} }}
C ::= A - B
{{ 0 <= {[C]} AND ( {[C]} + {[A]} = {[B]} OR {[C]} + {[B]} = {[A]} ) }}.
Fact if_minus_dec_correct:
{{ True }}
If A <= B
Then C ::= B - A
Else C ::= A - B
EndIf
{{ 0 <= {[C]} AND ( {[C]} + {[A]} = {[B]} OR {[C]} + {[B]} = {[A]} ) }}.
Proof.
apply hoare_if.
+ apply triple1.
+ apply triple2.
Qed.
(** Here, bullets (+, -, *, ++, --, **, etc.) are used to separated two proof
goals' proof scripts. *)
End if_minus_dec.
(* ================================================================= *)
(** ** Example: Our Counterexample *)
Module counter_example.
Import Axiomatic_semantics.
Local Instance X: var := new_var().
Local Instance Y: var := new_var().
Axiom hoare_if_first_try : forall P Q b c1 c2,
{{P}} c1 {{Q}} ->
{{P}} c2 {{Q}} ->
{{P}} If b Then c1 Else c2 EndIf {{Q}}.
Fact sample_program_correct_fail:
{{ True }}
If X == 0
Then Y ::= 2
Else Y ::= X + 1
EndIf
{{ {[X]} <= {[Y]} }}.
Proof.
apply hoare_if_first_try.
Abort.
Hypothesis triple1:
{{ True AND {[X == 0]} }}
Y ::= 2
{{ {[X]} <= {[Y]} }}.
Hypothesis triple2:
{{ True AND NOT {[X == 0]} }}
Y ::= X + 1
{{ {[X]} <= {[Y]} }}.
Fact sample_program_correct_:
{{ True }}
If X == 0
Then Y ::= 2
Else Y ::= X + 1
EndIf
{{ {[X]} <= {[Y]} }}.
Proof.
apply hoare_if.
+ apply triple1.
+ apply triple2.
Qed.
End counter_example.
(* ================================================================= *)
(** ** Example: Reduce to Zero *)
Module reduce_to_zero.
Import Axiomatic_semantics.
Local Instance X: var := new_var().
Hypothesis triple1:
{{ True AND {[!(X == 0)]} }}
X ::= X - 1
{{ True }}.
Fact reduce_to_zero_correct:
{{ True }}
While !(X == 0) Do
X ::= X - 1
EndWhile
{{ True AND NOT {[ !(X == 0) ]} }}.
Proof.
apply hoare_while.
apply triple1.
Qed.
End reduce_to_zero.
(* ================================================================= *)
(** ** Example: Division *)
Module div_mod_dec.
Import Axiomatic_semantics.
Local Instance X: var := new_var().
Local Instance Y: var := new_var().
Hypothesis triple1: forall m: Z,
{{ 0 <= m }}
X ::= m
{{ {[X]} = m AND 0 <= {[X]} }}.
Hypothesis triple2: forall m n: Z,
{{ {[X]} = m AND 0 <= {[X]} }}
Y ::= 0
{{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} }}.
Hypothesis triple3: forall m n: Z,
{{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} AND {[n <= X]} }}
X ::= X - n
{{ n * {[Y]} + {[X]} + n = m AND 0 <= {[X]} }}.
Hypothesis triple4: forall m n: Z,
{{ n * {[Y]} + {[X]} + n = m AND 0 <= {[X]} }}
Y ::= Y + 1
{{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} }}.
Fact div_mod_dec_correct: forall m n: Z,
{{ 0 <= m }}
X ::= m;;
Y ::= 0;;
While n <= X Do
X ::= X - n;;
Y ::= Y + 1
EndWhile
{{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} AND NOT {[n <= X]} }}.
Proof.
intros.
apply hoare_seq with ({[X]} = m AND 0 <= {[X]})%assert.
{ apply triple1. }
apply hoare_seq with (n * {[Y]} + {[X]} = m AND 0 <= {[X]})%assert.
{ apply triple2. }
apply hoare_while.
apply hoare_seq with (n * {[Y]} + {[X]} + n = m AND 0 <= {[X]})%assert.
+ apply triple3.
+ apply triple4.
Qed.
(** It is worth mentioning the proof style in the Coq proof script above. In
short, bullets (+, -, *, ++, --, **, etc.) and proof blocks ("{" and "}") can be
used to illustrate proof structures. Usually, they are used for two different
purposes. If two or more subgoals are equally complicated, we would prefer to
use bullets to list their proofs parallelly. If one branch among all subgoals
is the main proof target and the other proof goals are just side steps, we would
prefer to use proof blocks. *)
Fact div_mod_dec_correct_2: forall m n: Z,
{{ 0 <= m }}
X ::= m;;
Y ::= 0;;
While n <= X Do
X ::= X - n;;
Y ::= Y + 1
EndWhile
{{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} AND NOT {[n <= X]} }}.
Proof.
intros.
(** Originally, if we want to use [apply] to prove this Hoare triple in the
backward direction, we have to feed it an argument, indicating what the middle
condition [Q] in [hoare_seq] represents. But if we use [eapply] instead of
[apply], we do not need to provide that argument. Mainly, [eapply hoare_seq]
says, we will apply this proof rule [hoare_seq] without knowing what [Q] is,
and we will know what that [Q] should be later. *)
eapply hoare_seq.
(** As you can see, the unknown argument is marked as [?Q] which appear both in
the first proof goal and the second proof goal. Now, we use [triple1] to
solve the first proof goal. It will instantiate [?Q]. *)
{ apply triple1. }
eapply hoare_seq.
{ apply triple2 with (n := n). }
apply hoare_while.
eapply hoare_seq.
+ apply triple3.
+ apply triple4.
Qed.
Fact div_mod_dec_correct_3: forall m n: Z,
{{ 0 <= m }}
X ::= m;;
Y ::= 0;;
While n <= X Do
X ::= X - n;;
Y ::= Y + 1
EndWhile
{{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} AND NOT {[n <= X]} }}.
Proof.
intros.
pose proof triple1 m.
pose proof triple2 m n.
pose proof triple3 m n.
pose proof triple4 m n.
pose proof hoare_seq _ _ _ _ _ H1 H2.
clear H1 H2.
pose proof hoare_while _ (n <= X) _ H3.
clear H3.
pose proof hoare_seq _ _ _ _ _ H0 H1.
pose proof hoare_seq _ _ _ _ _ H H2.
exact H3.
Qed.
End div_mod_dec.
(** Till now, we have learned how to prove a program correct by Hoare
logic and how to use [apply] in Coq to formalize those proofs.
You might have noticed two tiny problems. 1. We have proved nothing for
assignment commands. We only assume that some Hoare triples about assignment
commands are true. Coq forces us to distinguish which parts really get proved
and which parts are actually hypotheses. 2. Our postconditions look not nice.
For example, in [div_mod_correct] we have to write
{{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} AND NOT {[n <= X]} }}
instead of
{{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} AND {[X]} < n }}.
Coq forces us to strictly follow axioms' statements. But we really hope that we
can at least replace an assertion with an equivalent one. *)
(** We will introduce several more axioms in response to this concern. *)
(* ################################################################# *)
(** * Assignment rule (forward) *)
(** We clearly see a common pattern when we try to write postconditions for
assignment commands with form [X ::= E]. Each of these postconditions has two
parts (1) the original value of [X] satisfies the precondition; (2) the current
value of [X] can be calculated by [E] using the original value of [X]. The
original value of [X] plays a very important role here! But, we cannot always
refer to the original value directly. Here is an example: *)
(** What is the best postcondition for the following command?
{{ n * {[Y]} + {[X]} = m AND 0 <= {[X]} AND {[X]} < n }}
Y ::= 0
{{ ??? }}
*)
(** The best postcondition is:
{{ EXISTS y, n * y + {[X]} = m AND
0 <= {[X]} AND {[X]} < n AND {[Y]} = 0 }}
This example gives us a hint---we do not need to refer to the old value
directly; we can talk about it using the existential quantifier. In general,
given a precondition [P], a program variable [X] and a program expression [E],
we can write the following postcondition informally:
There exists an old value x of program variable
X, such that (1) the precondition P would hold if
the value of X would be x (2) the value of X is
result of evaluating the expression E if treating
all occurrences of X in E as x.
*)
(* 2021-03-04 08:28 *)