Reputation: 72
I am trying to verify an algorithm using Dafny. I am struggling to fix the error message "decreases expression might not decrease (timed out)". The basic structure of my algorithm is as follows:
while (U != {})
decreases |S| - |B - U|; // S is a constant and |B| <= |S|
invariant U < B; // U is a subset of B
{
var u :| u in U; // pick an element of u
U := U - {u}; // remove the element
while (...)
invariant U < B; // U is a subset of B
{
// modifies B and U but does not modify |B - U|
}
}
S, B and U are all sets. S is not modified at all in the algorithm. I have proved that the cardinality of B is less than or equal to the cardinality of S, so the decreases clause is bound by zero.
After each assignment to either B or U (in the inner while loop), I can prove that |B - U| remains the same. However, this is not enough, I need a loop invariant in the inner while loop that states this, but I do not know how to express this in Dafny. I have tried the following but it did not solve the problem:
invariant |old(B) - old(U)| == |B - U|;
(Note: This is the first time I am using Dafny and I have been stuck on this problem for around a week, any suggestions will be helpful).
Upvotes: 1
Views: 1219
Reputation: 2087
To express that the inner loop has no effect on the value of |B - U|
, you can save the value of |B - U|
in a ghost variable just before the inner loop. Then, mention that ghost variable in the inner loop invariant. Like this:
ghost var previousCardinalityOfBminusU := |B - U|;
while (...)
invariant |B - U| == previousCardinalityOfBminusU
{
// ...
}
Here are some other comments:
If the inner loop does not add any elements to U
, then a simpler termination metric for the outer loop is just |U|
. You'll then want to add a loop invariant like |U| <= prev
to the inner loop, where prev
is a ghost variable that you set to |U|
just before the inner loop.
Do you also have a plan for proving the termination of inner loop?
The expression U < B
says that U
is a proper subset of B
, but maybe that is already what you meant by the comment "U
is a subset of B
"?
You error message also says "timed out". Sometimes, there may be more than one cause of the time out, and it could be that fixing one also takes care of the other. To make sure that everything else is fine, I suggest you temporarily let the verifier assume termination works out.
One way to do that is to (temporarily) mark each loop with decreases *
, which tells the verifier that you're okay with the loop not terminating. (This will also require you to mark any enclosing loop and the enclosing method with decreases *
.)
Another way to temporarily let the verifier assume termination works out is to use an assume
statement. For example, for the outer loop, you can do:
while U != {}
// as before
{
ghost var prev := |S| - |B - U|;
// as before
// inner loop as before
assume |S| - |B - U| < prev; // give verifier the (possibly incorrect) assumption that the termination metric has gone down
}
This may discover if there are other problems as well (that were previously masked by the time out), and those could be another cause of the time out. If, however, your program verifies correctly with these assumptions, then it is indeed termination proof that's causing the time out. You'll then need to try to provide more of the correctness argument to the verifier (my hunch is that some additional invariant on the inner loop will do the trick).
Upvotes: 1