From 9c30d01414561e579c0d8f474d7f3171f77b478e Mon Sep 17 00:00:00 2001 From: Mark Powers Date: Sun, 14 Jul 2024 15:25:52 -0500 Subject: Initial commit --- convert_to_sat.py | 266 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 convert_to_sat.py (limited to 'convert_to_sat.py') diff --git a/convert_to_sat.py b/convert_to_sat.py new file mode 100644 index 0000000..d538b08 --- /dev/null +++ b/convert_to_sat.py @@ -0,0 +1,266 @@ +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