Lecture notes 20210303 Hoare Logic 3

Remark. Some material in this lecture is from << Software Foundation >> volume 2.
Require Import PL.Imp.

Import Assertion_S.
Import Assertion_S_Tac.
Import Concrete_Pretty_Printing.
Last time, we discovered a common pattern in describing a strongest postcondition of an assignment command. That is, given a precondition P, and an assignment command X := E, their postcondition can be:
       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.
For example, the strongest postcondition of
        {n * [[Y]] + [[X]] = m AND 0 ≤ [[X]AND [[X]] < n }
       Y ::= 0
        {{ ??? }
can be:
        {EXISTS yn * y + [[X]] = m AND
          0 ≤ [[X]AND [[X]] < n AND [[Y]] = 0 }} .
Today, we will describe that in logic formally.

Symbolic substitution

The assertion language

We have seen many assertions. They mainly follow the following syntax rules.
P ::= t < t, t = t, t t, ... | [[ B ]] | P AND P | P OR P | NOT P | EXISTS x, P | FORALL x, P
t ::= 0, 1, -1, ... | x | [[ E ]] | t + t | t - t | t * t | ...
where x represents logical variables, E represents integer program expressions and B represents boolean integer program expressions. When we define an assertion that
       P would hold if the value of X would be x
or an expression that
       replacing all occurrences of X in E with x
we can complete the construction according to P's (or E's) syntax.

Symbolic substitution in program expressions

Suppose E is an integer-type expression of the programming language,
    E [X ⟼ E0]
represents the result of replacing every occurence of program variable X with program expression E0 in E. This result is still an integer-type expression.
What is the result of the following symbolic substitution (x is some constant)?
    (X + Y) [ X ⟼ x ]
x + Y
What is the result of the following symbolic substitution?
    (X - Y) [ X ⟼ 1 ]
1 - Y
What is the result of the following symbolic substitution?
    (X + Y) [ X ⟼ X - Y ]
(X - Y) + Y
What is the result of the following symbolic substitution?
    (X * Y) [ X ⟼ 1 ]
1 * Y
What is the result of the following symbolic substitution?
    (X + 1) [ X ⟼ 1 ]
1 + 1
  • (X * Y) [ X 1] is 1 * Y , not Y.
  • (X + 1) [ X 1] is 1 + 1 , not 2.
Remark. 1 * Y and Y are two different program expressions although their values are always the same.
We can also apply symbolic substitution on a boolean expression. Specifically, for a boolean-type expression B and an integer-type expression E0, we use
    B [X ⟼ E0]
to represent the result of replacing every occurence of program variable X with E0 in B, e.g.
    (!(X ≤ Y))[X ⟼ 0]
is ! (0 Y) .

Symbolic substitution in assertions

We can also define symbolic substitution in assertions. We use
    P [X ⟼ E0]
to represent the result of replacing every occurence of program variable X with program expression E0 in assertion P. For example,
        ([[X + Y ≤ Z]AND 0 ≤ [[X]] + [[Y]]) [X ⟼ X - Y]
        ([[X + Y ≤ Z]AND 0 ≤ [[X]] + [[Y]]) [X ⟼ x]
        ([[X + Y ≤ Z]AND 0 ≤ [[X]] + [[Y]]) [X ⟼ 0]
are
        [[(X - Y) + Y ≤ Z]AND 0 ≤ [[X - Y]] + [[Y]]
        [[x + Y ≤ Z]AND 0 ≤ [[x]] + [[Y]].
        [[0 + Y ≤ Z]AND 0 ≤ [[0]] + [[Y]].
In other words, the effect of symbolic substitution on an assertion P is applying substitution on those program expressions mentioned in P.

Assignment rule (forward)

The following axiom describes the behavior of assignment commands.
Axiom hoare_asgn_fwd : P `(X: var) E,
   {{ P }
  X ::= E
   {{ EXISTS x, P [Xx] AND [[X]] = [[ E [Xx] ]] }} .
Write down the postcondition described by hoare_asgn_fwd.
        {[[X]] = x AND [[Y]] = y }
       TEMP ::= X;;
        {{ ??? }
EXISTS x0, [[x0]] = x AND [[Y]] = y AND [[TEMP]] = [[x]]
Write down the postcondition described by hoare_asgn_fwd.
        {{ 0 ≤ [[Y]}
       X ::= Y;;
        {{ ??? }
EXISTS x0, 0 [[Y]] AND [[X]] = [[Y]]
Write down the postcondition described by hoare_asgn_fwd.
        {n * [[Y]] + [[X]] = m AND 0 ≤ [[X]AND [[n ≤ X]}
       X ::= X - n
        {{ ??? }
EXISTS x0, n * [[Y]] + [[x0]] = m AND 0 [[x0]] AND [[n x0]] AND [[X]] = [[x0 - n]]
Write down the postcondition described by hoare_asgn_fwd.
        {n * [[Y]] + [[X]] + n = m AND 0 ≤ [[X]}
       Y ::= Y + 1
        {{ ??? }
EXISTS y0, n * [[y0]] + [[X]] + n = m AND 0 [[X]] AND [[Y]] = [[y0 + 1]]
Write down the postcondition described by hoare_asgn_fwd.
        {[[X]] = x AND [[Y]] = y }
       X ::= X + Y;;
        {{ ??? }
EXISTS x0, [[x0]] = x AND [[Y]] = y AND [[X]] = [[x0 + Y]]

Assignment rule (backward)

When C. A. R. Hoare first proposed Hoare logic for program correctness proof in 1960s, the assignment rule is not in the forward direction; it was in the backward direction. Robert W. Floyd proposed forward assignment rule later. Due to this reason, people also use the nomenclature "Floyd-Hoare" logic sometimes. (Another reason is that the original ideas of Hoare logic were seeded by Floyd's work of a similar system for flowcharts.)
Axiom hoare_asgn_bwd : P `(X: var) E,
   {{ P [ XE] }X ::= E  {{ P }} .
Write down the precondition described by hoare_asgn_bwd.
        {{ ??? }
       X ::= X + Y;;
        {[[X]] = x + y AND [[Y]] = y }
[[X + Y]] = x + y AND [[Y]] = y
Write down the precondition described by hoare_asgn_bwd.
        {{ ??? }
       Y ::= Y + 1
        {n * [[Y]] + [[X]] = m AND 0 ≤ [[X]}
n * [[Y + 1]] + [[X]] = m AND 0 [[X]]

Consequence rule


Axiom hoare_consequence : (P P' Q Q' : Assertion) c,
  P  P' ->
   {{P'}c  {{Q'}}  ->
  Q'  Q ->
   {{P}c  {{Q}} .

Summary: axiomatic semantics


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]] }} .

Axiom hoare_asgn_fwd : P `(X: var) E,
   {{ P }
  X ::= E
   {{ EXISTS x, P [Xx] AND [[X]] = [[ E [Xx] ]] }} .

Axiom hoare_asgn_bwd : P `(X: var) E,
   {{ P [ XE] }X ::= E  {{ P }} .

Axiom hoare_consequence : (P P' Q Q' : Assertion) c,
  P  P' ->
   {{P'}c  {{Q'}}  ->
  Q'  Q ->
   {{P}c  {{Q}} .

End Axiomatic_semantics.

Module div_mod_dec_again.
Import Axiomatic_semantics.
Coq proof time! Now, we are able to prove a nicer Hoare triple with simpler hypothese.
Local Instance X: var := new_var().
Local Instance Y: var := new_var().

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

Hypothesis derivation2: m n: Z,
  EXISTS z, n * [[Y]] + z = m AND 0 ≤ z AND nz AND [[X]] = z - n  
  n * [[Y]] + [[X]] + n = m AND 0 ≤ [[X]].

Hypothesis derivation3: m n: Z,
  EXISTS z, n * z + [[X]] + n = m AND 0 ≤ [[X]] AND [[Y]] = z + 1  
  n * [[Y]] + [[X]] = m AND 0 ≤ [[X]].

Hypothesis derivation4: m n: Z,
  n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[nX]]  
  n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND [[X]] < n.

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 [[X]] < n }} .
Proof.
  intros.
  eapply hoare_seq.
  { apply hoare_asgn_fwd. }

  assert_subst.
  assert_simpl.
After eliminating the first assignment command using hoare_seq and hoare_asgn_fwd, we see the substitution symbol and the existential quantifier in our precondition. Our library PL.Imp provided the two tactics to simplify such assertions.
  eapply hoare_seq.
  { apply hoare_asgn_fwd. }
  assert_subst.
  assert_simpl.

  apply hoare_consequence with
    (n * [[Y]] + [[X]] = m AND 0 ≤ [[X]])%assert
    (n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[ nX ]] )%assert.
  1: { apply (derivation1 m n). }
  2: { apply derivation4. }

  apply hoare_while.
  eapply hoare_seq.
  { apply hoare_asgn_fwd. }
  assert_subst.
  assert_simpl.

  eapply hoare_consequence.
  + apply derivation2.
  + apply hoare_asgn_fwd.
  + assert_subst.
    assert_simpl.
    apply derivation3.
Qed.
Now we try to prove the same Hoare triple again with forward proof style.
Fact div_mod_dec_correct_again: 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 [[X]] < n }} .
Proof.
  intros.

  pose proof hoare_asgn_fwd
               (n * [[Y]] + [[X]] = m AND
                0 ≤ [[X]] AND [[nX]])%assert
               X (X - n).
These two tactics assert_subst and assert_simpl can also simplify assertions in assumptions
  assert_subst in H.
  assert_simpl in H.

  pose proof hoare_asgn_fwd
               (n * [[Y]] + [[X]] + n = m AND 0 ≤ [[X]])%assert
               Y (Y + 1).
  assert_subst in H0.
  assert_simpl in H0.

  pose proof hoare_consequence _ _ _ _ _
               (derivation2 m n) H0 (derivation3 m n).
  pose proof hoare_seq _ _ _ _ _ H H1.
  clear H H0 H1.
It is unfortunate that we cannot achieve a Hoare triple for the while loop here using H2 and hoare_while because the assertion that loop condition is true
    [[n ≤ X]]
has been simplified into n [[X]] . Thus, we assert the triple that we want to prove.
  assert ( {{n * [[Y]] + [[X]] = m AND 0 ≤ [[X]}} } While nX Do (X ::= X - n);; Y ::= Y + 1 EndWhile  {{n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] AND NOT [[nX]}} }).
After assert, we first prove the asserted triple.
  {
    apply hoare_while.
    assert_simpl.
    exact H2.
  }
  clear H2.
We then prove the original proof goal with an extra assumption—-the asserted triple H.
  pose proof hoare_asgn_fwd
               (0 ≤ m)%assert
               X
               m.
  assert_subst in H0.
  assert_simpl in H0.

  pose proof hoare_asgn_fwd
               (0 ≤ m AND [[X]] = m)%assert
               Y
               0.
  assert_subst in H1.
  assert_simpl in H1.

  pose proof hoare_consequence _ _ _ _ _
               (derivation1 m n) H (derivation4 m n).
  pose proof hoare_seq _ _ _ _ _ H1 H2.
  pose proof hoare_seq _ _ _ _ _ H0 H3.
  exact H4.
Qed.

End div_mod_dec_again.

Derived proof rules and programmable proof tactics


Module derived_rules.

Import Assertion_S_Rules.
Import Axiomatic_semantics.
We know that an assertion can always derive itself. This property is called derives_refl in our Imp library.
Check derives_refl.
  (* : forall P : Assertion, P  ⊢ P *)
Using it, we can derive two specialized consequence rule. The first one is a single-sided consequence rule for preconditions.
Corollary hoare_consequence_pre: P P' Q c,
  P  P' ->
   {{ P' }c  {{ Q }}  ->
   {{ P }c  {{ Q }} .
Proof.
  intros.
  eapply hoare_consequence.
  + exact H.
  + exact H0.
  + apply derives_refl.
Qed.
Similarly, we have a single-sided consequence rule for postconditions as well.
Corollary hoare_consequence_post: P Q Q' c,
   {{ P }c  {{ Q' }}  ->
  Q'  Q ->
   {{ P }c  {{ Q }} .
Proof.
  intros.
  eapply hoare_consequence.
  + apply derives_refl.
  + exact H.
  + exact H0.
Qed.
Since we prove these two rules from primary rules, we call them derived rules (导出规则). There are other derived proof rules. They can be useful for some special situations. The following rule is a weaker version of hoare_if (we have shown why it is weak in our previous lectures). We can derive it from our primary hoare_if rule now. But this proof is based on the following properties of AND.
Check AND_left1.
  (* : forall P Q R : Assertion,
       P  ⊢ R -> P AND Q  ⊢ R *)


Check AND_left2.
  (* : forall P Q R : Assertion,
       Q  ⊢ R -> P AND Q  ⊢ R *)


Corollary hoare_if_weak : P Q b c1 c2,
   {{P}c1  {{Q}}  ->
   {{P}c2  {{Q}}  ->
   {{P}If b Then c1 Else c2 EndIf  {{Q}} .
Proof.
  intros.
  apply hoare_if.
  + eapply hoare_consequence_pre.
    2: { exact H. }
    apply AND_left1.
    apply derives_refl.
  + eapply hoare_consequence_pre.
    2: { exact H0. }
    apply AND_left1.
    apply derives_refl.
Qed.
Also, we have seen a lot of examples, in which we use hoare_seq and hoare_asgn_fwd together when the first command is an assignment. We can pack them together:
Corollary hoare_asgn_seq: P `(X: var) E c Q,
   {{ EXISTS x, P [Xx] AND [[X]] = [[ E [Xx] ]] }c  {{ Q }}  ->
   {{ P }X ::= E ;; c  {{ Q }} .
Proof.
  intros.
  eapply hoare_seq.
  + apply hoare_asgn_fwd.
  + exact H.
Qed.
What if the assignment is the only command? We have also seen a combination of hoare_asgn_fwd and hoare_consequence.
Corollary hoare_asgn_conseq: P `(X: var) E Q,
  EXISTS x, P [Xx] AND [[X]] = [[ E [Xx] ]]  Q ->
   {{ P }X ::= E  {{ Q }} .
Proof.
  intros.
  eapply hoare_consequence_post.
  + apply hoare_asgn_fwd.
  + exact H.
Qed.
These derived rule can make Coq formalized proofs shorter; and more importantly, they help to describe how we understand Hoare logic proofs. We do not apply proof rules one by one in our mind, we use proof rule "combos" instead. Now, let's redo our proofs about swapping.
Local Instance X: var := new_var().
Local Instance Y: var := new_var().
Local Instance TEMP: var := new_var().

Hypothesis der1: x y,
  EXISTS x0, x0 = x AND [[Y]] = y AND [[TEMP]] = x0 AND [[X]] = [[Y]]  
    [[X]] = y AND [[TEMP]] = x.

Hypothesis der2: x y,
  [[X]] = y AND [[TEMP]] = x AND [[Y]] = [[TEMP]]  
    [[X]] = y AND [[Y]] = x.

Fact swaping_correct:
  x y: Z,
        {{ [[X]] = x AND [[Y]] = y }
       TEMP ::= X;;
       X ::= Y;;
       Y ::= TEMP
        {{ [[X]] = y AND [[Y]] = x }} .
Proof.
(* WORKED IN CLASS *)
  intros.
  apply hoare_asgn_seq.
  assert_subst.
  assert_simpl.
  apply hoare_asgn_seq.
  assert_subst.
  assert_simpl.

  eapply hoare_consequence_pre.
  { apply der1. }
  apply hoare_asgn_conseq.
  assert_subst.
  assert_simpl.

  apply der2.
Qed.

End derived_rules.

Decorated program as informal Hoare logic proof

The beauty of Hoare Logic is that it is compositional (可组合的): the structure of proofs exactly follows the structure of programs. This suggests that we can record the essential ideas of a proof by "decorating" a program with appropriate assertions on each of its commands.
In <<software foundation>>, such informal proofs are called decorated programs. In other text books or programming language literature, they are also called annotated programs or commented programs (带注释程序).
Here are two sample decorated programs.
Sample 1
  /* 0 ≤ m */
  X ::= m;;
  Y ::= 0;;
  /* n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] */
  While n ≤ X Do
    /* n * [[Y]] + [[X]] = m AND 0 ≤ [[X]AND [[n ≤ X]] */
    X ::= X - n;;
    Y ::= Y + 1
    /* n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] */
  EndWhile
  /* n * [[Y]] + [[X]] = m AND 0 ≤ [[X]AND NOT [[n ≤ X]] */
  /* n * [[Y]] + [[X]] = m AND 0 ≤ [[X]AND [[X]] < n */.
Sample 2
  /* 0 ≤ m */
  X ::= m;;
  /* EXISTS x, 0 ≤ m AND [[X]] = m */
  /* 0 ≤ m AND [[X]] = m */
  Y ::= 0;;
  /* EXISTS y, 0 ≤ m AND [[X]] = m AND [[Y]] = 0 */
  /* n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] */
  While n ≤ X Do
    /* n * [[Y]] + [[X]] = m AND 0 ≤ [[X]AND [[n ≤ X]] */
    X ::= X - n;;
    /* EXISTS xn * [[Y]] + [[x]] = m AND
         0 ≤ [[x]AND [[n ≤ x]AND [[X]] = [[x - n]] */
    /* EXISTS xn * [[Y]] + x = m AND
         0 ≤ x AND n ≤ x AND [[X]] = x - n */
    /* n * [[Y]] + [[X]] + n = m AND 0 ≤ [[X]] */
    Y ::= Y + 1
    /* EXISTS yn * [[y]] + [[X]] + n = m AND
         0 ≤ [[X]AND [[Y]] = [[y + 1]] */
    /* EXISTS yn * y + [[X]] + n = m AND
         0 ≤ [[X]AND [[Y]] = y + 1 */
    /* n * [[Y]] + [[X]] = m AND 0 ≤ [[X]] */
  EndWhile
  /* n * [[Y]] + [[X]] = m AND 0 ≤ [[X]AND NOT [[n ≤ X]] */
  /* n * [[Y]] + [[X]] = m AND 0 ≤ [[X]AND [[X]] < n */.

Exercises

Exercise: 3 stars, standard (remainder_only)


Module remainder_only.
Import Axiomatic_semantics.

Local Instance X: var := new_var().
In the this task, you can write hypothese about assignment commands without proving them.
(* FILL IN HERE *)

Fact remainder_only_correct: m n: Z,
     {{ 0 ≤ m }
    X ::= m;;
    While nX Do
      X ::= X - n
    EndWhile
     {{ (EXISTS q, n * q + [[X]] = m) AND 0 ≤ [[X]] AND NOT [[nX]] }} .
Proof.
  intros.
(* FILL IN HERE *) Admitted.
End remainder_only.

Exercise: 2 stars, standard (reduce_to_zero_alter1)

You are only allowed write assertion derivations as hypotheses in this exercise.
Module reduce_to_zero_alter1.
Import Axiomatic_semantics.
Import derived_rules.

Local Instance X: var := new_var().
Hint: the following decorated program shows a proof skeleton.
       /* True */
       While !(X ≤ 0) Do
         /* True AND [[ !(X ≤ 0) ]] */
         /* True */
         X ::= X - 1
         /* EXISTS xTrue AND [[X]] = [[x - 1]] */
         /* True */
       EndWhile
       /* True AND NOT [[ !(X ≤ 0) ]] */
       /* [[X]] ≤ 0 */.
(* FILL IN HERE *)

Fact reduce_to_zero_correct:
        {{ True }
       While !(X ≤ 0) Do
         X ::= X - 1
       EndWhile
        {{ [[X]] ≤ 0 }} .
Proof.
(* FILL IN HERE *) Admitted.
End reduce_to_zero_alter1.

Exercise: 3 stars, standard (reduce_to_zero_alter2)

You are only allowed write assertion derivations as hypotheses in this exercise.
Module reduce_to_zero_alter2.
Import Axiomatic_semantics.
Import derived_rules.

Local Instance X: var := new_var().

(* FILL IN HERE *)

Fact reduce_to_zero_correct:
        {{ 0 ≤ [[X]] }
       While !(X ≤ 0) Do
         X ::= X - 1
       EndWhile
        {{ [[X]] = 0 }} .
Proof.
(* FILL IN HERE *) Admitted.
End reduce_to_zero_alter2.

(* 2021-03-07 20:15 *)