Adding modifies to a method breaks loop invariant - dafny

The overarching problem is that when I add modifies to a method, suddenly some of my loop invariants no longer check correctly.
I have worked around this by extracting that loop out into its own method, however that feels very hacky.
method Merge (arr : array<int>, l : int, m : int, r : int) returns (res : array<int>)
requires 0 <= l < m < r <= arr.Length
requires sorted_slice(arr, l, m);
requires sorted_slice(arr, m, r);
ensures sorted_slice(res, l, r)
{
var ia := l;
var ib := m;
res := new int[r - l];
var ri := 0;
while ri < res.Length
decreases res.Length - ri
invariant ri == (ia - l) + (ib - m)
//Ensure that the ia/ib is within the sorted slice at all times
invariant l <= ia <= m
invariant m <= ib <= r
// r[:ri] is sorted
invariant forall j, k :: (0 <= j <= k < ri) && (0 <= j <= k < res.Length) ==> res[j] <= res[k]
invariant forall ja, jr :: (ia <= ja < m) && (0 <= jr < ri < res.Length) ==> res[jr] <= arr[ja]
invariant forall jb, jr :: (ib <= jb < r) && (0 <= jr < ri < res.Length) ==> res[jr] <= arr[jb]
{
if ia >= m {
res[ri] := arr[ib];
ib := ib + 1;
ri := ri + 1;
} else if ib >= r {
res[ri] := arr[ia];
ia := ia + 1;
ri := ri + 1;
} else {
if arr[ia] < arr[ib]
{
res[ri] := arr[ia];
ia := ia + 1;
ri := ri + 1;
} else {
res[ri] := arr[ib];
ib := ib + 1;
ri := ri + 1;
}
}
}
...
}
Specifically the 4th and 5th loop invariants fail when I add modifies arr to Merge's signature.
Why might this be occurring? I can understand that I might need to add an invariant to the loop saying that it doesn't edit arr, however I can't find how to do that?

A loop inherits any modifies clause of the enclosing method [0]. So, if your method says modifies arr, then, in effect, so does your loop. This means that the verifier will treat the loop as if it may modify the elements of arr, whether or not the loop body actually does [1]. Therefore, you are indeed correct that you need to add something to the loop specification that says that the loop doesn't actually modify arr.
Your method is also allowed to modify the elements of res, because array res is "newly allocated" inside the method. This means that your loop is allowed to modify both arr and res, if your method says modifies arr.
So, you want to override the inherited modifies clause, so that you can restrict the effective modifies clause for the loop to be only res. To do that, write
modifies res
among the decreases and invariant clauses on the loop.
Fine points, just for reference:
[0] For a nested loop, the inner loop inherits the effective modifies clause of the enclosing loop.
[1] If the verifier can determine by a simple syntactic scan that the loop body couldn't possibly modify anything in the heap, then the verifier uses this fact, regardless of modifies clauses.
Btw, for your program, you can omit the explicit decreases clause, because Dafny will infer it for you.

Related

Postcondition for method to find min value of an array

I have a function to find the minimun value of an array
method arrayMin(a: array<int>) returns (m: int)
requires a.Length > 0;
ensures forall k :: 0 <= k < a.Length ==> a[k] >= m;
{
var i: nat := 1 ;
m := a[0] ;
while (i < a.Length)
invariant 1 <= i <= a.Length && forall k :: 0 <= k < i ==> a[k] >= m;
decreases a.Length - i;
{
if (a[i] < m) { m := a[i] ; }
i := i + 1 ;
}
}
When I try to call
var a := new int[5];
a[0], a[1], a[2], a[3], a[4] := 9, 4, 6, 3, 8;
var min := arrayMin(a);
assert min == 3;
Dafny is to able to verfiy the assertion. Are the post conditions not sufficient?
You are correct that the postcondition is not sufficient. In English, the postcondition says the following:
No element in the array a is below the value m returned.
But, this does not mean m has to be a value from the original array! For example, we can safely modify this line:
if (a[i] < m) { m := a[i] ; }
To be this:
if (a[i] < m) { m := a[i] - 1; }
And this program will still meet the postcondition.
Therefore to get your assertion to pass, you will a stronger postcondition. In particular, it needs to ensure that m is one of the elements contained in a.

dafny assertion violation when using the result of a method

I have written the program below to verify that an array is 'clean' of any particular element or not. I am having trouble asserting the result of the method. I keep getting an assertion violation when trying to assert the result of the method.
method Main (){
var a:= new int[3];
a[0], a[1], a[2] := 1,2,3;
var v := isClean (a, 1);
assert v == false;
}
method isClean (a : array <int>, key : int) returns (clean : bool)
requires a.Length > 0
{
var i := 0;
while (i < a.Length)
invariant 0 <= i <= a.Length
invariant forall k :: 0 <= k < i ==> a[k] != key
{
if (a[i] == key) {
clean := false;
return;
}
i := i + 1;
}
clean := true;
}
Dafny 2.3.0.10506
stdin.dfy(8,11): Error: assertion violation
Dafny program verifier finished with 2 verified, 1 error
You need to give an ensures clause on isClean. When Dafny verifies a program, it only looks at one method at a time. So when Dafny verifies Main, it does not look at the definition of isClean at all. Instead, it only looks at the requires and ensures clauses.
You already have the hard part of the proof done in the loop invariant. Basically you just need to modify a copy of that invariant so that it makes sense in the context of the caller, as an ensures clause, like this:
ensures clean <==> (forall k :: 0 <= k < a.Length ==> a[k] != key)
(Add that right below the requires clause on isClean.) In this ensures clause, clean refers to the named return value of the isClean method. If you add this clause, Dafny will still complain, because you are asking it to prove that a forall quantifier is false. That is equivalent to trying to prove an exists quantifier true, and requires an explicit "witness", which is a value of k that makes the body of the formula turn out true/false.
In this case, the intuitive reason why isClean returns false is because a[0] has the value 1, so a is not "clean" of 1. We can demonstrate this "witness" to Dafny by adding the assertion
assert a[0] == 1;
to the body of Main, right after the call to isClean.
For clarity, here is a complete version of the program that verifies:
method Main() {
var a := new int[3];
a[0], a[1], a[2] := 1,2,3;
var v := isClean (a, 1);
assert a[0] == 1;
assert v == false;
}
method isClean(a: array <int>, key: int) returns (clean: bool)
requires a.Length > 0
ensures clean <==> (forall k :: 0 <= k < a.Length ==> a[k] != key)
{
var i := 0;
while (i < a.Length)
invariant 0 <= i <= a.Length
invariant forall k :: 0 <= k < i ==> a[k] != key
{
if (a[i] == key) {
clean := false;
return;
}
i := i + 1;
}
clean := true;
}

Dafny copy multiset data into array

I am new to Dafny. I am writing a programme to FindMax. By going through this tutorial, I am able to write the verified programme for arrays.
method FindMax(a: array<int>) returns (i: int)
requires 0<a.Length
ensures 0<= i < a.Length
ensures forall k :: 0 <= k < a.Length ==> a[k] <= a[i]
// Annotate this method with pre- and postconditions
// that ensure it behaves as described.
{
// Fill in the body that calculates the INDEX of the maximum.
i:=0;
var index := 0;
while index < a.Length
invariant 0 <= index <= a.Length
invariant 0 <= i < a.Length
invariant forall k :: 0 <= k < index ==> a[k] <= a[i]
{
if a[index] > a[i] {
i := index;
}
index := index + 1;
}
}
Now I want to write a similar programme when data is in the form of Multiset. As .length function is not allowed in multiset, it's giving error.
Another approach I thought is to convert multiset data into arrays -> apply operation and convert back.
Now I am stuck into writing function to convert multiset data into arrays.
I read this tutorial but still facing difficulties due to limited documentation and being new to Dafny.
Any help or resource link will be highly appreciated.
For all those who might visit this answer later - all you need to do is use |a| at the place of a.length and the same method would correctly work for multiset

Modifying an array in Dafny with postconditions

Trying to implement a fairly simple method, where you pass an empty array and place values into it (natural numbers).
The code runs fine, but a simple postcondition that ought to pass in my mind is throwing me errors.
method Main() {
var a := new int[5];
initialise(a);
}
method initialise(a: array<int>)
modifies a
requires a.Length > 0
ensures forall i :: 0 <= i < a.Length ==> a[i] == i
{
var i := 0;
while i < a.Length
invariant 0 <= i <= a.Length
decreases a.Length - i
{
a[i] := i;
i := i + 1;
}
}
Error:
A postcondition might not hold on this return path. Related location 1: Line: 10, Col: 8
You need to tell Dafny about the invariant maintained by the loop.
Once you add
invariant forall j :: 0 <= j < i ==> a[j] == j
the proof goes through.

Invariant set may vary

A method that copies the negative elements of an array of integers into another array has the property that the set of elements in the result is a subset of the elements in the original array, which stays the same during the copy.
The problem in the code below is that, as soon as we write something in the result array, Dafny somehow forgets that the original set is unchanged.
How to fix this?
method copy_neg (a: array<int>, b: array<int>)
requires a != null && b != null && a != b
requires a.Length == b.Length
modifies b
{
var i := 0;
var r := 0;
ghost var sa := set j | 0 <= j < a.Length :: a[j];
while i < a.Length
invariant 0 <= r <= i <= a.Length
invariant sa == set j | 0 <= j < a.Length :: a[j]
{
if a[i] < 0 {
assert sa == set j | 0 <= j < a.Length :: a[j]; // OK
b[r] := a[i];
assert sa == set j | 0 <= j < a.Length :: a[j]; // KO!
r := r + 1;
}
i := i + 1;
}
}
Edit
Following James Wilcox's answer, replacing inclusions of sets with predicates on sequences is what works the best.
Here is the complete specification (for an array with distinct elements). The post-condition has to be detailed a bit in the loop invariant and a dumb assert remains in the middle of the loop, but all ghost variables are gone, which is great.
method copy_neg (a: array<int>, b: array<int>)
returns (r: nat)
requires a != null && b != null && a != b
requires a.Length <= b.Length
modifies b
ensures 0 <= r <= a.Length
ensures forall x | x in a[..] :: x < 0 <==> x in b[..r]
{
r := 0;
var i := 0;
while i < a.Length
invariant 0 <= r <= i <= a.Length
invariant forall x | x in b[..r] :: x < 0
invariant forall x | x in a[..i] && x < 0 :: x in b[..r]
{
if a[i] < 0 {
b[r] := a[i];
assert forall x | x in b[..r] :: x < 0;
r := r + 1;
}
i := i + 1;
}
}
This is indeed confusing. I will explain why Dafny has trouble proving this below, but first let me give a few ways to make it go through.
First workaround
One way to make the proof go through is to insert the following forall statement after the line b[r] := a[i];.
forall x | x in sa
ensures x in set j | 0 <= j < a.Length :: a[j]
{
var j :| 0 <= j < a.Length && x == old(a[j]);
assert x == a[j];
}
The forall statement is a proof that sa <= set j | 0 <= j < a.Length :: a[j]. I will come back to why this works below.
Second workaround
In general, when reasoning about arrays in Dafny, it is best to use the a[..] syntax to convert the array to a mathematical sequence, and then work with that sequence. If you really need to work with the set of elements, you can use set x | x in a[..], and you will have a better time than if you use set j | 0 <= j < a.Length :: a[j].
Systematically replacing set j | 0 <= j < a.Length :: a[j] with set x | x in a[..] causes your program to verify.
Third solution
Popping up a level to specifying your method, it seems like you don't actually need to mention the set of all elements. Instead, you can get away with saying something like "every element of b is an element of a". Or, more formally forall x | x in b[..] :: x in a[..]. This is not quite a valid postcondition for your method, because your method may not fill out all of b. Since I'm not sure what your other constraints are, I'll leave that to you.
Explanations
Dafny's sets with elements of type A are translated to Boogie maps [A]Bool, where an element maps to true iff it is in the set. Comprehensions such as set j | 0 <= j < a.Length :: a[j] are translated to Boogie maps whose definition involves an existential quantifier. This particular comprehension translates to a map that maps x to
exists j | 0 <= j < a.Length :: x == read($Heap, a, IndexField(j))
where the read expression is the Boogie translation of a[j], which, in particular, makes the heap explicit.
So, to prove that an element is in the set defined by the comprehension, Z3 needs to prove an existential quantifier, which is hard. Z3 uses triggers to prove such quantifiers, and Dafny tells Z3 to use the trigger read($Heap, a, IndexField(j)) when trying to prove this quantifier. This turns out to not be a great trigger choice, because it mentions the current value of the heap. Thus, when the heap changes (ie, after updating b[r]), the trigger may not fire, and you get a failing proof.
Dafny lets you customize the trigger it uses for set comprehensions using a {:trigger} attribute. Unfortunately, there is no great choice of trigger at the Dafny level. However, a reasonable trigger for this program at the Boogie/Z3 level would be just IndexField(j) (though this is likely a bad trigger for such expressions in general, since it is overly general). Z3 itself will infer this trigger if Dafny doesn't tell it otherwise. You can Dafny to get out of the way by saying {:autotriggers false}, like this
invariant sa == set j {:autotriggers false} | 0 <= j < a.Length :: a[j]
This solution is unsatisfying and requires detailed knowledge of Dafny's internals. But now that we've understood it, we can also understand the other workarounds I proposed.
For the first workaround, the proof goes through because the forall statement mentions a[j], which is the trigger. This causes Z3 to successfully prove the existential.
For the second workaround, we have simplified the set comprehension expression so that it no longer introduces an existential quantifier. Instead the comprehension set x | x in a[..], is translated to a map that maps x to
x in a[..]
(ignoring details of how a[..] is translated). This means that Z3 never has to prove an existential, so the otherwise very similar proof goes through.
The third solution works for similar reasons, since it uses no comprehensions and thus no problematic existential quantifiers/

Resources