我设法避开了任何非线性算术问题。以下是我对这个问题的看法:
您试图建立一个后置条件,为了清楚起见,我将写为P(s, N, M),即s、N 和M 的某个函数。提出执行此操作的循环的一种技术是“用变量替换常量”。这意味着您选择所需后置条件的常量之一(在这里,您可以选择N 或M,因为s 不是常量)并将其替换为将要更改的变量在每个循环迭代中。让我们选择M 作为常量并引入(正如您在程序中所做的那样)a 作为变量。由于我们选择了M 作为常量,我们希望a 的最终值是M,所以我们将从a 开始N。然后我们有:
method doingMath(N: int, M: int) returns (s: int)
requires N <= M
ensures P(s, N, M)
{
var a := N;
while a < M
invariant N <= a <= M
invariant P(s, N, a) // postcondition, but with variable a instead of constant M
}
如果你在这个程序中输入(但将P(s, N, a)扩展为实际条件),那么你会发现Dafny证明了后置条件。换句话说,验证器给你的信息是如果你可以建立和保持这个循环不变量,那么程序就会正确地建立后置条件。
您自己也可以看到这一点。循环保护的否定给你M <= a,结合循环不变量a <= M给你a == M。当您结合a == M 和循环不变量P(s, N, a) 时,您将得到后置条件P(s, N, M)。
太好了。但是验证者抱怨说循环不变量在条目上不成立。这是因为我们没有为s 提供任何初始值。由于a 具有初始值N,我们需要为s 找到一个满足P(s, N, N) 的值。该值为0,因此我们将程序更新为
method doingMath(N: int, M: int) returns (s: int)
requires N <= M
ensures P(s, N, M)
{
var a := N;
s := 0;
while a < M
invariant N <= a <= M
invariant P(s, N, a)
}
接下来,让我们编写循环体。 (注意我是如何开始使用循环不变量的,而不是从循环体开始然后试图找出一个不变量。对于这类程序,我发现这是最简单的方法。)我们已经决定我们想让a从初始值N变化到最终值M,所以我们添加赋值a := a + 1;:
method doingMath(N: int, M: int) returns (s: int)
requires N <= M
ensures 2*s == M*(M+1) - N*(N+1)
{
var a := N;
s := 0;
while a < M
invariant N <= a <= M
invariant P(s, N, a)
{
a := a + 1;
}
}
这解决了终止问题。我们需要做的最后一件事是在循环内更新s,以便保持不变量。这很容易以目标导向的方式向后完成。方法如下:在循环体的末尾,我们要确保 P(s, N, a) 成立。这意味着我们希望条件P(s, N, a + 1) 在分配给a 之前保持。您可以通过将 desired 条件中的 a 替换为(赋值的右侧)a + 1 来获得此条件(同样,请记住我们正在向后工作)。
好的,所以在分配给a 之前,我们想要P(s, N, a + 1),而我们在循环体内得到的是不变量P(s, N, a)。现在,是我将P(...) 扩展到您的实际情况的时候了。好的,我们有
2*s == a*(a+1) - N*(N+1) (*)
我们想要
2*s == (a+1)*(a+2) - N*(N+1) (**)
让我们将(**) 中的(a+1)*(a+2) 重写为2*(a+1) + a*(a+1)。所以,(**) 等价地可以写成
2*s == 2*(a+1) + a*(a+1) - N*(N+1) (***)
如果您比较 (***)(这是我们想要的)和 (*)(这是我们已经得到的),那么您会注意到 (***) 的右侧比 2*(a+1) 多(*) 的右侧。所以,我们必须安排左手边增加同样的数量。
如果你将s 增加a+1,那么左边的2*s 增加2*(a+1),这就是我们想要的。所以,我们的最终程序是
method doingMath(N: int, M: int) returns (s: int)
requires N <= M
ensures 2*s == M*(M+1) - N*(N+1)
{
var a := N;
s := 0;
while a < M
invariant N <= a <= M
invariant 2*s == a*(a+1) - N*(N+1)
{
s := s + a + 1;
a := a + 1;
}
}
如果需要,您可以将分配的顺序交换为 s 和 a。这会给你
method doingMath(N: int, M: int) returns (s: int)
requires N <= M
ensures 2*s == M*(M+1) - N*(N+1)
{
var a := N;
s := 0;
while a < M
invariant N <= a <= M
invariant 2*s == a*(a+1) - N*(N+1)
{
a := a + 1;
s := s + a;
}
}
综上所述,我们从循环不变量构建了循环体,我们通过在后置条件中“用变量替换常量”来设计这个循环不变量。
鲁斯坦