PermBasic techniques for permutations and ordering

Several of the algorithms and data structures we consider in later chapters will use a common set of techniques: ordering (less-than) comparisons on numbers, and permutations (rearrangements of lists) for reasoning about the contents of data structures. In this chapter, we introduce some techniques for reasoning about comparisons and permutations.

Require Export SfLib. (* not essential *)
Require Export Coq.Bool.Bool.
Require Export Coq.Arith.Arith.
Require Export Coq.Arith.EqNat.
Require Export Coq.omega.Omega.
Require Export Coq.Lists.List.
Export ListNotations.
Require Export Permutation.
Export NPeano.

Less-than: a total order on the natural numbers


Check lt. (* : nat -> nat -> Prop *)
Check ltb. (* : nat -> nat -> bool *)
Locate "_ < _". (* "x < y" := lt x y *)
Locate "<?". (* x <? y  := ltb x y *)

We write x<y for the Proposition that x is less than y, and we write x <? y for the computable test that returns true or false depending on whether x<y or not. The theorem that lt is related in this way to ltb is this one:

Check Nat.ltb_lt.
(* : forall n m : nat, (n <? m) = true <-> n < m *)

For some reason, the Coq library has <? , <=? and =? notations, but is missing these three:

Notation "a >=? b" := (leb b a)
                       (at level 70, only parsing)
                       : nat_scope.
Notation "a >? b" := (ltb b a)
                       (at level 70, only parsing)
                       : nat_scope.
Notation " a =? b" := (beq_nat a b)
                       (at level 70) : nat_scope.

Wonder if we should mention the <? , <=? and =? notations in SF1 (Basics.v).
Now, we write some helper lemmas and a tactic to relate the propositional and the boolean comparisons. Later in this file, we explain how to use these. It is not important for you to understand the details of how they work.

Lemma beq_reflect : ∀ x y, reflect (x = y) (x =? y).
Proof.
  intros x y.
  apply iff_reflect. symmetry. apply beq_nat_true_iff.
Qed.

Lemma blt_reflect : ∀ x y, reflect (x < y) (x <? y).
Proof.
  intros x y.
  apply iff_reflect. symmetry. apply Nat.ltb_lt.
Qed.

Lemma ble_reflect : ∀ x y, reflect (xy) (x <=? y).
Proof.
  intros x y.
  apply iff_reflect. symmetry. apply Nat.leb_le.
Qed.

Ltac bdestruct X :=
  let H := fresh in
  match X with
  | ?A <? ?Bdestruct (blt_reflect A B) as [H|H];
                [ | apply not_lt in H]
  | ?A <=? ?Bdestruct (ble_reflect A B) as [H|H];
                 [ | apply not_le in H]
  | ?A =? ?Bdestruct (beq_reflect A B) as [H|H]
  end.

Ltac inv H := inversion H; clear H; subst. (* A useful tactic *)

Reasoning about linear integer inequalities. In our proofs about searching and sorting algorithms, we sometimes have to reason about the consequences of less-than and greater-than. Here's a contrived example.

Module Exploration1.

Theorem omega_example1:
 ∀ i j k,
    i < j
    ¬ (k - 3 ≤ j) →
   k > i.
Proof.
  intros.

Now, there's a hard way to prove this, and an easy way. Here's the hard way.

  (* try to remember the name ... *)
  SearchAbout___).
  apply not_le in H0.
  (* try to remember the name ... *)
  SearchAbout (_ > __ > __ > _).
  apply gt_trans with j.
  apply gt_trans with (k-3).
  (* Oops, this is not actually true, because we are talking about
     natural numbers with "bogus subtraction." *)

Abort.

Theorem bogus_subtraction: ¬ (∀ k:nat, k > k - 3).
Proof.
  (* intro introduces exactly one thing, like intros ? *)
  intro.
  (* specialize applies a hypothesis to an argument *)
  specialize (H O).
  simpl in H. inversion H.
Qed.

Here's the hard way, second try.

Theorem omega_example1:
 ∀ i j k,
    i < j
    ¬ (k - 3 ≤ j) →
   k > i.
Proof. (* try again! *)
  intros.
  apply not_le in H0.
  unfold gt in H0.
  unfold gt.
  (* try to remember the name ... *)
  SearchAbout (_ < ____ < _).
  apply lt_le_trans with j.
  apply H.
  apply le_trans with (k-3).
  SearchAbout (_ < ___).
  apply lt_le_weak.
  auto.
  apply le_minus.
Qed. (* Oof!  That was exhausting and tedious.  *)

And here's the easy way.

Theorem omega_example2:
 ∀ i j k,
    i < j
    ¬ (k - 3 ≤ j) →
   k > i.
Proof.
  intros.
  omega.
Qed.

Here we have used the omega tactic, made available by importing Coq.omega.Omega as we have done above. Omega is an algorithm for integer linear programming, invented in 1991 by William Pugh. Because ILP is NP-complete, we might expect that this algorithm is exponential-time in the worst case, and indeed that's true: if you have N equations, it could take 2^N time. But in the typical case that results from reasoning about programs, omega is much faster than that. Coq's omega tactic is an implementation of this algorithm that generates a machine-checkable Coq proof. It "understands" the types Z and nat, and these operators: < = > <= >= + - ~, as well as multiplication by small integer literals (such as 0,1,2,3...) and some uses of λ/ and /\.
Omega does not understand other operators. It treats things like a*b and f x y as if they were variables. That is, it can prove f x y > a*b f x y + 3 a*b, in the same way it would prove u > v u+3 v.
Now let's consider a silly little program: swap the first two elements of a list, if they are out of order.

Definition maybe_swap (al: list nat) : list nat :=
  match al with
  | a :: b :: arif a >? b then b::a::ar else a::b::ar
  | _al
  end.

Example maybe_swap_123:
  maybe_swap [1; 2; 3] = [1; 2; 3].
Proof. reflexivity. Qed.

Example maybe_swap_321:
  maybe_swap [3; 2; 1] = [2; 3; 1].
Proof. reflexivity. Qed.

In this program, we write a>?b instead of a>b. Why is that?

Check (1>2). (* : Prop *)
Check (1>?2). (* : bool *)

We cannot compute with elements of Prop: we need some kind of constructible (and pattern-matchable) value. For that we use bool.

Locate ">?". (* a >? b :=  ltb b a *)

The name ltb stands for "less-than boolean."

Print ltb.
(* =  fun n m : nat => S n <=? m : nat -> nat -> bool  *)
Locate ">=?".

Instead of defining an operator geb, the standard library just defines the notation for greater-or-equal-boolean as a less-or-equal-boolean with the arguments swapped.

Locate leb.
Print leb.

Here's a theorem: maybe_swap is idempotent — that is, applying it twice gives the same result as applying it once.

Theorem maybe_swap_idempotent:
  ∀ al, maybe_swap (maybe_swap al) = maybe_swap al.
Proof.
  intros.
  destruct al as [ | a al].
  simpl.
  reflexivity.
  destruct al as [ | b al].
  simpl.
  reflexivity.
  simpl.

What do we do here? We must proceed by case analysis on whether a>b.

  destruct (b <? a) eqn:H.
  simpl.
  destruct (a <? b) eqn:H0.

Now what? They can't both be true. In fact, omega "knows" how to prove that kind of thing. Let's try it:

  try omega.

omega didn't work, because it operates on comparisons in Prop, such as a>b; not upon comparisons yielding bool, such as a>?b. We need to convert these comparisons to Prop, so that we can use omega.
Actually, we don't "need" to. Instead, we could reason directly about these operations in bool. But that would be even more tedious than the omega_example1 proof. Therefore: let's set up some machinery so that we can use omega on boolean tests.

Abort.

Let's try again, a new way:

Theorem maybe_swap_idempotent:
  ∀ al, maybe_swap (maybe_swap al) = maybe_swap al.
Proof.
  intros.
  destruct al as [ | a al].
  simpl.
  reflexivity.
  destruct al as [ | b al].
  simpl.
  reflexivity.
  simpl.

This is where we left off before. Now, watch:

  destruct (blt_reflect b a). (* THIS LINE *)
  (* Notice that b<a is above the line as a Prop, not a bool.
     Now, comment out THIS LINE, and uncomment THAT LINE.  *)

  (* bdestruct (b <? a).    (* THAT LINE *) *)
  (* THAT LINE, with bdestruct, does the same thing as THIS LINE. *)
  simpl.
  bdestruct (a <? b).
  omega.

The omega tactic noticed that above the line we have an arithmetic contradiction. Perhaps it seems wasteful to bring out the "big gun" to shoot this flea, but really, it's easier than remembering the names of all those lemmas about arithmetic!

  reflexivity.
  simpl.
  bdestruct (b <? a).
  omega.
  reflexivity.
Qed.

Moral of this story: When proving things about a program that uses boolean comparisons (a <? b), use bdestruct. Then use omega.

Permutations

Another useful fact about maybe_swap is that it doesn't add or remove elements from the list, it only reorders them. We can say that the output list is a permutation of the input. The Coq Permutation library has an inductive definition of permutations, along with some lemmas about them.

Locate Permutation. (* Inductive Coq.Sorting.Permutation.Permutation *)

Print Permutation.
(*
 Inductive Permutation {A : Type} 
                     : list A -> list A -> Prop :=
    perm_nil : Permutation  
  | perm_skip : forall (x : A) (l l' : list A),
                Permutation l l' -> 
                Permutation (x :: l) (x :: l')
  | perm_swap : forall (x y : A) (l : list A),
                Permutation (y :: x :: l) (x :: y :: l)
  | perm_trans : forall l l' l'' : list A,
                 Permutation l l' -> 
                 Permutation l' l'' -> 
                 Permutation l l''.
*)


You might wonder, "is that really the right definition?" And indeed, it's important that we get a right definition, because Permutation is going to be used in the specification of correctness of our searching and sorting algorithms. If we have the wrong specification, then all our proofs of "correctness" will be useless.
It's not obvious that this is indeed the right specification of permutations. (It happens to be true, but it's not obvious!) In order to gain confidence that we have the right specification, we should use this specification to prove some properties that we think permutations ought to have.

Exercise: 2 stars (Permutation_properties)

Think of some properties of the Permutation relation and write them down informally in English, or a mix of Coq and English. Here are four to get you started:
  • 1. If Permutation al bl, then length al = length bl.
  • 2. If Permutation al bl, then Permutation bl al.
  • 3. [1;1] is NOT a permutation of [1;2].
  • 4. [1;2;3;4] IS a permutation of [3;4;2;1].
Add three more properties.
Now, let's examine all the theorems in the Coq library about permutations:

SearchAbout Permutation. (* Browse through the results of this query! *)

Which of the properties that we wrote down above have already been proved as theorems by the Coq library developers? Answer here:

Exercise: 3 stars (permut_example)

Use the permutation rules in the library (see the SearchAbout, above) to prove the following theorem. Hints given as Check commands.

Check perm_trans.
Check perm_skip.
Check Permutation_refl.
Check Permutation_app_comm.
Check app_ass. (* associativity of list-append *)

Example permut_example: ∀ (a b: list nat),
  Permutation (5::6::a++b) ((5::b)++(6::a++[])).
Proof.
(* FILL IN HERE *) Admitted.

Exercise: 1 star (not_a_permutation)

Prove that [1;1] is not a permutation of [1;2]. Hints are given as Check commands.

Check Permutation_cons_inv.
Check Permutation_length_1_inv.

Example not_a_permutation:
  ¬ Permutation [1;1] [1;2].
(* FILL IN HERE *) Admitted.
Back to maybe_swap. We prove that it doesn't lose or gain any elements, only reorders them.

Theorem maybe_swap_perm: ∀ al,
  Permutation al (maybe_swap al).
Proof.
  (* WORKED IN CLASS *)
  intros.
  destruct al as [ | a al].
  simpl. apply Permutation_refl.
  destruct al as [ | b al].
  simpl. apply Permutation_refl.
  simpl.
  bdestruct (a>?b).
  apply perm_swap.
  apply Permutation_refl.
Qed.

Now let us specify functional correctness of maybe_swap: it rearranges the elements in such a way that the first is less-or-equal than the second.

Definition first_le_second (al: list nat) : Prop :=
  match al with
  | a::b::_ ⇒ ab
  | _True
  end.

Theorem maybe_swap_correct: ∀ al,
    Permutation al (maybe_swap al)
    ∧ first_le_second (maybe_swap al).
Proof.
  intros.
  split.
  apply maybe_swap_perm.
  (* WORKED IN CLASS *)
  destruct al as [ | a al].
  simpl. auto.
  destruct al as [ | b al].
  simpl. auto.
  simpl.
  bdestruct (b <? a).
  simpl.
  omega.
  simpl.
  omega.
Qed.

End Exploration1.

Exercise: 2 stars (Forall_perm)

To close, a useful utility lemma:

Theorem Forall_perm: ∀ {A} (f: AProp) al bl,
  Permutation al bl
  Forall f alForall f bl.
Proof.
(* FILL IN HERE *) Admitted.