用于正确Dafny方法的Z3模型

用于正确Dafny方法的Z3模型,z3,dafny,Z3,Dafny,对于正确的方法,Z3能否找到方法验证条件的模型 我原以为不是,但这里有一个例子,说明该方法是正确的 然而,验证找到了一个模型 Dafny 1.9.7就是这样。Dafny未能证明引理,这是由于两种可能的不完全性来源的组合:递归定义(此处Pow)和归纳。由于信息太少,证明实际上失败了,也就是说,因为问题是欠约束的,这反过来解释了为什么可以找到反例 归纳法 自动归纳是困难的,因为它需要计算归纳假设,这并不总是可能的。但是,Dafny有一些应用归纳法的启发式方法(可能有效,也可能无效),可以切换,如

对于正确的方法,Z3能否找到方法验证条件的模型

我原以为不是,但这里有一个例子,说明该方法是正确的

然而,验证找到了一个模型


Dafny 1.9.7就是这样。

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)
之外不提供任何信息。一个看起来不那么奇怪的断言是
assertpow(A,b)=A*A*Pow(A,b-2)

计算证明

仅供参考:您还可以明确证明步骤,并让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) {
    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是可靠的,因为它只会验证正确的程序。换句话说,如果程序不正确,Dafny验证器将永远不会说它是正确的。然而,基本的决策问题通常是不可判定的。因此,不可避免地,在某些情况下,程序符合其规范,并且验证器仍然给出错误消息。事实上,在这种情况下,验证者甚至可能展示一个所谓的反例。这可能是一个错误的反例(如上面的例子所示)——它只是意味着,就验证者所知,这是一个反例。如果验证器只是花了多一点时间,或者它足够聪明,可以展开更多的函数定义,应用归纳假设,或者做许多其他的好事,那么就有可能确定反例是假的。因此,您得到的任何错误消息(包括可能伴随此类错误消息的任何反例)都应解释为可能的错误(以及可能的反例)

如果您试图验证循环的正确性,但没有提供足够强的循环不变量,则经常会出现类似的情况。然后,Dafny验证器可能会在进入循环时显示一些实际上永远不会发生的变量值。然后,反例试图让您了解如何适当地增强循环不变量

最后,让我对马尔特所说的话补充两点

首先,在这个例子中,至少还有另一个不完整性的来源,即非线性算法。有时很难在周围导航

其次,可以简化使用函数
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

我对Visual Studio的Dafny插件不是很熟悉,但红点不是表示验证失败吗?如果是这样,调试器应该提供一个反例(针对失败的验证条件),而不是一个模型。是的,红点表示验证失败。调试器确实提供了一个示例。(这就是我所说的“模型”的意思。)然而这个例子不是反例,因为引理是真的。特别是功率(2902)等于功率(2*2902/2)。回答得很好。我从中学到了很多。让我吃惊的不是Dafny/Z3没有引理体中的代码就不能证明定理;事实上,如果验证过程中没有任何代码,我会感到惊讶。我不明白,现在仍然不明白的是,为什么有一个“反例”被发现实际上不是反例。通常,当BVD给出数字时,它实际上发现了一个反例,即代码有bug的输入。在这种情况下,没有bug,那么数字902和2是从哪里来的呢?我的理解是:Dafny无法证明该属性,因为它只能有限地展开
Pow
。因此,有一个应用程序
Pow
,关于它没有任何已知/假设,其值实际上是无约束的。因此,底层SMT解算器可以为
Pow
选择一个不符合实际函数定义的模型,因此可能违反引理。具体数字——这里是902和2——是试探性确定的,即随机选取的。谢谢。Z3查询结果