Minimum and maximum values of integer variable - z3

Let's assume a very simple constraint: solve(x > 0 && x < 5).
Can Z3 (or any other SMT solver, or any other automatic technique)
compute the minimum and maximum values of (integer) variable x that satisfies the given constraints?
In our case, the minimum is 1 and the maximum is 4.

Z3 has not support for optimizing (maximizing/minimizing) objective functions or variables.
We plan to add this kind of capability, but it will not happen this year.
In the current version, we can "optimize" an objective function by solving several problems where in each iteration we add additional constraints. We know we found the optimal when the problem becomes unsatisfiable. Here is a small Python script that illustrates the idea. The script maximizes the value of a variable X. For minimization, we just have to replace s.add(X > last_model[X]) with s.add(X < last_model[X]). This script is very naive, it performs a "linear search". It can be improved in many ways, but it demonstrates the basic idea.
You can also try the script online at: http://rise4fun.com/Z3Py/KI1
See the following related question: Determine upper/lower bound for variables in an arbitrary propositional formula
from z3 import *
# Given formula F, find the model the maximizes the value of X
# using at-most M iterations.
def max(F, X, M):
s = Solver()
s.add(F)
last_model = None
i = 0
while True:
r = s.check()
if r == unsat:
if last_model != None:
return last_model
else:
return unsat
if r == unknown:
raise Z3Exception("failed")
last_model = s.model()
s.add(X > last_model[X])
i = i + 1
if (i > M):
raise Z3Exception("maximum not found, maximum number of iterations was reached")
x, y = Ints('x y')
F = [x > 0, x < 10, x == 2*y]
print max(F, x, 10000)

As Leonardo pointed out, this was discussed in detail before: Determine upper/lower bound for variables in an arbitrary propositional formula. Also see: How to optimize a piece of code in Z3? (PI_NON_NESTED_ARITH_WEIGHT related).
To summarize, one can either use a quantified formula, or go iteratively. Unfortunately, these techniques are not equivalent:
Quantified approach needs no iteration, and can find global min/max in a single call to the solver; at least in theory. However, it does give rise to harder formulas. So, the backend solver can time-out, or simply return "unknown".
Iterative approach creates simple formulas for the backend solver to deal with, but it can loop forever if there's no optimal value; simplest example being trying to find the largest Int value. Quantified version can solve this problem nicely by quickly telling you that there is no such value, while the iterative version would go on indefinitely. This can be a problem if you don't know ahead of time that your constraints do have an optimal solution. (Needless to say, the "sufficient" iteration count is typically hard to guess, and might depend on random factors, like the seed used by the solver.)
Also keep in mind that if there is a custom optimization algorithm for the problem domain at hand, it's unlikely that a general purpose SMT solver can outperform it.

z3 now supports optimization.
from z3 import *
o = Optimize()
x = Int( 'x' )
o.add(And(x > 0, x < 5))
o.maximize(x)
print(o.check()) # prints sat
print(o.model()) # prints [x = 4]
This particular problem is an integer program.

Related

Does Z3 have troubles with division?

During a project, I encountered a phenomenon I can't explain. It is boiled down in the following minimal example (using Z3Py) which should be unsat but returns a model.
from z3 import *
solver = Solver()
x = Int("x")
# The next proposition is clearly unsatisfiable
solver.add(And(x > 0, (x + 1) * (1 / (x + 1)) != 1))
solver.add(x == 1)
# Still, Z3 provides a "model"
print(solver.check())
print(solver.model())
Is there anything I am missing? Is it forbidden to use division in certain situations?
Z3 version: 4.8.14
Python version: 3.9
Note that when you use / over integers, than the result is an integer itself. That is, we use integer division; which in SMTLib is defined to be the Euclidian one. See https://smtlib.cs.uiowa.edu/theories-Ints.shtml for details.
This means that the expression 1 / (1+1) reduces to 0, since division produces an integer. So you end up with 0 != 1, which is true, and thus your problem is satisfiable.
If you want division to produce reals, then you should either start with a real x (i.e., x = Real("x")) in which case your problem will be unsat; or convert the arguments to be over the reals, via an expression of the form ToReal(x); which again will produce unsat for your problem.
So, in the first case, your modification will be:
x = Real('x')
casting the problem over reals. Or, if you want to keep x an integer, yet the division to be over the reals, you should use:
solver.add(And(x > 0, ToReal(x + 1) * (1 / ToReal(x + 1)) != 1))
which does the conversion.
I should note that mixing integers and reals in this way can lead to hard problems in general, and the solver might end up reporting unknown. But that's besides the point for your particular question here.

Is there a way to get the final system of equations sent by cvxpy to the solver?

If I understand correctly, cvxpy converts our high-level problem description to the standard canonical form before it is sent to a solver.
By the standard form I mean the form that can be used for the descent algorithms, so, for instance, it would convert all the absolute values in the objective to be a difference of two positive numbers with some new constraints, etc.
Wondering if its possible to see what the reduction looked like for a problem I specify in cvxpy?
For instance, lets say I have the following problem:
import numpy as np
import cvxpy as cp
x = cp.Variable(2)
L = np.asarray([[1,2],[2,3]])
P = L.T # L
constraints = []
constraints.append(x >= [-10, -10])
constraints.append(x <= [10, 10])
obj = cp.Minimize(cp.quad_form(x, P) - [1, 2] * x)
prob = cp.Problem(obj, constraints)
prob.solve(), prob.solver_stats.solver_name
(-0.24999999999999453, 'OSQP')
So, I would like to see the actual arguments (P, q, A, l, u) being sent to the OSQP solver https://github.com/oxfordcontrol/osqp-python/blob/master/module/interface.py#L278
Any help is greatly appreciated!
From looking at the documentation, it seems you can do this using the command get_problem_data as follows:
data, chain, inverse_data = prob.get_problem_data(prob.solver_stats.solver_name)
I have not tried it, and it says it output depends on the particular solver and the solver chain, but it may help you!

Do you know how to set "weak" initial values to each of z3 Array element?

For example, is there a something like below weak_Store function in Z3?
from z3 import *
a = Array('a', IntSort(), IntSort())
a = weak_Store(a, 0, 0)
a = weak_Store(a, 1, 1)
s = Solver()
s.add(a[0] == 100)
print(s.check()) # should print "sat"
print(s.model().eval(a[0])) # should print "100"
print(s.model().eval(a[1])) # should print "1" which is stored as weak_Store.
Since a[1] is not involved in the above constraint solving, this should not be computed and changed even after s.check().
I think this is related to model_completion variable in z3_model_eval,
but z3_model_eval does not work for z3 Array element.
Although the example is written in Python, I would like to do it with z3 C api.
Can anybody help me?
Thank you in advance.
This sort of constraints are called "soft" constraints, i.e., those that can be violated if necessary, but the solver will try to satisfy them otherwise. Note that you have to use the Optimize object, (not Solver), which has diminished capacity in general. (i.e., slower, more likely to say unknown etc.)
To add a soft constraint, use add_soft instead of add in z3py. You can code your example in Python like this:
from z3 import *
a = Array('a', IntSort(), IntSort())
s = Optimize()
s.add_soft(a[0] == 0)
s.add_soft(a[1] == 1)
s.add(a[0] == 100)
print(s.check())
print(s.model().eval(a[0]))
print(s.model().eval(a[1]))
This prints:
sat
100
1
as you requested.
In the C-API, the corresponding call is Z3_optimize_assert_soft.

Normal form of formula returned by Z3's qe tactic

I'm using Z3's quantifier elimination tactic via Z3py and have tried the following examples.
from z3 import *
x,y,xp,yp = Ints('x y xp yp')
t = Tactic('qe')
t(Exists((xp, yp), And(xp==x+1, yp==y+2, xp<=8, xp >=1, yp<=12, yp>=2)))
#returns: [[y <= 10, y >= 0, x <= 7, x >= 0]]
t(Exists((xp, yp), Implies(x<10 , And(xp==x+1, yp==y+2, xp<=8, xp >=1, yp<=12, yp>=2))))
#returns: [[Or(10 <= x, And(y <= 10, y >= 0, And(x <= 7, x >= 0)))]]
I think that the resultant formulas are in quantifier-free DNF(which is what I need), but I could not find anything in the API documentation that guarantees it. Does anyone know if qe always returns formulas in DNF?
Where can I(if at all) find such details regarding tactics without having to dig through the original source code?
EDIT: All formulas are restricted to linear integer arithmetic.
By design, tactics make "best effort." That is, while qe is designed to eliminate quantifiers, it may end up failing to do so, returning the goal stack unchanged.
Note that quantifier elimination is not just one tactic, but it is a whole collection of them, depending on what other theories are involved in your benchmark. See the directory: https://github.com/Z3Prover/z3/tree/master/src/qe

How to get z3 to return multiple unsat cores, multiple satisfying assignments

I am working on a component of a research tool;
I am interested in retrieving (for QF_LRA)
-multiple (minimal or otherwise) UNSAT cores and
-multiple SAT assignments
I have checked the forum for earlier discussions on this topic e.g.,
How to get different unsat cores when using z3 on logic QF_LRA
They refer to the z3 Python tutorial(s)
e.g, http://rise4fun.com/Z3Py/tutorial/musmss
which seems to be offline for now. I have tried other suggestions of github etc to find the mentioned tutorial, but have had no luck.
I am using the z3 Java API; but happy to switch to alternatives.
Here is the tutorial. You can find more information on MARCO
from Mark Liffiton's web pages.
Enumeration of Minimal Unsatisfiable Cores and Maximal Satisfying Subsets
This tutorial illustrates how to use Z3 for extracting all minimal unsatisfiable cores
together with all maximal satisfying subsets.
Origin
The algorithm that we describe next
represents the essence of the core extraction procedure by Liffiton and Malik and independently
by Previti and Marques-Silva:
Enumerating Infeasibility: Finding Multiple MUSes Quickly
Mark H. Liffiton and Ammar Malik
in Proc. 10th International Conference on Integration of Artificial
Intelligence (AI) and Operations Research (OR) techniques in Constraint Programming (CPAIOR-2013), 160-175, May 2013.
Partial MUS Enumeration
Alessandro Previti, Joao Marques-Silva
in Proc. AAAI-2013 July 2013
Z3py Features
This implementation contains no tuning.
It was contributed by Mark Liffiton and it is a simplification of one of the versions available from
his Marco Polo Web site.
Code for eMUS is also available.
The example illustrates the following features of Z3's Python-based API:
Using assumptions to track unsatisfiable cores.
Using multiple solvers and passing constraints between them.
Calling the C-based API from Python. Not all API functions are supported over the Python
wrappers. This example shows how to get a unique integer identifier of an AST,
which can be used as a key in a hash-table.
Idea of the Algorithm
The main idea of the algorithm is to maintain two
logical contexts and exchange information between them:
The MapSolver is used to enumerate sets of clauses that are not already
supersets of an existing unsatisfiable core and not already a subset of a maximal satisfying assignment.
The MapSolver uses one unique atomic predicate per soft clause, so it enumerates
sets of atomic predicates. For each minimal unsatisfiable core, say, represented by predicates
p1, p2, p5, the MapSolver contains the
clause ¬ p1 ∨ ¬ p2 ∨ ¬ p5.
For each maximal satisfiable subset, say, represented by predicats
p2, p3, p5, the
MapSolver contains a clause corresponding to the disjunction of all literals
not in the maximal satisfiable subset, p1 ∨ p4 ∨ p6.
The SubsetSolver contains a set
of soft clauses (clauses with the unique indicator atom occurring negated).
The MapSolver feeds it a set of clauses (the indicator atoms).
Recall that these are not already a superset of an existing minimal
unsatisfiable core, or a subset of a maximal satisfying assignment.
If asserting these atoms makes the SubsetSolver context infeasible,
then it finds a minimal unsatisfiable subset corresponding to these atoms.
If asserting the atoms is consistent with the SubsetSolver, then
it extends this set of atoms maximally to a satisfying set.
from Z3 import *
def main():
x, y = Reals('x y')
constraints = [x > 2, x < 1, x < 0, Or(x + y > 0, y < 0), Or(y >= 0, x >= 0), Or(y < 0, x < 0), Or(y > 0, x < 0)]
csolver = SubsetSolver(constraints)
msolver = MapSolver(n=csolver.n)
for orig, lits in enumerate_sets(csolver, msolver):
output = "%s %s" % (orig, lits)
print(output)
def get_id(x):
return Z3_get_ast_id(x.ctx.ref(),x.as_ast())
def MkOr(clause):
if clause == []:
return False
else:
return Or(clause)
SubsetSolver:
class SubsetSolver:
constraints = []
n = 0
s = Solver()
varcache = {}
idcache = {}
def __init__(self, constraints):
self.constraints = constraints
self.n = len(constraints)
for i in range(self.n):
self.s.add(Implies(self.c_var(i), constraints[i]))
def c_var(self, i):
if i not in self.varcache:
v = Bool(str(self.constraints[abs(i)]))
self.idcache[get_id(v)] = abs(i)
if i >= 0:
self.varcache[i] = v
else:
self.varcache[i] = Not(v)
return self.varcache[i]
def check_subset(self, seed):
assumptions = self.to_c_lits(seed)
return (self.s.check(assumptions) == sat)
def to_c_lits(self, seed):
return [self.c_var(i) for i in seed]
def complement(self, aset):
return set(range(self.n)).difference(aset)
def seed_from_core(self):
core = self.s.unsat_core()
return [self.idcache[get_id(x)] for x in core]
def shrink(self, seed):
current = set(seed)
for i in seed:
if i not in current:
continue
current.remove(i)
if not self.check_subset(current):
current = set(self.seed_from_core())
else:
current.add(i)
return current
def grow(self, seed):
current = seed
for i in self.complement(current):
current.append(i)
if not self.check_subset(current):
current.pop()
return current
MapSolver:
class MapSolver:
def __init__(self, n):
"""Initialization.
Args:
n: The number of constraints to map.
"""
self.solver = Solver()
self.n = n
self.all_n = set(range(n)) # used in complement fairly frequently
def next_seed(self):
"""Get the seed from the current model, if there is one.
Returns:
A seed as an array of 0-based constraint indexes.
"""
if self.solver.check() == unsat:
return None
seed = self.all_n.copy() # default to all True for "high bias"
model = self.solver.model()
for x in model:
if is_false(model[x]):
seed.remove(int(x.name()))
return list(seed)
def complement(self, aset):
"""Return the complement of a given set w.r.t. the set of mapped constraints."""
return self.all_n.difference(aset)
def block_down(self, frompoint):
"""Block down from a given set."""
comp = self.complement(frompoint)
self.solver.add( MkOr( [Bool(str(i)) for i in comp] ) )
def block_up(self, frompoint):
"""Block up from a given set."""
self.solver.add( MkOr( [Not(Bool(str(i))) for i in frompoint] ) )
def enumerate_sets(csolver, map):
"""Basic MUS/MCS enumeration, as a simple example."""
while True:
seed = map.next_seed()
if seed is None:
return
if csolver.check_subset(seed):
MSS = csolver.grow(seed)
yield ("MSS", csolver.to_c_lits(MSS))
map.block_down(MSS)
else:
MUS = csolver.shrink(seed)
yield ("MUS", csolver.to_c_lits(MUS))
map.block_up(MUS)
main()

Resources