整数序列元素的总和:循环

时间:2017-06-10 03:42:48

标签: dafny

阅读Dafny入门指南后,我决定创建我的第一个程序:给定一系列整数,计算其元素的总和。但是,我很难让Dafny验证该程序。

function G(a: seq<int>): int
  decreases |a|
{
  if |a| == 0 then 0 else a[0] + G(a[1..])
}

method sum(a: seq<int>) returns (r: int)
  ensures r == G(a)
{
  r := 0;
  var i: int := 0;
  while i < |a|
    invariant 0 <= i <= |a|
    invariant r == G(a[0..i])
  {
    r := r + a[i];
    i := i + 1;
  }
}

我得到了

stdin.dfy(12,2): Error BP5003: A postcondition might not hold on this return path.
stdin.dfy(8,12): Related location: This is the postcondition that might not hold.
stdin.dfy(14,16): Error BP5005: This loop invariant might not be maintained by the loop.

我怀疑Dafny需要一些“帮助”来验证程序(可能是lemmas?)但是我不知道从哪里开始。

1 个答案:

答案 0 :(得分:1)

Here是您验证的程序版本。

有两件事需要解决:后置条件在循环后跟随的证据,以及保留循环不变量的证据。

后置条件

Dafny需要一个暗示,试图证明a == a[..|a|]可能会有所帮助。断言相等就足以完成证明的这一部分:Dafny自动证明了相等并用它来证明循环不变的后置条件。

这是一种常见的模式。你可以尝试通过在Dafny中“手工”进行证明,通过做出各种断言来看看困扰Dafny的事情,你会用这些断言在纸上证明这一点。

循环不变

这个有点复杂。我们需要证明更新r和递增i会保留r == G(a[..i])。为此,我使用了calc语句,让我们通过一系列中间步骤证明是相等的。 (如果您愿意,通过断言calc内的所有相关等式以及任何断言,总是可以在没有calc的情况下证明这些事情。但我认为calc更好。 )

我在calcr的更新发生之前放置了i声明。我知道在更新发生后,我需要证明r == G(a[..i])更新了ri的值。因此,在更新发生之前,足以证明r + a[i] == G(a[..i+1])未更新的值。我的calc语句以r + a[i]开头,适用于G(a[..i+1])

首先,通过进入循环的循环不变量,我们知道当前值的r == G(a[i])

接下来,我们要将a[i]置于G内。这个事实并非完全无足轻重,所以我们需要一个引理。我选择证明某些事情比必要更普遍,对于任何整数序列G(a + b) == G(a) + G(b)a来说b。我将此引理称为G_append。其证明将在下面讨论。现在,我们只是使用它来将a[i]作为单例序列。

calc的最后一步是将a[0..i] + [a[i]]合并到a[0..i+1]中。这是另一个序列扩展事实,因此需要明确断言。

完成了calc,证明了不变量得以保留。

引理

G_append上通过归纳进行a的证明。自动处理a == []的基本情况。在归纳的情况下,我们需要显示G(a + b) == G(a) + G(b),假设a的任何子序列的归纳假设。我为此使用了另一个calc语句。

G(a + b)开始,我们首先扩展G的定义。接下来,我们注意(a + b)[0] == a[0] a != []以来(a + b)[1..] == a[1..] + b。类似地,我们有G(a[1..] + b) == G(a[1..]) + G(b),但由于这是另一个序列扩展事实,因此必须明确断言。最后,我们可以使用归纳假设(由Dafny自动调用)来显示carouselView