NUCS 2.0.0__tar.gz → 2.1.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 (119) hide show
  1. {nucs-2.0.0 → nucs-2.1.0/NUCS.egg-info}/PKG-INFO +4 -3
  2. {nucs-2.0.0 → nucs-2.1.0}/NUCS.egg-info/SOURCES.txt +2 -0
  3. {nucs-2.0.0/NUCS.egg-info → nucs-2.1.0}/PKG-INFO +4 -3
  4. {nucs-2.0.0 → nucs-2.1.0}/README.md +3 -2
  5. {nucs-2.0.0 → nucs-2.1.0}/docs/source/conf.py +2 -2
  6. {nucs-2.0.0 → nucs-2.1.0}/nucs/constants.py +4 -2
  7. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/golomb/golomb_problem.py +16 -8
  8. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/queens/__main__.py +3 -0
  9. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/propagators.py +18 -4
  10. {nucs-2.0.0 → nucs-2.1.0}/nucs/solvers/backtrack_solver.py +52 -42
  11. {nucs-2.0.0 → nucs-2.1.0}/nucs/solvers/bound_consistency_algorithm.py +23 -14
  12. nucs-2.1.0/nucs/solvers/choice_points.py +84 -0
  13. {nucs-2.0.0 → nucs-2.1.0}/nucs/solvers/consistency_algorithms.py +2 -0
  14. {nucs-2.0.0 → nucs-2.1.0}/nucs/solvers/heuristics.py +16 -4
  15. nucs-2.1.0/nucs/solvers/shaving_consistency_algorithm.py +269 -0
  16. {nucs-2.0.0 → nucs-2.1.0}/nucs/statistics.py +13 -1
  17. {nucs-2.0.0 → nucs-2.1.0}/pyproject.toml +1 -1
  18. {nucs-2.0.0 → nucs-2.1.0}/LICENSE.md +0 -0
  19. {nucs-2.0.0 → nucs-2.1.0}/NUCS.egg-info/dependency_links.txt +0 -0
  20. {nucs-2.0.0 → nucs-2.1.0}/NUCS.egg-info/requires.txt +0 -0
  21. {nucs-2.0.0 → nucs-2.1.0}/NUCS.egg-info/top_level.txt +0 -0
  22. {nucs-2.0.0 → nucs-2.1.0}/nucs/__init__.py +0 -0
  23. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/__init__.py +0 -0
  24. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/alpha/__init__.py +0 -0
  25. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/alpha/__main__.py +0 -0
  26. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/alpha/alpha_problem.py +0 -0
  27. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/bibd/__init__.py +0 -0
  28. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/bibd/__main__.py +0 -0
  29. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/bibd/bibd_problem.py +0 -0
  30. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/donald/__init__.py +0 -0
  31. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/donald/__main__.py +0 -0
  32. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/donald/donald_problem.py +0 -0
  33. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/golomb/__init__.py +0 -0
  34. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/golomb/__main__.py +0 -0
  35. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/knapsack/__init__.py +0 -0
  36. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/knapsack/__main__.py +0 -0
  37. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/knapsack/knapsack_problem.py +0 -0
  38. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/magic_sequence/__init__.py +0 -0
  39. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/magic_sequence/__main__.py +0 -0
  40. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/magic_sequence/magic_sequence_problem.py +0 -0
  41. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/magic_square/__init__.py +0 -0
  42. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/magic_square/__main__.py +0 -0
  43. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/magic_square/magic_square_problem.py +0 -0
  44. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/quasigroup/__init__.py +0 -0
  45. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/quasigroup/__main__.py +0 -0
  46. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/quasigroup/quasigroup_problem.py +0 -0
  47. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/queens/__init__.py +0 -0
  48. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/queens/queens_problem.py +0 -0
  49. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/schur_lemma/__init__.py +0 -0
  50. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/schur_lemma/__main__.py +0 -0
  51. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/schur_lemma/schur_lemma_problem.py +0 -0
  52. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/sports_tournament_scheduling/__init__.py +0 -0
  53. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/sports_tournament_scheduling/__main__.py +0 -0
  54. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/sports_tournament_scheduling/sports_tournament_scheduling_problem.py +0 -0
  55. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/sudoku/__init__.py +0 -0
  56. {nucs-2.0.0 → nucs-2.1.0}/nucs/examples/sudoku/sudoku_problem.py +0 -0
  57. {nucs-2.0.0 → nucs-2.1.0}/nucs/numba_helper.py +0 -0
  58. {nucs-2.0.0 → nucs-2.1.0}/nucs/numpy_helper.py +0 -0
  59. {nucs-2.0.0 → nucs-2.1.0}/nucs/problems/__init__.py +0 -0
  60. {nucs-2.0.0 → nucs-2.1.0}/nucs/problems/latin_square_problem.py +0 -0
  61. {nucs-2.0.0 → nucs-2.1.0}/nucs/problems/problem.py +0 -0
  62. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/__init__.py +0 -0
  63. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/affine_eq_propagator.py +0 -0
  64. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/affine_geq_propagator.py +0 -0
  65. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/affine_leq_propagator.py +0 -0
  66. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/alldifferent_propagator.py +0 -0
  67. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/and_propagator.py +0 -0
  68. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/count_eq_propagator.py +0 -0
  69. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/dummy_propagator.py +0 -0
  70. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/element_lic_propagator.py +0 -0
  71. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/element_liv_propagator.py +0 -0
  72. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/exactly_eq_propagator.py +0 -0
  73. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/exactly_true_propagator.py +0 -0
  74. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/gcc_propagator.py +0 -0
  75. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/lexicographic_leq_propagator.py +0 -0
  76. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/max_eq_propagator.py +0 -0
  77. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/max_leq_propagator.py +0 -0
  78. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/min_eq_propagator.py +0 -0
  79. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/min_geq_propagator.py +0 -0
  80. {nucs-2.0.0 → nucs-2.1.0}/nucs/propagators/relation_propagator.py +0 -0
  81. {nucs-2.0.0 → nucs-2.1.0}/nucs/solvers/__init__.py +0 -0
  82. {nucs-2.0.0 → nucs-2.1.0}/nucs/solvers/multiprocessing_solver.py +0 -0
  83. {nucs-2.0.0 → nucs-2.1.0}/nucs/solvers/solver.py +0 -0
  84. {nucs-2.0.0 → nucs-2.1.0}/setup.cfg +0 -0
  85. {nucs-2.0.0 → nucs-2.1.0}/tests/__init__.py +0 -0
  86. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_alpha.py +0 -0
  87. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_bibd.py +0 -0
  88. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_donald.py +0 -0
  89. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_golomb.py +0 -0
  90. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_knapsack.py +0 -0
  91. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_latin_square.py +0 -0
  92. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_magic_sequence.py +0 -0
  93. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_magic_square.py +0 -0
  94. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_quasigroup.py +0 -0
  95. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_queens.py +0 -0
  96. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_schur_lemma.py +0 -0
  97. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_sports_tournament_scheduling.py +0 -0
  98. {nucs-2.0.0 → nucs-2.1.0}/tests/examples/test_sudokus.py +0 -0
  99. {nucs-2.0.0 → nucs-2.1.0}/tests/problems/__init__.py +0 -0
  100. {nucs-2.0.0 → nucs-2.1.0}/tests/problems/test_problem.py +0 -0
  101. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/__init__.py +0 -0
  102. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_affine_eq.py +0 -0
  103. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_affine_geq.py +0 -0
  104. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_affine_leq.py +0 -0
  105. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_alldifferent.py +0 -0
  106. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_count_eq.py +0 -0
  107. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_element_lic.py +0 -0
  108. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_element_liv.py +0 -0
  109. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_exactly_eq.py +0 -0
  110. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_gcc.py +0 -0
  111. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_lexicographic_leq.py +0 -0
  112. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_max_eq.py +0 -0
  113. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_max_leq.py +0 -0
  114. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_min_eq.py +0 -0
  115. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_min_geq.py +0 -0
  116. {nucs-2.0.0 → nucs-2.1.0}/tests/propagators/test_relation.py +0 -0
  117. {nucs-2.0.0 → nucs-2.1.0}/tests/solvers/__init__.py +0 -0
  118. {nucs-2.0.0 → nucs-2.1.0}/tests/solvers/test_backtrack_solver.py +0 -0
  119. {nucs-2.0.0 → nucs-2.1.0}/tests/solvers/test_multiprocessing_solver.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: NUCS
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: A Numpy and Numba based Python library for solving Constraint Satisfaction Problems over finite domains
5
5
  Author-email: Yan Georget <yan.georget@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/yangeorget/nucs
@@ -35,8 +35,9 @@ Requires-Dist: numpy==2.0.1
35
35
 
36
36
  ## TLDR
37
37
  NuCS is a Python library for solving Constraint Satisfaction and Optimization Problems.
38
- Because it is 100% written in Python, NuCS is easy to install and use.
39
- NuCS is also very fast because it is powered by [Numpy](https://numpy.org/) and [Numba](https://numba.pydata.org/).
38
+ Because it is 100% written in Python,
39
+ NuCS is easy to install and allows to model complex problems in a few lines of code.
40
+ The NuCS solver is also very fast because it is powered by [Numpy](https://numpy.org/) and [Numba](https://numba.pydata.org/).
40
41
 
41
42
  ## Installation
42
43
  ```bash
@@ -74,9 +74,11 @@ nucs/propagators/relation_propagator.py
74
74
  nucs/solvers/__init__.py
75
75
  nucs/solvers/backtrack_solver.py
76
76
  nucs/solvers/bound_consistency_algorithm.py
77
+ nucs/solvers/choice_points.py
77
78
  nucs/solvers/consistency_algorithms.py
78
79
  nucs/solvers/heuristics.py
79
80
  nucs/solvers/multiprocessing_solver.py
81
+ nucs/solvers/shaving_consistency_algorithm.py
80
82
  nucs/solvers/solver.py
81
83
  tests/__init__.py
82
84
  tests/examples/test_alpha.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: NUCS
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: A Numpy and Numba based Python library for solving Constraint Satisfaction Problems over finite domains
5
5
  Author-email: Yan Georget <yan.georget@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/yangeorget/nucs
@@ -35,8 +35,9 @@ Requires-Dist: numpy==2.0.1
35
35
 
36
36
  ## TLDR
37
37
  NuCS is a Python library for solving Constraint Satisfaction and Optimization Problems.
38
- Because it is 100% written in Python, NuCS is easy to install and use.
39
- NuCS is also very fast because it is powered by [Numpy](https://numpy.org/) and [Numba](https://numba.pydata.org/).
38
+ Because it is 100% written in Python,
39
+ NuCS is easy to install and allows to model complex problems in a few lines of code.
40
+ The NuCS solver is also very fast because it is powered by [Numpy](https://numpy.org/) and [Numba](https://numba.pydata.org/).
40
41
 
41
42
  ## Installation
42
43
  ```bash
@@ -13,8 +13,9 @@
13
13
 
14
14
  ## TLDR
15
15
  NuCS is a Python library for solving Constraint Satisfaction and Optimization Problems.
16
- Because it is 100% written in Python, NuCS is easy to install and use.
17
- NuCS is also very fast because it is powered by [Numpy](https://numpy.org/) and [Numba](https://numba.pydata.org/).
16
+ Because it is 100% written in Python,
17
+ NuCS is easy to install and allows to model complex problems in a few lines of code.
18
+ The NuCS solver is also very fast because it is powered by [Numpy](https://numpy.org/) and [Numba](https://numba.pydata.org/).
18
19
 
19
20
  ## Installation
20
21
  ```bash
@@ -6,8 +6,8 @@ project = 'NUCS'
6
6
  copyright = '2024, Yan Georget'
7
7
  author = 'Yan Georget'
8
8
 
9
- release = '2.0.0'
10
- version = '2.0.0'
9
+ release = '2.1.0'
10
+ version = '2.1.0'
11
11
 
12
12
  # -- General configuration
13
13
 
@@ -42,8 +42,10 @@ SIGNATURE_CONSISTENCY_ALG = int64(
42
42
  int32[:, :], # props_dom_offsets
43
43
  int32[:], # props_parameters
44
44
  bool[:, :, :], # shr_domains_propagators
45
- int32[:, :], # shr_domains_arr
46
- bool[:], # not_entailed_propagators
45
+ int32[:, :, :], # shr_domains_stack
46
+ bool[:, :], # not_entailed_propagators_stack
47
+ uint16[:, :], # dom_update_stack
48
+ uint8[:], # stacks_height
47
49
  bool[:], # triggered_propagators
48
50
  int64[:], # compute_domains_addrs
49
51
  )
@@ -18,7 +18,7 @@ from numpy.typing import NDArray
18
18
 
19
19
  from nucs.constants import MAX, MIN
20
20
  from nucs.problems.problem import Problem
21
- from nucs.propagators.propagators import ALG_AFFINE_EQ, ALG_AFFINE_LEQ, ALG_ALLDIFFERENT
21
+ from nucs.propagators.propagators import ALG_AFFINE_EQ, ALG_AFFINE_LEQ, ALG_ALLDIFFERENT, add_propagators
22
22
  from nucs.solvers.bound_consistency_algorithm import bound_consistency_algorithm
23
23
  from nucs.solvers.heuristics import first_not_instantiated_var_heuristic
24
24
 
@@ -124,8 +124,10 @@ def golomb_consistency_algorithm(
124
124
  props_dom_offsets: NDArray,
125
125
  props_parameters: NDArray,
126
126
  shr_domains_propagators: NDArray,
127
- shr_domains_arr: NDArray,
128
- not_entailed_propagators: NDArray,
127
+ shr_domains_stack: NDArray,
128
+ not_entailed_propagators_stack: NDArray,
129
+ dom_update_stack: NDArray,
130
+ stacks_height: NDArray,
129
131
  triggered_propagators: NDArray,
130
132
  compute_domains_addrs: NDArray,
131
133
  ) -> int:
@@ -137,7 +139,7 @@ def golomb_consistency_algorithm(
137
139
  """
138
140
  # first prune the search space
139
141
  mark_nb = (1 + int(math.sqrt(8 * len(dom_indices_arr) + 1))) // 2
140
- ni_var_idx = first_not_instantiated_var_heuristic(shr_domains_arr) # no domains shared between vars
142
+ ni_var_idx = first_not_instantiated_var_heuristic(shr_domains_stack[0]) # no domains shared between vars
141
143
  if 1 < ni_var_idx < mark_nb - 1: # otherwise useless
142
144
  used_distance = np.zeros(sum_first(mark_nb - 2) + 1, dtype=np.bool)
143
145
  # a reusable array for storing the minimal sum of different integers:
@@ -146,7 +148,7 @@ def golomb_consistency_algorithm(
146
148
  # the following will mark at most sum(n-3) numbers as used
147
149
  # hence there will be at least n-2 unused numbers greater than 0
148
150
  for var_idx in range(index(mark_nb, ni_var_idx - 2, ni_var_idx - 1) + 1):
149
- dist = shr_domains_arr[dom_indices_arr[var_idx], MIN] # no offset
151
+ dist = shr_domains_stack[0, dom_indices_arr[var_idx], MIN] # no offset
150
152
  if dist < len(used_distance):
151
153
  used_distance[dist] = True
152
154
  # let's compute the sum of non-used numbers
@@ -158,7 +160,11 @@ def golomb_consistency_algorithm(
158
160
  distance += 1
159
161
  for i in range(ni_var_idx - 1, mark_nb - 1):
160
162
  for j in range(i + 1, mark_nb):
161
- shr_domains_arr[dom_indices_arr[index(mark_nb, i, j)], MIN] = minimal_sum[j - i] # no offset
163
+ dom_idx = dom_indices_arr[index(mark_nb, i, j)]
164
+ shr_domains_stack[0, dom_idx, MIN] = minimal_sum[j - i] # no offset
165
+ add_propagators(
166
+ triggered_propagators, not_entailed_propagators_stack[0], shr_domains_propagators, dom_idx, MIN
167
+ )
162
168
  return bound_consistency_algorithm(
163
169
  statistics,
164
170
  algorithms,
@@ -170,8 +176,10 @@ def golomb_consistency_algorithm(
170
176
  props_dom_offsets,
171
177
  props_parameters,
172
178
  shr_domains_propagators,
173
- shr_domains_arr,
174
- not_entailed_propagators,
179
+ shr_domains_stack,
180
+ not_entailed_propagators_stack,
181
+ dom_update_stack,
182
+ stacks_height,
175
183
  triggered_propagators,
176
184
  compute_domains_addrs,
177
185
  )
@@ -16,6 +16,7 @@ from rich import print
16
16
 
17
17
  from nucs.examples.queens.queens_problem import QueensProblem
18
18
  from nucs.solvers.backtrack_solver import BacktrackSolver
19
+ from nucs.solvers.consistency_algorithms import CONSISTENCY_ALG_BC, CONSISTENCY_ALG_SHAVING
19
20
  from nucs.solvers.heuristics import VAR_HEURISTIC_FIRST_NOT_INSTANTIATED, VAR_HEURISTIC_SMALLEST_DOMAIN
20
21
  from nucs.solvers.multiprocessing_solver import MultiprocessingSolver
21
22
  from nucs.statistics import get_statistics
@@ -26,6 +27,7 @@ if __name__ == "__main__":
26
27
  parser = argparse.ArgumentParser()
27
28
  parser.add_argument("-n", type=int, default=10)
28
29
  parser.add_argument("--processors", type=int, default=1)
30
+ parser.add_argument("--shaving", type=bool, action=argparse.BooleanOptionalAction, default=False)
29
31
  parser.add_argument("--ff", type=bool, action=argparse.BooleanOptionalAction, default=False)
30
32
  args = parser.parse_args()
31
33
  problem = QueensProblem(args.n)
@@ -34,6 +36,7 @@ if __name__ == "__main__":
34
36
  [
35
37
  BacktrackSolver(
36
38
  problem,
39
+ consistency_alg_idx=CONSISTENCY_ALG_SHAVING if args.shaving else CONSISTENCY_ALG_BC,
37
40
  var_heuristic_idx=VAR_HEURISTIC_SMALLEST_DOMAIN if args.ff else VAR_HEURISTIC_FIRST_NOT_INSTANTIATED,
38
41
  )
39
42
  for problem in problems
@@ -124,16 +124,30 @@ ALG_RELATION = register_propagator(get_triggers_relation, get_complexity_relatio
124
124
 
125
125
 
126
126
  @njit(cache=True)
127
- def pop_propagator(triggered_propagators: NDArray, not_entailed_propagators: NDArray, previous_prop_idx: int) -> int:
127
+ def pop_propagator(triggered_propagators: NDArray, previous_prop_idx: int) -> int:
128
128
  """
129
129
  Pops a propagator to be filtered.
130
130
  :param triggered_propagators: the candidate propagators
131
- :param not_entailed_propagators: the propagators that are not entailed yet
132
131
  :param previous_prop_idx: the index of the previous propagator which has been selected
133
132
  :return: an index
134
133
  """
135
- for prop_idx, triggered_prop in enumerate(triggered_propagators):
136
- if triggered_prop and not_entailed_propagators[prop_idx] and prop_idx != previous_prop_idx:
134
+ for prop_idx in range(len(triggered_propagators)):
135
+ if triggered_propagators[prop_idx] and prop_idx != previous_prop_idx:
137
136
  triggered_propagators[prop_idx] = False
138
137
  return prop_idx
139
138
  return -1
139
+
140
+
141
+ @njit(cache=True)
142
+ def add_propagators(
143
+ triggered_propagators: NDArray,
144
+ not_entailed_propagators: NDArray,
145
+ shr_domains_propagators: NDArray,
146
+ dom_idx: int,
147
+ bound: int,
148
+ ) -> None:
149
+ for prop_idx in range(len(triggered_propagators)):
150
+ if shr_domains_propagators[dom_idx, bound, prop_idx] and not_entailed_propagators[prop_idx]:
151
+ triggered_propagators[prop_idx] = True
152
+ # np.logical_or(triggered_propagators, shr_domains_propagators[dom_idx, bound], triggered_propagators)
153
+ # np.logical_and(triggered_propagators, not_entailed_propagators, triggered_propagators)
@@ -31,7 +31,8 @@ from nucs.constants import (
31
31
  )
32
32
  from nucs.numba_helper import build_function_address_list, function_from_address
33
33
  from nucs.problems.problem import Problem
34
- from nucs.propagators.propagators import COMPUTE_DOMAINS_FCTS
34
+ from nucs.propagators.propagators import COMPUTE_DOMAINS_FCTS, add_propagators
35
+ from nucs.solvers.choice_points import backtrack, cp_init, cp_put
35
36
  from nucs.solvers.consistency_algorithms import CONSISTENCY_ALG_BC, CONSISTENCY_ALG_FCTS
36
37
  from nucs.solvers.heuristics import (
37
38
  DOM_HEURISTIC_FCTS,
@@ -44,7 +45,6 @@ from nucs.statistics import (
44
45
  STATS_IDX_OPTIMIZER_SOLUTION_NB,
45
46
  STATS_IDX_PROBLEM_PROPAGATOR_NB,
46
47
  STATS_IDX_PROBLEM_VARIABLE_NB,
47
- STATS_IDX_SOLVER_BACKTRACK_NB,
48
48
  STATS_IDX_SOLVER_CHOICE_DEPTH,
49
49
  STATS_IDX_SOLVER_CHOICE_NB,
50
50
  STATS_IDX_SOLVER_SOLUTION_NB,
@@ -83,7 +83,7 @@ class BacktrackSolver(Solver):
83
83
  ):
84
84
  """
85
85
  Inits the solver.
86
- :param problem: the problem
86
+ :param problem: the problem to be solved
87
87
  :param consistency_alg_idx: the index of the consistency algorithm
88
88
  :param var_heuristic_idx: the index of the heuristic for selecting a variable/domain
89
89
  :param dom_heuristic_idx: the index of the heuristic for reducing a domain
@@ -98,10 +98,16 @@ class BacktrackSolver(Solver):
98
98
  self.problem = problem
99
99
  # choice points
100
100
  self.shr_domains_stack = np.empty((stack_max_height, self.problem.variable_nb, 2), dtype=np.int32)
101
- self.shr_domains_stack[0] = problem.shr_domains_lst
102
101
  self.not_entailed_propagators_stack = np.empty((stack_max_height, self.problem.propagator_nb), dtype=np.bool)
103
- self.not_entailed_propagators_stack[0] = True
102
+ self.dom_update_stack = np.empty((stack_max_height, 2), dtype=np.uint16)
104
103
  self.stacks_height = np.ones((1,), dtype=np.uint8)
104
+ cp_init(
105
+ self.shr_domains_stack,
106
+ self.not_entailed_propagators_stack,
107
+ self.dom_update_stack,
108
+ self.stacks_height,
109
+ np.array(problem.shr_domains_lst),
110
+ )
105
111
  # statistics
106
112
  self.statistics = init_statistics()
107
113
  self.statistics[STATS_IDX_PROBLEM_PROPAGATOR_NB] = self.problem.propagator_nb
@@ -148,6 +154,7 @@ class BacktrackSolver(Solver):
148
154
  self.problem.shr_domains_propagators,
149
155
  self.shr_domains_stack,
150
156
  self.not_entailed_propagators_stack,
157
+ self.dom_update_stack,
151
158
  self.stacks_height,
152
159
  self.triggered_propagators,
153
160
  self.consistency_alg_idx,
@@ -165,6 +172,7 @@ class BacktrackSolver(Solver):
165
172
  self.problem,
166
173
  self.shr_domains_stack,
167
174
  self.not_entailed_propagators_stack,
175
+ self.dom_update_stack,
168
176
  self.stacks_height,
169
177
  self.triggered_propagators,
170
178
  )
@@ -199,6 +207,7 @@ class BacktrackSolver(Solver):
199
207
  self.problem.shr_domains_propagators,
200
208
  self.shr_domains_stack,
201
209
  self.not_entailed_propagators_stack,
210
+ self.dom_update_stack,
202
211
  self.stacks_height,
203
212
  self.triggered_propagators,
204
213
  self.consistency_alg_idx,
@@ -216,8 +225,10 @@ class BacktrackSolver(Solver):
216
225
  self.statistics,
217
226
  self.shr_domains_stack,
218
227
  self.not_entailed_propagators_stack,
228
+ self.dom_update_stack,
219
229
  self.stacks_height,
220
230
  self.triggered_propagators,
231
+ self.problem.shr_domains_propagators,
221
232
  ):
222
233
  break
223
234
 
@@ -265,6 +276,7 @@ class BacktrackSolver(Solver):
265
276
  self.problem.shr_domains_propagators,
266
277
  self.shr_domains_stack,
267
278
  self.not_entailed_propagators_stack,
279
+ self.dom_update_stack,
268
280
  self.stacks_height,
269
281
  self.triggered_propagators,
270
282
  self.consistency_alg_idx,
@@ -283,6 +295,7 @@ class BacktrackSolver(Solver):
283
295
  self.problem,
284
296
  self.shr_domains_stack,
285
297
  self.not_entailed_propagators_stack,
298
+ self.dom_update_stack,
286
299
  self.stacks_height,
287
300
  self.triggered_propagators,
288
301
  )
@@ -318,6 +331,7 @@ class BacktrackSolver(Solver):
318
331
  self.problem.shr_domains_propagators,
319
332
  self.shr_domains_stack,
320
333
  self.not_entailed_propagators_stack,
334
+ self.dom_update_stack,
321
335
  self.stacks_height,
322
336
  self.triggered_propagators,
323
337
  self.consistency_alg_idx,
@@ -335,43 +349,20 @@ class BacktrackSolver(Solver):
335
349
  self.statistics,
336
350
  self.shr_domains_stack,
337
351
  self.not_entailed_propagators_stack,
352
+ self.dom_update_stack,
338
353
  self.stacks_height,
339
354
  self.triggered_propagators,
355
+ self.problem.shr_domains_propagators,
340
356
  ):
341
357
  break
342
358
  solution_queue.put((processor_idx, None, self.statistics))
343
359
 
344
360
 
345
- @njit(cache=True)
346
- def backtrack(
347
- statistics: NDArray,
348
- shr_domains_stack: NDArray,
349
- not_entailed_propagators_stack: NDArray,
350
- stacks_height: NDArray,
351
- triggered_propagators: NDArray,
352
- ) -> bool:
353
- """
354
- Backtracks and updates the problem's domains.
355
- :param statistics: the statistics array
356
- :param shr_domains_stack: the stack of shared domains
357
- :param not_entailed_propagators_stack: the stack of not entailed propagators
358
- :param stacks_height: the height of both stacks
359
- :return: true iff it is possible to backtrack
360
- """
361
- if stacks_height[0] == 1:
362
- return False
363
- stacks_height[0] -= 1
364
- shr_domains_stack[0, :, :] = shr_domains_stack[stacks_height[0], :, :]
365
- not_entailed_propagators_stack[0, :] = not_entailed_propagators_stack[stacks_height[0], :]
366
- statistics[STATS_IDX_SOLVER_BACKTRACK_NB] += 1
367
- triggered_propagators[:] = not_entailed_propagators_stack[0, :] # numba does not support copyto
368
- return True
369
-
370
-
371
361
  def reset(
372
362
  problem: Problem,
373
363
  shr_domains_stack: NDArray,
374
364
  not_entailed_propagators_stack: NDArray,
365
+ dom_update_stack: NDArray,
375
366
  stacks_height: NDArray,
376
367
  triggered_propagators: NDArray,
377
368
  ) -> None:
@@ -383,9 +374,13 @@ def reset(
383
374
  :param stacks_height: the height of both stacks
384
375
  :param triggered_propagators: the list of triggered propagators
385
376
  """
386
- shr_domains_stack[0] = problem.shr_domains_lst
387
- not_entailed_propagators_stack[0, :] = True
388
- stacks_height[0] = 1
377
+ cp_init(
378
+ shr_domains_stack,
379
+ not_entailed_propagators_stack,
380
+ dom_update_stack,
381
+ stacks_height,
382
+ np.array(problem.shr_domains_lst),
383
+ )
389
384
  triggered_propagators.fill(True)
390
385
 
391
386
 
@@ -403,6 +398,7 @@ def solve_one(
403
398
  shr_domains_propagators: NDArray,
404
399
  shr_domains_stack: NDArray,
405
400
  not_entailed_propagators_stack: NDArray,
401
+ dom_update_stack: NDArray,
406
402
  stacks_height: NDArray,
407
403
  triggered_propagators: NDArray,
408
404
  consistency_alg_idx: int,
@@ -462,8 +458,10 @@ def solve_one(
462
458
  props_dom_offsets,
463
459
  props_parameters,
464
460
  shr_domains_propagators,
465
- shr_domains_stack[0],
466
- not_entailed_propagators_stack[0],
461
+ shr_domains_stack,
462
+ not_entailed_propagators_stack,
463
+ dom_update_stack,
464
+ stacks_height,
467
465
  triggered_propagators,
468
466
  compute_domains_addrs,
469
467
  )
@@ -472,15 +470,27 @@ def solve_one(
472
470
  return get_solution(shr_domains_stack[0], dom_indices_arr, dom_offsets_arr)
473
471
  elif status == PROBLEM_UNBOUND:
474
472
  dom_idx = var_heuristic_fct(shr_domains_stack[0])
475
- shr_domains_stack[stacks_height[0], :, :] = shr_domains_stack[0, :, :]
476
- not_entailed_propagators_stack[stacks_height[0], :] = not_entailed_propagators_stack[0, :]
477
- stacks_height[0] += 1
478
- event = dom_heuristic_fct(shr_domains_stack[0, dom_idx], shr_domains_stack[stacks_height[0] - 1, dom_idx])
479
- np.logical_or(triggered_propagators, shr_domains_propagators[dom_idx, event], triggered_propagators)
473
+ cp_put(shr_domains_stack, not_entailed_propagators_stack, dom_update_stack, stacks_height, dom_idx)
474
+ cp_idx = stacks_height[0] - 1
475
+ event = dom_heuristic_fct(shr_domains_stack[0, dom_idx], shr_domains_stack[cp_idx, dom_idx])
476
+ dom_update_stack[cp_idx, 1] = event
477
+ add_propagators(
478
+ triggered_propagators,
479
+ not_entailed_propagators_stack[0],
480
+ shr_domains_propagators,
481
+ dom_idx,
482
+ event,
483
+ )
480
484
  statistics[STATS_IDX_SOLVER_CHOICE_NB] += 1
481
485
  if stacks_height[0] - 1 > statistics[STATS_IDX_SOLVER_CHOICE_DEPTH]:
482
486
  statistics[STATS_IDX_SOLVER_CHOICE_DEPTH] = stacks_height[0] - 1
483
487
  elif not backtrack(
484
- statistics, shr_domains_stack, not_entailed_propagators_stack, stacks_height, triggered_propagators
488
+ statistics,
489
+ shr_domains_stack,
490
+ not_entailed_propagators_stack,
491
+ dom_update_stack,
492
+ stacks_height,
493
+ triggered_propagators,
494
+ shr_domains_propagators,
485
495
  ):
486
496
  return None
@@ -28,7 +28,7 @@ from nucs.constants import (
28
28
  TYPE_COMPUTE_DOMAINS,
29
29
  )
30
30
  from nucs.numba_helper import function_from_address
31
- from nucs.propagators.propagators import COMPUTE_DOMAINS_FCTS, pop_propagator
31
+ from nucs.propagators.propagators import COMPUTE_DOMAINS_FCTS, add_propagators, pop_propagator
32
32
  from nucs.solvers.solver import is_solved
33
33
  from nucs.statistics import (
34
34
  STATS_IDX_PROBLEM_FILTER_NB,
@@ -51,8 +51,10 @@ def bound_consistency_algorithm(
51
51
  props_dom_offsets: NDArray,
52
52
  props_parameters: NDArray,
53
53
  shr_domains_propagators: NDArray,
54
- shr_domains_arr: NDArray,
55
- not_entailed_propagators: NDArray,
54
+ shr_domains_stack: NDArray,
55
+ not_entailed_propagators_stack: NDArray,
56
+ dom_update_stack: NDArray,
57
+ stacks_height: NDArray,
56
58
  triggered_propagators: NDArray,
57
59
  compute_domains_addrs: NDArray,
58
60
  ) -> int:
@@ -70,8 +72,11 @@ def bound_consistency_algorithm(
70
72
  :param shr_domains_propagators: a Numpy array of booleans indexed
71
73
  by shared domain indices, MIN/MAX and propagators; true means that the propagator has to be triggered when the MIN
72
74
  or MAX of the shared domain has changed
73
- :param shr_domains_arr: the current shared domains
74
- :param not_entailed_propagators: the propagators currently not entailed
75
+ :param shr_domains_stack: a stack of shared domains;
76
+ the first level correspond to the current shared domains, the rest correspond to the choice points
77
+ :param not_entailed_propagators_stack: a stack not entailed propagators;
78
+ the first level correspond to the propagators currently not entailed, the rest correspond to the choice points
79
+ :param stacks_height: the height of the stacks as a Numpy array
75
80
  :param triggered_propagators: the Numpy array of triggered propagators
76
81
  :param compute_domains_addrs: the addresses of the compute_domains functions
77
82
  :return: a status (consistency, inconsistency or entailment) as an integer
@@ -79,9 +84,9 @@ def bound_consistency_algorithm(
79
84
  statistics[STATS_IDX_PROBLEM_FILTER_NB] += 1
80
85
  prop_idx = -1
81
86
  while True:
82
- prop_idx = pop_propagator(triggered_propagators, not_entailed_propagators, prop_idx)
87
+ prop_idx = pop_propagator(triggered_propagators, prop_idx)
83
88
  if prop_idx == -1:
84
- return PROBLEM_BOUND if is_solved(shr_domains_arr) else PROBLEM_UNBOUND
89
+ return PROBLEM_BOUND if is_solved(shr_domains_stack[0]) else PROBLEM_UNBOUND
85
90
  statistics[STATS_IDX_PROPAGATOR_FILTER_NB] += 1
86
91
  prop_var_start = var_bounds[prop_idx, START]
87
92
  prop_var_end = var_bounds[prop_idx, END]
@@ -89,7 +94,7 @@ def bound_consistency_algorithm(
89
94
  prop_offsets = props_dom_offsets[prop_var_start:prop_var_end]
90
95
  prop_var_nb = prop_var_end - prop_var_start
91
96
  prop_domains = np.empty((prop_var_nb, 2), dtype=np.int32)
92
- np.add(shr_domains_arr[prop_indices], prop_offsets, prop_domains)
97
+ np.add(shr_domains_stack[0, prop_indices], prop_offsets, prop_domains)
93
98
  compute_domains_function = (
94
99
  COMPUTE_DOMAINS_FCTS[algorithms[prop_idx]]
95
100
  if NUMBA_DISABLE_JIT
@@ -102,19 +107,23 @@ def bound_consistency_algorithm(
102
107
  statistics[STATS_IDX_PROPAGATOR_INCONSISTENCY_NB] += 1
103
108
  return PROBLEM_INCONSISTENT
104
109
  if status == PROP_ENTAILMENT:
105
- not_entailed_propagators[prop_idx] = False
110
+ not_entailed_propagators_stack[0, prop_idx] = False
106
111
  statistics[STATS_IDX_PROPAGATOR_ENTAILMENT_NB] += 1
107
112
  shr_domains_changes = False
108
113
  for var_idx in range(prop_var_nb):
109
114
  shr_domain_idx = prop_indices[var_idx]
110
- prop_offset = prop_offsets[var_idx, 0]
115
+ prop_offset = prop_offsets[var_idx, 0] # because of vertical shape
111
116
  for bound in [MIN, MAX]:
112
117
  shr_domain_bound = prop_domains[var_idx, bound] - prop_offset
113
- if shr_domains_arr[shr_domain_idx, bound] != shr_domain_bound:
114
- shr_domains_arr[shr_domain_idx, bound] = shr_domain_bound
118
+ if shr_domains_stack[0, shr_domain_idx, bound] != shr_domain_bound:
119
+ shr_domains_stack[0, shr_domain_idx, bound] = shr_domain_bound
115
120
  shr_domains_changes = True
116
- np.logical_or(
117
- triggered_propagators, shr_domains_propagators[shr_domain_idx, bound], triggered_propagators
121
+ add_propagators(
122
+ triggered_propagators,
123
+ not_entailed_propagators_stack[0],
124
+ shr_domains_propagators,
125
+ shr_domain_idx,
126
+ bound,
118
127
  )
119
128
  if not shr_domains_changes:
120
129
  statistics[STATS_IDX_PROPAGATOR_FILTER_NO_CHANGE_NB] += 1
@@ -0,0 +1,84 @@
1
+ from typing import Optional
2
+
3
+ from numba import njit # type: ignore
4
+ from numpy.typing import NDArray
5
+
6
+ from nucs.propagators.propagators import add_propagators
7
+ from nucs.statistics import STATS_IDX_SOLVER_BACKTRACK_NB
8
+
9
+
10
+ @njit(cache=True)
11
+ def cp_init(
12
+ shr_domains_stack: NDArray,
13
+ not_entailed_propagators_stack: NDArray,
14
+ dom_update_stack: NDArray,
15
+ stacks_height: NDArray,
16
+ shr_domains_arr: NDArray,
17
+ ) -> None:
18
+ shr_domains_stack[0] = shr_domains_arr
19
+ not_entailed_propagators_stack[0] = True
20
+ dom_update_stack[0, :] = 0
21
+ stacks_height[0] = 1
22
+
23
+
24
+ @njit(cache=True)
25
+ def cp_put(
26
+ shr_domains_stack: NDArray,
27
+ not_entailed_propagators_stack: NDArray,
28
+ dom_update_stack: NDArray,
29
+ stacks_height: NDArray,
30
+ dom_index: int,
31
+ bound: Optional[int] = 0,
32
+ ) -> None:
33
+ shr_domains_stack[stacks_height[0], :, :] = shr_domains_stack[0, :, :]
34
+ not_entailed_propagators_stack[stacks_height[0], :] = not_entailed_propagators_stack[0, :]
35
+ dom_update_stack[stacks_height[0], 0] = dom_index
36
+ dom_update_stack[stacks_height[0], 1] = bound
37
+ stacks_height[0] += 1
38
+
39
+
40
+ @njit(cache=True)
41
+ def cp_pop(
42
+ shr_domains_stack: NDArray,
43
+ not_entailed_propagators_stack: NDArray,
44
+ dom_update_stack: NDArray,
45
+ stacks_height: NDArray,
46
+ ) -> bool:
47
+ if stacks_height[0] == 1:
48
+ return False
49
+ stacks_height[0] -= 1
50
+ shr_domains_stack[0, :, :] = shr_domains_stack[stacks_height[0], :, :]
51
+ not_entailed_propagators_stack[0, :] = not_entailed_propagators_stack[stacks_height[0], :]
52
+ dom_update_stack[0] = dom_update_stack[stacks_height[0]]
53
+ return True
54
+
55
+
56
+ @njit(cache=True)
57
+ def backtrack(
58
+ statistics: NDArray,
59
+ shr_domains_stack: NDArray,
60
+ not_entailed_propagators_stack: NDArray,
61
+ dom_update_stack: NDArray,
62
+ stacks_height: NDArray,
63
+ triggered_propagators: NDArray,
64
+ shr_domains_propagators: NDArray,
65
+ ) -> bool:
66
+ """
67
+ Backtracks and updates the problem's domains.
68
+ :param statistics: the statistics array
69
+ :param shr_domains_stack: the stack of shared domains
70
+ :param not_entailed_propagators_stack: the stack of not entailed propagators
71
+ :param stacks_height: the height of both stacks
72
+ :return: true iff it is possible to backtrack
73
+ """
74
+ if not cp_pop(shr_domains_stack, not_entailed_propagators_stack, dom_update_stack, stacks_height):
75
+ return False
76
+ statistics[STATS_IDX_SOLVER_BACKTRACK_NB] += 1
77
+ add_propagators(
78
+ triggered_propagators,
79
+ not_entailed_propagators_stack[0],
80
+ shr_domains_propagators,
81
+ dom_update_stack[0, 0],
82
+ 1 - dom_update_stack[0, 1],
83
+ )
84
+ return True
@@ -13,6 +13,7 @@
13
13
  from typing import Callable
14
14
 
15
15
  from nucs.solvers.bound_consistency_algorithm import bound_consistency_algorithm
16
+ from nucs.solvers.shaving_consistency_algorithm import shaving_consistency_algorithm
16
17
 
17
18
  CONSISTENCY_ALG_FCTS = []
18
19
 
@@ -28,3 +29,4 @@ def register_consistency_algorithm(consistency_algorithm_fct: Callable) -> int:
28
29
 
29
30
 
30
31
  CONSISTENCY_ALG_BC = register_consistency_algorithm(bound_consistency_algorithm)
32
+ CONSISTENCY_ALG_SHAVING = register_consistency_algorithm(shaving_consistency_algorithm)
@@ -26,8 +26,21 @@ def first_not_instantiated_var_heuristic(shr_domains: NDArray) -> int:
26
26
  :param shr_domains: the shared domains of the problem
27
27
  :return: the index of the shared domain
28
28
  """
29
- for dom_idx, shr_domain in enumerate(shr_domains):
30
- if shr_domain[MIN] < shr_domain[MAX]:
29
+ for dom_idx in range(len(shr_domains)):
30
+ if shr_domains[dom_idx, MIN] < shr_domains[dom_idx, MAX]:
31
+ return dom_idx
32
+ return -1 # cannot happen
33
+
34
+
35
+ @njit(cache=True)
36
+ def first_not_instantiated_var_heuristic_from_index(shr_domains: NDArray, start_idx: int) -> int:
37
+ """
38
+ Chooses the first non-instantiated shared domain.
39
+ :param shr_domains: the shared domains of the problem
40
+ :return: the index of the shared domain
41
+ """
42
+ for dom_idx in range(start_idx, len(shr_domains)):
43
+ if shr_domains[dom_idx, MIN] < shr_domains[dom_idx, MAX]:
31
44
  return dom_idx
32
45
  return -1 # cannot happen
33
46
 
@@ -40,8 +53,7 @@ def last_not_instantiated_var_heuristic(shr_domains: NDArray) -> int:
40
53
  :return: the index of the shared domain
41
54
  """
42
55
  for dom_idx in range(len(shr_domains) - 1, -1, -1):
43
- shr_domain = shr_domains[dom_idx]
44
- if shr_domain[MIN] < shr_domain[MAX]:
56
+ if shr_domains[dom_idx, MIN] < shr_domains[dom_idx, MAX]:
45
57
  return dom_idx
46
58
  return -1 # cannot happen
47
59