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)))
示例#11
0
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)
示例#12
0
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))