Z3 solver introduction and finding all feasible solutions under specific constraints

Z3 solver

1. Introduction to Z3 Solver:

Z3 is a high-performance theorem prover developed by Microsoft Research Institute. Z3 is used in many applications, such as software / hardware verification and testing, constraint resolution, hybrid system analysis, safety, biology (in silicon analysis), and geometric problems.

2.Z3 solver ubuntu installation:

git clone https://github.com/Z3Prover/z3.git
cd z3
python scripts/mk_make.py
cd build
make
sudo make install

3.Z3 example analysis:

**Example 1: * * the function Int('x ') creates an integer variable named X in Z3. The following example uses two variables X and Y and three constraints. Z3Py uses = to assign values like Python. Operators <, < =, >, > =, = = and= For comparison. In the following example, the expression x + 2*y == 7 is a Z3 constraint. Z3 can solve and calculate the formula.

x = Int('x')
y = Int('y')
solve(x > 2, y < 10, x + 2*y == 7)

**Example 2: * * function of traversal expression

x = Int('x')
y = Int('y')
n = x + y >= 3
print "num args: ", n.num_args()
print "children: ", n.children()
print "1st child:", n.arg(0)
print "2nd child:", n.arg(1)
print "operator: ", n.decl()
print "op name:  ", n.decl().name()

**Example 3: * * the following example demonstrates a common error. Expression 3 / 2 is a Python integer, not a Z3 rational number. The example also shows different ways to create rational numbers in Z3Py. Process Q(num, den) creates a Z3 rational function, where num is the numerator and den is the denominator. RealVal(1) creates a Z3 real number that represents the number 1.

print 1/3
print RealVal(1)/3
print Q(1,3)

x = Real('x')
print x + 1/3
print x + Q(1,3)
print x + "1/3"
print x + 0.25

4. Boolean logic

Z3 supports Boolean operators: And, Or, Not, Implies (implication), if (if - then else). Use equal = = for double meaning. The following example shows how to solve a simple set of Boolean constraints.

p = Bool('p')
q = Bool('q')
r = Bool('r')
solve(Implies(p, q), r == Not(q), Or(Not(p), r))

**Example 1: * * use a combination of polynomial and Boolean constraints

p = Bool('p')
x = Real('x')
solve(Or(x < 5, x > 10), Or(p, x**2 == 2), Not(p))

5. Solver (Solvers)

Z3 provides different solvers. The solve command used in the previous example is implemented using the Z3 solver API. This implementation can be found in the file Z3. Py in the Z3 distribution.

**Example 1. * * the following example demonstrates the basic Solver API.

x = Int('x')
y = Int('y')

s = Solver()
print s

s.add(x > 10, y == x + 2)
print s
print "Solving constraints in the solver s ..."
print s.check()

print "Create a new scope..."
s.push()
s.add(y < 11)
print s
print "Solving updated set of constraints..."
print s.check()

print "Restoring state..."
s.pop()
print s
print "Solving restored set of constraints..."
print s.check()

Code parsing: the command Solver() creates a general solver. Constraints can be added using the add method. We say that the constraint has been interrupted in the solver. The check() method resolves the constraint of assertions. If you find out, the result is satisfactory. If there is no solution, the result is unsatisfactory. We can also say that the system of assertion constraints is infeasible. Finally, the solver may fail to solve the constraint system and return unknown. In some applications, we want to explore several similar problems with the same constraints. We can do this using the commands push and pop. Each solver maintains an assertion stack. The push command creates a new scope by saving the current stack size. The command pop-up pop-up will delete any assertions executed between it and the matching push. The check method always operates on the contents of the solver assertion stack.

Example 2. The following example shows how to traverse the constraints of the solver interrupt statement and how to collect performance statistics for the inspection method.

x = Real('x')
y = Real('y')
s = Solver()
s.add(x > 1, y > 1, Or(x + y > 3, x - y < 2))
print "asserted constraints..."
for c in s.assertions():
    print c

print s.check()
print "statistics for the last check method..."
print s.statistics()
# Traversing statistics
for k, v in s.statistics():
    print "%s : %s" % (k, v)

When Z3 finds a solution for the asserted constraint set, the command check returns sat. We say that Z3 satisfies the constraint set. We say that this solution is a model of assertion constraint set. A model is an interpretation that holds the constraints of each assertion. The following example shows the basic method of checking the model.

x, y, z = Reals('x y z')
s = Solver()
s.add(x > 1, y > 1, x + y > 3, z - x < 10)
print s.check()

m = s.model()
print "x = %s" % m[x]

print "traversing model..."
for d in m.decls():
    print "%s = %s" % (d.name(), m[d])

In the above example, the function Reals('x, y, Z ') creates a variable. x y z, which can be abbreviated as:

x = Real('x')
y = Real('y')
z = Real('z')

The expression m [x] returns m in the interpretation model of X. The expression "% s =% s"% (d.name(), m [d]) returns a string. The first% s is replaced with the name of D (for example, d.name()), and the second text represents the interpretation of% s d (i.e., m [d]). Z3Py automatically converts Z3 objects to text representations when needed.

6. Arithmetic

Z3 supports real and integer variables. They can be mixed in one problem. Like most programming languages, Z3Py automatically adds mandatory variables when needed to convert integer expressions to actual expressions. The following examples demonstrate different methods of declaring integer and real variables.

Example 1:

x = Real('x')
y = Int('y')
a, b, c = Reals('a b c')
s, r = Ints('s r')
print x + y + 1 + (a + s)
print ToReal(y) + c

The function ToReal casts an integer expression to a real expression.

7. Satisfiability and effectiveness

Formula / constraint f is valid if f always assigns true to any appropriate value of its unexplained symbol. A formula / constraint f is satisfied, and F is calculated to be true under this condition if an appropriate value is assigned to its unexplained symbol. Validity is about finding a statement; satisfiability is about finding a solution to a set of constraints. Consider a formula F containing a and b. we can Ask whether f is valid and whether it is always so that it can be arbitrarily combined into the values of a and b. if f is always correct, then no (F) is always false, and then no (F) will not have any satisfactory job (for example, solution); that is, Not(F) is unsatisfiable. That is, f is valid just when Not(F) is unsatisfiable. In addition, if and only if Not(F) is invalid (invalid) The following example proves De Morgan's law.

The following example redefines the Z3Py function, which takes the formula as a parameter. This function creates a solver, adds / asserts the negation of the formula, and checks whether the negation is unsatisfiable. The implementation of this function is a simpler version of the Z3Py command prove.

p, q = Bools('p q')
demorgan = And(p, q) == Not(Or(Not(p), Not(q)))
print demorgan

def prove(f):
    s = Solver()
    s.add(Not(f))
    if s.check() == unsat:
        print "proved"
    else:
        print "failed to prove"

print "Proving demorgan..."
prove(demorgan)

8. Sudoku

Sudoku is a very popular puzzle game. The goal is to insert numbers in the box to meet a condition: each row, column and 3x3 box must contain numbers from 1 to 9 exactly once.

The following example encodes the Sudoku problem in Z3. Different Sudoku instances can be solved by modifying the matrix instance. This example uses a lot of list derivation in Python programming language.

# 9x9 matrix of integer variables
X = [ [ Int("x_%s_%s" % (i+1, j+1)) for j in range(9) ]
      for i in range(9) ]

# each cell contains a value in {1, ..., 9}
cells_c = [ And(1 <= X[i][j], X[i][j] <= 9)
             for i in range(9) for j in range(9) ]

# each row contains a digit at most once
rows_c = [ Distinct(X[i]) for i in range(9) ]

# each column contains a digit at most once
cols_c = [ Distinct([ X[i][j] for i in range(9) ])
             for j in range(9) ]

# each 3x3 square contains a digit at most once
sq_c = [ Distinct([ X[3*i0 + i][3*j0 + j]
                        for i in range(3) for j in range(3) ])
             for i0 in range(3) for j0 in range(3) ]

sudoku_c = cells_c + rows_c + cols_c + sq_c

# sudoku instance, we use '0' for empty cells
instance = ((0,0,0,0,9,4,0,3,0),
            (0,0,0,5,1,0,0,0,7),
            (0,8,9,0,0,0,0,4,0),
            (0,0,0,0,0,0,2,0,8),
            (0,6,0,2,0,1,0,5,0),
            (1,0,2,0,0,0,0,0,0),
            (0,7,0,0,0,0,5,2,0),
            (9,0,0,0,6,5,0,0,0),
            (0,4,0,9,7,0,0,0,0))

instance_c = [ If(instance[i][j] == 0,
                  True,
                  X[i][j] == instance[i][j])
               for i in range(9) for j in range(9) ]

s = Solver()
s.add(sudoku_c + instance_c)
if s.check() == sat:
    m = s.model()
    r = [ [ m.evaluate(X[i][j]) for j in range(9) ]
          for i in range(9) ]
    print_matrix(r)
else:
    print "failed to solve"

Because the model() method can only output the model of the last check() method, we cannot directly obtain all qualified models
Therefore, conditions are added with the help of while loop and Or statement to obtain all qualified model results. The following uses while loop to find all solutions that meet the constraint conditions as an example:

a = Int('a')
b = Int('b')
s = Solver()
s.add(1 <= a)
s.add(a <= 20)
s.add(1 <= b)
s.add(b <= 20)
s.add(a >= 2*b)
while s.check() == sat:
  print (s.model())
  s.add(Or(a != s.model()[a], b != s.model()[b]))


Tags: Python Back-end

Posted on Sun, 05 Dec 2021 14:57:22 -0500 by buttercupgreen