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