NUCS 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- NUCS-0.1.dist-info/LICENSE.md +21 -0
- NUCS-0.1.dist-info/METADATA +78 -0
- NUCS-0.1.dist-info/RECORD +35 -0
- NUCS-0.1.dist-info/WHEEL +5 -0
- NUCS-0.1.dist-info/top_level.txt +1 -0
- nucs/__init__.py +0 -0
- nucs/heuristics/heuristic.py +24 -0
- nucs/heuristics/variable_heuristic.py +97 -0
- nucs/memory.py +73 -0
- nucs/problems/__init__.py +0 -0
- nucs/problems/alpha_problem.py +107 -0
- nucs/problems/bibd_problem.py +49 -0
- nucs/problems/golomb_problem.py +76 -0
- nucs/problems/problem.py +271 -0
- nucs/problems/queens_problem.py +22 -0
- nucs/problems/schur_lemma_problem.py +29 -0
- nucs/problems/sudoku_problem.py +46 -0
- nucs/propagators/__init__.py +0 -0
- nucs/propagators/affine_eq_propagator.py +55 -0
- nucs/propagators/affine_geq_propagator.py +52 -0
- nucs/propagators/affine_leq_propagator.py +52 -0
- nucs/propagators/alldifferent_propagator.py +180 -0
- nucs/propagators/count_eq_propagator.py +47 -0
- nucs/propagators/dummy_propagator.py +21 -0
- nucs/propagators/exactly_eq_propagator.py +44 -0
- nucs/propagators/lexicographic_leq_propagator.py +139 -0
- nucs/propagators/max_eq_propagator.py +36 -0
- nucs/propagators/max_leq_propagator.py +46 -0
- nucs/propagators/min_eq_propagator.py +36 -0
- nucs/propagators/min_geq_propagator.py +46 -0
- nucs/propagators/propagators.py +135 -0
- nucs/solvers/__init__.py +0 -0
- nucs/solvers/backtrack_solver.py +105 -0
- nucs/solvers/solver.py +41 -0
- nucs/statistics.py +44 -0
nucs/problems/problem.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
from typing import Tuple, Union
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from numba import njit # type: ignore
|
|
5
|
+
from numba.typed import List
|
|
6
|
+
from numpy.typing import NDArray
|
|
7
|
+
|
|
8
|
+
from nucs.memory import (
|
|
9
|
+
END,
|
|
10
|
+
MAX,
|
|
11
|
+
MIN,
|
|
12
|
+
PROP_ENTAILMENT,
|
|
13
|
+
PROP_INCONSISTENCY,
|
|
14
|
+
START,
|
|
15
|
+
new_algorithms,
|
|
16
|
+
new_bounds,
|
|
17
|
+
new_data,
|
|
18
|
+
new_domain_offsets_by_values,
|
|
19
|
+
new_domains_by_values,
|
|
20
|
+
new_entailed_propagators,
|
|
21
|
+
new_indices,
|
|
22
|
+
new_indices_by_values,
|
|
23
|
+
new_offsets,
|
|
24
|
+
new_triggered_propagators,
|
|
25
|
+
new_triggers,
|
|
26
|
+
)
|
|
27
|
+
from nucs.propagators.propagators import (
|
|
28
|
+
compute_domains,
|
|
29
|
+
get_triggers,
|
|
30
|
+
init_triggered_propagators,
|
|
31
|
+
update_triggered_propagators,
|
|
32
|
+
)
|
|
33
|
+
from nucs.statistics import (
|
|
34
|
+
STATS_PROBLEM_FILTER_NB,
|
|
35
|
+
STATS_PROPAGATOR_ENTAILMENT_NB,
|
|
36
|
+
STATS_PROPAGATOR_FILTER_NB,
|
|
37
|
+
STATS_PROPAGATOR_FILTER_NO_CHANGE_NB,
|
|
38
|
+
STATS_PROPAGATOR_INCONSISTENCY_NB,
|
|
39
|
+
statistics_init,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Problem:
|
|
44
|
+
"""
|
|
45
|
+
A problem is conceptually defined by a list of domains, a list of variables and a list of propagators.
|
|
46
|
+
A domain can be computed by applying an offset to a shared domain.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, shr_domains: List[Union[int, Tuple[int, int]]], dom_indices: List[int], dom_offsets: List[int]):
|
|
50
|
+
"""
|
|
51
|
+
Inits the problem.
|
|
52
|
+
:param shr_domains: the shared domains expressed as a list
|
|
53
|
+
:param dom_indices: the domain indices expressed as a list
|
|
54
|
+
:param dom_offsets: the domain offsets expressed as a list
|
|
55
|
+
"""
|
|
56
|
+
self.variable_nb = len(dom_indices)
|
|
57
|
+
self.shr_domains_backup = shr_domains # a copy of the shared domains (as a list) used when resetting a problem
|
|
58
|
+
self.shr_domains = new_domains_by_values(shr_domains)
|
|
59
|
+
self.dom_indices = new_indices_by_values(dom_indices)
|
|
60
|
+
self.dom_offsets = new_domain_offsets_by_values(dom_offsets)
|
|
61
|
+
self.statistics = statistics_init()
|
|
62
|
+
|
|
63
|
+
def reset_shr_domains(self) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Resets the shared domains to their initial values.
|
|
66
|
+
"""
|
|
67
|
+
self.shr_domains = new_domains_by_values(self.shr_domains_backup)
|
|
68
|
+
|
|
69
|
+
def reset_entailed_propagators(self) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Marks all propagators as not entailed anymore.
|
|
72
|
+
"""
|
|
73
|
+
self.entailed_propagators[:] = False
|
|
74
|
+
|
|
75
|
+
def set_propagators(self, propagators: List[Tuple[List[int], int, List[int]]]) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Completes the initialization of the problem by setting the propagators.
|
|
78
|
+
The order of the propagators has an impact on the global speed of the resolution:
|
|
79
|
+
it is recommended to put the more costly propagators at the end of the list.
|
|
80
|
+
:param propagators: the list of propagators as tuples of the form (list of variables, algorithm, data).
|
|
81
|
+
"""
|
|
82
|
+
self.propagator_nb = len(propagators)
|
|
83
|
+
props_var_total_size = props_data_total_size = 0
|
|
84
|
+
self.triggered_propagators = new_triggered_propagators(self.propagator_nb)
|
|
85
|
+
self.entailed_propagators = new_entailed_propagators(self.propagator_nb)
|
|
86
|
+
self.algorithms = new_algorithms(self.propagator_nb)
|
|
87
|
+
# We will store propagator specific data in a global arrays, we need to compute variables and data bounds.
|
|
88
|
+
self.var_bounds = new_bounds(
|
|
89
|
+
self.propagator_nb
|
|
90
|
+
) # there is a bit of redundancy here for faster access to the bounds
|
|
91
|
+
self.data_bounds = new_bounds(
|
|
92
|
+
self.propagator_nb
|
|
93
|
+
) # there is a bit of redundancy here for faster access to the bounds
|
|
94
|
+
self.var_bounds[0, START] = self.data_bounds[0, START] = 0
|
|
95
|
+
for pidx, propagator in enumerate(propagators):
|
|
96
|
+
prop_var_size = len(propagator[0])
|
|
97
|
+
prop_data_size = len(propagator[2])
|
|
98
|
+
self.algorithms[pidx] = propagator[1]
|
|
99
|
+
if pidx > 0:
|
|
100
|
+
self.var_bounds[pidx, START] = self.var_bounds[pidx - 1, END]
|
|
101
|
+
self.data_bounds[pidx, START] = self.data_bounds[pidx - 1, END]
|
|
102
|
+
self.var_bounds[pidx, END] = self.var_bounds[pidx, START] + prop_var_size
|
|
103
|
+
self.data_bounds[pidx, END] = self.data_bounds[pidx, START] + prop_data_size
|
|
104
|
+
props_var_total_size += prop_var_size
|
|
105
|
+
props_data_total_size += prop_data_size
|
|
106
|
+
# Bounds have been computed and can now be used.
|
|
107
|
+
# The global arrays are the following:
|
|
108
|
+
self.props_dom_indices = new_indices(props_var_total_size)
|
|
109
|
+
self.props_dom_offsets = new_offsets(props_var_total_size)
|
|
110
|
+
self.props_triggers = new_triggers(props_var_total_size, False)
|
|
111
|
+
self.props_data = new_data(props_data_total_size)
|
|
112
|
+
# Let's init the global arrays.
|
|
113
|
+
for pidx, propagator in enumerate(propagators):
|
|
114
|
+
prop_vars = propagator[0]
|
|
115
|
+
self.props_dom_indices[self.var_bounds[pidx, START] : self.var_bounds[pidx, END]] = self.dom_indices[
|
|
116
|
+
prop_vars
|
|
117
|
+
] # this is cached for faster access
|
|
118
|
+
self.props_dom_offsets[self.var_bounds[pidx, START] : self.var_bounds[pidx, END]] = self.dom_offsets[
|
|
119
|
+
prop_vars
|
|
120
|
+
] # this is cached for faster access
|
|
121
|
+
self.props_data[self.data_bounds[pidx, START] : self.data_bounds[pidx, END]] = propagator[2]
|
|
122
|
+
self.props_triggers[self.var_bounds[pidx, START] : self.var_bounds[pidx, END]] = get_triggers(
|
|
123
|
+
propagator[1], len(propagator[0]), propagator[2]
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def get_values(self) -> List[int]:
|
|
127
|
+
"""
|
|
128
|
+
Gets the values for the variables (when instantiated).
|
|
129
|
+
:return: a list of integers
|
|
130
|
+
"""
|
|
131
|
+
mins = self.shr_domains[self.dom_indices, MIN] + self.dom_offsets
|
|
132
|
+
return mins.tolist()
|
|
133
|
+
|
|
134
|
+
def set_min_value(self, var_idx: int, min_value: int) -> None:
|
|
135
|
+
"""
|
|
136
|
+
Sets the minimal value of a variable.
|
|
137
|
+
:param var_idx: the index of the variable
|
|
138
|
+
:param min_value: the minimal value
|
|
139
|
+
"""
|
|
140
|
+
self.shr_domains[(self.dom_indices[var_idx]), MIN] = min_value - self.dom_offsets[var_idx]
|
|
141
|
+
|
|
142
|
+
def set_max_value(self, var_idx: int, max_value: int) -> None:
|
|
143
|
+
"""
|
|
144
|
+
Sets the maximal value of a variable.
|
|
145
|
+
:param var_idx: the index of the variable
|
|
146
|
+
:param min_value: the maximal value
|
|
147
|
+
"""
|
|
148
|
+
self.shr_domains[(self.dom_indices[var_idx]), MAX] = max_value - self.dom_offsets[var_idx]
|
|
149
|
+
|
|
150
|
+
def __str__(self) -> str:
|
|
151
|
+
# TODO: fix this
|
|
152
|
+
return f"domains={self.shr_domains}"
|
|
153
|
+
|
|
154
|
+
def filter(self, shr_domain_changes: NDArray) -> bool:
|
|
155
|
+
"""
|
|
156
|
+
Filters the problem's domains by applying the propagators until a fix point is reached.
|
|
157
|
+
:param shr_domain_changes: an array of shared domain changes
|
|
158
|
+
:return: False if the problem is not consistent
|
|
159
|
+
"""
|
|
160
|
+
return filter(
|
|
161
|
+
self.statistics,
|
|
162
|
+
self.propagator_nb,
|
|
163
|
+
self.triggered_propagators,
|
|
164
|
+
self.entailed_propagators,
|
|
165
|
+
self.algorithms,
|
|
166
|
+
self.var_bounds,
|
|
167
|
+
self.data_bounds,
|
|
168
|
+
self.props_dom_indices,
|
|
169
|
+
self.props_dom_offsets,
|
|
170
|
+
self.props_triggers,
|
|
171
|
+
self.props_data,
|
|
172
|
+
self.shr_domains,
|
|
173
|
+
shr_domain_changes,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def pretty_print_solution(self, solution: List[int]) -> None:
|
|
177
|
+
"""
|
|
178
|
+
Pretty prints a solution to the problem.
|
|
179
|
+
:param solution: a list of integers
|
|
180
|
+
"""
|
|
181
|
+
print(solution)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@njit(cache=True)
|
|
185
|
+
def pop_propagator(propagator_queue: NDArray) -> int:
|
|
186
|
+
for prop_idx in range(len(propagator_queue)):
|
|
187
|
+
if propagator_queue[prop_idx]:
|
|
188
|
+
propagator_queue[prop_idx] = False
|
|
189
|
+
return prop_idx
|
|
190
|
+
return -1
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@njit(cache=True)
|
|
194
|
+
def filter(
|
|
195
|
+
statistics: NDArray,
|
|
196
|
+
propagator_nb: int,
|
|
197
|
+
triggered_propagators: NDArray,
|
|
198
|
+
entailed_propagators: NDArray,
|
|
199
|
+
algorithms: NDArray,
|
|
200
|
+
var_bounds: NDArray,
|
|
201
|
+
data_bounds: NDArray,
|
|
202
|
+
props_indices: NDArray,
|
|
203
|
+
props_offsets: NDArray,
|
|
204
|
+
props_triggers: NDArray,
|
|
205
|
+
props_data: NDArray,
|
|
206
|
+
shr_domains: NDArray,
|
|
207
|
+
shr_domain_changes: NDArray,
|
|
208
|
+
) -> bool:
|
|
209
|
+
"""
|
|
210
|
+
Filters the problem's domains by applying the propagators until a fix point is reached.
|
|
211
|
+
:param shr_domain_changes: an array of shared domain changes
|
|
212
|
+
:return: False if the problem is not consistent
|
|
213
|
+
"""
|
|
214
|
+
shr_domains_cur = np.empty_like(shr_domains)
|
|
215
|
+
statistics[STATS_PROBLEM_FILTER_NB] += 1
|
|
216
|
+
init_triggered_propagators(
|
|
217
|
+
triggered_propagators,
|
|
218
|
+
entailed_propagators,
|
|
219
|
+
shr_domain_changes,
|
|
220
|
+
propagator_nb,
|
|
221
|
+
var_bounds,
|
|
222
|
+
props_indices,
|
|
223
|
+
props_triggers,
|
|
224
|
+
)
|
|
225
|
+
while True:
|
|
226
|
+
prop_idx = pop_propagator(triggered_propagators)
|
|
227
|
+
if prop_idx == -1:
|
|
228
|
+
return True
|
|
229
|
+
statistics[STATS_PROPAGATOR_FILTER_NB] += 1
|
|
230
|
+
prop_var_start = var_bounds[prop_idx, START]
|
|
231
|
+
prop_var_end = var_bounds[prop_idx, END]
|
|
232
|
+
prop_indices = props_indices[prop_var_start:prop_var_end]
|
|
233
|
+
prop_offsets = props_offsets[prop_var_start:prop_var_end].reshape((-1, 1))
|
|
234
|
+
prop_domains = np.empty((2, len(prop_offsets)), dtype=np.int32).T # trick for order=F
|
|
235
|
+
np.add(shr_domains[prop_indices], prop_offsets, prop_domains)
|
|
236
|
+
status = compute_domains(
|
|
237
|
+
algorithms[prop_idx],
|
|
238
|
+
prop_domains,
|
|
239
|
+
props_data[data_bounds[prop_idx, START] : data_bounds[prop_idx, END]],
|
|
240
|
+
)
|
|
241
|
+
if status == PROP_INCONSISTENCY:
|
|
242
|
+
statistics[STATS_PROPAGATOR_INCONSISTENCY_NB] += 1
|
|
243
|
+
return False
|
|
244
|
+
if status == PROP_ENTAILMENT:
|
|
245
|
+
entailed_propagators[prop_idx] = True
|
|
246
|
+
statistics[STATS_PROPAGATOR_ENTAILMENT_NB] += 1
|
|
247
|
+
shr_domains_cur[:, :] = shr_domains
|
|
248
|
+
shr_domains[prop_indices] = prop_domains - prop_offsets
|
|
249
|
+
shr_domain_changes[:, :] = shr_domains_cur != shr_domains
|
|
250
|
+
if np.any(shr_domain_changes): # type: ignore
|
|
251
|
+
update_triggered_propagators(
|
|
252
|
+
triggered_propagators,
|
|
253
|
+
entailed_propagators,
|
|
254
|
+
shr_domain_changes,
|
|
255
|
+
propagator_nb,
|
|
256
|
+
var_bounds,
|
|
257
|
+
props_indices,
|
|
258
|
+
props_triggers,
|
|
259
|
+
prop_idx,
|
|
260
|
+
)
|
|
261
|
+
else:
|
|
262
|
+
statistics[STATS_PROPAGATOR_FILTER_NO_CHANGE_NB] += 1
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@njit(cache=True)
|
|
266
|
+
def is_solved(shr_domains: NDArray) -> bool:
|
|
267
|
+
"""
|
|
268
|
+
Returns true iff the problem is solved.
|
|
269
|
+
:return: a boolean
|
|
270
|
+
"""
|
|
271
|
+
return bool(np.all(np.equal(shr_domains[:, MIN], shr_domains[:, MAX])))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from nucs.problems.problem import Problem
|
|
2
|
+
from nucs.propagators.propagators import ALG_ALLDIFFERENT
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class QueensProblem(Problem):
|
|
6
|
+
"""
|
|
7
|
+
A simple model for the n-queens problem.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, n: int):
|
|
11
|
+
super().__init__(
|
|
12
|
+
shr_domains=[(0, n - 1)] * n,
|
|
13
|
+
dom_indices=list(range(n)) * 3,
|
|
14
|
+
dom_offsets=[0] * n + list(range(n)) + list(range(0, -n, -1)),
|
|
15
|
+
)
|
|
16
|
+
self.set_propagators(
|
|
17
|
+
[
|
|
18
|
+
(list(range(n)), ALG_ALLDIFFERENT, []),
|
|
19
|
+
(list(range(n, 2 * n)), ALG_ALLDIFFERENT, []),
|
|
20
|
+
(list(range(2 * n, 3 * n)), ALG_ALLDIFFERENT, []),
|
|
21
|
+
]
|
|
22
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from nucs.problems.problem import Problem
|
|
2
|
+
from nucs.propagators.propagators import (
|
|
3
|
+
ALG_AFFINE_LEQ,
|
|
4
|
+
ALG_EXACTLY_EQ,
|
|
5
|
+
ALG_LEXICOGRAPHIC_LEQ,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SchurLemmaProblem(Problem):
|
|
10
|
+
"""
|
|
11
|
+
See https://www.csplib.org/Problems/prob015/.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, n: int) -> None:
|
|
15
|
+
super().__init__(shr_domains=[(0, 1)] * n * 3, dom_indices=list(range(n * 3)), dom_offsets=[0] * n * 3)
|
|
16
|
+
propagators = []
|
|
17
|
+
for x in range(n):
|
|
18
|
+
propagators.append(([x * 3, x * 3 + 1, x * 3 + 2], ALG_EXACTLY_EQ, [1, 1]))
|
|
19
|
+
for x in range(n):
|
|
20
|
+
for y in range(n):
|
|
21
|
+
z = (x + 1) + (y + 1) - 1
|
|
22
|
+
if 0 <= z < n:
|
|
23
|
+
for k in range(3):
|
|
24
|
+
propagators.append(([3 * x + k, 3 * y + k, 3 * z + k], ALG_AFFINE_LEQ, [1, 1, 1, 2]))
|
|
25
|
+
# breaking symmetries
|
|
26
|
+
propagators.append(
|
|
27
|
+
(list(range(0, n * 3, 3)) + list(range(1, n * 3, 3)) + list(range(2, n * 3, 3)), ALG_LEXICOGRAPHIC_LEQ, [])
|
|
28
|
+
)
|
|
29
|
+
self.set_propagators(propagators)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from typing import List, Tuple
|
|
2
|
+
|
|
3
|
+
from nucs.problems.problem import Problem
|
|
4
|
+
from nucs.propagators.propagators import ALG_ALLDIFFERENT
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SudokuProblem(Problem):
|
|
8
|
+
"""
|
|
9
|
+
A simple model for the sudoku problem.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, givens: List[List[int]]):
|
|
13
|
+
super().__init__(
|
|
14
|
+
shr_domains=[(1, 9) if given == 0 else (given, given) for line in givens for given in line],
|
|
15
|
+
dom_indices=list(range(81)),
|
|
16
|
+
dom_offsets=[0] * 81,
|
|
17
|
+
)
|
|
18
|
+
propagators: List[Tuple[List[int], int, List[int]]] = []
|
|
19
|
+
for i in range(9):
|
|
20
|
+
propagators.append((list(range(0 + i * 9, 9 + i * 9)), ALG_ALLDIFFERENT, []))
|
|
21
|
+
propagators.append((list(range(0 + i, 81 + i, 9)), ALG_ALLDIFFERENT, []))
|
|
22
|
+
for i in range(3):
|
|
23
|
+
for j in range(3):
|
|
24
|
+
offset = i * 27 + j * 3
|
|
25
|
+
propagators.append(
|
|
26
|
+
(
|
|
27
|
+
[
|
|
28
|
+
0 + offset,
|
|
29
|
+
1 + offset,
|
|
30
|
+
2 + offset,
|
|
31
|
+
9 + offset,
|
|
32
|
+
10 + offset,
|
|
33
|
+
11 + offset,
|
|
34
|
+
18 + offset,
|
|
35
|
+
19 + offset,
|
|
36
|
+
20 + offset,
|
|
37
|
+
],
|
|
38
|
+
ALG_ALLDIFFERENT,
|
|
39
|
+
[],
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
self.set_propagators(propagators)
|
|
43
|
+
|
|
44
|
+
def pretty_print_solution(self, solution: List[int]) -> None:
|
|
45
|
+
for i in range(0, 81, 9):
|
|
46
|
+
print(solution[i : i + 9])
|
|
File without changes
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numba import njit # type: ignore
|
|
3
|
+
from numpy.typing import NDArray
|
|
4
|
+
|
|
5
|
+
from nucs.memory import MAX, MIN, PROP_CONSISTENCY, PROP_INCONSISTENCY, new_triggers
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_triggers(n: int, data: NDArray) -> NDArray:
|
|
9
|
+
"""
|
|
10
|
+
This propagator is triggered whenever there is a change in the domain of a variable.
|
|
11
|
+
:param n: the number of variables
|
|
12
|
+
:return: an array of triggers
|
|
13
|
+
"""
|
|
14
|
+
return new_triggers(n, True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@njit(cache=True)
|
|
18
|
+
def compute_domain_sum(n: int, domains: NDArray, data: NDArray) -> NDArray:
|
|
19
|
+
domain_sum = np.empty(2, dtype=np.int32)
|
|
20
|
+
domain_sum[:] = data[-1]
|
|
21
|
+
for i in range(n):
|
|
22
|
+
c = data[i]
|
|
23
|
+
if c > 0:
|
|
24
|
+
domain_sum[MIN] -= domains[i, MAX] * c
|
|
25
|
+
domain_sum[MAX] -= domains[i, MIN] * c
|
|
26
|
+
elif c < 0:
|
|
27
|
+
domain_sum[MIN] -= domains[i, MIN] * c
|
|
28
|
+
domain_sum[MAX] -= domains[i, MAX] * c
|
|
29
|
+
return domain_sum
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@njit("int64(int32[::1,:], int32[:])", cache=True)
|
|
33
|
+
def compute_domains(domains: NDArray, a: NDArray) -> int:
|
|
34
|
+
"""
|
|
35
|
+
Implements Sigma_i a_i * x_i = a_{n-1}.
|
|
36
|
+
:param domains: the domains of the variables
|
|
37
|
+
:param a: the parameters of the propagator
|
|
38
|
+
"""
|
|
39
|
+
n = len(domains)
|
|
40
|
+
domain_sum = compute_domain_sum(n, domains, a)
|
|
41
|
+
new_domains = np.empty_like(domains)
|
|
42
|
+
new_domains[:] = domains[:]
|
|
43
|
+
for i in range(n):
|
|
44
|
+
if a[i] > 0:
|
|
45
|
+
new_min = domains[i, MAX] - (domain_sum[MIN] // -a[i])
|
|
46
|
+
new_max = domains[i, MIN] + (domain_sum[MAX] // a[i])
|
|
47
|
+
elif a[i] < 0:
|
|
48
|
+
new_min = domains[i, MAX] - (-domain_sum[MAX] // a[i])
|
|
49
|
+
new_max = domains[i, MIN] + (-domain_sum[MIN] // -a[i])
|
|
50
|
+
new_domains[i, MIN] = max(domains[i, MIN], new_min)
|
|
51
|
+
new_domains[i, MAX] = min(domains[i, MAX], new_max)
|
|
52
|
+
if new_domains[i, MIN] > new_domains[i, MAX]:
|
|
53
|
+
return PROP_INCONSISTENCY
|
|
54
|
+
domains[:] = new_domains[:]
|
|
55
|
+
return PROP_CONSISTENCY
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numba import njit # type: ignore
|
|
3
|
+
from numpy.typing import NDArray
|
|
4
|
+
|
|
5
|
+
from nucs.memory import (
|
|
6
|
+
MAX,
|
|
7
|
+
MIN,
|
|
8
|
+
PROP_CONSISTENCY,
|
|
9
|
+
PROP_ENTAILMENT,
|
|
10
|
+
PROP_INCONSISTENCY,
|
|
11
|
+
new_triggers,
|
|
12
|
+
)
|
|
13
|
+
from nucs.propagators.affine_eq_propagator import compute_domain_sum
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_triggers(n: int, data: NDArray) -> NDArray:
|
|
17
|
+
"""
|
|
18
|
+
Returns the triggers for this propagator.
|
|
19
|
+
:param n: the number of variables
|
|
20
|
+
:return: an array of triggers
|
|
21
|
+
"""
|
|
22
|
+
triggers = new_triggers(n, False)
|
|
23
|
+
for i in range(n):
|
|
24
|
+
triggers[i, MIN] = data[i] < 0
|
|
25
|
+
triggers[i, MAX] = data[i] > 0
|
|
26
|
+
return triggers
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@njit("int64(int32[::1,:], int32[:])", cache=True)
|
|
30
|
+
def compute_domains(domains: NDArray, a: NDArray) -> int:
|
|
31
|
+
"""
|
|
32
|
+
Implements Sigma_i a_i * x_i >= a_{n-1}.
|
|
33
|
+
:param domains: the domains of the variables
|
|
34
|
+
:param a: the parameters of the propagator
|
|
35
|
+
"""
|
|
36
|
+
n = len(domains)
|
|
37
|
+
domain_sum = compute_domain_sum(n, domains, a)
|
|
38
|
+
if domain_sum[MAX] <= 0:
|
|
39
|
+
return PROP_ENTAILMENT
|
|
40
|
+
new_domains = np.empty_like(domains)
|
|
41
|
+
new_domains[:] = domains[:]
|
|
42
|
+
for i in range(n):
|
|
43
|
+
if a[i] > 0:
|
|
44
|
+
new_min = domains[i, MAX] - (domain_sum[MIN] // -a[i])
|
|
45
|
+
new_domains[i, MIN] = max(domains[i, MIN], new_min)
|
|
46
|
+
elif a[i] < 0:
|
|
47
|
+
new_max = domains[i, MIN] + (-domain_sum[MIN] // -a[i])
|
|
48
|
+
new_domains[i, MAX] = min(domains[i, MAX], new_max)
|
|
49
|
+
if new_domains[i, MIN] > new_domains[i, MAX]:
|
|
50
|
+
return PROP_INCONSISTENCY
|
|
51
|
+
domains[:] = new_domains[:]
|
|
52
|
+
return PROP_CONSISTENCY
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numba import njit # type: ignore
|
|
3
|
+
from numpy.typing import NDArray
|
|
4
|
+
|
|
5
|
+
from nucs.memory import (
|
|
6
|
+
MAX,
|
|
7
|
+
MIN,
|
|
8
|
+
PROP_CONSISTENCY,
|
|
9
|
+
PROP_ENTAILMENT,
|
|
10
|
+
PROP_INCONSISTENCY,
|
|
11
|
+
new_triggers,
|
|
12
|
+
)
|
|
13
|
+
from nucs.propagators.affine_eq_propagator import compute_domain_sum
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_triggers(n: int, data: NDArray) -> NDArray:
|
|
17
|
+
"""
|
|
18
|
+
Returns the triggers for this propagator.
|
|
19
|
+
:param n: the number of variables
|
|
20
|
+
:return: an array of triggers
|
|
21
|
+
"""
|
|
22
|
+
triggers = new_triggers(n, False)
|
|
23
|
+
for i in range(n):
|
|
24
|
+
triggers[i, MIN] = data[i] > 0
|
|
25
|
+
triggers[i, MAX] = data[i] < 0
|
|
26
|
+
return triggers
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@njit("int64(int32[::1,:], int32[:])", cache=True)
|
|
30
|
+
def compute_domains(domains: NDArray, a: NDArray) -> int:
|
|
31
|
+
"""
|
|
32
|
+
Implements Sigma_i a_i * x_i <= a_{n-1}.
|
|
33
|
+
:param domains: the domains of the variables
|
|
34
|
+
:param a: the parameters of the propagator
|
|
35
|
+
"""
|
|
36
|
+
n = len(domains)
|
|
37
|
+
domain_sum = compute_domain_sum(n, domains, a)
|
|
38
|
+
if domain_sum[MIN] >= 0:
|
|
39
|
+
return PROP_ENTAILMENT
|
|
40
|
+
new_domains = np.empty_like(domains)
|
|
41
|
+
new_domains[:] = domains[:]
|
|
42
|
+
for i in range(n):
|
|
43
|
+
if a[i] > 0:
|
|
44
|
+
new_max = domains[i, MIN] + (domain_sum[MAX] // a[i])
|
|
45
|
+
new_domains[i, MAX] = min(domains[i, MAX], new_max)
|
|
46
|
+
elif a[i] < 0:
|
|
47
|
+
new_min = domains[i, MAX] - (-domain_sum[MAX] // a[i])
|
|
48
|
+
new_domains[i, MIN] = max(domains[i, MIN], new_min)
|
|
49
|
+
if new_domains[i, MIN] > new_domains[i, MAX]:
|
|
50
|
+
return PROP_INCONSISTENCY
|
|
51
|
+
domains[:] = new_domains[:]
|
|
52
|
+
return PROP_CONSISTENCY
|