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)
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)
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)
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
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())
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())
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))
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()
def _get_solver(self): solver = Solver(name='z3', logic=QF_BOOL) return solver
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]})
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())
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
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)
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)
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")
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))))
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
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 init_solver(self): solver = Solver(name="bdd", logic=BOOL) return solver
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")
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)
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)
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
def clear(self): self.solver.exit() self.solver = Solver(name=self.solver_name, logic=QF_ABV)
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
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())
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")))
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
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))
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")
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