NUCS 2.3.0__tar.gz → 3.0.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 (120) hide show
  1. {nucs-2.3.0 → nucs-3.0.0/NUCS.egg-info}/PKG-INFO +1 -1
  2. {nucs-2.3.0/NUCS.egg-info → nucs-3.0.0}/PKG-INFO +1 -1
  3. {nucs-2.3.0 → nucs-3.0.0}/docs/source/conf.py +2 -2
  4. {nucs-2.3.0 → nucs-3.0.0}/nucs/constants.py +5 -3
  5. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/golomb/golomb_problem.py +11 -6
  6. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/queens/__main__.py +21 -11
  7. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/propagators.py +4 -2
  8. {nucs-2.3.0 → nucs-3.0.0}/nucs/solvers/backtrack_solver.py +38 -35
  9. {nucs-2.3.0 → nucs-3.0.0}/nucs/solvers/bound_consistency_algorithm.py +14 -14
  10. nucs-3.0.0/nucs/solvers/choice_points.py +88 -0
  11. nucs-3.0.0/nucs/solvers/heuristics.py +193 -0
  12. {nucs-2.3.0 → nucs-3.0.0}/nucs/solvers/multiprocessing_solver.py +1 -4
  13. {nucs-2.3.0 → nucs-3.0.0}/nucs/solvers/shaving_consistency_algorithm.py +59 -131
  14. {nucs-2.3.0 → nucs-3.0.0}/nucs/solvers/solver.py +44 -11
  15. {nucs-2.3.0 → nucs-3.0.0}/pyproject.toml +1 -1
  16. nucs-2.3.0/nucs/solvers/choice_points.py +0 -84
  17. nucs-2.3.0/nucs/solvers/heuristics.py +0 -165
  18. {nucs-2.3.0 → nucs-3.0.0}/LICENSE.md +0 -0
  19. {nucs-2.3.0 → nucs-3.0.0}/NUCS.egg-info/SOURCES.txt +0 -0
  20. {nucs-2.3.0 → nucs-3.0.0}/NUCS.egg-info/dependency_links.txt +0 -0
  21. {nucs-2.3.0 → nucs-3.0.0}/NUCS.egg-info/requires.txt +0 -0
  22. {nucs-2.3.0 → nucs-3.0.0}/NUCS.egg-info/top_level.txt +0 -0
  23. {nucs-2.3.0 → nucs-3.0.0}/README.md +0 -0
  24. {nucs-2.3.0 → nucs-3.0.0}/nucs/__init__.py +0 -0
  25. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/__init__.py +0 -0
  26. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/alpha/__init__.py +0 -0
  27. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/alpha/__main__.py +0 -0
  28. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/alpha/alpha_problem.py +0 -0
  29. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/bibd/__init__.py +0 -0
  30. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/bibd/__main__.py +0 -0
  31. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/bibd/bibd_problem.py +0 -0
  32. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/donald/__init__.py +0 -0
  33. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/donald/__main__.py +0 -0
  34. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/donald/donald_problem.py +0 -0
  35. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/golomb/__init__.py +0 -0
  36. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/golomb/__main__.py +0 -0
  37. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/knapsack/__init__.py +0 -0
  38. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/knapsack/__main__.py +0 -0
  39. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/knapsack/knapsack_problem.py +0 -0
  40. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/magic_sequence/__init__.py +0 -0
  41. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/magic_sequence/__main__.py +0 -0
  42. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/magic_sequence/magic_sequence_problem.py +0 -0
  43. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/magic_square/__init__.py +0 -0
  44. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/magic_square/__main__.py +0 -0
  45. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/magic_square/magic_square_problem.py +0 -0
  46. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/quasigroup/__init__.py +0 -0
  47. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/quasigroup/__main__.py +0 -0
  48. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/quasigroup/quasigroup_problem.py +0 -0
  49. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/queens/__init__.py +0 -0
  50. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/queens/queens_problem.py +0 -0
  51. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/schur_lemma/__init__.py +0 -0
  52. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/schur_lemma/__main__.py +0 -0
  53. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/schur_lemma/schur_lemma_problem.py +0 -0
  54. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/sports_tournament_scheduling/__init__.py +0 -0
  55. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/sports_tournament_scheduling/__main__.py +0 -0
  56. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/sports_tournament_scheduling/sports_tournament_scheduling_problem.py +0 -0
  57. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/sudoku/__init__.py +0 -0
  58. {nucs-2.3.0 → nucs-3.0.0}/nucs/examples/sudoku/sudoku_problem.py +0 -0
  59. {nucs-2.3.0 → nucs-3.0.0}/nucs/numba_helper.py +0 -0
  60. {nucs-2.3.0 → nucs-3.0.0}/nucs/numpy_helper.py +0 -0
  61. {nucs-2.3.0 → nucs-3.0.0}/nucs/problems/__init__.py +0 -0
  62. {nucs-2.3.0 → nucs-3.0.0}/nucs/problems/latin_square_problem.py +0 -0
  63. {nucs-2.3.0 → nucs-3.0.0}/nucs/problems/problem.py +0 -0
  64. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/__init__.py +0 -0
  65. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/affine_eq_propagator.py +0 -0
  66. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/affine_geq_propagator.py +0 -0
  67. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/affine_leq_propagator.py +0 -0
  68. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/alldifferent_propagator.py +0 -0
  69. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/and_propagator.py +0 -0
  70. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/count_eq_propagator.py +0 -0
  71. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/dummy_propagator.py +0 -0
  72. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/element_lic_propagator.py +0 -0
  73. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/element_liv_propagator.py +0 -0
  74. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/exactly_eq_propagator.py +0 -0
  75. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/exactly_true_propagator.py +0 -0
  76. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/gcc_propagator.py +0 -0
  77. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/lexicographic_leq_propagator.py +0 -0
  78. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/max_eq_propagator.py +0 -0
  79. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/max_leq_propagator.py +0 -0
  80. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/min_eq_propagator.py +0 -0
  81. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/min_geq_propagator.py +0 -0
  82. {nucs-2.3.0 → nucs-3.0.0}/nucs/propagators/relation_propagator.py +0 -0
  83. {nucs-2.3.0 → nucs-3.0.0}/nucs/solvers/__init__.py +0 -0
  84. {nucs-2.3.0 → nucs-3.0.0}/nucs/solvers/consistency_algorithms.py +0 -0
  85. {nucs-2.3.0 → nucs-3.0.0}/setup.cfg +0 -0
  86. {nucs-2.3.0 → nucs-3.0.0}/tests/__init__.py +0 -0
  87. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_alpha.py +0 -0
  88. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_bibd.py +0 -0
  89. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_donald.py +0 -0
  90. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_golomb.py +0 -0
  91. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_knapsack.py +0 -0
  92. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_latin_square.py +0 -0
  93. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_magic_sequence.py +0 -0
  94. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_magic_square.py +0 -0
  95. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_quasigroup.py +0 -0
  96. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_queens.py +0 -0
  97. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_schur_lemma.py +0 -0
  98. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_sports_tournament_scheduling.py +0 -0
  99. {nucs-2.3.0 → nucs-3.0.0}/tests/examples/test_sudokus.py +0 -0
  100. {nucs-2.3.0 → nucs-3.0.0}/tests/problems/__init__.py +0 -0
  101. {nucs-2.3.0 → nucs-3.0.0}/tests/problems/test_problem.py +0 -0
  102. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/__init__.py +0 -0
  103. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_affine_eq.py +0 -0
  104. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_affine_geq.py +0 -0
  105. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_affine_leq.py +0 -0
  106. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_alldifferent.py +0 -0
  107. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_count_eq.py +0 -0
  108. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_element_lic.py +0 -0
  109. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_element_liv.py +0 -0
  110. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_exactly_eq.py +0 -0
  111. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_gcc.py +0 -0
  112. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_lexicographic_leq.py +0 -0
  113. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_max_eq.py +0 -0
  114. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_max_leq.py +0 -0
  115. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_min_eq.py +0 -0
  116. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_min_geq.py +0 -0
  117. {nucs-2.3.0 → nucs-3.0.0}/tests/propagators/test_relation.py +0 -0
  118. {nucs-2.3.0 → nucs-3.0.0}/tests/solvers/__init__.py +0 -0
  119. {nucs-2.3.0 → nucs-3.0.0}/tests/solvers/test_backtrack_solver.py +0 -0
  120. {nucs-2.3.0 → nucs-3.0.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.3.0
3
+ Version: 3.0.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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: NUCS
3
- Version: 2.3.0
3
+ Version: 3.0.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
@@ -6,8 +6,8 @@ project = 'NUCS'
6
6
  copyright = '2024, Yan Georget'
7
7
  author = 'Yan Georget'
8
8
 
9
- release = '2.3.0'
10
- version = '2.3.0'
9
+ release = '3.0.0'
10
+ version = '3.0.0'
11
11
 
12
12
  # -- General configuration
13
13
 
@@ -29,8 +29,10 @@ PROBLEM_UNBOUND = 1 # returned when the filtering of a problem has been complet
29
29
  PROBLEM_BOUND = 2 # returned when a problem is solved
30
30
 
31
31
  SIGNATURE_COMPUTE_DOMAINS = int64(int32[:, :], int32[:]) # domains, parameters
32
- SIGNATURE_DOM_HEURISTIC = int64(int32[:], int32[:]) # shr_domain, shr_domain_copy
33
- SIGNATURE_VAR_HEURISTIC = int64(int32[:, :]) # shr_domains
32
+ SIGNATURE_DOM_HEURISTIC = int64(
33
+ int32[:, :, :], uint16[:, :], uint8[:], int64 # shr_domains_stack # dom_update_stack # stacks_top # dom_idx
34
+ )
35
+ SIGNATURE_VAR_HEURISTIC = int64(int32[:, :, :], uint8[:]) # shr_domains_stack, stacks_top
34
36
  SIGNATURE_CONSISTENCY_ALG = int64(
35
37
  int64[:], # statistics
36
38
  uint8[:], # algorithms
@@ -57,7 +59,7 @@ TYPE_CONSISTENCY_ALG = types.FunctionType(SIGNATURE_CONSISTENCY_ALG)
57
59
 
58
60
  NUMBA_DISABLE_JIT = os.getenv("NUMBA_DISABLE_JIT")
59
61
 
60
- LOG_FORMAT = "%(asctime)s - %(levelname)s - %(name)s - %(message)s"
62
+ LOG_FORMAT = "%(asctime)s - %(processName)s - %(levelname)s - %(name)s - %(funcName)s - %(message)s"
61
63
  LOG_LEVEL_DEBUG = "DEBUG"
62
64
  LOG_LEVEL_INFO = "INFO"
63
65
  LOG_LEVEL_WARNING = "WARNING"
@@ -127,7 +127,7 @@ def golomb_consistency_algorithm(
127
127
  shr_domains_stack: NDArray,
128
128
  not_entailed_propagators_stack: NDArray,
129
129
  dom_update_stack: NDArray,
130
- stacks_height: NDArray,
130
+ stacks_top: NDArray,
131
131
  triggered_propagators: NDArray,
132
132
  compute_domains_addrs: NDArray,
133
133
  ) -> int:
@@ -139,7 +139,7 @@ def golomb_consistency_algorithm(
139
139
  """
140
140
  # first prune the search space
141
141
  mark_nb = (1 + int(math.sqrt(8 * len(dom_indices_arr) + 1))) // 2
142
- ni_var_idx = first_not_instantiated_var_heuristic(shr_domains_stack[0]) # no domains shared between vars
142
+ ni_var_idx = first_not_instantiated_var_heuristic(shr_domains_stack, stacks_top) # no domains shared between vars
143
143
  if 1 < ni_var_idx < mark_nb - 1: # otherwise useless
144
144
  used_distance = np.zeros(sum_first(mark_nb - 2) + 1, dtype=np.bool)
145
145
  # a reusable array for storing the minimal sum of different integers:
@@ -148,7 +148,7 @@ def golomb_consistency_algorithm(
148
148
  # the following will mark at most sum(n-3) numbers as used
149
149
  # hence there will be at least n-2 unused numbers greater than 0
150
150
  for var_idx in range(index(mark_nb, ni_var_idx - 2, ni_var_idx - 1) + 1):
151
- dist = shr_domains_stack[0, dom_indices_arr[var_idx], MIN] # no offset
151
+ dist = shr_domains_stack[stacks_top[0], dom_indices_arr[var_idx], MIN] # no offset
152
152
  if dist < len(used_distance):
153
153
  used_distance[dist] = True
154
154
  # let's compute the sum of non-used numbers
@@ -161,9 +161,14 @@ def golomb_consistency_algorithm(
161
161
  for i in range(ni_var_idx - 1, mark_nb - 1):
162
162
  for j in range(i + 1, mark_nb):
163
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
164
+ shr_domains_stack[stacks_top[0], dom_idx, MIN] = minimal_sum[j - i] # no offset
165
165
  add_propagators(
166
- triggered_propagators, not_entailed_propagators_stack[0], shr_domains_propagators, dom_idx, MIN
166
+ triggered_propagators,
167
+ not_entailed_propagators_stack,
168
+ stacks_top,
169
+ shr_domains_propagators,
170
+ dom_idx,
171
+ MIN,
167
172
  )
168
173
  return bound_consistency_algorithm(
169
174
  statistics,
@@ -179,7 +184,7 @@ def golomb_consistency_algorithm(
179
184
  shr_domains_stack,
180
185
  not_entailed_propagators_stack,
181
186
  dom_update_stack,
182
- stacks_height,
187
+ stacks_top,
183
188
  triggered_propagators,
184
189
  compute_domains_addrs,
185
190
  )
@@ -32,17 +32,27 @@ if __name__ == "__main__":
32
32
  parser.add_argument("--ff", type=bool, action=argparse.BooleanOptionalAction, default=False)
33
33
  args = parser.parse_args()
34
34
  problem = QueensProblem(args.n)
35
- problems = problem.split(args.processors, 0)
36
- solver = MultiprocessingSolver(
37
- [
38
- BacktrackSolver(
39
- problem,
40
- consistency_alg_idx=CONSISTENCY_ALG_SHAVING if args.shaving else CONSISTENCY_ALG_BC,
41
- var_heuristic_idx=VAR_HEURISTIC_SMALLEST_DOMAIN if args.ff else VAR_HEURISTIC_FIRST_NOT_INSTANTIATED,
42
- log_level=args.log_level,
43
- )
44
- for problem in problems
45
- ]
35
+ solver = (
36
+ MultiprocessingSolver(
37
+ [
38
+ BacktrackSolver(
39
+ problem,
40
+ consistency_alg_idx=CONSISTENCY_ALG_SHAVING if args.shaving else CONSISTENCY_ALG_BC,
41
+ var_heuristic_idx=(
42
+ VAR_HEURISTIC_SMALLEST_DOMAIN if args.ff else VAR_HEURISTIC_FIRST_NOT_INSTANTIATED
43
+ ),
44
+ log_level=args.log_level,
45
+ )
46
+ for problem in problem.split(args.processors, 0)
47
+ ]
48
+ )
49
+ if args.processors > 1
50
+ else BacktrackSolver(
51
+ problem,
52
+ consistency_alg_idx=CONSISTENCY_ALG_SHAVING if args.shaving else CONSISTENCY_ALG_BC,
53
+ var_heuristic_idx=VAR_HEURISTIC_SMALLEST_DOMAIN if args.ff else VAR_HEURISTIC_FIRST_NOT_INSTANTIATED,
54
+ log_level=args.log_level,
55
+ )
46
56
  )
47
57
  solver.solve_all()
48
58
  print(solver.get_statistics())
@@ -141,13 +141,15 @@ def pop_propagator(triggered_propagators: NDArray, previous_prop_idx: int) -> in
141
141
  @njit(cache=True)
142
142
  def add_propagators(
143
143
  triggered_propagators: NDArray,
144
- not_entailed_propagators: NDArray,
144
+ not_entailed_propagators_stack: NDArray,
145
+ stacks_top: NDArray,
145
146
  shr_domains_propagators: NDArray,
146
147
  dom_idx: int,
147
148
  bound: int,
148
149
  ) -> None:
150
+ cp_top_idx = stacks_top[0]
149
151
  for prop_idx in range(len(triggered_propagators)):
150
- if shr_domains_propagators[dom_idx, bound, prop_idx] and not_entailed_propagators[prop_idx]:
152
+ if shr_domains_propagators[dom_idx, bound, prop_idx] and not_entailed_propagators_stack[cp_top_idx, prop_idx]:
151
153
  triggered_propagators[prop_idx] = True
152
154
  # np.logical_or(triggered_propagators, shr_domains_propagators[dom_idx, bound], triggered_propagators)
153
155
  # np.logical_and(triggered_propagators, not_entailed_propagators, triggered_propagators)
@@ -19,7 +19,6 @@ from numba import njit # type: ignore
19
19
  from numpy.typing import NDArray
20
20
 
21
21
  from nucs.constants import (
22
- LOG_FORMAT,
23
22
  LOG_LEVEL_INFO,
24
23
  NUMBA_DISABLE_JIT,
25
24
  PROBLEM_BOUND,
@@ -96,10 +95,9 @@ class BacktrackSolver(Solver):
96
95
  :param var_heuristic_idx: the index of the heuristic for selecting a variable/domain
97
96
  :param dom_heuristic_idx: the index of the heuristic for reducing a domain
98
97
  :param stack_max_height: the maximal choice point stack height
98
+ :param log_level: the log level as a string
99
99
  """
100
- logging.basicConfig(format=LOG_FORMAT, level=getattr(logging, log_level))
101
- logger.debug("Initializing BacktrackSolver")
102
- super().__init__(problem)
100
+ super().__init__(problem, log_level)
103
101
  logger.info(f"BacktrackSolver uses variable heuristic {var_heuristic_idx}")
104
102
  self.var_heuristic_idx = var_heuristic_idx
105
103
  logger.info(f"BacktrackSolver uses domain heuristic {dom_heuristic_idx}")
@@ -111,13 +109,13 @@ class BacktrackSolver(Solver):
111
109
  self.shr_domains_stack = np.empty((stack_max_height, self.problem.variable_nb, 2), dtype=np.int32)
112
110
  self.not_entailed_propagators_stack = np.empty((stack_max_height, self.problem.propagator_nb), dtype=np.bool)
113
111
  self.dom_update_stack = np.empty((stack_max_height, 2), dtype=np.uint16)
114
- self.stacks_height = np.ones((1,), dtype=np.uint8)
112
+ self.stacks_top = np.ones((1,), dtype=np.uint8)
115
113
  logger.info(f"Choice points stack has a maximal height of {stack_max_height}")
116
114
  cp_init(
117
115
  self.shr_domains_stack,
118
116
  self.not_entailed_propagators_stack,
119
117
  self.dom_update_stack,
120
- self.stacks_height,
118
+ self.stacks_top,
121
119
  np.array(problem.shr_domains_lst),
122
120
  )
123
121
  logger.debug("Choice points initialized")
@@ -168,6 +166,7 @@ class BacktrackSolver(Solver):
168
166
  :param update_domain_fct: the function to update the domain of the variable
169
167
  :return: the solution if it exists or None
170
168
  """
169
+ logger.debug(f"Optimizing variable {variable_idx}")
171
170
  compute_domains_addrs, var_heuristic_addrs, dom_heuristic_addrs, consistency_alg_addrs = (
172
171
  get_function_addresses()
173
172
  )
@@ -187,7 +186,7 @@ class BacktrackSolver(Solver):
187
186
  self.shr_domains_stack,
188
187
  self.not_entailed_propagators_stack,
189
188
  self.dom_update_stack,
190
- self.stacks_height,
189
+ self.stacks_top,
191
190
  self.triggered_propagators,
192
191
  self.consistency_alg_idx,
193
192
  self.var_heuristic_idx,
@@ -205,11 +204,12 @@ class BacktrackSolver(Solver):
205
204
  self.shr_domains_stack,
206
205
  self.not_entailed_propagators_stack,
207
206
  self.dom_update_stack,
208
- self.stacks_height,
207
+ self.stacks_top,
209
208
  self.triggered_propagators,
210
209
  )
211
210
  update_domain_fct(
212
- self.shr_domains_stack[0],
211
+ self.shr_domains_stack,
212
+ self.stacks_top,
213
213
  self.problem.dom_indices_arr,
214
214
  self.problem.dom_offsets_arr,
215
215
  variable_idx,
@@ -222,6 +222,7 @@ class BacktrackSolver(Solver):
222
222
  Returns an iterator over the solutions.
223
223
  :return: an iterator
224
224
  """
225
+ logger.info("Solving and iterating over the solutions")
225
226
  compute_domains_addrs, var_heuristic_addrs, dom_heuristic_addrs, consistency_alg_addrs = (
226
227
  get_function_addresses()
227
228
  )
@@ -240,7 +241,7 @@ class BacktrackSolver(Solver):
240
241
  self.shr_domains_stack,
241
242
  self.not_entailed_propagators_stack,
242
243
  self.dom_update_stack,
243
- self.stacks_height,
244
+ self.stacks_top,
244
245
  self.triggered_propagators,
245
246
  self.consistency_alg_idx,
246
247
  self.var_heuristic_idx,
@@ -255,10 +256,9 @@ class BacktrackSolver(Solver):
255
256
  yield solution
256
257
  if not backtrack(
257
258
  self.statistics,
258
- self.shr_domains_stack,
259
259
  self.not_entailed_propagators_stack,
260
260
  self.dom_update_stack,
261
- self.stacks_height,
261
+ self.stacks_top,
262
262
  self.triggered_propagators,
263
263
  self.problem.shr_domains_propagators,
264
264
  ):
@@ -291,8 +291,10 @@ class BacktrackSolver(Solver):
291
291
  Enqueues the solution that optimizes a variable.
292
292
  :param variable_idx: the index of the variable
293
293
  :param update_domain_fct: the function to update the domain of the variable
294
+ :param processor_idx: the index of the processor
294
295
  :param solution_queue: the solution queue
295
296
  """
297
+ logger.debug(f"Optimizing variable {variable_idx} and queuing solutions found")
296
298
  compute_domains_addrs, var_heuristic_addrs, dom_heuristic_addrs, consistency_alg_addrs = (
297
299
  get_function_addresses()
298
300
  )
@@ -311,7 +313,7 @@ class BacktrackSolver(Solver):
311
313
  self.shr_domains_stack,
312
314
  self.not_entailed_propagators_stack,
313
315
  self.dom_update_stack,
314
- self.stacks_height,
316
+ self.stacks_top,
315
317
  self.triggered_propagators,
316
318
  self.consistency_alg_idx,
317
319
  self.var_heuristic_idx,
@@ -330,11 +332,12 @@ class BacktrackSolver(Solver):
330
332
  self.shr_domains_stack,
331
333
  self.not_entailed_propagators_stack,
332
334
  self.dom_update_stack,
333
- self.stacks_height,
335
+ self.stacks_top,
334
336
  self.triggered_propagators,
335
337
  )
336
338
  update_domain_fct(
337
- self.shr_domains_stack[0],
339
+ self.shr_domains_stack,
340
+ self.stacks_top,
338
341
  self.problem.dom_indices_arr,
339
342
  self.problem.dom_offsets_arr,
340
343
  variable_idx,
@@ -348,6 +351,7 @@ class BacktrackSolver(Solver):
348
351
  :param processor_idx: the index of the processor
349
352
  :param solution_queue: the solution queue
350
353
  """
354
+ logger.info("Solving and queuing solutions found")
351
355
  compute_domains_addrs, var_heuristic_addrs, dom_heuristic_addrs, consistency_alg_addrs = (
352
356
  get_function_addresses()
353
357
  )
@@ -366,7 +370,7 @@ class BacktrackSolver(Solver):
366
370
  self.shr_domains_stack,
367
371
  self.not_entailed_propagators_stack,
368
372
  self.dom_update_stack,
369
- self.stacks_height,
373
+ self.stacks_top,
370
374
  self.triggered_propagators,
371
375
  self.consistency_alg_idx,
372
376
  self.var_heuristic_idx,
@@ -381,10 +385,9 @@ class BacktrackSolver(Solver):
381
385
  solution_queue.put((processor_idx, solution, self.statistics))
382
386
  if not backtrack(
383
387
  self.statistics,
384
- self.shr_domains_stack,
385
388
  self.not_entailed_propagators_stack,
386
389
  self.dom_update_stack,
387
- self.stacks_height,
390
+ self.stacks_top,
388
391
  self.triggered_propagators,
389
392
  self.problem.shr_domains_propagators,
390
393
  ):
@@ -397,7 +400,7 @@ def reset(
397
400
  shr_domains_stack: NDArray,
398
401
  not_entailed_propagators_stack: NDArray,
399
402
  dom_update_stack: NDArray,
400
- stacks_height: NDArray,
403
+ stacks_top: NDArray,
401
404
  triggered_propagators: NDArray,
402
405
  ) -> None:
403
406
  """
@@ -405,14 +408,15 @@ def reset(
405
408
  :param problem: the problem
406
409
  :param shr_domains_stack: the stack of shared domains
407
410
  :param not_entailed_propagators_stack: the stack of not entailed propagators
408
- :param stacks_height: the height of both stacks
411
+ :param dom_update_stack: the stack of domain updates
412
+ :param stacks_top: the index of the top of the stacks as a Numpy array
409
413
  :param triggered_propagators: the list of triggered propagators
410
414
  """
411
415
  cp_init(
412
416
  shr_domains_stack,
413
417
  not_entailed_propagators_stack,
414
418
  dom_update_stack,
415
- stacks_height,
419
+ stacks_top,
416
420
  np.array(problem.shr_domains_lst),
417
421
  )
418
422
  triggered_propagators.fill(True)
@@ -433,7 +437,7 @@ def solve_one(
433
437
  shr_domains_stack: NDArray,
434
438
  not_entailed_propagators_stack: NDArray,
435
439
  dom_update_stack: NDArray,
436
- stacks_height: NDArray,
440
+ stacks_top: NDArray,
437
441
  triggered_propagators: NDArray,
438
442
  consistency_alg_idx: int,
439
443
  var_heuristic_idx: int,
@@ -461,7 +465,8 @@ def solve_one(
461
465
  the first level correspond to the current shared domains, the rest correspond to the choice points
462
466
  :param not_entailed_propagators_stack: a stack not entailed propagators;
463
467
  the first level correspond to the propagators currently not entailed, the rest correspond to the choice points
464
- :param stacks_height: the height of the stacks as a Numpy array
468
+ :param dom_update_stack: the stack of domain updates
469
+ :param stacks_top: the index of the top of the stacks as a Numpy array
465
470
  :param triggered_propagators: the Numpy array of triggered propagators
466
471
  :param consistency_alg_idx: the index of the consistency algorithm
467
472
  :param var_heuristic_idx: the index of the variable heuristic
@@ -495,35 +500,33 @@ def solve_one(
495
500
  shr_domains_stack,
496
501
  not_entailed_propagators_stack,
497
502
  dom_update_stack,
498
- stacks_height,
503
+ stacks_top,
499
504
  triggered_propagators,
500
505
  compute_domains_addrs,
501
506
  )
502
507
  if status == PROBLEM_BOUND:
503
508
  statistics[STATS_IDX_SOLVER_SOLUTION_NB] += 1
504
- return get_solution(shr_domains_stack[0], dom_indices_arr, dom_offsets_arr)
509
+ return get_solution(shr_domains_stack, stacks_top, dom_indices_arr, dom_offsets_arr)
505
510
  elif status == PROBLEM_UNBOUND:
506
- dom_idx = var_heuristic_fct(shr_domains_stack[0])
507
- cp_put(shr_domains_stack, not_entailed_propagators_stack, dom_update_stack, stacks_height, dom_idx)
508
- cp_idx = stacks_height[0] - 1
509
- event = dom_heuristic_fct(shr_domains_stack[0, dom_idx], shr_domains_stack[cp_idx, dom_idx])
510
- dom_update_stack[cp_idx, 1] = event
511
+ dom_idx = var_heuristic_fct(shr_domains_stack, stacks_top)
512
+ cp_put(shr_domains_stack, not_entailed_propagators_stack, dom_update_stack, stacks_top, dom_idx)
513
+ event = dom_heuristic_fct(shr_domains_stack, dom_update_stack, stacks_top, dom_idx)
511
514
  add_propagators(
512
515
  triggered_propagators,
513
- not_entailed_propagators_stack[0],
516
+ not_entailed_propagators_stack,
517
+ stacks_top,
514
518
  shr_domains_propagators,
515
519
  dom_idx,
516
520
  event,
517
521
  )
518
522
  statistics[STATS_IDX_SOLVER_CHOICE_NB] += 1
519
- if stacks_height[0] - 1 > statistics[STATS_IDX_SOLVER_CHOICE_DEPTH]:
520
- statistics[STATS_IDX_SOLVER_CHOICE_DEPTH] = stacks_height[0] - 1
523
+ if stacks_top[0] > statistics[STATS_IDX_SOLVER_CHOICE_DEPTH]:
524
+ statistics[STATS_IDX_SOLVER_CHOICE_DEPTH] = stacks_top[0]
521
525
  elif not backtrack(
522
526
  statistics,
523
- shr_domains_stack,
524
527
  not_entailed_propagators_stack,
525
528
  dom_update_stack,
526
- stacks_height,
529
+ stacks_top,
527
530
  triggered_propagators,
528
531
  shr_domains_propagators,
529
532
  ):
@@ -10,7 +10,6 @@
10
10
  #
11
11
  # Copyright 2024 - Yan Georget
12
12
  ###############################################################################
13
- import numpy as np
14
13
  from numba import njit # type: ignore
15
14
  from numpy.typing import NDArray
16
15
 
@@ -52,7 +51,7 @@ def bound_consistency_algorithm(
52
51
  shr_domains_stack: NDArray,
53
52
  not_entailed_propagators_stack: NDArray,
54
53
  dom_update_stack: NDArray,
55
- stacks_height: NDArray,
54
+ stacks_top: NDArray,
56
55
  triggered_propagators: NDArray,
57
56
  compute_domains_addrs: NDArray,
58
57
  ) -> int:
@@ -74,7 +73,8 @@ def bound_consistency_algorithm(
74
73
  the first level correspond to the current shared domains, the rest correspond to the choice points
75
74
  :param not_entailed_propagators_stack: a stack not entailed propagators;
76
75
  the first level correspond to the propagators currently not entailed, the rest correspond to the choice points
77
- :param stacks_height: the height of the stacks as a Numpy array
76
+ :param dom_update_stack: the stack of domain updates
77
+ :param stacks_top: the height of the stacks as a Numpy array
78
78
  :param triggered_propagators: the Numpy array of triggered propagators
79
79
  :param compute_domains_addrs: the addresses of the compute_domains functions
80
80
  :return: a status (consistency, inconsistency or entailment) as an integer
@@ -84,41 +84,41 @@ def bound_consistency_algorithm(
84
84
  while True:
85
85
  prop_idx = pop_propagator(triggered_propagators, prop_idx)
86
86
  if prop_idx == -1:
87
- return PROBLEM_BOUND if is_solved(shr_domains_stack[0]) else PROBLEM_UNBOUND
87
+ return PROBLEM_BOUND if is_solved(shr_domains_stack, stacks_top) else PROBLEM_UNBOUND
88
88
  statistics[STATS_IDX_PROPAGATOR_FILTER_NB] += 1
89
89
  prop_var_start = var_bounds[prop_idx, START]
90
90
  prop_var_end = var_bounds[prop_idx, END]
91
91
  prop_indices = props_dom_indices[prop_var_start:prop_var_end]
92
92
  prop_offsets = props_dom_offsets[prop_var_start:prop_var_end]
93
- prop_var_nb = prop_var_end - prop_var_start
94
- prop_domains = np.empty((prop_var_nb, 2), dtype=np.int32)
95
- np.add(shr_domains_stack[0, prop_indices], prop_offsets, prop_domains)
96
- compute_domains_function = (
93
+ prop_domains = shr_domains_stack[stacks_top[0], prop_indices] + prop_offsets
94
+ compute_domains_fct = (
97
95
  COMPUTE_DOMAINS_FCTS[algorithms[prop_idx]]
98
96
  if NUMBA_DISABLE_JIT
99
97
  else function_from_address(TYPE_COMPUTE_DOMAINS, compute_domains_addrs[algorithms[prop_idx]])
100
98
  )
101
- status = compute_domains_function(
99
+ status = compute_domains_fct(
102
100
  prop_domains, props_parameters[param_bounds[prop_idx, START] : param_bounds[prop_idx, END]]
103
101
  )
104
102
  if status == PROP_INCONSISTENCY:
105
103
  statistics[STATS_IDX_PROPAGATOR_INCONSISTENCY_NB] += 1
106
104
  return PROBLEM_INCONSISTENT
107
105
  if status == PROP_ENTAILMENT:
108
- not_entailed_propagators_stack[0, prop_idx] = False
106
+ not_entailed_propagators_stack[stacks_top[0], prop_idx] = False
109
107
  statistics[STATS_IDX_PROPAGATOR_ENTAILMENT_NB] += 1
110
108
  shr_domains_changes = False
111
- for var_idx in range(prop_var_nb):
109
+ top = stacks_top[0]
110
+ for var_idx in range(prop_var_end - prop_var_start):
112
111
  shr_domain_idx = prop_indices[var_idx]
113
112
  prop_offset = prop_offsets[var_idx, 0] # because of vertical shape
114
113
  for bound in [MIN, MAX]:
115
114
  shr_domain_bound = prop_domains[var_idx, bound] - prop_offset
116
- if shr_domains_stack[0, shr_domain_idx, bound] != shr_domain_bound:
117
- shr_domains_stack[0, shr_domain_idx, bound] = shr_domain_bound
115
+ if shr_domains_stack[top, shr_domain_idx, bound] != shr_domain_bound:
116
+ shr_domains_stack[top, shr_domain_idx, bound] = shr_domain_bound
118
117
  shr_domains_changes = True
119
118
  add_propagators(
120
119
  triggered_propagators,
121
- not_entailed_propagators_stack[0],
120
+ not_entailed_propagators_stack,
121
+ stacks_top,
122
122
  shr_domains_propagators,
123
123
  shr_domain_idx,
124
124
  bound,
@@ -0,0 +1,88 @@
1
+ from typing import Optional
2
+
3
+ from numba import njit # type: ignore
4
+ from numpy.typing import NDArray
5
+
6
+ from nucs.constants import STATS_IDX_SOLVER_BACKTRACK_NB
7
+ from nucs.propagators.propagators import add_propagators
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_top: NDArray,
16
+ shr_domains_arr: NDArray,
17
+ ) -> None:
18
+ """
19
+ Inits the choice points.
20
+ :param shr_domains_stack: the stack of shared domains
21
+ :param not_entailed_propagators_stack: the stack of not entailed propagators
22
+ :param stacks_top: the index of the top of the stacks as a Numpy array
23
+ :param shr_domains_arr: the shared domains
24
+ :return: a Numpy array
25
+ """
26
+ shr_domains_stack[0] = shr_domains_arr
27
+ not_entailed_propagators_stack[0] = True
28
+ dom_update_stack[0, :] = 0
29
+ stacks_top[0] = 0
30
+
31
+
32
+ @njit(cache=True)
33
+ def cp_put(
34
+ shr_domains_stack: NDArray,
35
+ not_entailed_propagators_stack: NDArray,
36
+ dom_update_stack: NDArray,
37
+ stacks_top: NDArray,
38
+ dom_index: int,
39
+ bound: Optional[int] = 0,
40
+ ) -> None:
41
+ """
42
+ Adds a choice point to the stack of choice points.
43
+ :param shr_domains_stack: the stack of shared domains
44
+ :param not_entailed_propagators_stack: the stack of not entailed propagators
45
+ :param dom_update_stack: the stack of domain updates
46
+ :param stacks_top: the index of the top of the stacks as a Numpy array
47
+ :param dom_index: the index of the shared domain which is changed
48
+ :param bound: the bound (MIN or MAX) which is changed
49
+ """
50
+ dom_update_stack[stacks_top[0], 0] = dom_index
51
+ dom_update_stack[stacks_top[0], 1] = bound
52
+ shr_domains_stack[stacks_top[0] + 1, :, :] = shr_domains_stack[stacks_top[0], :, :]
53
+ not_entailed_propagators_stack[stacks_top[0] + 1, :] = not_entailed_propagators_stack[stacks_top[0], :]
54
+ stacks_top[0] += 1
55
+
56
+
57
+ @njit(cache=True)
58
+ def backtrack(
59
+ statistics: NDArray,
60
+ not_entailed_propagators_stack: NDArray,
61
+ dom_update_stack: NDArray,
62
+ stacks_top: 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 not_entailed_propagators_stack: the stack of not entailed propagators
70
+ :param dom_update_stack: the stack of domain updates
71
+ :param stacks_top: the index of the top of the stacks as a Numpy array
72
+ :param triggered_propagators: the set propagators that are currently triggered as a Numpy array
73
+ :param shr_domains_propagators: a Numpy array indicating which propagators to trigger for a domain change
74
+ :return: true iff it is possible to backtrack
75
+ """
76
+ if stacks_top[0] == 0:
77
+ return False
78
+ stacks_top[0] -= 1
79
+ statistics[STATS_IDX_SOLVER_BACKTRACK_NB] += 1
80
+ add_propagators(
81
+ triggered_propagators,
82
+ not_entailed_propagators_stack,
83
+ stacks_top,
84
+ shr_domains_propagators,
85
+ dom_update_stack[stacks_top[0], 0],
86
+ 1 - dom_update_stack[stacks_top[0], 1],
87
+ )
88
+ return True