def string_from_file(file_name, indent=0): """Read file into string""" print_message('Reading file "' + file_name + '" ...', 3, indent=indent) with open(file_name, "r") as pattern_file: input_string = pattern_file.read() print_message('Done\n', 3, indent=indent) return input_string
def make_string(self, indent=0): print_message('Writing clauses into DIMACS format ...', 3, indent=indent) return "p cnf " + str(self.number_of_variables) + " " + str( len(self.clause_set)) + "\n" + "".join(self.clause_set) print_message('Done\n', 3, indent=indent)
def object_from_file(file_name, indent=0): """Load object from file""" print_message('Reading file "' + file_name + '" ...', 3, indent=indent) with open(file_name, "rb") as object_file: input_object = pickle.load(object_file) print_message('Done\n', 3, indent=indent) return input_object
def force_population_exactly(self, constraint, indent=0): (times, population) = constraint print_message("Forcing the population in generation" + ("s" if len(times) > 1 else "") + " " + ", ".join( str(t) for t in times) + " to be exactly " + str(population), 3, indent=indent) literals = [cell for t in times for row in self.grid[t] for cell in row] self.force_exactly(literals, population, indent=indent + 1) print_message("Done\n", 3, indent=indent)
def make_csv(grid, ignore_transition=None, background_grid=None, background_ignore_transition=None, rule=None, determined=None, show_background=None, indent=0): """Turn a search pattern in list form into nicely formatted csv string""" print_message('Format: csv', 3, indent=indent) grid = space_evenly(grid, ignore_transition) csv_string = "" if rule is not None: csv_string += "Rule = " + rulestring_from_rule(rule) + "\n" csv_string += "\n".join(",".join(line) for line in grid[0]) + "\n" if not determined: csv_string += "\n" + "\n\n".join("\n".join(",".join(line) for line in generation) for generation in grid[1:]) + "\n" if show_background: csv_string += "\nBackground:\n" background_grid = space_evenly(background_grid, background_ignore_transition) csv_string += "\n" + "\n\n".join( "\n".join(",".join(line) for line in generation) for generation in background_grid) + "\n" return csv_string
def remove_redundancies(self, indent=0): print_message("Removing redundant transitions...", 3, indent=indent) parents_dict = {} to_force_equal = [] background_duration = len(self.background_grid) for t, generation in enumerate(self.background_grid): for y, row in enumerate(generation): for x, cell in enumerate(row): predecessor_cell = self.background_grid[(t - 1) % background_duration][y][x] neighbours = neighbours_from_coordinates(self.background_grid, x, y, t, background_grid=self.background_grid) if not self.background_ignore_transition[t][y][x]: parents = [predecessor_cell] + list(src.rules.sort_neighbours(neighbours)) parents_string = str(parents) if parents_string in parents_dict: self.background_grid[t][y][x] = parents_dict[parents_string] to_force_equal.append((parents_dict[parents_string], cell)) self.background_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 = src.rules.transition_from_cells(neighbours) child = self.rule[bs_letter + transition] if cell not in ["0", "1"]: self.background_grid[t][y][x] = child to_force_equal.append((cell, child)) self.background_ignore_transition[t][y][x] = True parents_dict[parents_string] = self.background_grid[t][y][x] else: parents_dict[parents_string] = cell self.force_equal(to_force_equal) to_force_equal = [] 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, background_grid=self.background_grid) if not self.ignore_transition[t][y][x]: parents = [predecessor_cell] + list(src.rules.sort_neighbours(neighbours)) parents_string = str(parents) if parents_string in parents_dict: self.grid[t][y][x] = parents_dict[parents_string] 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 = src.rules.transition_from_cells(neighbours) child = self.rule[bs_letter + transition] if cell not in ["0", "1"]: self.grid[t][y][x] = child to_force_equal.append((cell, child)) self.ignore_transition[t][y][x] = True parents_dict[parents_string] = self.grid[t][y][x] else: parents_dict[parents_string] = cell self.force_equal(to_force_equal) print_message("Done\n", 3, indent=indent)
def force_at_least(self, literals, amount, indent=0): """Adds clauses forcing at least the given amount of literals to be true""" starting_number_of_clauses = len(self.clauses.clause_set) name = self.define_cardinality_variable(literals, amount) self.clauses.append([name]) print_message("Number of clauses used: " + str(len(self.clauses.clause_set) - starting_number_of_clauses), 3, indent=indent)
def parse_input_string(input_string, indent=0): """Parses a search pattern given as a string""" print_message("Parsing input pattern...", 3, indent=indent) input_string = format_carriage_returns(input_string) # Remove any comments input_string = re.sub('#.*', '', input_string) # Remove any trailing or leading whitespace and commas input_string = input_string.strip(" ,\t\n") # Break down string into list-of-lists-of-lists split_by_generation = re.split( r"[ ,\t]*\n(?:[ ,\t]*\n)+[ ,\t]*", # Split on at least two newlines and any spaces, commas or tabs input_string) split_by_line = [ re.split( r"[ ,\t]*\n[ ,\t]*", # Split on single newline and any amount of commas or spaces generation) for generation in split_by_generation ] grid = [ [ re.split( r"[ ,\t]+", # Split on any amount of commas or spaces line) for line in generation ] for generation in split_by_line ] 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] in "'’") for cell in row] for row in generation] for generation in grid] grid = [ [ [ cell.rstrip("'’") # The "'"s are now unnecessary for cell in row ] for row in generation ] for generation in grid ] print_message("Done\n", 3, indent=indent) return grid, ignore_transition
def search_pattern_from_string(input_string, indent=0): """Create the grid and ignore_transition of a search pattern from the given string""" grid, ignore_transition = src.formatting.parse_input_string(input_string, indent=indent) print_message("Pattern parsed as:\n" + src.formatting.make_csv(grid, ignore_transition) + "\n", 3, indent=indent) for t, generation in enumerate(grid): for y, row in enumerate(generation): for x, cell in enumerate(row): if cell not in ["0", "1", "*"]: variable, negated = variable_from_literal(cell) grid[t][y][x] = negate("user_input_" + variable, negated) return grid, ignore_transition
def standardise_variables_names(self, indent=0): print_message("Standardising variable names...", 3, indent=indent) # 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.background_grid): for y, row in enumerate(generation): for x, cell in enumerate(row): if cell == "*": self.background_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 variable not in standard_variables_from_input_variables: standard_variables_from_input_variables[variable] = negate( "c" + str(current_variable_number), negated) current_variable_number += 1 self.background_grid[t][y][x] = negate(standard_variables_from_input_variables[variable], negated) 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 variable not in standard_variables_from_input_variables: 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 rule for transition, literal in self.rule.items(): if literal not in ["0", "1"]: (variable, negated) = variable_from_literal(literal) if variable not in standard_variables_from_input_variables: 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)
def sat_solve(search_pattern, solver=None, parameters=None, timeout=None, save_dimacs=None, dry_run=None, indent=0): """Solve the given DIMACS problem, using the specified SAT solver""" print_message('Solving...', indent=indent) if solver is None: solver = src.defaults.solver if solver not in src.defaults.supported_solvers: raise ValueError if save_dimacs is not None: if not isinstance(save_dimacs, str): file_number = 0 while True: save_dimacs = "lls_dimacs" + str(file_number) + ".cnf" file_number += 1 if not os.path.isfile(save_dimacs): break search_pattern.clauses.make_file(save_dimacs, indent=indent + 1) dimacs_string = search_pattern.clauses.make_string(indent=indent + 1) if not dry_run: solution, time_taken = use_solver(solver, dimacs_string, parameters=parameters, timeout=timeout, indent=indent+1) else: solution = "DRYRUN\n" time_taken = None if solution not in ["UNSAT\n", "TIMEOUT\n", "DRYRUN\n"]: sat = "SAT" solution = search_pattern.substitute_solution( solution, indent=indent + 1 ) else: sat = solution[:-1] solution = None print_message('Done\n', indent=indent) return solution, sat, time_taken
def make_string(self, pattern_output_format=None, determined=None, show_background=None, indent=0): if pattern_output_format is None: pattern_output_format = src.defaults.pattern_output_format print_message('Formatting output...', 3, indent=indent) assert pattern_output_format in ["rle", "csv"], "Format not recognised" background_grid = copy.deepcopy(self.background_grid) src.literal_manipulation.offset_background(background_grid, -1, -1, 0) background_ignore_transition = copy.deepcopy(self.background_ignore_transition) src.literal_manipulation.offset_background(background_ignore_transition, -1, -1, 0) if pattern_output_format == "rle": output_string = src.formatting.make_rle( self.grid, background_grid=background_grid, rule=self.rule, determined=determined, show_background=show_background, indent=indent + 1 ) elif pattern_output_format == "csv": output_string = src.formatting.make_csv( self.grid, ignore_transition=self.ignore_transition, background_grid=background_grid, background_ignore_transition=background_ignore_transition, rule=self.rule, determined=determined, show_background=show_background, indent=indent + 1 ) else: raise Exception print_message('Done\n', 3, indent=indent) return output_string
def blank_search_pattern(width, height, duration, indent=0): print_message('Creating spaceship search pattern...', 3, indent=indent) grid = make_grid('*', width, height, duration) print_message("Pattern created:\n" + src.formatting.make_csv(grid) + "\n", 3, indent=indent + 1) print_message('Done\n', 3, indent=indent) return grid
def deterministic(self, indent=0): print_message("Checking if pattern is deterministic...", 3, indent=indent) determined = make_grid(False, template=self.grid) determined_variables = set() width = len(self.grid[0][0]) height = len(self.grid[0]) while True: determined_copy = copy.deepcopy(determined) for t, generation in enumerate(self.grid): for y, row in enumerate(generation): for x, cell in enumerate(row): if not determined[t][y][x]: if cell in ["0", "1"]: determined[t][y][x] = True else: variable, negated = variable_from_literal(cell) if t == 0: determined[t][y][x] = True determined_variables.add(variable) elif variable in determined_variables: determined[t][y][x] = True elif all(determined[t - 1][y + y_offset][x + x_offset] for x_offset in range(2) for y_offset in range(2) if x + x_offset in range(width) and y + y_offset in range(height)) and not \ self.ignore_transition[t][y][x]: determined[t][y][x] = True determined_variables.add(variable) if determined == determined_copy: break print_message("Done\n", 3, indent=indent) if all(flag for generation in determined for row in generation for flag in row): return True else: return False
def force_evolution(self, method=None, indent=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, Fascicle 6, solution to exercise 65b # (57 clauses and 13 auxiliary variables per cell) # 1. An implementation of the naive scheme Knuth gives in the solution to exercise 65a # (190 clauses and 0 auxiliary variables per cell) # 2. A very naive scheme just listing all possible predecessor neighbourhoods # (512 clauses and 0 auxiliary variables per cell) print_message("Enforcing evolution rule...", 3, indent=indent) if method is None: if src.rules.rulestring_from_rule(self.rule) == "B3/S23": method = src.defaults.life_encoding_method else: method = 2 # Default method assert method in range(3), "Method not found" assert method == 2 or src.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) starting_number_of_clauses = len(self.clauses.clause_set) # 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]: self.force_transition(self.grid, x, y, t, method) # Iterate over all background cells for t, generation in enumerate(self.background_grid): for y, row in enumerate(generation): for x, cell in enumerate(row): if not self.background_ignore_transition[t][y][x]: self.force_transition(self.background_grid, x, y, t, method) print_message("Number of clauses used: " + str(len(self.clauses.clause_set) - starting_number_of_clauses), 3, indent=indent + 1) print_message("Done\n", 3, indent=indent)
def force_max_growth(self, max_growth, indent=0): print_message("Forcing the pattern to never grow by more than " + str(max_growth) + " cells", 3, indent=indent) width = len(self.grid[0][0]) height = len(self.grid[0]) duration = len(self.grid) for t in range(1, duration): literals = [] for x in range(width): for y in range(height): literal = str(t) + "_" + str(x) + "_" + str(y) + "_grows" self.clauses.append(implies([self.grid[t][y][x], negate(self.grid[0][y][x])], literal)) literals.append(literal) print_message("Generation " + str(t), 3, indent=indent + 1) self.force_at_most(literals, max_growth, indent=indent + 2) print_message("Done\n", 3, indent=indent)
def force_change(self, times, indent=0): """Adds clauses forcing at least one cell to change between specified generations""" (t_0, t_1) = times print_message( "Forcing at least one cell to change between generations " + str(t_0) + " and " + str(t_1) + " ...", 3, indent=indent) starting_number_of_clauses = len(self.clauses.clause_set) width = len(self.grid[0][0]) height = len(self.grid[0]) self.force_unequal([(self.grid[t_0][y][x], self.grid[t_1][y][x]) for x in range(width) for y in range(height)]) print_message("Number of clauses used: " + str(len(self.clauses.clause_set) - starting_number_of_clauses), 3, indent=indent + 1) print_message("Done\n", 3, indent=indent)
def force_distinct(self, solution, determined=False, indent=0): """Force search_pattern to have at least one difference from given solution""" print_message("Forcing pattern to be different from solution...", 3, indent=indent) 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 t, generation in enumerate(self.background_grid): for y, row in enumerate(generation): for x, cell in enumerate(row): other_cell = solution.background_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("Number of clauses used: 1", 3, indent=indent + 1) print_message("Done\n", 3, indent=indent)
def substitute_solution(self, solution, indent=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) background_grid = copy.deepcopy(self.background_grid) print_message('Substituting solution back into search grid...', 3, indent=indent) # Remove the first line that just says "SAT", and split into a list of literals solution = set(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 CNF_variable in self.clauses.dimacs_literal_from_variable: dimacs_variable = self.clauses.dimacs_literal_from_variable[CNF_variable] dimacs_literal = negate(dimacs_variable, negated, dimacs=True) if dimacs_literal in solution: grid[t][y][x] = "1" else: grid[t][y][x] = "0" else: grid[t][y][x] = "0" for t, generation in enumerate(background_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 CNF_variable in self.clauses.dimacs_literal_from_variable: dimacs_variable = self.clauses.dimacs_literal_from_variable[CNF_variable] dimacs_literal = negate(dimacs_variable, negated, dimacs=True) if dimacs_literal in solution: background_grid[t][y][x] = "1" else: background_grid[t][y][x] = "0" else: background_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 CNF_variable in self.clauses.dimacs_literal_from_variable: dimacs_variable = self.clauses.dimacs_literal_from_variable[CNF_variable] dimacs_literal = negate(dimacs_variable, negated, dimacs=True) if dimacs_literal in solution: rule[transition] = "1" else: rule[transition] = "0" else: rule[transition] = "0" print_message('Done\n', 3, indent=indent) return SearchPattern(grid, background_grid=background_grid, rule=rule, add_border=False)
def make_rle(grid, background_grid=None, rule=None, determined=None, show_background=None, indent=0): """Turn a search pattern into nicely formatted string form""" print_message('Format: RLE', 3, indent=indent) 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 is not None: rle_string += ", rule = " + rulestring_from_rule(rule) rle_string += "\n" rle_string += "$\n".join("".join(line) for line in grid[0]) rle_string += "!\n" if not determined: rle_string += "\nOther generations:\n" rle_string += "\n\n".join("$\n".join("".join(line) for line in generation) for generation in grid[1:]) + "\n" if show_background: rle_string += "\nBackground:\n" background_grid = copy.deepcopy(background_grid) for t, generation in enumerate(background_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": background_grid[t][y][x] = "b" elif cell == "1": background_grid[t][y][x] = "o" rle_string += "\n\n".join("$\n".join("".join(line) for line in generation) for generation in background_grid) + "\n" return rle_string
def make_file(self, file_name, indent=0): output_string = self.make_string() print_message('Writing file "' + file_name + '" ...', 3, indent=indent) with open(file_name, "w") as output_file: output_file.write(output_string) print_message('Done\n', 3, indent=indent)
def append_to_file_from_string(file_name, input_string, indent=0): """Append string to file""" print_message('Writing to file "' + file_name + '" ...', 3, indent=indent) with open(file_name, "a+") as output_file: output_file.write(input_string) print_message('Done\n', 3, indent=indent)
def file_from_object(file_name, input_object, indent=0): """Write object to file""" print_message('Writing file "' + file_name + '" ...', 3, indent=indent) with open(file_name, "wb") as output_file: pickle.dump(input_object, output_file) print_message('Done\n', 3, indent=indent)
def preprocess_and_solve(search_pattern, symmetries=None, asymmetries=None, population_at_most=None, population_at_least=None, population_exactly=None, max_change=None, max_decay=None, max_growth=None, force_change=None, solver=None, parameters=None, timeout=None, save_dimacs=None, save_state=None, method=None, dry_run=False, indent=0 ): """Preprocess and solve the search pattern""" if force_change is None: force_change = [] if population_exactly is None: population_exactly = [] if population_at_least is None: population_at_least = [] if population_at_most is None: population_at_most = [] if asymmetries is None: asymmetries = [] if symmetries is None: symmetries = [] try: preprocess( search_pattern, symmetries=symmetries, asymmetries=asymmetries, population_at_most=population_at_most, population_at_least=population_at_least, population_exactly=population_exactly, max_change=max_change, max_decay=max_decay, max_growth=max_growth, force_change=force_change, method=method, indent=indent ) if save_state: if isinstance(save_state, str): state_file = save_state else: state_file = "lls_state.pkl" file_number = 0 while os.path.isfile(state_file): file_number += 1 state_file = "lls_state" + str(file_number) + ".pkl" print_message("Saving state...", 3, indent=indent + 1) src.files.file_from_object( state_file, (search_pattern.grid, search_pattern.ignore_transition, search_pattern.background_grid, search_pattern.background_ignore_transition, search_pattern.rule, search_pattern.clauses.DIMACS_literal_from_variable), indent=indent + 2 ) print_message("Done\n", 3, indent=indent + 1) # Problem statistics 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[t][y][x] not in ["0", "1"]) for y in range(height)) for t in range(duration)) for x in range(width) ) active_height = sum( any( any( (search_pattern.grid[t][y][x] not in ["0", "1"]) for t in range(duration)) for x in range(width)) for y in range(height) ) active_duration = sum( any( any( (search_pattern.grid[t][y][x] not in ["0", "1"]) for x in range(width)) for y in range(height)) for t in range(duration) ) number_of_cells = search_pattern.number_of_cells() number_of_variables = search_pattern.clauses.number_of_variables number_of_clauses = len(search_pattern.clauses.clause_set) print_message('Number of undetermined cells: ' + str(number_of_cells), indent=indent) print_message('Number of variables: ' + str(number_of_variables), indent=indent) print_message('Number of clauses: ' + str(number_of_clauses) + "\n", indent=indent) print_message('Active width: ' + str(active_width), indent=indent) print_message('Active height: ' + str(active_height), indent=indent) print_message('Active duration: ' + str(active_duration) + "\n", indent=indent) 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) print_message('Done\n', indent=indent) else: ( solution, sat, time_taken ) = src.sat_solvers.sat_solve( search_pattern, solver=solver, parameters=parameters, timeout=timeout, save_dimacs=save_dimacs, dry_run=dry_run, indent=indent ) if not dry_run: print_message('Time taken: ' + str(time_taken) + " seconds\n", indent=indent) return \ solution,\ sat, \ number_of_cells,\ number_of_variables, \ number_of_clauses, \ active_width, \ active_height, \ active_duration, \ time_taken
def lls( search_pattern, symmetries=None, asymmetries=None, population_at_most=None, population_at_least=None, population_exactly=None, max_change=None, max_decay=None, max_growth=None, force_change=None, solver=None, parameters=None, timeout=None, save_dimacs=None, save_state=None, method=None, dry_run=False, number_of_solutions=None, pattern_output_format=None, output_file_name=None, indent=0 ): """The central part of LLS. Controls the flow of the program""" if force_change is None: force_change = [] if population_exactly is None: population_exactly = [] if population_at_least is None: population_at_least = [] if population_at_most is None: population_at_most = [] if asymmetries is None: asymmetries = [] if symmetries is None: symmetries = [] ( solution, sat, number_of_cells, number_of_variables, number_of_clauses, active_width, active_height, active_duration, time_taken ) = preprocess_and_solve( search_pattern, symmetries=symmetries, asymmetries=asymmetries, population_at_most=population_at_most, population_at_least=population_at_least, population_exactly=population_exactly, max_change=max_change, max_decay=max_decay, max_growth=max_growth, force_change=force_change, solver=solver, parameters=parameters, timeout=timeout, save_dimacs=save_dimacs, save_state=save_state, method=method, dry_run=dry_run, indent=indent ) # Check if the first generation of pattern determines the others solutions = [] if sat == "SAT": determined = search_pattern.deterministic(indent=indent) show_background = search_pattern.background_nontrivial() solutions.append(solution) output_string = solution.make_string( pattern_output_format=pattern_output_format, determined=determined, show_background=show_background, indent=indent ) else: output_string = ["Unsatisfiable", "Timed Out", "Dry run"][["UNSAT", "TIMEOUT", "DRYRUN"].index(sat)] print_message(output_string + "\n", 1, indent=indent) if output_file_name: print_message('Writing to output file...', indent=indent) src.files.append_to_file_from_string(output_file_name, output_string, indent=indent + 1) print_message('Done\n', indent=indent) # Deal with the case where we need more than one solution 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: # Force the new solution to be different search_pattern.force_distinct(solution, determined=determined) # No need to apply the constraints again ( solution, sat, _, _, _, _, _, _, extra_time_taken ) = preprocess_and_solve( search_pattern, solver=solver, parameters=parameters, timeout=timeout, method=method, indent=indent ) time_taken += extra_time_taken if sat == "SAT": solutions.append(solution) output_string = solution.make_string(pattern_output_format=pattern_output_format, determined=determined, show_background=show_background, indent=indent) else: output_string = ["Unsatisfiable", "Timed Out", "Dry run"][["UNSAT", "TIMEOUT", None].index(sat)] print_message(output_string + "\n", 1, indent=indent) if output_file_name: print_message('Writing output file...', indent=indent) src.files.append_to_file_from_string(output_file_name, output_string, indent=indent + 1) print_message('Done\n', indent=indent) if number_of_solutions != "Infinity": enough_solutions = (len(solutions) >= number_of_solutions) sat = "SAT" print_message('Total solver time: ' + str(time_taken), indent=indent) return solutions, \ sat, \ number_of_cells, \ number_of_variables, \ number_of_clauses, \ active_width, \ active_height, \ active_duration, \ time_taken
def preprocess( search_pattern, symmetries=None, asymmetries=None, population_at_most=None, population_at_least=None, population_exactly=None, max_change=None, max_decay=None, max_growth=None, force_change=None, force_evolution=True, method=None, indent=0 ): """Apply constraints and create SAT problem""" if force_change is None: force_change = [] if population_exactly is None: population_exactly = [] if population_at_least is None: population_at_least = [] if asymmetries is None: asymmetries = [] if population_at_most is None: population_at_most = [] if symmetries is None: symmetries = [] print_message('Preprocessing...', indent=indent) # Constraints that change the grid search_pattern.standardise_variables_names(indent=indent + 1) for symmetry in symmetries: search_pattern.force_symmetry(symmetry) search_pattern.remove_redundancies(indent=indent + 1) search_pattern.standardise_variables_names(indent=indent + 1) print_message("Search grid:\n", 3, indent=indent + 1) print_message(search_pattern.make_string(pattern_output_format="csv", show_background=True), 3, indent=indent + 2) # Constraints that are enforced by clauses for asymmetry in asymmetries: search_pattern.force_asymmetry(asymmetry) for constraint in population_at_most: search_pattern.force_population_at_most(constraint, indent=indent + 1) for constraint in population_at_least: search_pattern.force_population_at_least(constraint, indent=indent + 1) for constraint in population_exactly: search_pattern.force_population_exactly(constraint, indent=indent + 1) if max_change is not None: search_pattern.force_max_change(max_change, indent=indent + 1) if max_decay is not None: search_pattern.force_max_decay(max_decay, indent=indent + 1) if max_growth is not None: search_pattern.force_max_growth(max_growth, indent=indent + 1) for times in force_change: search_pattern.force_change(times, indent=indent + 1) if force_evolution: # The most important bit. Enforces the evolution rules search_pattern.force_evolution(method=method, indent=indent + 1) print_message('Done\n', indent=indent)
def use_solver(solver, dimacs_string, parameters=None, timeout=None, indent=0): if parameters is not None: parameter_list = parameters.strip(" ").split(" ") else: parameter_list = [] solver_path = sys.path[0] + "/solvers/" + solver command = [solver_path] + parameter_list 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(process, flag): process.kill() 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) start_time = time.time() try: timeout_timer.start() out, error = solver_process.communicate(dimacs_string.encode()) except KeyboardInterrupt: solver_process.kill() timeout_flag[0] = "SIGINT" finally: out = out.decode("utf-8") error = error.decode("utf-8") timeout_timer.cancel() time_taken = time.time() - start_time if not timeout_flag[0]: print_message('Done\n', 3, indent=indent) print_message('Formatting SAT solver output...', 3, indent=indent) solution = str(out) solution = solution.split("\ns ") solution = solution[-1] solution = solution.split("\nc") solution = solution[0] solution = solution.split("\nv ") solution = solution[0] + "\n" + " ".join(solution[1:]) if "UNSAT" in solution.upper(): solution = "UNSAT\n" print_message("SAT solver output:", 3, indent=indent + 1) print_message(out, 3, indent=indent + 2) print_message('Error (if any): "' + error + '"', 3, indent=indent + 1) print_message('Time taken: ' + str(time_taken), 3, indent=indent + 1) print_message('Done\n', 3, indent=indent) else: print_message('Timed out\n', 3, indent=indent) solution = "TIMEOUT\n" return solution, time_taken
def rule_from_rulestring(rulestring, indent=0): if rulestring is 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 = 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 BS_letter + number_of_neighbours + "c" not in rule: 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) return rule