Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for irregular grids #3

Merged
merged 1 commit into from
Dec 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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