The Fibonacci function is often defined as follows:
Viewing this function as defining the sequence
gives rise to the so-called Fibonacci sequence
in which, beginning at the third element, each element is the sum of the preceding two.
Let's develop a program that, given as input a positive integer N, calculates f.N. Here is a specification:
|[ con N : int; { N > 0 }
var m : int;
compute_f.N
{ Q: m = f.N }
]|
It seems likely that we will need to use repetition to solve this problem. Applying the replace a constant by a variable heuristic, we can rewrite the postcondition Q as the slightly stronger Q':
Applying the remove a conjunct heuristic, we take the first conjunct of Q' as the loop invariant and the second as the negation of its guard. We obtain the following refinement of the specification:
|[ con N : int; { N > 0 }
var m : int;
var n : int;
n, m := ?, ?;
{ P : m = f.n }
do n != N ---> n, m := ?, ?;
od
{ Q' : m = f.n & n = N }
{ Q : m = f.N }
]|
First we consider how to initialize n and m so as to "truthify" the proposed loop invariant. That is, we wish to find expressions to replace the question marks so that the Hoare Triple
Either way, this suggests that n should be increased within the body of the loop so as to eventually reach value N. The simplest way to do that is by incrementing n, so that's what we propose. These refinements lead to the program
|[ con N : int; { N > 0 }
var m : int;
var n : int;
n, m := 1, 1;
{ P : m = f.n }
do n != N ---> n, m := n+1, ?;
od
{ Q' : m = f.n & n = N }
{ Q : m = f.N }
]|
It remains to determine how to update m inside the body of the loop. We attempt to calculate the appropriate expression E by proving item (ii) among the five proof obligations for a loop:
(ii) {P & B} n, m := n+1, E {P}
Assume P and B.
wp.(n,m := n+1,E).P
= < wp assignment law >
P(n,m := n+1,E)
= < defn of P; textual sub. >
E = f.(n+1)
At this point, our only manipulative opportunity arises from the third
equation in the definition of f. However, for that equation
to apply requires that n+1 > 1, i.e., n > 0.
As we initialized n to 1 and thereafter all changes to n
are due to incrementing it, it is clear that n > 0 will be
true. Hence, we incorporate this condition into the loop invariant
so that we can use it here as an assumption. Starting the proof over
again with the stronger version of P (namely,
P = P0 & P1, where
P0 : m = f.n and P1 : n > 0),
we get
(ii) {P & B} n, m := n+1, E {P}
Assume P (i.e., m = f.n and n > 0) and B.
wp.(n,m := n+1,E).P
= < wp assignment law >
P(n,m := n+1,E)
= < defn of P; textual sub. >
E = f.(n+1) & n+1 > 0
= < assumption n > 0 implies 2nd conjunct; (Gries 3.39) >
E = f.(n+1)
= < assumption n > 0 implies n+1 > 1,
justifying use of 3rd equation in defn of f >
E = f.(n-1) + f.n
= < assumption m = f.n >
E = f.(n-1) + m
At this point, we observe that it would be nice to have a program variable,
say r, guaranteed to have value f.(n-1) at this point in
execution, because then we could finish the derivation of E:
= < assumption r = f.(n-1) >
E = r + m
As we are free to introduce such a variable, we do so! That is, we
strengthen the loop invariant by adding
P2 : r = f.(n-1) as a new conjunct.
Of course, doing this brings with it the responsibility to introduce
code to initialize r so as to truthify P2
and to update r during each loop iteration so as to preserve
the truth of P2. The refined program is as follows:
|[ con N : int; { N > 0 }
var m, r : int;
var n : int;
n, m, r := 1, 1, ?;
{ P : P0 & P1 & P2, where
P0 : m = f.n, P1 : n > 0, and P2 : r = f.(n-1) }
do n != N ---> n, m, r := n+1, r+m, ?;
od
{ Q' : m = f.n & n = N }
{ Q : m = f.N }
]|
Let's calculate the proper initialization to r, by deriving an
expression F satisfying {true} n,m,r := 1,1,F {P}.
By the Hoare Triple Assignment Law (and (Gries 3.73)), this is equivalent to [P(n,m,r := 1,1,F)].
P(n,m,r := 1,1,F)
= < defn of P; text. sub. >
1 = f.1 & 1 > 0 & F = f.(1-1)
= < defn of f; arithmetic, (Gries 3.39) >
F = f.0
= < defn of f >
F = 0
Now to determine how to modify r during each iteration.
As our previous work has demonstrated that execution of the loop body
preserves the truth of P0 and P1,
we use as a postcondition only P2.
{P & B} n,m,r := n+1, r+m, G {P2}
Assume P and B.
wp.(n,m,r := n+1,r+m,G).P2
= < defn of P2; text. sub >
G = f.(n+1-1)
= < arithmetic >
G = f.n
= < assumption m = f.n >
G = m
We obtain the following program:
|[ con N : int; { N > 0 }
var m, r : int;
var n : int;
n, m, r := 1, 1, 0;
{ P : P0 & P1 & P2, where
P0 : m = f.n, P1 : n > 0, and P2 : r = f.(n-1) }
do n != N ---> n, m, r := n+1, r+m, m;
od
{ Q' : m = f.n & n = N }
{ Q : m = f.N }
]|
All that remains is to provide a bound function. Here, the obvious
choice is t : N - n. Technically, in order to prove item
(iv), we need to include the condition n <= N
within the loop invariant. Clearly, this condition is truthified
by initializing n to 1 (given that N > 0 is
a global invariant) and preserved by incrementing n when
it is known that n < N, as is guaranteed by P & B.