我在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.
?它究竟意味着什么?
答案 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数据结构,解除引用字段,设置成员资格和函数应用程序。
有时,写下公式的最自然方式将不包含有效的触发器,正如您的原始示例所做的那样。在那种情况下,达夫尼会警告你。有时,验证无论如何都会成功,但在大型程序中,您通常需要修复这些警告。一个好的总体策略是引入一个新的函数,抽象的一部分量化公式可以作为触发器。