Lecture notes 20190329

Denotational Semantics 2

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

Review: Programs' Denotational Semantics

We have learnt how to define integer expression's denotational semantics. We can define it using Coq's recursive function.
Module Relation_Operators.

Definition id {A: Type}: AAProp := fun a ba = b.

Definition empty {A B: Type}: ABProp := fun a bFalse.

Definition concat {A B C: Type} (r1: ABProp) (r2: BCProp): ACProp :=
  fun a cb, r1 a br2 b c.

Definition filter1 {A B: Type} (f: AProp): ABProp :=
  fun a bf a.

Definition filter2 {A B: Type} (f: BProp): ABProp :=
  fun a bf b.

Definition union {A B: Type} (r1 r2: ABProp): ABProp :=
  fun a br1 a br2 a b.

Definition intersection {A B: Type} (r1 r2: ABProp): ABProp :=
  fun a br1 a br2 a b.

Definition omega_union {A B: Type} (rs: natABProp): ABProp :=
  fun st1 st2n, rs n st1 st2.

End Relation_Operators.

Import Relation_Operators.

Fixpoint aeval (a : aexp) (st : state) : Z :=
  match a with
  | ANum nn
  | AId Xst X
  | APlus a1 a2 ⇒ (aeval a1 st) + (aeval a2 st)
  | AMinus a1 a2 ⇒ (aeval a1 st) - (aeval a2 st)
  | AMult a1 a2 ⇒ (aeval a1 st) * (aeval a2 st)
  end.

Definition aexp_dequiv (d1 d2: stateZ): Prop :=
  st, d1 st = d2 st.

Definition aexp_equiv (a1 a2: aexp): Prop :=
  aexp_dequiv (aeval a1) (aeval a2).

Fixpoint beval (b : bexp) (st : state) : Prop :=
  match b with
  | BTrueTrue
  | BFalseFalse
  | BEq a1 a2 ⇒ (aeval a1 st) = (aeval a2 st)
  | BLe a1 a2 ⇒ (aeval a1 st) ≤ (aeval a2 st)
  | BNot b1 ⇒ ¬(beval b1 st)
  | BAnd b1 b2 ⇒ (beval b1 st) ∧ (beval b2 st)
  end.

Definition if_sem
  (b: bexp)
  (then_branch else_branch: statestateProp)
  : statestateProp
:=
  union
    (intersection then_branch (filter1 (beval b)))
    (intersection else_branch (filter1 (beval (BNot b)))).
Soppose then_branch and else_branch are denotations of the then-branch and else-branch of an if-command. Here, the first clause of union the set of program state pairs (st1, st2) such that:
  • (st1, st2) belongs to then_branch and b is true on st1
And similarly, the second clause of union the set of program state pairs (st1, st2) such that:
  • (st1, st2) belongs to else_branch and b is false on st1
The union of them is the semantics of an if-command.
Fixpoint iter_loop_body
  (b: bexp)
  (loop_body: statestateProp)
  (n: nat)
  : statestateProp
:=
  match n with
  | O
         intersection
           id
           (filter1 (beval (BNot b)))
  | S n'
            intersection
              (concat
                loop_body
                (iter_loop_body b loop_body n'))
              (filter1 (beval b))
  end.

Definition loop_sem (b: bexp) (loop_body: statestateProp)
  : statestateProp
:=
  omega_union (iter_loop_body b loop_body).
Suppose loop_body is the denotation of loop body, then
    iter_loop_body b loop_body n
defines the begining-ending-program-state pairs of excuting the loop body for exactly n times. And
    loop_sem b loop_body
defines whole while-loop's semantics.
Fixpoint ceval (c: com): statestateProp :=
  match c with
  | CSkipid
  | CAss X E
      fun st1 st2
        st2 X = aeval E st1
        Y, XYst1 Y = st2 Y
  | CSeq c1 c2concat (ceval c1) (ceval c2)
  | CIf b c1 c2if_sem b (ceval c1) (ceval c2)
  | CWhile b cloop_sem b (ceval c)
  end.

Definition com_dequiv (d1 d2: statestateProp): Prop :=
  st1 st2, d1 st1 st2d2 st1 st2.

Definition cequiv (c1 c2: com): Prop :=
  com_dequiv (ceval c1) (ceval c2).
Adding all components up, we can define denotational semantics of progrmas and program equivalence.

Bourbaki-Witt Theorem

For now, we have successfully defined a fixpoint construction loop_sem which satisfies the recursive equation loop_recur:
Theorem loop_recur: b loop_body,
  com_dequiv
    (loop_sem b loop_body)
    (union
      (intersection
        (concat loop_body
          (loop_sem b loop_body))
        (filter1 (beval b)))
      (intersection
        id
        (filter1 (beval (BNot b))))).
Proof.
  intros.
  unfold com_dequiv.
  intros.
  split.
  + intros.
    unfold loop_sem, omega_union in H.
    unfold union.
    destruct H as [n H].
    destruct n as [| n'].
    - right.
      simpl in H.
      exact H.
    - left.
      simpl in H.
      unfold concat, intersection in H.
      unfold concat, intersection.
      destruct H as [[st' [? ?]] ?].
      split.
      * st'.
        split.
        { exact H. }
        unfold loop_sem, omega_union.
        n'.
        exact H0.
      * exact H1.
  + intros.
    unfold loop_sem, omega_union.
    unfold union in H.
    destruct H.
    - unfold intersection, concat in H.
      destruct H as [[st' [? ?]] ?].
      unfold loop_sem, omega_union in H0.
      destruct H0 as [n ?].
      (S n).
      simpl.
      unfold intersection, concat.
      split.
      * st'.
        split.
        { exact H. }
        { exact H0. }
      * exact H1.
    - O.
      simpl.
      exact H.
Qed.
This lemma that we proved last time is actually one special case of Bourbaki-Witt fixpoint theorem.

Partial Order

A partial order (偏序) on a set A is a binary relation R (usually written as ) which is reflexive (自反), transitive (传递), and antisymmetric (反对称). Formally,
    xAx ≤ x;
    x y zAx ≤ y → y ≤ z → x ≤ z;
    x yAx ≤ y → y ≤ x → x = y.
The least element of A w.r.t. a partial order is also called bottom:
    xAbot ≤ x

Chain

A subset of elements in A is called a chain w.r.t. a partial order if any two elements in this subset are comparable. For example, if a sequence xs: nat A is monotonically increasing:
    nnatxs n ≤ xs (n + 1),
then it forms a chain.
A partial order is called complete if every chain has its least upper bound lub and greatest lower bound glb. In short, the set A (companied with order ) is called a complete partial ordering, CPO (完备偏序集). Some text books require chains to be nonempty. We do not put such restriction on chain's definition here. Thus, the empty set is a chain. Its least upper bound is the least element of A, in other words, bot.

Monotonic and Continuous Functions

Given two CPOs A, A= and B, B=, a function F: A B is called monotonic (单调) if it preserves order. Formally,
    x yAx ≤Ay → F(x) ≤BF(y).
A function F: A B is called continuous (连续) if it preserves lub. Formally,
    xschain(A), lub(F(xs)) = F(lub(xs))
Here, the lub function on the left hand side means the least upper bound defined by B and the one on the right hand side is defined by A.
The definition of continuous does not require the preservation of glb becasue CPOs are usually defined in a direction that larger elements are more defined .

Least fixpoint

Given a CPO A, we can always construct a sequence of elements as follows:
    botF(bot), F(F(bot)), F(F(F(bot))), ...
Obviously, bot F(bot) is true due to the definition of bot. If F is monotonic, it is immediately followed by F(bot) F(F(bot)). Similarly,
    F(F(bot)) ≤ F(F(F(bot))), F(F(F(bot))) ≤ F(F(F(F(bot)))) ...
In other words, if F is monotonic, this sequence is a chain.
Main theorem: given a CPO A, if it has a least element, then every monotonic continuous function F has a fixpoint and the least fixpoint of F is:
    lub [botF(bot), F(F(bot)), F(F(F(bot))), ...].
Proof.
On one hand, this least upper bound is a fixpoint:
    F (lub [botF(bot), F(F(bot)), F(F(F(bot))), ...]) =
    lub [F(bot), F(F(bot)), F(F(F(bot))), F(F(F(F(bot)))), ...] =
    lub [botF(bot), F(F(bot)), F(F(F(bot))), ...].
The first equality is true because F is continuous. The second equality is true because bot is less than or equal to all other elements in the sequence.
On the other hand, this fixpoint is the least one. For any other fixpoint x, in other words, suppose F(x) = x. Then,
    bot ≤ x
Thus,
    F(bot) ≤ F(x) = x
due to the fact that F is monotonic and x is a fixpoint. And so on,
    F(F(bot)) ≤ xF(F(F(bot))) ≤ xF(F(F(F(bot)))) ≤ x, ...
That means, x is an upper bound of bot, F(bot), F(F(bot)), .... It must be greater than or equal to
    lub [botF(bot), F(F(bot)), F(F(F(bot))), ...].
QED.

Denotation of Loops as Bourbaki-Witt Fixpoint

Our definition loop_sem is actually a Bourbaki-Witt fixpoint of the recursive equation defined by loop_recur. In this case, set A is the set of binary relations between program stats, i.e. A := state state Prop.
The equivalence relation defined on A is com_dequiv. The partial order defined on A is the subset relation, i.e.
    "d1 ≤ d2" := st1 st2, (d1 st1 st2) → (d2 st1 st2).
We can easily show that this binary relation is actually a partial order, i.e., it is reflexive, transitive and antisymmetric.
Moreoever, this partial ordering is a CPO. The least upper bound, lub, of a chain is the union of all binary relations in the chain. Specifically, omega_union defines the lub of a sequence of relations.
In the end, the function that maps d to
    (union
      (concat
        (intersection loop_body
          (filter1 (beval b)))
        d)
      (intersection
        id
        (filter1 (beval (BNot b)))))
is monotonic and continuous. And loop_sem is exactly the Bourbaki-Witt fixpoint of this function.

Program Equivalence

For examples of command equivalence, let's start by looking at some trivial program transformations involving Skip:
Theorem skip_left : c,
  cequiv
    (Skip;; c)
    c.
Proof.
  intros.
  unfold cequiv, com_dequiv.
  intros.
  split; intros.
  + simpl in H.
    unfold concat, id in H.
    destruct H as [st' [? ?]].
    rewrite H.
    exact H0.
  + simpl.
    unfold concat, id.
    st1.
    split.
    - reflexivity.
    - exact H.
Qed.
Also, we can prove that adding a Skip after a command results in an equivalent program.
Theorem skip_right : c,
  cequiv
    (c ;; Skip)
    c.
Proof.
(* WORKED IN CLASS *)
  intros.
  unfold cequiv, com_dequiv.
  intros.
  split; intros.
  + simpl in H.
    unfold concat, id in H.
    destruct H as [st' [? ?]].
    rewrite <- H0.
    exact H.
  + simpl.
    unfold concat, id.
    st2.
    split.
    - exact H.
    - reflexivity.
Qed.
Now we show that we can swap the branches of an IF if we also negate its guard.
Theorem swap_if_branches : b e1 e2,
  cequiv
    (If b Then e1 Else e2 EndIf)
    (If (BNot b) Then e2 Else e1 EndIf).
Proof.
  intros.
  unfold cequiv, com_dequiv.
  intros.
  simpl.
  unfold if_sem.
  unfold union, intersection, filter1.
  split; intros.
  + (* -> *)
    destruct H as [[? ?] | [? ?]].
    - (* b is true *)
      right.
      split.
      * exact H.
      * simpl.
The next line tauto reads "tautology" (重言式). This tactic can be used to reason able normal logic connectives, including implication, conjunction, disjuction, negation and logical equivalence.
        tauto.
    - (* b is false *)
      left.
      split.
      * exact H.
      * exact H0.
  + (* <- *)
    destruct H as [[? ?] | [? ?]].
    - (* b is false *)
      right.
      split.
      * exact H.
      * exact H0.
    - (* b is true *)
      left.
      split.
      * exact H.
      * simpl in H0.
        tauto.
Qed.
An interesting fact about While commands is that any number of copies of the body can be "unrolled" without changing meaning. Loop unrolling is a common transformation in real compilers.
Theorem loop_unrolling : b c,
  cequiv
    (While b Do c EndWhile)
    (If b Then (c ;; While b Do c EndWhile) Else Skip EndIf).
Proof.
  intros.
  unfold cequiv, com_dequiv.
  intros.
  simpl.
  pose proof loop_recur b (ceval c).
  unfold com_dequiv in H.
  specialize (H st1 st2).
  unfold if_sem.
  exact H.
Qed.
Usually, we do not distinguish (c1;;c2);;c3 with c1;;(c2;;c3) when we write real programs. We do not have to because they have the same behavior.
Theorem seq_assoc : c1 c2 c3,
  cequiv ((c1;;c2);;c3) (c1;;(c2;;c3)).
Proof.
  intros.
  unfold cequiv, com_dequiv.
  intros st1 st4.
  simpl.
  split; unfold concat; intros.
  + (* -> *)
    destruct H as [st3 [H H34]].
    destruct H as [st2 [H12 H23]].
    st2.
    split.
    - exact H12.
    - st3.
      split.
      * exact H23.
      * exact H34.
  + (* <- *)
    destruct H as [st2 [H12 H]].
    destruct H as [st3 [H23 H34]].
    st3.
    split.
    - st2.
      split.
      * exact H12.
      * exact H23.
    - exact H34.
Qed.
The following theorem says, if two expressions are equivalent, then assigning their value into the same variable has the same behavior.
Theorem CAss_congruence : (X: var) (E E': aexp),
  aexp_equiv E E'
  cequiv (CAss X E) (CAss X E').
Proof.
  intros.
  unfold cequiv, com_dequiv.
  intros st1 st2.
  simpl.
  split; intros.
  - (* -> *)
    destruct H0.
    split.
    + (* For X *)
      unfold aexp_equiv, aexp_dequiv in H.
      specialize (H st1).
      rewrite <- H.
      exact H0.
    + (* For other program variables *)
      exact H1.
  - (* <- *)
    destruct H0.
    split.
    + (* For X *)
      unfold aexp_equiv, aexp_dequiv in H.
      specialize (H st1).
      rewrite H.
      exact H0.
    + (* For other program variables *)
      exact H1.
Qed.
We will verify that the equivalence on coms really are equivalences — i.e., that they are reflexive, symmetric, and transitive. The proofs are all easy.
Lemma refl_com_dequiv : (d : statestateProp),
  com_dequiv d d.
Proof.
  unfold com_dequiv.
  intros.
  tauto.
Qed.

Lemma refl_cequiv : (c : com), cequiv c c.
Proof.
  unfold cequiv.
  intros.
  apply refl_com_dequiv.
Qed.

Lemma sym_com_dequiv : (d1 d2: statestateProp),
  com_dequiv d1 d2com_dequiv d2 d1.
Proof.
  unfold com_dequiv.
  intros.
  specialize (H st1 st2).
  tauto.
Qed.

Lemma sym_cequiv : (c1 c2 : com),
  cequiv c1 c2cequiv c2 c1.
Proof.
  unfold cequiv.
  intros.
  apply sym_com_dequiv.
  exact H.
Qed.

Lemma trans_com_dequiv : (d1 d2 d3 : statestateProp),
  com_dequiv d1 d2com_dequiv d2 d3com_dequiv d1 d3.
Proof.
  unfold com_dequiv.
  intros.
  specialize (H st1 st2).
  specialize (H0 st1 st2).
  tauto.
Qed.

Lemma trans_cequiv : (c1 c2 c3 : com),
  cequiv c1 c2cequiv c2 c3cequiv c1 c3.
Proof.
  unfold cequiv.
  intros.
  pose proof trans_com_dequiv _ _ _ H H0.
  exact H1.
Qed.
Behavioral equivalence is also a congruence. That is, the equivalence of two subprograms implies the equivalence of the larger programs in which they are embedded.
The main idea is that the congruence property allows us to replace a small part of a large program with an equivalent small part and know that the whole large programs are equivalent without doing an explicit proof about the non-varying parts — i.e., the "proof burden" of a small change to a large program is proportional to the size of the change, not the program.
We will prove the congruence property for loops in class. Other congruence properties' proofs will be left as homework.
Theorem: Equivalence is a congruence for WHILE — that is, if c is equivalent to c', then While b Do c EndWhile is equivalent to While b Do c' EndWhile.
Theorem CWhile_congruence : b c c',
  cequiv c c'
  cequiv (While b Do c EndWhile) (While b Do c' EndWhile).
Proof.
  unfold cequiv, com_dequiv.
  intros.
  simpl.
  unfold loop_sem.
  unfold omega_union.
Hmmm, it seems that we need an auxilliary lemma for iter_loop_body's congruence.
Abort.

Lemma iter_loop_body_congruence: b loop_body loop_body' n,
  com_dequiv loop_body loop_body'
  com_dequiv (iter_loop_body b loop_body n) (iter_loop_body b loop_body' n).
Proof.
  intros.
Here, we need to do induction over n.
  induction n.
  + simpl.
    apply refl_com_dequiv.
  + simpl.
Hmm, it would be better if we have auxilliary lemmas for relation operators.
Abort.
Here, we first prove that concat has congruence property.
Lemma concat_congruence: (d1 d2 d1' d2': statestateProp),
  com_dequiv d1 d1'
  com_dequiv d2 d2'
  com_dequiv (concat d1 d2) (concat d1' d2').
Proof.
  unfold com_dequiv.
  intros.
  unfold concat.
  split; intros H1; destruct H1 as [st [? ?]].
  + st.
    split.
    - specialize (H st1 st).
      tauto.
    - specialize (H0 st st2).
      tauto.
  + st.
    split.
    - specialize (H st1 st).
      tauto.
    - specialize (H0 st st2).
      tauto.
Qed.
Also, intersection has congruence property.
Lemma intersection_congruence: (d1 d2 d1' d2': statestateProp),
  com_dequiv d1 d1'
  com_dequiv d2 d2'
  com_dequiv (intersection d1 d2) (intersection d1' d2').
Proof.
  unfold com_dequiv.
  intros.
  unfold intersection.
  specialize (H st1 st2).
  specialize (H0 st1 st2).
  tauto.
Qed.
Third, union has congruence property. Its proof is similar.
Lemma union_congruence: (d1 d2 d1' d2': statestateProp),
  com_dequiv d1 d1'
  com_dequiv d2 d2'
  com_dequiv (union d1 d2) (union d1' d2').
Proof.
  unfold com_dequiv.
  intros.
  unfold union.
  specialize (H st1 st2).
  specialize (H0 st1 st2).
  tauto.
Qed.
In addition, we prove the congruence property of omega_union.
Lemma omega_union_congruence: (ds1 ds2: natstatestateProp),
  (n, com_dequiv (ds1 n) (ds2 n)) →
  com_dequiv (omega_union ds1) (omega_union ds2).
Proof.
  unfold com_dequiv.
  intros.
  unfold omega_union.
  split; intros H0; destruct H0 as [n ?]; n.
  + specialize (H n st1 st2).
    tauto.
  + specialize (H n st1 st2).
    tauto.
Qed.
Now, we are ready to prove the congruence property of iter_loop_body.
Lemma iter_loop_body_congruence: b loop_body loop_body' n,
  com_dequiv loop_body loop_body'
  com_dequiv (iter_loop_body b loop_body n) (iter_loop_body b loop_body' n).
Proof.
  intros.
  induction n.
  + simpl.
    apply refl_com_dequiv.
  + simpl.
    apply intersection_congruence.
    - apply concat_congruence.
      * exact H.
      * exact IHn.
    - apply refl_com_dequiv.
Qed.
And eventually, we will prove our main theorem.
Theorem CWhile_congruence : b c c',
  cequiv c c'
  cequiv (While b Do c EndWhile) (While b Do c' EndWhile).
Proof.
  unfold cequiv.
  intros.
  simpl.
  unfold loop_sem.
  apply omega_union_congruence.
  intros.
  apply iter_loop_body_congruence.
  exact H.
Qed.

(* Wed Mar 27 17:33:26 UTC 2019 *)