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.
Files changed (79) hide show
  1. {nucs-0.2.0 → nucs-0.3.0/NUCS.egg-info}/PKG-INFO +2 -2
  2. nucs-0.3.0/NUCS.egg-info/SOURCES.txt +75 -0
  3. nucs-0.3.0/NUCS.egg-info/top_level.txt +5 -0
  4. {nucs-0.2.0/NUCS.egg-info → nucs-0.3.0}/PKG-INFO +2 -2
  5. nucs-0.3.0/nucs/problems/donald_problem.py +42 -0
  6. {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/golomb_problem.py +1 -1
  7. nucs-0.3.0/nucs/problems/knapsack_problem.py +17 -0
  8. nucs-0.3.0/nucs/problems/latin_square_problem.py +88 -0
  9. nucs-0.3.0/nucs/problems/quasigroup_problem.py +42 -0
  10. nucs-0.3.0/nucs/propagators/element_lic_propagator.py +61 -0
  11. nucs-0.3.0/nucs/propagators/element_liv_propagator.py +58 -0
  12. nucs-0.3.0/nucs/propagators/relation_propagator.py +35 -0
  13. {nucs-0.2.0 → nucs-0.3.0}/pyproject.toml +4 -4
  14. nucs-0.3.0/tests/__init__.py +0 -0
  15. nucs-0.3.0/tests/examples/test_alpha.py +50 -0
  16. nucs-0.3.0/tests/examples/test_bibd.py +26 -0
  17. nucs-0.3.0/tests/examples/test_donald.py +23 -0
  18. nucs-0.3.0/tests/examples/test_golomb.py +53 -0
  19. nucs-0.3.0/tests/examples/test_knapsack.py +37 -0
  20. nucs-0.3.0/tests/examples/test_latin_square.py +39 -0
  21. nucs-0.3.0/tests/examples/test_quasigroup.py +38 -0
  22. nucs-0.3.0/tests/examples/test_queens.py +41 -0
  23. nucs-0.3.0/tests/examples/test_schur_lemma.py +36 -0
  24. nucs-0.3.0/tests/examples/test_sudokus.py +41 -0
  25. nucs-0.3.0/tests/problems/__init__.py +0 -0
  26. nucs-0.3.0/tests/problems/test_problem.py +21 -0
  27. nucs-0.3.0/tests/propagators/__init__.py +0 -0
  28. nucs-0.3.0/tests/propagators/test_affine_eq.py +45 -0
  29. nucs-0.3.0/tests/propagators/test_affine_geq.py +28 -0
  30. nucs-0.3.0/tests/propagators/test_affine_leq.py +45 -0
  31. nucs-0.3.0/tests/propagators/test_alldifferent.py +41 -0
  32. nucs-0.3.0/tests/propagators/test_count_eq.py +47 -0
  33. nucs-0.3.0/tests/propagators/test_element_lic.py +29 -0
  34. nucs-0.3.0/tests/propagators/test_element_liv.py +29 -0
  35. nucs-0.3.0/tests/propagators/test_exactly_eq.py +35 -0
  36. nucs-0.3.0/tests/propagators/test_lexicographic_leq.py +79 -0
  37. nucs-0.3.0/tests/propagators/test_max_eq.py +33 -0
  38. nucs-0.3.0/tests/propagators/test_max_leq.py +40 -0
  39. nucs-0.3.0/tests/propagators/test_min_eq.py +33 -0
  40. nucs-0.3.0/tests/propagators/test_min_geq.py +40 -0
  41. nucs-0.3.0/tests/propagators/test_relation.py +37 -0
  42. nucs-0.3.0/tests/solvers/__init__.py +0 -0
  43. nucs-0.3.0/tests/solvers/test_backtrack_solver.py +41 -0
  44. nucs-0.2.0/NUCS.egg-info/SOURCES.txt +0 -38
  45. nucs-0.2.0/NUCS.egg-info/top_level.txt +0 -1
  46. {nucs-0.2.0 → nucs-0.3.0}/LICENSE.md +0 -0
  47. {nucs-0.2.0 → nucs-0.3.0}/NUCS.egg-info/dependency_links.txt +0 -0
  48. {nucs-0.2.0 → nucs-0.3.0}/NUCS.egg-info/requires.txt +0 -0
  49. {nucs-0.2.0 → nucs-0.3.0}/README.md +0 -0
  50. {nucs-0.2.0 → nucs-0.3.0}/nucs/__init__.py +0 -0
  51. {nucs-0.2.0 → nucs-0.3.0}/nucs/heuristics/heuristic.py +0 -0
  52. {nucs-0.2.0 → nucs-0.3.0}/nucs/heuristics/variable_heuristic.py +0 -0
  53. {nucs-0.2.0 → nucs-0.3.0}/nucs/memory.py +0 -0
  54. {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/__init__.py +0 -0
  55. {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/alpha_problem.py +0 -0
  56. {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/bibd_problem.py +0 -0
  57. {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/problem.py +0 -0
  58. {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/queens_problem.py +0 -0
  59. {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/schur_lemma_problem.py +0 -0
  60. {nucs-0.2.0 → nucs-0.3.0}/nucs/problems/sudoku_problem.py +0 -0
  61. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/__init__.py +0 -0
  62. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/affine_eq_propagator.py +0 -0
  63. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/affine_geq_propagator.py +0 -0
  64. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/affine_leq_propagator.py +0 -0
  65. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/alldifferent_propagator.py +0 -0
  66. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/count_eq_propagator.py +0 -0
  67. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/dummy_propagator.py +0 -0
  68. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/exactly_eq_propagator.py +0 -0
  69. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/lexicographic_leq_propagator.py +0 -0
  70. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/max_eq_propagator.py +0 -0
  71. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/max_leq_propagator.py +0 -0
  72. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/min_eq_propagator.py +0 -0
  73. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/min_geq_propagator.py +0 -0
  74. {nucs-0.2.0 → nucs-0.3.0}/nucs/propagators/propagators.py +0 -0
  75. {nucs-0.2.0 → nucs-0.3.0}/nucs/solvers/__init__.py +0 -0
  76. {nucs-0.2.0 → nucs-0.3.0}/nucs/solvers/backtrack_solver.py +0 -0
  77. {nucs-0.2.0 → nucs-0.3.0}/nucs/solvers/solver.py +0 -0
  78. {nucs-0.2.0 → nucs-0.3.0}/nucs/statistics.py +0 -0
  79. {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.2.0
4
- Summary: A Numpy and Numba based Python library for solving Constraint Satifaction Problems
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
@@ -0,0 +1,5 @@
1
+ assets
2
+ dist
3
+ nucs
4
+ scripts
5
+ tests
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: NUCS
3
- Version: 0.2.0
4
- Summary: A Numpy and Numba based Python library for solving Constraint Satifaction Problems
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
+ )
@@ -1,5 +1,5 @@
1
1
  import numpy as np
2
- from numba import njit
2
+ from numba import njit # type: ignore
3
3
  from numpy.typing import NDArray
4
4
 
5
5
  from nucs.heuristics.variable_heuristic import first_not_instantiated_var_heuristic
@@ -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.2.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 Satifaction Problems"
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
- packages = ["nucs"]
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)