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.
@@ -0,0 +1,135 @@
1
+ from numba import njit # type: ignore
2
+ from numpy.typing import NDArray
3
+
4
+ from nucs.memory import END, MAX, MIN, START
5
+ from nucs.propagators import (
6
+ affine_eq_propagator,
7
+ affine_geq_propagator,
8
+ affine_leq_propagator,
9
+ alldifferent_propagator,
10
+ count_eq_propagator,
11
+ dummy_propagator,
12
+ exactly_eq_propagator,
13
+ lexicographic_leq_propagator,
14
+ max_eq_propagator,
15
+ max_leq_propagator,
16
+ min_eq_propagator,
17
+ min_geq_propagator,
18
+ )
19
+
20
+ # The ordinals of the algorithms for all propagators (sorted by alphabetical ordering).
21
+ ALG_AFFINE_EQ = 0
22
+ ALG_AFFINE_GEQ = 1
23
+ ALG_AFFINE_LEQ = 2
24
+ ALG_ALLDIFFERENT = 3
25
+ ALG_COUNT_EQ = 4
26
+ ALG_DUMMY = 5
27
+ ALG_EXACTLY_EQ = 6
28
+ ALG_LEXICOGRAPHIC_LEQ = 7
29
+ ALG_MAX_EQ = 8
30
+ ALG_MAX_LEQ = 9
31
+ ALG_MIN_EQ = 10
32
+ ALG_MIN_GEQ = 11
33
+
34
+ PROPAGATOR_MODULES = [
35
+ affine_eq_propagator,
36
+ affine_geq_propagator,
37
+ affine_leq_propagator,
38
+ alldifferent_propagator,
39
+ count_eq_propagator,
40
+ dummy_propagator,
41
+ exactly_eq_propagator,
42
+ lexicographic_leq_propagator,
43
+ max_eq_propagator,
44
+ max_leq_propagator,
45
+ min_eq_propagator,
46
+ min_geq_propagator,
47
+ ]
48
+
49
+
50
+ def get_triggers(algorithm: int, size: int, data: NDArray) -> NDArray:
51
+ """
52
+ Returns the triggers for a propagator.
53
+ :param algorithm: the ordinal of the algorithm
54
+ :param size: the number of variables
55
+ :return: an array of triggers
56
+ """
57
+ return PROPAGATOR_MODULES[algorithm].get_triggers(size, data)
58
+
59
+
60
+ @njit("int64(uint8, int32[::1,:], int32[:])", cache=True)
61
+ def compute_domains(algorithm: int, domains: NDArray, data: NDArray) -> int:
62
+ """
63
+ Computes the new domains for the variables.
64
+ :param algorithm: the ordinal of the algorithm
65
+ :param domains: the initial domains of the variables
66
+ :param data: the parameters of the propagator
67
+ :return: a status as an integer (INCONSISTENCY, CONSISTENCY, ENTAILMENT)
68
+ """
69
+ if algorithm == ALG_AFFINE_EQ:
70
+ return affine_eq_propagator.compute_domains(domains, data)
71
+ if algorithm == ALG_AFFINE_GEQ:
72
+ return affine_geq_propagator.compute_domains(domains, data)
73
+ if algorithm == ALG_AFFINE_LEQ:
74
+ return affine_leq_propagator.compute_domains(domains, data)
75
+ if algorithm == ALG_ALLDIFFERENT:
76
+ return alldifferent_propagator.compute_domains(domains, data)
77
+ if algorithm == ALG_COUNT_EQ:
78
+ return count_eq_propagator.compute_domains(domains, data)
79
+ if algorithm == ALG_DUMMY:
80
+ return dummy_propagator.compute_domains(domains, data)
81
+ if algorithm == ALG_EXACTLY_EQ:
82
+ return exactly_eq_propagator.compute_domains(domains, data)
83
+ if algorithm == ALG_LEXICOGRAPHIC_LEQ:
84
+ return lexicographic_leq_propagator.compute_domains(domains, data)
85
+ if algorithm == ALG_MAX_EQ:
86
+ return max_eq_propagator.compute_domains(domains, data)
87
+ if algorithm == ALG_MAX_LEQ:
88
+ return max_leq_propagator.compute_domains(domains, data)
89
+ if algorithm == ALG_MIN_EQ:
90
+ return min_eq_propagator.compute_domains(domains, data)
91
+ return min_geq_propagator.compute_domains(domains, data)
92
+
93
+
94
+ @njit(cache=True)
95
+ def init_triggered_propagators(
96
+ triggered_propagators: NDArray,
97
+ entailed_propagators: NDArray,
98
+ shr_domain_changes: NDArray,
99
+ propagator_nb: int,
100
+ prop_var_bounds: NDArray,
101
+ prop_dom_indices: NDArray,
102
+ prop_triggers: NDArray,
103
+ ) -> None:
104
+ triggered_propagators.fill(False)
105
+ for prop_idx in range(propagator_nb):
106
+ if not entailed_propagators[prop_idx]:
107
+ for var_idx in range(prop_var_bounds[prop_idx, START], prop_var_bounds[prop_idx, END]):
108
+ dom_idx = prop_dom_indices[var_idx]
109
+ if (shr_domain_changes[dom_idx, MIN] and prop_triggers[var_idx, MIN]) or (
110
+ shr_domain_changes[dom_idx, MAX] and prop_triggers[var_idx, MAX]
111
+ ):
112
+ triggered_propagators[prop_idx] = True
113
+ break
114
+
115
+
116
+ @njit(cache=True)
117
+ def update_triggered_propagators(
118
+ triggered_propagators: NDArray,
119
+ entailed_propagators: NDArray,
120
+ shr_domain_changes: NDArray,
121
+ propagator_nb: int,
122
+ prop_var_bounds: NDArray,
123
+ prop_dom_indices: NDArray,
124
+ prop_triggers: NDArray,
125
+ previous_prop_idx: int,
126
+ ) -> None:
127
+ for prop_idx in range(propagator_nb):
128
+ if not entailed_propagators[prop_idx] and prop_idx != previous_prop_idx and not triggered_propagators[prop_idx]:
129
+ for var_idx in range(prop_var_bounds[prop_idx, START], prop_var_bounds[prop_idx, END]):
130
+ dom_idx = prop_dom_indices[var_idx]
131
+ if (shr_domain_changes[dom_idx, MIN] and prop_triggers[var_idx, MIN]) or (
132
+ shr_domain_changes[dom_idx, MAX] and prop_triggers[var_idx, MAX]
133
+ ):
134
+ triggered_propagators[prop_idx] = True
135
+ break
File without changes
@@ -0,0 +1,105 @@
1
+ from typing import Iterator, List, Optional
2
+
3
+ from nucs.heuristics.heuristic import Heuristic
4
+ from nucs.heuristics.variable_heuristic import (
5
+ VariableHeuristic,
6
+ first_not_instantiated_var_heuristic,
7
+ min_value_dom_heuristic,
8
+ )
9
+ from nucs.memory import new_domain_changes
10
+ from nucs.problems.problem import Problem, is_solved
11
+ from nucs.solvers.solver import Solver
12
+ from nucs.statistics import (
13
+ STATS_OPTIMIZER_SOLUTION_NB,
14
+ STATS_SOLVER_BACKTRACK_NB,
15
+ STATS_SOLVER_CHOICE_DEPTH,
16
+ STATS_SOLVER_CHOICE_NB,
17
+ STATS_SOLVER_SOLUTION_NB,
18
+ )
19
+
20
+
21
+ class BacktrackSolver(Solver):
22
+ """
23
+ A solver relying on a backtracking mechanism.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ problem: Problem,
29
+ heuristic: Heuristic = VariableHeuristic(first_not_instantiated_var_heuristic, min_value_dom_heuristic),
30
+ ):
31
+ super().__init__(problem)
32
+ self.choice_points = [] # type: ignore
33
+ self.shr_domain_changes = new_domain_changes(len(self.problem.shr_domains))
34
+ self.heuristic = heuristic
35
+
36
+ def solve(self) -> Iterator[List[int]]:
37
+ while True:
38
+ solution = self.solve_one()
39
+ if solution is None:
40
+ break
41
+ yield solution
42
+ if not self.backtrack():
43
+ break
44
+
45
+ def solve_one(self) -> Optional[List[int]]:
46
+ """
47
+ Find at most one solution.
48
+ :return: the solution if it exists or None
49
+ """
50
+ while self.filter():
51
+ if is_solved(self.problem.shr_domains):
52
+ self.problem.statistics[STATS_SOLVER_SOLUTION_NB] += 1
53
+ return self.problem.get_values() # problem is solved
54
+ self.heuristic.choose(
55
+ self.choice_points,
56
+ self.problem.shr_domains,
57
+ self.shr_domain_changes,
58
+ self.problem.dom_indices,
59
+ )
60
+ self.problem.statistics[STATS_SOLVER_CHOICE_NB] += 1
61
+ self.problem.statistics[STATS_SOLVER_CHOICE_DEPTH] = max(
62
+ self.problem.statistics[STATS_SOLVER_CHOICE_DEPTH], len(self.choice_points)
63
+ )
64
+ return None
65
+
66
+ def minimize(self, variable_idx: int) -> Optional[List[int]]:
67
+ solution = None
68
+ while (new_solution := self.solve_one()) is not None:
69
+ solution = new_solution
70
+ self.problem.statistics[STATS_OPTIMIZER_SOLUTION_NB] += 1
71
+ self.reset()
72
+ self.problem.set_max_value(variable_idx, solution[variable_idx] - 1)
73
+ return solution
74
+
75
+ def backtrack(self) -> bool:
76
+ """
77
+ Backtracks and updates the problem's domains
78
+ :return: true iff it is possible to backtrack
79
+ """
80
+ if len(self.choice_points) == 0:
81
+ return False
82
+ self.problem.statistics[STATS_SOLVER_BACKTRACK_NB] += 1
83
+ self.shr_domain_changes[:, :] = True
84
+ self.problem.shr_domains = self.choice_points.pop()
85
+ self.problem.reset_entailed_propagators()
86
+ return True
87
+
88
+ def filter(self) -> bool:
89
+ """
90
+ Achieves bound consistency and backtracks if necessary.
91
+ :return: true iff bound consistency has been achieved
92
+ """
93
+ while not self.problem.filter(self.shr_domain_changes):
94
+ if not self.backtrack():
95
+ return False
96
+ return True
97
+
98
+ def reset(self) -> None:
99
+ """
100
+ Resets the solver by resetting the problem and the choice points.
101
+ """
102
+ self.choice_points.clear()
103
+ self.shr_domain_changes[:, :] = True
104
+ self.problem.reset_shr_domains()
105
+ self.problem.reset_entailed_propagators()
nucs/solvers/solver.py ADDED
@@ -0,0 +1,41 @@
1
+ from typing import Iterator, List, Optional
2
+
3
+ from nucs.problems.problem import Problem
4
+
5
+
6
+ class Solver:
7
+ """
8
+ A solver.
9
+ """
10
+
11
+ def __init__(self, problem: Problem):
12
+ self.problem = problem
13
+
14
+ def solve(self) -> Iterator[List[int]]: # type: ignore
15
+ """
16
+ Returns an iterator over the solutions.
17
+ :return: an iterator
18
+ """
19
+ pass
20
+
21
+ def solve_all(self) -> List[List[int]]:
22
+ """
23
+ Returns the list of all solutions.
24
+ :return: a list of list of integers
25
+ """
26
+ return [s for s in self.solve()]
27
+
28
+ def find_all(self) -> None:
29
+ """
30
+ Find all solutions.
31
+ """
32
+ for _ in self.solve():
33
+ pass
34
+
35
+ def minimize(self, var_idx: int) -> Optional[List[int]]: # type: ignore
36
+ """
37
+ Finds, if it exists, the solution to the problem that minimizes a given variable.
38
+ :param variable_idx: the index of the variable
39
+ :return: the solution if it exists or None
40
+ """
41
+ pass
nucs/statistics.py ADDED
@@ -0,0 +1,44 @@
1
+ import numpy as np
2
+ from numpy.typing import NDArray
3
+
4
+
5
+ def statistics_init() -> NDArray:
6
+ """
7
+ Inits a Numpy array for storing the statistics.
8
+ :return: a Numpy array
9
+ """
10
+ return np.array([0] * STATS_MAX, dtype=np.int32, order="C")
11
+
12
+
13
+ def statistics_print(stats: NDArray) -> None:
14
+ """
15
+ Pretty-prints an array of statistics.
16
+ :param stats: a Numpy array of statistics
17
+ """
18
+ print(
19
+ {
20
+ "OPTIMIZER_SOLUTION_NB": int(stats[STATS_OPTIMIZER_SOLUTION_NB]),
21
+ "PROBLEM_FILTERS_NB": int(stats[STATS_PROBLEM_FILTER_NB]),
22
+ "PROPAGATOR_ENTAILMENT_NB": int(stats[STATS_PROPAGATOR_ENTAILMENT_NB]),
23
+ "PROPAGATOR_FILTERS_NB": int(stats[STATS_PROPAGATOR_FILTER_NB]),
24
+ "PROPAGATOR_FILTERS_NO_CHANGE_NB": int(stats[STATS_PROPAGATOR_FILTER_NO_CHANGE_NB]),
25
+ "PROPAGATOR_INCONSISTENCY_NB": int(stats[STATS_PROPAGATOR_INCONSISTENCY_NB]),
26
+ "SOLVER_BACKTRACK_NB": int(stats[STATS_SOLVER_BACKTRACK_NB]),
27
+ "SOLVER_CHOICE_NB": int(stats[STATS_SOLVER_CHOICE_NB]),
28
+ "SOLVER_CHOICE_DEPTH": int(stats[STATS_SOLVER_CHOICE_DEPTH]),
29
+ "SOLVER_SOLUTION_NB": int(stats[STATS_SOLVER_SOLUTION_NB]),
30
+ }
31
+ )
32
+
33
+
34
+ STATS_MAX = 10
35
+ STATS_OPTIMIZER_SOLUTION_NB = 0
36
+ STATS_PROBLEM_FILTER_NB = 1
37
+ STATS_PROPAGATOR_ENTAILMENT_NB = 2
38
+ STATS_PROPAGATOR_FILTER_NB = 3
39
+ STATS_PROPAGATOR_FILTER_NO_CHANGE_NB = 4
40
+ STATS_PROPAGATOR_INCONSISTENCY_NB = 5
41
+ STATS_SOLVER_BACKTRACK_NB = 6
42
+ STATS_SOLVER_CHOICE_NB = 7
43
+ STATS_SOLVER_CHOICE_DEPTH = 8
44
+ STATS_SOLVER_SOLUTION_NB = 9