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

test solution_count on small_trimino problem #73

Merged
merged 3 commits into from
Nov 15, 2023
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
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)