将整数数组的负元素复制到另一个数组的方法具有以下属性:结果中的元素集是原始数组中元素的子集,在复制期间保持不变。
下面代码中的问题是,一旦我们在结果数组中写了一些东西,Dafny就会忘记原始集合没有改变。 如何解决这个问题?
method copy_neg (a: array<int>, b: array<int>)
requires a != null && b != null && a != b
requires a.Length == b.Length
modifies b
{
var i := 0;
var r := 0;
ghost var sa := set j | 0 <= j < a.Length :: a[j];
while i < a.Length
invariant 0 <= r <= i <= a.Length
invariant sa == set j | 0 <= j < a.Length :: a[j]
{
if a[i] < 0 {
assert sa == set j | 0 <= j < a.Length :: a[j]; // OK
b[r] := a[i];
assert sa == set j | 0 <= j < a.Length :: a[j]; // KO!
r := r + 1;
}
i := i + 1;
}
}
在James Wilcox's answer之后,用序列上的谓词替换集合的包含是最有效的。
这是完整的规范(对于具有不同元素的数组)。后置条件必须在循环不变量中详细说明,并且哑声断言保留在循环的中间,但是所有的鬼变量都消失了,这很好。
method copy_neg (a: array<int>, b: array<int>)
returns (r: nat)
requires a != null && b != null && a != b
requires a.Length <= b.Length
modifies b
ensures 0 <= r <= a.Length
ensures forall x | x in a[..] :: x < 0 <==> x in b[..r]
{
r := 0;
var i := 0;
while i < a.Length
invariant 0 <= r <= i <= a.Length
invariant forall x | x in b[..r] :: x < 0
invariant forall x | x in a[..i] && x < 0 :: x in b[..r]
{
if a[i] < 0 {
b[r] := a[i];
assert forall x | x in b[..r] :: x < 0;
r := r + 1;
}
i := i + 1;
}
}
答案 0 :(得分:1)
这确实令人困惑。我将解释为什么Dafny在下面证明这一点有困难,但首先让我给出一些方法让它通过。
进行证明的一种方法是在行forall
之后插入以下b[r] := a[i];
语句。
forall x | x in sa
ensures x in set j | 0 <= j < a.Length :: a[j]
{
var j :| 0 <= j < a.Length && x == old(a[j]);
assert x == a[j];
}
forall
声明是sa <= set j | 0 <= j < a.Length :: a[j]
的证据。我将回到下面为什么会这样做。
通常,在Dafny中对数组进行推理时,最好使用a[..]
语法将数组转换为数学序列,然后使用该序列。如果您确实需要使用 set 元素,则可以使用set x | x in a[..]
,与使用set j | 0 <= j < a.Length :: a[j]
相比,您将获得更好的时间。
系统地将set j | 0 <= j < a.Length :: a[j]
替换为set x | x in a[..]
会导致您的程序验证。
弹出一个级别来指定你的方法,看起来你实际上并不需要提及所有元素的集合。相反,你可以说&#34; b
的每个元素都是a
&#34;的元素。或者,更正式地forall x | x in b[..] :: x in a[..]
。对于您的方法,这不是一个有效的后置条件,因为您的方法可能无法填写所有b
。由于我不确定您的其他限制是什么,我会留给您。
具有A
类型元素的Dafny集合被转换为Boogie贴图[A]Bool
,其中元素映射到true,如果它在集合中。诸如set j | 0 <= j < a.Length :: a[j]
之类的理解被翻译成Boogie地图,其定义涉及存在量词。这种特殊的理解转化为将x
映射到
exists j | 0 <= j < a.Length :: x == read($Heap, a, IndexField(j))
其中read
表达式是a[j]
的Boogie转换,特别是使堆显式。
因此,为了证明一个元素在理解中定义的集合中,Z3需要证明一个存在量词,这很难。 Z3使用触发器来证明这样的量词,而Dafny在尝试证明这个量词时告诉Z3使用触发器read($Heap, a, IndexField(j))
。事实证明这不是一个很好的触发器选择,因为它提到了堆的当前值。因此,当堆更改时(即,在更新b[r]
之后),触发器可能不会触发,并且您将获得失败的证据。
Dafny允许您使用{:trigger}
属性自定义用于集合理解的触发器。不幸的是,Dafny级别没有很好的触发选择。然而,在Boogie / Z3级别上该程序的合理触发器只是IndexField(j)
(尽管这可能是这种表达式的一个不良触发器,因为它过于笼统)。如果达夫尼没有说出来,Z3本身就会推断出这个触发器。你可以通过说{:autotriggers false}
,像这样
invariant sa == set j {:autotriggers false} | 0 <= j < a.Length :: a[j]
这个解决方案并不令人满意,需要详细了解Dafny的内部情况。但是现在我们已经理解了它,我们也可以理解我提出的其他解决方法。
对于第一个解决方法,证明会通过,因为forall
语句提到a[j]
,这是触发器。这使得Z3成功地证明了存在主义。
对于第二种解决方法,我们简化了集合理解表达式,使其不再引入存在量词。相反,理解set x | x in a[..]
将转换为将x
映射到
x in a[..]
(忽略a[..]
如何翻译的细节)。这意味着Z3永远不必证明一个存在主义,所以非常类似的证明就会通过。
第三种解决方案的工作原理类似,因为它不使用任何理解,因此没有问题的存在量词/