def solve_slitherlink(height, width, problem): solver = Solver() grid_frame = BoolGridFrame(solver, height, width) solver.add_answer_key(grid_frame) graph.active_edges_single_cycle(solver, grid_frame) for y in range(height): for x in range(width): if problem[y][x] >= 0: solver.ensure( count_true(grid_frame.cell_neighbors(y, x)) == problem[y] [x]) is_sat = solver.solve() return is_sat, grid_frame
def solve_geradeweg(height, width, problem): def line_length(edges): edges = list(edges) if len(edges) == 0: return 0 ret = edges[-1].cond(1, 0) for i in range(len(edges) - 2, -1, -1): ret = edges[i].cond(1 + ret, 0) return ret solver = Solver() grid_frame = BoolGridFrame(solver, height - 1, width - 1) solver.add_answer_key(grid_frame) is_passed = graph.active_edges_single_cycle(solver, grid_frame) for y in range(height): for x in range(width): if problem[y][x] >= 1: solver.ensure(is_passed[y, x]) solver.ensure(fold_or(([grid_frame.horizontal[y, x - 1]] if x > 0 else []) + ([grid_frame.horizontal[y, x]] if x < width - 1 else [])).then( line_length(reversed(list(grid_frame.horizontal[y, :x]))) + line_length(grid_frame.horizontal[y, x:]) == problem[y][x] )) solver.ensure(fold_or(([grid_frame.vertical[y - 1, x]] if y > 0 else []) + ([grid_frame.vertical[y, x]] if y < height - 1 else [])).then( line_length(reversed(list(grid_frame.vertical[:y, x]))) + line_length(grid_frame.vertical[y:, x]) == problem[y][x] )) is_sat = solver.solve() return is_sat, grid_frame
def _add_constraints(self, roots): solver = self.solver height = self.height width = self.width region_id = self.region_id rank = IntGrid(solver, height, width, low=0, high=height * width - 1) is_root = BoolGrid(solver, height, width) spanning_forest = BoolGridFrame(solver, height - 1, width - 1) for y in range(height): for x in range(width): neighbors = [] if y > 0: neighbors.append((y - 1, x)) if y < height - 1: neighbors.append((y + 1, x)) if x > 0: neighbors.append((y, x - 1)) if x < width - 1: neighbors.append((y, x + 1)) solver.ensure( sum([(spanning_forest[y + y2, x + x2] & (rank[y, x] > rank[y2, x2])).cond(1, 0) for y2, x2 in neighbors]) == is_root[y, x].cond(0, 1)) if y > 0: solver.ensure(spanning_forest[y * 2 - 1, x * 2].then( (region_id[y, x] == region_id[y - 1, x]) & (rank[y, x] != rank[y - 1, x]))) if x > 0: solver.ensure(spanning_forest[y * 2, x * 2 - 1].then( (region_id[y, x] == region_id[y, x - 1]) & (rank[y, x] != rank[y, x - 1]))) for i in range(self.num_regions): solver.ensure( sum([(r & (n == i)).cond(1, 0) for r, n in zip(is_root[:, :], region_id[:, :])]) == 1) if roots is not None: for i, (y, x) in enumerate(roots): solver.ensure(region_id[y, x] == i) solver.ensure(is_root[y, x])
def solve_masyu(height, width, problem): solver = Solver() grid_frame = BoolGridFrame(solver, height - 1, width - 1) solver.add_answer_key(grid_frame) graph.active_edges_single_cycle(solver, grid_frame) def get_edge(y, x, neg=False): if 0 <= y <= 2 * (height - 1) and 0 <= x <= 2 * (width - 1): if y % 2 == 0: r = grid_frame.horizontal[y // 2][x // 2] else: r = grid_frame.vertical[y // 2][x // 2] if neg: return ~r else: return r else: return neg for y in range(height): for x in range(width): if problem[y][x] == 1: solver.ensure( (get_edge(y * 2, x * 2 - 1) & get_edge(y * 2, x * 2 + 1) & (get_edge(y * 2, x * 2 - 3, True) | get_edge(y * 2, x * 2 + 3, True))) | (get_edge(y * 2 - 1, x * 2) & get_edge(y * 2 + 1, x * 2) & (get_edge(y * 2 - 3, x * 2, True) | get_edge(y * 2 + 3, x * 2, True)))) elif problem[y][x] == 2: dirs = [ get_edge(y * 2, x * 2 - 1) & get_edge(y * 2, x * 2 - 3), get_edge(y * 2 - 1, x * 2) & get_edge(y * 2 - 3, x * 2), get_edge(y * 2, x * 2 + 1) & get_edge(y * 2, x * 2 + 3), get_edge(y * 2 + 1, x * 2) & get_edge(y * 2 + 3, x * 2), ] solver.ensure((dirs[0] | dirs[2]) & (dirs[1] | dirs[3])) is_sat = solver.solve() return is_sat, grid_frame
def solve_yajilin(height, width, problem): solver = Solver() grid_frame = BoolGridFrame(solver, height - 1, width - 1) is_passed = graph.active_edges_single_cycle(solver, grid_frame) black_cell = solver.bool_array((height, width)) graph.active_vertices_not_adjacent(solver, black_cell) solver.add_answer_key(grid_frame) solver.add_answer_key(black_cell) for y in range(height): for x in range(width): if problem[y][x] != '..': # clue solver.ensure(~is_passed[y, x]) solver.ensure(~black_cell[y, x]) if problem[y][x][0] == '^': solver.ensure( count_true(black_cell[0:y, x]) == int(problem[y][x][1:])) elif problem[y][x][0] == 'v': solver.ensure( count_true(black_cell[(y + 1):height, x]) == int(problem[y][x][1:])) elif problem[y][x][0] == '<': solver.ensure( count_true(black_cell[y, 0:x]) == int(problem[y][x][1:])) elif problem[y][x][0] == '>': solver.ensure( count_true(black_cell[y, ( x + 1):width]) == int(problem[y][x][1:])) else: solver.ensure(is_passed[y, x] != black_cell[y, x]) is_sat = solver.solve() return is_sat, grid_frame, black_cell
def solve_slalom(height, width, origin, is_black, gates, reference_sol_loop=None): solver = Solver() loop = BoolGridFrame(solver, height - 1, width - 1) loop_dir = BoolGridFrame(solver, height - 1, width - 1) solver.add_answer_key(loop.all_edges()) graph.active_edges_single_cycle(solver, loop) gate_ord = solver.int_array((height, width), 0, len(gates)) passed = solver.bool_array((height, width)) gate_id = [[None for _ in range(width)] for _ in range(height)] for y, x, d, l, n in gates: if d == 0: # horizontal gate_cells = [(y, x + i) for i in range(l)] elif d == 1: # vertical gate_cells = [(y + i, x) for i in range(l)] for y2, x2 in gate_cells: gate_id[y2][x2] = n solver.ensure( count_true([passed[y2, x2] for y2, x2 in gate_cells]) == 1) solver.ensure(passed[origin]) for y in range(height): for x in range(width): neighbors = [] if y > 0: neighbors.append((y - 1, x)) if y < height - 1: neighbors.append((y + 1, x)) if x > 0: neighbors.append((y, x - 1)) if x < width - 1: neighbors.append((y, x + 1)) # in-degree, out-degree solver.ensure( count_true([ loop[y + y2, x + x2] & (loop_dir[y + y2, x + x2] != ((y2, x2) < (y, x))) for y2, x2 in neighbors ]) == passed[y, x].cond(1, 0)) solver.ensure( count_true([ loop[y + y2, x + x2] & (loop_dir[y + y2, x + x2] == ((y2, x2) < (y, x))) for y2, x2 in neighbors ]) == passed[y, x].cond(1, 0)) if is_black[y][x]: solver.ensure(~passed[y, x]) continue if (y, x) == origin: continue if gate_id[y][x] is None: for y2, x2 in neighbors: solver.ensure((loop[y + y2, x + x2] & (loop_dir[y + y2, x + x2] != ((y2, x2) < (y, x)))).then( (gate_ord[y2, x2] == gate_ord[y, x]))) else: for y2, x2 in neighbors: solver.ensure((loop[y + y2, x + x2] & (loop_dir[y + y2, x + x2] != ((y2, x2) < (y, x)))).then( (gate_ord[y2, x2] == gate_ord[y, x] - 1))) if gate_id[y][x] >= 1: solver.ensure(passed[y, x].then(gate_ord[y, x] == gate_id[y][x])) # auxiliary constraint for y0 in range(height): for x0 in range(width): for y1 in range(height): for x1 in range(width): if (y0, x0) < (y1, x1) and gate_id[y0][ x0] is not None and gate_id[y1][x1] is not None: solver.ensure((passed[y0, x0] & passed[y1, x1]).then( gate_ord[y0, x0] != gate_ord[y1, x1])) if reference_sol_loop is not None: avoid_reference_sol = [] for y in range(height): for x in range(width): if y < height - 1: avoid_reference_sol.append( loop.vertical[y, x] != reference_sol_loop.vertical[y, x].sol) if x < width - 1: avoid_reference_sol.append(loop.horizontal[ y, x] != reference_sol_loop.horizontal[y, x].sol) solver.ensure(fold_or(avoid_reference_sol)) is_sat = solver.find_answer() return is_sat, loop else: is_sat = solver.solve() return is_sat, loop
def generate_slalom_initial_placement(height, width, n_min_gates=None, n_max_gates=None, n_max_isolated_black_cells=None, no_adjacent_black_cell=False, no_facing_length_two=False, no_space_2x2=False, black_cell_in_every_3x3=False, min_go_through=0): solver = Solver() loop = BoolGridFrame(solver, height - 1, width - 1) is_black = solver.bool_array((height, width)) is_horizontal = solver.bool_array((height, width)) is_vertical = solver.bool_array((height, width)) solver.ensure(~(is_black & is_horizontal)) solver.ensure(~(is_black & is_vertical)) solver.ensure(~(is_horizontal & is_vertical)) solver.ensure(~(is_horizontal[0, :])) solver.ensure(~(is_horizontal[-1, :])) solver.ensure(~(is_vertical[:, 0])) solver.ensure(~(is_vertical[:, -1])) is_passed = graph.active_edges_single_cycle(solver, loop) # --------- board must be valid as a problem --------- # loop constraints for y in range(height): for x in range(width): if y > 0: solver.ensure(is_black[y, x].then(~loop.vertical[y - 1, x])) solver.ensure(is_vertical[y, x].then(~loop.vertical[y - 1, x])) if y < height - 1: solver.ensure(is_black[y, x].then(~loop.vertical[y, x])) solver.ensure(is_vertical[y, x].then(~loop.vertical[y, x])) if x > 0: solver.ensure(is_black[y, x].then(~loop.horizontal[y, x - 1])) solver.ensure( is_horizontal[y, x].then(~loop.horizontal[y, x - 1])) if x < width - 1: solver.ensure(is_black[y, x].then(~loop.horizontal[y, x])) solver.ensure(is_horizontal[y, x].then(~loop.horizontal[y, x])) # gates must be closed solver.ensure(is_vertical[1:, :].then(is_vertical[:-1, :] | is_black[:-1, :])) solver.ensure(is_vertical[:-1, :].then(is_vertical[1:, :] | is_black[1:, :])) solver.ensure(is_horizontal[:, 1:].then(is_horizontal[:, :-1] | is_black[:, :-1])) solver.ensure(is_horizontal[:, :-1].then(is_horizontal[:, 1:] | is_black[:, 1:])) # each horizontal gate must be passed exactly once for y in range(1, height - 1): for x in range(width): on_loop = [] for x2 in range(width): cond = [is_passed[y, x2]] if x2 < x: cond += [is_horizontal[y, i] for i in range(x2, x)] elif x < x2: cond += [is_horizontal[y, i] for i in range(x + 1, x2 + 1)] on_loop.append(fold_and(cond)) solver.ensure(is_horizontal[y, x].then(count_true(on_loop) == 1)) # each vertical gate must be passed exactly once for y in range(height): for x in range(1, width - 1): on_loop = [] for y2 in range(width): cond = [is_passed[y2, x]] if y2 < y: cond += [is_vertical[i, x] for i in range(y2, y)] elif y < y2: cond += [is_vertical[i, x] for i in range(y + 1, y2 + 1)] on_loop.append(fold_and(cond)) solver.ensure(is_vertical[y, x].then(count_true(on_loop) == 1)) # --------- loop must be canonical --------- # for simplicity, no stacked gates (although this is not necessary for the canonicity) solver.ensure(~(is_horizontal[:-1, :] & is_horizontal[1:, :])) solver.ensure(~(is_vertical[:, :-1] & is_vertical[:, 1:])) for y in range(height): for x in range(width): if 0 < y < height - 1: if x == 0 or x == width - 1: solver.ensure(is_horizontal[y, x].then(~is_black[y - 1, x] & ~is_black[y + 1, x])) else: solver.ensure((is_horizontal[y, x] & (is_black[y - 1, x] | is_black[y + 1, x]) ).then(is_horizontal[y, x - 1] & is_horizontal[y, x + 1] & ~is_black[y - 1, x - 1] & ~is_black[y + 1, x - 1] & ~is_black[y + 1, x - 1] & ~is_black[y + 1, x + 1])) if 0 < x < width - 1: if y == 0 or y == height - 1: solver.ensure(is_vertical[y, x].then(~is_black[y, x - 1] & ~is_black[y, x + 1])) else: solver.ensure( (is_vertical[y, x] & (is_black[y, x - 1] | is_black[y, x + 1]) ).then(is_vertical[y - 1, x] & is_vertical[y + 1, x] & ~is_black[y - 1, x - 1] & ~is_black[y + 1, x - 1] & ~is_black[y + 1, x - 1] & ~is_black[y + 1, x + 1])) # no detour for y in range(height - 1): for x in range(width - 1): solver.ensure(count_true(loop.cell_neighbors(y, x)) <= 2) solver.ensure( fold_and(~is_black[y:y + 2, x:x + 2], ~is_horizontal[y:y + 2, x:x + 2], ~is_vertical[y:y + 2, x:x + 2]).then( count_true(loop.cell_neighbors(y, x)) + 1 < count_true(is_passed[y:y + 2, x:x + 2]))) # no ambiguous L-shaped turning for y in range(height - 1): for x in range(width - 1): for dy in [0, 1]: for dx in [0, 1]: solver.ensure(~fold_and([ loop.horizontal[y + dy, x], loop.vertical[ y, x + dx], ~is_vertical[y + dy, x + 1 - dx], ~is_horizontal[y + 1 - dx, x + dx], ~is_black[y + 1 - dy, x + 1 - dx], count_true(is_passed[y:y + 2, x:x + 2]) == 3 ])) # no ambiguous L-shaped turning involving gates for y in range(height - 1): for x in range(width - 2): solver.ensure( fold_and( is_vertical[y:y + 2, x + 1], ~is_black[y:y + 2, x:x + 3]).then( count_true(loop.horizontal[y, x], loop.horizontal[ y + 1, x], loop.vertical[y, x], loop.vertical[y, x + 2]) + 1 < count_true(is_passed[y:y + 2, x], is_passed[y:y + 2, x + 2]))) for y in range(height - 2): for x in range(width - 1): solver.ensure( fold_and(is_horizontal[y + 1, x:x + 2], ~is_black[y:y + 3, x:x + 2]). then( count_true(loop.vertical[y, x], loop.vertical[y, x + 1], loop.horizontal[y, x], loop.horizontal[y + 2, x]) + 1 < count_true(is_passed[y, x:x + 2], is_passed[y + 2, x:x + 2]))) # no dead ends for y in range(height): for x in range(width): solver.ensure((~is_black[y, x]).then( count_true(~is_black.four_neighbors(y, x)) >= 2)) # --------- avoid "trivial" problems --------- solver.ensure(count_true(is_vertical) > 5) solver.ensure(count_true(is_horizontal) > 4) if n_max_isolated_black_cells is not None: lonely_black_cell = [] for y in range(height): for x in range(width): cond = [is_black[y, x]] if y > 0: cond.append(~is_vertical[y - 1, x]) if y < height - 1: cond.append(~is_vertical[y + 1, x]) if x > 0: cond.append(~is_horizontal[y, x - 1]) if x < width - 1: cond.append(~is_horizontal[y, x + 1]) lonely_black_cell.append(fold_and(cond)) solver.ensure( count_true(lonely_black_cell) <= n_max_isolated_black_cells) short_gates = [] for y in range(height): for x in range(width): g1 = fold_and([ is_vertical[y, x], ~is_vertical[y - 1, x] if y > 0 else True, ~is_vertical[y + 1, x] if y < height - 1 else True ]) g2 = fold_and([ is_horizontal[y, x], ~is_horizontal[y, x - 1] if x > 0 else True, ~is_horizontal[y, x + 1] if x < width - 1 else True ]) if 0 < y < height - 1 and 0 < x < width - 1: short_gates.append(g1) short_gates.append(g2) solver.ensure((g1 | g2).then(~is_black[y - 1, x - 1] & ~is_black[y - 1, x + 1] & ~is_black[y + 1, x - 1] & ~is_black[y + 1, x + 1])) else: solver.ensure(~g1) solver.ensure(~g2) solver.ensure(count_true(short_gates) <= 0) for y in range(1, height - 1): for x in range(1, width - 1): solver.ensure( count_true( is_horizontal[y - 1, x] & is_black[y - 1, x - 1] & is_black[y - 1, x + 1], is_horizontal[y + 1, x] & is_black[y + 1, x - 1] & is_black[y + 1, x + 1], is_vertical[y, x - 1] & is_black[y - 1, x - 1] & is_black[y + 1, x - 1], is_vertical[y, x + 1] & is_black[y - 1, x + 1] & is_black[y + 1, x + 1], ) <= 1) # --------- ensure randomness --------- passed_constraints = [[0 for _ in range(width)] for _ in range(height)] for y in range(height): for x in range(width): if (y > 0 and passed_constraints[y - 1][x] != 0) or ( x > 0 and passed_constraints[y][x - 1] != 0): continue passed_constraints[y][x] = max(0, random.randint(-20, 2)) for y in range(height): for x in range(width): if passed_constraints[y][x] == 1: solver.ensure(is_passed[y, x]) elif passed_constraints[y][x] == 2: solver.ensure(~is_passed[y, x]) # --------- extra constraints --------- if n_min_gates is not None or n_max_gates is not None: gate_representative = [] for y in range(height): for x in range(width): gate_representative.append(is_horizontal[y, x] & ( ~is_horizontal[y, x - 1] if x > 0 else True)) gate_representative.append(is_vertical[y, x] & ( ~is_vertical[y - 1, x] if y > 0 else True)) if n_min_gates is not None: solver.ensure(n_min_gates <= count_true(gate_representative)) if n_max_gates is not None: solver.ensure(count_true(gate_representative) <= n_max_gates) if min_go_through > 0: go_through = [] for y in range(height): for x in range(width): if y < height - 4 and 0 < x < width - 1: go_through.append( fold_and( is_horizontal[y + 1, x], is_horizontal[y + 1, x - 1] | is_horizontal[y + 1, x + 1], ~is_black[y + 2, x - 1], ~is_black[y + 2, x + 1], is_horizontal[y + 3, x], is_horizontal[y + 3, x - 1] | is_horizontal[y + 3, x + 1], loop.vertical[y:y + 4, x])) if x < width - 4 and 0 < y < height - 1: go_through.append( fold_and( is_vertical[y, x + 1], is_vertical[y - 1, x + 1] | is_vertical[y + 1, x + 1], ~is_black[y - 1, x + 2], ~is_black[y + 1, x + 2], is_vertical[y, x + 3], is_vertical[y - 1, x + 3] | is_vertical[y + 1, x + 3], loop.horizontal[y, x:x + 4])) solver.ensure(count_true(go_through) >= 2) if no_adjacent_black_cell: solver.ensure(~(is_black[:-1, :] & is_black[1:, :])) solver.ensure(~(is_black[:, :-1] & is_black[:, 1:])) solver.ensure(~(is_black[:-1, :-1] & is_black[1:, 1:])) solver.ensure(~(is_black[:-1, 1:] & is_black[1:, :-1])) if no_facing_length_two: for y in range(height): for x in range(width): if y <= height - 3 and x <= width - 4: solver.ensure(~fold_and( is_black[y, x], is_black[y + 2, x], is_black[y, x + 3], is_black[y + 2, x + 3], is_horizontal[ y, x + 1], is_horizontal[y, x + 2], is_horizontal[ y + 2, x + 1], is_horizontal[y + 2, x + 2])) if y <= height - 4 and x <= width - 3: solver.ensure( ~fold_and(is_black[y, x], is_black[y, x + 2], is_black[ y + 3, x], is_black[y + 3, x + 2], is_vertical[ y + 1, x], is_vertical[y + 2, x], is_vertical[ y + 1, x + 2], is_vertical[y + 2, x + 2])) if no_space_2x2: has_some = is_black | is_vertical | is_horizontal solver.ensure(has_some[:-1, :-1] | has_some[1:, :-1] | has_some[:-1, 1:] | has_some[1:, 1:]) if black_cell_in_every_3x3: for y in range(-1, height - 2): for x in range(-1, width - 2): solver.ensure( fold_or(is_black[max(0, y):min(height, y + 3), max(0, x):min(width, x + 3)])) is_sat = solver.find_answer() if not is_sat: return None return loop, is_passed, is_black, is_horizontal, is_vertical
def solve_firefly(height, width, problem): solver = Solver() has_line = BoolGridFrame(solver, height - 1, width - 1) solver.add_answer_key(has_line) line_ul = BoolGridFrame(solver, height - 1, width - 1) line_dr = BoolGridFrame(solver, height - 1, width - 1) solver.ensure( Array(list(has_line)) == (Array(list(line_ul)) | Array(list(line_dr)))) solver.ensure(~(Array(list(line_ul)) & Array(list(line_dr)))) # unicyclic (= connected) ignored_edge = BoolGridFrame(solver, height - 1, width - 1) solver.ensure(count_true(ignored_edge) == 1) rank = solver.int_array((height, width), 0, height * width - 1) solver.ensure( (line_ul.horizontal & ~ignored_edge.horizontal).then(rank[:, :-1] < rank[:, 1:])) solver.ensure((line_ul.vertical & ~ignored_edge.vertical).then(rank[:-1, :] < rank[1:, :])) solver.ensure( (line_dr.horizontal & ~ignored_edge.horizontal).then(rank[:, :-1] > rank[:, 1:])) solver.ensure((line_dr.vertical & ~ignored_edge.vertical).then(rank[:-1, :] > rank[1:, :])) max_n_turn = 0 for y in range(height): for x in range(width): if problem[y][x][0] != '.' and problem[y][x][1] != '?': max_n_turn = max(max_n_turn, int(problem[y][x][1:])) n_turn_unknown = max_n_turn + 1 n_turn_horizontal = solver.int_array((height, width - 1), 0, max_n_turn + 1) n_turn_vertical = solver.int_array((height - 1, width), 0, max_n_turn + 1) for y in range(height): for x in range(width): # u, d, l, r adj = [] # (line_in, line_out, # turn) if y > 0: adj.append((line_dr.vertical[y - 1, x], line_ul.vertical[y - 1, x], n_turn_vertical[y - 1, x])) else: adj.append(None) if y < height - 1: adj.append((line_ul.vertical[y, x], line_dr.vertical[y, x], n_turn_vertical[y, x])) else: adj.append(None) if x > 0: adj.append( (line_dr.horizontal[y, x - 1], line_ul.horizontal[y, x - 1], n_turn_horizontal[y, x - 1])) else: adj.append(None) if x < width - 1: adj.append((line_ul.horizontal[y, x], line_dr.horizontal[y, x], n_turn_horizontal[y, x])) else: adj.append(None) if problem[y][x][0] != '.': if problem[y][x][0] == '^': out_idx = 0 elif problem[y][x][0] == 'v': out_idx = 1 elif problem[y][x][0] == '<': out_idx = 2 elif problem[y][x][0] == '>': out_idx = 3 else: raise ValueError('invalid direction: {}'.format( problem[y][x][0])) if adj[out_idx] is None: solver.ensure(False) break solver.ensure(adj[out_idx][1]) if problem[y][x][1] != '?': solver.ensure(adj[out_idx][2] == int(problem[y][x][1:])) else: solver.ensure(adj[out_idx][2] == n_turn_unknown) for i in range(4): if adj[i] is not None and i != out_idx: solver.ensure(~adj[i][1]) solver.ensure( adj[i][0].then((adj[i][2] == 0) | (adj[i][2] == n_turn_unknown))) else: adj_present = list(filter(lambda x: x is not None, adj)) solver.ensure( count_true(map(lambda x: x[0], adj_present)) <= 1) solver.ensure( count_true(map(lambda x: x[0], adj_present)) == count_true( map(lambda x: x[1], adj_present))) for i in range(4): for j in range(4): if adj[i] is not None and adj[j] is not None and i != j: if (i // 2) == (j // 2): # straight solver.ensure( (adj[i][0] & adj[j][1]).then(adj[i][2] == adj[j][2])) else: solver.ensure( (adj[i][0] & adj[j][1] ).then(((adj[i][2] == n_turn_unknown) & (adj[j][2] == n_turn_unknown)) | (adj[i][2] == adj[j][2] + 1))) is_sat = solver.solve() return is_sat, has_line