Skip to content

Commit

Permalink
Support for irregular grids
Browse files Browse the repository at this point in the history
  • Loading branch information
jaylorch committed Dec 29, 2019
1 parent 7e643e9 commit 3125ccb
Show file tree
Hide file tree
Showing 33 changed files with 1,214 additions and 832 deletions.
18 changes: 11 additions & 7 deletions examples/akari.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import grilops
import grilops.sightlines
from grilops import Point


def main():
Expand Down Expand Up @@ -58,21 +59,23 @@ def main():
("EMPTY", " "),
("LIGHT", "*"),
])
sg = grilops.SymbolGrid(size, size, sym)
locations = grilops.get_square_locations(size)
sg = grilops.SymbolGrid(locations, sym)

for y in range(size):
for x in range(size):
p = Point(y, x)
if (y, x) in black_cells:
sg.solver.add(sg.cell_is(y, x, sym.BLACK))
light_count = black_cells[(y, x)]
sg.solver.add(sg.cell_is(p, sym.BLACK))
light_count = black_cells[p]
if light_count is not None:
sg.solver.add(PbEq(
[(n.symbol == sym.LIGHT, 1) for n in sg.adjacent_cells(y, x)],
[(n.symbol == sym.LIGHT, 1) for n in sg.adjacent_cells(p)],
light_count
))
else:
# All black cells are given; don't allow this cell to be black.
sg.solver.add(sg.cell_is_one_of(y, x, [sym.EMPTY, sym.LIGHT]))
sg.solver.add(sg.cell_is_one_of(p, [sym.EMPTY, sym.LIGHT]))

def is_black(c):
return c == sym.BLACK
Expand All @@ -83,16 +86,17 @@ def count_light(c):
for x in range(size):
if (y, x) in black_cells:
continue
p = Point(y, x)
visible_light_count = sum(
grilops.sightlines.count_cells(
sg, n.location, n.direction, count=count_light, stop=is_black
) for n in sg.adjacent_cells(y, x)
) for n in sg.adjacent_cells(p)
)
# 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(y, x, sym.LIGHT),
sg.cell_is(p, sym.LIGHT),
visible_light_count == 0,
visible_light_count > 0
)
Expand Down
44 changes: 25 additions & 19 deletions examples/araf.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import grilops
import grilops.regions

from grilops import Point

HEIGHT, WIDTH = 7, 7
GIVENS = {
Expand Down Expand Up @@ -36,42 +36,45 @@ def add_given_pair_constraints(sg, rc):
# 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 (ly, lx), lv in GIVENS.items():
lp = Point(ly, lx)
partner_terms = []
for (sy, sx), sv in GIVENS.items():
if (ly, lx) == (sy, sx):
continue
if lv <= sv:
continue

sp = Point(sy, sx)

# Rule out pairs that can't possibly work, due to the Manhattan distance
# between givens being too large.
manhattan_distance = abs(ly - sy) + abs(lx - sx)
min_region_size = manhattan_distance + 1
if lv < min_region_size:
sg.solver.add(
rc.region_id_grid[sy][sx] != rc.location_to_region_id((ly, lx)))
rc.region_id_grid[sp] != rc.location_to_region_id(lp))
continue

partner_terms.append(
And(
# The smaller given must not be a region root.
Not(rc.parent_grid[sy][sx] == grilops.regions.R),
Not(rc.parent_grid[sp] == grilops.regions.R),

# The givens must share a region, rooted at the larger given.
rc.region_id_grid[sy][sx] == rc.location_to_region_id((ly, lx)),
rc.region_id_grid[sp] == rc.location_to_region_id(lp),

# The region must be larger than the smaller given's value.
sv < rc.region_size_grid[ly][lx]
sv < rc.region_size_grid[lp]
)
)
if not partner_terms:
sg.solver.add(rc.parent_grid[ly][lx] != grilops.regions.R)
sg.solver.add(rc.parent_grid[lp] != grilops.regions.R)
else:
sg.solver.add(
Implies(
rc.parent_grid[ly][lx] == grilops.regions.R,
rc.parent_grid[lp] == grilops.regions.R,
And(
rc.region_size_grid[ly][lx] < lv,
rc.region_size_grid[lp] < lv,
PbEq([(term, 1) for term in partner_terms], 1)
)
)
Expand All @@ -85,34 +88,37 @@ def main():

# The grid symbols will be the region IDs from the region constrainer.
sym = grilops.make_number_range_symbol_set(0, HEIGHT * WIDTH - 1)
sg = grilops.SymbolGrid(HEIGHT, WIDTH, sym)
locations = grilops.get_rectangle_locations(HEIGHT, WIDTH)
sg = grilops.SymbolGrid(locations, sym)
rc = grilops.regions.RegionConstrainer(
HEIGHT, WIDTH, sg.solver,
locations, sg.solver,
min_region_size=min_given_value + 1,
max_region_size=max_given_value - 1
)
for y in range(HEIGHT):
for x in range(WIDTH):
sg.solver.add(sg.cell_is(y, x, rc.region_id_grid[y][x]))
p = Point(y, x)
sg.solver.add(sg.cell_is(p, rc.region_id_grid[p]))

# 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 (y, x), v in GIVENS.items():
p = Point(y, x)
if v == min_given_value:
sg.solver.add(Not(rc.parent_grid[y][x] == grilops.regions.R))
sg.solver.add(Not(rc.parent_grid[p] == grilops.regions.R))
elif v == max_given_value:
sg.solver.add(rc.parent_grid[y][x] == grilops.regions.R)
sg.solver.add(rc.parent_grid[p] == grilops.regions.R)
num_undetermined_roots -= 1
else:
undetermined_given_locations.append((y, x))
undetermined_given_locations.append(p)
sg.solver.add(
PbEq(
[
(rc.parent_grid[y][x] == grilops.regions.R, 1)
for (y, x) in undetermined_given_locations
(rc.parent_grid[p] == grilops.regions.R, 1)
for p in undetermined_given_locations
],
num_undetermined_roots
)
Expand All @@ -122,15 +128,15 @@ def main():
for y in range(HEIGHT):
for x in range(WIDTH):
if (y, x) not in GIVENS:
sg.solver.add(Not(rc.parent_grid[y][x] == grilops.regions.R))
sg.solver.add(Not(rc.parent_grid[Point(y, x)] == grilops.regions.R))

add_given_pair_constraints(sg, rc)

region_id_to_label = {
rc.location_to_region_id((y, x)): chr(65 + i)
rc.location_to_region_id(Point(y, x)): chr(65 + i)
for i, (y, x) in enumerate(GIVENS.keys())
}
def show_cell(unused_y, unused_x, region_id):
def show_cell(unused_loc, region_id):
return region_id_to_label[region_id]

if sg.solve():
Expand Down
62 changes: 32 additions & 30 deletions examples/battleship.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import grilops
import grilops.shapes
from grilops import Point, Vector


SYM = grilops.SymbolSet([
Expand All @@ -26,20 +27,20 @@

def main():
"""Battleship solver example."""
sg = grilops.SymbolGrid(HEIGHT, WIDTH, SYM)
locations = grilops.get_rectangle_locations(HEIGHT, WIDTH)
sg = grilops.SymbolGrid(locations, SYM)
sc = grilops.shapes.ShapeConstrainer(
HEIGHT,
WIDTH,
locations,
[
[(0, i) for i in range(4)],
[(0, i) for i in range(3)],
[(0, i) for i in range(3)],
[(0, i) for i in range(2)],
[(0, i) for i in range(2)],
[(0, i) for i in range(2)],
[(0, i) for i in range(1)],
[(0, i) for i in range(1)],
[(0, i) for i in range(1)],
[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
Expand All @@ -48,29 +49,30 @@ def main():
# Constrain the given ship segment counts and ship segments.
for y, count in enumerate(GIVENS_Y):
sg.solver.add(
PbEq([(Not(sg.cell_is(y, x, SYM.X)), 1) for x in range(WIDTH)], count)
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(y, x, SYM.X)), 1) for y in range(HEIGHT)], count)
PbEq([(Not(sg.cell_is(Point(y, x), SYM.X)), 1) for y in range(HEIGHT)], count)
)
for (y, x), s in GIVENS.items():
sg.solver.add(sg.cell_is(y, x, s))
sg.solver.add(sg.cell_is(Point(y, x), s))

for y in range(HEIGHT):
for x in range(WIDTH):
shape_type = sc.shape_type_grid[y][x]
shape_id = sc.shape_instance_grid[y][x]
p = Point(y, x)
shape_type = sc.shape_type_grid[p]
shape_id = sc.shape_instance_grid[p]
touching_types = [
n.symbol for n in grilops.touching_cells(sc.shape_type_grid, y, x)
n.symbol for n in grilops.touching_cells(sc.shape_type_grid, p)
]
touching_ids = [
n.symbol for n in grilops.touching_cells(sc.shape_instance_grid, y, x)
n.symbol for n in grilops.touching_cells(sc.shape_instance_grid, p)
]

# Link the X symbol to the absence of a ship segment.
sg.solver.add(
(sc.shape_type_grid[y][x] == -1) == sg.cell_is(y, x, SYM.X))
(sc.shape_type_grid[p] == -1) == sg.cell_is(p, SYM.X))

# Ship segments of different ships may not touch.
and_terms = []
Expand All @@ -88,13 +90,13 @@ def main():
sg.solver.add(
Implies(
And(shape_type >= 0, PbEq(touching_count_terms, 2)),
sg.cell_is(y, x, SYM.B)
sg.cell_is(p, SYM.B)
)
)
sg.solver.add(
Implies(
And(shape_type >= 0, PbEq(touching_count_terms, 0)),
sg.cell_is(y, x, SYM.O)
sg.cell_is(p, SYM.O)
)
)
if y > 0:
Expand All @@ -103,9 +105,9 @@ def main():
And(
shape_type >= 0,
PbEq(touching_count_terms, 1),
sc.shape_type_grid[y - 1][x] == shape_type
sc.shape_type_grid[Point(y - 1, x)] == shape_type
),
sg.cell_is(y, x, SYM.S)
sg.cell_is(p, SYM.S)
)
)
if y < HEIGHT - 1:
Expand All @@ -114,9 +116,9 @@ def main():
And(
shape_type >= 0,
PbEq(touching_count_terms, 1),
sc.shape_type_grid[y + 1][x] == shape_type
sc.shape_type_grid[Point(y + 1, x)] == shape_type
),
sg.cell_is(y, x, SYM.N)
sg.cell_is(p, SYM.N)
)
)
if x > 0:
Expand All @@ -125,9 +127,9 @@ def main():
And(
shape_type >= 0,
PbEq(touching_count_terms, 1),
sc.shape_type_grid[y][x - 1] == shape_type
sc.shape_type_grid[Point(y, x - 1)] == shape_type
),
sg.cell_is(y, x, SYM.E)
sg.cell_is(p, SYM.E)
)
)
if x < WIDTH - 1:
Expand All @@ -136,9 +138,9 @@ def main():
And(
shape_type >= 0,
PbEq(touching_count_terms, 1),
sc.shape_type_grid[y][x + 1] == shape_type
sc.shape_type_grid[Point(y, x + 1)] == shape_type
),
sg.cell_is(y, x, SYM.W)
sg.cell_is(p, SYM.W)
)
)

Expand Down
18 changes: 11 additions & 7 deletions examples/castle_wall.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import grilops
from grilops.loops import I, O, LoopSymbolSet, LoopConstrainer
import grilops.sightlines
from grilops import Point, Vector


HEIGHT, WIDTH = 7, 7
Expand Down Expand Up @@ -41,28 +42,31 @@

def main():
"""Castle Wall solver example."""
sg = grilops.SymbolGrid(HEIGHT, WIDTH, SYM)
locations = grilops.get_rectangle_locations(HEIGHT, WIDTH)
sg = grilops.SymbolGrid(locations, SYM)
lc = LoopConstrainer(sg, single_loop=True)

for (y, x), (io, expected_count, direction) in GIVENS.items():
p = Point(y, x)
# Constrain whether the given cell is inside or outside of the loop. This
# also prevents these cells from containing loop symbols themselves.
sg.solver.add(lc.inside_outside_grid[y][x] == io)
sg.solver.add(lc.inside_outside_grid[p] == io)

if expected_count is not None and direction is not None:
# Count and constrain the number of loop segments in the given direction.
segment_symbols = DIRECTION_SEGMENT_SYMBOLS[direction]
dy, dx = direction
actual_count = grilops.sightlines.count_cells(
sg, (y, x), direction,
sg, p, Vector(dy, dx),
lambda c: If(Or(*[c == s for s in segment_symbols]), 1, 0)
)
sg.solver.add(actual_count == expected_count)

def show_cell(y, x, _):
if (y, x) in GIVENS:
if GIVENS[(y, x)][0] == I:
def show_cell(p, _):
if (p.y, p.x) in GIVENS:
if GIVENS[(p.y, p.x)][0] == I:
return chr(0x25AB)
if GIVENS[(y, x)][0] == O:
if GIVENS[(p.y, p.x)][0] == O:
return chr(0x25AA)
return None

Expand Down
Loading

0 comments on commit 3125ccb

Please sign in to comment.