我的任务是初始化8x8矩阵并确保矩阵内的所有元素都设置为零。我还需要使用while循环和循环不变量来实现。我的实现如下:
method initMatrix(a: array2<int>)
modifies a
// require an 8 x 8 matrix
requires a.Length0 == 8 && a.Length1 == 8
// Ensures that all rows in the matrix are zeroes
ensures forall row, col :: 0 <= row < a.Length0 && 0 <= col < a.Length1 ==> a[row, col] == 0
{
var row : nat := 0;
while(row < a.Length0)
invariant 0 <= row <= a.Length0
invariant forall i,j :: 0 <= i < row && 0 <= j < a.Length1 ==> a[i,j] == 0
{
var col : nat := 0;
while(col < a.Length1)
invariant 0 <= row <= a.Length0
invariant 0 <= col <= a.Length1
invariant forall i,j :: i == row && 0 <= j < col ==> a[i,j] == 0
{
a[row, col] := 0;
col := col + 1;
}
row := row + 1;
}
}
从逻辑上讲,我认为我的所有量词都是正确的。但是,Dafny认为我的循环在while while循环中不变
invariant forall i,j :: 0 <= i < row && 0 <= j < a.Length1 ==> a[i,j] == 0
没有被循环维护。
我首先怀疑我的实施中存在问题。所以我首先在没有任何前置和后置条件的情况下运行该方法并打印出数组中的所有元素;输出是一个充满零的矩阵。
要确保 替换所有元素而不仅仅使用矩阵实例化时的零,我将矩阵中的所有元素初始化为1并打印出其中的所有值。结果是一样的 - 矩阵中的所有元素都是1。
从这个实验中,我可以得出结论,我的实现中不存在问题,但我是如何定义我的规范的。
我手工完成了我的算法并发现它应该在逻辑上有效,但我认为我对Dafny中循环不变量中量词的工作方式的理解可能会有所偏差。
条件0 <= i < row && 0 <= j < a.Length1
为假时究竟发生了什么?在第一次迭代中,变量row
等于0
,它不满足条件0 <= i < row
。它是否通过第一行并检查该行中的所有元素是否为零还是跳过它?
现在当它在初始迭代时到达语句row := row + 1;
时会发生什么?循环不变invariant forall i,j :: 0 <= i < row && 0 <= j < a.Length1 ==> a[i,j] == 0
在输入时是否使用row
的值(为零)还是使用递增的row
(1)值?
答案 0 :(得分:1)
原因是你的内环不变不够强大。它只涉及i == row
的情况,但没有说明你已经为i < row
所做的事情。
由于循环不变量的工作方式,Dafny基本上会忘记&#34;这些信息,因为你不记得&#34;它在内环不变。
您可以通过加强内循环不变来讨论i < row
来修复程序。例如,如下:
invariant
forall i,j ::
((0 <= i < row && 0 <= j < a.Length1) || (i == row && 0 <= j < col)) ==>
a[i,j] == 0
表示a
在这些行的所有列中的row
之前的所有先前行中都为零,并且row
中也是col
,但最后只有列0 <= i < row && 0 <= j < a.Length1
让我也借此机会解决您在描述调试过程时提出的其他一些问题。
条件
0 <= i < row
为假时究竟发生了什么?在第一次迭代中,变量行等于0,这不满足条件A
。它是否通过第一行并检查该行中的所有元素是否为零还是跳过它?
它&#34;跳过它&#34;。如果某个公式A ==> B
为false,则B
为真,甚至没有查看row := row + 1;
。
现在当它在初始迭代时到达语句
forall i,j :: 0 <= i < row && 0 <= j < a.Length1 ==> a[i,j] == 0
时会发生什么?循环不变量row
在输入时是否使用row
的值(为零)还是使用递增的row
(1)值?
在每个循环的顶部,循环不变量必须为真。当您第一次进入循环时(甚至在评估分支条件之前),它必须为真。此外,每次执行正文后,都必须如此。
换句话说,它使用递增的值Just because Dafny reports a verification error doesn't mean the program is wrong.
(在第一次执行循环体后为1)。
最后,让我更一般地评论循环不变量以及如何调试它们。
首先,让我介绍詹姆斯的 Dafny Verification第一定律(我刚刚编写):
row
Dafny的错误信息(大多数)通过使用&#34;可能没有&#34;等短语提醒您这个法律,如
循环可能无法维护此循环不变量。
实际上,您的示例程序实际上是正如您通过实际运行它所证明的那样。运行时,它会按预期打印全零。然而,达菲仍然报告错误。
这是我们为攻击程序验证等不可判定问题而付出的代价。使用Dafny时,你的工作是说服你的程序是正确的。当Dafny报告错误时,它只是表明Dafny还不相信。
现在,回到循环不变量。为什么达菲不相信? Dafny验证了关于循环不变量的三个方面。
困难的通常是#2。如果您不习惯于编程验证,那么Dafny会以一种奇怪且不直观的方式检查#2。 Dafny试图证明从满足循环不变量的任意状态使得循环的分支条件为真,如果然后执行循环的一次迭代,则循环不变量仍然成立。
这里的关键是任意。它不检查&#34;循环的每次迭代都以一个在程序执行期间实际发生的状态开始,保持循环不变&#34;。不会。它检查从任意状态开始的执行是否保留循环不变量。
回到你的示例程序,你得到了一个双嵌套循环。达夫尼说,它并不确定外环循环体的执行是否保留了一个外环不变量。什么是外环体?它是内循环的一堆迭代的执行? Dafny如何推理内循环?只能通过它的循环不变量。
因此,当证明#2关于外部循环时,内部循环的后置条件是它需要重新建立外部循环不变量,请注意,对于所有行,row
。然后Dafny尝试证明这假设仅内循环不变(以及循环的分支条件的否定),其仅涉及行{{1}}。由于国家不受约束,我们可以看出为什么达菲尼并不相信。