Let A be an integer array. Define, for p and q satisfying 0≤p≤q≤#A,
That is, S.p.q is the sum of the elements in the array segment A[p..q).
Among the elementary observations we can make regarding S are these:
(1) S.k.k = 0 for all k (0≤k≤#A) because A[k..k) is an empty segment,
(2) S.k.(k+1) = A.k for all k (0≤k<#A) because A[k,k+1) is a one-element
segment containing only A.k
(3) S.k.n = S.k.m + S.m.n for all k,m,n satisfying 0≤k≤m≤n≤#A,
because
As a special case of (3), take m=n-1:
(3') S.k.n = S.k.(n-1) + S.(n-1).n
which, by (2), gives us
(3'') S.k.n = S.k.(n-1) + A.(n-1)
In the Maximum Segment Sum problem, A is given as input and the output to be produced is the maximum among all the S.p.q's. That is, the program's specification is
|[con A : array of int;
var z : int;
{ true }
max_seg_sum
{Q : z = (MAX p,q | 0≤p≤q≤#A : S.p.q) }
]|
As an example, consider the integer array
0 1 2 3 4 5 6 7 8 9 10 11 12
+---+---+---+---+---+---+---+---+---+---+---+---+---+
A | 2 |-1 |-2 | 3 | 2 |-2 | 3 |-1 | 1 |-6 | 4 |-1 | 3 |
+---+---+---+---+---+---+---+---+---+---+---+---+---+
The maximum among all segment sums is 6, which is achieved by each of the following segments: A[3..6], A[3..8], A[3..12], and A[10..12].
Let us attempt to derive a solution by employing the techniques from the course. The first observation we can make is that we are unlikely to find a solution that doesn't involve a loop (or recursion, or some device for iteration). Among our heuristics for developing loops, the one that seems to be applicable is "replace a constant (in the postcondition) by a variable". The two constants appearing in the postcondition are 0 and #A. Suppose that we replace #A by a "fresh" variable r in order to obtain as a candidate for a loop invariant the following:
As a loop guard, we would choose B : r ≠ #A, because then we obviously have [I ∧ ¬B ==> Q] (as required by proof obligation (iii) with respect to loops).
Indeed, one could rewrite the postcondition as
which strengthens the original postcondition insofar as it requires the fresh variable r to satisfy r = #A. Employing the "delete a conjunct" heuristic, we get I as the suggested loop invariant and ¬(r = #A) (i.e., r ≠ #A) as the suggested loop guard.
Our program skeleton is now
|[con A : array of int;
var z : int;
var r : int;
{ true }
r, z := ?, ?;
{ loop invariant I : z = (MAX p,q | 0≤p≤q≤r : S.p.q) }
do r ≠ #A --->
r, z := ?, ?
od
{Q : z = (MAX p,q | 0≤p≤q≤#A : S.p.q) }
]|
In order to satisfy proof obligation (i), r and z must be initialized so as to truthify I. The easiest way to do this is to set r to 0, thereby making the range in the quantifier correspond to p=q=0. Applying the one-point rule (and using our above observation that S.k.k = 0 for all k), we obtain that z should be set to zero, too.
Now consider the loop body. Given that we have decided to initialize r to zero and that the loop guard indicates termination when r becomes #A, the most obvious way to modify r inside the loop is by incrementing it. Let's assume, for the moment at least, that this is what we shall do.
As the expression needed on the right-hand side of the assignment command for incrementing r does not involve any other variables, we make it the last command within the loop body and place the command(s) that update z before it. (This allows us to focus upon z exclusively without requiring that r be updated simultaneously.) The refined (and annotated) program is as follows:
|[con A : array of int;
var z : int;
var r : int;
{ true }
r, z := 0, 0;
{ invariant I : z = (MAX p,q | 0≤p≤q≤r : S.p.q) }
do r ≠ #A --->
{I ∧ r ≠ #A}
z := E;
{ I(r:=r+1), i.e., wp.(r:=r+1).I }
r := r + 1;
{I}
od
{Q : z = (MAX p,q | 0≤p≤q≤#A : S.p.q) }
]|
The loop body has been annotated with pre- and post-conditions in accord with proof obligation (ii) on the "loop checklist". The intermediate assertion I(r:=r+1) is motivated by the Hoare Triple Law of Catenation (which says that {P} S;T {Q} follows from {P} S {R} ∧ {R} T {Q}) and the fact that this assertion corresponds to wp.(r:=r+1).I, making it the weakest predicate R satisfying {R} r:=r+1 {I}.
We have reduced our problem to that of finding E to truthify the Hoare triple
Let's try to calculate E using proof obligation (ii):
Assume I ∧ r≠#A.
wp.(z:=E).I(r:=r+1)
= < wp assignment law >
(I(r:=r+1))(z:=E)
= < defn of I >
((z = (MAX p,q | 0≤p≤q≤r : S.p.q))(r:=r+1))(z:=E)
= < textual substitution >
(z = (MAX p,q | 0≤p≤q≤r+1 : S.p.q))(z:=E)
= < textual substitution >
E = (MAX p,q | 0≤p≤q≤r+1 : S.p.q)
= < split off term (range is guaranteed to be
non-empty if we strengthen I to include 0≤r) >
E = (MAX p,q | 0≤p≤q≤r : S.p.q) max (MAX p,q | 0≤p≤q=r+1 : S.p.q)
= < (Gries 8.14) One-point rule (Technically, need to use
(8.20) Nesting, then (8.14) and then (8.20) "in reverse".) >
E = (MAX p,q | 0≤p≤q≤r : S.p.q) max (MAX p | 0≤p≤r+1 : S.p.(r+1))
= < assumption I : z = (MAX ... ) >
E = z max (MAX p | 0≤p≤r+1 : S.p.(r+1))
At this point, we employ the "strengthening the invariant" heuristic by introducing a fresh variable y and including as a new conjunct of the invariant the following:
However, there is a problem with doing this. In order for this to hold upon completion of the final loop iteration (at which point r = #A), it would have to be the case that y = (MAX p | 0≤p≤#A+1 : S.p.(#A+1)). But S is not defined when its second argument exceeds #A, so it will not be possible for y's value to satisfy this condition (unless we insert code to cause y's value to become "undefined", which seems rather silly).
Is there any way to circumvent this obstacle? Well, suppose that we decide to augment the invariant with the new conjunct
instead. (This is exactly what we suggested above as a new conjunct, except that we have replaced the two occurrences of "r+1" with "r".) Then we arrange the code in the body of the loop so that the value of y is updated to satisfy I2(r:=r+1) before the assignment to z. Note that our choice of "z max y" as the right-hand side of the assignment to z is correct as long as I2(r:=r+1) holds at the time that that assignment is performed. Our program now looks like this:
|[con A : array of int;
var z,y : int;
var r : int;
{#A ≥ 0}
r, z, y := 0, 0, ?;
{ invariant I : I1 ∧ I2, where
I1 : z = (MAX p,q | 0≤p≤q≤r : S.p.q)
I2 : y = (MAX p | 0≤p≤r : S.p.r)
}
do r ≠ #A --->
{ I ∧ r≠#A }
y := F;
{ I1 ∧ I2(r:=r+1) }
z := z max y;
{ (I1 ∧ I2)(r:=r+1), i.e., I(r:=r+1) }
r := r + 1;
{ I }
od
{Q : z = (MAX p,q | 0≤p≤q≤#A : S.p.q) }
]|
It remains to figure out how to fill in the right-hand sides of the two assignments to y. Given that r is initialized to zero, the initial value of y must satisfy I2(r:=0), which requires that y = 0. Hence, we initialize y to zero. As for the assignment inside the loop, we calculate F in the context of trying to prove {I ∧ r≠#A} y := F { I1 ∧ I2(r:=r+1) }:
wp.(y:=F).(I1 ∧ I2(r:=r+1))
= < wp assignment law >
(I1 ∧ I2(r:=r+1))(y:=F)
= < textual substitution distributes over operators >
I1(y:=F) ∧ (I2(r:=r+1))(y:=F)
= < defn of I1, I2 and textual substitution >
z = (MAX p,q | 0≤p≤q≤r : S.p.q) ∧ F = (MAX p | 0≤p≤r+1 : S.p.(r+1))
= < 1st conjunct is assumption I1; (3.39) >
F = (MAX p | 0≤p≤r+1 : S.p.(r+1))
= < split off term (8.23); range is non-empty due to assumption 0≤r >
F = (MAX p | 0≤p≤r : S.p.(r+1)) max S.(r+1).(r+1)
= < from observation (1), S.(r+1).(r+1) = 0 >
F = (MAX p | 0≤p≤r : S.p.(r+1)) max 0
= < from observation (3''), S.p.(r+1) = S.p.r + A.r >
F = (MAX p | 0≤p≤r : S.p.r + A.r) max 0
= < provided R is non-empty and there are no free occurrences
of x in V, (MAX x | R : U + V) = (MAX x | R : U) + V >
F = (MAX p | 0≤p≤r : S.p.r) + A.r max 0
= < assumption I2 >
F = (y + A.r) max 0
Now we have a completed program. In order to prove obligations (iv) and (v), we need to include 0≤r≤#A as another conjunct of our loop invariant. (Indeed, we cheated a little above in that we twice used 0≤r as an assumption, as though it had already been included in the invariant.)
|[con A : array of int;
var z,y : int;
var r : int;
{#A ≥ 0}
r, z, y := 0, 0, 0;
{ invariant I : I1 ∧ I2 ∧ I3, where
I1 : z = (MAX p,q | 0≤p≤q≤r : S.p.q)
I2 : y = (MAX p | 0≤p≤r : S.p.r)
I3 : 0≤r≤#A
}
{ bound function t : #A - r }
do r ≠ #A --->
{ I1 ∧ I2 ∧ I3 ∧ r≠#A }
{ I1 ∧ I2 ∧ I3(r:=r+1) }
y := (y + A.r) max 0;
{ I1 ∧ I2(r:=r+1) ∧ I3(r:=r+1) }
z := z max y;
{ I(r:=r+1) }
r := r + 1;
{ I }
od
{Q : z = (MAX p,q | 0≤p≤q≤#A : S.p.q) }
]|