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 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 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 transition_rule(grid, x, y, t): """Outputs clauses enforcing the transition rule at coordinates x, y, t of grid""" clauses = [] # These clauses define variables a_i meaning at least i of the neighbours were alive at time t - 1 clauses += definition_clauses(grid, x, y, t - 1, "a", at_least=2) clauses += definition_clauses(grid, x, y, t - 1, "a", at_least=3) clauses += definition_clauses(grid, x, y, t - 1, "a", at_least=4) cell = literal_name(grid, x, y, t) predecessor_cell = literal_name(grid, x, y, t - 1) # These clauses implement the cellular automaton rule # If there are at least 4 neighbours in the previous generation then the cell dies clauses.append( implies(literal_name(grid, x, y, t - 1, "a", at_least=4), negate(cell))) # If there aren't at least 2 neighbours in the previous generation then the cell dies clauses.append( implies(negate(literal_name(grid, x, y, t - 1, "a", at_least=2)), negate(cell))) # If the predecessor is dead and there aren't at least 3 neighbours then the cell dies clauses.append( implies([ negate(predecessor_cell), negate(literal_name(grid, x, y, t - 1, "a", at_least=3)) ], negate(cell))) # If the predecessor is dead and there are at least 3 neighbours then the cell lives TODO: This doesn't seem true clauses.append( implies([ negate(literal_name(grid, x, y, t - 1, "a", at_least=4)), literal_name(grid, x, y, t - 1, "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 clauses.append( implies([ predecessor_cell, literal_name(grid, x, y, t - 1, "a", at_least=2), negate(literal_name(grid, x, y, t - 1, "a", at_least=4)) ], cell)) return clauses
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 definition_clauses(grid, x, y, t, letter, at_least): """Defines clauses that define variables for Knuth's neighbour counting scheme""" if letter == 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) clauses = [ ] # We'll build up a list of the clauses we need one at a time 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: clauses.append([literal_name(grid, x, y, t, letter, at_least)]) elif at_least > maximum_number_of_live_cells_1 + maximum_number_of_live_cells_2: clauses.append( [negate(literal_name(grid, x, y, t, letter, at_least))]) # Otherwise define the appropriate clauses else: if at_least <= maximum_number_of_live_cells_1: clauses.append([ negate( literal_name(grid, child_1_x, child_1_y, t, child_1_letter, at_least)), literal_name(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: clauses.append([ negate( literal_name(grid, child_1_x, child_1_y, t, child_1_letter, i)), negate( literal_name(grid, child_2_x, child_2_y, t, child_2_letter, j)), literal_name(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: clauses.append([ negate( literal_name(grid, child_2_x, child_2_y, t, child_2_letter, at_least)), literal_name(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 clauses.append([ literal_name(grid, child_1_x, child_1_y, t, child_1_letter, i), negate(literal_name(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: clauses.append([ literal_name(grid, child_1_x, child_1_y, t, child_1_letter, i), literal_name(grid, child_2_x, child_2_y, t, child_2_letter, j), negate( literal_name(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 clauses.append([ literal_name(grid, child_2_x, child_2_y, t, child_2_letter, j), negate(literal_name(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: clauses += definition_clauses(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: clauses += definition_clauses(grid, child_2_x, child_2_y, t, child_2_letter, child_2_at_least) return clauses
def force_equal(self, argument_0, argument_1 = None): grid_changed = False clauses_changed = False rule_changed = False if argument_1 != None: assert isinstance(argument_0, basestring) and isinstance(argument_1, basestring), "force_equal arguments not understood" cell_pair_list = [(argument_0, argument_1)] elif argument_0 == []: return grid_changed, clauses_changed, rule_changed elif isinstance(argument_0[0], basestring): assert len(argument_0) == 2 and isinstance(argument_0[1], basestring), "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 replacement.has_key(variable_0): 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 replacement.has_key(variable_1): 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 not replaces.has_key(variable_1): replaces[variable_1] = [] if replaces.has_key(variable_0): 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 replacement.has_key(variable): if replacement[variable] != variable: grid_changed = True self.grid[t][y][x] = negate(replacement[variable], negated) 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 replacement.has_key(variable): if replacement[variable] != variable: clauses_changed = True self.clauses[clause_number][literal_number] = negate(replacement[variable], negated) for transition, literal in self.rule.items(): if literal not in ["0", "1"]: variable, negated = variable_from_literal(literal) if replacement.has_key(variable): if replacement[variable] != variable: rule_changed = True self.rule[transition] = negate(replacement[variable], negated) return grid_changed, clauses_changed, rule_changed
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 == 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) clauses = [] number_of_clauses = 0 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: clauses.append([name]) elif at_least > max_literals: clauses.append([negate(name)]) elif max_literals == 1: literal = literals_copy[0] clauses.append([negate(name), literal]) clauses.append([name, negate(literal)]) # Otherwise define the appropriate clauses else: if at_least <= max_literals_1: 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: 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: 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 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: 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 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: _, number_of_extra_clauses = self.define_cardinality_variable(literals_1, at_least_1, already_defined, preprocessing = False) number_of_clauses += number_of_extra_clauses for at_least_2 in variables_to_define_2: _, number_of_extra_clauses = self.define_cardinality_variable(literals_2, at_least_2, already_defined, preprocessing = False) number_of_clauses += number_of_extra_clauses number_of_clauses += len(clauses) self.clauses += clauses return name, 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 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 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