Let me introduce you to the almost universal technique of verification debugging introduced recently in the docs.
Let me apply it to your case. First, you get the message that the "invariant might not be maintained by the loop". So the first idea, is to move the assertion at the end of the loop, to capture this idea, like this:
method sort(a: array<int>)
modifies a
ensures forall h, k: int ::
0 <= h < k < a.Length ==> a[h] <= a[k];
{
var i := 0;
while i < a.Length
invariant 0 <= i <= a.Length
invariant forall h, k: int :: //No more error
0 <= h < i && i <= k < a.Length ==> a[h] <= a[k];
invariant forall h, k: int :: //No more error
0 <= h < k < i ==> a[h] <= a[k];
{
var index := i;
var j := i+1;
while j < a.Length
invariant 0 <= i < j <= a.Length
invariant 0 <= index < j
invariant forall h, k: int ::
0 <= h < i && i <= k < a.Length ==> a[h] <= a[k];
invariant forall h, k: int ::
0 <= h < k < i ==> a[h] <= a[k];
invariant forall k: int ::
i <= k < j ==> a[index] <= a[k]
{
if a[j] < a[index] {
index := j;
}
j := j + 1;
}
var tmp := a[index];
a[index] := a[i];
a[i] := tmp;
i := i + 1;
assert forall h, k: int :: //error (forall)
0 <= h < i && i <= k < a.Length ==> a[h] <= a[k];
assert forall h, k: int :: //error (error forall)
0 <= h < k < i ==> a[h] <= a[k];
}
}
Now, the technique of verification debugging consists of moving the assertions back through the definitions in what is called a weakest precondition calculus (mostly described practically in the link at the beginning of this post). For example, to move these assertions above the assignment i := i + 1
, you'd simply replace i
by i + 1
in these assertions, like this:
method sort(a: array<int>)
modifies a
ensures forall h, k: int ::
0 <= h < k < a.Length ==> a[h] <= a[k];
{
var i := 0;
while i < a.Length
invariant 0 <= i <= a.Length
invariant forall h, k: int ::
0 <= h < i && i <= k < a.Length ==> a[h] <= a[k];
invariant forall h, k: int ::
0 <= h < k < i ==> a[h] <= a[k];
{
var index := i;
var j := i+1;
while j < a.Length
invariant 0 <= i < j <= a.Length
invariant 0 <= index < j
invariant forall h, k: int ::
0 <= h < i && i <= k < a.Length ==> a[h] <= a[k];
invariant forall h, k: int ::
0 <= h < k < i ==> a[h] <= a[k];
invariant forall k: int ::
i <= k < j ==> a[index] <= a[k]
{
if a[j] < a[index] {
index := j;
}
j := j + 1;
}
var tmp := a[index];
a[index] := a[i];
a[i] := tmp;
assert forall h, k: int :: //error (forall)
0 <= h < i+1 && i+1 <= k < a.Length ==> a[h] <= a[k];
assert forall h, k: int :: //error (error forall)
0 <= h < k < i+1 ==> a[h] <= a[k];
i := i + 1;
assert forall h, k: int :: //no more error
0 <= h < i && i <= k < a.Length ==> a[h] <= a[k];
assert forall h, k: int :: //no more error
0 <= h < k < i ==> a[h] <= a[k];
}
}
Now, note that the assignment with temporary variable:
var tmp := a[index];
a[index] := a[i];
a[i] := tmp;
can actually be simplified in Dafny to:
a[i], a[index] := a[index], a[i];
it makes verification easier for the step below (one step instead of three steps). To move the assertions up this double assignment, we need to ensure we can swap the values a[i]
and a[index]
in the assertions. For that, we need to explicit what the forall means for these two indices, so that we can swap them.
Since our indices h
can be i
in the first forall, and k
can be i
in the second forall, and because we don't know the relative position between index
and i
, we will need to split the cases. To split the cases, we just insert a if-then-else structure with the conditions, and put back our assertions in them:
method sort(a: array<int>)
modifies a
ensures forall h, k: int ::
0 <= h < k < a.Length ==> a[h] <= a[k];
{
var i := 0;
while i < a.Length
invariant 0 <= i <= a.Length
invariant forall h, k: int ::
0 <= h < i && i <= k < a.Length ==> a[h] <= a[k];
invariant forall h, k: int ::
0 <= h < k < i ==> a[h] <= a[k];
{
var index := i;
var j := i+1;
while j < a.Length
invariant 0 <= i < j <= a.Length
invariant 0 <= index < j
invariant forall h, k: int ::
0 <= h < i && i <= k < a.Length ==> a[h] <= a[k];
invariant forall h, k: int ::
0 <= h < k < i ==> a[h] <= a[k];
invariant forall k: int ::
i <= k < j ==> a[index] <= a[k]
{
if a[j] < a[index] {
index := j;
}
j := j + 1;
}
a[i], a[index] := a[index], a[i];
if i < index {
assert forall h, k: int ::
0 <= h < i+1 && i+1 <= k < a.Length ==> a[h] <= a[k];
assert forall h, k: int ::
0 <= h < k < i+1 ==> a[h] <= a[k];
} else if i == index {
assert forall h, k: int ::
0 <= h < i+1 && i+1 <= k < a.Length ==> a[h] <= a[k];
assert forall h, k: int ::
0 <= h < k < i+1 ==> a[h] <= a[k];
} else {
assert forall h, k: int :: //error (forall)
0 <= h < i+1 && i+1 <= k < a.Length ==> a[h] <= a[k];
assert forall h, k: int :: //error (forall)
0 <= h < k < i+1 ==> a[h] <= a[k];
}
assert forall h, k: int :: //no more error
0 <= h < i+1 && i+1 <= k < a.Length ==> a[h] <= a[k];
assert forall h, k: int :: //no more error
0 <= h < k < i+1 ==> a[h] <= a[k];
i := i + 1;
assert forall h, k: int ::
0 <= h < i && i <= k < a.Length ==> a[h] <= a[k];
assert forall h, k: int ::
0 <= h < k < i ==> a[h] <= a[k];
}
}
Now, something interesting happens. The assertions are proved if i == index
or i < index
, but not if index < i
.
Looking at the inner while loop, it looks like index
started at i
and is assigned j
from time to time, j
is increased, and j
starts at i+1
. Could it be that index <= i
in any case?
Let's add the invariant invariant index <= i
in the inner loop...
Magic, everything verifies now!
You can now remove the assertions one by one until you get back to your original code.