Skip to content

Commit

Permalink
Merge pull request #73 from parmentelat/all_solutions
Browse files Browse the repository at this point in the history
test solution_count on small_trimino problem
  • Loading branch information
jwg4 authored Nov 15, 2023
2 parents d214e73 + 4faecc5 commit 40fa64b
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 50 deletions.
23 changes: 23 additions & 0 deletions tests/bruteforce.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
bruteforce algorithm to compute the expected solutions
and help write tests
"""

from itertools import chain, combinations

import numpy as np

# from the itertools module documentation
def powerset(iterable):
"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))


def bruteforce(data):
"""
Brute-force generator of all exact cover solutions
"""
for subset in powerset(range(data.shape[0])):
if np.all(data[list(subset)].sum(axis=0) == 1):
yield subset
126 changes: 126 additions & 0 deletions tests/problems.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import numpy as np

from exact_cover.io import DTYPE_FOR_ARRAY

# one specific problem that I had trouble with
# originally based on solving the trivial problem
# of arranging 2 identical triminos on a 3x3 board

# +--+
# | |
# +--+--+
# | | |
# +--+--+

# +--+--+--+
# |xx| |xx|
# +--+--+--+
# | | | |
# +--+--+--+
# |xx| | |
# +--+--+--+

# this problem has 2 solutions
# (5, 13) and (6, 12)
def small_trimino_problem():
to_cover = [
[1, 0, 0, 1, 1, 0, 1, 0],
[1, 0, 0, 0, 1, 1, 0, 1],
[1, 0, 0, 0, 1, 1, 1, 0],
[1, 0, 1, 0, 1, 1, 0, 0],
[1, 0, 0, 0, 1, 0, 1, 1],
[1, 0, 1, 1, 1, 0, 0, 0], # <- 5
[1, 0, 0, 0, 0, 1, 1, 1], # <- 6
[0, 1, 0, 1, 1, 0, 1, 0],
[0, 1, 0, 0, 1, 1, 0, 1],
[0, 1, 0, 0, 1, 1, 1, 0],
[0, 1, 1, 0, 1, 1, 0, 0],
[0, 1, 0, 0, 1, 0, 1, 1],
[0, 1, 1, 1, 1, 0, 0, 0], # <- 12
[0, 1, 0, 0, 0, 1, 1, 1], # <- 13
]
return dict(
data=np.array(to_cover, dtype=DTYPE_FOR_ARRAY),
solution1=[5, 13],
solution_count=2,
)

def small_trimino_problem_from_file():
return dict(
data=np.load("tests/files/small_trimino_problem.npy"),
solution1=[5, 13],
solution_count=2,
)

# https://en.wikipedia.org/wiki/Exact_cover#Detailed_example
def detailed_wikipedia_problem():
sets = [
{1, 4, 7},
{1, 4}, # <- 1
{4, 5, 7},
{3, 5, 6}, # <- 3
{2, 3, 6, 7},
{2, 7}, # <- 5
]
return dict(
data=np.array(
[[1 if i in s else 0 for i in range(1, 8)] for s in sets],
dtype=DTYPE_FOR_ARRAY),
solution1=[1, 3, 5],
solution_count=1,
)

def bruteforce_problem1():
to_cover = [
[1, 0, 0, 1, 0, 0, 1, 0], # <- sol1
[0, 1, 0, 0, 1, 0, 0, 1], # <- sol1
[0, 0, 1, 0, 0, 1, 0, 0], # <- sol1
[0, 0, 0, 1, 0, 0, 0, 0], # <- sol2
[1, 0, 1, 0, 1, 0, 0, 1], # <- sol2
[0, 1, 0, 0, 0, 1, 1, 0], # <- sol2

]
return dict(
data=np.array(to_cover, dtype=DTYPE_FOR_ARRAY),
solution1=[0, 1, 2],
solution_count=2,
)

def bruteforce_problem2():
to_cover = [
[1, 0, 0, 1, 0, 0, 1, 0], # <- sol1
[0, 1, 0, 0, 1, 0, 0, 1], # <- sol1
[0, 0, 1, 0, 0, 1, 0, 0], # <- sol1
[0, 0, 0, 1, 0, 0, 0, 0], # <- sol2
[1, 0, 1, 0, 1, 0, 0, 1], # <- sol2
[0, 1, 0, 0, 0, 1, 1, 0], # <- sol2
[1, 0, 0, 1, 0, 0, 1, 0], # <- sol1
[0, 1, 0, 0, 1, 0, 0, 1], # <- sol1
[0, 0, 1, 0, 0, 1, 0, 0], # <- sol1
]
return dict(
data=np.array(to_cover, dtype=DTYPE_FOR_ARRAY),
solution1=[0, 1, 2],
solution_count=9,
)

def bruteforce_problem3():
to_cover = [
[1, 0, 0, 1, 0, 0, 1, 0], # <- sol1
[0, 1, 0, 0, 1, 0, 0, 1], # <- sol1
[0, 0, 1, 0, 0, 1, 0, 0], # <- sol1
[0, 0, 0, 1, 0, 0, 0, 0], # <- sol2
[1, 0, 1, 0, 1, 0, 0, 1], # <- sol2
[0, 1, 0, 0, 0, 1, 1, 0], # <- sol2
[1, 0, 0, 1, 0, 0, 1, 0], # <- sol1
[0, 1, 0, 0, 1, 0, 0, 1], # <- sol1
[0, 0, 1, 0, 0, 1, 0, 0], # <- sol1
[0, 0, 0, 1, 0, 0, 0, 0], # <- sol2
[1, 0, 1, 0, 1, 0, 0, 1], # <- sol2
[0, 1, 0, 0, 0, 1, 1, 0], # <- sol2
]
return dict(
data=np.array(to_cover, dtype=DTYPE_FOR_ARRAY),
solution1=[0, 1, 2],
solution_count=16,
)
33 changes: 33 additions & 0 deletions tests/test_solution_count.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
from exact_cover_impl import get_solution_count
from exact_cover.io import DTYPE_FOR_ARRAY

from .problems import small_trimino_problem
from .problems import detailed_wikipedia_problem
from .problems import bruteforce_problem1

def test_solution_count():
data = np.array([[1, 0, 0], [0, 1, 0], [0, 1, 1], [0, 0, 1]], dtype=DTYPE_FOR_ARRAY)
Expand All @@ -20,3 +23,33 @@ def test_solution_count_no_solutions():
data = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0], [0, 0, 1, 0]], dtype=DTYPE_FOR_ARRAY)
result = get_solution_count(data)
assert result == 0

def test_small_trimino_problem():
problem = small_trimino_problem()
data = problem['data']
result = get_solution_count(data)
assert result == problem['solution_count']

def test_detailed_wikipedia_problem():
problem = detailed_wikipedia_problem()
data = problem['data']
result = get_solution_count(data)
assert result == problem['solution_count']

def test_bruteforce_problem1():
problem = bruteforce_problem1()
data = problem['data']
result = get_solution_count(data)
assert result == problem['solution_count']

def test_bruteforce_problem2():
problem = bruteforce_problem2()
data = problem['data']
result = get_solution_count(data)
assert result == problem['solution_count']

def test_bruteforce_problem3():
problem = bruteforce_problem3()
data = problem['data']
result = get_solution_count(data)
assert result == problem['solution_count']
61 changes: 11 additions & 50 deletions tests/test_trimino_based.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,19 @@

from exact_cover import get_exact_cover
from exact_cover.error import NoSolution
from exact_cover.io import DTYPE_FOR_ARRAY

# from exact_cover.helpers import is_solution
from .problems import (
# the exact cover matrix built manually
small_trimino_problem,
# the same store in a file
small_trimino_problem_from_file)

DTYPE = dict(dtype=DTYPE_FOR_ARRAY)

# from exact_cover.io import load_problem

# from tests.config import GLOBAL_CONFIG


# one specific problem that I had trouble with
# originally based on solving the trivial problem
# of arranging 2 identical triminos on a 3x3 board

# +--+
# | |
# +--+--+
# | | |
# +--+--+

# +--+--+--+
# |xx| |xx|
# +--+--+--+
# | | | |
# +--+--+--+
# |xx| | |
# +--+--+--+


# the exact cover matrix built manually
def input1():
to_cover = [
[1, 0, 0, 1, 1, 0, 1, 0],
[1, 0, 0, 0, 1, 1, 0, 1],
[1, 0, 0, 0, 1, 1, 1, 0],
[1, 0, 1, 0, 1, 1, 0, 0],
[1, 0, 0, 0, 1, 0, 1, 1],
[1, 0, 1, 1, 1, 0, 0, 0], # <- 5
[1, 0, 0, 0, 0, 1, 1, 1],
[0, 1, 0, 1, 1, 0, 1, 0],
[0, 1, 0, 0, 1, 1, 0, 1],
[0, 1, 0, 0, 1, 1, 1, 0],
[0, 1, 1, 0, 1, 1, 0, 0],
[0, 1, 0, 0, 1, 0, 1, 1],
[0, 1, 1, 1, 1, 0, 0, 0],
[0, 1, 0, 0, 0, 1, 1, 1], # <- 13
]
return np.array(to_cover, **DTYPE)

return small_trimino_problem()['data']

def input2():
return np.load("tests/files/small_trimino_problem.npy")

return small_trimino_problem_from_file()['data']

def test_inputs_are_equal():
m1 = input1()
Expand All @@ -77,8 +36,10 @@ def run_on_input(array, expected):


def test_input1():
run_on_input(input1(), [5, 13])
expected = small_trimino_problem()['solution1']
run_on_input(input1(), expected)


def test_input2():
run_on_input(input2(), [5, 13])
expected = small_trimino_problem_from_file()['solution1']
run_on_input(input2(), expected)

0 comments on commit 40fa64b

Please sign in to comment.