Skip to content

Commit

Permalink
Add negative table, review cpmpy callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
Wout4 committed May 28, 2024
1 parent aa7b0fa commit 3b5ba24
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 20 deletions.
1 change: 1 addition & 0 deletions cpmpy/expressions/globalfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def decompose_comparison(self):
Count
Among
NValue
NValueExcept
Abs
"""
Expand Down
50 changes: 30 additions & 20 deletions xcsp3/executable/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def ctr_intension(self, scope: list[Variable], tree: Node):
"eq": (0, lambda x: x[0] == x[1] if len(x) == 2 else cp.AllEqual(x)),
# Set
'in': (2, lambda x, y: cp.InDomain(x, y)),
#TODO 'notin' is the only other set operator (negative indomain)
# Logic
"not": (1, lambda x: ~x),
"and": (0, lambda x: cp.all(x)),
Expand Down Expand Up @@ -143,7 +144,13 @@ def ctr_primitive1c(self, x: Variable, aop: TypeArithmeticOperator, p: int, op:
self.ctr_primitive3(x, aop, p, op, k) #for cpmpy ints and vars are just interchangeable..

def ctr_primitive2a(self, x: Variable, aop: TypeUnaryArithmeticOperator, y: Variable):
self._unimplemented(x, aop, y)
#TODO this was unimplemented not sure if it should be aop(x) == y or x == aop(y)..
arity, cpm_op = self.funcmap[aop.name.lower()]
assert arity == 1, "unary operator expected"
x = self.get_cpm_var(x)
y = self.get_cpm_var(y)
self.cpm_model += cpm_op(x) == y


def ctr_primitive2b(self, x: Variable, aop: TypeArithmeticOperator, y: Variable, op: TypeConditionOperator, k: int):
#(x aop y) rel k
Expand Down Expand Up @@ -214,13 +221,13 @@ def ctr_extension_unary(self, x: Variable, values: list[int], positive: bool, fl
if len(values) == 1:
self.cpm_model += self.get_cpm_var(x) == values[0]
else:
self.cpm_model += cp.InDomain(self.get_cpm_var(x), values)
self.cpm_model += cp.InDomain(self.get_cpm_var(x), values) #TODO should we optimize indomain like we did table?
else:
# negative, so not in domain
if len(values) == 1:
self.cpm_model += self.get_cpm_var(x) != values[0]
else:
self.cpm_model += ~cp.InDomain(self.get_cpm_var(x), values)
self.cpm_model += ~cp.InDomain(self.get_cpm_var(x), values) #TODO should we optimize a negative indomain? probably not worth it.

def ctr_extension(self, scope: list[Variable], tuples: list, positive: bool, flags: set[str]):
def strwildcard(x):
Expand All @@ -239,7 +246,7 @@ def strwildcard(x):
if positive:
self.cpm_model += cp.Table(cpm_vars, tuples)
else:
self.cpm_model += ~cp.Table(cpm_vars, tuples)
self.cpm_model += cp.NegativeTable(cpm_vars, tuples)


def ctr_regular(self, scope: list[Variable], transitions: list, start_state: str, final_states: list[str]):
Expand All @@ -252,9 +259,12 @@ def ctr_all_different(self, scope: list[Variable] | list[Node], excepting: None
cpm_exprs = self.get_cpm_exprs(scope)
if excepting is None:
self.cpm_model += cp.AllDifferent(cpm_exprs)
elif len(excepting) == 1:
self.cpm_model += cp.AllDifferentExceptN(cpm_exprs, excepting[0])
else: # unsupported for competition
elif len(excepting) > 0: # TODO changed this, we now support excepting list
self.cpm_model += cp.AllDifferentExceptN(cpm_exprs, excepting)
elif len(excepting) == 0:
# just in case they get tricky
self.cpm_model += cp.AllDifferent(cpm_exprs)
else: # unsupported for competition
self._unimplemented(scope, excepting)

def ctr_all_different_lists(self, lists: list[list[Variable]], excepting: None | list[list[int]]):
Expand All @@ -268,7 +278,7 @@ def ctr_all_different_matrix(self, matrix: list[list[Variable]], excepting: None
for row in matrix:
self.ctr_all_different(row, excepting)
for col in np.array(matrix).T:
self.ctr_all_different(col, excepting)
self.ctr_all_different(col, excepting) #TODO: nice

def ctr_all_equal(self, scope: list[Variable] | list[Node], excepting: None | list[int]):
if excepting is None:
Expand Down Expand Up @@ -313,7 +323,7 @@ def ctr_lex_limit(self, lst: list[Variable], limit: list[int], operator: TypeOrd
def ctr_lex(self, lists: list[list[Variable]], operator: TypeOrderedOperator):
cpm_lists = [self.get_cpm_vars(lst) for lst in lists]
if operator == TypeOrderedOperator.STRICTLY_INCREASING:
self.cpm_model += cp.LexChainLess(cpm_lists)
self.cpm_model += cp.LexChainLess(cpm_lists) #TODO why is our less their increasing? is this opposite by accident? ok i checked seems correct
elif operator == TypeOrderedOperator.INCREASING:
self.cpm_model += cp.LexChainLessEq(cpm_lists)
elif operator == TypeOrderedOperator.STRICTLY_DECREASING:
Expand Down Expand Up @@ -373,12 +383,12 @@ def ctr_sum(self, lst: list[Variable] | list[Node], coefficients: None | list[in

import numpy as np
if coefficients is None:
coefficients = np.ones(len(lst))
coefficients = np.ones(len(lst)) # TODO I guess, if wsums are preferred over sums

lhs = cp.sum(coefficients * self.get_cpm_exprs(lst))
if condition.operator == TypeConditionOperator.IN:
from pycsp3.classes.auxiliary.conditions import ConditionInterval
assert isinstance(condition, ConditionInterval), "Competition only supports intervals when operator is `in`"
assert isinstance(condition, ConditionInterval), "Competition only supports intervals when operator is `in`" #TODO and not in? (notin)
rhs = list(range(condition.min, condition.max+1))
else:
rhs = self.get_cpm_var(condition.right_operand())
Expand All @@ -392,7 +402,7 @@ def ctr_count(self, lst: list[Variable] | list[Node], values: list[int] | list[V
cpm_vals = self.get_cpm_vars(values)
if condition.operator == TypeConditionOperator.IN:
from pycsp3.classes.auxiliary.conditions import ConditionInterval
assert isinstance(condition, ConditionInterval), "Competition only supports intervals when operator is `in`"
assert isinstance(condition, ConditionInterval), "Competition only supports intervals when operator is `in`" #TODO notin?
rhs = list(range(condition.min, condition.max + 1))
else:
rhs = self.get_cpm_var(condition.right_operand())
Expand Down Expand Up @@ -420,9 +430,9 @@ def ctr_nvalues(self, lst: list[Variable] | list[Node], excepting: None | list[i
lhs = cp.NValue(self.get_cpm_exprs(lst))
else:
assert len(excepting) == 1, "Competition only allows 1 integer value in excepting list"
lhs = cp.NValue(self.get_cpm_exprs(lst), excepting[0])
lhs = cp.NValueExcept(self.get_cpm_exprs(lst), excepting[0])

self.cpm_model += self.eval_cpm_comp(lhs, condition.operator, self.get_cpm_var(condition.right_operand()))
self.cpm_model += self.eval_cpm_comp(lhs, condition.operator, self.get_cpm_var(condition.right_operand())) #TODO this could also be in and notin with a range?

def ctr_not_all_qual(self, lst: list[Variable]):
cpm_vars = self.get_cpm_vars(lst)
Expand All @@ -439,14 +449,14 @@ def ctr_minimum(self, lst: list[Variable] | list[Node], condition: Condition):
cpm_vars = self.get_cpm_exprs(lst)
self.cpm_model += self.eval_cpm_comp(cp.Minimum(cpm_vars),
condition.operator,
self.get_cpm_var(condition.right_operand()))
self.get_cpm_var(condition.right_operand())) #TODO range for in/notin?


def ctr_maximum(self, lst: list[Variable] | list[Node], condition: Condition):
cpm_vars = self.get_cpm_exprs(lst)
self.cpm_model += self.eval_cpm_comp(cp.Maximum(cpm_vars),
condition.operator,
self.get_cpm_var(condition.right_operand()))
self.get_cpm_var(condition.right_operand())) #TODO range for in/notin?

def ctr_minimum_arg(self, lst: list[Variable] | list[Node], condition: Condition, rank: TypeRank): # should enter XCSP3-core
self._unimplemented(lst, condition, rank)
Expand All @@ -457,13 +467,13 @@ def ctr_maximum_arg(self, lst: list[Variable] | list[Node], condition: Condition
def ctr_element(self, lst: list[Variable] | list[int], i: Variable, condition: Condition):
cpm_lst = self.get_cpm_vars(lst)
cpm_index = self.get_cpm_var(i)
cpm_rhs = self.get_cpm_var(condition.right_operand())
cpm_rhs = self.get_cpm_var(condition.right_operand()) #TODO range for in/notin?
self.cpm_model += self.eval_cpm_comp(cp.Element(cpm_lst, cpm_index), condition.operator, cpm_rhs)

def ctr_element_matrix(self, matrix: list[list[Variable]] | list[list[int]], i: Variable, j: Variable, condition: Condition):
mtrx = cp.cpm_array([self.get_cpm_vars(lst) for lst in matrix])
cpm_i, cpm_j = self.get_cpm_vars([i,j])
cpm_rhs = self.get_cpm_var(condition.right_operand())
cpm_rhs = self.get_cpm_var(condition.right_operand()) #TODO range for in/notin? Sorry for spamming this, i think it's possible in theory according to the specs.

self.cpm_model += self.eval_cpm_comp(mtrx[cpm_i, cpm_j], condition.operator, cpm_rhs)

Expand Down Expand Up @@ -542,7 +552,7 @@ def ctr_cumulative(self, origins: list[Variable], lengths: list[int] | list[Vari
self.cpm_model += cp.Cumulative(cpm_start, cpm_durations, cpm_ends, cpm_demands, self.get_cpm_var(condition.right_operand()))
else:
# post decomposition directly
# be smart and chose task or time decomposition
# be smart and chose task or time decomposition #TODO you did task decomp in both cases
if max(get_bounds(cpm_ends)[1]) >= 100:
self._cumulative_task_decomp(cpm_start, cpm_durations, cpm_ends, heights, condition)
else:
Expand Down Expand Up @@ -680,7 +690,7 @@ def obj_minimize_special(self, obj_type: TypeObj, terms: list[Variable] | list[N
self.cpm_model.minimize(cpm_expr)
else:
self._unimplemented(obj_type, coefficients, terms)

#TODO objectives are a bit confusing but i think it's correct
def obj_maximize_special(self, obj_type: TypeObj, terms: list[Variable] | list[Node], coefficients: None | list[int]):
import numpy as np
if coefficients is None:
Expand Down

0 comments on commit 3b5ba24

Please sign in to comment.