达夫尼:没有任何条款触发意味着什么?

时间:2018-03-21 05:14:37

标签: formal-verification dafny

我在Dafny收到警告,说我的量词有

No terms found to trigger on.

我想为我的代码做的是找到一个方形值小于或等于给定自然数'n'的最大数字。这是我到目前为止提出的代码:

method sqrt(n : nat) returns (r: int)
  // square less than or equal to n
  ensures (r * r) <= n 
  // largest number
  ensures forall i :: 0 <= i < r ==> (i * i) < (r * r)
{
    var i := 0; // increasing number
    r := 0;
    while ((i*i) <= n)
      invariant (r*r) <= n
      invariant forall k :: 0 <= k < r ==> (k*k) < (r*r)
      decreases n - i
    {
      r := i;
      i := i + 1;
    }

    return r;
}

在这个片段中,我通过使用后置条件ensures (r * r) <= n验证我返回的平方值小于或等于'n'的值。

我也尝试通过使用量词forall i :: 0 <= i < r ==> (i*i) < (r*r) <验证返回的值确实是平方值小于或等于'n'的最大值/ p>

这个量词意味着'r'之前的所有元素的平方值都小于r的平方值。

如何修复No terms found to trigger on.?它究竟意味着什么?

达菲告诉我,这是一个警告。这是否意味着我的量词是错误的?或者这是否意味着Dafny根本无法验证它,但这是正确的?

1 个答案:

答案 0 :(得分:4)

警告与Dafny(和底层求解器Z3)如何处理量词有关。

首先,它确实是一个警告。如果程序没有错误(您的示例就是这种情况),那么它已通过验证程序并满足其规范。你不需要修理警告。

但是,在更复杂的程序中,您经常会发现警告伴随着失败或不可预测的验证结果。在这些情况下,值得知道如何解决它。通常,可以通过引入一个无用的辅助函数来作为触发器来消除警告。

例如,以下是您的示例版本,其中Dafny没有警告触发器

function square(n: int): int
{
    n * n
}

method sqrt(n : nat) returns (r: int)
  // square less than or equal to n
  ensures r * r <= n
  // largest number
  ensures forall i :: 0 <= i < r ==> square(i) < r * r
{
    var i := 0; // increasing number
    r := 0;
    while i * i <= n
      invariant r * r <= n
      invariant forall k :: 0 <= k < r ==> square(k) < r * r 
      decreases n - i
    {
      r := i;
      i := i + 1;
    }

    return r;
}

我所做的只是介绍一个定义为square(n)的新函数n * n,然后在程序其余部分的量词下的几个关键位置使用它。

如果你只关心这个例子来验证,你可以在这里停止阅读。答案的其余部分试图解释为什么这个修复工作。

简而言之,它起作用是因为Dafny现在能够使用square(i)square(k)作为两个量词的触发器。

但是,无论如何,什么是触发器,为什么square(i)是有效的触发器,但i * i不是?

什么是触发器?

触发器是一种语法模式,涉及量化变量,用作解算器对量词执行某些操作的启发式算法。使用forall量词,触发器告诉Dafny何时用其他表达式实例化量化公式。否则,Dafny永远不会使用量化的公式。

例如,考虑公式

forall x {:trigger P(x)} :: P(x) && Q(x)

此处,注释{:trigger P(x)}会关闭Dafny的自动触发推理,并手动将触发器指定为P(x)。否则,Dafny会将P(x)Q(x)推断为触发器,这通常会更好,但更难以解释触发器:)。

这个触发器意味着每当我们提到P(...)形式的表达式时,量词就会得到实例化,这意味着我们得到量词体的副本{{1插入...

现在考虑这个程序

x

Dafny抱怨它无法验证后置条件。但这在逻辑上是显而易见的!只需在前提条件中为method test() requires forall x {:trigger P(x)} :: P(x) && Q(x) ensures Q(0) { } 插入0即可获得x,这意味着后置条件P(0) && Q(0)

由于我们选择了触发器,Dafny无法验证此方法。由于后置条件仅提及Q(0),而没有提及Q(0),但量词仅由P触发,因此Dafny将永远不会实例化前置条件。

我们可以通过添加看似无用的断言来修复此方法

P

到方法的主体。整个方法现在验证,包括后置条件。为什么?因为我们提到assert P(0); ,它从前置条件触发量词,导致求解器学习P(0),这允许它证明后置条件。

花一点时间意识到刚刚发生了什么。我们有一个逻辑上正确但失败的验证方法,并为它添加了逻辑上无关但真实的断言,导致验证者突然成功。换句话说,Dafny的验证者有时可以依赖于逻辑上无关的影响来取得成功,特别是当涉及量词时。

成为一名称职的Dafny用户,必须了解何时可以通过逻辑上无关的影响来解决失败,以及如何找到说服Dafny成功的正确技巧。

(顺便说一句,请注意,如果我们让Dafny推断触发器而不是手动阻塞它,那么这个例子没有无关断言就没有了。)

什么是一个好的触发器?

好的触发器通常是包含量化变量的小表达式,这些变量不涉及所谓的&#34;解释符号&#34;,出于我们的目的,可以考虑&#34;算术运算&#34;。触发器中不允许使用算术,因为解算器无法轻易判断何时提到了触发器。例如,如果P(0) && Q(0)是允许的触发器并且程序员提到x + y,则解算器会立即识别出这等于触发表达式。由于这会导致量词的实例化不一致,因此触发器中不允许运算。

许多其他表达式 允许作为触发器,例如索引到Dafny数据结构,解除引用字段,设置成员资格和函数应用程序。

有时,写下公式的最自然方式将不包含有效的触发器,正如您的原始示例所做的那样。在那种情况下,达夫尼会警告你。有时,验证无论如何都会成功,但在大型程序中,您通常需要修复这些警告。一个好的总体策略是引入一个新的函数,抽象的一部分量化公式可以作为触发器。