Exemple #1
0
 def test_multiple_exit(self):
     for sname in get_env().factory.all_solvers():
         # Multiple exits should be ignored
         s = Solver(name=sname)
         s.exit()
         s.exit()
         self.assertTrue(True)
Exemple #2
0
 def test_btor_does_not_support_int_arrays(self):
     a = Symbol("a", ARRAY_INT_INT)
     formula = Equals(Select(Store(a, Int(10), Int(100)), Int(10)),
                      Int(100))
     btor = Solver(name="btor")
     with self.assertRaises(ConvertExpressionError):
         btor.add_assertion(formula)
Exemple #3
0
 def test_btor_options(self):
     for (f, _, sat, logic) in get_example_formulae():
         if logic == QF_BV:
             solver = Solver(name="btor",
                             solver_options={"rewrite-level":0,
                                             "fun:dual-prop":1,
                                             "eliminate-slices":1})
             solver.add_assertion(f)
             res = solver.solve()
             self.assertTrue(res == sat)
Exemple #4
0
    def get_prepared_solver(self, logic, solver_name=None):
        """Returns a solver initialized with the sudoku constraints and a
        matrix of SMT variables, each representing a cell of the game.
        """
        sq_size = self.size**2
        ty = self.get_type()
        var_table = [[FreshSymbol(ty) for _ in xrange(sq_size)]
                          for _ in xrange(sq_size)]

        solver = Solver(logic=logic, name=solver_name)
        # Sudoku constraints
        # all variables are positive and lower or equal to than sq_size
        for row in var_table:
            for var in row:
                solver.add_assertion(Or([Equals(var, self.const(i))
                                         for i in xrange(1, sq_size + 1)]))

        # each row and each column contains all different numbers
        for i in xrange(sq_size):
            solver.add_assertion(AllDifferent(var_table[i]))
            solver.add_assertion(AllDifferent([x[i] for x in var_table]))

        # each square contains all different numbers
        for sx in xrange(self.size):
            for sy in xrange(self.size):
                square = [var_table[i + sx * self.size][j + sy * self.size]
                          for i in xrange(self.size) for j in xrange(self.size)]
                solver.add_assertion(AllDifferent(square))

        return solver, var_table
Exemple #5
0
 def test_examples_solving(self):
     for example in EXAMPLE_FORMULAS:
         if example.logic != pysmt.logics.BOOL:
             continue
         solver = Solver(logic=pysmt.logics.BOOL,
                         name='bdd')
         solver.add_assertion(example.expr)
         if example.is_sat:
             self.assertTrue(solver.solve())
         else:
             self.assertFalse(solver.solve())
Exemple #6
0
 def test_examples_solving(self):
     for example in get_example_formulae():
         if example.logic != BOOL:
             continue
         solver = Solver(logic=BOOL,
                         name='bdd')
         solver.add_assertion(example.expr)
         if example.is_sat:
             self.assertTrue(solver.solve())
         else:
             self.assertFalse(solver.solve())
Exemple #7
0
    def test_create_and_solve(self):
        solver = Solver(logic=QF_BOOL)

        varA = Symbol("A", BOOL)
        varB = Symbol("B", BOOL)

        f = And(varA, Not(varB))

        g = f.substitute({varB:varA})
        solver.add_assertion(g)
        res = solver.solve()
        self.assertFalse(res, "Formula was expected to be UNSAT")

        h = And(g, Bool(False))
        simp_h = h.simplify()
        self.assertEqual(simp_h, Bool(False))
Exemple #8
0
    def test_msat_partial_model(self):
        msat = Solver(name="msat")
        x, y = Symbol("x"), Symbol("y")
        msat.add_assertion(x)
        c = msat.solve()
        self.assertTrue(c)

        model = msat.get_model()
        self.assertNotIn(y, model)
        self.assertIn(x, model)
        msat.exit()
Exemple #9
0
 def _get_solver(self):
     solver = Solver(name='z3', logic=QF_BOOL)
     return solver
Exemple #10
0
 def test_invalid_ordering(self):
     with self.assertRaises(ValueError):
         Solver(name="bdd", logic=BOOL,
                solver_options={'static_ordering':
                                [And(self.x, self.y), self.y]})
Exemple #11
0
 def test_reordering(self):
     with Solver(name="bdd", logic=BOOL,
                 solver_options={'dynamic_reordering': True}) as s:
         s.add_assertion(self.big_tree)
         self.assertTrue(s.solve())
Exemple #12
0
class PDR(object):
    def __init__(self, system):
        self.system = system
        self.frames = [system.init]
        self.solver = Solver()
        self.prime_map = dict([(v, next_var(v)) for v in self.system.variables])

    def check_property(self, prop):
        """Property Directed Reachability approach without optimizations."""
        print("Checking property %s..." % prop)

        while True:
            cube = self.get_bad_state(prop)
            if cube is not None:
                # Blocking phase of a bad state
                if self.recursive_block(cube):
                    print("--> Bug found at step %d" % (len(self.frames)))
                    break
                else:
                    print("   [PDR] Cube blocked '%s'" % str(cube))
            else:
                # Checking if the last two frames are equivalent i.e., are inductive
                if self.inductive():
                    print("--> The system is safe!")
                    break
                else:
                    print("   [PDR] Adding frame %d..." % (len(self.frames)))
                    self.frames.append(TRUE())

    def get_bad_state(self, prop):
        """Extracts a reachable state that intersects the negation
        of the property and the last current frame"""
        return self.solve(And(self.frames[-1], Not(prop)))

    def solve(self, formula):
        """Provides a satisfiable assignment to the state variables that are consistent with the input formula"""
        if self.solver.solve([formula]):
            return And([EqualsOrIff(v, self.solver.get_value(v)) for v in self.system.variables])
        return None

    def recursive_block(self, cube):
        """Blocks the cube at each frame, if possible.

        Returns True if the cube cannot be blocked.
        """
        for i in range(len(self.frames)-1, 0, -1):
            cubeprime = cube.substitute(dict([(v, next_var(v)) for v in self.system.variables]))
            cubepre = self.solve(And(self.frames[i-1], self.system.trans, Not(cube), cubeprime))
            if cubepre is None:
                for j in range(1, i+1):
                    self.frames[j] = And(self.frames[j], Not(cube))
                return False
            cube = cubepre
        return True

    def inductive(self):
        """Checks if last two frames are equivalent """
        if len(self.frames) > 1 and \
           self.solve(Not(EqualsOrIff(self.frames[-1], self.frames[-2]))) is None:
            return True
        return False
Exemple #13
0
def callback(model, converter, result):
    """Callback for msat_all_sat.

    This function is called by the MathSAT API everytime a new model
    is found. If the function returns 1, the search continues,
    otherwise it stops.
    """
    # Elements in model are msat_term .
    # Converter.back() provides the pySMT representation of a solver term.
    py_model = [converter.back(v) for v in model]
    result.append(And(py_model))
    return 1 # go on

x, y = Symbol("x"), Symbol("y")
f = Or(x, y)

msat = Solver(name="msat")
converter = msat.converter # .converter is a property implemented by all solvers
msat.add_assertion(f) # This is still at pySMT level

result = []
# Directly invoke the mathsat API !!!
# The second term is a list of "important variables"
mathsat.msat_all_sat(msat.msat_env(),
      [converter.convert(x)],      # Convert the pySMT term into a MathSAT term
      lambda model : callback(model, converter, result))

print("'exists y . %s' is equivalent to '%s'" %(f, Or(result)))
#exists y . (x | y) is equivalent to ((! x) | x)
Exemple #14
0
def callback(model, converter, result):
    """Callback for msat_all_sat.

    This function is called by the MathSAT API everytime a new model
    is found. If the function returns 1, the search continues,
    otherwise it stops.
    """
    # Elements in model are msat_term .
    # Converter.back() provides the pySMT representation of a solver term.
    py_model = [converter.back(v) for v in model]
    result.append(And(py_model))
    return 1 # go on

x, y = Symbol("x"), Symbol("y")
f = Or(x, y)

msat = Solver(name="msat")
converter = msat.converter # .converter is a property implemented by all solvers
msat.add_assertion(f) # This is still at pySMT level

result = []
# Directly invoke the mathsat API !!!
# The second term is a list of "important variables"
mathsat.msat_all_sat(msat.msat_env,
      [converter.convert(x)],      # Convert the pySMT term into a MathSAT term
      lambda model : callback(model, converter, result))

print("'exists y . %s' is equivalent to '%s'" %(f, Or(result)))
#exists y . (x | y) is equivalent to ((! x) | x)
Exemple #15
0
from pysmt.typing import INT

hello = [Symbol(s, INT) for s in "hello"]
world = [Symbol(s, INT) for s in "world"]

letters = set(hello + world)

domains = And(And(LE(Int(1), l), GE(Int(10), l)) for l in letters)

sum_hello = Plus(hello)
sum_world = Plus(world)

problem = And(Equals(sum_hello, sum_world), Equals(sum_hello, Int(25)))

formula = And(domains, problem)

print("Serialization of the formula:")
print(formula)

with Solver(name="z3") as solver:
    solver.add_assertion(domains)
    if not solver.solve():
        print("Domain is not SAT!!!")
        exit()
    solver.add_assertion(problem)
    if solver.solve():
        for l in letters:
            print("%s = %s" % (l, solver.get_value(l)))
    else:
        print("No solution found")
Exemple #16
0
 def test_btor_does_not_support_const_arryas(self):
     with self.assertRaises(ConvertExpressionError):
         btor = Solver(name="btor")
         btor.add_assertion(Equals(Array(BV8, BV(0, 8)),
                                   FreshSymbol(ArrayType(BV8, BV8))))
Exemple #17
0
class PDR(object):
    def __init__(self, system):
        self.system = system
        self.frames = [system.init]
        self.solver = Solver()
        self.prime_map = dict([(v, next_var(v))
                               for v in self.system.variables])

    def check_property(self, prop):
        """Property Directed Reachability approach without optimizations."""
        print("Checking property %s..." % prop)

        while True:
            cube = self.get_bad_state(prop)
            if cube is not None:
                # Blocking phase of a bad state
                if self.recursive_block(cube):
                    print("--> Bug found at step %d" % (len(self.frames)))
                    break
                else:
                    print("   [PDR] Cube blocked '%s'" % str(cube))
            else:
                # Checking if the last two frames are equivalent i.e., are inductive
                if self.inductive():
                    print("--> The system is safe!")
                    break
                else:
                    print("   [PDR] Adding frame %d..." % (len(self.frames)))
                    self.frames.append(TRUE())

    def get_bad_state(self, prop):
        """Extracts a reachable state that intersects the negation
        of the property and the last current frame"""
        return self.solve(And(self.frames[-1], Not(prop)))

    def solve(self, formula):
        """Provides a satisfiable assignment to the state variables that are consistent with the input formula"""
        if self.solver.solve([formula]):
            return And([
                EqualsOrIff(v, self.solver.get_value(v))
                for v in self.system.variables
            ])
        return None

    def recursive_block(self, cube):
        """Blocks the cube at each frame, if possible.

        Returns True if the cube cannot be blocked.
        """
        for i in range(len(self.frames) - 1, 0, -1):
            cubeprime = cube.substitute(
                dict([(v, next_var(v)) for v in self.system.variables]))
            cubepre = self.solve(
                And(self.frames[i - 1], self.system.trans, Not(cube),
                    cubeprime))
            if cubepre is None:
                for j in range(1, i + 1):
                    self.frames[j] = And(self.frames[j], Not(cube))
                return False
            cube = cubepre
        return True

    def inductive(self):
        """Checks if last two frames are equivalent """
        if len(self.frames) > 1 and \
           self.solve(Not(EqualsOrIff(self.frames[-1], self.frames[-2]))) is None:
            return True
        return False
Exemple #18
0
 def __init__(self, system):
     self.system = system
     self.frames = [system.init]
     self.solver = Solver()
     self.prime_map = dict([(v, next_var(v))
                            for v in self.system.variables])
Exemple #19
0
 def init_solver(self):
     solver = Solver(name="bdd", logic=BOOL)
     return solver
Exemple #20
0
 def clear(self):
     self.solver.exit()
     self.solver = Solver(name=self.solver_name, logic=self.logic, incremental=self.incremental, solver_options=self.solver_options)
hello = [Symbol(s, INT) for s in "hello"]
world = [Symbol(s, INT) for s in "world"]

letters = set(hello+world)

domains = And(And(LE(Int(1), l),
                  GE(Int(10), l)) for l in letters)

sum_hello = Plus(hello)
sum_world = Plus(world)

problem = And(Equals(sum_hello, sum_world),
              Equals(sum_hello, Int(36)))

formula = And(domains, problem)

print("Serialization of the formula:")
print(formula)

with Solver(logic="QF_LIA") as solver:
    solver.add_assertion(domains)
    if not solver.solve():
        print("Domain is not SAT!!!")
        exit()
    solver.add_assertion(problem)
    if solver.solve():
        for l in letters:
            print("%s = %s" %(l, solver.get_value(l)))
    else:
        print("No solution found")
Exemple #22
0
    def test_msat_preferred_variable(self):
        a, b, c = [Symbol(x) for x in "abc"]
        na, nb, nc = [Not(Symbol(x)) for x in "abc"]

        f = And(Implies(a, And(b,c)),
                Implies(na, And(nb,nc)))

        s1 = Solver("msat")
        s1.add_assertion(f)
        s1.set_preferred_var(a, True)
        self.assertTrue(s1.solve())
        self.assertTrue(s1.get_value(a).is_true())

        s2 = Solver("msat")
        s2.add_assertion(f)
        s2.set_preferred_var(a, False)
        self.assertTrue(s2.solve())
        self.assertTrue(s2.get_value(a).is_false())

        # Show that calling without polarity still works
        # This case is harder to test, because we only say
        # that the split will occur on that variable first.
        s1.set_preferred_var(a)
Exemple #23
0
    def test_msat_preferred_variable(self):
        a, b, c = [Symbol(x) for x in "abc"]
        na, nb, nc = [Not(Symbol(x)) for x in "abc"]

        f = And(Implies(a, And(b, c)), Implies(na, And(nb, nc)))

        s1 = Solver("msat")
        s1.add_assertion(f)
        s1.set_preferred_var(a, True)
        self.assertTrue(s1.solve())
        self.assertTrue(s1.get_value(a).is_true())

        s2 = Solver("msat")
        s2.add_assertion(f)
        s2.set_preferred_var(a, False)
        self.assertTrue(s2.solve())
        self.assertTrue(s2.get_value(a).is_false())

        # Show that calling without polarity still works
        # This case is harder to test, because we only say
        # that the split will occur on that variable first.
        s1.set_preferred_var(a)
Exemple #24
0
def generate_ising(graph, feasible_configurations, decision_variables,
                   linear_energy_ranges, quadratic_energy_ranges,
                   smt_solver_name):
    """Generates the Ising model that induces the given feasible configurations.

    Args:
        graph (nx.Graph): The target graph on which the Ising model is to be built.
        feasible_configurations (dict): The set of feasible configurations
            of the decision variables. The key is a feasible configuration
            as a tuple of spins, the values are the associated energy.
        decision_variables (list/tuple): Which variables in the graph are
            assigned as decision variables.
        linear_energy_ranges (dict, optional): A dict of the form
            {v: (min, max, ...} where min and max are the range
            of values allowed to v.
        quadratic_energy_ranges (dict): A dict of the form
            {(u, v): (min, max), ...} where min and max are the range
            of values allowed to (u, v).
        smt_solver_name (str/None): The name of the smt solver. Must
            be a solver available to pysmt. If None, uses the pysmt default.

    Returns:
        tuple: A 4-tuple contiaing:

            dict: The linear biases of the Ising problem.

            dict: The quadratic biases of the Ising problem.

            float: The ground energy of the Ising problem.

            float: The classical energy gap between ground and the first
            excited state.

    Raises:
        ImpossiblePenaltyModel: If the penalty model cannot be built. Normally due
            to a non-zero infeasible gap.

    """
    # we need to build a Table. The table encodes all of the information used by the smt solver
    table = Table(graph, decision_variables, linear_energy_ranges,
                  quadratic_energy_ranges)

    # iterate over every possible configuration of the decision variables.
    for config in itertools.product((-1, 1), repeat=len(decision_variables)):

        # determine the spin associated with each varaible in decision variables.
        spins = dict(zip(decision_variables, config))

        if config in feasible_configurations:
            # if the configuration is feasible, we require that the mininum energy over all
            # possible aux variable settings be exactly its target energy (given by the value)
            table.set_energy(spins, feasible_configurations[config])
        else:
            # if the configuration is infeasible, we simply want its minimum energy over all
            # possible aux variable settings to be an upper bound on the classical gap.
            table.set_energy_upperbound(spins)

    # now we just need to get a solver
    with Solver(smt_solver_name) as solver:

        # add all of the assertions from the table to the solver
        for assertion in table.assertions:
            solver.add_assertion(assertion)

        # check if the model is feasible at all.
        if solver.solve():

            # we want to increase the gap until we have found the max classical gap
            gmin = 0
            gmax = sum(
                max(abs(r) for r in linear_energy_ranges[v]) for v in graph)
            gmax += sum(
                max(abs(r) for r in quadratic_energy_ranges[(u, v)])
                for (u, v) in graph.edges)

            # 2 is a good target gap
            g = 2.

            while abs(gmax - gmin) >= .01:
                solver.push()

                gap_assertion = table.gap_bound_assertion(g)
                solver.add_assertion(gap_assertion)

                if solver.solve():
                    model = solver.get_model()
                    gmin = float(
                        model.get_py_value(table.gap).limit_denominator())
                else:
                    solver.pop()
                    gmax = g

                g = min(gmin + .1, (gmax + gmin) / 2)

        else:
            raise pm.ImpossiblePenaltyModel("Model cannot be built")

    # finally we need to convert our values back into python floats.
    # we use limit_denominator to deal with some of the rounding
    # issues.
    theta = table.theta
    linear = {
        v: float(model.get_py_value(bias).limit_denominator())
        for v, bias in iteritems(theta.linear)
    }
    quadratic = {(u, v): float(model.get_py_value(bias).limit_denominator())
                 for (u, v), bias in iteritems(theta.quadratic)}
    ground_energy = -float(
        model.get_py_value(theta.offset).limit_denominator())
    classical_gap = float(model.get_py_value(table.gap).limit_denominator())

    return linear, quadratic, ground_energy, classical_gap
Exemple #25
0
 def clear(self):
     self.solver.exit()
     self.solver = Solver(name=self.solver_name, logic=QF_ABV)
Exemple #26
0
    def setUp(self):
        self.x, self.y = Symbol("x"), Symbol("y")

        self.bdd_solver = Solver(logic=pysmt.logics.BOOL, name='bdd')
        self.bdd_converter = self.bdd_solver.converter
Exemple #27
0
 def test_msat_bool_back_conversion(self):
     f = Symbol("A")
     with Solver(name='msat') as solver:
         solver.solve()
         val = solver.get_value(Symbol("A"))
         self.assertTrue(val.is_bool_constant())
Exemple #28
0
 def test_incremental_is_sat(self):
     from pysmt.exceptions import SolverStatusError
     with Solver(incremental=False, logic=QF_BOOL) as s:
         self.assertTrue(s.is_sat(Symbol("x")))
         with self.assertRaises(SolverStatusError):
             s.is_sat(Not(Symbol("x")))
Exemple #29
0
path = ["/tmp/mathsat"] # Path to the solver
logics = [QF_UFLRA, QF_UFIDL] # Some of the supported logics

env = get_env()

# Add the solver to the environment
env.factory.add_generic_solver(name, path,logics)

r, s = Symbol("r", REAL), Symbol("s", REAL)
p, q = Symbol("p", INT), Symbol("q", INT)

f_lra = GT(r, s)
f_idl = GT(p, q)

# PySMT takes care of recognizing that QF_LRA can be solved by a QF_UFLRA solver.
with Solver(name=name, logic=QF_LRA) as s:
    res = s.solve()
    assert res, "Was expecting '%s' to be SAT" % f_lra


with Solver(name=name, logic=QF_IDL) as s:
    s.add_assertion(f_idl)
    res = s.solve()
    assert res, "Was expecting '%s' to be SAT" % f_idl

try:
    with Solver(name=name, logic=QF_LIA) as s:
        pass
except NoSolverAvailableError:
    # If we ask for a logic that is not contained by any of the
    # supported logics an exception is thrown
Exemple #30
0
 def test_div(self):
     x = FreshSymbol(REAL)
     f = Equals(Div(x, x), x)
     with Solver(name="z3") as s:
         self.assertTrue(s.is_sat(f))
Exemple #31
0
 def __init__(self, system):
     self.system = system
     self.frames = [system.init]
     self.solver = Solver()
     self.prime_map = dict([(v, next_var(v)) for v in self.system.variables])
Exemple #32
0
if __name__ == '__main__':
    hello = [Symbol(s, REAL) for s in "hello"]
    world = [Symbol(s, REAL) for s in "world"]

    letters = set(hello+world)

    # All letters between 1 and 10
    domains = And([And(GE(l, Real(1)),
                    LT(l, Real(10))) for l in letters])

    sum_hello = Plus(hello) # n-ary operators can take lists
    sum_world = Plus(world) # as arguments
    problem = And(Equals(sum_hello, sum_world), Equals(sum_hello, Real(10)))
    formula = And(domains, problem)

    print("Serialization of the formula:")
    print(formula)


    with Solver(name="msat") as solver:
        solver.add_assertion(domains)
        if not solver.solve():
            print("Domain is not SAT!!!")
            exit()
        solver.add_assertion(problem)
        if solver.solve():
            for l in letters:
                print("%s = %s" %(l, solver.get_value(l)))
        else:
            print("No solution found")
Exemple #33
0
def solve_smt_problem(max_outputs, min_anonymity_score=None, timeout=None):
    #constraints:
    input_constraints = set()
    output_constraints = set()
    anonymityset_constraints = set()
    txfee_constraints = set()
    invariants = set()

    #variables:
    total_in = Symbol("total_in", INT)  #total satoshis from inputs
    total_out = Symbol("total_out", INT)  #total satoshis sent to outputs
    num_outputs = Symbol("num_outputs",
                         INT)  #num outputs actually used in the tx
    max_outputs_sym = Symbol("max_outputs",
                             INT)  #the symbolic variable for max_outputs
    anonymity_score = Symbol("anonymity_score",
                             INT)  #see below for meaning. higher is better.
    txsize = Symbol(
        "txsize", INT
    )  #estimated tx size in vbytes, given the number of inputs and outputs in the tx
    txfee = Symbol("txfee", INT)  #estimated tx fee, given the supplied feerate
    party_gives = dict(
    )  #party ID -> total satoshis on inputs contributed by that party
    party_gets = dict(
    )  #party ID -> total satoshis on outputs belonging to that party
    party_txfee = dict(
    )  #party ID -> satoshis contributed by that party towards the txfee
    party_numinputs = dict()
    party_numoutputs = dict()
    input_party = dict(
    )  #index into inputs -> party ID that contributed that input
    input_amt = dict(
    )  #index into inputs -> satoshis contributed by that input
    output_party = dict(
    )  #index into outputs -> party ID to whom the output belongs
    output_amt = dict()  #index into outputs -> satoshis sent to that output
    output_score = dict(
    )  #index into outputs -> number of other outputs in the same amount not owned by that output's owner

    for (party, _) in example_txfees:
        party_gives[party] = Symbol("party_gives[%d]" % party, INT)
        party_gets[party] = Symbol("party_gets[%d]" % party, INT)
        party_txfee[party] = Symbol("party_txfee[%d]" % party, INT)
        party_numinputs[party] = Symbol("party_numinputs[%d]" % party, INT)
        party_numoutputs[party] = Symbol("party_numoutputs[%d]" % party, INT)
    for i in range(0, num_inputs):
        input_party[i] = Symbol("input_party[%d]" % i, INT)
        input_amt[i] = Symbol("input_amt[%d]" % i, INT)
    for i in range(0, max_outputs):
        output_party[i] = Symbol("output_party[%d]" % i, INT)
        output_amt[i] = Symbol("output_amt[%d]" % i, INT)
        output_score[i] = Symbol("output_score[%d]" % i, INT)

    #constraint construction:

    #party_txfee constraints:
    for (party, fee_contribution) in example_txfees:
        txfee_constraints.add(LE(party_txfee[party], Int(fee_contribution)))

    #input_party and input_amt bindings:
    for i in range(0, num_inputs):
        input_used_conditions = And(
            Equals(input_party[i], Int(example_inputs[i][0])),
            Equals(input_amt[i], Int(example_inputs[i][1])))
        input_not_used_conditions = And(Equals(input_party[i], Int(-1)),
                                        Equals(input_amt[i], Int(0)))
        input_constraints.add(
            Or(input_used_conditions, input_not_used_conditions))

    #add constraints on output_party and output_amt:
    # -either output_party[i] == -1 and output_amt[i] == 0
    # -or else output_amt[i] > 0
    output_unused = list()
    for i in range(0, max_outputs):
        output_is_unused = Equals(output_party[i], Int(-1))
        output_unused.append(output_is_unused)
        min_delta_satisfied = Or(output_is_unused,
                                 And([Or(Equals(output_amt[i],
                                                output_amt[j]),
                                         Or(GE(output_amt[j],
                                               Plus(output_amt[i],
                                                    Int(min_output_amt_delta))),
                                            LE(output_amt[j],
                                               Minus(output_amt[i],
                                                     Int(min_output_amt_delta)))))\
                                      for j in filter(lambda j: j != i, range(0, max_outputs))]))
        output_constraints.add(min_delta_satisfied)
        output_constraints.add(
            Ite(output_is_unused, Equals(output_amt[i], Int(0)),
                GT(output_amt[i], Int(min(0, min_output_amt - 1)))))
    #calculate num_outputs and bind max_outputs:
    output_constraints.add(
        Equals(num_outputs,
               Plus([bool_to_int(Not(x)) for x in output_unused])))
    output_constraints.add(Equals(max_outputs_sym, Int(max_outputs)))

    #txfee, party_gets, and party_gives calculation/constraints/binding:
    for party in parties:
        #party_gives and input constraint/invariant
        owned_vec = list()
        amt_vec = list()
        for i in range(0, num_inputs):
            owned = Equals(input_party[i], Int(party))
            amt = Ite(owned, input_amt[i], Int(0))
            owned_vec.append(bool_to_int(owned))
            amt_vec.append(amt)
        input_constraints.add(Equals(party_numinputs[party], Plus(owned_vec)))
        input_constraints.add(Equals(party_gives[party], Plus(amt_vec)))

        #txfee calculations:
        txfee_constraints.add(
            Equals(party_gets[party],
                   Minus(party_gives[party], party_txfee[party])))

        #party_gets and output constraint/invariant:
        owned_vec = list()
        amt_vec = list()
        for i in range(0, max_outputs):
            owned = Equals(output_party[i], Int(party))
            amt = Ite(owned, output_amt[i], Int(0))
            owned_vec.append(bool_to_int(owned))
            amt_vec.append(amt)
        output_constraints.add(Equals(party_gets[party], Plus(amt_vec)))
        output_constraints.add(Equals(party_numoutputs[party],
                                      Plus(owned_vec)))

        #build party fragmentation constraints:
        output_constraints.add(
            LE(
                party_numoutputs[party],
                Times(Int(max_party_fragmentation_factor),
                      party_numinputs[party])))

    #build anonymity set constraints:
    #each party should not have any uniquely-identifiable output:
    for (idx, amt) in output_amt.items():
        not_unique = Or(Equals(output_party[idx],
                               Int(-1)),
                        Or([And(Equals(v,
                                       amt),
                                Not(Equals(output_party[k],
                                           output_party[idx])))\
                            for (k, v) in filter(lambda x: x[0] != idx, output_amt.items())]))
        anonymityset_constraints.add(not_unique)

    #constrain the anonymity score, if set:
    def score_output(
        idx
    ):  #how many other outputs in the same amount that do not belong to us?
        outputs_equal_not_ours = [And(Equals(v,
                                             output_amt[idx]),
                                      Not(Equals(output_party[k],
                                                 output_party[idx])))\
                                  for (k, v) in filter(lambda x: x[0] != idx, output_amt.items())]
        score = Plus([bool_to_int(x) for x in outputs_equal_not_ours])
        anonymityset_constraints.add(Equals(output_score[idx], score))
        return score

    anonymityset_constraints.add(
        Equals(anonymity_score,
               Plus([score_output(idx) for (idx, _) in output_amt.items()])))
    if min_anonymity_score is not None:
        anonymityset_constraints.add(
            GE(anonymity_score, Int(min_anonymity_score)))

    #set transaction invariants:
    invariants.add(Equals(total_in, Plus(total_out, txfee)))
    invariants.add(Equals(total_in, Plus([v for (k, v) in input_amt.items()])))
    invariants.add(
        Equals(total_in, Plus([v for (k, v) in party_gives.items()])))
    invariants.add(
        Equals(total_out, Plus([v for (k, v) in output_amt.items()])))
    invariants.add(
        Equals(total_out, Plus([v for (k, v) in party_gets.items()])))

    #build txfee calculation constraint: 11 + 68 * num_inputs + 31 * num_outputs
    num_used_inputs = Plus([party_numinputs[party] for party in parties])
    txfee_constraints.add(
        Equals(
            txsize,
            Plus(Plus(Int(11), Times(Int(68), num_used_inputs)),
                 Times(Int(31), num_outputs))))
    txfee_constraints.add(GE(txfee, Times(txsize, Int(min_feerate))))
    txfee_constraints.add(LE(txfee, Times(txsize, Int(max_feerate))))

    #finish problem construction:
    constraints = list()
    for x in [
            input_constraints, invariants, txfee_constraints,
            output_constraints, anonymityset_constraints
    ]:
        for c in x:
            constraints.append(c)
    problem = And(constraints)

    with Solver(name='z3',
                solver_options={'timeout': solver_iteration_timeout}) as s:
        try:
            if s.solve([problem]):
                model_lines = sorted(
                    str(s.get_model()).replace("'", "").split('\n'))
                result = ([
                    s.get_py_value(num_outputs),
                    s.get_py_value(anonymity_score)
                ], parse_model_lines(model_lines))
                return result
            else:
                return None
        except SolverReturnedUnknownResultError:
            return None