面对测试数据时,Dafny 量词没有有效地实例化
Dafny quantifier not instantiated helpfully when faced with test data
我正在尝试在 dafny 中创建一个谓词来确定字符数组是否包含任何重复项。它最终应该测试以下内容:对于数组中的任何两个元素,如果理论值相等,则它们的索引也必须相等。请忽略少于2个元素的情况
predicate noDuplicates (a:array<char>)
requires a!= null
reads a
{
forall j,k:: (0<=j<a.Length && 0<=k<a.Length) ==> ((a[j]==a[k]) ==> (j==k))
}
但是当我运行下面的测试时,断言失败了。为什么会这样?
var b: array<char> := new char[5];
b[0], b[1], b[2], b[3], b[4] := 't', 'e', 's', 't', 's';
assert noDuplicates(b) == false;
我认为问题是 dafny 的求解器图中没有任何东西可以用来实例化量词。您可以通过添加额外的断言来验证它,这样 Dafny 就会想到使用这些值来实例化量词。
例如这个:
method Main() {
var b: array<char> := new char[5];
b[0], b[1], b[2], b[3], b[4] := 't', 'e', 's', 't', 's';
assert b[0] == 't';
assert b[3] == 't';
assert noDuplicates(b) == false;
}
或者这个
method Main() {
var b: array<char> := new char[5];
b[0], b[1], b[2], b[3], b[4] := 't', 'e', 's', 't', 's';
assert b[0] == b[3];
assert noDuplicates(b) == false;
}
为什么这样有效
通常当 Dafny 需要证明涉及量词的东西时,它必须选择值来实例化量词(由于减少到 SAT)。它通常会尝试的其中一件事是一个新的任意常量,但这不会让你走得太远。您需要它来选择值 0 和 3(例如)。然而,有很多整数,所以随机选择这样的值的机会很低——低到事实上它甚至不会尝试。相反,它所做的是查看它已知的任何事实,并挑选出与量化公式中原子形状相匹配的任何事实。在这种情况下,它正在寻找 a[i]==a[j]
之类的东西 - 因此通过放入涉及 b[0]
和 b[3]
的断言,提示 Dafny 最终尝试值 0
和 3
i
和 j
。直接版本 b[0]==b[3]
非常直接,而间接版本 b[0]==t && b[3]==t
将间接导致 Dafny 在其 egraph 中将边缘放在 b[0]
和 b[3]
之间并推断出事实 b[0]==b[3]
然后将提示它尝试 0
和 3
来实例化量词。
我很想知道任何其他解决方案。
编辑
我想到了另一个解决方案using the fuel
annotation,但我不知道它是否真的是更好的解决方案:
method Main() {
var b: array<char> := new char[5];
b[0], b[1], b[2], b[3], b[4] := 't', 'e', 's', 't', 's';
assert {:fuel contains,3} noDuplicates(b[..]) == false;
}
predicate noDuplicates (a:seq<char>)
{
a == [] || (!contains(a[1..], a[0]) && noDuplicates(a[1..]))
}
predicate contains (a:seq<char>, v:char)
{
a != [] && (a[0] == v || contains(a[1..],v))
}
我正在尝试在 dafny 中创建一个谓词来确定字符数组是否包含任何重复项。它最终应该测试以下内容:对于数组中的任何两个元素,如果理论值相等,则它们的索引也必须相等。请忽略少于2个元素的情况
predicate noDuplicates (a:array<char>)
requires a!= null
reads a
{
forall j,k:: (0<=j<a.Length && 0<=k<a.Length) ==> ((a[j]==a[k]) ==> (j==k))
}
但是当我运行下面的测试时,断言失败了。为什么会这样?
var b: array<char> := new char[5];
b[0], b[1], b[2], b[3], b[4] := 't', 'e', 's', 't', 's';
assert noDuplicates(b) == false;
我认为问题是 dafny 的求解器图中没有任何东西可以用来实例化量词。您可以通过添加额外的断言来验证它,这样 Dafny 就会想到使用这些值来实例化量词。
例如这个:
method Main() {
var b: array<char> := new char[5];
b[0], b[1], b[2], b[3], b[4] := 't', 'e', 's', 't', 's';
assert b[0] == 't';
assert b[3] == 't';
assert noDuplicates(b) == false;
}
或者这个
method Main() {
var b: array<char> := new char[5];
b[0], b[1], b[2], b[3], b[4] := 't', 'e', 's', 't', 's';
assert b[0] == b[3];
assert noDuplicates(b) == false;
}
为什么这样有效
通常当 Dafny 需要证明涉及量词的东西时,它必须选择值来实例化量词(由于减少到 SAT)。它通常会尝试的其中一件事是一个新的任意常量,但这不会让你走得太远。您需要它来选择值 0 和 3(例如)。然而,有很多整数,所以随机选择这样的值的机会很低——低到事实上它甚至不会尝试。相反,它所做的是查看它已知的任何事实,并挑选出与量化公式中原子形状相匹配的任何事实。在这种情况下,它正在寻找 a[i]==a[j]
之类的东西 - 因此通过放入涉及 b[0]
和 b[3]
的断言,提示 Dafny 最终尝试值 0
和 3
i
和 j
。直接版本 b[0]==b[3]
非常直接,而间接版本 b[0]==t && b[3]==t
将间接导致 Dafny 在其 egraph 中将边缘放在 b[0]
和 b[3]
之间并推断出事实 b[0]==b[3]
然后将提示它尝试 0
和 3
来实例化量词。
我很想知道任何其他解决方案。
编辑
我想到了另一个解决方案using the fuel
annotation,但我不知道它是否真的是更好的解决方案:
method Main() {
var b: array<char> := new char[5];
b[0], b[1], b[2], b[3], b[4] := 't', 'e', 's', 't', 's';
assert {:fuel contains,3} noDuplicates(b[..]) == false;
}
predicate noDuplicates (a:seq<char>)
{
a == [] || (!contains(a[1..], a[0]) && noDuplicates(a[1..]))
}
predicate contains (a:seq<char>, v:char)
{
a != [] && (a[0] == v || contains(a[1..],v))
}