Skip to content

Commit

Permalink
simplify heuristics
Browse files Browse the repository at this point in the history
  • Loading branch information
yangeorget committed Sep 20, 2024
1 parent 7abf4e4 commit 6516c4d
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 94 deletions.
12 changes: 6 additions & 6 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,13 @@ NUCS currently provides the following propagators:
- `relation_propagator`

### Heuristics
#### Functions for selecting a variable
- `first_not_instantiated_var_heuristic`: selects the first non instantiated variable
- `last_not_instantiated_var_heuristic`: selects the last non instantiated variable
- `smallest_domain_var_heuristic`: selects the variable with the smallest domain which is not instantiated
- `greatest_domain_var_heuristic`: selects the variable with the greatest domain which is not instantiated
#### Functions for selecting a shared domain
- `first_not_instantiated_var_heuristic`: selects the first non-instantiated shared domain
- `last_not_instantiated_var_heuristic`: selects the last non-instantiated shared domain
- `smallest_domain_var_heuristic`: selects the smallest shared domain which is not instantiated
- `greatest_domain_var_heuristic`: selects the greatest shared domain which is not instantiated
-
#### Functions for selecting a value
#### Functions for reducing the chosen shared domain
- `min_value_dom_heuristic`: selects the minimal value of the domain
- `max_value_dom_heuristic`: selects the maximal value of the domain
- `split_low_dom_heuristic`: selects the first half of the domain
Expand Down
2 changes: 1 addition & 1 deletion nucs/examples/golomb_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def __init__(self, mark_nb: int) -> None:

def golomb_consistency_algorithm(statistics: NDArray, problem: GolombProblem) -> int:
# first prune the search space
ni_var_idx = first_not_instantiated_var_heuristic(problem.shr_domains_arr, problem.dom_indices_arr)
ni_var_idx = first_not_instantiated_var_heuristic(problem.shr_domains_arr) # no domains shared between vars
if 1 < ni_var_idx < problem.mark_nb - 1: # otherwise useless
problem.used_distance.fill(False)
# the following will mark at most sum(n-3) numbers as used
Expand Down
2 changes: 1 addition & 1 deletion nucs/propagators/exactly_eq_propagator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


def get_complexity_exactly_eq(n: int, data: NDArray) -> float:
return 2* n
return 2 * n


def get_triggers_exactly_eq(n: int, data: NDArray) -> NDArray:
Expand Down
25 changes: 12 additions & 13 deletions nucs/solvers/backtrack_solver.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import Callable, Iterator, List, Optional

import numpy as np

from nucs.constants import MIN, PROBLEM_INCONSISTENT, PROBLEM_SOLVED
from nucs.problems.problem import Problem
from nucs.solvers.consistency_algorithms import bound_consistency_algorithm
Expand Down Expand Up @@ -54,22 +56,19 @@ def solve_one(self) -> Optional[List[int]]:
self.statistics[STATS_SOLVER_SOLUTION_NB] += 1
values = self.problem.shr_domains_arr[self.problem.dom_indices_arr, MIN] + self.problem.dom_offsets_arr
return values.tolist()
# TODO: refactor
var_idx = self.var_heuristic(self.problem.shr_domains_arr, self.problem.dom_indices_arr)
shr_domain_idx = self.problem.dom_indices_arr[var_idx]
dom_idx = self.var_heuristic(self.problem.shr_domains_arr)
shr_domains_copy = self.problem.shr_domains_arr.copy(order="F")
self.dom_heuristic(
self.problem.shr_domains_arr,
shr_domains_copy,
shr_domain_idx,
self.choice_points.append(shr_domains_copy)
event = self.dom_heuristic(self.problem.shr_domains_arr[dom_idx], shr_domains_copy[dom_idx])
np.logical_or(
self.problem.triggered_propagators,
self.problem.shr_domains_propagators[dom_idx, event],
self.problem.triggered_propagators,
self.problem.shr_domains_propagators,
)
self.choice_points.append(shr_domains_copy)
self.statistics[STATS_SOLVER_CHOICE_NB] += 1
self.statistics[STATS_SOLVER_CHOICE_DEPTH] = max(
self.statistics[STATS_SOLVER_CHOICE_DEPTH], len(self.choice_points)
)
cp_max_depth = len(self.choice_points)
if cp_max_depth > self.statistics[STATS_SOLVER_CHOICE_DEPTH]:
self.statistics[STATS_SOLVER_CHOICE_DEPTH] = cp_max_depth

def minimize(self, variable_idx: int) -> Optional[List[int]]:
solution = None
Expand Down Expand Up @@ -97,7 +96,7 @@ def backtrack(self) -> bool:
if len(self.choice_points) == 0:
return False
self.statistics[STATS_SOLVER_BACKTRACK_NB] += 1
self.problem.reset(self.choice_points.pop())
self.problem.reset(self.choice_points.pop()) # TODO: optimize by reusing
return True

def reset(self) -> None:
Expand Down
6 changes: 3 additions & 3 deletions nucs/solvers/consistency_algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ def _bound_consistency_algorithm(
statistics[STATS_PROPAGATOR_FILTER_NB] += 1
prop_var_start = var_bounds[prop_idx, START]
prop_var_end = var_bounds[prop_idx, END]
prop_var_nb = prop_var_end - prop_var_start
prop_indices = props_indices[prop_var_start:prop_var_end]
prop_offsets = props_offsets[prop_var_start:prop_var_end]
prop_var_nb = prop_var_end - prop_var_start
prop_domains = np.empty((2, prop_var_nb), dtype=np.int32).T # trick for order=F
np.add(shr_domains[prop_indices], prop_offsets, prop_domains)
algorithm = algorithms[prop_idx]
Expand All @@ -99,13 +99,13 @@ def _bound_consistency_algorithm(
prop_offset = prop_offsets[var_idx][0]
shr_domain_min = prop_domains[var_idx, MIN] - prop_offset
shr_domain_max = prop_domains[var_idx, MAX] - prop_offset
if shr_domains[shr_domain_idx, MIN] < shr_domain_min:
if shr_domains[shr_domain_idx, MIN] != shr_domain_min:
shr_domains[shr_domain_idx, MIN] = shr_domain_min
shr_domains_changes = True
np.logical_or(
triggered_propagators, shr_domains_propagators[shr_domain_idx, MIN], triggered_propagators
)
if shr_domains[shr_domain_idx, MAX] > shr_domain_max:
if shr_domains[shr_domain_idx, MAX] != shr_domain_max:
shr_domains[shr_domain_idx, MAX] = shr_domain_max
shr_domains_changes = True
np.logical_or(
Expand Down
104 changes: 35 additions & 69 deletions nucs/solvers/heuristics.py
Original file line number Diff line number Diff line change
@@ -1,61 +1,57 @@
import sys

import numpy as np
from numba import njit # type: ignore
from numpy.typing import NDArray

from nucs.constants import MAX, MIN


@njit(cache=True)
def first_not_instantiated_var_heuristic(shr_domains: NDArray, dom_indices: NDArray) -> int:
def first_not_instantiated_var_heuristic(shr_domains: NDArray) -> int:
"""
Chooses the first non instantiated variable.
:param shr_domains: the shared domains of the problem
:param dom_indices: the domain indices of the problem variables
:return: the index of the variable
"""
for var_idx, dom_index in enumerate(dom_indices):
if shr_domains[dom_index, MIN] < shr_domains[dom_index, MAX]:
return var_idx
for dom_idx, shr_domain in enumerate(shr_domains):
if shr_domain[MIN] < shr_domain[MAX]:
return dom_idx
return -1 # cannot happen


@njit(cache=True)
def last_not_instantiated_var_heuristic(shr_domains: NDArray, dom_indices: NDArray) -> int:
def last_not_instantiated_var_heuristic(shr_domains: NDArray) -> int:
"""
Chooses the last non instantiated variable.
:param shr_domains: the shared domains of the problem
:param dom_indices: the domain indices of the problem variables
:return: the index of the variable
"""
for var_idx in range(len(dom_indices) - 1, -1, -1):
dom_index = dom_indices[var_idx]
if shr_domains[dom_index, MIN] < shr_domains[dom_index, MAX]:
return var_idx
for dom_idx in range(len(shr_domains) - 1, -1, -1):
shr_domain = shr_domains[dom_idx]
if shr_domain[MIN] < shr_domain[MAX]:
return dom_idx
return -1 # cannot happen


@njit(cache=True)
def smallest_domain_var_heuristic(shr_domains: NDArray, dom_indices: NDArray) -> int:
def smallest_domain_var_heuristic(shr_domains: NDArray) -> int:
"""
Chooses the variable with the smallest domain and which is not instantiated.
:param shr_domains: the shared domains of the problem
:param dom_indices: the domain indices of the problem variables
:return: the index of the variable
"""
min_size = sys.maxsize
min_idx = -1
for var_idx, dom_index in enumerate(dom_indices):
size = shr_domains[dom_index, MAX] - shr_domains[dom_index, MIN] # actually this is size - 1
for dom_idx, shr_domain in enumerate(shr_domains):
size = shr_domain[MAX] - shr_domain[MIN] # actually this is size - 1
if 0 < size < min_size:
min_idx = var_idx
min_idx = dom_idx
min_size = size
return min_idx


@njit(cache=True)
def greatest_domain_var_heuristic(shr_domains: NDArray, dom_indices: NDArray) -> int:
def greatest_domain_var_heuristic(shr_domains: NDArray) -> int:
"""
Chooses the variable with the greatest domain and which is not instantiated.
:param shr_domains: the shared domains of the problem
Expand All @@ -64,72 +60,42 @@ def greatest_domain_var_heuristic(shr_domains: NDArray, dom_indices: NDArray) ->
"""
max_size = 0
max_idx = -1
for var_idx, dom_index in enumerate(dom_indices):
size = shr_domains[dom_index, MAX] - shr_domains[dom_index, MIN] # actually this is size - 1
for dom_index, shr_domain in enumerate(shr_domains):
size = shr_domain[MAX] - shr_domain[MIN] # actually this is size - 1
if max_size < size:
max_idx = var_idx
max_idx = dom_index
max_size = size
return max_idx


@njit(cache=True)
def min_value_dom_heuristic(
shr_domains: NDArray,
shr_domains_copy: NDArray,
domain_idx: int,
triggered_propagators: NDArray,
shr_domains_propagators: NDArray,
) -> None:
def min_value_dom_heuristic(shr_domain: NDArray, shr_domain_copy: NDArray) -> int:
"""
Chooses the first value of the domain
:param shr_domains: the shared domains of the problem
:param: shr_domain_changes: the changes to the shared domains
:param: shr_domains_copy: the shared domains to be added to the choice point
:param domain_idx: the index of the domain
Chooses the first value of the domain.
"""
value = shr_domains[domain_idx, MIN]
shr_domains_copy[domain_idx, MIN] = value + 1
shr_domains[domain_idx, MAX] = value
np.logical_or(triggered_propagators, shr_domains_propagators[domain_idx, MAX], triggered_propagators)
value = shr_domain[MIN]
shr_domain_copy[MIN] = value + 1
shr_domain[MAX] = value
return MAX


@njit(cache=True)
def max_value_dom_heuristic(
shr_domains: NDArray,
shr_domains_copy: NDArray,
domain_idx: int,
triggered_propagators: NDArray,
shr_domains_propagators: NDArray,
) -> None:
def max_value_dom_heuristic(shr_domain: NDArray, shr_domain_copy: NDArray) -> int:
"""
Chooses the last value of the domain
:param shr_domains: the shared domains of the problem
:param: shr_domain_changes: the changes to the shared domains
:param: shr_domains_copy: the shared domains to be added to the choice point
:param domain_idx: the index of the domain
Chooses the last value of the domain.
"""
value = shr_domains[domain_idx, MAX]
shr_domains_copy[domain_idx, MAX] = value - 1
shr_domains[domain_idx, MIN] = value
np.logical_or(triggered_propagators, shr_domains_propagators[domain_idx, MIN], triggered_propagators)
value = shr_domain[MAX]
shr_domain_copy[MAX] = value - 1
shr_domain[MIN] = value
return MIN


@njit(cache=True)
def split_low_dom_heuristic(
shr_domains: NDArray,
shr_domains_copy: NDArray,
domain_idx: int,
triggered_propagators: NDArray,
shr_domains_propagators: NDArray,
) -> None:
def split_low_dom_heuristic(shr_domain: NDArray, shr_domain_copy: NDArray) -> int:
"""
Chooses the first half of the domain
:param shr_domains: the shared domains of the problem
:param: shr_domain_changes: the changes to the shared domains
:param: shr_domains_copy: the shared domains to be added to the choice point
:param domain_idx: the index of the domain
Chooses the first half of the domain.
"""
value = (shr_domains[domain_idx, MIN] + shr_domains[domain_idx, MAX]) // 2
shr_domains_copy[domain_idx, MIN] = value + 1
shr_domains[domain_idx, MAX] = value
np.logical_or(triggered_propagators, shr_domains_propagators[domain_idx, MAX], triggered_propagators)
value = (shr_domain[MIN] + shr_domain[MAX]) // 2
shr_domain_copy[MIN] = value + 1
shr_domain[MAX] = value
return MAX
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name="NUCS"
version="0.6.0"
version="0.7.0"
authors = [
{ name="Yan Georget", email="[email protected]" },
]
Expand Down

0 comments on commit 6516c4d

Please sign in to comment.