我目前正在 Z3 之上为我的编程语言编写一个自动验证器,作为一个有趣的项目,我试图用它来证明使用循环的斐波那契实现等同于递归。
如果输入程序正确,它似乎可以工作,即它为 Z3 生成合理的输入,而 Z3 说它不能满足,这意味着在我的上下文中,程序是正确的。但是当我更改了一个变量初始化并因此导致程序不正确时,我的验证器提出了以下 Z3 公式,这对我来说似乎并不太复杂,但 Z3 说“未知”。
(set-option :smt.auto-config false)
(set-option :smt.mbqi false)
(declare-fun fib (Int) Int)
(declare-fun fakeFib (Int) Int)
(declare-fun n () Int)
(assert (forall ((n Int))
(! (= (fib n)
(ite (= n 0) 0
(ite (= n 1) 1
(+ (fakeFib (- n 1)) (fakeFib (- n 2))))))
:pattern ((fib n)))))
(assert (forall ((n Int))
(! (= (fib n) (fakeFib n)) :pattern ((fib n)))))
(assert (>= n 0))
(assert (not (and (<= 0 n) (= 1 (fib 1)) (= 1 (fib 0)))))
(check-sat)
请注意,这两个量词表示斐波那契的递归定义。我的一个朋友告诉我这个技巧来避免匹配循环:我没有将 fib 定义为递归函数,而是引入了另一个我没有提供定义的函数 fakeFib,我在 fib 的递归定义中使用了该函数。另外,我告诉验证者它们是相等的,但是量词只得到 fib 的模式,而不是 fakeFib 的模式。由于模式的限制,它是不完整的,即,它只能证明程序的正确性,只要看一层递归就足以证明它(但它可以很容易地扩展到 k 层)。但我可以忍受这个限制,以避免匹配循环。
代码的“错误”是我错误地初始化了一个变量,这最终导致(= 1 (fib 0))
最后一个断言中的组件不正确。对于正确的程序,它将是(= 0 (fib 0))
.
一些观察:
- 如果我替换
(= 1 (fib 0))
为(= 0 (fib 0))
,那么 Z3 立即发现它无法满足。 - 以前,我没有选项
(set-option :smt.auto-config false)
和(set-option :smt.mbqi false)
设置,然后它在我的机器上耗尽内存并且在rise4fun 上超时。 - 似乎对有类似问题的人有用的设置
(set-option :smt.macro-finder true)
对我不起作用。我猜这是因为我有两个量词,fib
而不仅仅是一个。 - 我尝试使用 cvc4 作为比较,它立即说“未知”。所以我的问题似乎不是 Z3 特有的。
- 该公式显然是可满足的。
(= 1 (fib 0))
是false
,因此整个最后一个断言自动为真。(>= n 0)
也很容易满足。