Skip to content

Commit

Permalink
Merge branch 'master' into AllDiffExpceptN
Browse files Browse the repository at this point in the history
# Conflicts:
#	cpmpy/expressions/__init__.py
  • Loading branch information
Wout4 committed May 10, 2024
2 parents 99a6f9f + 3a94691 commit 54e1c28
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 10 deletions.
2 changes: 1 addition & 1 deletion cpmpy/expressions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# 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, AllDifferentExceptN, AllEqualExceptN, AllEqual, Circuit, Inverse, Table, Xor, Cumulative, IfThenElse, GlobalCardinalityCount, DirectConstraint, InDomain
from .globalconstraints import AllDifferent, AllDifferentExcept0, AllDifferentExceptN, AllEqualExceptN, 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
101 changes: 99 additions & 2 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 @@ -380,8 +387,6 @@ class InDomain(GlobalConstraint):
"""

def __init__(self, expr, arr):
assert not (is_boolexpr(expr) or any(is_boolexpr(a) for a in arr)), \
"The expressions in the InDomain constraint should not be boolean"
super().__init__("InDomain", [expr, arr])

def decompose(self):
Expand Down Expand Up @@ -553,6 +558,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__("strictly_increasing", 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__("strictly_decreasing", 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
20 changes: 17 additions & 3 deletions cpmpy/solvers/choco.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,14 @@ 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","strictly_increasing","strictly_decreasing"}

# 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 +496,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","strictly_increasing","strictly_decreasing"}:
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 +508,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 == "strictly_increasing":
return self.chc_model.increasing(chc_args,1)
elif cpm_expr.name == "strictly_decreasing":
return self.chc_model.decreasing(chc_args,1)

# but not all
elif cpm_expr.name == 'table':
Expand Down
3 changes: 2 additions & 1 deletion cpmpy/solvers/minizinc.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,8 @@ def transform(self, cpm_expr):
"""
cpm_cons = toplevel_list(cpm_expr)
supported = {"min", "max", "abs", "element", "count", "nvalue", "alldifferent", "alldifferent_except0", "allequal",
"inverse", "ite" "xor", "table", "cumulative", "circuit", "gcc"}
"inverse", "ite" "xor", "table", "cumulative", "circuit", "gcc", "increasing",
"decreasing","strictly_increasing","strictly_decreasing"}
return decompose_in_tree(cpm_cons, supported, supported_reified=supported - {"circuit"})


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","strictly_increasing","strictly_decreasing"},
"pysdd": {"circuit", "element","min","max","count", "nvalue", "allequal","alldifferent","cumulative","xor","increasing","decreasing","strictly_increasing","strictly_decreasing"},
"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
62 changes: 62 additions & 0 deletions tests/test_globalconstraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,24 @@ def test_InDomain(self):
model = cp.Model(cons)
self.assertTrue(model.solve())
self.assertIn(iv.value(), vals)
vals = [1, 5, 8, -4]
bv = cp.boolvar()
cons = [cp.InDomain(bv, vals)]
model = cp.Model(cons)
self.assertTrue(model.solve())
self.assertIn(bv.value(), vals)
vals = [iv2, 5, 8, -4]
bv = cp.boolvar()
cons = [cp.InDomain(bv, vals)]
model = cp.Model(cons)
self.assertTrue(model.solve())
self.assertIn(bv.value(), vals)
vals = [bv & bv, 5, 8, -4]
bv = cp.boolvar()
cons = [cp.InDomain(bv, vals)]
model = cp.Model(cons)
self.assertTrue(model.solve())
self.assertIn(bv.value(), vals)

def test_indomain_onearg(self):

Expand Down Expand Up @@ -830,6 +848,50 @@ def test_not_allEqualExceptn(self):
self.assertEqual(total, len(circuit_sols) + len(not_circuit_sols))


def test_increasing(self):
x = cp.intvar(-8, 8)
y = cp.intvar(-7, -1)
b = cp.boolvar()
a = cp.boolvar()
self.assertTrue(cp.Model([cp.Increasing(x,y)]).solve())
self.assertTrue(cp.Model([cp.Increasing(a,b)]).solve())
self.assertTrue(cp.Model([cp.Increasing(x,y,b)]).solve())
z = cp.intvar(2,5)
self.assertFalse(cp.Model([cp.Increasing(z,b)]).solve())

def test_decreasing(self):
x = cp.intvar(-8, 8)
y = cp.intvar(-7, -1)
b = cp.boolvar()
a = cp.boolvar()
self.assertTrue(cp.Model([cp.Decreasing(x,y)]).solve())
self.assertTrue(cp.Model([cp.Decreasing(a,b)]).solve())
self.assertFalse(cp.Model([cp.Decreasing(x,y,b)]).solve())
z = cp.intvar(2,5)
self.assertTrue(cp.Model([cp.Decreasing(z,b)]).solve())

def test_increasing_strict(self):
x = cp.intvar(-8, 8)
y = cp.intvar(-7, -1)
b = cp.boolvar()
a = cp.boolvar()
self.assertTrue(cp.Model([cp.IncreasingStrict(x,y)]).solve())
self.assertTrue(cp.Model([cp.IncreasingStrict(a,b)]).solve())
self.assertTrue(cp.Model([cp.IncreasingStrict(x,y,b)]).solve())
z = cp.intvar(1,5)
self.assertFalse(cp.Model([cp.IncreasingStrict(z,b)]).solve())

def test_decreasing_strict(self):
x = cp.intvar(-8, 8)
y = cp.intvar(-7, 0)
b = cp.boolvar()
a = cp.boolvar()
self.assertTrue(cp.Model([cp.DecreasingStrict(x,y)]).solve())
self.assertTrue(cp.Model([cp.DecreasingStrict(a,b)]).solve())
self.assertFalse(cp.Model([cp.DecreasingStrict(x,y,b)]).solve())
z = cp.intvar(1,5)
self.assertTrue(cp.Model([cp.DecreasingStrict(z,b)]).solve())

def test_circuit(self):
x = cp.intvar(-8, 8)
y = cp.intvar(-7, -1)
Expand Down

0 comments on commit 54e1c28

Please sign in to comment.