SE 504
Development of a program to compute prefix sums

Let b be an integer array. Suppose that we want to develop a program that establishes, for all i satisfying 0≤i<#b,

b.i = B.0 + B.1 + ... + B.i

where B is the original value of b. Intuitively, this means that we want to replace the value in each element of b by the sum of all the values (originally) occupying the prefix of b ending with that element.

A formal specification is as follows:

|[var b : array  of int;  { b=B }

  prefix_sums

  {Q: (∀i | 0≤i<#b : b.i = prefix_sum.B.i) }
]|
where prefix_sum.a.m = (+j | 0<=j<=m : a.j).

Clearly, to solve this we should use a loop. As a first step in developing the program, we employ the heuristic of replacing a constant (in the postcondition) by a fresh variable and adding a new conjunct stating that the fresh variable is equal to that constant. (This yields a slightly strengthened form of the postcondition.) Choosing to replace constant #b by (fresh variable) n, we get

Q' : (∀i | 0≤i<n : b.i = prefix_sum.B.i)   ∧   n = #b

We take the modified postcondition to be a candidate for a loop invariant and the negation of the added conjunct (n≠#b) to be the loop guard. The loop body should involve changes to n and b. We get:

|[var b : array of int;  { b=B }
  var n : int;

  n := E;
  {loop invariant I: (∀i | 0≤i<n : b.i = prefix_sum.B.i) }
  do n ≠ #b  →  n, b := F, G;
  od
  {Q': (∀i | 0≤i<n : b.i = prefix_sum.B.i) ∧ n = #b }
  {Q: (∀i | 0≤i<#b : b.i = prefix_sum.B.i) }
]|

The obvious initialization for n is to make it zero, as that truthifies I. Considering this, together with the loop guard, the obvious modification to n inside the loop is to increment it. This suggests that 0≤n≤#b is also an invariant of the loop, which could turn out be important (especially in showing item (iv) on the loop checklist). It also suggests that G be of the form b(n:E). That is, it would seem that each iteration of the loop should modify b.n. Our proposed program is then

|[var b : array of int;  { b=B }
  var n : int;

  n := 0;
  {loop invariant I: (∀i | 0≤i<n : b.i = prefix_sum.B.i)  ∧  0≤n≤#b }
  do n ≠ #b  →  n, b := n+1, b(n:E);
  od
  {Q': (∀i | 0≤i<n : b.i = prefix_sum.B.i) ∧ n = #b }
  {Q: (∀i | 0≤i<#b : b.i = prefix_sum.B.i) }
]|

To calculate E, we attempt to prove item (ii) of the loop checklist. (Note the use of the Irrelevant Array Element Axiom.)

  {I ∧ B} n, b := n+1, b(n:E) {I}  (i.e., [I ∧ B  ⇒ wp.(n,b:=n+1,b(n:E)).I])

 Assume I and B.

    wp.(n,b := n+1,b(n:E)).I

 =    < wp law, defn. of I, text. sub >

    (∀i | 0≤i<n+1 : b(n:E).i = prefix_sum.B.i)

 =    < (8.23) split off term (note importance here of assumption 0≤n) >

    (∀i | 0≤i<n : b(n:E).i = prefix_sum.B.i)  ∧  b(n:E).n = prefix_sum.B.n

 =    < b(n:E).n = E by defn of b(n:E) >

    (∀i | 0≤i<n : b(n:E).i = prefix_sum.B.i)  ∧  E = prefix_sum.B.n

 =    < b(n:E).i = b.i for all i<n, which follows from 
        Irrelevant Array Element Axiom instantiated by
        R := 0≤i<n, b := b, i := i, P := b.i = prefix_sum.B.i,
        k := n, E := E, and * := ∧                                >

    (∀i | 0≤i<n : b.i = prefix_sum.B.i)  ∧  E = prefix_sum.B.n

 =    < 1st conjunct above is 1st conjuct of I, which is assumed; (3.39) >

    E = prefix_sum.B.n

 =    < defn of function prefix_sum >

    E = (+j | 0≤j≤n : B.j)
   
 =    < (8.23) Split Off Term (note importance of assumption n≥0) >

    E = (+j | 0≤j<n : B.j)  +  B.n

 =    < j<n = j≤n-1 (due to j and n being integers) >

    E = (+j | 0≤j≤n-1 : B.j)  +  B.n

 =    < defn of prefix_sum >

    E = prefix_sum.B.(n-1)  +  B.n

 =    < by assumption I, b.(n-1) = prefix_sum.B.(n-1) >

    E = b.(n-1) + B.n

As B is only a specification variable, and not a variable (or constant) that can be referred to in the (executable code within the) program, we are not done. Yet we realize that the loop invariant must say something about B.n, or else there is no way to proceed. Examining the program, we notice that, as any given iteration of the loop begins, the only elements of b that have been modified (during previous iterations) are those in b[0..n). Thus, it is clear that B.n = b.n is an invariant of the loop. Indeed, we can make the stronger statement that B[n..#b) = b[n..#b) is an invariant of the loop. (It turns out that we must make this stronger statement in order to be able to prove (ii) on the loop checklist (i.e., that I is a loop invariant). This is an example of where it is easier to prove a stronger statement than to prove a weaker one.) Using this, we get:

 =    < assume I has been augmented to include
        B[n..#b) = b[n..#b) as a new conjunct      >

    E = b.(n-1) + b.n
Thus, we have calulated an expression for E. The resulting program is
|[var b : array of int;  { b=B }
  var n : int;

  n := 0;
  {loop invariant I: 
     (∀i | 0≤i<n : b.i = prefix_sum.B.i) ∧ 0≤n≤#b ∧ b[n..#b)=B[n..#b)
  }
  do n ≠ #b  →
     n, b := n+1, b(n : b.(n-1) + b.n);
  od
  {Q': (∀i | 0≤i<n : b.i = prefix_sum.B.i) ∧ n = #b }
  {Q: (∀i | 0≤i<#b : b.i = prefix_sum.B.i) }
]|

However, this program is faulty because during the loop's first iteration an access will be made to b[-1], which does not exist. To repair this, we can nest a selection command within the loop body to force different actions to be taken when n=0 vs. (the more usual case) when n>0. Doing so, we get:

|[var b : array of int;  { b=B }
  var n : int;

  n := 0;
  {loop invariant I: 
     (∀i | 0≤i<n : b.i = prefix_sum.B.i) ∧ 0≤n≤#b ∧ b[n..#b)=B[n..#b)
  }
  do n ≠ #b  →
     if n=0 →  n, b := n+1, ?; 
     [] n>0 →  n, b := n+1, b(n : b.(n-1) + b.n);
     fi
  od
  {Q': (∀i | 0≤i<n : b.i = prefix_sum.B.i) ∧ n = #b }
  {Q: (∀i | 0≤i<#b : b.i = prefix_sum.B.i) }
]|

We are left to figure out the correct assignment to b during the loop iteration when n=0 (which is, of course, the first iteration). The intended effect of this assignment is to establish I(n:=1), of course. But the relevant part of I(n:=1) simply says that b.0 = prefix_sum.B.0, which is to say that b.0 = B.0. But to establish b.0 = B.0 is trivial, as the loop invariant says that it already holds! (From an operational point of view, B.0 (the initial value of b.0) must be equal to b.0 because no changes are made to b.0 prior to the first iteration of the loop.) Hence, during that iteration, we need not do an assignment to b at all! We get

|[var b : array of int;  { b=B }
  var n : int;

  n := 0;
  {loop invariant I: 
     (∀i | 0≤i<n : b.i = prefix_sum.B.i) ∧ 0≤n≤#b ∧ b[n..#b)=B[n..#b)
  }
  do n ≠ #b  --->
     if n=0 →  n := n+1; 
     [] n>0 →  n, b := n+1, b(n : b.(n-1) + b.n);
     fi
  od
  {Q': (∀i | 0≤i<n : b.i = prefix_sum.B.i) ∧ n = #b }
  {Q: (∀i | 0≤i<#b : b.i = prefix_sum.B.i) }
]|

The fact that the first loop iteration has no effect except to increment n to 1 suggests that we should initialize n to 1, thereby eliminating the need for an iteration when n=0! By doing that, we can modify the second conjunct of I to say 0<n<#b, making unnecessary the selection command inside the loop.

Unfortunately, we have introduced a problem, namely that the program no longer works when b has length zero (i.e., #b = 0). Why? Because during the first iteration of the loop (which really shouldn't happen!), an index-out-of-range error will occur. We can fix this by changing the loop guard to n≤#b. It is intuitively obvious that the resulting program will work correctly (including in the case that #b = 0), but, in that same case, the loop invariant will be false after the initialization of n.

If we don't mind this bit of logical awkwardness (after all, you might argue, we were simply trying to develop a correct program; we don't really care whether the loop invariant that helped to get us there is no longer right), then we can live with this.

If, on the other hand, you are a stickler for details, you can resolve the issue in at least three ways. One is to strengthen the precondition to say #b>0. This is not much of a sacrifice, given that there is absolutely no need to invoke the program in the case #b=0, as it will have no effect. (This solution would impose a slight burden on clients of the program, as they would now be obligated not to invoke the program to compute the prefix sums of an empty array.)

An alternative resolution would be to tweak the loop invariant (see boldfaced subexpressions) to arrive at the following:

loop invariant I:
  (∀i | 0≤i<(n min #b) : b.i = prefix_sum.B.i) ∧
  0<n≤(#b max 1) ∧ b[n..#b)=B[n..#b)

A third alternative would be to nest the loop (including its initialization code) inside one branch of a selection command, as follows:

   if #b = 0 → skip
   [] #b > 0 → n := 1;
               do n ≠ #b → n, b := n+1, b(n : b(n-1) + b.n)
               od
   fi

From a performance standpoint, this is superior to nesting the selection command inside the loop (as we had in a previous version of the program) because it is better to test the condition #b=0 just once, rather than every time around the loop.

Choosing the option of strengthening the pre-condition, the final program is

|[var b : array of int;  { b=B }
  var n : int;
  {#b > 0}
  n := 1;
  {loop invariant I: 
     (∀i | 0≤i<n : b.i = prefix_sum.B.i) ∧ 0<n≤#b ∧ b[n..#b)=B[n..#b)
  }
  do n ≠ #b  →
     n, b := n+1, b(n : b.(n-1) + b.n);
  od
  {Q: (∀i | 0≤i<#b : b.i = prefix_sum.B.i) }
]|
Of course, the more usual way of writing the assignment to b is

b.n := b.(n-1) + b.n

There is yet another issue to address. In calculating the right-hand side of the assignment to b, we strengthened the loop invariant to include the new conjunct b[n..#b) = B[n..#b). Having done so, we are responsible for showing that execution of the loop body preserves this new conjunct. Now, intuitively it is obvious that if we modify b.n but also increment n, then the truth of b[n..#b) = B[n..#b) is preserved. (Note that the modified array element is now b.(n-1), on which this condition does not depend.) But let's prove it formally, so as to illustrate, once again, the use of the Irrelevant Array Element Axiom. What we want to show is

{I ∧ B} n, b := n+1, b(n : b.(n-1) + b.n) {I}

Letting the three conjuncts of I be I0, I1, and I2, our previous work has demonstrated that

{I ∧ B} n, b := n+1, b(n : b.(n-1) + b.n) {I0 ∧ I1}

Hence, it remains to show

{I ∧ B} n, b := n+1, b(n : b.(n-1) + b.n) {I2}

Note that I2, as written above, is an abbreviation for the expression

(∀i | n≤i<#b : b.i = B.i)

From now on, we use this as our I2.

By the relationship between Hoare Triples and wp, this is equivalent to

[I ∧ B  ⇒  wp.(n, b := n+1, b(n : b.(n-1) + b.n)).I2]

We assume the antecedent, I ∧ B, and show the consequent:

  Assume I and B.

     wp.(n, b := n+1, b(n : b.(n-1) + b.n)).I2]

  =    < wp assignment law >

     I2(n, b := n+1, b(n : b.(n-1) + b.n)

  =    < defn of I2 >

     (∀i | n≤i<#b : b.i = B.i)(n, b := n+1, b(n : b.(n-1) + b.n)

  =    < textual substitution >

     (∀i | n+1≤i<#(b(n:b.(n-1)+b.n)) : b(n : b.(n-1) + b.n).i = B.i)

  =    < length of array b is same as length of array b(n:b.(n-1)+b.n) >

     (∀i | n+1≤i<#b : b(n : b.(n-1) + b.n).i = B.i)

  =    < Irrelevant Array Element Axiom, with instantiation 
         R := n+1≤i<#b, b := b, i := i, P := b.i = B.i,
         k := n, E := b.(n-1) + b.n, and * := ∧            >

     (∀i | n+1≤i<#b : b.i = B.i)

 <==   < (3.76b) >

     b.n = B.n ∧ (∀i | n+1≤i<#b : b.i = B.i)

  =    < split off term (8.23) (assumptions I and B yield n<#b) >

     (∀i | n≤i<#b : b.i = B.i)

  =    < assumption I2 >

     true