diff --git a/cpmpy/expressions/globalconstraints.py b/cpmpy/expressions/globalconstraints.py index 7250b1d19..9cc88b9ce 100644 --- a/cpmpy/expressions/globalconstraints.py +++ b/cpmpy/expressions/globalconstraints.py @@ -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)): diff --git a/cpmpy/solvers/minizinc.py b/cpmpy/solvers/minizinc.py index 4e15e99d4..9b551a6c6 100644 --- a/cpmpy/solvers/minizinc.py +++ b/cpmpy/solvers/minizinc.py @@ -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): diff --git a/xcsp3/executable/callbacks.py b/xcsp3/executable/callbacks.py index 3907d7b94..f4c70c798 100644 --- a/xcsp3/executable/callbacks.py +++ b/xcsp3/executable/callbacks.py @@ -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) @@ -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) @@ -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]): @@ -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 @@ -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 @@ -752,7 +755,9 @@ 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) @@ -760,7 +765,14 @@ 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) diff --git a/xcsp3/executable/main.py b/xcsp3/executable/main.py index da60a16aa..7a799a772 100644 --- a/xcsp3/executable/main.py +++ b/xcsp3/executable/main.py @@ -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 @@ -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] @@ -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(): @@ -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() @@ -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: diff --git a/xcsp3/executable/test/conftest.py b/xcsp3/executable/test/conftest.py index 335a74813..6bc5b96f7 100644 --- a/xcsp3/executable/test/conftest.py +++ b/xcsp3/executable/test/conftest.py @@ -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`. """ @@ -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): """ @@ -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): @@ -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: