Java - Iteration Formally (Full Notes)
Java - Iteration Formally (Full Notes)
Iteration Iteration
25
Iteration Iteration
}
// POST: Q
We have to find a loop invariant I and a loop variant V such that the
invariant holds before, during and after the loop and the variant is
bounded and decreases on every loop iteration.
The above Hoare logic rule for while captures the partial correctness requirements for
a while-loop.
Note that this Hoare logic rule imposes three proof obligations on us:
(a) P −→ I
(b) { I ∧ cond } body { I }
(c) I ∧ ¬cond −→ Q
The first and last of these proof obligations are simple assertions on the current program
state. However, the second is itself a Hoare Triple, which will require more care.
26
Iteration Iteration
// MID: M
Partial Correctness:
(a) The loop invariant I holds before the loop is entered.
(b) Given the condition, the loop body re-establishes the loop invariant I.
(c) Termination of the loop and the loop invariant I imply the
mid-condition M immediately after loop.
Total Correctness: (as above and additionally)
(d) The variant V is bounded.
(e) The variant V decreases with each loop iteration.
Drossopoulou & Wheelhouse (DoC) Discrete Mathematics, Logic & Reasoning 39 / 58
Proving total correctness imposes two further proof obligations on us, namely:
where V old is the variant V with all variables/object attributes modified by the code body
replaced with their old values (i.e. values before running the code body).
The purpose of these proof obligations is to show that I ∧ cond hold only a finite number
of times.
The first of these proof obligations requires us to find a lower-bound for the variant V .
Remember that in this course we look for an integer expression that has a fixed lower
bound (typically 0), but in general any well-founded ordering (such as increasing integer
values or lexicographical ordering) with a limit will do.
The second proof obligation requires us to prove that the variant monotonically decreases
on every loop iteration (i.e. moves towards the lower-bound identified above). This
obligation requires us to relate the values stored in each program variable used in the
variant both before and after the execution of the code body.
27
Iteration Iteration
{ I ∧ cond } body { I }
Slide 40
In proving that the loop re-establishes the invariant, we need to take care to track the
potential change in state caused by the body code. This means that we need to make
variable substitutions to correctly set-up our proof obligation.
To help with this, we make use of the Mod function, which gives us a set of all of the
variables/object-attributes modified by some code. We add to this set the bodies of any
arrays that the code modifies and then convert the set to a list in alphabetical order, to
ensure a consistent renaming of each modified variable or object (array) attribute.
As an example consider:
Mod(res = res + a[i]) = {res}
Mod(a[i] = res) = {a[i], a[..)}
Mod(i++) = {i}
therefore
Mod(res = res + a[i]; a[i] = res; i++;) = {res, a[i], a[..), i}
and so
mod = [a[..), a[i], i, res]
28
Iteration Iteration
The most important part of this diagram is tracking which parts of the array a have
been modified by the loop and which parts are still unmodified from the provided array’s
contents.
It is also useful to note how the value of res matches the cumulative sum that is going
to be stored in the array at each iteration of the loop.
29
Iteration Iteration
7 int i = 0;
8 // INV: a 6= null ∧ 0 ≤ i ≤ a.length ∧ a[i..) ≈ a[i..)pre
P P
9 ∧ res = a[..i)pre ∧ ∀k ∈ [0..i).[ a[k] = a[..k+1)pre ] (I)
10 // VAR: a.length − i (V )
11 while (i < a.length) {
12 res = res + a[i];
13 a[i] = res;
14 i++;
15 } P
16 // MID: res = a[..)pre
P
17 ∧ ∀k ∈ [0..a.length).[ a[k] = a[..k+1)pre ] (M )
18 return res;
19 }
Drossopoulou & Wheelhouse (DoC) Discrete Mathematics, Logic & Reasoning 42 / 58
This method calculates the sum of an array, but it also replaces each element of the array
with the cumulative sum of the elements up to (and including) that element from the
original input array.
Note that due to Java’s call by value nature, and that the culSum method does not make
any local modification to the array reference a, we do not need to track the distinction
between a and apre or similarly a.length and a.lengthpre in any of our assertions.
We choose to explicitly tack the assertion a 6= null in our invariant so that we do not
need to worry about the safety of the loop condition i < a.length. However, we could
actually infer this property from the fact that the assertion 0 ≤ i ≤ a.length also holds
in our invariant. (If a were null, then it would not be well-defined to compare a.length
to i).
30
Iteration Iteration
Given:
Slide 43
In this proof obligation, we are verifying that the loop invariant (I) is established by the
method’s initialisation code. In this case this is simply the initialisation of the variables
res and i with 0 values and the implicit assertion that the contents of the array a have
not been modified yet.
We choose substitute a[..)old for a[..) in the mid-condition M0 to be completely explicit
about the contents of the array a during this proof obligation. However, we could ac-
tually safely drop this substitution (and the assertion about the array’s contents being
unmodified) in this case, as we can see that the array’s contents are not being modified
by this code.
Note that we could choose to substitute resold for res and iold for i in the mid-condition
M0 , but since neither of these variables exist before we run the initialisation code, this
would be a vacuous substitution.
We will always choose to omit such vacuous substitutions to keep our notation a little
lighter, where possible.
31
Iteration Iteration
Proof:
α follows immediately from (1)
(6) 0 ≤ 0 ≤ a.length from (1)
β follows from (6) and (4)
Slide 44
The proof of invariant initialisation is not that demanding, but it is important to try to
start the proof of each invariant conjunct from some known mathematical or logical step,
so that we are being clear and precise with our reasoning.
The conclusion of each invariant conjunct proof above is always a simple substitution of
a concrete value with its corresponding initialised variable. This is common for all such
invariant initialisation proofs.
32
Iteration Iteration
Given:
(1) a 6= null from I
(2) 0 ≤ iold ≤ a.length from I
(3) a[iold ..)oldP≈ a[iold ..)pre from I
(4) resold = a[..iold )pre P from I
(5) ∀k ∈ [0..iold ).[ a[k]old = a[..k+1)pre ] from I
(6) iold < a.length cond
(7) res = resold + a[iold ]old from code line 12
(8) a[iold ] = res from code line 13
(9) i = iold + 1 from code line 14
(10) a[..) ≈ a[..iold )old : a[iold ] : a[iold +1..)old implicit from code
The set-up of the givens for the proof that the loop body re-establishes the invariant may
look rather intimidating, but this can actually be broken down into simpler pieces that
should each be easily understandable.
First, we have the givens (1-5) that come from the fact that the loop invariant (I) must
hold at the start of each loop iteration. These assertions need to have any variables that
are modified by the code in the loop body replaced with their old values.
Next, we have a given (6) that comes directly from the fact that the loop condition (cond)
must hold for the program’s execution to have entered the loop body.
Then we have the givens (7-9) that come from the logical and mathematical descriptions
of the explicit effects of the code in the loop body.
Finally, we have a given (10) that comes from the logical and mathematical description of
the implicit effects of the code in the loop body (i.e. those things which are not modified
by the code in the loop body). In this example, we have an assertion that describes that
the only element of the array a that is modified by the code in the loop body is that at
index i (for the value that i has at the start of the loop iteration).
33
Iteration Iteration
−→
I
To show:
(α) a 6= null I
(β) 0 ≤ i ≤ a.length I
(γ) a[i..) ≈
Pa[i..)pre I
(δ) res = a[..i)pre P I
() ∀k ∈ [0..i).[ a[k] = a[..k+1)pre ] I
Iteration Iteration
34
Iteration Iteration
I ∧ i ≥ a.length
−→
M
Slide 48
Given:
(1) a 6= null from I
(2) 0 ≤ i ≤ a.length from I
(3) a[i..) ≈Pa[i..)pre from I
(4) res = a[..i)pre P from I
(5) ∀k ∈ [0..i).[ a[k] = a[..k+1)pre ] from I
(6) i ≥ a.length ¬cond
To show: P
(α) res = a[..)pre P M
(β) ∀k ∈ [0..a.length).[ a[k] = a[..k+1)pre ] M
The proof obligation for showing that the mid-condition (in this case M ) holds straight
after the loop is in the context where we know that the loop invaraint (I) must still hold
(we’ve only just left the loop), but we also know that the loop condition must also have
just evaluated to false.
This gives rise to the informal obligation: I ∧ ¬cond −→ M
The most interesting part of this obligation is what we get from the ¬cond conjunct (the
rest is just writing out I and M ).
In this particular example, the loop condition (cond) is i < a.length. Thus,
¬cond , ¬(i < a.length)
←→ i ≥ a.length
It is important that we only take what is given to us by the negation of the loop condition
at this point in the proof set-up.
During the proof itself, we will be able to deduce that i = a.length from the above
i ≥ a.length and the loop invaraint’s 0 ≤ i ≤ a.length (i’s exact equality being the
only possible state that satisfies both assertions).
However, this derivation is a part of the proof itself, not something that you should write
in the givens for this proof obligation.
35
Iteration Iteration
Proof:
(7) i = a.length
P from (2) and (6)
Slide 49
Notice that here we did not need to refer to the code at all
It is worth briefly remarking that substituting (7) into (3) leads to the assertion:
a[a.length..) ≈ a[a.length..)pre
but this actually describes an empty range. So, we no longer have any assertion about the
unmodified part of the array. However, this make sense, as in our desired post-condition
all of the array contents have been updated.
36
Iteration Iteration
Given:
(1) a 6= null from I
(2) 0 ≤ iold ≤ a.length from I
(3) a[iold ..)oldP≈ a[iold ..)pre from I
(4) resold = a[..iold )pre P from I
(5) ∀k ∈ [0..iold ).[ a[k]old = a[..k+1)pre ] from I
(6) iold < a.length cond
(7) res = resold + a[iold ]old from code line 12
(8) a[iold ] = res from code line 13
(9) i = iold + 1 from code line 14
(10) a[..) ≈ a[..iold )old : a[iold ] : a[iold +1..)old implicit from code
Since the proof set-up is identical for each termination property, we tend to prove both
properties at the same time.
Note that the givens above are the same as for proof (b) since we are again verifying code
that runs within the body of the loop.
Also note that the only program variable used in our variant V for this program is i. So
we make the minimum substitutions necessary on the old value of the variant V old .
37
Iteration Iteration
−→
V ≥ 0 ∧ V [ i 7→ iold ] > V
To show:
(α) a.length − i ≥ 0 V ≥0
(β) a.length − iold > a.length − i V [ i 7→ iold ] > V
Iteration Iteration
Proof:
(11) iold + 1 ≤ a.length from (6)
(12) i ≤ a.length from (11) and (9)
Slide 52
38
Iteration Iteration
Have we now shown that the sum method satisfies its specification?
We still need to show that the code after the loop establishes the
post-condition.
Slide 53
Iteration Iteration
M ∧ r = res
−→
Q
Slide 54
Given: P
(1) res = a[..)pre P from M
(2) ∀k ∈ [0..a.length).[ a[k] = a[..k+1)pre ] from M
(3) r = res from code line 18
To show: P
(α) r = a[..)pre P Q
(β) ∀k ∈ [0..a.length).[ a[k] = a[..k+1)pre ] Q
Proof:
α follows from (3) and (1)
β follows directly from (2)
Drossopoulou & Wheelhouse (DoC) Discrete Mathematics, Logic & Reasoning 54 / 58
39
Iteration Iteration
Given:
(1) a 6= null from I
(2) 0 ≤ iold ≤ a.length from I
(3) a[iold ..)oldP≈ a[iold ..)pre from I
(4) resold = a[..iold )pre P from I
(5) ∀k ∈ [0..iold ).[ a[k]old = a[..k+1)pre ] from I
(6) iold < a.length cond
To show:
(α) 0 ≤ iold < a.length legal array access on lines 12 and 13
Proof:
α follows from (2) and (6)
Drossopoulou & Wheelhouse (DoC) Discrete Mathematics, Logic & Reasoning 55 / 58
Iteration Iteration
40
effectively reasoning independently of the platform that the code is running on.
Of course, in practice, this is something that you would need to be worried about. After
all, this kind of problem is what caused the Ariane 5 Disaster.
The Java keywords break and continue can be very useful constructs for manipulating
the control flow of a loop.
break immediately terminates the execution of the loop, returning the control flow to the
code outside the loop.
continue immediately stops the execution of the current iteration of the loop, returning the
control flow to the condition check at the start of a new iteration.
The reasoning for each of these constructs ties in rather neatly with what we have already
considered.
On the execution of the break keyword we will leave the loop, so we need to establish
that the midcondion M after the loop now holds at this point in the program. Note that
when we break out of the loop, we do not need to re-establish that the loop invariant I
still holds.
On the execution of the continue keyword we will start a new loop iteration, so we need
to establish that the loop invariant now holds at this point in the program.
41
Iteration Iteration
achieve.
Method specifications extract the essence of what a method is
promising to achieve.
There is an underlying inductive thinking in the notion of loop
invariants.
There is an underlying inductive thinking in the specification of
recursive functions.
Iteration Iteration
42