Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extending automatic teststuite #464

Merged
merged 56 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
6818896
add transform to ortools, fix default args of transfs
tias Jan 1, 2023
08a0332
solvers: transform() to superclass and template
tias Jan 1, 2023
ed0af00
gurobi: use generic transform (UNTESTED! license does not activate fr…
tias Jan 1, 2023
74a407f
minizinc: use generic transform()
tias Jan 1, 2023
db05833
pysat/pysdd: use generic transform
tias Jan 1, 2023
8447c5d
z3: use transform
tias Jan 1, 2023
ac82820
Merge branch 'master' into 140-refactoring-of-solverinterface
tias Jan 1, 2023
c8d71f5
z3 transform: ensure it is a list
tias Jan 2, 2023
83ae171
solvers: tweaks of transform()
tias Jan 2, 2023
1751152
docs: debugging guide to include using a solvers transform() function
tias Jan 2, 2023
4e20463
Merge branch 'master' into 140-refactoring-of-solverinterface
IgnaceBleukx Jan 24, 2023
d94238e
cascaded xors, leave new vars to flatten_contraint in solver interfaces
IgnaceBleukx Jan 24, 2023
35950e5
global xor correctly transformed to cnf by recursive call
IgnaceBleukx Jan 24, 2023
a4f9afd
run all supported solvers in testsuite
IgnaceBleukx Jan 24, 2023
9787f6d
cumulative constraint duration for minizinc
IgnaceBleukx Jan 24, 2023
8227122
_post constraint does not support lists of expr
IgnaceBleukx Jan 24, 2023
c42ddf7
implemented ConstraintNotImplementedError
IgnaceBleukx Jan 25, 2023
4845676
ensure solver assigns value to any user var
IgnaceBleukx May 2, 2024
068f83e
add test
IgnaceBleukx May 2, 2024
3847201
extend and clean automatic testsuite
IgnaceBleukx May 2, 2024
6d21c8b
undefinedness -> False in neirest Bool context
IgnaceBleukx May 2, 2024
240976a
fix simplication when comparison is used as num argument
IgnaceBleukx May 2, 2024
49f9975
finish merge
IgnaceBleukx May 2, 2024
1f244f6
Merge branch 'master' into extended_testsuite
Wout4 May 2, 2024
f1bbd68
vals in gcc are ints
Dimosts May 2, 2024
d95fad2
cleanup of test_constraints file
IgnaceBleukx May 7, 2024
88705eb
ensure comparisons of Bool to constant are translated properly
IgnaceBleukx May 7, 2024
3763247
Merge remote-tracking branch 'origin/extended_testsuite' into extende…
Wout4 May 7, 2024
2efbe75
introduce argvals() helper function
Wout4 May 8, 2024
3f8ef4e
exclude inverse from tests
Wout4 May 8, 2024
daf505d
include inverse again, test are failing anyway
Wout4 May 8, 2024
23b89b0
Merge branch 'extended_testsuite' of github.com:CPMpy/cpmpy into exte…
IgnaceBleukx May 8, 2024
8adcf5b
Revert "introduce argvals() helper function"
IgnaceBleukx May 10, 2024
3a84ded
hack: ensure all variables have a value
IgnaceBleukx May 10, 2024
828d2cd
update tests to exclude inverse
IgnaceBleukx May 10, 2024
a387aef
Merge remote-tracking branch 'refs/remotes/origin/master' into extend…
IgnaceBleukx May 10, 2024
47fd9c5
add increasing and decreasing to numerical globals in testsuite
IgnaceBleukx May 10, 2024
d461be8
add new global constraints as numeric
IgnaceBleukx May 10, 2024
fc8f5f0
fix test, we have relational semantics in value computation now
IgnaceBleukx May 10, 2024
464e555
indexerror -> false
Wout4 May 10, 2024
970bbb0
Safe_value() on expressions, automatically handle incompletefunction …
Wout4 May 13, 2024
c013ee8
use helper function argvals()
Wout4 May 13, 2024
8d7a165
uncomment gurobi supported function
Wout4 May 13, 2024
01c53f5
fix test, .value() raises but argval() does not
Wout4 May 13, 2024
6870a91
remove unnecessary aux vars in value calculation
Wout4 May 13, 2024
3fee8d0
use argval in tests
Wout4 May 13, 2024
db8efd7
use argval in tests
Wout4 May 13, 2024
429daf9
use argval and .value() in test constraints
Wout4 May 14, 2024
2b04967
reference .safe_value when incompletefunctionerror is thrown.
Wout4 May 14, 2024
72cbc49
remove safe_value, only need argval
Wout4 May 15, 2024
b674a59
Merge branch 'master' into extended_testsuite
IgnaceBleukx May 17, 2024
4dc7e50
nvalueexcept constructor fixed in test_constraints
IgnaceBleukx May 17, 2024
e5debe4
test all solvers in test_constraints
Wout4 May 17, 2024
32c8535
remove print
Wout4 May 17, 2024
d34a971
add nvalueexcept to num_globals in test constraints
Wout4 May 17, 2024
3376000
exclude strictly_increasing from mzn tests
IgnaceBleukx May 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions cpmpy/expressions/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
import numpy as np


from .utils import is_num, is_any_list, flatlist, argval, get_bounds, is_boolexpr, is_true_cst, is_false_cst
from .utils import is_num, is_any_list, flatlist, argval, get_bounds, is_boolexpr, is_true_cst, is_false_cst, argvals
from ..exceptions import IncompleteFunctionError, TypeError


Expand Down Expand Up @@ -144,6 +144,7 @@ def is_bool(self):
def value(self):
return None # default


def get_bounds(self):
if self.is_bool():
return 0, 1 #default for boolean expressions
Expand Down Expand Up @@ -400,7 +401,8 @@ def __repr__(self):
# return the value of the expression
# optional, default: None
def value(self):
arg_vals = [argval(a) for a in self.args]
arg_vals = argvals(self.args)

if any(a is None for a in arg_vals): return None
if self.name == "==": return arg_vals[0] == arg_vals[1]
elif self.name == "!=": return arg_vals[0] != arg_vals[1]
Expand Down Expand Up @@ -526,11 +528,12 @@ def wrap_bracket(arg):
return "{}({})".format(self.name, self.args)

def value(self):

if self.name == "wsum":
# wsum: arg0 is list of constants, no .value() use as is
arg_vals = [self.args[0], [argval(arg) for arg in self.args[1]]]
arg_vals = [self.args[0], argvals(self.args[1])]
else:
arg_vals = [argval(arg) for arg in self.args]
arg_vals = argvals(self.args)


if any(a is None for a in arg_vals): return None
Expand All @@ -546,7 +549,8 @@ def value(self):
try:
return arg_vals[0] // arg_vals[1]
except ZeroDivisionError:
raise IncompleteFunctionError(f"Division by zero during value computation for expression {self}")
raise IncompleteFunctionError(f"Division by zero during value computation for expression {self}"
+ "\n Use argval(expr) to get the value of expr with relational semantics.")

# boolean
elif self.name == "and": return all(arg_vals)
Expand Down
36 changes: 20 additions & 16 deletions cpmpy/expressions/globalconstraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def my_circuit_decomp(self):
from ..exceptions import CPMpyException, IncompleteFunctionError, TypeError
from .core import Expression, Operator, Comparison
from .variables import boolvar, intvar, cpm_array, _NumVarImpl, _IntVarImpl
from .utils import flatlist, all_pairs, argval, is_num, eval_comparison, is_any_list, is_boolexpr, get_bounds
from .utils import flatlist, all_pairs, argval, is_num, eval_comparison, is_any_list, is_boolexpr, get_bounds, argvals
from .globalfunctions import * # XXX make this file backwards compatible


Expand Down Expand Up @@ -178,7 +178,7 @@ def decompose(self):
return [var1 != var2 for var1, var2 in all_pairs(self.args)], []

def value(self):
return len(set(a.value() for a in self.args)) == len(self.args)
return len(set(argvals(self.args))) == len(self.args)


class AllDifferentExcept0(GlobalConstraint):
Expand All @@ -193,10 +193,9 @@ def decompose(self):
return [(var1 == var2).implies(var1 == 0) for var1, var2 in all_pairs(self.args)], []

def value(self):
vals = [a.value() for a in self.args if a.value() != 0]
vals = [argval(a) for a in self.args if argval(a) != 0]
return len(set(vals)) == len(vals)


def allequal(args):
warnings.warn("Deprecated, use AllEqual(v1,v2,...,vn) instead, will be removed in stable version", DeprecationWarning)
return AllEqual(*args) # unfold list as individual arguments
Expand All @@ -215,8 +214,7 @@ def decompose(self):
return [var1 == var2 for var1, var2 in zip(self.args[:-1], self.args[1:])], []

def value(self):
return len(set(a.value() for a in self.args)) == 1

return len(set(argvals(self.args))) == 1

def circuit(args):
warnings.warn("Deprecated, use Circuit(v1,v2,...,vn) instead, will be removed in stable version", DeprecationWarning)
Expand Down Expand Up @@ -259,7 +257,8 @@ def value(self):
pathlen = 0
idx = 0
visited = set()
arr = [argval(a) for a in self.args]
arr = argvals(self.args)

while idx not in visited:
if idx is None:
return False
Expand Down Expand Up @@ -294,9 +293,13 @@ def decompose(self):
return [all(rev[x] == i for i, x in enumerate(fwd))], []

def value(self):
fwd = [argval(a) for a in self.args[0]]
rev = [argval(a) for a in self.args[1]]
return all(rev[x] == i for i, x in enumerate(fwd))
fwd = argvals(self.args[0])
rev = argvals(self.args[1])
# args are fine, now evaluate actual inverse cons
try:
return all(rev[x] == i for i, x in enumerate(fwd))
except IndexError: # partiality of Element constraint
return False


class Table(GlobalConstraint):
Expand All @@ -315,10 +318,11 @@ def decompose(self):

def value(self):
arr, tab = self.args
arrval = [argval(a) for a in arr]
arrval = argvals(arr)
return arrval in tab



# 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 @@ -379,7 +383,7 @@ def decompose(self):


def value(self):
return argval(self.args[0]) in argval(self.args[1])
return argval(self.args[0]) in argvals(self.args[1])

def __repr__(self):
return "{} in {}".format(self.args[0], self.args[1])
Expand Down Expand Up @@ -409,7 +413,7 @@ def decompose(self):
return decomp, []

def value(self):
return sum(argval(a) for a in self.args) % 2 == 1
return sum(argvals(self.args)) % 2 == 1

def __repr__(self):
if len(self.args) == 2:
Expand Down Expand Up @@ -478,14 +482,14 @@ def decompose(self):
return cons, []

def value(self):
argvals = [np.array([argval(a) for a in arg]) if is_any_list(arg)
arg_vals = [np.array(argvals(arg)) if is_any_list(arg)
else argval(arg) for arg in self.args]

if any(a is None for a in argvals):
if any(a is None for a in arg_vals):
return None

# start, dur, end are np arrays
start, dur, end, demand, capacity = argvals
start, dur, end, demand, capacity = arg_vals
# start and end seperated by duration
if not (start + dur == end).all():
return False
Expand Down
3 changes: 2 additions & 1 deletion cpmpy/expressions/globalfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ def value(self):
if idxval is not None:
if idxval >= 0 and idxval < len(arr):
return argval(arr[idxval])
raise IncompleteFunctionError(f"Index {idxval} out of range for array of length {len(arr)} while calculating value for expression {self}")
raise IncompleteFunctionError(f"Index {idxval} out of range for array of length {len(arr)} while calculating value for expression {self}"
+ "\n Use argval(expr) to get the value of expr with relational semantics.")
return None # default

def decompose_comparison(self, cpm_op, cpm_rhs):
Expand Down
18 changes: 13 additions & 5 deletions cpmpy/expressions/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,19 @@ def argval(a):

We check with hasattr instead of isinstance to avoid circular dependency
"""
try:
return a.value() if hasattr(a, "value") else a
except IncompleteFunctionError as e:
if a.is_bool(): return False
raise e
if hasattr(a, "value"):
try:
return a.value()
except IncompleteFunctionError as e:
if a.is_bool():
return False
else:
raise e
return a


def argvals(arr):
return [argval(a) for a in arr]


def eval_comparison(str_op, lhs, rhs):
Expand Down
7 changes: 4 additions & 3 deletions cpmpy/solvers/choco.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from ..expressions.globalconstraints import DirectConstraint
from ..expressions.variables import _NumVarImpl, _IntVarImpl, _BoolVarImpl, NegBoolView, intvar
from ..expressions.globalconstraints import GlobalConstraint
from ..expressions.utils import is_num, is_int, is_boolexpr, is_any_list, get_bounds
from ..expressions.utils import is_num, is_int, is_boolexpr, is_any_list, get_bounds, argval, argvals
from ..transformations.decompose_global import decompose_in_tree
from ..transformations.get_variables import get_variables
from ..transformations.flatten_model import flatten_constraint, flatten_objective
Expand Down Expand Up @@ -208,9 +208,9 @@ def solveAll(self, display=None, time_limit=None, solution_limit=None, call_from
cpm_var._value = value
# print the desired display
if isinstance(display, Expression):
print(display.value())
print(argval(display))
elif isinstance(display, list):
print([v.value() for v in display])
print(argvals(display))
else:
display() # callback

Expand Down Expand Up @@ -349,6 +349,7 @@ def __add__(self, cpm_expr):
"""
# add new user vars to the set
get_variables(cpm_expr, collect=self.user_vars)
# ensure all vars are known to solver

# transform and post the constraints
for con in self.transform(cpm_expr):
Expand Down
6 changes: 3 additions & 3 deletions cpmpy/solvers/exact.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from ..transformations.normalize import toplevel_list
from ..expressions.globalconstraints import DirectConstraint
from ..exceptions import NotSupportedError
from ..expressions.utils import flatlist
from ..expressions.utils import flatlist, argvals

import numpy as np
import numbers
Expand Down Expand Up @@ -263,9 +263,9 @@ def solveAll(self, display=None, time_limit=None, solution_limit=None, call_from
if display is not None:
self._fillObjAndVars()
if isinstance(display, Expression):
print(display.value())
print(argval(display))
elif isinstance(display, list):
print([v.value() for v in display])
print(argvals(display))
else:
display() # callback
elif my_status == 2: # found inconsistency
Expand Down
5 changes: 3 additions & 2 deletions cpmpy/solvers/gurobi.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

from .solver_interface import SolverInterface, SolverStatus, ExitStatus
from ..expressions.core import *
from ..expressions.utils import argvals
from ..expressions.variables import _BoolVarImpl, NegBoolView, _IntVarImpl, _NumVarImpl, intvar
from ..expressions.globalconstraints import DirectConstraint
from ..transformations.comparison import only_numexpr_equality
Expand Down Expand Up @@ -461,9 +462,9 @@ def solveAll(self, display=None, time_limit=None, solution_limit=None, call_from

if display is not None:
if isinstance(display, Expression):
print(display.value())
print(argval(display))
elif isinstance(display, list):
print([v.value() for v in display])
print(argvals(display))
else:
display() # callback

Expand Down
6 changes: 3 additions & 3 deletions cpmpy/solvers/minizinc.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from ..expressions.core import Expression, Comparison, Operator, BoolVal
from ..expressions.variables import _NumVarImpl, _IntVarImpl, _BoolVarImpl, NegBoolView, intvar
from ..expressions.globalconstraints import DirectConstraint
from ..expressions.utils import is_num, is_any_list, eval_comparison
from ..expressions.utils import is_num, is_any_list, eval_comparison, argvals, argval
from ..transformations.decompose_global import decompose_in_tree
from ..transformations.get_variables import get_variables
from ..exceptions import MinizincPathException, NotSupportedError
Expand Down Expand Up @@ -323,9 +323,9 @@ async def _solveAll(self, display=None, time_limit=None, solution_limit=None, **

# and the actual displaying
if isinstance(display, Expression):
print(display.value())
print(argval(display))
elif isinstance(display, list):
print([v.value() for v in display])
print(argvals(display))
else:
display() # callback

Expand Down
6 changes: 3 additions & 3 deletions cpmpy/solvers/ortools.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from ..expressions.globalconstraints import DirectConstraint
from ..expressions.variables import _NumVarImpl, _IntVarImpl, _BoolVarImpl, NegBoolView, boolvar
from ..expressions.globalconstraints import GlobalConstraint
from ..expressions.utils import is_num, is_any_list, eval_comparison, flatlist
from ..expressions.utils import is_num, is_any_list, eval_comparison, flatlist, argval, argvals
from ..transformations.decompose_global import decompose_in_tree
from ..transformations.get_variables import get_variables
from ..transformations.flatten_model import flatten_constraint, flatten_objective
Expand Down Expand Up @@ -689,10 +689,10 @@ def on_solution_callback(self):
cpm_var._value = self.Value(self._varmap[cpm_var])

if isinstance(self._display, Expression):
print(self._display.value())
print(argval(self._display))
elif isinstance(self._display, list):
# explicit list of expressions to display
print([v.value() for v in self._display])
print(argvals(self._display))
else: # callable
self._display()

Expand Down
3 changes: 2 additions & 1 deletion cpmpy/solvers/pysat.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from ..transformations.decompose_global import decompose_in_tree
from ..transformations.get_variables import get_variables
from ..transformations.flatten_model import flatten_constraint
from ..transformations.normalize import toplevel_list
from ..transformations.normalize import toplevel_list, simplify_boolean
from ..transformations.reification import only_implies, only_bv_reifies


Expand Down Expand Up @@ -233,6 +233,7 @@ def transform(self, cpm_expr):
"""
cpm_cons = toplevel_list(cpm_expr)
cpm_cons = decompose_in_tree(cpm_cons)
cpm_cons = simplify_boolean(cpm_cons)
cpm_cons = flatten_constraint(cpm_cons)
cpm_cons = only_bv_reifies(cpm_cons)
cpm_cons = only_implies(cpm_cons)
Expand Down
9 changes: 5 additions & 4 deletions cpmpy/solvers/pysdd.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from ..expressions.core import Expression, Comparison, Operator, BoolVal
from ..expressions.variables import _BoolVarImpl, NegBoolView, boolvar
from ..expressions.globalconstraints import DirectConstraint
from ..expressions.utils import is_any_list, is_bool
from ..expressions.utils import is_any_list, is_bool, argval, argvals
from ..transformations.decompose_global import decompose_in_tree
from ..transformations.get_variables import get_variables
from ..transformations.normalize import toplevel_list, simplify_boolean
Expand Down Expand Up @@ -121,7 +121,8 @@ def solve(self, time_limit=None, assumptions=None):
if lit in sol:
cpm_var._value = bool(sol[lit])
else:
raise ValueError(f"Var {cpm_var} is unknown to the PySDD solver, this is unexpected - please report on github...")
cpm_var._value = cpm_var.get_bounds()[0] # dummy value - TODO: ensure Pysdd assigns an actual value
# cpm_var._value = None # not specified...

return has_sol

Expand Down Expand Up @@ -177,9 +178,9 @@ def solveAll(self, display=None, time_limit=None, solution_limit=None, call_from

# display is not None:
if isinstance(display, Expression):
print(display.value())
print(argval(display))
elif isinstance(display, list):
print([v.value() for v in display])
print(argvals(display))
else:
display() # callback
return solution_count
Expand Down
12 changes: 6 additions & 6 deletions cpmpy/transformations/normalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,15 @@ def simplify_boolean(lst_of_expr, num_context=False):
if name == "==" or name == "<=":
newlist.append(recurse_negation(lhs))
if name == "<":
newlist.append(BoolVal(False))
newlist.append(0 if num_context else BoolVal(False))
if name == ">=":
newlist.append(BoolVal(True))
newlist.append(1 if num_context else BoolVal(True))
elif 0 < rhs < 1:
# a floating point value
if name == "==":
newlist.append(BoolVal(False))
newlist.append(0 if num_context else BoolVal(False))
if name == "!=":
newlist.append(BoolVal(True))
newlist.append(1 if num_context else BoolVal(True))
if name == "<" or name == "<=":
newlist.append(recurse_negation(lhs))
if name == ">" or name == ">=":
Expand All @@ -151,9 +151,9 @@ def simplify_boolean(lst_of_expr, num_context=False):
if name == "!=" or name == "<":
newlist.append(recurse_negation(lhs))
if name == ">":
newlist.append(BoolVal(False))
newlist.append(0 if num_context else BoolVal(False))
if name == "<=":
newlist.append(BoolVal(True))
newlist.append(1 if num_context else BoolVal(True))
elif rhs > 1:
newlist.append(BoolVal(name in {"!=", "<", "<="})) # all other operators evaluate to False
else:
Expand Down
Loading
Loading