SE 504
Proof of Correctness of a Linear Search Program

Note that this is a variation on what appears on pages 131-134 of Programming in the 1990's, by Edward Cohen.

Given the following specification, devise a program that meets it, and prove the program's correctness.

|[ con N : int; 
   con b : array [0..N) of bool;  
   {P: N>0  ∧  (∃j | 0≤j<N : b.j) }
   var k : int;

   k := ? 

   {Q: b.k  ∧  (∀j | 0≤j<k : ¬b.j) }
]| 

In informal terms, the above specification describes a program that, given a fixed boolean array b[0..N) containing at least one occurrence of true, terminates with variable k equal to the lowest-numbered location containing true.

Let us refer to the two conjuncts of postcondition Q as Q0 and Q1.

It would seem that, in order to find an occurrence of true in a boolean array, it is necessary to search for it using a loop. One of the heuristics for developing a loop is sometimes called deleting a conjunct. It suggests that we take the loop invariant to be the same as the postcondition, except with one (or more) of its conjuncts omitted. The negation of the omitted conjunct(s) is taken to be the guard of the loop. Applying that heuristic here, suppose that we take Q1 to be the loop invariant and ¬Q0 to be the loop guard. (The opposite choice would not have been wise, as it would have required us to initialize k so as to establish b.k, which is almost as difficult as the original problem.)

Keeping in mind that we should initialize the variable k prior to the loop, we get

|[ con N : int;
   con b : array [0..N) of bool;
   {P: N>0  ∧  (∃j | 0≤j<N : b.j) }
   var k : int;
   k := ?;
   {invariant I: (∀j | 0≤j<k : ¬b.j) }
   do ¬b.k  --->  ?
   od
   {postcondition Q: b.k  ∧  (∀j | 0≤j<k : ¬b.j) }
]| 

The loop guard is ¬b.k. As the index range of b is [0..N), evaluation of the guard in a state in which k<0 or k≥N results in abortion (due to an "array subscript out-of-bounds error"). Hence, we should include 0≤k<N (equivalently, 0≤k ∧ k<N) as another conjunct in the loop invariant. That is, we augment I to become I: (∀j | 0≤j<k : ¬b.j) ∧ 0≤k ∧ k<N.

To what value should we initialize k? As the loop immediately follows, the initialization of k must result in a state satisfying the loop invariant. Examining the invariant, we notice that, if k has value zero, the universal quantification in I has an empty range, which makes it true. (To prove this, use (9.2), (3.75), and (9.8) from Gries & Schneider.) Hence, we choose k := 0 as our initialization.

Indeed, every other choice is wrong, because the only way that we can be sure, without examining any array elements first, that there are no occurrences of true in b[0..k) (as required by I) is to make [0..k) an empty range. And the only value for k that satisfies 0≤k<N (also required by I) and that makes [0..k) an empty range is zero.

Now consider the body of the loop. The only variable in the program is k; hence, the loop body should modify k in some way. Because the loop invariant says that every value in b[0..k) is false and the loop guard (which we also assume to be true, because the loop body executes only in that case) says that b.k is also false, we conclude that every element in b[0..k+1) is false. Hence incrementing k preserves the truth of the loop invariant!

Increasing k by more than one would be wrong, however, because it could have the effect of falsifying the loop invariant (by "skipping past" the first occurrence of true in the array).

What about a bound function? As k increases during every loop iteration (in accord with what we said above), one possible bound function is t: C-k, where C is some constant that k cannot possibly exceed. (Such a choice for the bound function guarantees that its value will decrease on every iteration, in compliance with checkpoint (v), and that its value will never fall below the lower bound of zero, in compliance with (iv).) As k's value should never go as high as N (because the precondition tells us that we should find a true element somewhere within b[0..N)), we choose bound function t: N-k.

What we have, then, is

|[ con N : int; 
   con b : array [0..N) of bool;  
   {P: N>0  ∧  (∃j | 0≤j<N : b.j) }
   var k : int;
   k := 0;
   {invariant I: (∀j | 0≤j<k : ¬b.j)  ∧  0≤k  ∧  k<N }
   {bound t: N-k }
   do ¬b.k  --->  k := k+1
   od
   {postcondition Q: b.k  ∧  (∀j | 0≤j<k : ¬b.j) }
]| 

Let us now prove the five items on the loop checklist. Note that any assertion made in the program about one of its constants can be taken to be a "global invariant" and can be assumed in proving any of the five items. (For example, we can make use of the fact that N>0.) For convenience, we refer to the three conjuncts of I as I0, I1, and I2.

(i) {P} Sinit {I} (equivalently, [P ==> wp.Sinit.I])

Assume P (i.e., N>0 ∧ (∃j | 0≤j<N : b.j)).

    wp.Sinit.I

 =    < defn of Sinit >

    wp.(k := 0).I

 =    < wp assignment law >

    I(k:=0)

 =    < defn of I, textual sub. >

    (∀j | 0≤j<0 : ¬b.j)  ∧  0≤0  ∧  0<N 

 =    < 0≤0 is trivial (reflexivity of ≤), 0<N is assumed, (3.39) >

    (∀j | 0≤j<0 : ¬b.j) 

 =    < 0≤j<0  =  false >

    (∀j | false : ¬b.j) 

 =    < (9.2); (3.75); (9.8) >

    true

(ii) {I ∧ ¬b.k} k := k+1 { I } (equivalently, [I ∧ ¬b.k ==> wp.(k:=k+1).I])

We (attempt to) prove wp.(k:=k+1).I using I and ¬b.k as assumptions.

    wp.(k:=k+1).I

 =    < wp assignment law >

    I(k:=k+1)

 =    < defn of I, textual sub. >

    (∀j | 0≤j<k+1 : ¬b.j)  ∧  0≤k+1  ∧  k+1<N 

 =    < split off term (8.23) >

    (∀j | 0≤j<k : ¬b.j) ∧ ¬b.k  ∧  0≤k+1  ∧  k+1<N 

 =    < 1st conjunct above is assumption I0; second is assumed, too; (3.39) >

    0≤k+1  ∧  k+1<N 

 =    < 0≤k+1 follows from assumption I1; (3.39) >

    k+1<N 

 =    < algebra >

    k<N-1

At this point, we get stuck! It remains to show that k<N-1 is true, assuming I ∧ ¬b.k. In other words, it remains to show [I ∧ ¬b.k ==> k<N-1]. We have

    I ∧ ¬b.k  ==>  k<N-1

 =    < contrapositive (3.61) >

    ¬(k<N-1)  ==>  ¬(I ∧ ¬b.k)

 =    < ¬(a<b) = a≥b; DeMorgan (3.47a), (3.12) >

    k ≥ N-1  ==>  ¬I ∨ b.k

 =    < defn of ≥ : a≥b  =  a>b ∨ a=b >

    (k>N-1 ∨ k=N-1)  ==>  ¬I ∨ b.k

 =    < (3.78) >

    (k>N-1 ==> ¬I ∨ b.k)  ∧  (k=N-1 ==> ¬I ∨ b.k)

Thus, it suffices to prove each of the two conjuncts of the above. We prove the first by assuming its antecedant (k>N-1) and showing its consequent:

    ¬I ∨ b.k

 =    < defn of I >

    ¬((∀j | 0≤j<k : ¬b.j)  ∧  0≤k  ∧  k<N)  ∨  b.k

 =    < DeMorgan (3.47a)  >

    ¬(∀j | 0≤j<k : ¬b.j)  ∨  ¬(0≤k)  ∨  ¬(k<N)  ∨  b.k

 =    < generalized DeMorgan (9.17); (3.12); ¬(a<b) = a≥b >

    (∃j | 0≤j<k : b.j)  ∨  ¬(0≤k)  ∨  k≥N  ∨  b.k

 =    < assumption k>N-1 is equivalant to 3rd disjunct above >

    (∃j | 0≤j<k : b.j)  ∨  ¬(0≤k)  ∨  true  ∨  b.k

 =    < (3.29) >

    true

Now we prove the 2nd conjunct (again, by assuming the antecedant (k=N-1) and showing the consequent):

    ¬I ∨ b.k

 =    < repeating the first three steps from proof of 1st conjunct >

    (∃j | 0≤j<k : b.j)  ∨  ¬(0≤k)  ∨  k≥N  ∨  b.k

 =    < split off term (8.23) >

    (∃j | 0≤j<k+1 : b.j)  ∨  ¬(0≤k)  ∨  k≥N 

 =    < assumption k=N-1 and (3.84a) >

    (∃j | 0≤j<N-1+1 : b.j)  ∨  ¬(0≤k)  ∨  k≥N 

 =    < algebra >

    (∃j | 0≤j<N : b.j)  ∨  ¬(0≤k)  ∨  k≥N 

 =    < 1st disjunct above is global invariant >

    true  ∨  ...

 =    < (3.29) >

    true

This completes the proof of item (ii) in the loop checklist!!

(iii) [I ∧ ¬B ==> Q]

We need not even go through the proof, because the invariant and loop guard were chosen to satisfy this! The loop invariant I is I0 ∧ I1 ∧ I2, the loop guard is ¬B, and the postcondition is Q: I0 ∧ B. Hence, we have

    I ∧ ¬B

 =     < I is I0 ∧ I1 ∧ P2 >

    I0 ∧ I1 ∧ I2 ∧ ¬B

==>    < (3.76b) > 

    I0 ∧ ¬B

 =     < Q is I0 ∧ ¬B >

   Q 

Postponing checkpoint (iv), we now do (v):

(v) {I ∧ ¬¬b.k ∧ N-k=C} k:=k+1 { N-k<C }

    wp.(k:=k+1).(N-k < C)

 =    < wp assignment law >

    (N-k < C)(k:=k+1)

 =    < textual sub. >

    N - (k+1) < C

 =    < assumption N-k=C >

    N - (k+1) < N-k

 =    < algebra >

    0 < 1

 =    < number theory !! >

    true

Finally, let us prove loop checkpoint (iv):

(iv) [I ∧ ¬b.k ==> N-k > 0].

    N-k > 0

 =    < algebra >

    N > k

 =    < assumption I2: k<N >

    true

Just for fun, let's show that, even if neither I1 nor I2 were included as conjuncts of I, we could still carry out a proof of (iv). That is, we will show [I0 ∧ ¬b.k ==> N-k > 0]. To do this, we show its contrapositive, [N <= k ==> ¬I0 ∨ b.k] by assuming the antecedant and proving the consequent.

     ¬I0 ∨ b.k

  =    < defn of I0 >

     ¬(∀j | 0≤j<k : ¬b.j) ∨ b.k

  =    < generalized De Morgan (9.17) >

     (∃j | 0≤j<k : b.j) ∨ b.k

  =    < Split off term (8.23) >

     (∃j | 0≤j<k+1 : b.j)

  =    < from assumption N≤k it follows
         that (0≤j<k+1)  =  (0≤j<N ∨ N≤j<k+1) >

     (∃j | 0≤j<N ∨ N≤j<k+1 : b.j) 

  =    < Range Split (8.16) >

     (∃j | 0≤j<N : b.j) ∨ (∃j | N≤j<k+1 : b.j) 

  =    < 1st conjunct above is assumed "global invariant" >

     true  v  (∃j | N≤j<k+1 : b.j)

  =    < true is zero of disjunction (3.29) >

     true