不变集可能会有所不同

时间:2017-09-10 20:26:55

标签: dafny

将整数数组的负元素复制到另一个数组的方法具有以下属性:结果中的元素集是原始数组中元素的子集,在复制期间保持不变。

下面代码中的问题是,一旦我们在结果数组中写了一些东西,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;
  }
}

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},像这样

来让Dafny摆脱困境
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永远不必证明一个存在主义,所以非常类似的证明就会通过。

第三种解决方案的工作原理类似,因为它不使用任何理解,因此没有问题的存在量词/