diff --git a/cpmpy/expressions/__init__.py b/cpmpy/expressions/__init__.py index e6859d70a..bd71085cf 100644 --- a/cpmpy/expressions/__init__.py +++ b/cpmpy/expressions/__init__.py @@ -5,13 +5,13 @@ List of submodules ================== .. autosummary:: + python_builtins :nosignatures: variables core globalconstraints globalfunctions - python_builtins utils @@ -21,7 +21,8 @@ # others need to be imported by the developer explicitely from .variables import boolvar, intvar, cpm_array from .variables import BoolVar, IntVar, cparray # Old, to be deprecated -from .globalconstraints import AllDifferent, AllDifferentExcept0, AllEqual, Circuit, Inverse, Table, Xor, Cumulative, IfThenElse, GlobalCardinalityCount, DirectConstraint, InDomain +from .globalconstraints import AllDifferent, AllDifferentExcept0, AllEqual, Circuit, Inverse, Table, Xor, Cumulative, \ + IfThenElse, GlobalCardinalityCount, DirectConstraint, InDomain, Increasing, Decreasing, IncreasingStrict, DecreasingStrict from .globalconstraints import alldifferent, allequal, circuit # Old, to be deprecated from .globalfunctions import Maximum, Minimum, Abs, Element, Count, NValue from .core import BoolVal diff --git a/cpmpy/expressions/globalconstraints.py b/cpmpy/expressions/globalconstraints.py index cb792bd89..61e7965a6 100644 --- a/cpmpy/expressions/globalconstraints.py +++ b/cpmpy/expressions/globalconstraints.py @@ -104,7 +104,14 @@ def my_circuit_decomp(self): Table Xor Cumulative + IfThenElse GlobalCardinalityCount + DirectConstraint + InDomain + Increasing + Decreasing + IncreasingStrict + DecreasingStrict """ import copy @@ -517,6 +524,98 @@ def value(self): return all(decomposed).value() +class Increasing(GlobalConstraint): + """ + The "Increasing" constraint, the expressions will have increasing (not strictly) values + """ + + def __init__(self, *args): + super().__init__("increasing", flatlist(args)) + + def decompose(self): + """ + Returns two lists of constraints: + 1) the decomposition of the Increasing constraint + 2) empty list of defining constraints + """ + args = self.args + return [args[i] <= args[i+1] for i in range(len(args)-1)], [] + + def value(self): + from .python_builtins import all + args = self.args + return all(args[i].value() <= args[i+1].value() for i in range(len(args)-1)) + + +class Decreasing(GlobalConstraint): + """ + The "Decreasing" constraint, the expressions will have decreasing (not strictly) values + """ + + def __init__(self, *args): + super().__init__("decreasing", flatlist(args)) + + def decompose(self): + """ + Returns two lists of constraints: + 1) the decomposition of the Decreasing constraint + 2) empty list of defining constraints + """ + args = self.args + return [args[i] >= args[i+1] for i in range(len(args)-1)], [] + + def value(self): + from .python_builtins import all + args = self.args + return all(args[i].value() >= args[i+1].value() for i in range(len(args)-1)) + + +class IncreasingStrict(GlobalConstraint): + """ + The "IncreasingStrict" constraint, the expressions will have increasing (strictly) values + """ + + def __init__(self, *args): + super().__init__("increasing_strict", flatlist(args)) + + def decompose(self): + """ + Returns two lists of constraints: + 1) the decomposition of the IncreasingStrict constraint + 2) empty list of defining constraints + """ + args = self.args + return [args[i] < args[i+1] for i in range(len(args)-1)], [] + + def value(self): + from .python_builtins import all + args = self.args + return all((args[i].value() < args[i+1].value()) for i in range(len(args)-1)) + + +class DecreasingStrict(GlobalConstraint): + """ + The "DecreasingStrict" constraint, the expressions will have decreasing (strictly) values + """ + + def __init__(self, *args): + super().__init__("decreasing_strict", flatlist(args)) + + def decompose(self): + """ + Returns two lists of constraints: + 1) the decomposition of the DecreasingStrict constraint + 2) empty list of defining constraints + """ + args = self.args + return [(args[i] > args[i+1]) for i in range(len(args)-1)], [] + + def value(self): + from .python_builtins import all + args = self.args + return all((args[i].value() > args[i+1].value()) for i in range(len(args)-1)) + + class DirectConstraint(Expression): """ A DirectConstraint will directly call a function of the underlying solver when added to a CPMpy solver diff --git a/cpmpy/solvers/choco.py b/cpmpy/solvers/choco.py index 5bfc09084..7976cfe66 100644 --- a/cpmpy/solvers/choco.py +++ b/cpmpy/solvers/choco.py @@ -313,8 +313,13 @@ def transform(self, cpm_expr): cpm_cons = toplevel_list(cpm_expr) supported = {"min", "max", "abs", "count", "element", "alldifferent", "alldifferent_except0", "allequal", - "table", "InDomain", "cumulative", "circuit", "gcc", "inverse", "nvalue"} - supported_reified = supported # choco supports reification of any constraint + "table", "InDomain", "cumulative", "circuit", "gcc", "inverse", "nvalue", "increasing", + "decreasing","increasing_strict","decreasing_strict"} + # choco supports reification of any constraint, but has a bug in increasing and decreasing + supported_reified = {"min", "max", "abs", "count", "element", "alldifferent", "alldifferent_except0", + "allequal", "table", "InDomain", "cumulative", "circuit", "gcc", "inverse", "nvalue"} + # for when choco new release comes, fixing the bug on increasing and decreasing + #supported_reified = supported cpm_cons = decompose_in_tree(cpm_cons, supported, supported_reified) cpm_cons = flatten_constraint(cpm_cons) # flat normal form cpm_cons = canonical_comparison(cpm_cons) @@ -490,7 +495,7 @@ def _get_constraint(self, cpm_expr): elif isinstance(cpm_expr, GlobalConstraint): # many globals require all variables as arguments - if cpm_expr.name in {"alldifferent", "alldifferent_except0", "allequal", "circuit", "inverse"}: + if cpm_expr.name in {"alldifferent", "alldifferent_except0", "allequal", "circuit", "inverse","increasing","decreasing","increasing_strict","decreasing_strict"}: chc_args = self._to_vars(cpm_expr.args) if cpm_expr.name == 'alldifferent': return self.chc_model.all_different(chc_args) @@ -502,6 +507,14 @@ def _get_constraint(self, cpm_expr): return self.chc_model.circuit(chc_args) elif cpm_expr.name == "inverse": return self.chc_model.inverse_channeling(*chc_args) + elif cpm_expr.name == "increasing": + return self.chc_model.increasing(chc_args,0) + elif cpm_expr.name == "decreasing": + return self.chc_model.decreasing(chc_args,0) + elif cpm_expr.name == "increasing_strict": + return self.chc_model.increasing(chc_args,1) + elif cpm_expr.name == "decreasing_strict": + return self.chc_model.decreasing(chc_args,1) # but not all elif cpm_expr.name == 'table': diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 40b7e1590..53a7a2669 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -14,8 +14,8 @@ EXCLUDE_GLOBAL = {"ortools": {}, "gurobi": {}, "minizinc": {"circuit"}, - "pysat": {"circuit", "element","min","max","count", "nvalue", "allequal","alldifferent","cumulative"}, - "pysdd": {"circuit", "element","min","max","count", "nvalue", "allequal","alldifferent","cumulative",'xor'}, + "pysat": {"circuit", "element","min","max","count", "nvalue", "allequal","alldifferent","cumulative","increasing","decreasing","increasing_strict","decreasing_strict"}, + "pysdd": {"circuit", "element","min","max","count", "nvalue", "allequal","alldifferent","cumulative","xor","increasing","decreasing","increasing_strict","decreasing_strict"}, "exact": {}, "choco": {} } @@ -152,7 +152,7 @@ def global_constraints(solver): - AllDifferent, AllEqual, Circuit, Minimum, Maximum, Element, Xor, Cumulative, NValue, Count """ - global_cons = [AllDifferent, AllEqual, Minimum, Maximum, NValue] + global_cons = [AllDifferent, AllEqual, Minimum, Maximum, NValue, Increasing, Decreasing, IncreasingStrict, DecreasingStrict] for global_type in global_cons: cons = global_type(NUM_ARGS) if solver not in EXCLUDE_GLOBAL or cons.name not in EXCLUDE_GLOBAL[solver]: