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 (rigid/ghost variable) B refers to the original contents 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. For example:

Before:
    0   1   2   3   4   5   6   7
  +---+---+---+---+---+---+---+---+
B | 3 | 9 |-4 | 6 |-1 | 9 | 0 |-5 |
  +---+---+---+---+---+---+---+---+
After:
    0   1   2   3   4   5   6   7
  +---+---+---+---+---+---+---+---+
b | 3 |12 | 8 |14 |13 |22 |22 |17 |
  +---+---+---+---+---+---+---+---+

A formal specification is as follows:

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

  b := ?

  {Q: (∀i | 0≤i<#b : b.i = prefixSum.i) }
]|
where prefixSum.m = (+j | 0≤j≤m : B.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 Q) by a fresh variable and adding a new conjunct stating that the fresh variable is equal to that constant. (This yields Q', 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 = prefixSum.i)   ∧   n = #b

We take the modified postcondition (not including the new conjunct) to be a candidate for a loop invariant and the negation of the new 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 = prefixSum.i) }
  do n ≠ #b  --->  n, b := F, G;
  od
  {Q': (∀i | 0≤i<n : b.i = prefixSum.i) ∧ n = #b }
  {Q: (∀i | 0≤i<#b : b.i = prefixSum.i) }
]|

The obvious initialization for n is to make it zero, as that truthifies I (by G&S's (8.13)). 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 to 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 likely 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: I1 ∧ I2, where
     I1: (∀i | 0≤i<n : b.i = prefixSum.i),
     I2: 0≤n≤#b
  }
  {bound: #b - n}
  do n ≠ #b  --->  n, b := n+1, b(n:E);
  od
  {Q': (∀i | 0≤i<n : b.i = prefixSum.i) ∧ n = #b }
  {Q: (∀i | 0≤i<#b : b.i = prefixSum.i) }
]|

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

To be proved:
{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 (I1 and I2) 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 = prefixSum.i)

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

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

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

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

 =    < b(n:E).i = b.i for all i<n; hence, it follows from the
        Irrelevant Array Element Axiom (instantiated by
        R := 0≤i<n, b := b, i := i, P := b.i = prefixSum.i,
        k := n, E := E, and * := ∧) that "b(n:E)" in the body 
        of the quantification can be replaced by "b"            >

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

 =    < 1st conjunct above is assumption I1; (3.39) >

    E = prefixSum.n

 =    < defn of function prefixSum >

    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 prefixSum >

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

 =    < by assumption I1, b.(n-1) = prefixSum.(n-1) >

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

As B is only a rigid variable (or what some authors call a ghost variable or specification constant), 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) (which, of course, is an abbreviation for (∀j | n≤j<#b : B.j = b.j)) 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 continue as follows:

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

 =    < 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 calculated an expression for E. The resulting program is

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

  n := 0;
  {loop invariant I: I1 ∧ I2 ∧ I3, where
     I1: (∀i | 0≤i<n : b.i = prefixSum.i),
     I2: 0≤n≤#b,
     I3: b[n..#b) = B[n..#b)
  }
  {bound: #b - n}
  do n ≠ #b ⟶ n, b := n+1, b(n : b.(n-1) + b.n);
  od
  {Q': (∀i | 0≤i<n : b.i = prefixSum.i) ∧ n = #b }
  {Q: (∀i | 0≤i<#b : b.i = prefixSum.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: I1 ∧ I2 ∧ I3, where
     I1: (∀i | 0≤i<n : b.i = prefixSum.i),
     I2: 0≤n≤#b,
     I3: b[n..#b) = B[n..#b)
  }
  {bound: #b - n}
  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 = prefixSum.i) ∧ n = #b }
  {Q: (∀i | 0≤i<#b : b.i = prefixSum.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 = prefixSum.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: I1 ∧ I2 ∧ I3, where
     I1: (∀i | 0≤i<n : b.i = prefixSum.i),
     I2: 0≤n≤#b,
     I3: b[n..#b) = B[n..#b)
  }
  {bound: #b - n}
  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 = prefixSum.i) ∧ n = #b }
  {Q: (∀i | 0≤i<#b : b.i = prefixSum.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 new 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 correct), 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 = prefixSum.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 ∧ #b > 0 }
  var n : int;
  n := 1;
  {loop invariant I: I1 ∧ I2 ∧ I3, where
     I1: (∀i | 0≤i<n : b.i = prefixSum.i),
     I2: 0<n≤#b,
     I3: b[n..#b) = B[n..#b)
  }
  {bound: #b - n}
  do n ≠ #b ⟶ n, b := n+1, b(n : b.(n-1) + b.n);
  od
  {Q: (∀i | 0≤i<#b : b.i = prefixSum.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}

Our previous work has demonstrated that

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

Hence, it remains to show

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

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)).I3]

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

Assume I (i.e., I1, I2, and I3) and B.

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

  =    < wp assignment law >

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

  =    < defn of I3 >

     (∀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:E) (no matter what E is) >

     (∀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 I2 and B yield n<#b) >

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

  =    < assumption I3 >

     true