from z3 import * import sys variables = {} def reachable(graph, node_reachable): # check reachability to node_reachable clauses = [] # at least one pebble can be gotted to node_reachble (from config or otherwise) clauses.append(1 <= Sum(list(variables[node_reachable]["in"].values()))) return clauses def build_model(graph, add_property, node_reachable, config=None, config_count=0): clauses = [] for node in graph["v"]: variables[node] = {"out":{}, "in":{}} # creat symbolic variables for each edge in/out for node in graph["v"]: for edge in graph["e"][node]: variables[node]["out"][edge] = Int(node+"_out_"+edge) variables[edge]["in"][node] = Int(edge+"_in_"+node) # add initial pebbles variables[node]["in"]["config"] = Int(node+"_in_config") if config is not None: for node in graph["v"]: clauses.append(variables[node]["in"]["config"] == config[node]) else: sum_terms = [] for node in graph["v"]: clauses.append(variables[node]["in"]["config"] >= 0) sum_terms.append(variables[node]["in"]["config"]) clauses.append(Sum(sum_terms) == config_count) # make edges linked for node in graph["v"]: for edge in graph["e"][node]: clauses.append(variables[node]["out"][edge] == variables[edge]["in"][node]) # encode possible moves # make sure we don't move too many total from pebble for node in graph["v"]: sum_out = Sum(list(variables[node]["out"].values())) sum_in = Sum(list(variables[node]["in"].values())) # The number of pebbles out from a node is no more than half the sum # of all pebbles that are moved into a node clauses.append( Implies(sum_in % 2 == 0, sum_out <= (sum_in/2 )) ) clauses.append( Implies(sum_in % 2 == 1, sum_out <= ((sum_in-1)/2 )) ) # make sure each move is a valid amount for node in graph["v"]: for in_var in variables[node]["in"].values(): clauses.append(in_var >= 0) for out_var in variables[node]["out"].values(): sum_in = Sum(list(variables[node]["in"].values())) clauses.append( Implies(And(sum_in % 2 == 0, sum_in > 2), And(0 <= out_var, out_var <= (sum_in/2 ))) ) clauses.append( Implies(And(sum_in % 2 == 1, sum_in > 2), And(0 <= out_var, out_var <= ((sum_in-1)/2 ))) ) # encode cycle lemma # TODO get all cycles, and remove them? for node in graph["v"]: # if we along one edge, do not move back along it for edge in graph["e"][node]: in1 = variables[node]["in"][edge] in2 = variables[edge]["in"][node] clauses.append(Implies(in1 > 0, in2 == 0)) clauses += add_property(graph, node_reachable) return And(clauses) def is_sat(f, output=True): s = Solver() s.add(f) if output: print(f) if s.check() == sat: if output: print("SAT") model = s.model() # print(model) moves = [] for key in model: if "_in_" in key.name() and 0