Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/xcsp3_executable' into xcsp3_exe…
Browse files Browse the repository at this point in the history
…cutable
  • Loading branch information
Wout4 committed Jun 6, 2024
2 parents 278dcd3 + dfbd92b commit a1da5c4
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 19 deletions.
3 changes: 3 additions & 0 deletions cpmpy/expressions/globalconstraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -1262,8 +1262,11 @@ def decompose(self):
Principles and Practice of Constraint Programming–CP 2004: 10th International Conference, CP 2004
"""
from .python_builtins import any as cpm_any
from .variables import NDVarArray

args, precedence = self.args
if not isinstance(args, NDVarArray):
args = cpm_array(args)
constraints = []
for s,t in zip(precedence[:-1], precedence[1:]):
for j in range(len(args)):
Expand Down
12 changes: 11 additions & 1 deletion cpmpy/solvers/minizinc.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,15 +433,25 @@ def transform(self, cpm_expr):
:return: list of Expression
"""
with TimerContext("transformation") as top_tc:
expr_store = self.expr_store

with TimerContext("toplevel_list") as tc:
cpm_cons = toplevel_list(cpm_expr)
print(f"mzn:toplevel_list took {(tc.time):.4f} -- {len(cpm_cons)}")

supported = {"min", "max", "abs", "element", "count", "nvalue", "alldifferent", "alldifferent_except0", "allequal",
"inverse", "ite" "xor", "table", "cumulative", "circuit", "subcircuit", "gcc", "increasing",
"precedence", "no_overlap",
"decreasing","strictly_increasing","strictly_decreasing", "lex_lesseq", "lex_less", "lex_chain_less",
"lex_chain_lesseq", "among"}
with TimerContext("decompose_in_tree") as tc:
cpm_cons = decompose_in_tree(cpm_cons, supported, supported_reified=supported - {"circuit", "subcircuit", "precedence"})
cpm_cons = decompose_in_tree(cpm_cons, supported, supported_reified=supported - {"circuit", "subcircuit", "precedence"}, expr_store=expr_store)
print(f"mzn:decompose_in_tree took {(tc.time):.4f} -- {len(cpm_cons)}")

print(f"ort:transformation took {(top_tc.time):.4f}")
print("final size: " + str(len(cpm_cons)))
print("STORE: " + str(len(expr_store.items())))

return cpm_cons

def __add__(self, cpm_expr):
Expand Down
26 changes: 19 additions & 7 deletions xcsp3/executable/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,10 @@ def ctr_precedence(self, lst: list[Variable], values: None | list[int], covered:
cpm_vars = self.get_cpm_vars(lst)
if values is None: # assumed to be ordered set of all values collected from domains in lst
lbs, ubs = get_bounds(cpm_vars)
values = sorted(range(min(lbs), max(lbs)+1))
values = set()
for lb, ub in zip(lbs, ubs):
values.update(list(range(lb, ub+1)))
values = sorted(values)

self.cpm_model += cp.Precedence(cpm_vars, values)

Expand Down Expand Up @@ -603,7 +606,7 @@ def ctr_binpacking(self, lst: list[Variable], sizes: list[int], condition: Condi
cpm_vars = self.get_cpm_vars(lst)
cpm_rhs = self.get_cpm_var(condition.right_operand())

for bin in range(1, len(cpm_vars)+1): # bin labeling starts at 1
for bin in range(0, len(cpm_vars)): # bin labeling starts at 0, contradicting the xcsp3 specification document?
self.cpm_model += self.eval_cpm_comp(cp.sum((cpm_array(cpm_vars) == bin) * sizes),
condition.operator,
cpm_rhs)
Expand All @@ -614,7 +617,7 @@ def ctr_binpacking_limits(self, lst: list[Variable], sizes: list[int], limits: l

for bin, lim in enumerate(limits):
self.cpm_model += eval_comparison("<=",
cp.sum((cpm_array(cpm_vars) == (bin+1)) * sizes), # bin labeling starts at 1
cp.sum((cpm_array(cpm_vars) == (bin)) * sizes),
lim)

def ctr_binpacking_loads(self, lst: list[Variable], sizes: list[int], loads: list[int] | list[Variable]):
Expand All @@ -625,7 +628,7 @@ def ctr_binpacking_loads(self, lst: list[Variable], sizes: list[int], loads: lis

for bin, load in enumerate(cpm_loads):
self.cpm_model += eval_comparison("==",
cp.sum((cpm_array(cpm_vars) == (bin + 1)) * sizes), # bin labeling starts at 1
cp.sum((cpm_array(cpm_vars) == (bin)) * sizes),
load)

def ctr_binpacking_conditions(self, lst: list[Variable], sizes: list[int], conditions: list[Condition]): # not in XCSP3-core
Expand Down Expand Up @@ -658,7 +661,7 @@ def ctr_clause(self, pos: list[Variable], neg: list[Variable]): # not in XCSP3-
self._unimplemented(pos, neg)

def ctr_circuit(self, lst: list[Variable], size: None | int | Variable): # size is None in XCSP3 competitions
return cp.SubCircuitWithStart(lst, start_index=0)
self.cpm_model += cp.SubCircuitWithStart(lst, start_index=0)

# # # # # # # # # #
# All methods about objectives to be implemented
Expand Down Expand Up @@ -752,15 +755,24 @@ def get_cpm_vars(self, lst):
if isinstance(lst[0], (XVar, int)):
return [self.get_cpm_var(x) for x in lst]
if isinstance(lst[0], range):
return list(eval(str(lst[0])))
assert len(lst) == 1, "Expected range here, but got list with multiple elements, what's the semantics???"
return list(lst[0]) # this should work without converting to str first
# return list(eval(str(lst[0])))
else:
return self.vars_from_node(lst)

def get_cpm_exprs(self, lst):
if isinstance(lst[0], XVar):
return [self.get_cpm_var(x) for x in lst]
if isinstance(lst[0], range):
return list(eval(str(lst[0])))
# assert len(lst) == 1, f"Expected range here, but got list with multiple elements, what's the semantics???{lst}"

if len(lst) == 1:
return list(lst[0]) # this should work without converting to str first
else:
return [cp.intvar(l.start, l.stop-1) for l in lst]

# return list(eval(str(lst[0])))
else:
return self.exprs_from_node(lst)

Expand Down
16 changes: 8 additions & 8 deletions xcsp3/executable/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"minizinc": ["gecode", "chuffed"]
}
DEFAULT_SOLVER = "ortools"
TIME_BUFFER = 1 # seconds
TIME_BUFFER = 5 # seconds
# TODO : see if good value
MEMORY_BUFFER_SOFT = 2 # MB
MEMORY_BUFFER_HARD = 2 # MMB
Expand Down Expand Up @@ -237,9 +237,9 @@ def __post_init__(self):
self.dir = dir_path(self.dir)
if not is_supported_solver(self.solver):
raise(ValueError(f"solver:{self.solver} is not a supported solver. Options are: {str(SUPPORTED_SOLVERS)}"))
if self.subsolver is not None:
if not is_supported_subsolver(self.solver, self.subsolver):
raise(ValueError(f"subsolver:{self.subsolver} is not a supported subsolver for solver {self.solver}. Options are: {str(SUPPORTED_SUBSOLVERS[self.solver])}"))
# if self.subsolver is not None:
# if not is_supported_subsolver(self.solver, self.subsolver):
# raise(ValueError(f"subsolver:{self.subsolver} is not a supported subsolver for solver {self.solver}. Options are: {str(SUPPORTED_SUBSOLVERS[self.solver])}"))
self.benchdir = os.path.join(*(str(self.benchpath).split(os.path.sep)[:-1]))
self.benchname = str(self.benchpath).split(os.path.sep)[-1].split(".")[0]

Expand Down Expand Up @@ -398,7 +398,7 @@ def solver_arguments(args: Args, model:cp.Model):
def subsolver_arguments(args: Args, model:cp.Model):
if args.subsolver == "gecode": return gecode_arguments(args, model)
elif args.subsolver == "chuffed": return choco_arguments(args, model)
else: raise()
else: return {}

@contextmanager
def prepend_print():
Expand Down Expand Up @@ -526,9 +526,9 @@ def run_helper(args:Args):

# ------------------------------ Parse instance ------------------------------ #

start = time.time()
parse_start = time.time()
parser = ParserXCSP3(args.benchpath)
print_comment(f"took {(time.time() - start):.4f} seconds to parse XCSP3 model [{args.benchname}]")
print_comment(f"took {(time.time() - parse_start):.4f} seconds to parse XCSP3 model [{args.benchname}]")

# -------------------------- Configure XCSP3 parser callbacks -------------------------- #
start = time.time()
Expand Down Expand Up @@ -566,7 +566,7 @@ def run_helper(args:Args):
print_comment(f"took {tc.time:.4f} seconds to transfer model to {args.solver}")

# Solve model
time_limit = args.time_limit - tc.time - args.time_buffer if args.time_limit is not None else None
time_limit = args.time_limit - (time.time() - parse_start) - args.time_buffer if args.time_limit is not None else None

# If not time left
if time_limit is not None and time_limit <= 0:
Expand Down
11 changes: 8 additions & 3 deletions xcsp3/executable/test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def get_all_instances(instance_dir: os.PathLike, year:str):
cop_instance_types = ["COP", "MiniCOP"]
csp_instance_types = ["CSP", "MiniCSP"]

def instances(type, year) -> list:
def instances(type, year, filter) -> list:
"""
Filters and aggregates problem instances based on the provided `type`.
"""
Expand All @@ -103,9 +103,13 @@ def instances(type, year) -> list:
instances = get_all_instances(INSTANCES_DIR, year)["COP"]
else:
raise()


# return instances.keys(), instances.values()
return list(instances.items())
res = list(instances.items())
if filter is not None:
res = [i for i in res if filter in i[0]]
return res

def pytest_addoption(parser):
"""
Expand All @@ -124,6 +128,7 @@ def pytest_addoption(parser):
parser.addoption("--only_transform", action="store_true")
parser.addoption("--check", action="store_true")
parser.addoption("--year", action="store", default="2022")
parser.addoption("--filter", action="store", default=None)


def pytest_generate_tests(metafunc):
Expand All @@ -132,7 +137,7 @@ def pytest_generate_tests(metafunc):
"""

# Get the test instances based on the provided filter
instance = instances(type=metafunc.config.getoption("type"), year=metafunc.config.getoption("year"))
instance = instances(type=metafunc.config.getoption("type"), year=metafunc.config.getoption("year"), filter=metafunc.config.getoption("filter"))

# The test instances to solve
if "instance" in metafunc.fixturenames:
Expand Down

0 comments on commit a1da5c4

Please sign in to comment.