Lecture notes 20210301 Hoare Logic 2

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 : 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 : 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 qn * q + [[X]] = mAND 0 ≤ [[X]AND NOT [[n ≤ X]}} .
and we may use the following loop invariant:
       (EXISTS qn * q + [[X]] = mAND 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 : (P Q R: Assertion) (c1 c2: com),
   {{P}c1  {{Q}}  ->
   {{Q}c2  {{R}}  ->
   {{P}c1;;c2  {{R}} .

Axiom hoare_skip : P,
   {{P}Skip  {{P}} .

Axiom hoare_if : 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 : 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 [[AB]] }
       C ::= B - A
        {{ 0 ≤ [[C]] AND ( [[C]] + [[A]] = [[B]] OR [[C]] + [[B]] = [[A]] ) }} .

Hypothesis triple2:
        {{ True AND NOT [[AB]] }
       C ::= A - B
        {{ 0 ≤ [[C]] AND ( [[C]] + [[A]] = [[B]] OR [[C]] + [[B]] = [[A]] ) }} .

Fact if_minus_dec_correct:
        {{ True }
       If AB
       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 : 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: m: Z,
        {{ 0 ≤ m }
       X ::= m
        {{ [[X]] = m AND 0 ≤ [[X]] }} .

Hypothesis triple2: m n: Z,
        {{ [[X]] = m AND 0 ≤ [[X]] }
       Y ::= 0
        {{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] }} .

Hypothesis triple3: m n: Z,
        {{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND [[nX]] }
       X ::= X - n
        {{ n * [[Y]] + [[X]] + n = m AND 0 ≤ [[X]] }} .

Hypothesis triple4: 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: m n: Z,
        {{ 0 ≤ m }
       X ::= m;;
       Y ::= 0;;
       While nX Do
         X ::= X - n;;
         Y ::= Y + 1
       EndWhile
        {{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[nX]] }} .
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: m n: Z,
        {{ 0 ≤ m }
       X ::= m;;
       Y ::= 0;;
       While nX Do
         X ::= X - n;;
         Y ::= Y + 1
       EndWhile
        {{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[nX]] }} .
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: m n: Z,
        {{ 0 ≤ m }
       X ::= m;;
       Y ::= 0;;
       While nX Do
         X ::= X - n;;
         Y ::= Y + 1
       EndWhile
        {{ n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[nX]] }} .
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 _ (nX) _ 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 yn * 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 *)