我正在使用dafny来证明对数字列表求和的不变量:
function sum (s: seq<int>, i: int) : int {
if |s| == 0 || i == 0 then 0
else s[0] + sum(s[1..], i - 1)
}
/*
code:
cnt = 0;
while i < |input|
cnt += input[i];
i += 1
*/
method test (input : seq<int>, cnt : int, i : int)
{
// invariant: cnt = sum(input, i) && i <= |input| && i >= 0
// prove that loop invariant is preserved:
if cnt == sum(input, i) && i <= |input| && i >= 0 && i < |input|
{ assert (cnt + input[i]) == sum(input, i + 1) && i+1 <= |input| && i+1 >= 0; }
}
dafny无法验证这一点。我错过了sum
的后置条件吗?
答案 0 :(得分:1)
Dafny可以做证明,但证明需要一些归纳。因此,你必须以这样一种方式写下你的断言,即Dafny被诱导尝试归纳。最简单的方法是写一个引理。通常,简单地写一个断言不会导致Dafny尝试归纳证明。
function sum (s: seq<int>, i: int) : int {
if |s| == 0 || i == 0 then 0
else s[0] + sum(s[1..], i - 1)
}
lemma sumLemma(s: seq<int>, i: int)
requires i >= 0 && i < |s|
ensures (sum(s, i) + s[i]) == sum(s, i + 1)
{
}
/*
code:
cnt = 0;
while i < |input|
cnt += input[i];
i += 1
*/
method test (input : seq<int>, cnt : int, i : int)
{
// invariant: cnt = sum(input, i) && i <= |input| && i >= 0
// prove that loop invariant is preserved:
if cnt == sum(input, i) && i >= 0 && i < |input|
{
assert i+1 <= |input|;
assert i+1 > 0;
sumLemma(input,i);
assert (cnt + input[i]) == sum(input, i + 1);
}
}
发生的事情是,当你写出引理时,Dafny猜测归纳步骤可能是什么。如果你关闭Dafny的感应启发式,它会迫使你称之为归纳假设:
lemma {:induction false} sumLemma(s: seq<int>, i: int)
requires i >= 0 && i < |s|
ensures (sum(s, i) + s[i]) == sum(s, i + 1)
{
if |s| == 0 || i == 0
{ } else {
sumLemma(s[1..], i-1);
}
}
我们在这里做的是对序列的(归纳)定义或正整数的有根(自然)排序进行归纳论证。你通常可以将归纳证明看作是一个完善的排序 - 这里我们可以选择序列尾部小于序列的顺序,除了没有尾部的空序列;或正整数的自然排序。归纳证明技术表明,您可以通过以下方式证明排序的所有元素的某些属性:
所以在我们的案例中,证据有两种情况:
基本情况,序列为空或i==0
- 其中
我们也处于递归函数sum
的基本情况。
Dafny通过sum
。
归纳案例 - 这里我们引用归纳假设。 sumLemma
适用于序列的尾部和i-1
。 Dafny可以从归纳假设和sum
的定义证明这个案例(您可以将其视为Dafny一次展开sum
的定义)。
为了健全,Dafny还必须证明感应本身是有根据的。这相当于证明引理sumLemma
终止。 Dafny总是证明函数和程序的完全正确性(终止)(除非你不告诉它,或者在某些特殊情况下)。大多数时候Dafny猜测正确的终止措施,但如果您遇到无法猜测终止措施的情况,您可以提供减少条款。
lemma {:induction false} sumLemma(s: seq<int>, i: int)
decreases s
requires i >= 0 && i < |s|
ensures (sum(s, i) + s[i]) == sum(s, i + 1)
归纳引理和归纳函数在结构上非常相似的原因是我们需要归纳的基本情况对应于感应证据的基本情况,以避免不得不展开的潜在问题。 sum
的定义未知/任意次数(即如果归纳的基本情况在排序中比sum
的基本情况高出一些步数。)
你可能会发现,对于一些更困难的引理,Dafny将无法猜出正确的感应步骤,无论如何你都会自己进行归纳调用。