Develop a program to satisfy the following specification.
|[ con N : int; { N > 0 } con F : array [0..N) of int; var h : array [0..N) of int; h := ? {Q: (∀k | 0≤k<N : F.k = (+i | 0≤i≤k : h.i)) | ]| |
In words, the goal is to place values into array h so that, for each k, the sum of the elements in h[0..k] is equal to F.k.
As an example, the arrays F and h shown here are consistent with the postcondition:
+----+----+----+----+----+----+----+----+ F | 3 | 12 | -5 | 6 | 0 | 13 | -2 | 7 | +----+----+----+----+----+----+----+----+ +----+----+----+----+----+----+----+----+ h | 3 | 9 |-17 | 11 | -6 | 13 |-15 | 9 | +----+----+----+----+----+----+----+----+ |
We strengthen the postcondition Q by replacing the constant N by the fresh variable n and adding the conjunct n=N. Then we take the first conjunct as a possible loop invariant and the negation of the second as the guard of the loop. It would appear that, as is often the case, the variable n should be incremented during each loop iteration. In addition, we probably need to modify h on each iteration. This gives rise to
|[ con N : int; { N > 0 } con F : array [0..N) of int; var h : array [0..N) of int; var n : int; n,h := D, E; {invariant I: I1 ∧ I2, where I1: (∀k | 0≤k<n : F.k = (+i | 0≤i≤k : h.i)), I2: 0≤n≤N } {bound: N - n} do n ≠ N ⟶ n,h := n+1,G; od {Q: (∀k | 0≤k<N : F.k = (+i | 0≤i≤k : h.i)) } ]| |
To make initialization easy, we could take D to be 0. That would truthify I (by G&S's Theorem (8.13)) and make initialization of h unnecessary. If we were to take D to be 1, then (applying G&S's Theorem (8.14) would lead us to conclude that) to truthify I we would have to initialize h to h(0:F.0). (That is, we would have to assign to h.0 the value of F.0.)
As for G, let us attempt to derive it by showing (ii) of the loop checklist:
Assume I and B. wp.(n,h := n+1,G).I = < defn of I, wp assignment law, textual sub > (∀k | 0≤k<n+1 : F.k = (+i | 0≤i≤k : G.i)) ∧ 0≤n+1≤N = < 2nd conjunct is implied by conjunction of I2 (0≤n≤N) and loop guard (n≠N); (3.39) > (∀k | 0≤k<n+1 : F.k = (+i | 0≤i≤k : G.i)) = < split off term (8.23) > (∀k | 0≤k<n : F.k = (+i | 0≤i≤k : G.i)) ∧ F.n = (+i | 0≤i≤n : G.i) = < assume that G[0..n) = h[0..n) > (∀k | 0≤k<n : F.k = (+i | 0≤i≤k : h.i)) ∧ F.n = (+i | 0≤i≤n : G.i) = < assumption I, (3.39) > F.n = (+i | 0≤i≤n : G.i) = < (8.23) split off term (the range is nonempty by assumption n≥0) > F.n = (+i | 0≤i<n : G.i) + G.n = < assume (again) that G satisfies G.i = h.i for i satisfying 0≤i<n > F.n = (+i | 0≤i<n : h.i) + G.n |
At this point, we notice that the first term on the right-hand side looks very much like a subexpression of the first conjunct of our loop invariant. It would look even more similar if we rewrote the inequality i<n as i≤n-1, so we do that now:
F.n = (+i | 0≤i<n : h.i) + G.n = < rewrite inequality i<n as i≤n-1 > F.n = (+i | 0≤i≤n-1 : h.i) + G.n |
The first conjunct of the loop invariant (which we have taken as an assumption, you will recall) says that F.k = (+i | 0≤i≤k : h.i) for every k from zero up to and including n-1. In particular, then, by plugging in n-1 for k we get F.(n-1) = (+i | 0≤i≤n-1 : h.i). Note that this makes sense only for n>0, so now we assume that condition. To justify this step technically, we use a slightly more general form of Gries's and Schneider's (9.13) Instantiation theorem, which we call (9.13') and can be found in the Appendix. Continuing the proof:
F.n = (+i | 0≤i≤n-1 : h.i) + G.n = < assumptions I (the loop invariant) and n > 0, and application of (9.13') (see Appendix), instantiated by x: k, e: n-1, R: 0≤k<n, and P: F.k = (+i | 0≤i≤k : h.i) > F.n = F.(n-1) + G.n = < algebra > G.n = F.n - F.(n-1) = < choose G to be h(n : F.n - F.(n-1)), so that G.n = F.n - F.(n-1) and so that earlier assumption that G.i = h.i for i satisfying 0≤i<n also holds > true |
Recall that during the derivation above, we assumed that n>0. Hence, our pre-loop initialization of n should be n:=1 rather than n:=0. Earlier we figured that such an initialization of n requires that h.0 be initialized to F.0. The assignment h := h(n : F.n - F.(n-1)) is usually written h.n := F.n - F.(n-1). The resulting program is
|[ con N : int; { N > 0 } con F : array [0..N) of int; var h : array [0..N) of int; var n : int; n,h.0 := 1,F.0; {invariant I: (∀k | 0≤k<n : F.k = (+i | 0≤i≤k : h.i)) ∧ 1≤n≤N } do n ≠ N ⟶ n, h.n := n+1, F.n - F.(n-1); od {(∀k | 0≤k<N : F.k = (+i | 0≤i≤k : h.i)) } ]| |
Had we recognized from the start that the only modification needing to be made to h on each iteration were to the element h.n, we would have started with our loop body being the assignment
This would have made the calculation of G somewhat easier.
In Gries & Schneider, Theorem 9.13 (Instantiation) says
Informally, this says that, if every choice of x satisfies P, then e (any expression whose type is compatible with x's) satisfies P.
A somewhat more general form of this theorem is as follows:
Theorem 9.13': R(x:=e) ⟹ ((∀x | R : P) ⟹ P(x:=e))
It is readily seen that (9.13) is obtained from (9.13') by taking R to be true (and then applying (3.73)).
Informally, (9.13') says that, if e satisfies R and P is satisfied by every x satisfying R, then e satisfies P.
Here is a proof of (9.13').
Assume R(x:=e). (∀x | R : P) = < (9.2) Trading > (∀x |: R ⇒ P) ⇒ < (9.13) Instantiation > (R ⇒ P)(x:=e) = < textual substitution distributes over operators > R(x:=e) ⇒ P(x:=e) = < assumption R(x:=e) > true ⇒ P(x:=e) = < (3.73) true is left identity of ⇒ > P(x:=e) |