NUCS 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nucs-0.2.0 → nucs-0.3.0/NUCS.egg-info}/PKG-INFO +2 -2
- nucs-0.3.0/NUCS.egg-info/SOURCES.txt +75 -0
- nucs-0.3.0/NUCS.egg-info/top_level.txt +5 -0
- {nucs-0.2.0/NUCS.egg-info → nucs-0.3.0}/PKG-INFO +2 -2
- nucs-0.3.0/nucs/problems/donald_problem.py +42 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/golomb_problem.py +1 -1
- nucs-0.3.0/nucs/problems/knapsack_problem.py +17 -0
- nucs-0.3.0/nucs/problems/latin_square_problem.py +88 -0
- nucs-0.3.0/nucs/problems/quasigroup_problem.py +42 -0
- nucs-0.3.0/nucs/propagators/element_lic_propagator.py +61 -0
- nucs-0.3.0/nucs/propagators/element_liv_propagator.py +58 -0
- nucs-0.3.0/nucs/propagators/relation_propagator.py +35 -0
- {nucs-0.2.0 → nucs-0.3.0}/pyproject.toml +4 -4
- nucs-0.3.0/tests/__init__.py +0 -0
- nucs-0.3.0/tests/examples/test_alpha.py +50 -0
- nucs-0.3.0/tests/examples/test_bibd.py +26 -0
- nucs-0.3.0/tests/examples/test_donald.py +23 -0
- nucs-0.3.0/tests/examples/test_golomb.py +53 -0
- nucs-0.3.0/tests/examples/test_knapsack.py +37 -0
- nucs-0.3.0/tests/examples/test_latin_square.py +39 -0
- nucs-0.3.0/tests/examples/test_quasigroup.py +38 -0
- nucs-0.3.0/tests/examples/test_queens.py +41 -0
- nucs-0.3.0/tests/examples/test_schur_lemma.py +36 -0
- nucs-0.3.0/tests/examples/test_sudokus.py +41 -0
- nucs-0.3.0/tests/problems/__init__.py +0 -0
- nucs-0.3.0/tests/problems/test_problem.py +21 -0
- nucs-0.3.0/tests/propagators/__init__.py +0 -0
- nucs-0.3.0/tests/propagators/test_affine_eq.py +45 -0
- nucs-0.3.0/tests/propagators/test_affine_geq.py +28 -0
- nucs-0.3.0/tests/propagators/test_affine_leq.py +45 -0
- nucs-0.3.0/tests/propagators/test_alldifferent.py +41 -0
- nucs-0.3.0/tests/propagators/test_count_eq.py +47 -0
- nucs-0.3.0/tests/propagators/test_element_lic.py +29 -0
- nucs-0.3.0/tests/propagators/test_element_liv.py +29 -0
- nucs-0.3.0/tests/propagators/test_exactly_eq.py +35 -0
- nucs-0.3.0/tests/propagators/test_lexicographic_leq.py +79 -0
- nucs-0.3.0/tests/propagators/test_max_eq.py +33 -0
- nucs-0.3.0/tests/propagators/test_max_leq.py +40 -0
- nucs-0.3.0/tests/propagators/test_min_eq.py +33 -0
- nucs-0.3.0/tests/propagators/test_min_geq.py +40 -0
- nucs-0.3.0/tests/propagators/test_relation.py +37 -0
- nucs-0.3.0/tests/solvers/__init__.py +0 -0
- nucs-0.3.0/tests/solvers/test_backtrack_solver.py +41 -0
- nucs-0.2.0/NUCS.egg-info/SOURCES.txt +0 -38
- nucs-0.2.0/NUCS.egg-info/top_level.txt +0 -1
- {nucs-0.2.0 → nucs-0.3.0}/LICENSE.md +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/NUCS.egg-info/dependency_links.txt +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/NUCS.egg-info/requires.txt +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/README.md +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/__init__.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/heuristics/heuristic.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/heuristics/variable_heuristic.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/memory.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/__init__.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/alpha_problem.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/bibd_problem.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/problem.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/queens_problem.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/schur_lemma_problem.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/sudoku_problem.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/__init__.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/affine_eq_propagator.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/affine_geq_propagator.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/affine_leq_propagator.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/alldifferent_propagator.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/count_eq_propagator.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/dummy_propagator.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/exactly_eq_propagator.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/lexicographic_leq_propagator.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/max_eq_propagator.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/max_leq_propagator.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/min_eq_propagator.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/min_geq_propagator.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/propagators.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/solvers/__init__.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/solvers/backtrack_solver.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/solvers/solver.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/nucs/statistics.py +0 -0
- {nucs-0.2.0 → nucs-0.3.0}/setup.cfg +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: NUCS
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A Numpy and Numba based Python library for solving Constraint
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A Numpy and Numba based Python library for solving Constraint Satisfaction Problems
|
|
5
5
|
Author-email: Yan Georget <yan.georget@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/yangeorget/nucs
|
|
7
7
|
Project-URL: Issues, https://github.com/yangeorget/nucs
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
LICENSE.md
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
NUCS.egg-info/PKG-INFO
|
|
5
|
+
NUCS.egg-info/SOURCES.txt
|
|
6
|
+
NUCS.egg-info/dependency_links.txt
|
|
7
|
+
NUCS.egg-info/requires.txt
|
|
8
|
+
NUCS.egg-info/top_level.txt
|
|
9
|
+
nucs/__init__.py
|
|
10
|
+
nucs/memory.py
|
|
11
|
+
nucs/statistics.py
|
|
12
|
+
nucs/heuristics/heuristic.py
|
|
13
|
+
nucs/heuristics/variable_heuristic.py
|
|
14
|
+
nucs/problems/__init__.py
|
|
15
|
+
nucs/problems/alpha_problem.py
|
|
16
|
+
nucs/problems/bibd_problem.py
|
|
17
|
+
nucs/problems/donald_problem.py
|
|
18
|
+
nucs/problems/golomb_problem.py
|
|
19
|
+
nucs/problems/knapsack_problem.py
|
|
20
|
+
nucs/problems/latin_square_problem.py
|
|
21
|
+
nucs/problems/problem.py
|
|
22
|
+
nucs/problems/quasigroup_problem.py
|
|
23
|
+
nucs/problems/queens_problem.py
|
|
24
|
+
nucs/problems/schur_lemma_problem.py
|
|
25
|
+
nucs/problems/sudoku_problem.py
|
|
26
|
+
nucs/propagators/__init__.py
|
|
27
|
+
nucs/propagators/affine_eq_propagator.py
|
|
28
|
+
nucs/propagators/affine_geq_propagator.py
|
|
29
|
+
nucs/propagators/affine_leq_propagator.py
|
|
30
|
+
nucs/propagators/alldifferent_propagator.py
|
|
31
|
+
nucs/propagators/count_eq_propagator.py
|
|
32
|
+
nucs/propagators/dummy_propagator.py
|
|
33
|
+
nucs/propagators/element_lic_propagator.py
|
|
34
|
+
nucs/propagators/element_liv_propagator.py
|
|
35
|
+
nucs/propagators/exactly_eq_propagator.py
|
|
36
|
+
nucs/propagators/lexicographic_leq_propagator.py
|
|
37
|
+
nucs/propagators/max_eq_propagator.py
|
|
38
|
+
nucs/propagators/max_leq_propagator.py
|
|
39
|
+
nucs/propagators/min_eq_propagator.py
|
|
40
|
+
nucs/propagators/min_geq_propagator.py
|
|
41
|
+
nucs/propagators/propagators.py
|
|
42
|
+
nucs/propagators/relation_propagator.py
|
|
43
|
+
nucs/solvers/__init__.py
|
|
44
|
+
nucs/solvers/backtrack_solver.py
|
|
45
|
+
nucs/solvers/solver.py
|
|
46
|
+
tests/__init__.py
|
|
47
|
+
tests/examples/test_alpha.py
|
|
48
|
+
tests/examples/test_bibd.py
|
|
49
|
+
tests/examples/test_donald.py
|
|
50
|
+
tests/examples/test_golomb.py
|
|
51
|
+
tests/examples/test_knapsack.py
|
|
52
|
+
tests/examples/test_latin_square.py
|
|
53
|
+
tests/examples/test_quasigroup.py
|
|
54
|
+
tests/examples/test_queens.py
|
|
55
|
+
tests/examples/test_schur_lemma.py
|
|
56
|
+
tests/examples/test_sudokus.py
|
|
57
|
+
tests/problems/__init__.py
|
|
58
|
+
tests/problems/test_problem.py
|
|
59
|
+
tests/propagators/__init__.py
|
|
60
|
+
tests/propagators/test_affine_eq.py
|
|
61
|
+
tests/propagators/test_affine_geq.py
|
|
62
|
+
tests/propagators/test_affine_leq.py
|
|
63
|
+
tests/propagators/test_alldifferent.py
|
|
64
|
+
tests/propagators/test_count_eq.py
|
|
65
|
+
tests/propagators/test_element_lic.py
|
|
66
|
+
tests/propagators/test_element_liv.py
|
|
67
|
+
tests/propagators/test_exactly_eq.py
|
|
68
|
+
tests/propagators/test_lexicographic_leq.py
|
|
69
|
+
tests/propagators/test_max_eq.py
|
|
70
|
+
tests/propagators/test_max_leq.py
|
|
71
|
+
tests/propagators/test_min_eq.py
|
|
72
|
+
tests/propagators/test_min_geq.py
|
|
73
|
+
tests/propagators/test_relation.py
|
|
74
|
+
tests/solvers/__init__.py
|
|
75
|
+
tests/solvers/test_backtrack_solver.py
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: NUCS
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A Numpy and Numba based Python library for solving Constraint
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A Numpy and Numba based Python library for solving Constraint Satisfaction Problems
|
|
5
5
|
Author-email: Yan Georget <yan.georget@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/yangeorget/nucs
|
|
7
7
|
Project-URL: Issues, https://github.com/yangeorget/nucs
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from nucs.problems.problem import Problem
|
|
4
|
+
from nucs.propagators.propagators import ALG_AFFINE_EQ, ALG_ALLDIFFERENT
|
|
5
|
+
|
|
6
|
+
A, B, D, E, G, L, N, O, R, T = tuple(range(10))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DonaldProblem(Problem):
|
|
10
|
+
"""
|
|
11
|
+
This is the famous crypto-arithmetic puzzle DONALD+GERALD=ROBERT.
|
|
12
|
+
The letters represent digits that have to be all different.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
super().__init__([(0, 9)] * 10)
|
|
17
|
+
self.add_propagator(
|
|
18
|
+
(
|
|
19
|
+
[A, B, D, E, G, L, N, O, R, T],
|
|
20
|
+
ALG_AFFINE_EQ,
|
|
21
|
+
[200, -1000, 100002, 9900, 100000, 20, 1000, 0, -99010, -1, 0],
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
self.add_propagator(
|
|
25
|
+
([A, B, D, E, G, L, N, O, R, T], ALG_ALLDIFFERENT, []),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def pretty_print_solution(self, solution: List[int]) -> None:
|
|
29
|
+
print(
|
|
30
|
+
{
|
|
31
|
+
"A": solution[A],
|
|
32
|
+
"B": solution[B],
|
|
33
|
+
"D": solution[D],
|
|
34
|
+
"E": solution[E],
|
|
35
|
+
"G": solution[G],
|
|
36
|
+
"L": solution[L],
|
|
37
|
+
"N": solution[N],
|
|
38
|
+
"O": solution[O],
|
|
39
|
+
"R": solution[R],
|
|
40
|
+
"T": solution[T],
|
|
41
|
+
}
|
|
42
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from nucs.problems.problem import Problem
|
|
4
|
+
from nucs.propagators.propagators import ALG_AFFINE_EQ, ALG_AFFINE_LEQ
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class KnapsackProblem(Problem):
|
|
8
|
+
"""
|
|
9
|
+
This is problem #133 on https://www.csplib.org/Problems/prob133/.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, weights: List[int], volumes: List[int], capacity: int) -> None:
|
|
13
|
+
n = len(weights)
|
|
14
|
+
super().__init__([(0, 1)] * n + [(0, sum(weights))])
|
|
15
|
+
self.add_propagator((list(range(n)), ALG_AFFINE_LEQ, [*volumes, capacity]))
|
|
16
|
+
self.add_propagator((list(range(n + 1)), ALG_AFFINE_EQ, [*weights, -1, 0]))
|
|
17
|
+
self.weight = n
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
from nucs.problems.problem import Problem
|
|
4
|
+
from nucs.propagators.propagators import ALG_ALLDIFFERENT, ALG_ELEMENT_LIC
|
|
5
|
+
|
|
6
|
+
M_COLOR = 0
|
|
7
|
+
M_ROW = 1
|
|
8
|
+
M_COLUMN = 2
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LatinSquareProblem(Problem):
|
|
12
|
+
"""
|
|
13
|
+
A simple model for latin squares.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, colors: List[int], givens: Optional[List[List[int]]] = None):
|
|
17
|
+
"""
|
|
18
|
+
Inits the latin square.
|
|
19
|
+
:colors: the possible values for the cells,
|
|
20
|
+
usually [0, ..., n-1] except in some cases (eg Sudokus) where [1, ..., n] is preferred;
|
|
21
|
+
the number of colors is also the size of the square
|
|
22
|
+
:givens: initial values for the cells, any value different from the possible colors is used as a wildcard
|
|
23
|
+
"""
|
|
24
|
+
self.colors = colors
|
|
25
|
+
self.n = len(colors)
|
|
26
|
+
if givens is None:
|
|
27
|
+
shr_domains = [(colors[0], colors[-1])] * self.n**2
|
|
28
|
+
else:
|
|
29
|
+
shr_domains = [
|
|
30
|
+
(colors[0], colors[-1]) if given not in self.colors else (given, given)
|
|
31
|
+
for line in givens
|
|
32
|
+
for given in line
|
|
33
|
+
]
|
|
34
|
+
super().__init__(shr_domains)
|
|
35
|
+
self.add_propagators([(self.row(i), ALG_ALLDIFFERENT, []) for i in range(self.n)])
|
|
36
|
+
self.add_propagators([(self.column(j), ALG_ALLDIFFERENT, []) for j in range(self.n)])
|
|
37
|
+
|
|
38
|
+
def cell(self, i: int, j: int, model: int = M_COLOR) -> int:
|
|
39
|
+
offset = model * (self.n**2)
|
|
40
|
+
return offset + i * self.n + j
|
|
41
|
+
|
|
42
|
+
def row(self, i: int, model: int = M_COLOR) -> List[int]:
|
|
43
|
+
offset = model * (self.n**2)
|
|
44
|
+
return list(range(offset + i * self.n, offset + self.n + i * self.n))
|
|
45
|
+
|
|
46
|
+
def column(self, j: int, model: int = M_COLOR) -> List[int]:
|
|
47
|
+
offset = model * (self.n**2)
|
|
48
|
+
return list(range(offset + j, offset + self.n**2 + j, self.n))
|
|
49
|
+
|
|
50
|
+
def pretty_print_solution(self, solution: List[int]) -> None:
|
|
51
|
+
for i in range(0, self.n**2, self.n):
|
|
52
|
+
print(solution[i : i + self.n])
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class LatinSquareRCProblem(LatinSquareProblem):
|
|
56
|
+
"""
|
|
57
|
+
A full model for latin squares with 3 kinds of variables:
|
|
58
|
+
- n*n variables for the values (aka colors), these variables are indexed by rows then columns
|
|
59
|
+
- n*n variables for the rows, these variables are indexed by colors then columns
|
|
60
|
+
- n*n variables for the columns, these variables are indexed by rows then colors
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, n: int):
|
|
64
|
+
super().__init__(list(range(n))) # the color model
|
|
65
|
+
self.add_variables([(0, n - 1)] * n**2) # the row model
|
|
66
|
+
self.add_variables([(0, n - 1)] * n**2) # the column model
|
|
67
|
+
self.add_propagators([(self.row(i, M_ROW), ALG_ALLDIFFERENT, []) for i in range(self.n)])
|
|
68
|
+
self.add_propagators([(self.column(j, M_ROW), ALG_ALLDIFFERENT, []) for j in range(self.n)])
|
|
69
|
+
self.add_propagators([(self.row(i, M_COLUMN), ALG_ALLDIFFERENT, []) for i in range(self.n)])
|
|
70
|
+
self.add_propagators([(self.column(j, M_COLUMN), ALG_ALLDIFFERENT, []) for j in range(self.n)])
|
|
71
|
+
# row[c,j]=i <=> column[i,c]=j
|
|
72
|
+
for c in range(n):
|
|
73
|
+
for i in range(n):
|
|
74
|
+
self.add_propagator(([*self.row(c, M_ROW), self.cell(i, c, M_COLUMN)], ALG_ELEMENT_LIC, [i]), 0)
|
|
75
|
+
for j in range(n):
|
|
76
|
+
self.add_propagator(([*self.column(c, M_COLUMN), self.cell(c, j, M_ROW)], ALG_ELEMENT_LIC, [j]), 0)
|
|
77
|
+
# row[c,j]=i <=> color[i,j]=c
|
|
78
|
+
for j in range(n):
|
|
79
|
+
for i in range(n):
|
|
80
|
+
self.add_propagator(([*self.column(j, M_ROW), self.cell(i, j, M_COLOR)], ALG_ELEMENT_LIC, [i]), 0)
|
|
81
|
+
for c in range(n):
|
|
82
|
+
self.add_propagator(([*self.column(j, M_COLOR), self.cell(c, j, M_ROW)], ALG_ELEMENT_LIC, [c]), 0)
|
|
83
|
+
# color[i,j]=c <=> column[i,c]=j
|
|
84
|
+
for i in range(n):
|
|
85
|
+
for c in range(n):
|
|
86
|
+
self.add_propagator(([*self.row(i, M_COLOR), self.cell(i, c, M_COLUMN)], ALG_ELEMENT_LIC, [c]), 0)
|
|
87
|
+
for j in range(n):
|
|
88
|
+
self.add_propagator(([*self.row(i, M_COLUMN), self.cell(i, j, M_COLOR)], ALG_ELEMENT_LIC, [j]), 0)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from nucs.problems.latin_square_problem import M_COLOR, M_COLUMN, M_ROW, LatinSquareRCProblem
|
|
2
|
+
from nucs.propagators.propagators import ALG_ELEMENT_LIV
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class QuasigroupProblem(LatinSquareRCProblem):
|
|
6
|
+
"""
|
|
7
|
+
This is problem #3 on CSPLIB (https://www.csplib.org/Problems/prob003/).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, n: int):
|
|
11
|
+
super().__init__(n)
|
|
12
|
+
# idempotence
|
|
13
|
+
for model in [M_COLOR, M_ROW, M_COLUMN]:
|
|
14
|
+
for i in range(n):
|
|
15
|
+
self.shr_domains_list[self.cell(i, i, model)] = i
|
|
16
|
+
# symmetry breaking
|
|
17
|
+
for i in range(1, n):
|
|
18
|
+
self.shr_domains_list[self.cell(i, n - 1)] = (i - 1, n - 1)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Quasigroup5Problem(QuasigroupProblem):
|
|
22
|
+
"""
|
|
23
|
+
Defined by:
|
|
24
|
+
((b∗a)∗b)∗b=a
|
|
25
|
+
Can be enforced by:
|
|
26
|
+
color[color[j, i] ,j] = row[i, j]
|
|
27
|
+
which avoids the creation of additional variables
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, n: int):
|
|
31
|
+
super().__init__(n)
|
|
32
|
+
for j in range(n):
|
|
33
|
+
for i in range(n):
|
|
34
|
+
if i != j:
|
|
35
|
+
self.add_propagator(
|
|
36
|
+
(
|
|
37
|
+
[*self.column(j, M_COLOR), self.cell(j, i, M_COLOR), self.cell(i, j, M_ROW)],
|
|
38
|
+
ALG_ELEMENT_LIV,
|
|
39
|
+
[],
|
|
40
|
+
),
|
|
41
|
+
0,
|
|
42
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from numba import njit # type: ignore
|
|
2
|
+
from numpy.typing import NDArray
|
|
3
|
+
|
|
4
|
+
from nucs.memory import MAX, MIN, PROP_CONSISTENCY, PROP_ENTAILMENT, PROP_INCONSISTENCY, new_triggers
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_triggers_element_lic(n: int, data: NDArray) -> NDArray:
|
|
8
|
+
"""
|
|
9
|
+
This propagator is triggered whenever there is a change in the domain of a variable.
|
|
10
|
+
:param n: the number of variables
|
|
11
|
+
:return: an array of triggers
|
|
12
|
+
"""
|
|
13
|
+
return new_triggers(n, True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@njit("int64(int32[::1,:], int32[:])", cache=True)
|
|
17
|
+
def compute_domains_element_lic(domains: NDArray, data: NDArray) -> int:
|
|
18
|
+
"""
|
|
19
|
+
Enforces l_i = c.
|
|
20
|
+
:param domains: the domains of the variables
|
|
21
|
+
:param data: the parameters of the propagator
|
|
22
|
+
"""
|
|
23
|
+
l = domains[:-1]
|
|
24
|
+
i = domains[-1]
|
|
25
|
+
# update i
|
|
26
|
+
i[MIN] = max(i[MIN], 0)
|
|
27
|
+
i[MAX] = min(i[MAX], len(l) - 1)
|
|
28
|
+
if i[MAX] < i[MIN]:
|
|
29
|
+
return PROP_INCONSISTENCY
|
|
30
|
+
c = data[0]
|
|
31
|
+
i_min = i[MIN]
|
|
32
|
+
i_max = i[MAX]
|
|
33
|
+
for idx in range(i[MIN], i[MAX] + 1):
|
|
34
|
+
if c < l[idx, MIN] or c > l[idx, MAX]: # no intersection
|
|
35
|
+
if idx == i_min:
|
|
36
|
+
i_min += 1
|
|
37
|
+
elif idx == i_max:
|
|
38
|
+
i_max -= 1
|
|
39
|
+
if i_max < i_min:
|
|
40
|
+
return PROP_INCONSISTENCY
|
|
41
|
+
i[MIN] = i_min
|
|
42
|
+
i[MAX] = i_max
|
|
43
|
+
# when strict, update l
|
|
44
|
+
# for idx in range(0, i[MIN]):
|
|
45
|
+
# if l[idx, MIN] == c:
|
|
46
|
+
# l[idx, MIN] = c + 1
|
|
47
|
+
# elif l[idx, MAX] == c:
|
|
48
|
+
# l[idx, MAX] = c - 1
|
|
49
|
+
# if l[idx, MIN] > l[idx, MAX]:
|
|
50
|
+
# return PROP_INCONSISTENCY
|
|
51
|
+
# for idx in range(i[MAX] + 1, len(l)):
|
|
52
|
+
# if l[idx, MIN] == c:
|
|
53
|
+
# l[idx, MIN] = c + 1
|
|
54
|
+
# elif l[idx, MAX] == c:
|
|
55
|
+
# l[idx, MAX] = c - 1
|
|
56
|
+
# if l[idx, MIN] > l[idx, MAX]:
|
|
57
|
+
# return PROP_INCONSISTENCY
|
|
58
|
+
if i_min == i_max:
|
|
59
|
+
l[i_min, MIN] = l[i_max, MAX] = c
|
|
60
|
+
return PROP_ENTAILMENT
|
|
61
|
+
return PROP_CONSISTENCY
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from numba import njit # type: ignore
|
|
4
|
+
from numpy.typing import NDArray
|
|
5
|
+
|
|
6
|
+
from nucs.memory import MAX, MIN, PROP_CONSISTENCY, PROP_ENTAILMENT, PROP_INCONSISTENCY, new_triggers
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_triggers_element_liv(n: int, data: NDArray) -> NDArray:
|
|
10
|
+
"""
|
|
11
|
+
This propagator is triggered whenever there is a change in the domain of a variable.
|
|
12
|
+
:param n: the number of variables
|
|
13
|
+
:return: an array of triggers
|
|
14
|
+
"""
|
|
15
|
+
return new_triggers(n, True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@njit("int64(int32[::1,:], int32[:])", cache=True)
|
|
19
|
+
def compute_domains_element_liv(domains: NDArray, data: NDArray) -> int:
|
|
20
|
+
"""
|
|
21
|
+
Enforces l_i = x.
|
|
22
|
+
:param domains: the domains of the variables
|
|
23
|
+
:param data: the parameters of the propagator
|
|
24
|
+
"""
|
|
25
|
+
l = domains[:-2]
|
|
26
|
+
i = domains[-2]
|
|
27
|
+
i[MIN] = max(i[MIN], 0)
|
|
28
|
+
i[MAX] = min(i[MAX], len(l) - 1)
|
|
29
|
+
if i[MAX] < i[MIN]:
|
|
30
|
+
return PROP_INCONSISTENCY
|
|
31
|
+
x = domains[-1]
|
|
32
|
+
x_min = sys.maxsize
|
|
33
|
+
x_max = -sys.maxsize
|
|
34
|
+
i_min = i[MIN]
|
|
35
|
+
i_max = i[MAX]
|
|
36
|
+
for idx in range(i[MIN], i[MAX] + 1):
|
|
37
|
+
if x[MAX] < l[idx, MIN] or x[MIN] > l[idx, MAX]: # no intersection
|
|
38
|
+
if idx == i_min:
|
|
39
|
+
i_min += 1
|
|
40
|
+
elif idx == i_max:
|
|
41
|
+
i_max -= 1
|
|
42
|
+
else: # intersection
|
|
43
|
+
if l[idx, MIN] < x_min:
|
|
44
|
+
x_min = l[idx, MIN]
|
|
45
|
+
if l[idx, MAX] > x_max:
|
|
46
|
+
x_max = l[idx, MAX]
|
|
47
|
+
if i_max < i_min:
|
|
48
|
+
return PROP_INCONSISTENCY
|
|
49
|
+
i[MIN] = i_min
|
|
50
|
+
i[MAX] = i_max
|
|
51
|
+
x[MIN] = max(x[MIN], x_min)
|
|
52
|
+
x[MAX] = min(x[MAX], x_max)
|
|
53
|
+
if i_min == i_max:
|
|
54
|
+
l[i_min, MIN] = max(l[i_min, MIN], x[MIN])
|
|
55
|
+
l[i_max, MAX] = min(l[i_max, MAX], x[MAX])
|
|
56
|
+
if x[MIN] == x[MAX]:
|
|
57
|
+
return PROP_ENTAILMENT
|
|
58
|
+
return PROP_CONSISTENCY
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from numba import njit # type: ignore
|
|
2
|
+
from numpy.typing import NDArray
|
|
3
|
+
|
|
4
|
+
from nucs.memory import MAX, MIN, PROP_CONSISTENCY, PROP_ENTAILMENT, PROP_INCONSISTENCY, new_triggers
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_triggers_relation(n: int, data: NDArray) -> NDArray:
|
|
8
|
+
"""
|
|
9
|
+
This propagator is triggered whenever there is a change in the domain of a variable.
|
|
10
|
+
:param n: the number of variables
|
|
11
|
+
:return: an array of triggers
|
|
12
|
+
"""
|
|
13
|
+
return new_triggers(n, True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@njit("int64(int32[::1,:], int32[:])", cache=True)
|
|
17
|
+
def compute_domains_relation(domains: NDArray, data: NDArray) -> int:
|
|
18
|
+
"""
|
|
19
|
+
:param domains: the domains of the variables
|
|
20
|
+
:param data: the parameters of the propagator
|
|
21
|
+
"""
|
|
22
|
+
n = len(domains)
|
|
23
|
+
tuples = data.copy().reshape((-1, n))
|
|
24
|
+
for domain_idx in range(n):
|
|
25
|
+
tuples = tuples[
|
|
26
|
+
(tuples[:, domain_idx] >= domains[domain_idx, MIN]) & (tuples[:, domain_idx] <= domains[domain_idx, MAX])
|
|
27
|
+
]
|
|
28
|
+
if len(tuples) == 0:
|
|
29
|
+
return PROP_INCONSISTENCY
|
|
30
|
+
for domain_idx in range(n): # no support for .min(axis=0) in Numba
|
|
31
|
+
domains[domain_idx, MIN] = tuples[:, domain_idx].min()
|
|
32
|
+
domains[domain_idx, MAX] = tuples[:, domain_idx].max()
|
|
33
|
+
if len(tuples) == 1:
|
|
34
|
+
return PROP_ENTAILMENT
|
|
35
|
+
return PROP_CONSISTENCY
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name="NUCS"
|
|
3
|
-
version="0.
|
|
3
|
+
version="0.3.0"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="Yan Georget", email="yan.georget@gmail.com" },
|
|
6
6
|
]
|
|
7
|
-
description="A Numpy and Numba based Python library for solving Constraint
|
|
7
|
+
description="A Numpy and Numba based Python library for solving Constraint Satisfaction Problems"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
requires-python = ">=3.9"
|
|
10
10
|
classifiers = [
|
|
@@ -30,5 +30,5 @@ line_length = 120
|
|
|
30
30
|
[tool.black]
|
|
31
31
|
line-length = 120
|
|
32
32
|
|
|
33
|
-
[tool.setuptools]
|
|
34
|
-
|
|
33
|
+
[tool.setuptools.packages]
|
|
34
|
+
find = {}
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from pprint import pprint
|
|
2
|
+
|
|
3
|
+
from nucs.heuristics.variable_heuristic import VariableHeuristic, min_value_dom_heuristic, smallest_domain_var_heuristic
|
|
4
|
+
from nucs.problems.alpha_problem import AlphaProblem
|
|
5
|
+
from nucs.solvers.backtrack_solver import BacktrackSolver
|
|
6
|
+
from nucs.statistics import STATS_SOLVER_SOLUTION_NB, get_statistics
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestAlpha:
|
|
10
|
+
def test_alpha(self) -> None:
|
|
11
|
+
problem = AlphaProblem()
|
|
12
|
+
solver = BacktrackSolver(problem, VariableHeuristic(smallest_domain_var_heuristic, min_value_dom_heuristic))
|
|
13
|
+
solutions = solver.solve_all()
|
|
14
|
+
assert problem.statistics[STATS_SOLVER_SOLUTION_NB] == 1
|
|
15
|
+
assert solutions[0][:26] == [
|
|
16
|
+
5,
|
|
17
|
+
13,
|
|
18
|
+
9,
|
|
19
|
+
16,
|
|
20
|
+
20,
|
|
21
|
+
4,
|
|
22
|
+
24,
|
|
23
|
+
21,
|
|
24
|
+
25,
|
|
25
|
+
17,
|
|
26
|
+
23,
|
|
27
|
+
2,
|
|
28
|
+
8,
|
|
29
|
+
12,
|
|
30
|
+
10,
|
|
31
|
+
19,
|
|
32
|
+
7,
|
|
33
|
+
11,
|
|
34
|
+
15,
|
|
35
|
+
3,
|
|
36
|
+
1,
|
|
37
|
+
26,
|
|
38
|
+
6,
|
|
39
|
+
22,
|
|
40
|
+
14,
|
|
41
|
+
18,
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
problem = AlphaProblem()
|
|
47
|
+
solver = BacktrackSolver(problem, VariableHeuristic(smallest_domain_var_heuristic, min_value_dom_heuristic))
|
|
48
|
+
solutions = solver.solve_all()
|
|
49
|
+
pprint(get_statistics(problem.statistics))
|
|
50
|
+
print(solutions[0])
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pprint import pprint
|
|
2
|
+
|
|
3
|
+
from nucs.problems.bibd_problem import BIBDProblem
|
|
4
|
+
from nucs.solvers.backtrack_solver import BacktrackSolver
|
|
5
|
+
from nucs.statistics import STATS_SOLVER_SOLUTION_NB, get_statistics
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestBIBD:
|
|
9
|
+
def test_6_10_5_3_2(self) -> None:
|
|
10
|
+
problem = BIBDProblem(6, 10, 5, 3, 2)
|
|
11
|
+
solver = BacktrackSolver(problem)
|
|
12
|
+
solver.find_all()
|
|
13
|
+
assert problem.statistics[STATS_SOLVER_SOLUTION_NB] == 1
|
|
14
|
+
|
|
15
|
+
def test_8_14_7_4_3(self) -> None:
|
|
16
|
+
problem = BIBDProblem(8, 14, 7, 4, 3)
|
|
17
|
+
solver = BacktrackSolver(problem)
|
|
18
|
+
solver.find_all()
|
|
19
|
+
assert problem.statistics[STATS_SOLVER_SOLUTION_NB] == 92
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if __name__ == "__main__":
|
|
23
|
+
problem = BIBDProblem(8, 14, 7, 4, 3)
|
|
24
|
+
solver = BacktrackSolver(problem)
|
|
25
|
+
solver.find_all()
|
|
26
|
+
pprint(get_statistics(problem.statistics))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from pprint import pprint
|
|
2
|
+
|
|
3
|
+
from nucs.heuristics.variable_heuristic import VariableHeuristic, min_value_dom_heuristic, smallest_domain_var_heuristic
|
|
4
|
+
from nucs.problems.donald_problem import DonaldProblem
|
|
5
|
+
from nucs.solvers.backtrack_solver import BacktrackSolver
|
|
6
|
+
from nucs.statistics import STATS_SOLVER_SOLUTION_NB, get_statistics
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestDonald:
|
|
10
|
+
def test_donald(self) -> None:
|
|
11
|
+
problem = DonaldProblem()
|
|
12
|
+
solver = BacktrackSolver(problem, VariableHeuristic(smallest_domain_var_heuristic, min_value_dom_heuristic))
|
|
13
|
+
solutions = solver.solve_all()
|
|
14
|
+
assert problem.statistics[STATS_SOLVER_SOLUTION_NB] == 1
|
|
15
|
+
assert solutions[0] == [4, 3, 5, 9, 1, 8, 6, 2, 7, 0]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
problem = DonaldProblem()
|
|
20
|
+
solver = BacktrackSolver(problem, VariableHeuristic(smallest_domain_var_heuristic, min_value_dom_heuristic))
|
|
21
|
+
for solution in solver.solve():
|
|
22
|
+
problem.pretty_print_solution(solution)
|
|
23
|
+
pprint(get_statistics(problem.statistics))
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from pprint import pprint
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from nucs.memory import MIN
|
|
8
|
+
from nucs.problems.golomb_problem import GolombProblem, index, init_domains
|
|
9
|
+
from nucs.solvers.backtrack_solver import BacktrackSolver
|
|
10
|
+
from nucs.statistics import get_statistics
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestGolomb:
|
|
14
|
+
|
|
15
|
+
@pytest.mark.parametrize(
|
|
16
|
+
"mark_nb,i,j,idx", [(4, 0, 1, 0), (4, 0, 2, 1), (4, 0, 3, 2), (4, 1, 2, 3), (4, 1, 3, 4), (4, 2, 3, 5)]
|
|
17
|
+
)
|
|
18
|
+
def test_index(self, mark_nb: int, i: int, j: int, idx: int) -> None:
|
|
19
|
+
assert index(mark_nb, i, j) == idx
|
|
20
|
+
|
|
21
|
+
def test_init_domains(self) -> None:
|
|
22
|
+
domains = init_domains(6, 4)
|
|
23
|
+
assert domains[:, MIN].tolist() == [1, 3, 6, 1, 3, 1]
|
|
24
|
+
|
|
25
|
+
def test_golomb_4_filter(self) -> None:
|
|
26
|
+
problem = GolombProblem(4)
|
|
27
|
+
problem.shr_domains_list[0] = 1
|
|
28
|
+
problem.shr_domains_list[1] = 4
|
|
29
|
+
problem.shr_domains_list[2] = 6
|
|
30
|
+
problem.shr_domains_list[3] = 3
|
|
31
|
+
problem.shr_domains_list[4] = 5
|
|
32
|
+
problem.shr_domains_list[5] = 2
|
|
33
|
+
assert problem.filter(np.ones((6, 2), dtype=bool))
|
|
34
|
+
|
|
35
|
+
@pytest.mark.parametrize("mark_nb,solution_nb", [(4, 6), (5, 11), (6, 17), (7, 25), (8, 34), (9, 44)])
|
|
36
|
+
def test_golomb(self, mark_nb: int, solution_nb: int) -> None:
|
|
37
|
+
problem = GolombProblem(mark_nb)
|
|
38
|
+
solver = BacktrackSolver(problem)
|
|
39
|
+
solution = solver.minimize(problem.length_idx)
|
|
40
|
+
assert solution
|
|
41
|
+
assert solution[problem.length_idx] == solution_nb
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
parser = argparse.ArgumentParser()
|
|
46
|
+
parser.add_argument("-n", type=int, default=10)
|
|
47
|
+
args = parser.parse_args()
|
|
48
|
+
problem = GolombProblem(args.n)
|
|
49
|
+
solver = BacktrackSolver(problem)
|
|
50
|
+
solution = solver.minimize(problem.length_idx)
|
|
51
|
+
pprint(get_statistics(problem.statistics))
|
|
52
|
+
print(solution)
|
|
53
|
+
print(solution[problem.length_idx]) # type: ignore
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from pprint import pprint
|
|
2
|
+
|
|
3
|
+
from nucs.heuristics.variable_heuristic import (
|
|
4
|
+
VariableHeuristic,
|
|
5
|
+
first_not_instantiated_var_heuristic,
|
|
6
|
+
max_value_dom_heuristic,
|
|
7
|
+
)
|
|
8
|
+
from nucs.problems.knapsack_problem import KnapsackProblem
|
|
9
|
+
from nucs.solvers.backtrack_solver import BacktrackSolver
|
|
10
|
+
from nucs.statistics import get_statistics
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestKnapsack:
|
|
14
|
+
def test_knapsack(self) -> None:
|
|
15
|
+
problem = KnapsackProblem(
|
|
16
|
+
[40, 40, 38, 38, 36, 36, 34, 34, 32, 32, 30, 30, 28, 28, 26, 26, 24, 24, 22, 22],
|
|
17
|
+
[40, 40, 38, 38, 36, 36, 34, 34, 32, 32, 30, 30, 28, 28, 26, 26, 24, 24, 22, 22],
|
|
18
|
+
55,
|
|
19
|
+
)
|
|
20
|
+
solver = BacktrackSolver(
|
|
21
|
+
problem, VariableHeuristic(first_not_instantiated_var_heuristic, max_value_dom_heuristic)
|
|
22
|
+
)
|
|
23
|
+
solution = solver.maximize(problem.weight)
|
|
24
|
+
assert solution
|
|
25
|
+
assert solution[problem.weight] == 54
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
problem = KnapsackProblem(
|
|
30
|
+
[40, 40, 38, 38, 36, 36, 34, 34, 32, 32, 30, 30, 28, 28, 26, 26, 24, 24, 22, 22],
|
|
31
|
+
[40, 40, 38, 38, 36, 36, 34, 34, 32, 32, 30, 30, 28, 28, 26, 26, 24, 24, 22, 22],
|
|
32
|
+
55,
|
|
33
|
+
)
|
|
34
|
+
solver = BacktrackSolver(problem, VariableHeuristic(first_not_instantiated_var_heuristic, max_value_dom_heuristic))
|
|
35
|
+
solution = solver.maximize(problem.weight)
|
|
36
|
+
pprint(get_statistics(problem.statistics))
|
|
37
|
+
print(solution)
|