diff --git a/cpmpy/expressions/globalconstraints.py b/cpmpy/expressions/globalconstraints.py index 59017ad88..569da628c 100644 --- a/cpmpy/expressions/globalconstraints.py +++ b/cpmpy/expressions/globalconstraints.py @@ -134,7 +134,7 @@ def my_circuit_decomp(self): from .core import BoolVal from .utils import all_pairs, is_int, is_bool, STAR -from .variables import _IntVarImpl +from .variables import _IntVarImpl, _BoolVarImpl from .globalfunctions import * # XXX make this file backwards compatible @@ -173,6 +173,14 @@ def get_bounds(self): """ return 0, 1 + def negate(self): + """ + Returns the negation of this global constraint. + Defaults to ~self, but subclasses can implement a better version, + > Fages, François, and Sylvain Soliman. Reifying global constraints. Diss. INRIA, 2012. + """ + return ~self + # Global Constraints (with Boolean return type) def alldifferent(args): @@ -430,6 +438,9 @@ def value(self): arrval = argvals(arr) return arrval in tab + def negate(self): + return NegativeTable(self.args[0], self.args[1]) + class ShortTable(GlobalConstraint): """ Extension of the `Table` constraint where the `table` matrix may contain wildcards (STAR), meaning there are @@ -479,6 +490,9 @@ def value(self): arrval = argvals(arr) tabval = argvals(tab) return arrval not in tabval + + def negate(self): + return Table(self.args[0], self.args[1]) class Regular(GlobalConstraint): @@ -596,6 +610,9 @@ def __repr__(self): condition, if_true, if_false = self.args return "If {} Then {} Else {}".format(condition, if_true, if_false) + def negate(self): + return IfThenElse(self.args[0], self.args[2], self.args[1]) + class InDomain(GlobalConstraint): @@ -636,6 +653,11 @@ def value(self): def __repr__(self): return "{} in {}".format(self.args[0], self.args[1]) + def negate(self): + lb, ub = get_bounds(self.args[0]) + return InDomain(self.args[0], + [v for v in range(lb,ub+1) if v not in set(self.args[1])]) + class Xor(GlobalConstraint): """ @@ -668,6 +690,21 @@ def __repr__(self): return "{} xor {}".format(*self.args) return "xor({})".format(self.args) + def negate(self): + # negate one of the arguments, ideally a variable + new_args = None + for i, a in enumerate(self.args): + if isinstance(a, _BoolVarImpl): + new_args = self.args[:i] + [~a] + self.args[i+1:] + break + + if new_args is None:# did not find a Boolean variable to negate + # pick first arg, and push down negation + new_args = list(self.args) + new_args[0] = cp.transformations.negation.recurse_negation(self.args[0]) + + return Xor(new_args) + class Cumulative(GlobalConstraint): """ diff --git a/cpmpy/transformations/negation.py b/cpmpy/transformations/negation.py index 8829e7e3e..cc7f38650 100644 --- a/cpmpy/transformations/negation.py +++ b/cpmpy/transformations/negation.py @@ -117,9 +117,9 @@ def recurse_negation(expr): # global constraints elif hasattr(expr, "decompose"): newexpr = copy.copy(expr) - # args are positive as we will negate the global, still check if no 'not' in its arguments + # args are positive as we will negate the global, still check if no 'not' in its arguments newexpr.update_args(push_down_negation(expr.args, toplevel=False)) - return ~newexpr + return newexpr.negate() elif is_bool(expr): # unlikely case with non-CPMpy True or False return ~BoolVal(expr) diff --git a/cpmpy/transformations/normalize.py b/cpmpy/transformations/normalize.py index f45e4401c..4523688b5 100644 --- a/cpmpy/transformations/normalize.py +++ b/cpmpy/transformations/normalize.py @@ -227,7 +227,8 @@ def simplify_boolean(lst_of_expr, num_context=False): if is_bool(res): # Result is a Boolean constant newlist.append(int(res) if num_context else BoolVal(res)) else: # Result is an expression - newlist.append(res) + newlist.append(res) + elif isinstance(expr, (GlobalConstraint, GlobalFunction)): newargs = simplify_boolean(expr.args) # TODO: how to determine which are Bool/int? if any(a1 is not a2 for a1,a2 in zip(expr.args, newargs)):