Skip to content

Commit

Permalink
Increasing and decreasing global constraints (#471)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dimosts authored May 10, 2024
1 parent 969b360 commit 3619176
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 8 deletions.
5 changes: 3 additions & 2 deletions cpmpy/expressions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
List of submodules
==================
.. autosummary::
python_builtins
:nosignatures:
variables
core
globalconstraints
globalfunctions
python_builtins
utils
Expand All @@ -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
Expand Down
99 changes: 99 additions & 0 deletions cpmpy/expressions/globalconstraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,14 @@ def my_circuit_decomp(self):
Table
Xor
Cumulative
IfThenElse
GlobalCardinalityCount
DirectConstraint
InDomain
Increasing
Decreasing
IncreasingStrict
DecreasingStrict
"""
import copy
Expand Down Expand Up @@ -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
Expand Down
19 changes: 16 additions & 3 deletions cpmpy/solvers/choco.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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':
Expand Down
6 changes: 3 additions & 3 deletions tests/test_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": {}
}
Expand Down Expand Up @@ -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]:
Expand Down

0 comments on commit 3619176

Please sign in to comment.