Avoiding quantifiers in Z3 - z3

I am experimenting with Z3 where I combine the theories of arithmetic, quantifiers and equality. This does not seem to be very efficient, in fact it seems to be more efficient to replace the quantifiers with all instantiated ground instances when possible. Consider the following example, in which I have encoded the unique names axiom for a function f that takes two arguments of sort Obj and returns an interpreted sort S. This axiom states that each unique list of arguments to f returns a unique object:
(declare-datatypes () ((Obj o1 o2 o3 o4 o5 o6 o7 o8)))
(declare-sort S 0)
(declare-fun f (Obj Obj) S)
(assert (forall ((o11 Obj) (o12 Obj) (o21 Obj) (o22 Obj))
(=>
(not (and (= o11 o21) (= o12 o22)))
(not (= (f o11 o12) (f o21 o22))))))
Although this is a standard way of defining such an axiom in logic, implementing it like this is computationally very expensive. It contains 4 quantified variables, which each can have 8 values. This means that this results in 8^4 = 4096 equalities. It takes Z3 0.69s and 2016 quantifier instantiations to prove this. When I write a simple script that generates the instances of this formula:
(assert (distinct (f o1 o1) (f o1 o2) .... (f o8 o7) (f o8 o8)))
It takes 0.002s to generate these axioms, and another 0.01s (or less) to prove it in Z3. When we increase the objects in the domain, or the number of arguments to the function f this different increases rapidly, and the quantified case quickly becomes unfeasible.
This makes me wonder: when we have a bounded domain, why would we use quantifiers in Z3 in the first place? I know that SMT uses heuristics to find solutions, but I get the feeling that it still cannot compete in efficiency with a simple domain-specific grounder that feeds the grounded instances to SMT, which is then nothing more than SAT solving. Is my intuition correct?

Your intuition is correct. The heuristics for handling quantifiers in Z3 are not tuned for problems where universal variables range over finite/bounded domains.
In this kind of problem, using quantifiers is a good option only if a very small percentage of the instances are needed to show that a problem is unsatisfiable.
I usually suggest that users should expand this quantifiers using the programmatic API.
Here a two related posts. They contain links to Python code that implements this approach.
Does Z3 take a longer time to give an unsat result compared to a sat result?
Quantifier Vs Non-Quantifier
Here is one of the code fragments:
VFunctionAt = Function('VFunctionAt', IntSort(), IntSort(), IntSort())
s = Solver()
s.add([VFunctionAt(V,S) >= 0 for V in range(1, 5) for S in range(1, 9)])
print s
In this example, I'm essentially encoding forall V in [1,4] S in [1,8] VFunctionAt(V,S) >= 0.
Finally, your encoding (assert (distinct (f o1 o1) (f o1 o2) .... (f o8 o7) (f o8 o8)) is way more compact than expanding the quantifier 4096 times. However, even if we use a naive encoding (just expand the quantifier 4096 times), it is stil faster to solve the expanded version.

Related

Solving predicate calculus problems with Z3 SMT

I'd like to use Z3 to solve problems that are most naturally expressed in terms of atoms (symbols), sets, predicates, and first order logic. For example (in pseudocode):
A = {a1, a2, a3, ...} # A is a set
B = {b1, b2, b3...}
C = {c1, c2, c3...}
def p = (a:A, b:B, c:C) -> Bool # p is unspecified predicate
def q = (a:A, b:B, c:C) -> Bool
# Predicates can be defined in terms of other predicates:
def teaches = (a:A, b:B) -> there_exists c:C
such_that [ p(a, b, c) OR q(a, b, c) ]
constraint1 = forall b:B there_exists a:A
such_that teaches(a, b)
solve(constraint1)
What are good ways to express atoms, sets, predicates, relations, and first order quantifiers in Z3 (or other SMTs)?
Is there a standard idiom for this? Must it be done manually? Is there perhaps a translation library (not necessarily specific to Z3) that can convert them?
I believe Alloy uses SMT to implement predicate logic and relations, but Alloy seems designed more for interactive use to explore consistency of models, rather than to find specific solutions for problems.
"Alloy seems designed more for interactive use to explore consistency of models, rather than to find specific solutions for problems."
IMHO, Alloy shines when it comes to validate your own way of thinking. You model something and through the visualization of several instances you can sometime come to realize that what you modeled is not exactly what you'd have hoped for.
In that sense, I agree with you.
Yet, Alloy can also be used to find specific solutions to problems. You can overload a model with constraints so that only one instance can be found (i.e. your solution).
It works also quite well when your domain space remains relatively small.
Here's your model translated in Alloy :
sig A,B,C{}
pred teaches(a:A,b:B) {
some c:C | a->b->c in REL.q or a->b->c in REL.p}
// I'm a bit rusted, so .. that's my unelegant take on defining an "undefined predicate"
one sig REL {
q: A->B ->C,
p: A->B->C
}
fact constraint1 {
all b:B | some a:A | teaches[a,b]
}
run{}
If you want to define the atoms in sets A,B,C yourself and refer to them in predicates you could always over-constraint this model as follows:
abstract sig A,B,C{}
one sig A1,A2 extends A{}
one sig B1 extends B{}
one sig C1,C2,C3 extends C{}
pred teaches(a:A,b:B) {
some c:C | a->b->c in REL.q or a->b->c in REL.p}
one sig REL {
q: A->B ->C,
p: A->B->C
}{
// here you could for example define the content of p and q yourself
q= A1->B1->C2 + A2 ->B1->C3
p= A1->B1->C3 + A1 ->B1->C2
}
fact constraint1 {
all b:B | some a:A | teaches[a,b]
}
run{}
Modeling predicate logic in SMTLib is indeed possible; though it might be a bit cumbersome compared to a regular theorem prover like Isabelle/HOL etc. And interpreting the results can require a fair amount of squinting.
Having said that, here's a direct encoding of your sample problem using SMTLib:
(declare-sort A)
(declare-sort B)
(declare-sort C)
(declare-fun q (A B C) Bool)
(declare-fun p (A B C) Bool)
(assert (forall ((b B))
(exists ((a A))
(exists ((c C)) (or (p a b c) (q a b c))))))
(check-sat)
(get-model)
A few notes:
declare-sort creates an uninterpreted sort. It's essentially a non-empty set of values. (Can be infinite as well, there are no cardinality assumptions made, aside from the fact that it's not empty.) For your specific problem, it doesn't seem to matter what this sort actually is since you didn't use any of its elements directly. If you do so, you might also want to try a "declared" sort, i.e., a data-type declaration. This can be an enumeration, or something even more complicated; depending on the problem. For the current question as posed, an uninterpreted sort works just fine.
declare-fun tells the solver that there's an uninterpreted function with that name and the signature. But otherwise it neither defines it, nor constrains it in any way. You can add "axioms" about them to be more specific on how they behave.
Quantifiers are supported, as you see with forall and exists in how your constraint1 is encoded. Note that SMTLib isn't that suitable for code-reuse, and one usually programs in a higher-level binding. (Bindings from C/C++/Java/Python/Scala/O'Caml/Haskell etc. are provided, with similar but varying degrees of support and features.) Otherwise, it should be easy to read.
We finally issue check-sat and get-model, to ask the solver to create a universe where all the asserted constraints are satisfied. If so, it'll print sat and will have a model. Otherwise, it'll print unsat if there's no such universe; or it can also print unknown (or loop forever!) if it cannot decide. Use of quantifiers are difficult for SMT solvers to deal with, and heavy use of quantifiers will no doubt lead to unknown as the answer. This is an inherent limitation of the semi-decidability of first-order predicate calculus.
When I run this specification through z3, I get:
sat
(
;; universe for A:
;; A!val!1 A!val!0
;; -----------
;; definitions for universe elements:
(declare-fun A!val!1 () A)
(declare-fun A!val!0 () A)
;; cardinality constraint:
(forall ((x A)) (or (= x A!val!1) (= x A!val!0)))
;; -----------
;; universe for B:
;; B!val!0
;; -----------
;; definitions for universe elements:
(declare-fun B!val!0 () B)
;; cardinality constraint:
(forall ((x B)) (= x B!val!0))
;; -----------
;; universe for C:
;; C!val!0 C!val!1
;; -----------
;; definitions for universe elements:
(declare-fun C!val!0 () C)
(declare-fun C!val!1 () C)
;; cardinality constraint:
(forall ((x C)) (or (= x C!val!0) (= x C!val!1)))
;; -----------
(define-fun q ((x!0 A) (x!1 B) (x!2 C)) Bool
(and (= x!0 A!val!0) (= x!2 C!val!0)))
(define-fun p ((x!0 A) (x!1 B) (x!2 C)) Bool
false)
)
This takes a bit of squinting to understand fully. The first set of values tell you how the solver constructed a model for the uninterpreted sorts A, B, and C; with witness elements and cardinality constraints. You can ignore this part for the most part, though it does contain useful information. For instance, it tells us that A is a set with two elements (named A!val!0 and A!val!1), so is C, and B only has one element. Depending on your constraints, you'll get different sets of elements.
For p, we see:
(define-fun p ((x!0 A) (x!1 B) (x!2 C)) Bool
false)
This means p always is False; i.e., it's the empty set, regardless of what the arguments passed to it are.
For q we get:
(define-fun q ((x!0 A) (x!1 B) (x!2 C)) Bool
(and (= x!0 A!val!0) (= x!2 C!val!0)))
Let's rewrite this a little more simply:
q (a, b, c) = a == A0 && c == C0
where A0 and C0 are the members of the sorts A and C respectively; see the sort declarations above. So, it says q is True whenever a is A0, c is C0, and it doesn't matter what b is.
You can convince yourself that this model does indeed satisfy the constraint you wanted.
To sum up; modeling these problems in z3 is indeed possible, though a bit clumsy and heavy use of quantifiers can make the solver loop-forever or return unknown. Interpreting the output can be a bit cumbersome, though you'll realize that the models will follow a similar schema: First the uninterpreted sorts, and then the the definitions for the predicates.
Side note
As I mentioned, programming z3 in SMTLib is cumbersome and error-prone. Here's the same program done using the Python interface:
from z3 import *
A = DeclareSort('A')
B = DeclareSort('B')
C = DeclareSort('C')
p = Function('p', A, B, C, BoolSort())
q = Function('q', A, B, C, BoolSort())
dummyA = Const('dummyA', A)
dummyB = Const('dummyB', B)
dummyC = Const('dummyC', C)
def teaches(a, b):
return Exists([dummyC], Or(p(a, b, dummyC), q(a, b, dummyC)))
constraint1 = ForAll([dummyB], Exists([dummyA], teaches(dummyA, dummyB)))
s = Solver()
s.add(constraint1)
print(s.check())
print(s.model())
This has some of its idiosyncrasies as well, though hopefully it'll provide a starting point for your explorations should you choose to program z3 in Python. Here's the output:
sat
[p = [else -> And(Var(0) == A!val!0, Var(2) == C!val!0)],
q = [else -> False]]
Which has the exact same info as the SMTLib output, though written slightly differently.
Function definition style
Note that we defined teaches as a regular Python function. This is the usual style in z3py programming, as the expression it produces gets substituted as calls are made. You can also create a z3-function as well, like this:
teaches = Function('teaches', A, B, BoolSort())
s.add(ForAll([dummyA, dummyB],
teaches(dummyA, dummyB) == Exists([dummyC], Or(p(dummyA, dummyB, dummyC), q(dummyA, dummyB, dummyC)))))
Note that this style of definition will rely on quantifier instantiation internally, instead of the general function-definition facilities of SMTLib. So, you should prefer the python function style in general as it translates to "simpler" internal constructs. It is also much easier to define and use in general.
One case where you need the z3 function definition style is if the function you're defining is recursive and its termination relies on a symbolic argument. For a discussion of this, see: https://stackoverflow.com/a/68457868/936310

forall usage in SMT

How does forall statement work in SMT? I could not find information about usage. Can you please simply explain this? There is an example from
https://rise4fun.com/Z3/Po5.
(declare-fun f (Int) Int)
(declare-fun g (Int) Int)
(declare-const a Int)
(declare-const b Int)
(declare-const c Int)
(assert (forall ((x Int))
(! (= (f (g x)) x)
:pattern ((g x)))))
(assert (= (g a) c))
(assert (= (g b) c))
(assert (not (= a b)))
(check-sat)
For general information on quantifiers (and everything else SMTLib) see the official SMTLib document:
http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf
Quoting from Section 3.6.1:
Exists and forall quantifiers. These binders correspond to the usual
universal and existential quantifiers of first-order logic, except
that each variable they quantify is also associated with a sort. Both
binders have a non-empty list of variables, which abbreviates a
sequential nesting of quantifiers. Specifically, a formula of the form
(forall ((x1 σ1) (x2 σ2) · · · (xn σn)) ϕ) (3.1) has the same
semantics as the formula (forall ((x1 σ1)) (forall ((x2 σ2)) (· · ·
(forall ((xn σn)) ϕ) · · · ) (3.2) Note that the variables in the list
((x1 σ1) (x2 σ2) · · · (xn σn)) of (3.1) are not required to be
pairwise disjoint. However, because of the nested quantifier
semantics, earlier occurrences of same variable in the list are
shadowed by the last occurrence—making those earlier occurrences
useless. The same argument applies to the exists binder.
If you have a quantified assertion, that means the solver has to find a satisfying instance that makes that formula true. For a forall quantifier, this means it has to find a model that the assertion is true for all assignments to the quantified variables of the relevant sorts. And likewise, for exists the model needs to be able to exhibit a particular value satisfying the assertion.
Top-level exists quantifiers are usually left-out in SMTLib: By skolemization, declaring a top-level variable fills that need, and also has the advantage of showing up automatically in models. (That is, any top-level declared variable is automatically existentially quantified.)
Using forall will typically make the logic semi-decidable. So, you're likely to get unknown as an answer if you use quantifiers, unless some heuristic can find a satisfying assignment. Similarly, while the syntax allows for nested quantifiers, most solvers will have very hard time dealing with them. Patterns can help, but they remain hard-to-use to this day. To sum up: If you use quantifiers, then SMT solvers are no longer decision-procedures: They may or may not terminate.
If you are using the Python interface for z3, also take a look at: https://ericpony.github.io/z3py-tutorial/advanced-examples.htm. It does contain some quantification examples that can clarify things for you. (Even if you don't use the Python interface, I heartily recommend going over that page to see what the capabilities are. They more or less translate to SMTLib directly.)
Hope that gets you started. Stack-overflow works the best if you ask specific questions, so feel free to ask clarification on actual code as you need.
Semantically, a quantifier forall x: T . e(x) is equivalent to e(x_1) && e(x_2) && ..., where the x_i are all the values of type T. If T has infinitely many (or statically unknown many) values, then it is intuitively clear that an SMT solver cannot simply turn a quantifier into the equivalent conjunction.
The classical approach in this case are patterns (also called triggers), pioneered by Simplify and available in Z3 and others. The idea is rather simple: users annotate a quantifier with a syntactical pattern that serves a heuristic for when (and how) to instantiate the quantifier.
Here is an example (in pseudo-code):
assume forall x :: {foo(x)} foo(x) ==> false
Here, {foo(x)} is the pattern, indicating to the SMT solver that the quantifier should be instantiated whenever the solver gets a ground term foo(something). For example:
assume forall x :: {foo(x)} foo(x) ==> 0 < x
assume foo(y)
assert 0 < y
Since the ground term foo(y) matches the trigger foo(x) when the quantified variable x is instantiated with y, the solver will instantiate the quantifier accordingly and learn 0 < y.
Patterns and quantfier triggering is difficult, though. Consider this example:
assume forall x :: {foo(x)} (foo(x) || bar(x)) ==> 0 < y
assume bar(y)
assert 0 < y
Here, the quantifier won't be instantiated because the ground term bar(y) does not match the chosen pattern.
The previous example shows that patterns can cause incompletenesses. However, they can also cause termination problems. Consider this example:
assume forall x :: {f(x)} !f(x) || f(f(x))
assert f(y)
The pattern now admits a matching loop, which can cause nontermination. Ground term f(y) allows to instantiate the quantifier, which yields the ground term f(f(y)). Unfortunately, f(f(y)) matches the trigger (instantiate x with f(y)), which yields f(f(f(y))) ...
Patterns are dreaded by many and indeed tricky to get right. On the other hand, working out a triggering strategy (given a set of quantifiers, find patterns that allow the right instantiations, but ideally not more than these) ultimately "only" required logical reasoning and discipline.
Good starting points are:
* https://rise4fun.com/Z3/tutorial/, section "Quantifiers"
* http://moskal.me/smt/e-matching.pdf
* https://dl.acm.org/citation.cfm?id=1670416
* http://viper.ethz.ch/tutorial/, section "Quantifiers"
Z3 also has offers Model-based Quantifier Instantiation (MBQI), an approach to quantifiers that doesn't use patterns. As far as I know, it is unfortunately also much less well documented, but the Z3 tutorial has a short section on MBQI as well.

∃-queries and ∀-queries with Z3 fixedpoint engine

I'm confused and struggling to understand how two different input formats for Z3 fixedpoint engine are related. Short example: suppose I want to prove the existance of negative numbers. I declare a function that returns 1 for non-negative numbers and 0 for negative and then asking solver to fail if there are arguments for which function returns 0. But there is one restriction: I want solver to respond sat when there exists at least one negative number and unsat if all numbers are non-negative.
It is trivially with using declare-rel and query format:
(declare-rel f (Int Int))
(declare-rel fail ())
(declare-var n Int)
(declare-var m Int)
(rule (=> (< n 0) (f n 0)))
(rule (=> (>= n 0) (f n 1)))
(rule (=> (and (f n m) (= m 0)) fail))
(query fail)
But it becomes tricky while using pure SMT-LIB2 format (with forall). For example, straightforward
(set-logic HORN)
(declare-fun f (Int Int) Bool)
(declare-fun fail () Bool)
(assert (forall ((n Int))
(=> (< n 0) (f n 0))))
(assert (forall ((n Int))
(=> (>= n 0) (f n 1))))
(assert (forall ((n Int) (m Int))
(=> (and (f n m) (= m 0)) fail)))
(assert (not fail))
(check-sat)
returns unsat. Unsurprisingly, changing (= m 0) to (= m 1) results the same. We can get sat only implying fail from (= m 2). The problem is that I can't understand, how to ask solver using this format.
How I'm understanding it at the moment, while using forall-form we can ask to find only ∀-solutions, i.e. the answer sat means that solver managed to find interpretation (or invariant) satisfiying all assertions for all values, and unsat means that there are no such functions. In other words, it tries to prove, putting the 'proof' (the invariant) into the model (obviously, when sat).
On the contrary, when querying the solution in the declare-rel format solver searches the solution for some variables, just like the constraints are under the ∃-quantifier. In other words, it gives the counter-example. It can only print the invariant in case of unsat.
I have a couple of questions:
Am I understanding it correct? I feel like I miss some key ideas. For example, a general idea of how to express (query ...) in terms of (assert (forall ...)) will be really helpfull (and will answer question 2 automaticly).
Is there a way to solve such ∃-constraints (outputting sat when counterexample was found) with pure SMT-LIB2 format? If yes then how?
First of all, the format that uses "declare-rel", "declare-var", "rule" and "query" is a custom extension to SMT-LIB2. The "declare-var" feature is convenient for omitting bound variables from multiple rules. It also allows formulating Datalog rules with stratified negation and the semantics of this is what you should expect from stratified negation. By convention it uses "sat" to indicate that a query has a derivation, and "unsat" that no derivation exists for a query.
It turns out that standard SMT-LIB2 can express pretty much what you want for
Horn clauses without negation. Rules become implications and queries are implications of the form: (=> query false), or as you wrote it (not query).
A derivation in the custom format corresponds to a proof of the empty clause (e.g., proof of "query", which then proves "false"). So existence of a derivation means that the SMT-LIB2 assertions are "unsat". Conversely, if there is an interpretation (a model) for the Horn clauses, then such a model establishes that there is no derivation. The clauses are "sat".
In other words:
"sat" for datalog extension <=> "unsat" for SMT-LIB2 formulation
"unsat" for datalog extension <=> "sat" for SMT-LIB2 formulation
The advantage of using the pure SMT-LIB2 format, when it applies, is that
there are no special syntax extensions. These are plain SMT formulas and
others who wish to solve this class of formulas don't have to write special
extensions, they just have to ensure that the solvers that are tuned to
Horn clauses recognize the appropriate class of formulas. (Z3's implementation
of the HORN fragment does allow some flexibility in writing down Horn clauses.
You can have disjunctions in the bodies and you can have Curried implications).
There is one drawback with using the SMT-LIB2 format that the rule-based format helps with: when there is a derivation of the query, then the rule-based format has pragmas for printing elements of a tuple. Note that in general the query relation can take arguments. This feature is useful for finite domain relations.
Your example above uses integers, so the relations are not finite domain, but examples in the online-tutorial contain finite domain instances.
Now a derivation of a query also corresponds to a resolution proof. You can extract a resolution proof from the SMT-LIB2 case, but I have to say it is rather
convoluted and I have not found a way to use it effectively. The "duality" engine for Horn clauses generates derivations in a more accessible format than
the default proof format of Z3. Either way, it is likely that users run into obstacles if they try to work with the proof certificates because they are rarely used. The rule-based format does have another feature that assembles a set of predicates with instances that correspond to a derivation trail. It is easier to eyeball this output.

Equivalent Quantifier Free Formulas

I want to know if Z3 can show equivalent formulas after Quantifier Elimination.
Example (exists k) (i x k) = 1 and k > 5
is equivalent to
i > 0 and 5 i - 1 < 0.
Here, quantifier k has been eliminated.
Is this possible?
Thanks,
Kaustubh.
Yes, Z3 can check whether two formulas are equivalent. To check whether p and q are equivalent. We must check whether (not (iff p q)) is unsatisfiable.
Your example uses nonlinear arithmetic i*k. The quantifier elimination module in Z3 has limited support for nonlinear real arithmetic. It is based on virtual term substitution, which is not complete. However, it is sufficient for your example.
We must enable the quantifier elimination module in Z3, and the nonlinear extensions (i.e., virtual term substitution).
Here is how we can encode your example in Z3: http://rise4fun.com/Z3/rXfi
In general, the result of eliminating quantifiers can be obtained. For instance by entering the following into rise4fun:
(declare-const i Real)
(assert (exists ((k Real)) (and (= (* i k) 1.0) (> k 5.0))))
(apply qe)
This case involves non-linear arithmetic, and Z3 does not eliminate the quantifier.

Symmetry breaking for Z3 -- in the context of the UFBV logic (new version)

(This is my second try to get help. If the question/approach do not make sense or not clear, please just let me know. I would also be pleased about any small hint or reference, which can help me to understand the behaviour of Z3 with my SBAs)
I am working on bounded verification of relational specification using the UFBV Z3 logic. The current problem I am investigating, needs the falsification of all possible models (because of a negative use of a reachability predicate), which kills the solver performance in higher bounds.
Because only a part of the possible models are indeed interesting (not isomorphic to others), I am trying to introduce symmetry breaking techniques (known in the SAT area).
However the use of what I call symmetry breaking axioms can improve the performance of Z3 in some cases, but the general, behaviour of the solver becomes instable.
One of my approaches (I think the most promising one), bases on breaking the symmetry on relations w.r.t. their domains. It introduces of each domains D of a relation R and each atom a \in D axioms, which enforce an order on the binary representation of R^{M} and R^{M[a+1/a]}, where M is a model for the specification. For homogeneous relations the axioms are relaxed.
Let be R \subset AxA a relation. My relaxed symmetry breaking axioms for R look like this:
;; SBA(R, A)_upToDiag
(assert
(forall ( (ai A) (aj A) )
(=>
(bvult ai aj)
(=>
(forall ((x A))
(=>
(bvult x aj)
(= (R ai x) (R (bvadd ai (_ bv1 n)) x))
)
)
(=>
(R ai aj)
(R (bvadd ai (_ bv1 n)) aj)
)))))
;; SBA(R, A)_diag
(assert
(forall ( (ai A) )
(=>
(forall ((x A))
(=>
(bvult x ai)
(= (R ai x) (R (bvadd ai (_ bv1 n)) x))
)
)
(=>
(R ai ai)
(R (bvadd ai (_ bv1 n)) (bvadd ai (_ bv1 n)))
))))
My problem is, that the effect of using this SBAs is not stable/consistent. It differs from bound to bound and form specification to another. Also the use of all or only one of the SBAs affects the performance.
In the SAT context the success of the so-called symmetry breaking predicate (SBP) approach bases on the backtracking capability of the SAT solver, which (somehow) guaranty, that if the solver back track, it will then prune the search space using, amongst others, the SBPs.
What is the differences (if any) in the context of Z3?
How can I enforce the solve to use these axioms to prune the search space (when it back track)?
Would the use of (quantifier) patterns for my SBAs helps?
Regards,
Aboubakr Achraf El Ghazi
In Z3 3.2, there are two main engines for handling quantified formulas: E-matching and MBQI (model based quantifier instantiation). E-matching is only effective in unsatisfiable formulas. Z3 will not be able to show that a formula is satisfiable using this engine. MBQI is more expensive, but it can show that several classes of formulas (containing quantifiers) are satisfiable. The Z3 guide describes these two engines (and other options). To use Z3 effectively on nontrivial problems, it is very useful to understand how these two engines work.
Symmetry breaking is usually very effective way to reduce the search space. It is hard to pinpoint exactly what is going on in your problem. I can see the following explanations for the non stable behavior:
MBQI is having a hard time creating a model that satisfies the SBAs. Although the SBAs prune the search space, if the problem is satisfiable, Z3 will try to build an interpretation (model) that satisfies them. So, in this case, the SBA is just overhead. This is particularly true, if the input formula is very easy to satisfy, but becomes hard when you add the SBAs. You can confirm this hypothesis by using the option MBQI_TRACE=true. Z3 will display messages such as: [mbqi] failed k!18. Where k![line-number] is the quantifier id. You can assign your own ids using the tag :qid. Here is an example:
(assert (forall ((x T) (y T)) (! (=> (and (subtype x y)
(subtype y x))
(= x y))
:qid antisymmetry)))
BTW, you can disable the MBQI module using MBQI=false.
In future versions of Z3, we are planning to add an option to disable MBQI for some quantified formulas. This feature may be useful for SBAs.
Another explanation is that E-matching is creating too many instances of the SBAs. You can confirm that using the option QI_PROFILE=true. Z3 will dump information such as:
[quantifier_instances] antisymmetry : 12 : 1 : 2.00
The first number is the number of generated instances. If that is the source of the problem, one solution is to assign restrictive patterns for the SBAs that are generating too many instances. For example, Z3 will use (R ai aj) as a pattern for SBA(R, A)_upToDiag. This kind of pattern may create a quadratic number of instances. Another experiment consists in disabling E-matching. Example, the option
AUTO_CONFIG=false EMATCHING=false MBQI=true
You may also try to disable relevancy propagation in the configuration above, option: RELEVANCY=0.
Finally, another option is to generate the instances of the SBAs that you believe are useful, and remove the quantified formulas.

Resources