Skip to content

Commit

Permalink
add Quasigroup problems
Browse files Browse the repository at this point in the history
  • Loading branch information
Yan Georget committed Dec 27, 2024
1 parent bac8122 commit ef1395d
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 42 deletions.
2 changes: 1 addition & 1 deletion nucs/examples/quasigroup/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
parser.add_argument("--idempotent", action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--shaving", type=bool, action=argparse.BooleanOptionalAction, default=False)
parser.add_argument("--symmetry_breaking", action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--kind", type=int, choices=[3, 4, 5], default=5)
parser.add_argument("--kind", type=int, choices=[3, 4, 5, 6, 7], default=5)
args = parser.parse_args()
problem = QuasigroupProblem(args.kind, args.n, args.idempotent, args.symmetry_breaking)
solver = BacktrackSolver(
Expand Down
102 changes: 70 additions & 32 deletions nucs/examples/quasigroup/quasigroup_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,48 +27,86 @@ def __init__(self, kind: int, n: int, idempotent: bool, symmetry_breaking: bool
:param symmetry_breaking: a boolean indicating if symmetry constraints should be added to the model
"""
super().__init__(n)
if kind == 3:
# Defined by: (a∗b)∗(b*a)=a
if kind == 3: # (a∗b)∗(b*a)=a
# Equivalent to: color[color[i, j], color[j, i]] = i
# Equivalent to: column[color[i, j], i] = color[j, i] which avoids the creation of additional variables
for i in range(n):
for j in range(n):
if i != j:
self.add_propagator(
(
[*self.column(i, M_COLUMN), self.cell(i, j, M_COLOR), self.cell(j, i, M_COLOR)],
ALG_ELEMENT_LIV,
[],
)
)
elif kind == 4:
# Defined by: (b∗a)∗(a*b)=a
self.add_propagators(
[
(
[*self.column(i, M_COLUMN), self.cell(i, j, M_COLOR), self.cell(j, i, M_COLOR)],
ALG_ELEMENT_LIV,
[],
)
for i in range(n)
for j in range(n)
]
)
elif kind == 4: # (b∗a)∗(a*b) = a
# Equivalent to: color[color[j, i], color[i, j]] = i
# Equivalent to: column[color[j, i], i] = color[i, j] which avoids the creation of additional variables
self.add_propagators(
[
(
[*self.column(i, M_COLUMN), self.cell(j, i, M_COLOR), self.cell(i, j, M_COLOR)],
ALG_ELEMENT_LIV,
[],
)
for i in range(n)
for j in range(n)
]
)
elif kind == 5: # ((b∗a)∗b)∗b = a
# Equivalent to: color[color[color[j, i], j], j] = i
# Equivalent to: row[i, j] = color[color[j, i], j] which avoids the creation of additional variables
self.add_propagators(
[
(
[*self.column(j, M_COLOR), self.cell(j, i, M_COLOR), self.cell(i, j, M_ROW)],
ALG_ELEMENT_LIV,
[],
)
for i in range(n)
for j in range(n)
]
)
elif kind == 6: # (a∗b)∗b = a*(a*b)
# Equivalent to: color[color[i, j], j] = color[i, color[i, j]]
additional_vars_idx = self.add_variables([(0, n - 1)] * n**2) # additional variables
for i in range(n):
for j in range(n):
if i != j:
self.add_propagator(
(
[*self.column(i, M_COLUMN), self.cell(j, i, M_COLOR), self.cell(i, j, M_COLOR)],
ALG_ELEMENT_LIV,
[],
)
self.add_propagator(
(
[*self.column(j, M_COLOR), self.cell(i, j, M_COLOR), additional_vars_idx + i * n + j],
ALG_ELEMENT_LIV,
[],
)
elif kind == 5:
# Defined by: ((b∗a)∗b)∗b=a
# Equivalent to: color[color[color[j, i], j], j] = i
# Equivalent to: row[i, j] = color[color[j, i], j] which avoids the creation of additional variables
)
self.add_propagator(
(
[*self.row(i, M_COLOR), self.cell(i, j, M_COLOR), additional_vars_idx + i * n + j],
ALG_ELEMENT_LIV,
[],
)
)
elif kind == 7: # (b∗a)∗b = a*(b*a)
# Equivalent to: color[color[j, i], j] = color[i, color[j, i]]
additional_vars_idx = self.add_variables([(0, n - 1)] * n**2) # additional variables
for i in range(n):
for j in range(n):
if i != j:
self.add_propagator(
(
[*self.column(j, M_COLOR), self.cell(j, i, M_COLOR), self.cell(i, j, M_ROW)],
ALG_ELEMENT_LIV,
[],
)
self.add_propagator(
(
[*self.column(j, M_COLOR), self.cell(j, i, M_COLOR), additional_vars_idx + i * n + j],
ALG_ELEMENT_LIV,
[],
)
)
self.add_propagator(
(
[*self.row(i, M_COLOR), self.cell(j, i, M_COLOR), additional_vars_idx + i * n + j],
ALG_ELEMENT_LIV,
[],
)
)
if idempotent:
for model in [M_COLOR, M_ROW, M_COLUMN]:
for i in range(n):
Expand Down
6 changes: 3 additions & 3 deletions nucs/propagators/element_lic_propagator.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ def compute_domains_element_lic(domains: NDArray, parameters: NDArray) -> int:
indices: List[int] = []
for idx in range(i[MIN], i[MAX] + 1):
if c < l[idx, MIN] or c > l[idx, MAX]: # no intersection
indices.insert(0, idx)
indices.append(idx)
if idx == i[MIN]:
i[MIN] += 1
for idx in indices:
if idx != i[MAX]:
for ix in range(len(indices) - 1, -1, -1):
if indices[ix] != i[MAX]:
break
i[MAX] -= 1
if i[MAX] < i[MIN]:
Expand Down
8 changes: 4 additions & 4 deletions nucs/propagators/element_liv_propagator.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,19 @@ def compute_domains_element_liv(domains: NDArray, parameters: NDArray) -> int:
i[MAX] = min(i[MAX], len(l) - 1)
v_min = sys.maxsize
v_max = -sys.maxsize
indices: List[int] = []
indices: List[int] = [] # TODO: do not allocate a list
for idx in range(i[MIN], i[MAX] + 1):
if v[MAX] < l[idx, MIN] or v[MIN] > l[idx, MAX]: # no intersection
indices.insert(0, idx)
indices.append(idx)
if idx == i[MIN]:
i[MIN] += 1
else: # intersection
if l[idx, MIN] < v_min:
v_min = l[idx, MIN]
if l[idx, MAX] > v_max:
v_max = l[idx, MAX]
for idx in indices:
if idx != i[MAX]:
for ix in range(len(indices) - 1, -1, -1):
if indices[ix] != i[MAX]:
break
i[MAX] -= 1
if i[MAX] < i[MIN]:
Expand Down
18 changes: 16 additions & 2 deletions tests/examples/test_quasigroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

from nucs.constants import STATS_IDX_SOLVER_SOLUTION_NB
from nucs.examples.quasigroup.quasigroup_problem import QuasigroupProblem
from nucs.heuristics.heuristics import VAR_HEURISTIC_SMALLEST_DOMAIN
from nucs.heuristics.heuristics import VAR_HEURISTIC_SMALLEST_DOMAIN, DOM_HEURISTIC_SPLIT_LOW, \
VAR_HEURISTIC_GREATEST_DOMAIN
from nucs.solvers.backtrack_solver import BacktrackSolver


Expand All @@ -38,12 +39,25 @@ class TestQuasigroup:
(5, 10, True, 0),
# (5, 11, True, 5),
# (5, 12, True, 0),
(6, 5, True, 0),
(6, 6, True, 0),
(6, 7, True, 0),
(6, 8, True, 2),
(6, 9, True, 4),
(7, 5, True, 3),
(7, 6, True, 0),
(7, 7, True, 0),
(7, 8, True, 0),
# (7, 9, True, 1),
],
)
def test_qg(self, kind: int, size: int, idempotent: bool, solution_nb: int) -> None:
problem = QuasigroupProblem(kind, size, idempotent, True)
solver = BacktrackSolver(
problem, decision_domains=list(range(0, size * size)), var_heuristic_idx=VAR_HEURISTIC_SMALLEST_DOMAIN
problem,
decision_domains=list(range(0, size * size)),
var_heuristic_idx=VAR_HEURISTIC_SMALLEST_DOMAIN,
dom_heuristic_idx=DOM_HEURISTIC_SPLIT_LOW,
)
solver.solve_all()
assert solver.statistics[STATS_IDX_SOLVER_SOLUTION_NB] == solution_nb
1 change: 1 addition & 0 deletions tests/propagators/test_element_liv.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def test_compute_domains_3(self) -> None:
domains = new_shr_domains_by_values([3, 0, 1, 2, 4, (0, 4), (-2, 0)])
data = new_parameters_by_values([])
assert compute_domains_element_liv(domains, data) == PROP_ENTAILMENT
assert np.all(domains == np.array([[3, 3], [0, 0], [1, 1], [2, 2], [4, 4], [1, 1], [0, 0]]))

def test_compute_domains_4(self) -> None:
domains = new_shr_domains_by_values([(-4, -2), (1, 2), (0, 1), (0, 0)])
Expand Down

0 comments on commit ef1395d

Please sign in to comment.