def string_from_file(file_name, indent = 0, verbosity = 0): """Read pattern file into string""" print_message('Reading file "' + file_name + '" ...', 3, indent = indent, verbosity = verbosity) with open(file_name, "r") as pattern_file: input_string = pattern_file.read() print_message('Done\n', 3, indent = indent, verbosity = verbosity) return input_string
def solve(search_pattern, DIMACS_string, DIMACS_variables_from_CNF_list_variables, solver=None, parameters=None, timeout=None, save_dimacs=None, dry_run=None, indent=0, verbosity=0): print_message('Solving...', indent=indent, verbosity=verbosity) DIMACS_solution, time_taken = LLS_SAT_solvers.SAT_solve( DIMACS_string, solver=solver, parameters=parameters, timeout=timeout, save_dimacs=save_dimacs, dry_run=dry_run, indent=indent + 1, verbosity=verbosity) if DIMACS_solution not in ["UNSAT\n", "TIMEOUT\n", "DRYRUN\n"]: sat = "SAT" solution = search_pattern.substitute_solution( DIMACS_solution, DIMACS_variables_from_CNF_list_variables, indent=indent + 1, verbosity=verbosity) else: sat = DIMACS_solution[:-1] solution = None print_message('Done\n', indent=indent, verbosity=verbosity) return solution, sat, time_taken
def force_distinct(self, solution, determined = False, indent = 0, verbosity = 0): """Force search_pattern to have at least one differnence from given solution""" print_message("Forcing pattern to be different from solution...", 3, indent = indent, verbosity = verbosity) clause = [] for t, generation in enumerate(self.grid): if t == 0 or not determined: for y, row in enumerate(generation): for x, cell in enumerate(row): other_cell = solution.grid[t][y][x] assert other_cell in ["0", "1"], "Only use force_distinct against solved patterns" if other_cell == "0": clause.append(cell) else: clause.append(negate(cell)) for transition, literal in self.rule.items(): other_literal = solution.rule[transition] assert other_literal in ["0", "1"], "Only use force_distinct against solved patterns" if other_literal == "0": clause.append(literal) else: clause.append(negate(literal)) self.clauses.append(clause) print_message("Done\n", 3, indent = indent, verbosity = verbosity)
def make_csv(grid, ignore_transition=None, rule=None, determined=None, indent=0, verbosity=0): """Turn a search pattern in list form into nicely formatted csv string""" print_message('Format: csv', 3, indent=indent, verbosity=verbosity) grid = copy.deepcopy(grid) if ignore_transition == None: ignore_transition = [[[False for cell in row] for row in generation] for generation in grid] lengths = [] for t, generation in enumerate(grid): for y, row in enumerate(generation): for x, cell in enumerate(row): if x != 0: lengths.append(len(cell) + ignore_transition[t][y][x - 1]) length_first_column = max( [max([len(row[0]) for row in generation]) for generation in grid]) if len(lengths) > 0: length_other_columns = max(lengths) for t, generation in enumerate(grid): for y, row in enumerate(generation): for x, cell in enumerate(row): if x == 0: grid[t][y][x] = " " * (length_first_column - len(cell)) + cell else: grid[t][y][x] = " " * (length_other_columns - len( cell) - ignore_transition[t][y][x - 1]) + grid[t][y][x] if ignore_transition[t][y][x]: grid[t][y][x] += "'" csv_string = "" if rule != None: csv_string += "Rule = " + LLS_rules.rulestring_from_rule(rule) + "\n" csv_string += "\n".join(",".join(line) for line in grid[0]) if not determined: csv_string += "\n\n" + "\n\n".join("\n".join(",".join(line) for line in generation) for generation in grid[1:]) return csv_string
def substitute_solution(self, solution, DIMACS_variables_from_CNF_list_variables, indent = 0, verbosity = 0): """Return a copy of the search_pattern with the solution substituted back into it""" grid = copy.deepcopy(self.grid) rule = copy.deepcopy(self.rule) print_message('Substituting solution back into search grid...', 3, indent = indent, verbosity = verbosity) # Remove the first line that just says "SAT", and split into a list of literals solution = solution.split("\n")[1].split() for t, generation in enumerate(grid): for y, row in enumerate(generation): for x, cell in enumerate(row): if cell in ["0","1"]: pass else: (CNF_variable, negated) = variable_from_literal(cell) if DIMACS_variables_from_CNF_list_variables.has_key(CNF_variable): DIMACS_variable = DIMACS_variables_from_CNF_list_variables[CNF_variable] DIMACS_literal = negate(DIMACS_variable, negated) if DIMACS_literal in solution: grid[t][y][x] = "1" else: grid[t][y][x] = "0" else: grid[t][y][x] = "0" for transition, literal in rule.items(): if literal in ["0","1"]: pass else: (CNF_variable, negated) = variable_from_literal(literal) if DIMACS_variables_from_CNF_list_variables.has_key(CNF_variable): DIMACS_variable = DIMACS_variables_from_CNF_list_variables[CNF_variable] DIMACS_literal = negate(DIMACS_variable, negated) if DIMACS_literal in solution: rule[transition] = "1" else: rule[transition] = "0" else: rule[transition] = "0" print_message('Done\n', 3, indent = indent, verbosity = verbosity) return SearchPattern(grid, rule = rule)
def force_change(self, t_0, t_1, indent = 0, verbosity = 0): """Adds clauses forcing at least one cell to change between specified generations""" print_message("Forcing at least one cell to change between generations " + str(t_0) + " and " + str(t_1) + " ...", 3, indent = indent, verbosity = verbosity) generation_0 = self.grid[t_0] generation_1 = self.grid[t_1] clause = [] clauses = [] for y, rows in enumerate(zip(generation_0,generation_1)): for x, cells in enumerate(zip(*rows)): cells_equal = ("x" + str(x) + "y" + str(y) + "is_equal_at" + "t" + str(t_0) + "and" + "t" + str(t_1)) clauses.append(implies(cells, cells_equal)) clauses.append(implies(map(negate, cells), cells_equal)) clause.append(negate(cells_equal)) clauses.append(clause) print_message("Number of clauses used: " + str(len(clauses)), 3, indent = indent + 1, verbosity = verbosity) self.clauses += clauses print_message("Done\n", 3, indent = indent, verbosity = verbosity)
def force_nonempty(self, indent = 0, verbosity = 0): """Adds clauses forcing at least one cell to be live in first generation""" print_message("Forcing at least one cell to be live in first generation...", 3, indent = indent, verbosity = verbosity) self.clauses += [[cell for row in self.grid[0] for cell in row]] print_message("Number of clauses used: 1", 3, indent = indent + 1, verbosity = verbosity) print_message("Done\n", 3, indent = indent, verbosity = verbosity)
def standardise_varaibles_names(self, indent = 0, verbosity = 0): print_message("Standardising variable names...", 3, indent = indent, verbosity = verbosity) #Give variables standard names and replace stars with new variable names standard_variables_from_input_variables = {} current_variable_number = 0 for t, generation in enumerate(self.grid): for y, row in enumerate(generation): for x, cell in enumerate(row): if cell == "*": self.grid[t][y][x] = "c" + str(current_variable_number) current_variable_number += 1 elif cell not in ["0","1"]: (variable, negated) = variable_from_literal(cell) if not standard_variables_from_input_variables.has_key(variable): standard_variables_from_input_variables[variable] = negate("c" + str(current_variable_number),negated) current_variable_number += 1 self.grid[t][y][x] = negate(standard_variables_from_input_variables[variable],negated) # Rename any literals in clauses for clause_number, clause in enumerate(self.clauses): for literal_number, literal in enumerate(clause): if literal not in ["0", "1"]: (variable, negated) = variable_from_literal(literal) if not standard_variables_from_input_variables.has_key(variable): standard_variables_from_input_variables[variable] = negate("c" + str(current_variable_number),negated) current_variable_number += 1 self.clauses[clause_number][literal_number] = negate(standard_variables_from_input_variables[variable],negated) # Rename any literals in rule for transition, literal in self.rule.items(): if literal not in ["0", "1"]: (variable, negated) = variable_from_literal(literal) if not standard_variables_from_input_variables.has_key(variable): standard_variables_from_input_variables[variable] = negate("c" + str(current_variable_number),negated) current_variable_number += 1 self.rule[transition] = negate(standard_variables_from_input_variables[variable],negated) print_message("Done\n", 3, indent = indent, verbosity = verbosity)
def make_rle(grid, rule=None, determined=None, indent=0, verbosity=0): """Turn a search pattern into nicely formatted string form""" print_message('Format: RLE', 3, indent=indent, verbosity=verbosity) grid = copy.deepcopy(grid) width = len(grid[0][0]) height = len(grid[0]) for t, generation in enumerate(grid): for y, row in enumerate(generation): for x, cell in enumerate(row): assert cell in ["0", "1"], "Cell not equal to 0 or 1 in RLE format" if cell == "0": grid[t][y][x] = "b" elif cell == "1": grid[t][y][x] = "o" rle_string = "x = " + str(width) + ", y = " + str(height) if rule != None: rle_string += ", rule = " + LLS_rules.rulestring_from_rule(rule) rle_string += "\n" rle_string += "$\n".join("".join(line) for line in grid[0]) rle_string += "!" if not determined: rle_string += "\n\nOther generations:\n" rle_string += "\n\n".join("$\n".join("".join(line) for line in generation) for generation in grid[1:]) return rle_string
def force_symmetry(self, symmetry, indent = 0, verbosity = 0): """Changes the grid to enforce the specified symmetry""" symmetry = symmetry.upper() if symmetry == "C1": return None print_message("Enforcing symmetry...", 3, indent = indent, verbosity = verbosity) symmetries = ["C1","C2","C4","D2-","D2/","D2|","D2\\","D4+","D4X","D8"] assert symmetry in symmetries, "Specified symmetry is unknown" width = len(self.grid[0][0]) height = len(self.grid[0]) if symmetry in ["C4","D2/","D2\\","D4X","D8"]: assert width == height, "Pattern must have width = height for the given symmetry" to_force_equal = [] for t, generation in enumerate(self.grid): for y, row in enumerate(generation): for x, cell in enumerate(row): if symmetry == "C2": to_force_equal.append((self.grid[t][y][x], self.grid[t][height-y-1][width-x-1])) if symmetry == "C4": to_force_equal.append((self.grid[t][y][x], self.grid[t][height-x-1][y])) if symmetry in ["D2-","D4+","D8"]: to_force_equal.append((self.grid[t][y][x], self.grid[t][height-y-1][x])) if symmetry in ["D2/","D4X","D8"]: to_force_equal.append((self.grid[t][y][x], self.grid[t][height-x-1][width-y-1])) if symmetry in ["D2|","D4+"]: to_force_equal.append((self.grid[t][y][x], self.grid[t][y][width-x-1])) if symmetry in ["D2\\","D4X"]: to_force_equal.append((self.grid[t][y][x], self.grid[t][x][y])) self.force_equal(to_force_equal) print_message("Done\n", 3, indent = indent, verbosity = verbosity)
def optimise(self, grid_changed = True, clauses_changed = True, indent = 0, verbosity = 0): print_message("Optimising search pattern...", 3, indent = indent, verbosity = verbosity) number_of_improvements = 0 while grid_changed or clauses_changed: number_of_improvements += 1 print_message("Improving search pattern (Pass " + str(number_of_improvements) + ") ...", 3, indent = indent + 1, verbosity = verbosity) grid_changed0 = False clauses_changed0 = False if grid_changed: grid_changed1, clauses_changed1, _ = self.improve_grid(indent = indent + 2, verbosity = verbosity) grid_changed0 = grid_changed0 or grid_changed1 clauses_changed0 = clauses_changed0 or clauses_changed1 if clauses_changed: grid_changed1, clauses_changed1, _ = self.improve_clauses(indent = indent + 2, verbosity = verbosity) grid_changed0 = grid_changed0 or grid_changed1 clauses_changed0 = clauses_changed0 or clauses_changed1 grid_changed = grid_changed0 clauses_changed = clauses_changed0 print_message("Done\n", 3, indent = indent + 1, verbosity = verbosity) self.standardise_varaibles_names() print_message("Done\n", 3, indent = indent, verbosity = verbosity)
def force_period(self, period, x_translate = None, y_translate = None, indent = 0, verbosity = 0): if period != None: width = len(self.grid[0][0]) height = len(self.grid[0]) duration = len(self.grid) if x_translate is None: x_translate = 0 if y_translate is None: y_translate = 0 if (x_translate, y_translate) == (0, 0): if period == 1: print_message("Forcing pattern to be a still life...", 3, indent = indent, verbosity = verbosity) else: print_message("Forcing pattern to have period " + str(period) + " ...", 3, indent = indent, verbosity = verbosity) else: print_message("Forcing pattern to move with speed (" + str(x_translate) + ", " + str(y_translate) + ")c/" + str(period) + " ...", 3, indent = indent, verbosity = verbosity) to_force_equal = [] for t, generation in enumerate(self.grid): for y, row in enumerate(generation): for x, cell in enumerate(row): if t - period in range(duration): if x - x_translate in range(width) and y - y_translate in range(height): to_force_equal.append((self.grid[t][y][x], self.grid[t - period][y - y_translate][x - x_translate])) else: to_force_equal.append((self.grid[t][y][x], "0")) if t + period in range(duration): if x + x_translate in range(width) and y + y_translate in range(height): to_force_equal.append((self.grid[t][y][x], self.grid[t + period][y + y_translate][x + x_translate])) else: to_force_equal.append((self.grid[t][y][x], "0")) self.force_equal(to_force_equal) print_message("Done\n", 3, indent = indent, verbosity = verbosity)
def make_string(self, pattern_output_format = None, determined = None, indent = 0, verbosity = 0): if pattern_output_format == None: pattern_output_format = "rle" print_message('Formatting output...', 3, indent = indent, verbosity = verbosity) assert pattern_output_format in ["rle","csv"], "Format not recognised" if not determined: print_message('(Including all generations)', 3, indent = indent, verbosity = verbosity) if pattern_output_format == "rle": output_string = LLS_formatting.make_rle(self.grid, rule = self.rule, determined = determined, indent = indent + 1, verbosity = verbosity) elif pattern_output_format == "csv": output_string = LLS_formatting.make_csv(self.grid, ignore_transition = self.ignore_transition, rule = self.rule, determined = determined, indent = indent + 1, verbosity = verbosity) print_message('Done\n', 3, indent = indent, verbosity = verbosity) return output_string
def improve_grid(self, indent = 0, verbosity = 0): print_message("Improving grid...", 3, indent = indent, verbosity = verbosity) parents_dict = {} to_force_equal = [] grid_changed0 = False outer_totalistic = LLS_rules.outer_totalistic(self.rule) for t, generation in enumerate(self.grid): if t > 0: for y, row in enumerate(generation): for x, cell in enumerate(row): predecessor_cell = self.grid[t - 1][y][x] neighbours = neighbours_from_coordinates(self.grid, x, y, t) if not self.ignore_transition[t][y][x]: parents = [predecessor_cell] + list(LLS_rules.sort_neighbours(neighbours, outer_totalistic)) parents_string = str(parents) if parents_dict.has_key(parents_string): self.grid[t][y][x] = parents_dict[parents_string] if self.grid[t][y][x] != cell: grid_changed0 = True to_force_equal.append((parents_dict[parents_string],cell)) self.ignore_transition[t][y][x] = True elif all(parent in ["0", "1"] for parent in parents): BS_letter = ["B", "S"][["0", "1"].index(predecessor_cell)] transition = LLS_rules.transition_from_cells(neighbours) child = self.rule[BS_letter + transition] self.grid[t][y][x] = child if self.grid[t][y][x] != cell: grid_changed0 = True to_force_equal.append((cell, child)) self.ignore_transition[t][y][x] = True parents_dict[parents_string] = child else: parents_dict[parents_string] = cell variables = [] for literal in [cell, predecessor_cell] + neighbours: if literal not in ["0", "1"]: variable, _ = variable_from_literal(literal) if variable not in variables: variables.append(variable) booleans = [False, True] transition_redundant = True variables_true = set(range(len(variables))) variables_false = set(range(len(variables))) variables_equal = set(itertools.combinations(range(len(variables)),2)) variables_unequal = set(itertools.combinations(range(len(variables)),2)) for variables_alive in itertools.product(booleans, repeat=len(variables)): dead_literals = map(negate, variables, variables_alive) if cell == "0": cell_alive = False elif cell not in dead_literals: cell_alive = True else: cell_alive = False if predecessor_cell == "0": predecessor_cell_alive = False elif predecessor_cell not in dead_literals: predecessor_cell_alive = True else: predecessor_cell_alive = False neighbours_alive = [] for neighbour in neighbours: if neighbour == "0": neighbours_alive.append(False) elif neighbour not in dead_literals: neighbours_alive.append(True) else: neighbours_alive.append(False) BS_letter = "S" if predecessor_cell_alive else "B" transition = LLS_rules.transition_from_cells(neighbours_alive) transition_variable = self.rule[BS_letter + transition] if (transition_variable not in ["0", "1"]) or (transition_variable == "0" and not cell_alive) or (transition_variable == "1" and cell_alive): to_remove = [] for variable_number in variables_true: if not variables_alive[variable_number]: to_remove.append(variable_number) variables_true.difference_update(to_remove) to_remove = [] for variable_number in variables_false: if variables_alive[variable_number]: to_remove.append(variable_number) variables_false.difference_update(to_remove) to_remove = [] for variable_number_0, variable_number_1 in variables_equal: if variables_alive[variable_number_0] != variables_alive[variable_number_1]: to_remove.append((variable_number_0, variable_number_1)) variables_equal.difference_update(to_remove) to_remove = [] for variable_number_0, variable_number_1 in variables_unequal: if variables_alive[variable_number_0] == variables_alive[variable_number_1]: to_remove.append((variable_number_0, variable_number_1)) variables_unequal.difference_update(to_remove) if (transition_variable not in ["0", "1"]) or (transition_variable == "0" and cell_alive) or (transition_variable == "1" and not cell_alive): transition_redundant = False if transition_redundant: self.ignore_transition[t][y][x] = True for variable_number in variables_true: to_force_equal.append((variables[variable_number], "1")) for variable_number in variables_false: to_force_equal.append((variables[variable_number], "0")) for variable_number_0, variable_number_1 in variables_equal: to_force_equal.append((variables[variable_number_0], variables[variable_number_1])) for variable_number_0, variable_number_1 in variables_unequal: to_force_equal.append((variables[variable_number_0], negate(variables[variable_number_1]))) grid_changed1, clauses_changed, rule_changed = self.force_equal(to_force_equal) grid_changed = grid_changed0 or grid_changed1 print_message("Done\n", 3, indent = indent, verbosity = verbosity) return grid_changed, clauses_changed, rule_changed
def rule_from_rulestring(rulestring, indent = 0, verbosity = 0): if rulestring == None: return None else: rule = {} rulestring = rulestring.strip() original_rulestring = rulestring partial_flag = False if rulestring[0] == "{": rule_unsanitized = ast.literal_eval(rulestring) for BS_letter in "BS": for number_of_neighbours in "012345678": for character in possible_transitions[number_of_neighbours]: literal = LLS_formatting.standard_form_literal(str(rule_unsanitized[BS_letter + number_of_neighbours + character])) assert literal[-1] not in ['\xe2\x80\x99', "'"], "Can't ignore transition in rule" rule[BS_letter + number_of_neighbours + character] = literal return rule elif rulestring[0] in ["p", "P"]: partial_flag = True if len(rulestring) == 1: rulestring = "B012345678/S012345678" else: rulestring = rulestring[1:] rulestring = re.sub(' ', '', rulestring.upper()) rulestrings = re.split("/", rulestring) if len(rulestrings) == 1: assert "B" in rulestring or "S" in rulestring, 'Rule sting not recognised (no "B" or "S")' B_position = rulestring.find("B") S_position = rulestring.find("S") rulestring = rulestring.strip("BS") rulestrings = re.split("[BS]*", rulestring) assert len(rulestrings) < 3, "Rule sting not recognised" if B_position > S_position: birth_string = rulestrings[1] if len(rulestrings) == 2 else "" survival_string = rulestrings[0] else: birth_string = rulestrings[0] survival_string = rulestrings[1] if len(rulestrings) == 2 else "" else: assert len(rulestrings) == 2, 'Rule sting not recognised (too many "/"s)' if "S" in rulestrings[0] or "B" in rulestrings[1]: birth_string = rulestrings[1] survival_string = rulestrings[0] else: birth_string = rulestrings[0] survival_string = rulestrings[1] assert "S" not in birth_string and "B" not in survival_string, "Rule sting not recognised" birth_string = re.sub('B', '', birth_string).lower() survival_string = re.sub('S', '', survival_string).lower() assert (birth_string == "" or birth_string[0] in "012345678") and (survival_string == "" or survival_string[0] in "012345678"), "Rule sting not recognised" if partial_flag: variable_number = 0 for BS_letter, rulestring in zip(["B", "S"], [birth_string, survival_string]): transitions = [] previous_number = 0 if rulestring != "": for position in range(1,len(rulestring)): if rulestring[position] in "012345678": transitions.append(rulestring[previous_number:position]) previous_number = position transitions.append(rulestring[previous_number:]) for transition in transitions: number_of_neighbours = transition[0] if not partial_flag: if len(transition) == 1: for character in possible_transitions[number_of_neighbours]: rule[BS_letter + number_of_neighbours + character] = "1" elif transition[1] == "-": banned_characters = transition[2:] assert all(character in possible_transitions[number_of_neighbours] for character in banned_characters), "Unrecognized character" for character in possible_transitions[number_of_neighbours]: if character in banned_characters: rule[BS_letter + number_of_neighbours + character] = "0" else: rule[BS_letter + number_of_neighbours + character] = "1" else: characters = transition[1:] assert all(character in possible_transitions[number_of_neighbours] for character in characters), "Unrecognized character" for character in possible_transitions[number_of_neighbours]: if character in characters: rule[BS_letter + number_of_neighbours + character] = "1" else: rule[BS_letter + number_of_neighbours + character] = "0" else: if len(transition) == 1: for character in possible_transitions[number_of_neighbours]: rule[BS_letter + number_of_neighbours + character] = "rule_variable_" + str(variable_number) variable_number += 1 else: characters = transition[1:] if "-" in characters: characters, banned_characters = re.split("-", characters) else: banned_characters = "" for character in possible_transitions[number_of_neighbours]: if character in characters: rule[BS_letter + number_of_neighbours + character] = "1" elif character in banned_characters: rule[BS_letter + number_of_neighbours + character] = "0" else: rule[BS_letter + number_of_neighbours + character] = "rule_variable_" + str(variable_number) variable_number += 1 for number_of_neighbours in "012345678": if not rule.has_key(BS_letter + number_of_neighbours + "c"): for character in possible_transitions[number_of_neighbours]: rule[BS_letter + number_of_neighbours + character] = "0" new_rulestring = rulestring_from_rule(rule) if original_rulestring != new_rulestring: print_message("Rulestring parsed as: " + new_rulestring,3, indent = indent, verbosity = verbosity) return rule
def use_solver(solver, file_name, parameters=None, timeout=None, indent=0, verbosity=0): if parameters != None: parameter_list = parameters.strip(" ").split(" ") else: parameter_list = [] solver_path = sys.path[0] + "/solvers/" + solver if solver in ["minisat", "MapleCOMSPS", "MapleCOMSPS_LRB", "riss"]: command = [solver_path, file_name, "temp_SAT_solver_output" ] + parameter_list elif solver in ["lingeling", "plingeling", "treengeling"]: command = [solver_path, file_name] + parameter_list elif solver in ["glucose", "glucose-syrup"]: command = [solver_path, file_name, "-model"] + parameter_list else: assert False, "Solver not recognised" solver_process = subprocess.Popen(command, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) timeout_flag = [ False ] #We want the flag to be mutable, so we put it into a little box. def timeout_function(solver_process, timeout_flag): solver_process.kill() timeout_flag[0] = "TIMEOUT" timeout_timer = threading.Timer(timeout, timeout_function, [solver_process, timeout_flag]) print_message('Solving with "' + solver + '" ... (Start time: ' + time.ctime() + ")", 3, indent=indent, verbosity=verbosity) try: start_time = time.time() timeout_timer.start() out, error = solver_process.communicate() except KeyboardInterrupt: solver_process.kill() timeout_flag[0] = "SIGINT" finally: timeout_timer.cancel() time_taken = time.time() - start_time if not timeout_flag[0]: print_message('Done\n', 3, indent=indent, verbosity=verbosity) print_message('Formatting SAT solver output...', 3, indent=indent, verbosity=verbosity) if solver in ["minisat", "MapleCOMSPS", "MapleCOMSPS_LRB", "riss"]: solution = LLS_files.string_from_file("temp_SAT_solver_output", indent=indent + 1, verbosity=verbosity) print_message('Removing SAT solver output file...', 3, indent=indent + 1, verbosity=verbosity) os.remove("temp_SAT_solver_output") print_message('Done\n', 3, indent=indent + 1, verbosity=verbosity) elif solver in ["lingeling", "plingeling", "treengeling"]: solution = out.split("\ns ")[1].split("\nc")[0].split("\nv ") solution = solution[0] + "\n" + " ".join(solution[1:]) elif solver in ["glucose", "glucose-syrup"]: try: solution = out.split("\ns ")[1] solution = re.sub("s ", "", solution) solution = re.sub("v ", "", solution) except IndexError: solution = "UNSAT\n" if solver == "MapleCOMSPS_LRB": if solution == "": solution = "UNSAT\n" if solver == "riss": solution = re.sub("s ", "", solution) solution = re.sub("v ", "", solution) if solution == "UNSATISFIABLE\n": solution = "UNSAT\n" print_message("SAT solver output:", 3, indent=indent + 1, verbosity=verbosity) print_message(out, 3, indent=indent + 2, verbosity=verbosity) print_message('Error (if any): "' + error + '"', 3, indent=indent + 1, verbosity=verbosity) print_message('Time taken: ' + str(time_taken), 3, indent=indent + 1, verbosity=verbosity) print_message('Done\n', 3, indent=indent, verbosity=verbosity) else: print_message( 'Timed out\n', 3, indent=indent, verbosity=verbosity ) #TODO: How to capture output from SAT solver after killing it? solution = "TIMEOUT\n" return solution, time_taken
def SAT_solve(DIMACS_string, solver=None, parameters=None, timeout=None, save_dimacs=None, dry_run=None, indent=0, verbosity=0): """Solve the given DIMACS problem, using the specified SAT solver""" print_message('Preparing SAT solver input...', 3, indent=indent, verbosity=verbosity) solvers = [ "minisat", "MapleCOMSPS", "MapleCOMSPS_LRB", "riss", "glucose", "glucose-syrup", "lingeling", "plingeling", "treengeling" ] try: if solver is None: solver = "glucose-syrup" # Default solver elif int(solver) in range(len(solvers)): solver = solvers[solver] # Allow solver to be specified by number except ValueError: pass assert solver in solvers, "Solver not found" if isinstance(save_dimacs, basestring): dimacs_file = save_dimacs else: dimacs_file = "lls_dimacs.cnf" file_number = 0 while os.path.isfile(dimacs_file): file_number += 1 dimacs_file = "lls_dimacs" + str(file_number) + ".cnf" # The solvers prefer their input as a file, so write it out LLS_files.file_from_string(dimacs_file, DIMACS_string, indent=indent + 1, verbosity=verbosity) print_message('Done\n', 3, indent=indent, verbosity=verbosity) if not dry_run: solution, time_taken = use_solver(solver, dimacs_file, parameters=parameters, timeout=timeout, indent=indent, verbosity=verbosity) else: solution = "DRYRUN\n" time_taken = None if save_dimacs == None: print_message('Removing DIMACS file...', 3, indent=indent, verbosity=verbosity) try: os.remove(dimacs_file) except OSError as e: if e.errno == errno.ENOENT: print_message('DIMACS file "' + dimacs_file + '" not found', 3, indent=indent + 1, verbosity=verbosity) else: raise print_message('Done\n', 3, indent=indent, verbosity=verbosity) return solution, time_taken
def improve_clauses(self, indent = 0, verbosity = 0): print_message("Improving clause list...", 3, indent = indent, verbosity = verbosity) print_message('Tidying clauses...', 3, indent = indent + 1, verbosity = verbosity) clauses_to_remove = set() to_force_equal = [] clauses_seen_so_far = set() clauses_changed0 = False for clause_number, clause in enumerate(self.clauses): remove_flag = False if "1" in clause: remove_flag = True else: for literal in clause: if negate(literal) in clause: remove_flag = True if remove_flag: clauses_to_remove.add(clause_number) else: starting_length = len(self.clauses[clause_number]) self.clauses[clause_number] = [literal for literal in sorted(list(set(clause))) if literal != "0"] number_of_literals = len(self.clauses[clause_number]) if starting_length != number_of_literals: clauses_changed0 = True if number_of_literals == 1: to_force_equal.append((self.clauses[clause_number][0],"1")) elif number_of_literals == 2: if str(sorted([negate(self.clauses[clause_number][0]), negate(self.clauses[clause_number][1])])) in clauses_seen_so_far: to_force_equal.append((self.clauses[clause_number][0],negate(self.clauses[clause_number][1]))) elif str(sorted([self.clauses[clause_number][0], negate(self.clauses[clause_number][1])])) in clauses_seen_so_far: to_force_equal.append((self.clauses[clause_number][0],"1")) elif str(sorted([negate(self.clauses[clause_number][0]), self.clauses[clause_number][1]])) in clauses_seen_so_far: to_force_equal.append((self.clauses[clause_number][1],"1")) else: clauses_seen_so_far.add(str(self.clauses[clause_number])) if clauses_to_remove: clauses_changed0 = True self.clauses = [clause for clause_number, clause in enumerate(self.clauses) if clause_number not in clauses_to_remove] print_message('Done\n', 3, indent = indent + 1, verbosity = verbosity) print_message('Removing duplicate clauses...', 3, indent = indent + 1, verbosity = verbosity) seen = set() temporary_list = [] for clause in self.clauses: clause.sort() clause_string = str(clause) if clause_string not in seen: seen.add(clause_string) temporary_list.append(clause) else: clauses_changed0 = True self.clauses = temporary_list print_message('Done\n', 3, indent = indent + 1, verbosity = verbosity) print_message('Making deductions...', 3, indent = indent + 1, verbosity = verbosity) grid_changed, clauses_changed1, rule_changed = self.force_equal(to_force_equal) print_message('Done\n', 3, indent = indent + 1, verbosity = verbosity) clauses_changed = clauses_changed0 or clauses_changed1 print_message("Done\n", 3, indent = indent, verbosity = verbosity) return grid_changed, clauses_changed, rule_changed
def preprocess(search_pattern, symmetry="C1", period=None, x_translate=0, y_translate=0, force_movement=False, method=None, force_at_most=False, force_at_least=False, force_change=[], force_nonempty=False, force_evolution=True, indent=0, verbosity=0): print_message('Preprocessing...', indent=indent, verbosity=verbosity) search_pattern.force_symmetry(symmetry, indent=indent + 1, verbosity=verbosity) search_pattern.force_period(period, x_translate, y_translate, indent=indent + 1, verbosity=verbosity) if force_nonempty: search_pattern.force_nonempty(indent=indent + 1, verbosity=verbosity) if force_movement: search_pattern.force_movement(indent=indent + 1, verbosity=verbosity) for t_0, t_1 in force_change: search_pattern.force_change(t_0, t_1, indent=indent + 1, verbosity=verbosity) for arguments in force_at_least: amount, ts = arguments[0], arguments[1:] if ts == []: ts = [0] if len(ts) == 1: print_message('Enforcing at least ' + str(amount) + ' cells in generation ' + str(ts[0]) + ' ...', 3, indent=indent + 1, verbosity=verbosity) else: print_message('Enforcing at least ' + str(amount) + ' cells in generations ' + str(ts) + ' ...', 3, indent=indent + 1, verbosity=verbosity) literals = [] literals = [ literal for t in ts for row in search_pattern.grid[t] for literal in row ] search_pattern.force_at_least(literals, amount, indent=indent + 1, verbosity=verbosity) print_message('Done\n', 3, indent=indent + 1, verbosity=verbosity) for arguments in force_at_most: amount, ts = arguments[0], arguments[1:] if ts == []: ts = [0] if len(ts) == 1: print_message('Enforcing at most ' + str(amount) + ' cells in generation ' + str(ts[0]) + ' ...', 3, indent=indent + 1, verbosity=verbosity) else: print_message('Enforcing at most ' + str(amount) + ' cells in generations ' + str(ts) + ' ...', 3, indent=indent + 1, verbosity=verbosity) literals = [ literal for t in ts for row in search_pattern.grid[t] for literal in row ] search_pattern.force_at_most(literals, amount, indent=indent + 1, verbosity=verbosity) print_message('Done\n', 3, indent=indent + 1, verbosity=verbosity) search_pattern.optimise(indent=indent + 1, verbosity=verbosity) if force_evolution: search_pattern.force_evolution(method=method, indent=indent + 1, verbosity=verbosity) search_pattern.optimise(grid_changed=False, clauses_changed=True, indent=indent + 1, verbosity=verbosity) print_message("Search pattern optimised to:\n" + search_pattern.make_string(pattern_output_format="csv") + "\n", 3, indent=indent + 1, verbosity=verbosity) DIMACS_string, DIMACS_variables_from_CNF_list_variables, number_of_variables, number_of_clauses = LLS_DIMACS.DIMACS_from_CNF_list( search_pattern.clauses, indent=indent + 1, verbosity=verbosity) number_of_cells = search_pattern.number_of_cells() print_message('Done\n', indent=indent, verbosity=verbosity) return DIMACS_string, DIMACS_variables_from_CNF_list_variables, number_of_variables, number_of_clauses, number_of_cells
def preprocess_solve_and_postprocess(search_pattern, symmetry="C1", period=None, x_translate=0, y_translate=0, force_movement=False, solver=None, parameters=None, timeout=None, save_dimacs=None, method=None, force_at_most=[], force_at_least=[], force_change=[], force_nonempty=False, force_evolution=True, dry_run=False, number_of_solutions=None, pattern_output_format="rle", output_file_name=None, indent=0, verbosity=0): (solution, sat, number_of_cells, number_of_variables, number_of_clauses, active_width, active_height, active_duration, time_taken) = preprocess_and_solve(search_pattern, symmetry=symmetry, period=period, x_translate=x_translate, y_translate=y_translate, force_movement=force_movement, solver=solver, parameters=parameters, timeout=timeout, save_dimacs=save_dimacs, method=method, force_at_most=force_at_most, force_at_least=force_at_least, force_change=force_change, force_nonempty=force_nonempty, force_evolution=force_evolution, dry_run=dry_run, indent=indent, verbosity=verbosity) determined = search_pattern.deterministic() solutions = [] if sat == "SAT": solutions.append(solution) output_string = solution.make_string( pattern_output_format=pattern_output_format, determined=determined, indent=indent, verbosity=verbosity) else: output_string = ["Unsatisfiable", "Timed Out", "Dry run"][["UNSAT", "TIMEOUT", "DRYRUN"].index(sat)] print_message(output_string + "\n", 1, indent=indent, verbosity=verbosity) if output_file_name: print_message('Writing to output file...', indent=indent, verbosity=verbosity) LLS_files.append_to_file_from_string(output_file_name, output_string, indent=indent + 1, verbosity=verbosity) print_message('Done\n', indent=indent, verbosity=verbosity) if number_of_solutions and sat == "SAT" and not dry_run: if number_of_solutions != "Infinity": number_of_solutions = int(number_of_solutions) enough_solutions = (len(solutions) >= number_of_solutions) else: enough_solutions = False while sat == "SAT" and not enough_solutions: search_pattern.force_distinct(solution, determined=determined) (solution, sat, _, _, _, _, _, _, extra_time_taken) = preprocess_and_solve(search_pattern, solver=solver, parameters=parameters, timeout=timeout, method=method, force_evolution=False, indent=indent, verbosity=verbosity) time_taken += extra_time_taken if sat == "SAT": solutions.append(solution) output_string = solution.make_string( pattern_output_format=pattern_output_format, determined=determined, indent=indent, verbosity=verbosity) if verbosity == 1: print_message("", 1, indent=indent, verbosity=verbosity) else: output_string = ["Unsatisfiable", "Timed Out", "Dry run"][["UNSAT", "TIMEOUT", None].index(sat)] print_message(output_string + "\n", 1, indent=indent, verbosity=verbosity) if output_file_name: print_message('Writing output file...', indent=indent, verbosity=verbosity) LLS_files.append_to_file_from_string(output_file_name, output_string, indent=indent + 1, verbosity=verbosity) print_message('Done\n', indent=indent, verbosity=verbosity) if number_of_solutions != "Infinity": enough_solutions = (len(solutions) >= number_of_solutions) sat = "SAT" print_message('Total solver time: ' + str(time_taken), indent=indent, verbosity=verbosity) return solutions, sat, number_of_cells, number_of_variables, number_of_clauses, active_width, active_height, active_duration, time_taken
def DIMACS_from_CNF_list(clauses, indent=0, verbosity=0): """Convert CNF list-of-lists to DIMACS format""" clauses_copy = copy.deepcopy(clauses) print_message("Converting to DIMACS format...", 3, indent=indent, verbosity=verbosity) print_message("Subsituting in DIMACS variable names...", 3, indent=indent + 1, verbosity=verbosity) # In DIMACS format variables are called "1", "2", ... . So we will rename # all our variables. This keeps track of which numbers we've used numbers_used = 0 # We'll also build a dictionary of which of our old variables recieves # which new name DIMACS_variables_from_CNF_list_variables = {} for clause_number, clause in enumerate(clauses_copy): for literal_number, literal in enumerate(clause): (variable, negated) = variable_from_literal(literal, DIMACS=True) # If we haven't seen it before then add it to the dictionary if not DIMACS_variables_from_CNF_list_variables.has_key(variable): DIMACS_variables_from_CNF_list_variables[variable] = str( numbers_used + 1) numbers_used += 1 # We've used another number, so increment the counter # Substitute in its new name clauses_copy[clause_number][literal_number] = negate( DIMACS_variables_from_CNF_list_variables[variable], negated, DIMACS=True) print_message("Done\n", 3, indent=indent + 1, verbosity=verbosity) number_of_clauses = len(clauses_copy) number_of_variables = numbers_used # From our list of clauses create a string according to the DIMACS format print_message("Creating DIMACS string...", 3, indent=indent + 1, verbosity=verbosity) DIMACS_string = "p cnf " + str(number_of_variables) + " " + str(number_of_clauses) + \ "\n" + "\n".join([(" ".join(clause) + " 0") for clause in clauses_copy]) print_message("Done\n", 3, indent=indent + 1, verbosity=verbosity) print_message("Done\n", 3, indent=indent, verbosity=verbosity) return DIMACS_string, DIMACS_variables_from_CNF_list_variables, number_of_variables, number_of_clauses
def force_evolution(self, method=None, indent = 0, verbosity = 0): """Adds clauses that force the search pattern to obey the transition rule""" # Methods: # 0. An implementation of the scheme Knuth describes in TAOCP Volume 4, Fascile 6, solution to exercise 65b (57 clauses and 13 auxillary variables per cell) # 1. An implementation of the naive scheme Knuth gives in the solution to exercise 65a (190 clauses and 0 auxillary variables per cell) # 2. A very naive scheme just listing all possible predecessor neighbourhoods (512 clauses and 0 auxillary variables per cell) print_message("Enforcing evolution rule...", 3, indent = indent, verbosity = verbosity) if method is None: if LLS_rules.rulestring_from_rule(self.rule) == "B3/S23": method = 1 # Optimal method for Life else: method = 2 # Default method assert method in range(3), "Method not found" assert method == 2 or LLS_rules.rulestring_from_rule(self.rule) == "B3/S23", "Rules other than Life can only use method 2" print_message("Method: " + str(method), 3, indent = indent + 1, verbosity = verbosity) clauses = [] # Iterate over all cells not in the first generation for t, generation in enumerate(self.grid): if t > 0: for y, row in enumerate(generation): for x, cell in enumerate(row): if not self.ignore_transition[t][y][x]: if method == 0: clauses += LLS_taocp_variable_scheme.transition_rule(self.grid, x, y, t) elif method == 1: predecessor_cell = self.grid[t - 1][y][x] neighbours = neighbours_from_coordinates(self.grid, x, y, t) #TODO: Do the following clauses work if there aren't eight neighbours? # If any four neighbours were live, then the cell is # dead for four_neighbours in itertools.combinations(neighbours, 4): clause = implies(four_neighbours, negate(cell)) clauses.append(clause) # If any seven neighbours were dead, the cell is dead for seven_neighbours in itertools.combinations(neighbours, 7): clause = implies(map(negate,seven_neighbours), negate(cell)) clauses.append(clause) # If the cell was dead, and any six neighbours were # dead, the cell is dead for six_neighbours in itertools.combinations(neighbours, 6): clause = implies([negate(predecessor_cell)] + map(negate,six_neighbours), negate(cell)) clauses.append(clause) # If three neighbours were alive and five were dead, # then the cell is live for three_neighbours in itertools.combinations(neighbours, 3): neighbours_counter = collections.Counter(neighbours) neighbours_counter.subtract(three_neighbours) three_neighbours, five_neighbours = list(three_neighbours), list(neighbours_counter.elements()) clause = implies(three_neighbours + map(negate, five_neighbours), cell) clauses.append(clause) # Finally, if the cell was live, and two neighbours # were live, and five neighbours were dead, then the # cell is live (independantly of the final neighbour) for two_neighbours in itertools.combinations(neighbours, 2): neighbours_counter = collections.Counter(neighbours) neighbours_counter.subtract(two_neighbours) two_neighbours, five_neighbours = list(two_neighbours), list(neighbours_counter.elements())[1:] clause = implies([predecessor_cell] + two_neighbours + map(negate, five_neighbours), cell) clauses.append(clause) elif method == 2: predecessor_cell = self.grid[t - 1][y][x] neighbours = neighbours_from_coordinates(self.grid,x,y,t) booleans = [True, False] # For each combination of neighbourhoods for predecessor_cell_alive in booleans: for neighbours_alive in itertools.product(booleans,repeat=8): BS_letter = "S" if predecessor_cell_alive else "B" transition = LLS_rules.transition_from_cells(neighbours_alive) transition_literal = self.rule[BS_letter + transition] clauses.append(implies([transition_literal] + [negate(predecessor_cell, not predecessor_cell_alive)] + map(negate, neighbours, map(lambda P: not P, neighbours_alive)), cell)) clauses.append(implies([negate(transition_literal)] + [negate(predecessor_cell, not predecessor_cell_alive)] + map(negate, neighbours, map(lambda P: not P, neighbours_alive)), negate(cell))) print_message("Number of clauses used: " + str(len(clauses)), 3, indent = indent + 1, verbosity = verbosity) print_message("Done\n", 3, indent = indent, verbosity = verbosity) self.clauses += clauses
def parse_input_string(input_string, indent=0, verbosity=0): """Transforms a "search pattern" given as a string into a SearchPattern""" print_message("Parsing input pattern...", 3, indent=indent, verbosity=verbosity) # Remove any trailing (or leading) whitespace and commas input_string = input_string.strip(", \t\n\r\f\v") # Break down string into list-of-lists-of-lists split_by_generation = re.split( "[ ,\t]*(?:\n|\r|(?:\r\n))(?:[ ,\t]*(?:\n|\r|(?:\r\n)))+[ ,\t]*", # Split on at least two newlines (or carriage returns) and any commas or spaces input_string) split_by_line = [ re.split( "[ ,\t]*(?:\n|\r|(?:\r\n))[ ,\t]*", # Split on signle newline (or carriage return) and any ammount of commas or spaces generation) for generation in split_by_generation ] grid = [ [ re.split( "[ ,\t]*", # Split on any amount of commas or spaces line) for line in generation ] for generation in split_by_line ] # Check that the list is cuboidal assert (all( len(generation) == len(grid[0]) for generation in grid) and all(all( len(line) == len(grid[0][0]) for line in generation) for generation in grid)), \ "Search pattern is not cuboidal" # Tidy up any weird inputs grid = [[[standard_form_literal(cell) for cell in row] for row in generation] for generation in grid] # Create array which says when a "'" means that a transition should be ignored ignore_transition = [[[(cell[-1] == "'") for cell in row] for row in generation] for generation in grid] grid = [ [ [ cell.rstrip("'") # The "'"s are now unnecessary for cell in line ] for line in generation ] for generation in grid ] # #Check that "" isn't being used as a variable name # assert all(all(all( # cell not in ["","-"] # for cell in row) for row in generation) for generation in grid), \ # "Malformed input and/or null string used as variable name" print_message("Done\n", 3, indent=indent, verbosity=verbosity) return grid, ignore_transition
def preprocess_and_solve(search_pattern, symmetry="C1", period=None, x_translate=0, y_translate=0, force_movement=False, solver=None, parameters=None, timeout=None, save_dimacs=None, method=None, force_at_most=[], force_at_least=[], force_change=[], force_nonempty=False, force_evolution=True, dry_run=False, indent=0, verbosity=0): try: DIMACS_string, DIMACS_variables_from_CNF_list_variables, number_of_variables, number_of_clauses, number_of_cells = preprocess( search_pattern, symmetry=symmetry, period=period, x_translate=x_translate, y_translate=y_translate, force_movement=force_movement, method=method, force_at_most=force_at_most, force_at_least=force_at_least, force_change=force_change, force_nonempty=force_nonempty, force_evolution=force_evolution, indent=indent, verbosity=verbosity) width = len(search_pattern.grid[0][0]) height = len(search_pattern.grid[0]) duration = len(search_pattern.grid) active_width = sum( any( any((search_pattern.grid[z][y][x] not in ["0", "1"]) for y in range(height)) for z in range(duration)) for x in range(width)) active_height = sum( any( any((search_pattern.grid[z][y][x] not in ["0", "1"]) for z in range(duration)) for x in range(width)) for y in range(height)) active_duration = sum( any( any((search_pattern.grid[z][y][x] not in ["0", "1"]) for x in range(width)) for y in range(height)) for z in range(duration)) print_message('Number of undetermined cells: ' + str(number_of_cells), indent=indent, verbosity=verbosity) print_message('Number of variables: ' + str(number_of_variables), indent=indent, verbosity=verbosity) print_message('Number of clauses: ' + str(number_of_clauses) + "\n", indent=indent, verbosity=verbosity) print_message('Active width: ' + str(active_width), indent=indent, verbosity=verbosity) print_message('Active height: ' + str(active_height), indent=indent, verbosity=verbosity) print_message('Active duration: ' + str(active_duration) + "\n", indent=indent, verbosity=verbosity) except UnsatInPreprocessing: solution, sat, number_of_cells, number_of_variables, number_of_clauses, active_width, active_height, active_duration, time_taken = None, "UNSAT", None, None, None, None, None, None, 0 print_message("Unsatisfiability proved in preprocessing", indent=indent + 1, verbosity=verbosity) print_message('Done\n', indent=indent, verbosity=verbosity) else: solution, sat, time_taken = solve( search_pattern, DIMACS_string, DIMACS_variables_from_CNF_list_variables, solver=solver, parameters=parameters, timeout=timeout, save_dimacs=save_dimacs, dry_run=dry_run, indent=indent, verbosity=verbosity) if not dry_run: print_message('Time taken: ' + str(time_taken) + " seconds\n", indent=indent, verbosity=verbosity) return solution, sat, number_of_cells, number_of_variables, number_of_clauses, active_width, active_height, active_duration, time_taken
def append_to_file_from_string(file_name, input_string, indent = 0, verbosity = 0): """Append string to file""" print_message('Writing to file "' + file_name + '" ...', 3, indent = indent, verbosity = verbosity) with open(file_name, "a+") as output_file: output_file.write(input_string) print_message('Done\n', 3, indent = indent, verbosity = verbosity)
def force_at_least(self, literals, at_least, indent = 0, verbosity = 0): """Adds clauses forcing at least at_least of literals to be true""" name, number_of_clauses = self.define_cardinality_variable(literals, at_least) self.clauses.append([name]) print_message("Number of clauses used: " + str(number_of_clauses + 1), 3, indent = indent + 1, verbosity = verbosity)