def force_max_change(self, max_change, indent=0): print_message("Forcing the pattern to never change by more than " + str(max_change) + " 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) + "_changes" self.clauses.append(implies([self.grid[t][y][x], negate(self.grid[0][y][x])], literal)) self.clauses.append(implies([negate(self.grid[t][y][x]), self.grid[0][y][x]], literal)) literals.append(literal) print_message("Generation " + str(t), 3, indent=indent + 1) self.force_at_most(literals, max_change, indent=indent + 2) 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 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 append(self, clause): dimacs_clause = [] clause = set(clause) if "1" in clause: return for literal in clause: if literal == "0": pass else: (variable, negated) = variable_from_literal(literal) # If we haven't seen it before then add it to the dictionary if variable not in self.dimacs_literal_from_variable: self.dimacs_literal_from_variable[variable] = str( self.number_of_variables + 1) self.number_of_variables += 1 elif negate(literal) in clause: return dimacs_clause.append( negate(self.dimacs_literal_from_variable[variable], negated, dimacs=True)) dimacs_clause.sort() dimacs_clause.append("0\n") self.clause_set.add(" ".join(dimacs_clause))
def force_unequal(self, argument_0, argument_1=None): if argument_1 is not None: assert isinstance(argument_0, str) and isinstance(argument_1, str), "force_equal arguments not understood" cell_pair_list = [(argument_0, argument_1)] elif argument_0 == []: return elif isinstance(argument_0[0], str): assert len(argument_0) == 2 and isinstance(argument_0[1], str), "force_equal arguments not understood" cell_pair_list = [argument_0] else: cell_pair_list = argument_0 clause = [] for cell_pair in cell_pair_list: cells_equal = str(cell_pair[0]) + "_equals_" + str(cell_pair[1]) self.clauses.append(implies(cell_pair, cells_equal)) self.clauses.append(implies(map(negate, cell_pair), cells_equal)) clause.append(negate(cells_equal)) self.clauses.append(clause)
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 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 force_equal(self, argument_0, argument_1=None): if argument_1 is not None: assert isinstance(argument_0, str) and isinstance(argument_1, str), "force_equal arguments not understood" cell_pair_list = [(argument_0, argument_1)] elif argument_0 == []: return elif isinstance(argument_0[0], str): assert len(argument_0) == 2 and isinstance(argument_0[1], str), "force_equal arguments not understood" cell_pair_list = [argument_0] else: cell_pair_list = argument_0 replacement = {} replaces = {} for cell_0, cell_1 in cell_pair_list: while cell_0 not in ["0", "1"]: variable_0, negated_0 = variable_from_literal(cell_0) if variable_0 in replacement: cell_0 = negate(replacement[variable_0], negated_0) else: break while cell_1 not in ["0", "1"]: variable_1, negated_1 = variable_from_literal(cell_1) if variable_1 in replacement: cell_1 = negate(replacement[variable_1], negated_1) else: break if cell_0 != cell_1: if cell_0 == negate(cell_1): raise UnsatInPreprocessing elif cell_0 in ["0", "1"]: cell_0, cell_1 = cell_1, cell_0 variable_0, negated_0 = variable_from_literal(cell_0) cell_0, cell_1 = variable_0, negate(cell_1, negated_0) if cell_1 not in ["0", "1"]: variable_1, negated_1 = variable_from_literal(cell_1) if variable_1 not in replaces: replaces[variable_1] = [] if variable_0 in replaces: for variable in replaces[variable_0]: replacement_variable, replacement_negated = variable_from_literal(replacement[variable]) replacement[variable] = negate(cell_1, replacement_negated) if cell_1 not in ["0", "1"]: replaces[variable_1].append(variable) del replaces[variable_0] replacement[variable_0] = cell_1 if cell_1 not in ["0", "1"]: replaces[variable_1].append(variable_0) for t, generation in enumerate(self.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) if variable in replacement: if replacement[variable] != variable: self.grid[t][y][x] = negate(replacement[variable], negated) for t, generation in enumerate(self.background_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) if variable in replacement: if replacement[variable] != variable: self.background_grid[t][y][x] = negate(replacement[variable], negated) for transition, literal in self.rule.items(): if literal not in ["0", "1"]: variable, negated = variable_from_literal(literal) if variable in replacement: if replacement[variable] != variable: self.rule[transition] = negate(replacement[variable], negated)
def define_cardinality_variable(self, literals, at_least, already_defined=None, preprocessing=True): """Generates clauses defining a cardinality variable""" if preprocessing: # Remove "0"s and "1"s literals_copy = [] for literal in literals: if literal in ["0", "1"]: at_least -= int(literal) else: literals_copy.append(literal) literals_copy.sort() else: literals_copy = copy.deepcopy(literals) if already_defined is None: already_defined = [] def cardinality_variable_name(literals, at_least): return "at_least_" + str(at_least) + "_of_" + str(literals) name = cardinality_variable_name(literals_copy, at_least) if name not in already_defined: already_defined.append(name) max_literals = len(literals_copy) # The most literals that could be true max_literals_1 = max_literals // 2 literals_1 = literals_copy[:max_literals_1] variables_to_define_1 = [] # A list of variables we need to define max_literals_2 = max_literals - max_literals_1 literals_2 = literals_copy[max_literals_1:] variables_to_define_2 = [] # A list of variables we need to define # If at_least is obviously too small or too big, give the obvious answer if at_least <= 0: self.clauses.append([name]) elif at_least > max_literals: self.clauses.append([negate(name)]) elif max_literals == 1: literal = literals_copy[0] self.clauses.append([negate(name), literal]) self.clauses.append([name, negate(literal)]) # Otherwise define the appropriate clauses else: if at_least <= max_literals_1: self.clauses.append( implies( cardinality_variable_name(literals_1, at_least), name)) variables_to_define_1.append(at_least) for j in range(1, max_literals_2 + 1): for i in range(1, max_literals_1 + 1): if i + j == at_least: self.clauses.append( implies( [cardinality_variable_name(literals_1, i), cardinality_variable_name(literals_2, j)], name)) variables_to_define_1.append(i) variables_to_define_2.append(j) if at_least <= max_literals_2: self.clauses.append( implies( cardinality_variable_name(literals_2, at_least), name)) variables_to_define_2.append(at_least) if at_least > max_literals_2: i = at_least - max_literals_2 self.clauses.append( implies( negate(cardinality_variable_name(literals_1, i)), negate(name))) variables_to_define_1.append(i) for j in range(1, max_literals_2 + 1): for i in range(1, max_literals_1 + 1): if i + j == at_least + 1: self.clauses.append(implies([ negate(cardinality_variable_name(literals_1, i)), negate(cardinality_variable_name(literals_2, j))], negate(name))) variables_to_define_1.append(i) variables_to_define_2.append(j) if at_least > max_literals_1: j = at_least - max_literals_1 self.clauses.append( implies( negate(cardinality_variable_name(literals_2, j)), negate(name))) variables_to_define_2.append(j) # Remove duplicates from our lists of child variables we need to define variables_to_define_1 = set(variables_to_define_1) variables_to_define_2 = set(variables_to_define_2) # Define the child variables for at_least_1 in variables_to_define_1: self.define_cardinality_variable(literals_1, at_least_1, already_defined, preprocessing=False) for at_least_2 in variables_to_define_2: self.define_cardinality_variable(literals_2, at_least_2, already_defined, preprocessing=False) return name
def force_transition(self, grid, x, y, t, method): cell = grid[t][y][x] duration = len(grid) if method == 0: src.taocp_variable_scheme.transition_rule(self, grid, x, y, t) elif method == 1: predecessor_cell = grid[(t - 1) % duration][y][x] neighbours = neighbours_from_coordinates(grid, x, y, t, background_grid=self.background_grid) # 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)) self.clauses.append(clause) # If any seven neighbours were dead, the cell is dead for seven_neighbours in itertools.combinations(neighbours, 7): clause = implies([negate(neighbour) for neighbour in seven_neighbours], negate(cell)) self.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)] + [negate(neighbour) for neighbour in six_neighbours], negate(cell)) self.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 + [negate(neighbour) for neighbour in five_neighbours], cell) self.clauses.append(clause) # Finally, if the cell was live, and two neighbours # were live, and five neighbours were dead, then the # cell is live (independently 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 + [negate(neighbour) for neighbour in five_neighbours], cell) self.clauses.append(clause) elif method == 2: predecessor_cell = grid[(t - 1) % duration][y][x] neighbours = neighbours_from_coordinates(self.grid, x, y, t, background_grid=self.background_grid) booleans = [True, False] # For each combination of neighbourhoods for predecessor_cell_alive in booleans: for neighbours_alive in itertools.product(booleans, repeat=8): p = "S" if predecessor_cell_alive else "B" transition = src.rules.transition_from_cells(neighbours_alive) transition_literal = self.rule[p + transition] self.clauses.append(implies( [transition_literal] + [negate(predecessor_cell, not predecessor_cell_alive)] + list( map(negate, neighbours, map(lambda q: not q, neighbours_alive))), cell)) self.clauses.append(implies( [negate(transition_literal)] + [negate(predecessor_cell, not predecessor_cell_alive)] + list( map(negate, neighbours, map(lambda q: not q, neighbours_alive))), negate(cell)))
def definition_clauses(search_pattern, grid, x, y, t, letter, at_least): """Defines clauses that define variables for Knuth's neighbour counting scheme""" if letter is None: return [] else: (child_1_letter, child_1_x, child_1_y, child_2_letter, child_2_x, child_2_y) = children(letter, x, y) maximum_number_of_live_cells_1 = maximum_number_of_live_cells( child_1_letter) maximum_number_of_live_cells_2 = maximum_number_of_live_cells( child_2_letter) child_1_needing_definition = [ ] # A list of child1 variables we need to define child_2_needing_definition = [ ] # A list of child2 variables we need to define # If at_least is obviously too small or too big, give the obvious answer if at_least <= 0: search_pattern.clauses.append([ literal_name(search_pattern, grid, x, y, t, letter, at_least) ]) elif at_least > maximum_number_of_live_cells_1 + maximum_number_of_live_cells_2: search_pattern.clauses.append([ negate( literal_name(search_pattern, grid, x, y, t, letter, at_least)) ]) # Otherwise define the appropriate clauses else: if at_least <= maximum_number_of_live_cells_1: search_pattern.clauses.append([ negate( literal_name(search_pattern, grid, child_1_x, child_1_y, t, child_1_letter, at_least)), literal_name(search_pattern, grid, x, y, t, letter, at_least) ]) child_1_needing_definition.append(at_least) for j in range(1, maximum_number_of_live_cells_2 + 1): for i in range(1, maximum_number_of_live_cells_1 + 1): if i + j == at_least: search_pattern.clauses.append([ negate( literal_name(search_pattern, grid, child_1_x, child_1_y, t, child_1_letter, i)), negate( literal_name(search_pattern, grid, child_2_x, child_2_y, t, child_2_letter, j)), literal_name(search_pattern, grid, x, y, t, letter, at_least) ]) child_1_needing_definition.append(i) child_2_needing_definition.append(j) if at_least <= maximum_number_of_live_cells_2: search_pattern.clauses.append([ negate( literal_name(search_pattern, grid, child_2_x, child_2_y, t, child_2_letter, at_least)), literal_name(search_pattern, grid, x, y, t, letter, at_least) ]) child_2_needing_definition.append(at_least) if at_least > maximum_number_of_live_cells_2: i = at_least - maximum_number_of_live_cells_2 search_pattern.clauses.append([ literal_name(search_pattern, grid, child_1_x, child_1_y, t, child_1_letter, i), negate( literal_name(search_pattern, grid, x, y, t, letter, at_least)) ]) child_1_needing_definition.append(i) for j in range(1, maximum_number_of_live_cells_2 + 1): for i in range(1, maximum_number_of_live_cells_1 + 1): if i + j == at_least + 1: search_pattern.clauses.append([ literal_name(search_pattern, grid, child_1_x, child_1_y, t, child_1_letter, i), literal_name(search_pattern, grid, child_2_x, child_2_y, t, child_2_letter, j), negate( literal_name(search_pattern, grid, x, y, t, letter, at_least)) ]) child_1_needing_definition.append(i) child_2_needing_definition.append(j) if at_least > maximum_number_of_live_cells_1: j = at_least - maximum_number_of_live_cells_1 search_pattern.clauses.append([ literal_name(search_pattern, grid, child_2_x, child_2_y, t, child_2_letter, j), negate( literal_name(search_pattern, grid, x, y, t, letter, at_least)) ]) child_2_needing_definition.append(j) # Remove duplicates from our lists of child variables we need to define child_1_needing_definition = set(child_1_needing_definition) child_2_needing_definition = set(child_2_needing_definition) # Define the child variables for child_1_at_least in child_1_needing_definition: definition_clauses(search_pattern, grid, child_1_x, child_1_y, t, child_1_letter, child_1_at_least) for child_2_at_least in child_2_needing_definition: definition_clauses(search_pattern, grid, child_2_x, child_2_y, t, child_2_letter, child_2_at_least)
def transition_rule(search_pattern, grid, x, y, t): """Creates clauses enforcing the transition rule at coordinates x, y, t of grid""" duration = len(grid) # These clauses define variables a_i meaning at least i of the neighbours were alive at time t - 1 definition_clauses(search_pattern, grid, x, y, (t - 1) % duration, "a", at_least=2) definition_clauses(search_pattern, grid, x, y, (t - 1) % duration, "a", at_least=3) definition_clauses(search_pattern, grid, x, y, (t - 1) % duration, "a", at_least=4) cell = literal_name(search_pattern, grid, x, y, t) predecessor_cell = literal_name(search_pattern, grid, x, y, (t - 1) % duration) # These clauses implement the cellular automaton rule # If there are at least 4 neighbours in the previous generation then the cell dies search_pattern.clauses.append( implies( literal_name(search_pattern, grid, x, y, (t - 1) % duration, "a", at_least=4), negate(cell))) # If there aren't at least 2 neighbours in the previous generation then the cell dies search_pattern.clauses.append( implies( negate( literal_name(search_pattern, grid, x, y, (t - 1) % duration, "a", at_least=2)), negate(cell))) # If the predecessor is dead and there aren't at least 3 neighbours then the cell dies search_pattern.clauses.append( implies([ negate(predecessor_cell), negate( literal_name(search_pattern, grid, x, y, (t - 1) % duration, "a", at_least=3)) ], negate(cell))) # If there are exactly 3 neighbours then the cell lives search_pattern.clauses.append( implies([ negate( literal_name(search_pattern, grid, x, y, (t - 1) % duration, "a", at_least=4)), literal_name(search_pattern, grid, x, y, (t - 1) % duration, "a", at_least=3) ], cell)) # If the predecessor is alive and there are at least 2 neighbours but not at least 4 neighbours then the cell lives search_pattern.clauses.append( implies([ predecessor_cell, literal_name(search_pattern, grid, x, y, (t - 1) % duration, "a", at_least=2), negate( literal_name(search_pattern, grid, x, y, (t - 1) % duration, "a", at_least=4)) ], cell))