def constrain_visited_counts(sg): """The number of visited squares constraints must be satisfied.""" for y, count in enumerate(ROW_COUNTS): if count is None: continue row = [sg.grid[(y, x)] for x in range(SIZE)] terms = [(c != SYM.EMPTY, 1) for c in row] sg.solver.add(PbEq(terms, count)) for x, count in enumerate(COL_COUNTS): if count is None: continue col = [sg.grid[(y, x)] for y in range(SIZE)] terms = [(c != SYM.EMPTY, 1) for c in col] sg.solver.add(PbEq(terms, count))
def main(): """Aquarium solver example.""" sym = grilops.SymbolSet([("B", chr(0x2588)), ("W", " ")]) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) # Number of shaded cells per row / column must match clues. for y in range(HEIGHT): sg.solver.add( PbEq([(sg.grid[(y, x)] == sym.B, 1) for x in range(WIDTH)], ROW_CLUES[y])) for x in range(WIDTH): sg.solver.add( PbEq([(sg.grid[(y, x)] == sym.B, 1) for y in range(HEIGHT)], COL_CLUES[x])) # The water level in each aquarium is the same across its full width. for y in range(HEIGHT): for al in set(REGIONS[y]): # If any aquarium cell is filled within a row, then all cells of that # aquarium within that row must be filled. cells = [ sg.grid[(y, x)] for x in range(WIDTH) if REGIONS[y][x] == al ] for cell in cells[1:]: sg.solver.add(cell == cells[0]) # If an aquarium is filled within a row, and that aquarium also has # cells in the row below that row, then that same aquarium's cells below # must be filled as well. if y < HEIGHT - 1: cells_below = [ sg.grid[(y + 1, x)] for x in range(WIDTH) if REGIONS[y + 1][x] == al ] if cells_below: sg.solver.add( Implies(cells[0] == sym.B, cells_below[0] == sym.B)) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def add_area_constraints(lattice, sc): """Ensure each area of the puzzle contains exactly one tetrahex.""" for area_label in {c for line in AREAS for c in line}: area_points = [] area_type_cells = [] area_instance_cells = [] for p in lattice.points: r, c = point_to_areas_row_col(p) if AREAS[r][c] == area_label: area_points.append(p) area_type_cells.append(sc.shape_type_grid[p]) area_instance_cells.append(sc.shape_instance_grid[p]) area_type = Int(f"at-{area_label}") sc.solver.add(area_type >= 0) sc.solver.add(area_type <= 4) sc.solver.add( And(*[Or(c == -1, c == area_type) for c in area_type_cells])) area_instance = Int(f"ai-{area_label}") sc.solver.add( Or(*[ area_instance == lattice.point_to_index(p) for p in area_points ])) sc.solver.add( And(* [Or(c == -1, c == area_instance) for c in area_instance_cells])) sc.solver.add(PbEq([(c != -1, 1) for c in area_type_cells], 4))
def main(): """Gokigen Naname solver example.""" sym = grilops.SymbolSet([("F", chr(0x2571)), ("B", chr(0x2572))]) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) # Ensure the given number of line segment constraints are met. for (y, x), v in GIVENS.items(): terms = [] if y > 0: if x > 0: terms.append(sg.cell_is(Point(y - 1, x - 1), sym.B)) if x < WIDTH: terms.append(sg.cell_is(Point(y - 1, x), sym.F)) if y < HEIGHT: if x > 0: terms.append(sg.cell_is(Point(y, x - 1), sym.F)) if x < WIDTH: terms.append(sg.cell_is(Point(y, x), sym.B)) sg.solver.add(PbEq([(term, 1) for term in terms], v)) add_loop_constraints(sym, sg) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def region_solve(): """Hex slitherlink solver example using regions.""" lattice = get_lattice() sym = grilops.SymbolSet(["I", "O"]) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver, complete=True) # There must be exactly two connected regions: the inside and the outside. # The outside region ID will be 0 because the top-left-most element will # always be outside. inside_region_id = Int("inside_region_id") sg.solver.add(inside_region_id != 0) for p in lattice.points: # A cell should have symbol I if and only if it's in the inside region. sg.solver.add((sg.grid[p] == sym.I) == ( rc.region_id_grid[p] == inside_region_id)) # If this cell isn't in the givens array, it must be outside the loop. givens_addr = point_to_givens_row_col(p) if givens_addr is None: sg.solver.add(sg.grid[p] == sym.O) continue # Find the given corresponding to this cell. If it's None, we don't # know anything about it. r, c = givens_addr given = GIVENS[r][c] if given is None: continue # The given number must equal the number of adjacent cells on the # opposite side of the loop line. num_different_neighbors_terms = [(n.symbol != sg.grid[p], 1) for n in sg.edge_sharing_neighbors(p)] sg.solver.add(PbEq(num_different_neighbors_terms, given)) def hook_function(p, _): addr = point_to_givens_row_col(p) return " " if addr is None else None if sg.solve(): sg.print(hook_function) print_loop(sg.grid, sg.solver.model()) print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print(hook_function) print_loop(sg.grid, sg.solver.model()) else: print("No solution")
def main(): """Halloween Town / Valentine's Day Town solver.""" sg = grilops.SymbolGrid(LATTICE, SYM) lc = grilops.loops.LoopConstrainer(sg, single_loop=True) # Cheat a little bit and force the loop order to start such that the answer # extraction starts at the correct place and proceeds in the correct order. sg.solver.add(lc.loop_order_grid[Point(5, 6)] == 0) sg.solver.add(lc.loop_order_grid[Point(5, 5)] == 1) # Count the number of Os in the puzzle answers. o_count = sum(c == "O" for row in ANSWERS for c in row) # There will be exactly twice as many turns as Os. This constraint is not # strictly necessary to add, but the solver runs faster when it is added. sg.solver.add( PbEq([(sg.cell_is_one_of(p, TURN_SYMBOLS), 1) for p in LATTICE.points], o_count * 2)) # Find the loop order values for all of the turns. turn_loop_orders = [Int(f"tlo-{i}") for i in range(o_count * 2)] for tlo in turn_loop_orders: sg.solver.add(tlo >= 0) sg.solver.add(tlo < 8 * 8) for i in range(len(turn_loop_orders) - 1): sg.solver.add(turn_loop_orders[i] < turn_loop_orders[i + 1]) for p in LATTICE.points: # Figure out each turn's loop order value. sg.solver.add( Implies( sg.cell_is_one_of(p, TURN_SYMBOLS), Or(*[tlo == lc.loop_order_grid[p] for tlo in turn_loop_orders]))) if ANSWERS[p.y][p.x] == "O": # An O must be a turn. sg.solver.add(sg.cell_is_one_of(p, TURN_SYMBOLS)) # An O must be in an odd position in the list of turn loop order values. or_terms = [] for i in range(1, len(turn_loop_orders), 2): or_terms.append(lc.loop_order_grid[p] == turn_loop_orders[i]) sg.solver.add(Or(*or_terms)) if sg.solve(): sg.print() print(extract_answer(sg, lc.loop_order_grid)) print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def n_sense_codons( T: CodeRef, n_codons: int, codons: Sequence[CodonRef] = z3_enum_codons, aminos: Sequence[AminoRef] = z3_enum_aminos, exclude: Optional[AminoRef] = None ) -> List[ConstraintRef]: if exclude is None: exclude = (get_null(aminos), get_stop(aminos)) return [PbEq( [ (And([decode(T, c) != aa for aa in exclude]), 1) for c in codons ], k=n_codons )]
def add_given_pair_constraints(sg, rc): """Add constraints for the pairs of givens contained within each region.""" # Each larger (root) given must be paired with a single smaller given in its # same region, and the size of the region must be between the givens' values. for lp, lv in GIVENS.items(): partner_terms = [] for sp, sv in GIVENS.items(): if lp == sp or lv <= sv: continue # Rule out pairs that can't possibly work, due to the Manhattan distance # between givens being too large. manhattan_distance = abs(lp.y - sp.y) + abs(lp.x - sp.x) min_region_size = manhattan_distance + 1 if lv < min_region_size: sg.solver.add( rc.region_id_grid[sp] != sg.lattice.point_to_index(lp)) continue partner_terms.append( And( # The smaller given must not be a region root. Not(rc.parent_grid[sp] == grilops.regions.R), # The givens must share a region, rooted at the larger given. rc.region_id_grid[sp] == sg.lattice.point_to_index(lp), # The region must be larger than the smaller given's value. sv < rc.region_size_grid[lp] ) ) if not partner_terms: sg.solver.add(rc.parent_grid[lp] != grilops.regions.R) else: sg.solver.add( Implies( rc.parent_grid[lp] == grilops.regions.R, And( rc.region_size_grid[lp] < lv, PbEq([(term, 1) for term in partner_terms], 1) ) ) )
def exactly_one_codon_per_amino( T: CodeRef, codons: Sequence[CodonRef] = z3_enum_codons, aminos: Sequence[AminoRef] = z3_enum_aminos, exclude: Optional[AminoRef] = None ) -> List[ConstraintRef]: """ :param T: genetic code :param codons: list of CodonRef objects :param aminos: list of AminoRef objects :param exclude: range of T to ignore (list or list-like of aminos) :return: list of constraints """ if exclude is None: exclude = (get_null(aminos), get_stop(aminos)) return [ PbEq([(decode(T, c) == aa, 1) for c in codons], k=1) for aa in aminos if aa not in exclude ]
def loop_solve(): """Slitherlink solver example using loops.""" # We'll place symbols at the intersections of the grid lines, rather than in # the spaces between the grid lines where the givens are written. This # requires increasing each dimension by one. lattice = grilops.get_rectangle_lattice(HEIGHT + 1, WIDTH + 1) sym = grilops.loops.LoopSymbolSet(lattice) sym.append("EMPTY", " ") sg = grilops.SymbolGrid(lattice, sym) grilops.loops.LoopConstrainer(sg, single_loop=True) for (y, x), c in GIVENS.items(): # For each side of this given location, add one if there's a loop edge # along that side. We'll determine this by checking the kinds of loop # symbols in the north-west and south-east corners of this given location. terms = [ # Check for east edge of north-west corner (given's north edge). sg.cell_is_one_of(Point(y, x), [sym.EW, sym.NE, sym.SE]), # Check for north edge of south-east corner (given's east edge). sg.cell_is_one_of(Point(y + 1, x + 1), [sym.NS, sym.NE, sym.NW]), # Check for west edge of south-east corner (given's south edge). sg.cell_is_one_of(Point(y + 1, x + 1), [sym.EW, sym.SW, sym.NW]), # Check for south edge of north-west corner (given's west edge). sg.cell_is_one_of(Point(y, x), [sym.NS, sym.SE, sym.SW]), ] sg.solver.add(PbEq([(term, 1) for term in terms], c)) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def Exactly(*args): assert len(args) >= 1, 'Non empty list of arguments expected' return PbEq([(arg, 1) for arg in args[:-1]], args[-1])
def main(): """Akari solver example.""" size = 10 lattice = grilops.get_square_lattice(size) black_cells = { (0, 0): None, (0, 3): None, (0, 9): None, (1, 7): None, (2, 1): 3, (2, 6): 0, (3, 2): 2, (3, 5): None, (3, 9): 1, (4, 3): 1, (4, 4): 0, (4, 5): None, (5, 4): 1, (5, 5): None, (5, 6): None, (6, 0): None, (6, 4): 2, (6, 7): 2, (7, 3): None, (7, 8): None, (8, 2): 1, (9, 0): 0, (9, 6): 1, (9, 9): 0, } def print_given(point): if point in black_cells: v = black_cells.get(point) if v is None: return chr(0x2588) return str(v) return None lattice.print(print_given) print() sym = grilops.SymbolSet([ ("BLACK", chr(0x2588)), ("EMPTY", " "), ("LIGHT", "*"), ]) sg = grilops.SymbolGrid(lattice, sym) for point in lattice.points: if point in black_cells: sg.solver.add(sg.cell_is(point, sym.BLACK)) light_count = black_cells[point] if light_count is not None: sg.solver.add( PbEq([(n.symbol == sym.LIGHT, 1) for n in sg.edge_sharing_neighbors(point)], light_count)) else: # All black cells are given; don't allow this cell to be black. sg.solver.add(sg.cell_is_one_of(point, [sym.EMPTY, sym.LIGHT])) def is_black(c): return c == sym.BLACK def count_light(c): return If(c == sym.LIGHT, 1, 0) for point in lattice.points: if point in black_cells: continue visible_light_count = sum( grilops.sightlines.count_cells( sg, n.location, n.direction, count=count_light, stop=is_black) for n in sg.edge_sharing_neighbors(point)) # Ensure that each light cannot see any other lights, and that each cell # is lit by at least one light. sg.solver.add( If(sg.cell_is(point, sym.LIGHT), visible_light_count == 0, visible_light_count > 0)) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def main(): """Araf solver example.""" min_given_value = min(GIVENS.values()) max_given_value = max(GIVENS.values()) # The grid symbols will be the region IDs from the region constrainer. sym = grilops.make_number_range_symbol_set(0, HEIGHT * WIDTH - 1) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer( lattice, sg.solver, min_region_size=min_given_value + 1, max_region_size=max_given_value - 1 ) for point in lattice.points: sg.solver.add(sg.cell_is(point, rc.region_id_grid[point])) # Exactly half of the givens must be region roots. As an optimization, add # constraints that the smallest givens must not be region roots, and that the # largest givens must be region roots. undetermined_given_locations = [] num_undetermined_roots = len(GIVENS) // 2 for p, v in GIVENS.items(): if v == min_given_value: sg.solver.add(Not(rc.parent_grid[p] == grilops.regions.R)) elif v == max_given_value: sg.solver.add(rc.parent_grid[p] == grilops.regions.R) num_undetermined_roots -= 1 else: undetermined_given_locations.append(p) sg.solver.add( PbEq( [ (rc.parent_grid[p] == grilops.regions.R, 1) for p in undetermined_given_locations ], num_undetermined_roots ) ) # Non-givens must not be region roots for point in lattice.points: if point not in GIVENS: sg.solver.add(Not(rc.parent_grid[point] == grilops.regions.R)) add_given_pair_constraints(sg, rc) region_id_to_label = { lattice.point_to_index(point): chr(65 + i) for i, point in enumerate(GIVENS.keys()) } def show_cell(unused_loc, region_id): return region_id_to_label[region_id] if sg.solve(): sg.print(show_cell) print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print(show_cell) else: print("No solution")
def main(): """Heyawake solver example.""" sym = grilops.SymbolSet([("B", chr(0x2588)), ("W", " ")]) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) # Rule 1: Painted cells may never be orthogonally connected (they may not # share a side, although they can touch diagonally). for p in lattice.points: sg.solver.add( Implies( sg.cell_is(p, sym.B), And(*[n.symbol != sym.B for n in sg.edge_sharing_neighbors(p)]) ) ) # Rule 2: All white cells must be interconnected (form a single polyomino). rc = grilops.regions.RegionConstrainer( lattice, sg.solver, complete=False) white_region_id = Int("white_region_id") sg.solver.add(white_region_id >= 0) sg.solver.add(white_region_id < HEIGHT * WIDTH) for p in lattice.points: sg.solver.add( If( sg.cell_is(p, sym.W), rc.region_id_grid[p] == white_region_id, rc.region_id_grid[p] == -1 ) ) # Rule 3: A number indicates exactly how many painted cells there must be in # that particular room. region_cells = defaultdict(list) for p in lattice.points: region_cells[REGIONS[p.y][p.x]].append(sg.grid[p]) for region, count in REGION_COUNTS.items(): sg.solver.add(PbEq([(c == sym.B, 1) for c in region_cells[region]], count)) # Rule 4: A room which has no number may contain any number of painted cells, # or none. # Rule 5: Where a straight (orthogonal) line of connected white cells is # formed, it must not contain cells from more than two rooms—in other words, # any such line of white cells which connects three or more rooms is # forbidden. region_names = sorted(list(set(c for row in REGIONS for c in row))) bits = len(region_names) def set_region_bit(bv, p): i = region_names.index(REGIONS[p.y][p.x]) chunks = [] if i < bits - 1: chunks.append(Extract(bits - 1, i + 1, bv)) chunks.append(BitVecVal(1, 1)) if i > 0: chunks.append(Extract(i - 1, 0, bv)) return Concat(*chunks) for p in lattice.points: for n in sg.edge_sharing_neighbors(p): bv = reduce_cells( sg, p, n.direction, set_region_bit(BitVecVal(0, bits), p), lambda acc, c, ap: set_region_bit(acc, ap), lambda acc, c, sp: c == sym.B ) popcnt = Sum(*[BV2Int(Extract(i, i, bv)) for i in range(bits)]) sg.solver.add(Implies(sg.cell_is(p, sym.W), popcnt <= 2)) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
is_scout = np.array([ Bool("is_scout_%s%s" % (r, c)) for (r, c) in board() ]).reshape(H, W).tolist() # Piece placement no_scouts_in_lakes = [ Not(is_scout[r][c]) for (r, c) in lakes() ] # Segments open_rows = [ list(zip(repeat(r), range(W))) for r in chain(range(0, 4), range(6, H)) ] open_cols = [ list(zip(range(H), repeat(c))) for c in chain(range(0, 2), range(4, 6), range(8, W)) ] lake_rows = [ list(zip(repeat(r), range(c, c + 2))) for r in range(4, 6) for c in (0, 4, 8) ] lake_cols = [ list(zip(range(r, r + 4), repeat(c))) for r in (0, 6) for c in chain(range(2, 4), range(6, 8)) ] segments = open_rows + open_cols + lake_rows + lake_cols # TODO: incorporate the fixed issue https://github.com/Z3Prover/z3/issues/1782 as soon as there is a new release available at_most_one_scout_per_segment = [ PbEq([ (is_scout[r][c], 1) for (r, c) in s ], 1) for s in segments ] # Scout moves in the left (L), right (R), downward (D) and upward (U) directions def L_scout_moves_from(r, c): if r in chain(range(0, 4), range(6, H)): return range(0, c) elif c in range(0, 2): return range(0, c) elif c in range(4, 6): return range(4, c) elif c in range(8, W): return range(8, c) else:
def main(): """Yajilin solver example.""" for y in range(HEIGHT): for x in range(WIDTH): if (y, x) in GIVENS: direction, count = GIVENS[(y, x)] sys.stdout.write(str(count)) sys.stdout.write(direction) sys.stdout.write(" ") else: sys.stdout.write(" ") print() print() lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sym = grilops.loops.LoopSymbolSet(lattice) sym.append("BLACK", chr(0x25AE)) sym.append("GRAY", chr(0x25AF)) sym.append("INDICATIVE", " ") sg = grilops.SymbolGrid(lattice, sym) grilops.loops.LoopConstrainer(sg, single_loop=True) for y in range(HEIGHT): for x in range(WIDTH): p = Point(y, x) if (y, x) in GIVENS: sg.solver.add(sg.cell_is(p, sym.INDICATIVE)) elif (y, x) in GRAYS: sg.solver.add(sg.cell_is(p, sym.GRAY)) else: sg.solver.add( Not(sg.cell_is_one_of(p, [sym.INDICATIVE, sym.GRAY]))) sg.solver.add( Implies( sg.cell_is(p, sym.BLACK), And(*[ n.symbol != sym.BLACK for n in sg.edge_sharing_neighbors(p) ]))) for (sy, sx), (direction, count) in GIVENS.items(): if direction == U: cells = [(y, sx) for y in range(sy)] elif direction == R: cells = [(sy, x) for x in range(sx + 1, WIDTH)] elif direction == D: cells = [(y, sx) for y in range(sy + 1, HEIGHT)] elif direction == L: cells = [(sy, x) for x in range(sx)] sg.solver.add( PbEq([(sg.cell_is(Point(y, x), sym.BLACK), 1) for (y, x) in cells], count)) def print_grid(): sg.print(lambda p, _: GIVENS[(p.y, p.x)][0] if (p.y, p.x) in GIVENS else None) if sg.solve(): print_grid() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") print_grid() else: print("No solution")
def main(): """Battleship solver example.""" sg = grilops.SymbolGrid(LATTICE, SYM) sc = grilops.shapes.ShapeConstrainer( LATTICE, [ [Vector(0, i) for i in range(4)], [Vector(0, i) for i in range(3)], [Vector(0, i) for i in range(3)], [Vector(0, i) for i in range(2)], [Vector(0, i) for i in range(2)], [Vector(0, i) for i in range(2)], [Vector(0, i) for i in range(1)], [Vector(0, i) for i in range(1)], [Vector(0, i) for i in range(1)], ], solver=sg.solver, allow_rotations=True ) # Constrain the given ship segment counts and ship segments. for y, count in enumerate(GIVENS_Y): sg.solver.add( PbEq([(Not(sg.cell_is(Point(y, x), SYM.X)), 1) for x in range(WIDTH)], count) ) for x, count in enumerate(GIVENS_X): sg.solver.add( PbEq([(Not(sg.cell_is(Point(y, x), SYM.X)), 1) for y in range(HEIGHT)], count) ) for p, s in GIVENS.items(): sg.solver.add(sg.cell_is(p, s)) for p in LATTICE.points: shape_type = sc.shape_type_grid[p] shape_id = sc.shape_instance_grid[p] touching_types = [ n.symbol for n in LATTICE.vertex_sharing_neighbors(sc.shape_type_grid, p) ] touching_ids = [ n.symbol for n in LATTICE.vertex_sharing_neighbors(sc.shape_instance_grid, p) ] # Link the X symbol to the absence of a ship segment. sg.solver.add( (sc.shape_type_grid[p] == -1) == sg.cell_is(p, SYM.X)) # Ship segments of different ships may not touch. and_terms = [] for touching_id in touching_ids: and_terms.append( Implies( shape_id >= 0, Or(touching_id == shape_id, touching_id == -1) ) ) sg.solver.add(And(*and_terms)) # Choose the correct symbol for each ship segment. touching_count_terms = [(c == shape_type, 1) for c in touching_types] sg.solver.add( Implies( And(shape_type >= 0, PbEq(touching_count_terms, 2)), sg.cell_is(p, SYM.B) ) ) sg.solver.add( Implies( And(shape_type >= 0, PbEq(touching_count_terms, 0)), sg.cell_is(p, SYM.O) ) ) for n in sg.edge_sharing_neighbors(p): sg.solver.add( Implies( And( shape_type >= 0, PbEq(touching_count_terms, 1), sc.shape_type_grid[n.location] == shape_type ), sg.cell_is(p, DIR_TO_OPPOSITE_SYM[n.direction]) ) ) if sg.solve(): sg.print() print() sc.print_shape_instances() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() print() sc.print_shape_instances() print() else: print("No solution")
def region_solve(): """Slitherlink solver example using regions.""" sym = grilops.SymbolSet([("I", chr(0x2588)), ("O", " ")]) lattice = grilops.get_rectangle_lattice(HEIGHT, WIDTH) sg = grilops.SymbolGrid(lattice, sym) rc = grilops.regions.RegionConstrainer(lattice, solver=sg.solver, complete=False) def constrain_no_inside_diagonal(y, x): """Add constraints for diagonally touching cells. "Inside" cells may not diagonally touch each other unless they also share an adjacent cell. """ nw = sg.grid[Point(y, x)] ne = sg.grid[Point(y, x + 1)] sw = sg.grid[Point(y + 1, x)] se = sg.grid[Point(y + 1, x + 1)] sg.solver.add( Implies(And(nw == sym.I, se == sym.I), Or(ne == sym.I, sw == sym.I))) sg.solver.add( Implies(And(ne == sym.I, sw == sym.I), Or(nw == sym.I, se == sym.I))) region_id = Int("region_id") for p in lattice.points: # Each cell must be either "inside" (part of a single region) or # "outside" (not part of any region). sg.solver.add( Or(rc.region_id_grid[p] == region_id, rc.region_id_grid[p] == -1)) sg.solver.add( (sg.grid[p] == sym.I) == (rc.region_id_grid[p] == region_id)) if p not in GIVENS: continue given = GIVENS[p] neighbors = sg.edge_sharing_neighbors(p) # The number of grid edge border segments adjacent to this cell. num_grid_borders = 4 - len(neighbors) # The number of adjacent cells on the opposite side of the loop line. num_different_neighbors_terms = [(n.symbol != sg.grid[p], 1) for n in neighbors] # If this is an "inside" cell, we should count grid edge borders as loop # segments, but if this is an "outside" cell, we should not. sg.solver.add( If(sg.grid[p] == sym.I, PbEq(num_different_neighbors_terms, given - num_grid_borders), PbEq(num_different_neighbors_terms, given))) # "Inside" cells may not diagonally touch each other unless they also share # an adjacent cell. for y in range(HEIGHT - 1): for x in range(WIDTH - 1): constrain_no_inside_diagonal(y, x) if sg.solve(): sg.print() print() if sg.is_unique(): print("Unique solution") else: print("Alternate solution") sg.print() else: print("No solution")
def __add_single_copy_constraints(self, shape_index, shape): sum_terms = [] for shape_type in self.__shape_type_grid.values(): sum_terms.append((shape_type == shape_index, 1)) self.__solver.add(PbEq(sum_terms, len(shape.offsets_with_payloads)))
Or([is_bomb[rb][c] for rb in squares_in_between(rD, r)])) for rD in D_scout_moves_rows[r, c] ] + [ Implies(is_scout[rU][c], Or([is_bomb[rb][c] for rb in squares_in_between(r, rU)])) for rU in U_scout_moves_rows[r, c] ])) for (r, c) in board() if (r, c) not in lakes() ] # Clauses s = Solver() s.add(no_scouts_and_bombs_on_same_square) s.add(no_scouts_or_bombs_in_lakes) s.add(no_bombs_in_dmz) s.add(at_most_one_more_scout_than_bombs_per_segment) s.add(no_scout_threatens_another_scout) s.add(at_most_six_bombs_in_red_setup) s.add(at_most_six_bombs_in_blu_setup) # Objective max_scouts = 24 num_scouts = PbEq([(is_scout[r][c], 1) for (r, c) in board()], max_scouts) s.add(num_scouts) if s.check() == sat: print("Maximum number of scouts satisfying constraints == %s." % max_scouts) print(diagram(s.model())) else: print("Z3 failed to find a solution.")
scout_moves_from = np.array([ list(chain( zip(repeat(r), L_scout_moves_from(r, c)), zip(repeat(r), R_scout_moves_from(r, c)), zip(D_scout_moves_from(r, c), repeat(c)), zip(U_scout_moves_from(r, c), repeat(c)) )) for (r, c) in board() ]).reshape(H, W) scouts_threaten_exactly_one_other_scout = [ Implies( is_scout[r][c], PbEq([ (is_scout[dr][dc], 1) for (dr, dc) in scout_moves_from[r, c] ], 1) ) for (r, c) in board() if (r, c) not in lakes() ] # Clauses (Optimize() takes too long on this problem, Solver() will proof max_scouts = 18 instantly, and disproof max_scouts = 20 within a minute) s = Solver() s.add(no_scouts_in_lakes) s.add(at_most_two_scouts_per_segment) s.add(scouts_threaten_exactly_one_other_scout) # Objective max_scouts = 20 num_scouts = PbEq([ (is_scout[r][c], 1) for (r, c) in board() ], max_scouts) s.add(num_scouts)
def __add_single_copy_constraints(self, shape_index, shape): sum_terms = [] for p in self.__shape_type_grid: sum_terms.append((self.__shape_type_grid[p] == shape_index, 1)) self.__solver.add(PbEq(sum_terms, len(shape)))