简体   繁体   English

在 smtlib 中定义列表 concat

[英]defining list concat in smtlib

I defined my own version of list concat based on Haskell as below:我基于 Haskell 定义了我自己的列表 concat 版本,如下所示:

(declare-datatypes ((MyList 1))
                   ((par (T) ((cons (head T) (tail (MyList T))) (nil)))))
(declare-fun my-concat ( (MyList T1) (MyList T1) ) (MyList T1))
(assert (forall ((xs (MyList T1)) (ys (MyList T1)) (x T1))
            (ite (= (as nil (MyList T1)) xs)
                 (= (my-concat xs ys) ys)
                 (= (my-concat (cons x xs) ys) (cons x (my-concat xs ys))))))

I am wondering why z3 is not able to reason about the following?我想知道为什么 z3 无法推理以下内容?

(assert (not (= (my-concat (cons 4 (as nil (MyList Int))) (as nil (MyList Int))) 
                (cons 4 (as nil (MyList Int))))))
(check-sat) ; runs forever

Loading your file加载您的文件

If I load your program to z3 as you've given it, it says:如果我按照您提供的方式将您的程序加载到 z3,它会说:

(error "line 3 column 33: Parsing function declaration. Expecting sort list '(': unknown sort 'T1'")
(error "line 4 column 29: invalid sorted variables: unknown sort 'T1'")
(error "line 8 column 79: unknown function/constant my-concat")

That's because you cannot define "polymorphic" functions in SMTLib.那是因为您不能在 SMTLib 中定义“多态”函数。 At the user-level, only fully monomorphic functions are allowed.在用户级别,只允许使用完全单态的函数。 (Though internally SMTLib does provide polymorphic constants, there's no way for the user to actually create any polymorphic constants.) (虽然 SMTLib 内部确实提供了多态常量,但用户无法实际创建任何多态常量。)

So, I'm not sure how you got to load that file.所以,我不确定你是如何加载该文件的。

Monomorphisation单态化

Looks like you only care about integer-lists anyhow, so let's just modify our program to work on lists of integers.看起来您无论如何只关心整数列表,所以让我们修改我们的程序以处理整数列表。 This process is called monomorphisation, and is usually automatically done by some front-end tool before you even get to the SMT-solver, depending on what framework you're working in. That's just a fancy way of saying create instances of all your polymorphic constants at the mono-types they are used.这个过程称为单态化,通常在您使用 SMT 求解器之前由某些前端工具自动完成,具体取决于您正在使用的框架。这只是一种奇特的说法,即创建所有多态的实例它们使用的单一类型的常量。 (Since you mentioned Haskell, I'll just throw in that while monomorphisation is usually possible, it's not always feasible: There might be way too many variants to generate making it impractical. Also, if you have polymoprhic-recursion then monomorphisation doesn't work. But that's a digression for the time being.) (既然你提到了 Haskell,我只想说,虽然单态化通常是可能的,但它并不总是可行的:生成的变体可能太多,使其不切实际。此外,如果你有多态递归,那么单态化就不会工作。但这暂时是题外话。)

If I monomorphise your program to Int 's only, I get:如果我将您的程序单态为Int ,我会得到:

(declare-datatypes ((MyList 1))
                   ((par (T) ((cons (head T) (tail (MyList T))) (nil)))))
(declare-fun my-concat ( (MyList Int) (MyList Int) ) (MyList Int))
(assert (forall ((xs (MyList Int)) (ys (MyList Int)) (x Int))
            (ite (= (as nil (MyList Int)) xs)
                 (= (my-concat xs ys) ys)
                 (= (my-concat (cons x xs) ys) (cons x (my-concat xs ys))))))
(assert (not (= (my-concat (cons 4 (as nil (MyList Int))) (as nil (MyList Int)))
                (cons 4 (as nil (MyList Int))))))
(check-sat)
(get-info :reason-unknown)

When I run z3 on this, I get:当我对此运行 z3 时,我得到:

unknown
(:reason-unknown "smt tactic failed to show goal to be sat/unsat (incomplete quantifiers)")

So, it doesn't loop at all like you mentioned;所以,它根本不像你提到的那样循环; but your file didn't really load in the first place.但是您的文件一开始并没有真正加载。 So maybe you were working with some other contents in the file as well, but that's a digression for the current question.因此,也许您也在处理文件中的其他一些内容,但这是当前问题的题外话。

But it still doesn't prove it!但是还是不能证明!

Of course, you wanted unsat for this trivial formula.当然,对于这个微不足道的公式,您想要unsat But z3 said it's too hard for it to deal with.但是z3说它太难对付了。 The reason-unknown is "incomplete quantifiers?"原因未知是“不完整的量词?” What does that mean?这意味着什么?

In short, SMTLib is essentially logic of many-sorted first order formulas.简而言之,SMTLib 本质上是多排序一阶公式的逻辑。 Solvers are "complete" for the quantifier-free fragment of this logic.对于该逻辑的无量词片段,求解器是“完整的”。 But adding quantifiers makes the logic semi-decidable.但是添加量词会使逻辑半可判定。 What that means is that if you give enough resources, and if smart heuristics are in play, the solver will eventually say sat for a satisifiable formula, but it may loop forever if given an unsat one.这意味着如果你提供足够的资源,并且如果智能启发式在发挥作用,求解器最终会说sat是一个可满足的公式,但如果给定一个unsat的公式,它可能会永远循环。 (It might get lucky and say unsat as well, but most likely it'll loop.) (它可能会很幸运并说unsat ,但很可能它会循环。)

There are very good reasons why above is the case, but keep in mind that this has nothing to do with z3: First-order logic with quantifiers is semi-decidable.上述情况有很好的理由,但请记住,这与 z3 无关:带量词的一阶逻辑是半可判定的。 (Here's a good place to start reading: https://en.wikipedia.org/wiki/Decidability_(logic)#Semidecidability ) (这里是开始阅读的好地方: https://en.wikipedia.org/wiki/Decidability_(logic)#Semidecidability

What usually happens in practice, however, is that the solver will not even answer sat , and will simply give up and say unknown as z3 did above.然而,在实践中通常发生的情况是求解器甚至不会回答sat ,而会像上面的 z3 那样简单地放弃并说unknown Quantifiers are simply beyond the means of SMT-solvers in general.一般而言,量词完全超出了 SMT 求解器的手段。 You can try using patterns (search stack-overflow for quantifiers and pattern triggers), but that usually is futile as patterns and triggers are tricky to work with and they can be quite brittle.您可以尝试使用模式(在堆栈溢出中搜索量词和模式触发器),但这通常是徒劳的,因为模式和触发器很难使用,而且它们可能非常脆弱。

What course of action do you have:你有什么行动方针:

Honestly, this is one of those cases where "give up" is good advice.老实说,这是“放弃”是个好建议的情况之一。 An SMT solver is just not a good fit for this sort of a problem. SMT 求解器不适合此类问题。 Use a theorem-prover like Isabelle, ACL2, Coq, HOL, HOL-Light, Lean, ... where you can express quantifiers and recursive functions and reason with them.使用 Isabelle、ACL2、Coq、HOL、HOL-Light、Lean 等定理证明器,您可以在其中表达量词和递归函数并对其进行推理。 They are built for this sort of thing.它们为这种事情而建造的。 Don't expect your SMT solver to handle these sorts of queries.不要指望您的 SMT 求解器能够处理这些类型的查询。 It's just not the right match.这不是正确的匹配。

Still, is there anything I can do?不过,有什么我可以做的吗?

You can try SMTLib's recursive-function definition facilities.您可以尝试 SMTLib 的递归函数定义工具。 You'd write:你会写:

(declare-datatypes ((MyList 1))
                   ((par (T) ((cons (head T) (tail (MyList T))) (nil)))))

(define-fun-rec my-concat ((xs (MyList Int)) (ys (MyList Int))) (MyList Int)
    (ite (= (as nil (MyList Int)) xs)
         ys
         (cons (head xs) (my-concat (tail xs) ys))))

(assert (not (= (my-concat (cons 4 (as nil (MyList Int))) (as nil (MyList Int)))
                (cons 4 (as nil (MyList Int))))))
(check-sat)

Note the define-fun-rec construct, which allows for recursive definitions.请注意define-fun-rec结构,它允许递归定义。 And voila, we get:瞧,我们得到:

unsat

But this does not mean z3 will be able to prove arbitrary theorems regarding this concat function.但这并不意味着 z3 将能够证明关于这个 concat function 的任意定理。 If you try anything that requires induction, it'll either give up (saying unknown ) or loop-forever.如果你尝试任何需要归纳的东西,它要么放弃(说unknown )要么永远循环。 Of course, as the capabilities improve some induction-like proofs might be possible in z3 or other SMT-solvers, but that's really beyond what they're designed for.当然,随着功能的改进,在 z3 或其他 SMT 求解器中可能会进行一些类似归纳的证明,但这确实超出了它们的设计目的。 So, use with caution.所以,谨慎使用。

You can read more about recursive definitions in Section 4.2.3 of http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf您可以在http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf的第 4.2.3 节中阅读有关递归定义的更多信息

To sum up总结一下

Do not use an SMT-solver for reasoning with quantified or recursive definitions.不要使用 SMT 求解器进行量化或递归定义的推理。 They just don't have the necessary power to deal with such problems, and it's unlikely they'll ever get there.他们只是没有处理此类问题的必要权力,而且他们不太可能到达那里。 Use a proper theorem prover for these tasks.为这些任务使用适当的定理证明器。 Note that most theorem-provers use SMT-solvers as underlying tactics, so you get the best of both worlds: You can do some manual work to guide the inductive proof, and have the prover use an SMT-solver to handle most of the goals automatically for you.请注意,大多数定理证明者使用 SMT 求解器作为基本策略,因此您可以两全其美:您可以做一些手动工作来指导归纳证明,并让证明者使用 SMT 求解器来处理大部分目标自动为你。 Here's a good paper to read to get you started on the details: https://people.mpi-inf.mpg.de/~jblanche/jar-smt.pdf这是一篇很好的论文,可以帮助您了解详细信息: https://people.mpi-inf.mpg.de/~jblanche/jar-smt.pdf

alias makes valid points, but I don't completely agree with the final conclusion to " not use an SMT-solver for reasoning with quantified or recursive definitions" . alias 提出了有效的观点,但我并不完全同意“不使用 SMT 求解器进行量化或递归定义的推理”的最终结论。 In the end, it depends on which kind of properties you need to reason about, what kind of answers you need (is unsat/unknown OK, or do you need unsat/sat and a model?), and how much work you're willing to invest:-)最后,这取决于您需要推理哪种属性,需要哪种答案(是 unsat/unknown OK,还是需要 unsat/sat 和 model?),以及您做了多少工作愿意投资:-)

For example, SMT-based program verifiers such as Dafny and Viper quickly verify the following assertions about lists:例如,基于 SMT 的程序验证器(如DafnyViper )可以快速验证以下关于列表的断言:

assert [4] + [] == [4]; // holds
assert [4,1] + [1,4] == [4,1,1,4]; // holds
assert [4] + [1] == [1,4]; // fails

Both tools can be used online, but the websites are rather slow and not terribly reliable.这两种工具都可以在线使用,但是网站速度很慢,而且不太可靠。 You can find the Dafny example here and the Viper example here .您可以在此处找到Dafny 示例和在此处找到Viper 示例

Here is the relevant SMT code that Viper generates:以下是 Viper 生成的相关 SMT 代码:

(set-option :auto_config false) ; Usually a good idea
(set-option :smt.mbqi false)

;; The following definitions are an excerpt of Viper's sequence axiomatisation,
;; which is based on Dafny's sequence axiomatisation.
;; See also:
;;   https://github.com/dafny-lang/dafny
;;   http://viper.ethz.ch

(declare-sort Seq<Int>) ;; Monomorphised sort of integer sequences

(declare-const Seq_empty Seq<Int>)
(declare-fun Seq_length (Seq<Int>) Int)
(declare-fun Seq_singleton (Int) Seq<Int>)
(declare-fun Seq_index (Seq<Int> Int) Int)
(declare-fun Seq_append (Seq<Int> Seq<Int>) Seq<Int>)
(declare-fun Seq_equal (Seq<Int> Seq<Int>) Bool)

(assert (forall ((s Seq<Int>)) (!
  (<= 0 (Seq_length s))
  :pattern ((Seq_length s))
  )))

(assert (= (Seq_length (as Seq_empty  Seq<Int>)) 0))

(assert (forall ((s1 Seq<Int>) (s2 Seq<Int>)) (!
  (implies
    (and
      (not (= s1 (as Seq_empty  Seq<Int>)))
      (not (= s2 (as Seq_empty  Seq<Int>))))
    (= (Seq_length (Seq_append s1 s2)) (+ (Seq_length s1) (Seq_length s2))))
  :pattern ((Seq_length (Seq_append s1 s2)))
  )))

(assert (forall ((s Seq<Int>)) (!
  (= (Seq_append (as Seq_empty  Seq<Int>) s) s)
  :pattern ((Seq_append (as Seq_empty  Seq<Int>) s))
  )))

(assert (forall ((s Seq<Int>)) (!
  (= (Seq_append s (as Seq_empty  Seq<Int>)) s)
  :pattern ((Seq_append s (as Seq_empty  Seq<Int>)))
  )))

(assert (forall ((s1 Seq<Int>) (s2 Seq<Int>) (i Int)) (!
  (implies
    (and
      (not (= s1 (as Seq_empty  Seq<Int>)))
      (not (= s2 (as Seq_empty  Seq<Int>))))
    (ite
      (< i (Seq_length s1))
      (= (Seq_index (Seq_append s1 s2) i) (Seq_index s1 i))
      (= (Seq_index (Seq_append s1 s2) i) (Seq_index s2 (- i (Seq_length s1))))))
  :pattern ((Seq_index (Seq_append s1 s2) i))
  :pattern ((Seq_index s1 i) (Seq_append s1 s2))
  )))

(assert (forall ((s1 Seq<Int>) (s2 Seq<Int>)) (!
  (=
    (Seq_equal s1 s2)
    (and
      (= (Seq_length s1) (Seq_length s2))
      (forall ((i Int)) (!
        (implies
          (and (<= 0 i) (< i (Seq_length s1)))
          (= (Seq_index s1 i) (Seq_index s2 i)))
        :pattern ((Seq_index s1 i))
        :pattern ((Seq_index s2 i))
        ))))
  :pattern ((Seq_equal s1 s2))
  )))

(assert (forall ((s1 Seq<Int>) (s2 Seq<Int>)) (!
  (implies (Seq_equal s1 s2) (= s1 s2))
  :pattern ((Seq_equal s1 s2))
  )))

; ------------------------------------------------------------

; assert Seq(4) ++ Seq[Int]() == Seq(4)
(push)
(assert (not 
  (Seq_equal
    (Seq_append (Seq_singleton 4) Seq_empty)
    (Seq_singleton 4))))
(check-sat) ; unsat -- good!
(pop)

; assert Seq(4, 1) ++ Seq(1, 4) == Seq(4, 1, 1, 4)
(push)
(assert (not 
  (Seq_equal
    (Seq_append
      (Seq_append (Seq_singleton 4) (Seq_singleton 1))
      (Seq_append (Seq_singleton 1) (Seq_singleton 4)))
    (Seq_append
      (Seq_append
        (Seq_append (Seq_singleton 4) (Seq_singleton 1))
        (Seq_singleton 1))
      (Seq_singleton 4)))))
(check-sat) ; unsat -- good!
(pop)

; assert Seq(4) ++ Seq(1) == Seq(1, 4)
(push)
(assert (not (Seq_equal
  (Seq_append (Seq_singleton 4) (Seq_singleton 1))
  (Seq_append (Seq_singleton 1) (Seq_singleton 4)))))
(check-sat) ; unknown -- OK, since property doesn't hold
(pop)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM