答案 0 :(得分:2)
Malte所说的是正确的(我发现它也得到了很好的解释)。
Dafny是健全的,因为它只会验证正确的程序。换句话说,如果程序不正确,Dafny验证程序将永远不会说它是正确的。但是,潜在的决策问题通常是不可判定的。因此,不可避免地会出现程序满足其规范并且验证者仍然给出错误消息的情况。实际上,在这种情况下,验证者甚至可能会显示声称的反例。这可能是一个错误的反例(如上例所示) - 它只是意味着,就验证者所知,这是一个反例。如果验证者只花了一点时间,或者它是否足够聪明地展开更多功能定义,应用归纳假设,或者做许多其他好事,那么就有可能确定反例是伪造的。 。因此,您获得的任何错误消息(包括可能伴随此类错误消息的任何反例)都应解释为可能的错误(以及可能的反例)。
如果您尝试验证循环的正确性并且没有提供足够强的循环不变量,则会经常发生类似情况。然后,Dafny验证器可以在进入循环时显示一些变量值,这些变量实际上永远不会发生。然后,反例试图让您了解如何适当地加强循环不变量。
最后,让我在Malte所说的内容中添加两个注释。
首先,在这个例子中至少涉及另一个不完整的来源,即非线性算术。导航有时很难。
其次,可以简化使用函数Dummy
的技巧。只需提及Pow
调用就足够了(至少在本例中),例如:
lemma EvenPowerLemma(a: int, b: nat)
requires Even(b)
ensures Pow(a, b) == Pow(a*a, b/2)
{
if b != 0 {
var dummy := Pow(a, b - 2);
}
}
尽管如此,我还是更喜欢其他两种手动校样,因为它们可以更好地向用户解释校样是什么。
Rustan
答案 1 :(得分:1)
由于两种可能的不完整来源的组合,Dafny未能证明这一引理:递归定义(此处为Pow
)和归纳。由于信息太少,证据有效失败,即因为问题不严格,这反过来解释了为什么可以找到反例。
<强>感应强>
自动化归纳很困难,因为它需要计算归纳假设,这并不总是可行的。但是,Dafny有一些启发式用于应用归纳(可能或可能不起作用),并且可以切换,如下面的代码所示:
lemma {:induction false} EvenPowerLemma_manual(a: int, b: nat)
requires Even(b);
ensures Pow(a, b) == Pow(a*a, b/2);
{
if (b != 0) {
EvenPowerLemma_manual(a, b - 2);
}
}
关闭启发式算法,您需要手动“调用”引理,即使用归纳假设(此处,仅在b >= 2
的情况下),以便获得证明。
在你的情况下,启发式算法被激活了,但它们并不“足够好”以完成证明。我将解释下一步的原因。
递归定义
通过展开它们来静态推理递归定义很容易产生无限下降,因为它通常在停止时是不可判定的。因此,Dafny默认只展开一次函数定义。在您的示例中,仅展开Pow
的定义一次不足以使归纳启发式工作,因为归纳假设必须应用于Pow(a, b-2)
,而Pow(a, b - 1)
不会出现在证明中(因为展开一次只能让你到Pow(a, b-2)
)。在证明中明确提及function Dummy(a: int): bool
{ true }
lemma EvenPowerLemma(a: int, b: nat)
requires Even(b);
ensures Pow(a, b) == Pow(a*a, b/2);
{
if (b != 0) {
assert Dummy(Pow(a, b - 2));
}
}
,即使在一个无意义的公式中,也会引发归纳启发式,但是:
Dummy
Pow(a, b-2)
函数用于确保断言不提供语法以外的任何信息,包括assert Pow(a, b) == a * a * Pow(a, b - 2)
。一个看起来不那么奇怪的断言是lemma {:induction false} EvenPowerLemma_manual(a: int, b: nat)
requires Even(b);
ensures Pow(a, b) == Pow(a*a, b/2);
{
if (b != 0) {
calc {
Pow(a, b);
== a * Pow(a, b - 1);
== a * a * Pow(a, b - 2);
== {EvenPowerLemma_manual(a, b - 2);}
a * a * Pow(a*a, (b-2)/2);
== Pow(a*a, (b-2)/2 + 1);
== Pow(a*a, b/2);
}
}
}
。
计算证据
仅供参考:您也可以明确证明步骤并让Dafny检查它们:
Dim OutClm as long