简体   繁体   中英

How to use Z3py and Sympy together

I am trying to perform some symbolic calculation on matrices (with symbols as an entries of matrices), and after that I will have a number of possible solution. My goal is to select solutions/ solution based upon constraints.

for example, M is a matrix which have one element as a symbol . This matrix will have 2 eigenvalues one is positive an one is negative. Using z3 I am trying to find out only negative value, but I am unable to do so as a is defined as a symbol and I cannot write it as a constraint unless I convert it to a real value.

what should I do? is there any way to convert a (symbol) in to real or integer so that I can use it as a constraint s.add(a>0)

from sympy import* 
from z3 import* 
from math import*

a=Symbol('a')

M=Matrix([[a,2],[3,4]]) m=M.eigenvals();

s=Solver()

s.add(m<0)
print(s.check())
model = s.model() print(model)

One possibility is to convert sympy expressions to stings, modify them to represent z3 expressions, then call python's eval to evaluate them to z3 expressions. More precisely:

  1. Convert your sympy expressions to strings. You can generate the strings from sympy expressions by simply calling Python's str().
  2. Append the inequality / equality sign to each string (' > 0', ' < 0', ' >= 0 ', or ' <= 0').
  3. Convert all mismatches on the strings from sympy to z3. For example, change all occurrences of "sqrt" to "z3.Sqrt".
  4. Call Python's eval(), which accepts those strings and evaluates them to z3 expressions.
  5. Now you are in z3's world.

Below is a function that I wrote to convert a list of strings from sympy expressions to a system of inequalities in z3.

import z3
import sympy

###################################
def sympy_to_z3(str_ineq_list, syms):
# converts a list of strings representing sympy expressions (inequalities)
# to a conjunction of z3 expressions in order to be processed by the solver
system_str = 'z3.And('
for str_ineq in str_ineq_list:
    system_str += str_ineq.replace('sqrt', 'z3.Sqrt') + ', '
system_str += ')'
for sym in syms:
    # this initializes the symbols (x1, x2,..) as real variables
    exec(str(sym) + ', = z3.Reals("' + str(sym) + '")')
system = eval(system_str)
return system

I do not particularly like this approach because it involves string manipulations as well as dynamic calls to eval() and exec(), which makes intensive computation considerably slower if you are dynamically generating your system, but this is what I could come up with.

More con converting strings to z3 expressions:

An alternative to eval and exec is to walk the sympy expression and construct the corresponding z3 expression. Here's some code:

from z3 import Real, Sqrt 
from sympy.core import Mul, Expr, Add, Pow, Symbol, Number

def sympy_to_z3(sympy_var_list, sympy_exp):
    'convert a sympy expression to a z3 expression. This returns (z3_vars, z3_expression)'

    z3_vars = []
    z3_var_map = {}

    for var in sympy_var_list:
        name = var.name
        z3_var = Real(name)
        z3_var_map[name] = z3_var
        z3_vars.append(z3_var)

    result_exp = _sympy_to_z3_rec(z3_var_map, sympy_exp)

    return z3_vars, result_exp

def _sympy_to_z3_rec(var_map, e):
    'recursive call for sympy_to_z3()'

    rv = None

    if not isinstance(e, Expr):
        raise RuntimeError("Expected sympy Expr: " + repr(e))

    if isinstance(e, Symbol):
        rv = var_map.get(e.name)

        if rv == None:
            raise RuntimeError("No var was corresponds to symbol '" + str(e) + "'")

    elif isinstance(e, Number):
        rv = float(e)
    elif isinstance(e, Mul):
        rv = _sympy_to_z3_rec(var_map, e.args[0])

        for child in e.args[1:]:
            rv *= _sympy_to_z3_rec(var_map, child)
    elif isinstance(e, Add):
        rv = _sympy_to_z3_rec(var_map, e.args[0])

        for child in e.args[1:]:
            rv += _sympy_to_z3_rec(var_map, child)
    elif isinstance(e, Pow):
        term = _sympy_to_z3_rec(var_map, e.args[0])
        exponent = _sympy_to_z3_rec(var_map, e.args[1])

        if exponent == 0.5:
            # sqrt
            rv = Sqrt(term)
        else:
            rv = term**exponent

    if rv == None:
        raise RuntimeError("Type '" + str(type(e)) + "' is not yet implemented for convertion to a z3 expresion. " + \
                            "Subexpression was '" + str(e) + "'.")

    return rv

And here's an example using the code:

from sympy import symbols
from z3 import Solver, sat

var_list = x, y = symbols("x y")

sympy_exp = -x**2 + y + 1
z3_vars, z3_exp = sympy_to_z3(var_list, sympy_exp)

z3_x = z3_vars[0]
z3_y = z3_vars[1]

s = Solver()
s.add(z3_exp == 0) # add a constraint with converted expression
s.add(z3_y >= 0) # add an extra constraint

result = s.check()

if result == sat:
    m = s.model()

    print "SAT at x={}, y={}".format(m[z3_x], m[z3_y])
else:
    print "UNSAT"

Running this produces the output that solves the constraints y >= 0 and -x^2 + y + 1 == 0 :

SAT at x=2, y=3

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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