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

Add function get_solution_count. #72

Merged
merged 25 commits into from
Dec 1, 2023
Merged
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
58bb5dc
Add function get_solution_count.
jwg4 Nov 14, 2023
19687bf
Declare the function here.
jwg4 Nov 14, 2023
efc34e6
Remove two lines left over from copy-paste.
jwg4 Nov 14, 2023
6761cd9
Add a single test.
jwg4 Nov 15, 2023
584448d
Add numpy import back in.
jwg4 Nov 15, 2023
d214e73
Add two more similar tests.
jwg4 Nov 15, 2023
5525dfa
test solution_count on small_trimino problem
parmentelat Nov 15, 2023
26d8435
solution_count tested through another problem
parmentelat Nov 15, 2023
4faecc5
more tests
parmentelat Nov 15, 2023
40fa64b
Merge pull request #73 from parmentelat/all_solutions
jwg4 Nov 15, 2023
92066c6
Import the two new problems.
jwg4 Nov 15, 2023
f335125
Formatting cleanups.
jwg4 Nov 15, 2023
1dcf285
Count solutions in C code.
jwg4 Nov 18, 2023
d75da3f
Test solution count with blank rows and repeated rows.
jwg4 Nov 18, 2023
1515a2f
Comment these new tests.
jwg4 Nov 18, 2023
1d351f2
Add a test for solution count.
jwg4 Nov 18, 2023
7428322
Fix long lines.
jwg4 Nov 18, 2023
0561acc
Preminor version 1.4.0a0
jwg4 Nov 18, 2023
14d9cb0
remove declaration of unused variable that triggers a build-time war…
parmentelat Nov 19, 2023
12c94b9
trailing spaces and trailing newlines
parmentelat Nov 19, 2023
2737c18
trailing spaces in the .github area
parmentelat Nov 19, 2023
a9c94d3
Merge pull request #82 from parmentelat/unused-var
jwg4 Nov 30, 2023
d7975ff
Merge pull request #81 from parmentelat/spaces
jwg4 Nov 30, 2023
134ddfc
Import from exact_cover_impl.
jwg4 Nov 30, 2023
81a908f
Add some info about this function to the docs.
jwg4 Nov 30, 2023
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
@@ -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)
@@ -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
@@ -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()
@@ -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)