Skip to content

Commit

Permalink
Merge branch 'master' into xcsp3_2022_benchmarks
Browse files Browse the repository at this point in the history
# Conflicts:
#	cpmpy/expressions/__init__.py
#	cpmpy/expressions/globalconstraints.py
  • Loading branch information
Wout4 committed May 16, 2024
2 parents ed9cbd4 + b3b30c1 commit 63aa892
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 43 deletions.
6 changes: 3 additions & 3 deletions cpmpy/expressions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
core
globalconstraints
globalfunctions
python_builtins
utils
Expand All @@ -21,8 +20,9 @@
# 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, SmartTable, 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 .globalfunctions import Maximum, Minimum, Abs, Element, Count, NValue, NValueExcept
from .core import BoolVal
from .python_builtins import all, any, max, min, sum
131 changes: 99 additions & 32 deletions cpmpy/expressions/globalconstraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,16 @@ def my_circuit_decomp(self):
Circuit
Inverse
Table
SmartTable
Xor
Cumulative
IfThenElse
GlobalCardinalityCount
DirectConstraint
InDomain
Increasing
Decreasing
IncreasingStrict
DecreasingStrict
"""
import copy
Expand Down Expand Up @@ -313,35 +319,6 @@ def value(self):
return arrval in tab


class SmartTable(GlobalConstraint):
"""The values of the variables in 'array' correspond to a row in 'table'
"""
def __init__(self, array, table):
array = flatlist(array)
if not all(isinstance(x, Expression) for x in array):
raise TypeError("the first argument of a Table constraint should only contain variables/expressions")
super().__init__("smarttable", [array, table])

def decompose(self):
from .python_builtins import any, all
arr, tab = self.args
return [any(all(ai == ri for ai, ri in zip(arr, row) if ri != '*') for row in tab)], []

def value(self):
arr, tab = self.args
arrval = [argval(a) for a in arr]
for tup in tab:
thistup = True
for aval, tval in zip(arrval, tup):
if tval != '*':
if aval != tval:
thistup = False
if thistup:
#found tuple that matches
return True
#didn't find tuple that matches
return False

# syntax of the form 'if b then x == 9 else x == 0' is not supported (no override possible)
# same semantic as CPLEX IfThenElse constraint
# https://www.ibm.com/docs/en/icos/12.9.0?topic=methods-ifthenelse-method
Expand Down Expand Up @@ -374,8 +351,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 @@ -547,6 +522,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
58 changes: 57 additions & 1 deletion cpmpy/expressions/globalfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,60 @@ def get_bounds(self):
"""
Returns the bounds of the (numerical) global constraint
"""
return 1, len(self.args)
return 1, len(self.args)


class NValueExcept(GlobalFunction):

"""
The NValueExceptN constraint counts the number of distinct values,
not including value N, if any argument is assigned to it.
"""

def __init__(self, arr, n):
if not is_any_list(arr):
raise ValueError("NValueExcept takes an array as input")
if not is_num(n):
raise ValueError(f"NValueExcept takes an integer as second argument, but got {n} of type {type(n)}")
super().__init__("nvalue_except",[arr, n])

def decompose_comparison(self, cmp_op, cpm_rhs):
"""
NValue(arr) can only be decomposed if it's part of a comparison
Based on "simple decomposition" from:
Bessiere, Christian, et al. "Decomposition of the NValue constraint."
International Conference on Principles and Practice of Constraint Programming.
Berlin, Heidelberg: Springer Berlin Heidelberg, 2010.
"""
from .python_builtins import sum, any

arr, n = self.args
arr = cpm_array(arr)
lbs, ubs = get_bounds(arr)
lb, ub = min(lbs), max(ubs)

constraints = []

# introduce boolvar for each possible value
bvars = boolvar(shape=(ub + 1 - lb))
idx_of_n = n - lb
if 0 <= idx_of_n < len(bvars):
count_of_vals = sum(bvars[:idx_of_n]) + sum(bvars[idx_of_n+1:])
else:
count_of_vals = sum(bvars)

# bvar is true if the value is taken by any variable
for bv, val in zip(bvars, range(lb, ub + 1)):
constraints += [any(arr == val) == bv]

return [eval_comparison(cmp_op, count_of_vals, cpm_rhs)], constraints

def value(self):
return len(set(argval(a) for a in self.args[0]) - {self.args[1]})

def get_bounds(self):
"""
Returns the bounds of the (numerical) global constraint
"""
return 0, len(self.args)
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
Loading

0 comments on commit 63aa892

Please sign in to comment.