diff --git a/tests/README.md b/tests/README.md index e69de29bb..c1688258a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -0,0 +1,177 @@ +# CPMpy Test Suite + +This directory contains the comprehensive test suite for CPMpy, covering all major components including variables, constraints, models, solvers, transformations, and tools. + +## Running Tests + +### Basic Usage + +Run all tests: +```bash +pytest +``` + +Run a specific test file: +```bash +pytest tests/test_model.py +``` + +Run a specific test: +```bash +pytest tests/test_model.py::TestModel::test_ndarray +``` + +### Parallelisation + +Through the `pytest-xdist` pytest plugin, running tests can be parallelised. +E.g. running with 40 workers: +```console +pytest -n 40 tests/test_model.py +``` + +Install using: +```console +pip install pytest-xdist +``` + +### Solver Selection + +The test suite supports changing the solver backend used to run the tests via the `--solver` command-line option: + +#### Single Solver +Run tests with a specific solver: +```bash +pytest --solver=gurobi +``` + +#### Multiple Solvers +Run tests with multiple solvers +- non-solver-specific tests will run against all specified solvers +- solver-specific tests will be filtered on specified solvers +```bash +pytest --solver=ortools,cplex,gurobi +``` + +#### All Installed Solvers +Run tests with all installed solvers: +```bash +pytest --solver=all +``` + +This automatically detects all installed solvers from `SolverLookup` and parametrises non-solver-specific tests to run against each one. + +#### Skip Solver Tests +Skip all solver-parametrised tests (only run tests that don't depend on solver parametrisation): +```bash +pytest --solver=None +``` + +#### Default Behavior + +If no `--solver` option is provided: +- Non-solver-specific tests run with the default solver (OR-Tools) +- All solver-specific tests run for their respective declared solver (if installed) + +## Test Organization + +### Test Files + +- **`test_model.py`** - Model creation, manipulation, and I/O +- **`test_expressions.py`** - Expression types and operations (comparisons, operators, sums, etc.) +- **`test_constraints.py`** - Constraint types and validation (boolean, comparison, reification, implication) +- **`test_globalconstraints.py`** - Global constraint implementations (AllDifferent, Circuit, Cumulative, etc.) +- **`test_solvers.py`** - Solver interface and functionality (high-level solver tests) +- **`test_solverinterface.py`** - Low-level solver interface tests (constructor, native model, solve methods) +- **`test_variables.py`** - Variable types (intvar, boolvar, shapes, naming) +- **`test_builtins.py`** - Python builtin functions (max, min, all, any) +- **`test_cse.py`** - Common subexpression elimination +- **`test_direct.py`** - Direct solver constraints (automaton, etc.) +- **`test_flatten.py`** - Model flattening transformations +- **`test_int2bool.py`** - Integer to boolean transformation +- **`test_pysat_*.py`** - PySAT-specific tests (cardinality, interrupt, weighted sum) +- **`test_solveAll.py`** - solveAll functionality across solvers +- **`test_solvers_solhint.py`** - Solver hints functionality +- **`test_tocnf.py`** - Conversion to CNF (conjunctive normal form) +- **`test_tool_dimacs.py`** - DIMACS format tools +- **`test_trans_*.py`** - Transformation tests (linearize, safen, simplify) +- **`test_transf_*.py`** - Additional transformation tests (comp, decompose, reif) +- **`test_tools_*.py`** - Tool functionality (MUS, tuning, etc.) +- **`test_examples.py`** - Run examples as a testsuite + +### Test Markers + +Tests can be marked with special markers: + +- **`@pytest.mark.requires_solver("solver_name")`** - Test requires a specific solver +- **`@pytest.mark.requires_dependency("package_name")`** - Test requires a specific Python package + +Example: +```python +@pytest.mark.requires_solver("cplex") +def test_cplex_specific_feature(): + # This test only runs if cplex is available + pass +``` + +## Writing Tests + +### Basic Test Structure + +```python +import pytest +import cpmpy as cp + +def test_basic_model(): + x = cp.intvar(0, 10, name="x") + m = cp.Model(x >= 5) + assert m.solve() + assert x.value() >= 5 +``` + +### Using the Solver Fixture + +For tests that should run with different solvers: + +```python +@pytest.mark.usefixtures("solver") +class TestMyFeature: + def test_with_solver(self): + x = cp.intvar(0, 10) + m = cp.Model(x >= 5) + assert m.solve(solver=self.solver) +``` + +When multiple solvers are provided via `--solver`, these tests will automatically be parametrised to run against each solver. + +### Solver-Parametrised Tests + +For tests that explicitly parametrise with solvers: + +```python +@pytest.mark.parametrise("solver", ["ortools", "cplex", "gurobi"]) +def test_with_explicit_solvers(solver): + x = cp.intvar(0, 10) + m = cp.Model(x >= 5) + assert m.solve(solver=solver) +``` + +### Solver-Specific Tests + +For tests that only work with specific solvers: + +```python +@pytest.mark.requires_solver("cplex") +def test_cplex_feature(): + # Test cplex-specific functionality + pass +``` + +## Contributing + +When adding new tests: + +1. Follow existing test patterns +2. Use appropriate markers for solver-specific tests +3. Ensure tests work with multiple solvers when possible +4. Add docstrings explaining what the test validates +5. Use descriptive test names \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..aa9606f06 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,315 @@ +import pytest +import cpmpy as cp +import importlib +import warnings +import logging +from typing import Optional + +logger = logging.getLogger(__name__) + +def _parse_solver_option(solver_option: Optional[str] , filter_not_installed: bool = True) -> Optional[list[str]]: + """ + Parse the --solver option into a list of solvers. + Returns 'None' if no solver was specified, otherwise returns a list of solver names. + Supports the special "all" keyword to expand to all installed solvers. + Supports the special "None" keyword to skip all solver-parametrized tests. + + Arguments: + solver_option (str): The solver option string from command line + filter_not_installed (bool): If True, filter out non-installed solvers from the result + + Returns: + list[str] | None: + A list of solver names, or 'None' if no solver was specified + Returns empty list [] if "None" was explicitly specified or solver was specified but all were filtered out + If 'filter_not_installed' is True, the list will only contain installed solvers + """ + if solver_option is None: + return None + + # Split by comma and strip whitespace + original_solvers = [s.strip() for s in solver_option.split(",") if s.strip()] + if not original_solvers: # no solver specified + warnings.warn('--solver option set, but no solver specified. Using default solver (OR-Tools).') + return None + + # Handle special "None" keyword - skip all solver-parametrized tests (no solver at all) + if "None" in original_solvers or "none" in original_solvers: + if len(original_solvers) == 1: + # Only "None" specified, return empty list to skip all solver-parametrized tests + # Non-solver tests will still run + return [] + else: + # "None" mixed with other solvers - remove it and warn + original_solvers = [s for s in original_solvers if s.lower() != "none"] + warnings.warn('Special "None" solver was specified along with other solvers. Ignoring "None" and using specified solvers.') + + # Expand "all" to all installed solvers + if "all" in original_solvers: + solvers = cp.SolverLookup.supported() + if filter_not_installed: + warnings.warn('Option "all" already expands to all installed solvers. Ignoring filter for "filter_not_installed".') + else: + solvers = original_solvers.copy() + # Filter out non-installed solvers if requested + if filter_not_installed: + solvers = [s for s in solvers if s in cp.SolverLookup.supported()] + + # If solver was provided but all were filtered out, return empty list (not None) + # This distinguishes "no solver specified" from "solver specified but not available" + if not solvers: + return [] + + return solvers if solvers else None + +def pytest_addoption(parser): + """ + Adds cli arguments to the pytest command + """ + parser.addoption( + "--solver", type=str, action="store", default=None, help="Only run the tests on these solvers. Can be a single solver, a comma-separated list (e.g., 'ortools,cplex'), 'all' to use all installed solvers, or 'None' to skip all solver-parametrized tests." + ) + +@pytest.fixture +def solver(request): + """ + Limit tests to specific solvers. + + By providing the cli argument `--solver=`, `--solver=`, `--solver=all`, or `--solver=None`, two things will happen: + - non-solver-specific tests which make a `.solve()` call will now run against all specified solvers (instead of just the default OR-Tools) + - solver-specific tests, like the ones produced through `_generate_inputs`, will be filtered if they don't match any of the specified solvers + + Special values: + - "all" expands to all installed solvers from SolverLookup + - "None" skips all solver-parametrized tests (no solver at all), only runs tests that don't depend on solver parametrization + + By not providing a value for ``--solver`, the default behaviour will be to run non-solver-specific on the default solver (OR-Tools), + and to run all solver-specific tests for which the solver has been installed on the system. + """ + # Check if solver was parametrized for test (via pytest_generate_tests or explicit parametrization) + if hasattr(request, "param"): + solver_value = request.param + else: + # Not parametrized, use command line option + # This branch is reached when: + # - Single solver provided (will use that solver) + # - No solver provided (will use None/default) + # - Empty list returned (all solvers filtered out) - use default solver (OR-Tools) + # - Multiple solvers provided but test wasn't parametrized (shouldn't happen, but uses first solver as fallback) + solver_option = request.config.getoption("--solver") + parsed_solvers = _parse_solver_option(solver_option) + # Handle empty list (all solvers filtered out) same as None + solver_value = parsed_solvers[0] if parsed_solvers else None + + # Set on class if available (for tests using self.solver) + if hasattr(request, "cls") and request.cls: + request.cls.solver = solver_value + + return solver_value + +def pytest_configure(config): + # Configure logging for test filtering information + logging.basicConfig( + level=logging.INFO, + format='%(levelname)s: %(message)s' + ) + + # Register custom marker for documentation and linting + config.addinivalue_line( + "markers", + "requires_solver(name): mark test as requiring a specific solver", # to filter tests when required solver is not installed + ) + config.addinivalue_line( + "markers", + "requires_dependency(name): mark test as requiring a specific dependency", # to filter tests when required solver is not installed + ) + + # Check for non-installed solvers and issue warnings + solver_option = config.getoption("--solver") + if solver_option: + # Parse without filtering to check original list + parsed_solvers_unfiltered = _parse_solver_option(solver_option, filter_not_installed=False) + if parsed_solvers_unfiltered: + installed_solvers = cp.SolverLookup.supported() # installed base solvers + not_installed_solvers = list(set(parsed_solvers_unfiltered) - set(installed_solvers)) + if not_installed_solvers: + warnings.warn( + f"The following solvers are not installed and will not be tested: {', '.join(not_installed_solvers)}. " + f"Only installed solvers will be used for testing.", + UserWarning, + stacklevel=2 + ) + + +def pytest_generate_tests(metafunc): + """ + Dynamically parametrize non-solver-specific tests with all provided solvers. + + When multiple solvers are provided via --solver, tests that use the 'solver' fixture + but are not already parametrized will be parametrized to run against all provided solvers. + """ + # Check if this test uses the 'solver' fixture + if "solver" not in metafunc.fixturenames: + return + + # Check if test is already parametrized with solver + # Check callspec (for tests parametrized programmatically) + if hasattr(metafunc, "callspec") and metafunc.callspec and "solver" in metafunc.callspec.params: + return + + # Check parametrize markers (for tests parametrized via @pytest.mark.parametrize) + for marker in metafunc.definition.iter_markers("parametrize"): + # marker.args[0] is the argnames (can be string or tuple/list) + argnames = marker.args[0] if marker.args else None + if argnames: + # Handle both string "solver" and tuple ("solver", ...) cases + if isinstance(argnames, str): + if argnames == "solver" or "solver" in argnames.split(","): + return + elif isinstance(argnames, (tuple, list)): + if "solver" in argnames: + return + + # Check if test has requires_solver marker (solver-specific tests) + if metafunc.definition.get_closest_marker("requires_solver"): + return + + # Get solvers from command line option + solver_option = metafunc.config.getoption("--solver") + parsed_solvers = _parse_solver_option(solver_option) + + # Only parametrize if multiple solvers are explicitly provided + # When parsed_solvers is None (no --solver specified), don't parametrize - use default solver + # When parsed_solvers is empty list (all filtered out), don't parametrize + # Only parametrize when we have 2+ solvers explicitly specified + if parsed_solvers is not None and len(parsed_solvers) > 1: + metafunc.parametrize("solver", parsed_solvers) + + +def pytest_collection_modifyitems(config, items): + """ + Centrally apply filters and skips to test targets. + + For now, only solver-based filtering gets applied. + """ + initial_count = len(items) + logger.info(f"Test suite size before filtering: {initial_count} tests") + + cmd_solver_option = config.getoption("--solver") # get cli `--solver`` arg + cmd_solvers = _parse_solver_option(cmd_solver_option) # parse into list + + if cmd_solver_option: + if cmd_solvers is None: + logger.info(f"Solver option '{cmd_solver_option}' parsed to None (using default solver)") + elif len(cmd_solvers) == 0: + logger.info(f"Solver option '{cmd_solver_option}' resulted in empty list (all solvers filtered out)") + else: + logger.info(f"Solver option '{cmd_solver_option}' parsed to: {cmd_solvers}") + else: + logger.info("No --solver option provided (using default behavior)") + + filtered = [] + skipped_dependency = 0 + skipped_solver_specific = 0 + skipped_parametrized = 0 + skipped_solver_fixture = 0 + + for item in items: + required_solver_marker = item.get_closest_marker("requires_solver") + required_dependency_marker = item.get_closest_marker("requires_dependency") + + # --------------------------------- Dependency filtering --------------------------------- # + if required_dependency_marker: + if not all(importlib.util.find_spec(dependency) is not None for dependency in required_dependency_marker.args): + skip = pytest.mark.skip(reason=f"Dependency {required_dependency_marker.args} not installed") + item.add_marker(skip) + skipped_dependency += 1 + continue + + # --------------------------------- Solver filtering --------------------------------- # + if required_solver_marker: + required_solvers = required_solver_marker.args + + # --------------------------------- Filtering -------------------------------- # + + # when solvers are specified on the command line, + # only run solver-specific tests that require any of those solvers + if cmd_solvers is not None: + # If cmd_solvers is empty (all filtered out), skip solver-specific tests + if len(cmd_solvers) == 0: + skipped_solver_specific += 1 + continue + if any(cmd_solver in required_solvers for cmd_solver in cmd_solvers): + filtered.append(item) + else: + skipped_solver_specific += 1 + continue + # instance has survived filtering + else: + filtered.append(item) + + # --------------------------------- Skipping --------------------------------- # + + # skip test if the required solver is not installed + if not {k:v for k,v in cp.SolverLookup.base_solvers()}[required_solvers[0]].supported(): + skip = pytest.mark.skip(reason=f"Solver {required_solvers[0]} not installed") + item.add_marker(skip) + + continue # skip rest of the logic for this test + + # ------------------------------ More filtering ------------------------------ # + + # Check if test uses solver fixture (for non-parametrized tests) + uses_solver_fixture = hasattr(item, "_fixtureinfo") and "solver" in getattr(item._fixtureinfo, "names_closure", []) + + # Only filter tests that are parameterized with a 'solver' (through `_generate_inputs`) + if hasattr(item, "callspec"): + if cmd_solvers is not None: + # If cmd_solvers is empty (all filtered out), skip solver-dependent tests + if len(cmd_solvers) == 0: + # Skip tests parametrized with solver, but keep others that don't depend on solver + if "solver" not in item.callspec.params and "solver_name" not in item.callspec.params: + filtered.append(item) + else: + skipped_parametrized += 1 + continue + if "solver" in item.callspec.params: + solver = item.callspec.params["solver"] + if solver in cmd_solvers: + filtered.append(item) + else: + skipped_parametrized += 1 + elif "solver_name" in item.callspec.params: + solver = item.callspec.params["solver_name"] + if solver in cmd_solvers: + filtered.append(item) + else: + skipped_parametrized += 1 + else: + # Parametrized but not with solver - keep it + filtered.append(item) + else: + filtered.append(item) + else: + # Non-parametrized tests: filter out if they use solver fixture and cmd_solvers is empty + if cmd_solvers is not None and len(cmd_solvers) == 0: + if uses_solver_fixture: + skipped_solver_fixture += 1 + continue # Skip tests that use solver fixture when no solvers available + # keep non-parametrized tests that don't depend on solver + filtered.append(item) + + final_count = len(filtered) + logger.info(f"Test suite filtering summary:") + logger.info(f" Initial tests: {initial_count}") + if skipped_dependency > 0: + logger.info(f" Skipped (missing dependency): {skipped_dependency}") + if skipped_solver_specific > 0: + logger.info(f" Skipped (solver-specific, not matching): {skipped_solver_specific}") + if skipped_parametrized > 0: + logger.info(f" Skipped (parametrized solver, not matching): {skipped_parametrized}") + if skipped_solver_fixture > 0: + logger.info(f" Skipped (solver fixture, no solvers): {skipped_solver_fixture}") + logger.info(f" Final tests: {final_count} ({final_count - initial_count:+d})") + + items[:] = filtered \ No newline at end of file diff --git a/tests/test_builtins.py b/tests/test_builtins.py index 5b1694cb8..f698e52d7 100644 --- a/tests/test_builtins.py +++ b/tests/test_builtins.py @@ -1,4 +1,5 @@ import unittest +import pytest import cpmpy as cp from cpmpy.expressions.python_builtins import all as cpm_all, any as cpm_any @@ -6,45 +7,45 @@ iv = cp.intvar(-8, 8, shape=5) - +@pytest.mark.usefixtures("solver") class TestBuiltin(unittest.TestCase): def test_max(self): constraints = [cp.max(iv) + 9 <= 8] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(cp.max(iv.value()) <= -1) model = cp.Model(cp.max(iv).decompose_comparison('!=', 4)) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertNotEqual(str(cp.max(iv.value())), '4') def test_min(self): constraints = [cp.min(iv) + 9 == 8] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertEqual(str(cp.min(iv.value())), '-1') model = cp.Model(cp.min(iv).decompose_comparison('==', 4)) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertEqual(str(cp.min(iv.value())), '4') def test_abs(self): constraints = [cp.abs(iv[0]) + 9 <= 8] model = cp.Model(constraints) - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) #with list constraints = [cp.abs(iv+2) <= 8, iv < 0] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) constraints = [cp.abs([iv[0], iv[2], iv[1], -8]) <= 8, iv < 0] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) model = cp.Model(cp.abs(iv[0]).decompose_comparison('!=', 4)) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertNotEqual(str(cp.abs(iv[0].value())), '4') # Boolean builtins diff --git a/tests/test_direct.py b/tests/test_direct.py index 4a3063a20..9e22fa9a1 100644 --- a/tests/test_direct.py +++ b/tests/test_direct.py @@ -6,7 +6,7 @@ from cpmpy import * from cpmpy.solvers import CPM_gurobi, CPM_pysat, CPM_minizinc, CPM_pysdd, CPM_z3, CPM_exact, CPM_choco, CPM_hexaly - +@pytest.mark.requires_solver("ortools") class TestDirectORTools(unittest.TestCase): def test_direct_automaton(self): @@ -26,8 +26,7 @@ def test_direct_automaton(self): self.assertEqual(model.solveAll(), 6) - -@pytest.mark.skipif(not CPM_exact.supported(), reason="Exact not installed") +@pytest.mark.requires_solver("exact") class TestDirectExact(unittest.TestCase): def test_direct_left_reif(self): @@ -40,9 +39,7 @@ def test_direct_left_reif(self): print(model) self.assertEqual(model.solveAll(), 3) - -@pytest.mark.skipif(not CPM_pysat.supported(), - reason="PySAT not installed") +@pytest.mark.requires_solver("pysat") class TestDirectPySAT(unittest.TestCase): def test_direct_clause(self): @@ -55,8 +52,7 @@ def test_direct_clause(self): self.assertTrue(model.solve()) self.assertTrue(x.value() or y.value()) -@pytest.mark.skipif(not CPM_pysdd.supported(), - reason="PySDD not installed") +@pytest.mark.requires_solver("pysdd") class TestDirectPySDD(unittest.TestCase): def test_direct_clause(self): @@ -69,8 +65,7 @@ def test_direct_clause(self): self.assertTrue(model.solve()) self.assertTrue(x.value() or y.value()) -@pytest.mark.skipif(not CPM_z3.supported(), - reason="Z3py not installed") +@pytest.mark.requires_solver("z3") class TestDirectZ3(unittest.TestCase): def test_direct_clause(self): @@ -83,8 +78,7 @@ def test_direct_clause(self): self.assertTrue(model.solve()) self.assertTrue(AllDifferent(iv).value()) -@pytest.mark.skipif(not CPM_minizinc.supported(), - reason="MinZinc not installed") +@pytest.mark.requires_solver("minizinc") class TestDirectMiniZinc(unittest.TestCase): def test_direct_clause(self): @@ -102,9 +96,7 @@ def test_direct_clause(self): self.assertTrue(model.solve()) self.assertTrue(AllDifferent(iv).value()) - -@pytest.mark.skipif(not CPM_gurobi.supported(), - reason="Gurobi not installed") +@pytest.mark.requires_solver("gurobi") class TestDirectGurobi(unittest.TestCase): def test_direct_poly(self): @@ -127,8 +119,7 @@ def test_direct_poly(self): self.assertEqual(y.value(), poly_val) -@pytest.mark.skipif(not CPM_choco.supported(), - reason="pychoco not installed") +@pytest.mark.requires_solver("choco") class TestDirectChoco(unittest.TestCase): def test_direct_global(self): @@ -142,8 +133,7 @@ def test_direct_global(self): self.assertFalse(model.solve()) -@pytest.mark.skipif(not CPM_hexaly.supported(), - reason="hexaly is not installed") +@pytest.mark.requires_solver("hexaly") class TestDirectHexaly(unittest.TestCase): def test_direct_distance(self): diff --git a/tests/test_examples.py b/tests/test_examples.py index 1db1a819a..72c23be36 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -95,4 +95,17 @@ def test_advanced_example(example): """Loads the advanced example file and executes its __main__ block with no default solver set.""" if any(skip_name in example for skip_name in SKIPPED_EXAMPLES): pytest.skip(f"Skipped {example}, waiting for issues to be resolved") - test_example(None, example) + try: + test_example(None, example) + except Exception as e: + # Check if the exception indicates a missing solver installation (or other optional dependencies) + # TODO: this is a hack to skip tests when the solver is not installed, + # for now no better way to do this without having to manually label all + # examples with the required solvers / dependencies + error_msg = str(e).lower() + if ("install" in error_msg and ("package" in error_msg or "solver" in error_msg)) or \ + ("not installed" in error_msg) or \ + ("not available" in error_msg): + pytest.skip(f"Skipped, solver not installed: {e}") + # Re-raise other exceptions + raise diff --git a/tests/test_expressions.py b/tests/test_expressions.py index 74520dc2b..46fde2de5 100644 --- a/tests/test_expressions.py +++ b/tests/test_expressions.py @@ -1,4 +1,5 @@ import unittest +import pytest import cpmpy as cp import numpy as np @@ -8,6 +9,7 @@ from cpmpy.expressions.core import Comparison, Operator, Expression from cpmpy.expressions.utils import eval_comparison, get_bounds, argval +@pytest.mark.usefixtures("solver") class TestComparison(unittest.TestCase): def test_comps(self): # from the docs @@ -30,8 +32,9 @@ def test_comps(self): bv & (iv != 0), # allowed ) for c in m.constraints: - self.assertTrue(cp.Model(c).solve()) + self.assertTrue(cp.Model(c).solve(solver=self.solver)) +@pytest.mark.usefixtures("solver") class TestSum(unittest.TestCase): def setUp(self): @@ -74,7 +77,7 @@ def test_subadd_iv_int(self): def test_sum_unary(self): v = cp.intvar(1,9) model = cp.Model(v>=1, minimize=sum([v])) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertEqual(v.value(), 1) class TestWeightedSum(unittest.TestCase): @@ -219,19 +222,20 @@ def test_nullarg_mul(self): for expr in prod.args: self.assertTrue(isinstance(expr, Expression) or expr == 0) +@pytest.mark.usefixtures("solver") class TestArrayExpressions(unittest.TestCase): def test_sum(self): x = intvar(0,5,shape=10, name="x") y = intvar(0, 1000, name="y") model = cp.Model(y == x.sum()) - model.solve() + model.solve(solver=self.solver) self.assertTrue(y.value() == sum(x.value())) # with axis arg x = intvar(0,5,shape=(10,10), name="x") y = intvar(0, 1000, shape=10, name="y") model = cp.Model(y == x.sum(axis=0)) - model.solve() + model.solve(solver=self.solver) res = np.array([sum(x[i, ...].value()) for i in range(len(y))]) self.assertTrue(all(y.value() == res)) @@ -239,7 +243,7 @@ def test_prod(self): x = intvar(0,5,shape=10, name="x") y = intvar(0, 1000, name="y") model = cp.Model(y == x.prod()) - model.solve() + model.solve(solver=self.solver) res = 1 for v in x: res *= v.value() @@ -248,7 +252,7 @@ def test_prod(self): x = intvar(0,5,shape=(10,10), name="x") y = intvar(0, 1000, shape=10, name="y") model = cp.Model(y == x.prod(axis=0)) - model.solve() + model.solve(solver=self.solver) for i,vv in enumerate(x): res = 1 for v in vv: @@ -259,13 +263,13 @@ def test_max(self): x = intvar(0,5,shape=10, name="x") y = intvar(0, 1000, name="y") model = cp.Model(y == x.max()) - model.solve() + model.solve(solver=self.solver) self.assertTrue(y.value() == max(x.value())) # with axis arg x = intvar(0,5,shape=(10,10), name="x") y = intvar(0, 1000, shape=10, name="y") model = cp.Model(y == x.max(axis=0)) - model.solve() + model.solve(solver=self.solver) res = np.array([max(x[i, ...].value()) for i in range(len(y))]) self.assertTrue(all(y.value() == res)) @@ -273,13 +277,13 @@ def test_min(self): x = intvar(0,5,shape=10, name="x") y = intvar(0, 1000, name="y") model = cp.Model(y == x.min()) - model.solve() + model.solve(solver=self.solver) self.assertTrue(y.value() == min(x.value())) # with axis arg x = intvar(0,5,shape=(10,10), name="x") y = intvar(0, 1000, shape=10, name="y") model = cp.Model(y == x.min(axis=0)) - model.solve() + model.solve(solver=self.solver) res = np.array([min(x[i, ...].value()) for i in range(len(y))]) self.assertTrue(all(y.value() == res)) @@ -288,13 +292,13 @@ def test_any(self): x = boolvar(shape=10, name="x") y = boolvar(name="y") model = cp.Model(y == x.any()) - model.solve() + model.solve(solver=self.solver) self.assertTrue(y.value() == cpm_any(x.value())) # with axis arg x = boolvar(shape=(10,10), name="x") y = boolvar(shape=10, name="y") model = cp.Model(y == x.any(axis=0)) - model.solve() + model.solve(solver=self.solver) res = np.array([cpm_any(x[i, ...].value()) for i in range(len(y))]) self.assertTrue(all(y.value() == res)) @@ -304,13 +308,13 @@ def test_all(self): x = boolvar(shape=10, name="x") y = boolvar(name="y") model = cp.Model(y == x.all()) - model.solve() + model.solve(solver=self.solver) self.assertTrue(y.value() == cpm_all(x.value())) # with axis arg x = boolvar(shape=(10,10), name="x") y = boolvar(shape=10, name="y") model = cp.Model(y == x.all(axis=0)) - model.solve() + model.solve(solver=self.solver) res = np.array([cpm_all(x[i, ...].value()) for i in range(len(y))]) self.assertTrue(all(y.value() == res)) @@ -330,6 +334,7 @@ def test_multidim(self): def inclusive_range(lb,ub): return range(lb,ub+1) +@pytest.mark.usefixtures("solver") class TestBounds(unittest.TestCase): def test_bounds_mul_sub_sum(self): x = intvar(-8,8) @@ -459,7 +464,7 @@ def test_incomplete_func(self): cons = (arr[i] == 1).implies(p) m = cp.Model([cons, i == 5]) - self.assertTrue(m.solve()) + self.assertTrue(m.solve(solver=self.solver)) self.assertTrue(cons.value()) # div constraint @@ -507,20 +512,20 @@ def test_not_operator(self): p = boolvar() q = boolvar() x = intvar(0,9) - self.assertTrue(cp.Model([~p]).solve()) + self.assertTrue(cp.Model([~p]).solve(solver=self.solver)) #self.assertRaises(cp.exceptions.TypeError, cp.Model([~x]).solve()) - self.assertTrue(cp.Model([~(x == 0)]).solve()) - self.assertTrue(cp.Model([~~p]).solve()) - self.assertTrue(cp.Model([~(p & p)]).solve()) - self.assertTrue(cp.Model([~~~~~(p & p)]).solve()) - self.assertTrue(cp.Model([~cpm_array([p,q,p])]).solve()) - self.assertTrue(cp.Model([~p.implies(q)]).solve()) - self.assertTrue(cp.Model([~p.implies(~q)]).solve()) - self.assertTrue(cp.Model([p.implies(~q)]).solve()) - self.assertTrue(cp.Model([p == ~q]).solve()) - self.assertTrue(cp.Model([~~p == ~q]).solve()) - self.assertTrue(cp.Model([Operator('not',[p]) == q]).solve()) - self.assertTrue(cp.Model([Operator('not',[p])]).solve()) + self.assertTrue(cp.Model([~(x == 0)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([~~p]).solve(solver=self.solver)) + self.assertTrue(cp.Model([~(p & p)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([~~~~~(p & p)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([~cpm_array([p,q,p])]).solve(solver=self.solver)) + self.assertTrue(cp.Model([~p.implies(q)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([~p.implies(~q)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([p.implies(~q)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([p == ~q]).solve(solver=self.solver)) + self.assertTrue(cp.Model([~~p == ~q]).solve(solver=self.solver)) + self.assertTrue(cp.Model([Operator('not',[p]) == q]).solve(solver=self.solver)) + self.assertTrue(cp.Model([Operator('not',[p])]).solve(solver=self.solver)) def test_description(self): @@ -557,7 +562,7 @@ def test_description(self): def test_dtype(self): x = cp.intvar(1,10,shape=(3,3), name="x") - self.assertTrue(cp.Model(cp.sum(x) >= 10).solve()) + self.assertTrue(cp.Model(cp.sum(x) >= 10).solve(solver=self.solver)) self.assertIsNotNone(x.value()) # test all types of expressions self.assertEqual(int, type(x[0,0].value())) # just the var diff --git a/tests/test_flatten.py b/tests/test_flatten.py index 5384c5260..367d31aef 100644 --- a/tests/test_flatten.py +++ b/tests/test_flatten.py @@ -1,8 +1,10 @@ import unittest +import pytest import cpmpy as cp from cpmpy.transformations.flatten_model import * from cpmpy.expressions.variables import _IntVarImpl, _BoolVarImpl +@pytest.mark.usefixtures("solver") class TestFlattenModel(unittest.TestCase): def setUp(self): self.ivars = cp.intvar(1, 10, (5,)) @@ -27,14 +29,14 @@ def test_objective(self): def test_abs(self): l = cp.intvar(0,9, shape=3) # bounds used to be computed wrong, making both unsat - self.assertTrue( cp.Model(abs(l[0]-l[1])- abs(l[2]-l[1]) < 0).solve() ) - self.assertTrue( cp.Model(abs(l[0]-l[1])- abs(l[2]-l[1]) > 0).solve() ) + self.assertTrue( cp.Model(abs(l[0]-l[1])- abs(l[2]-l[1]) < 0).solve(solver=self.solver) ) + self.assertTrue( cp.Model(abs(l[0]-l[1])- abs(l[2]-l[1]) > 0).solve(solver=self.solver) ) def test_mod(self): iv1 = cp.intvar(2,9) iv2 = cp.intvar(5,9) m = cp.Model([(iv1+iv2) % 2 >= 0, (iv1+iv2) % 2 <= 1]) - self.assertTrue( m.solve() ) + self.assertTrue( m.solve(solver=self.solver) ) class TestFlattenConstraint(unittest.TestCase): diff --git a/tests/test_globalconstraints.py b/tests/test_globalconstraints.py index 9608949db..ebd33af61 100644 --- a/tests/test_globalconstraints.py +++ b/tests/test_globalconstraints.py @@ -11,7 +11,7 @@ from utils import skip_on_missing_pblib - +@pytest.mark.usefixtures("solver") @skip_on_missing_pblib(skip_on_exception_only=True) class TestGlobal(unittest.TestCase): def test_alldifferent(self): @@ -33,7 +33,7 @@ def test_alldifferent(self): # SOLVE if True: - _ = model.solve() + _ = model.solve(solver=self.solver) vals = [x.value() for x in vars] # ensure all different values @@ -52,7 +52,7 @@ def test_alldifferent2(self): iv = cp.intvar(0,4, shape=3) c = cp.AllDifferent(iv) for (vals, oracle) in tuples: - ret = cp.Model(c, iv == vals).solve() + ret = cp.Model(c, iv == vals).solve(solver=self.solver) assert (ret == oracle), f"Mismatch solve for {vals,oracle}" # don't try this at home, forcibly overwrite variable values (so even when ret=false) for (var,val) in zip(iv,vals): @@ -80,7 +80,7 @@ def test_alldifferent_except0(self): iv = cp.intvar(0,4, shape=3) c = cp.AllDifferentExcept0(iv) for (vals, oracle) in tuples: - ret = cp.Model(c, iv == vals).solve() + ret = cp.Model(c, iv == vals).solve(solver=self.solver) assert (ret == oracle), f"Mismatch solve for {vals,oracle}" # don't try this at home, forcibly overwrite variable values (so even when ret=false) for (var,val) in zip(iv,vals): @@ -89,14 +89,14 @@ def test_alldifferent_except0(self): # and some more iv = cp.intvar(-8, 8, shape=3) - self.assertTrue(cp.Model([cp.AllDifferentExcept0(iv)]).solve()) + self.assertTrue(cp.Model([cp.AllDifferentExcept0(iv)]).solve(solver=self.solver)) self.assertTrue(cp.AllDifferentExcept0(iv).value()) - self.assertTrue(cp.Model([cp.AllDifferentExcept0(iv), iv == [0,0,1]]).solve()) + self.assertTrue(cp.Model([cp.AllDifferentExcept0(iv), iv == [0,0,1]]).solve(solver=self.solver)) self.assertTrue(cp.AllDifferentExcept0(iv).value()) #test with mixed types bv = cp.boolvar() - self.assertTrue(cp.Model([cp.AllDifferentExcept0(iv[0], bv)]).solve()) + self.assertTrue(cp.Model([cp.AllDifferentExcept0(iv[0], bv)]).solve(solver=self.solver)) def test_alldifferent_except_n(self): # test known input/outputs @@ -111,7 +111,7 @@ def test_alldifferent_except_n(self): iv = cp.intvar(0, 4, shape=3) c = cp.AllDifferentExceptN(iv, 2) for (vals, oracle) in tuples: - ret = cp.Model(c, iv == vals).solve() + ret = cp.Model(c, iv == vals).solve(solver=self.solver) assert (ret == oracle), f"Mismatch solve for {vals, oracle}" # don't try this at home, forcibly overwrite variable values (so even when ret=false) for (var, val) in zip(iv, vals): @@ -120,25 +120,25 @@ def test_alldifferent_except_n(self): # and some more iv = cp.intvar(-8, 8, shape=3) - self.assertTrue(cp.Model([cp.AllDifferentExceptN(iv,2)]).solve()) + self.assertTrue(cp.Model([cp.AllDifferentExceptN(iv,2)]).solve(solver=self.solver)) self.assertTrue(cp.AllDifferentExceptN(iv,2).value()) - self.assertTrue(cp.Model([cp.AllDifferentExceptN(iv,7), iv == [7, 7, 1]]).solve()) + self.assertTrue(cp.Model([cp.AllDifferentExceptN(iv,7), iv == [7, 7, 1]]).solve(solver=self.solver)) self.assertTrue(cp.AllDifferentExceptN(iv,7).value()) # test with mixed types bv = cp.boolvar() - self.assertTrue(cp.Model([cp.AllDifferentExceptN([iv[0], bv],4)]).solve()) + self.assertTrue(cp.Model([cp.AllDifferentExceptN([iv[0], bv],4)]).solve(solver=self.solver)) # test with list of n iv = cp.intvar(0, 4, shape=7) - self.assertFalse(cp.Model([cp.AllDifferentExceptN([iv], [7,8])]).solve()) - self.assertTrue(cp.Model([cp.AllDifferentExceptN([iv], [4, 1])]).solve()) + self.assertFalse(cp.Model([cp.AllDifferentExceptN([iv], [7,8])]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.AllDifferentExceptN([iv], [4, 1])]).solve(solver=self.solver)) def test_not_alldifferentexcept0(self): iv = cp.intvar(-8, 8, shape=3) - self.assertTrue(cp.Model([~cp.AllDifferentExcept0(iv)]).solve()) + self.assertTrue(cp.Model([~cp.AllDifferentExcept0(iv)]).solve(solver=self.solver)) self.assertFalse(cp.AllDifferentExcept0(iv).value()) - self.assertFalse(cp.Model([~cp.AllDifferentExcept0(iv), iv == [0, 0, 1]]).solve()) + self.assertFalse(cp.Model([~cp.AllDifferentExcept0(iv), iv == [0, 0, 1]]).solve(solver=self.solver)) def test_alldifferent_onearg(self): iv = cp.intvar(0,10) @@ -167,46 +167,46 @@ def test_circuit(self): x = cp.intvar(0, 5, 6) constraints = [cp.Circuit(x)] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(cp.Circuit(x).value()) constraints = [cp.Circuit(x).decompose()] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(cp.Circuit(x).value()) # Test with domain (-2,7) x = cp.intvar(-2, 7, 6) circuit = cp.Circuit(x) model = cp.Model([circuit]) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(circuit.value()) model = cp.Model([~circuit]) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertFalse(circuit.value()) # Test decomposition with domain (-2,7) constraints = [cp.Circuit(x).decompose()] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(cp.Circuit(x).value()) # Test with smaller domain (1,5) x = cp.intvar(1, 5, 5) circuit = cp.Circuit(x) model = cp.Model([circuit]) - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) self.assertFalse(circuit.value()) model = cp.Model([~circuit]) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertFalse(circuit.value()) # Test decomposition with domain (1,5) constraints = [cp.Circuit(x).decompose()] model = cp.Model(constraints) - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) self.assertFalse(cp.Circuit(x).value()) @@ -214,12 +214,12 @@ def test_not_circuit(self): x = cp.intvar(lb=-1, ub=5, shape=4) circuit = cp.Circuit(x) model = cp.Model([~circuit, x == [1,2,3,0]]) - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) model = cp.Model([~circuit]) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertFalse(circuit.value()) - self.assertFalse(cp.Model([circuit, ~circuit]).solve()) + self.assertFalse(cp.Model([circuit, ~circuit]).solve(solver=self.solver)) all_sols = set() not_all_sols = set() @@ -256,12 +256,12 @@ def test_inverse(self): # Test decomposed model: model = cp.Model(inv.decompose(), fix_fwd) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertEqual(list(rev.value()), expected_inverse) # Not decomposed: model = cp.Model(inv, fix_fwd) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertEqual(list(rev.value()), expected_inverse) # constraint can be used as value @@ -283,46 +283,46 @@ def test_InDomain(self): iv_arr = cp.intvar(-8, 8, shape=5) cons = [cp.InDomain(iv, iv_arr)] model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertIn(iv.value(), iv_arr.value()) vals = [1, 5, 8, -4] cons = [cp.InDomain(iv, vals)] model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertIn(iv.value(), vals) cons = [cp.InDomain(iv, [])] model = cp.Model(cons) - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) cons = [cp.InDomain(iv, [1])] model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertEqual(iv.value(),1) cons = cp.InDomain(cp.min(iv_arr), vals) model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) iv2 = cp.intvar(-8, 8) vals = [1, 5, 8, -4, iv2] cons = [cp.InDomain(iv, vals)] model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertIn(iv.value(), argvals(vals)) vals = [1, 5, 8, -4] bv = cp.boolvar() cons = [cp.InDomain(bv, vals)] model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertIn(bv.value(), set(vals)) vals = [iv2, 5, 8, -4] bv = cp.boolvar() cons = [cp.InDomain(bv, vals)] model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertIn(bv.value(), argvals(vals)) vals = [bv & bv, 5, 8, -4] bv = cp.boolvar() cons = [cp.InDomain(bv, vals)] model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertIn(bv.value(), argvals(vals)) def test_lex_lesseq(self): @@ -333,7 +333,7 @@ def test_lex_lesseq(self): c = cp.LexLessEq(X, Y) c2 = c != (BoolVal(True)) m = cp.Model([c1, c2]) - self.assertTrue(m.solve()) + self.assertTrue(m.solve(solver=self.solver)) self.assertTrue(c2.value()) self.assertFalse(c.value()) @@ -352,7 +352,7 @@ def test_lex_less(self): c = cp.LexLess(X, Y) c2 = c != (BoolVal(True)) m = cp.Model([c1, c2]) - self.assertTrue(m.solve()) + self.assertTrue(m.solve(solver=self.solver)) self.assertTrue(c2.value()) self.assertFalse(c.value()) @@ -377,7 +377,7 @@ def test_lex_chain(self): c = cp.LexChainLess([X, Y]) c2 = c != (BoolVal(True)) m = cp.Model([c1, c2]) - self.assertTrue(m.solve()) + self.assertTrue(m.solve(solver=self.solver)) self.assertTrue(c2.value()) self.assertFalse(c.value()) @@ -388,13 +388,16 @@ def test_lex_chain(self): from cpmpy.expressions.utils import argval self.assertTrue(sum(argval(X)) == 0) - Z = cp.intvar(0, 1, shape=(3,2)) + Z = cp.intvar(0, 1, shape=(4,2)) c = cp.LexChainLess(Z) m = cp.Model(c) - self.assertTrue(m.solve()) + self.assertTrue(m.solve(solver=self.solver)) self.assertTrue(sum(argval(Z[0])) == 0) self.assertTrue(sum(argval(Z[1])) == 1) - self.assertTrue(sum(argval(Z[2])) >= 1) + self.assertTrue(argval(Z[1,0]) == 0) + self.assertTrue(sum(argval(Z[2])) == 1) + self.assertTrue(argval(Z[2,1]) == 0) + self.assertTrue(sum(argval(Z[3])) >= 1) def test_indomain_onearg(self): @@ -413,70 +416,70 @@ def test_table(self): constraints = [cp.Table([iv[0], iv[1], iv[2]], [ (5, 2, 2)])] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) model = cp.Model(constraints[0].decompose()) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) constraints = [cp.Table(iv, [[10, 8, 2], [5, 2, 2]])] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) model = cp.Model(constraints[0].decompose()) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(cp.Table(iv, [[10, 8, 2], [5, 2, 2]]).value()) self.assertFalse(cp.Table(iv, [[10, 8, 2], [5, 3, 2]]).value()) constraints = [cp.Table(iv, [[10, 8, 2], [5, 9, 2]])] model = cp.Model(constraints) - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) constraints = [cp.Table(iv, [[10, 8, 2], [5, 9, 2]])] model = cp.Model(constraints[0].decompose()) - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) def test_negative_table(self): iv = cp.intvar(-8,8,3) constraints = [cp.NegativeTable([iv[0], iv[1], iv[2]], [ (5, 2, 2)])] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) model = cp.Model(constraints[0].decompose()) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) constraints = [cp.NegativeTable(iv, [[10, 8, 2], [5, 2, 2]])] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) model = cp.Model(constraints[0].decompose()) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(cp.NegativeTable(iv, [[10, 8, 2], [5, 2, 2]]).value()) constraints = [~cp.NegativeTable(iv, [[10, 8, 2], [5, 2, 2]])] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertFalse(cp.NegativeTable(iv, [[10, 8, 2], [5, 2, 2]]).value()) self.assertTrue(cp.Table(iv, [[10, 8, 2], [5, 2, 2]]).value()) constraints = [cp.NegativeTable(iv, [[10, 8, 2], [5, 9, 2]])] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) constraints = [cp.NegativeTable(iv, [[10, 8, 2], [5, 9, 2]])] model = cp.Model(constraints[0].decompose()) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) constraints = [cp.NegativeTable(iv, [[10, 8, 2], [5, 9, 2]]), cp.Table(iv, [[10, 8, 2], [5, 9, 2]])] model = cp.Model(constraints) - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) constraints = [cp.NegativeTable(iv, [[10, 8, 2], [5, 9, 2]]), cp.Table(iv, [[10, 8, 2], [5, 9, 2]])] model = cp.Model(constraints[0].decompose()) model += constraints[1].decompose() - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) def test_shorttable(self): iv = cp.intvar(-8,8,shape=3, name="x") @@ -485,17 +488,17 @@ def test_shorttable(self): cons = cp.ShortTable([iv[0], iv[1], iv[2]], [ (5, 2, 2)]) model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) model = cp.Model(cons.decompose()) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) short_cons = cp.ShortTable(iv, [[10, 8, 2], ['*', '*', 2]]) model = cp.Model(short_cons) self.assertTrue(model.solve(solver=solver)) model = cp.Model(short_cons.decompose()) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(short_cons.value()) self.assertEqual(iv[-1].value(), 2) @@ -507,7 +510,7 @@ def test_shorttable(self): short_cons = cp.ShortTable(iv, [[10, 8, STAR], [5, 9, STAR]]) model = cp.Model(short_cons.decompose()) - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) # unconstrained true_cons = cp.ShortTable(iv, [[1,2,3],[STAR, STAR, STAR]]) @@ -547,8 +550,8 @@ def test_regular(self): true_model = cp.Model(cp.Regular(x, transitions, start, ends)) false_model = cp.Model(~cp.Regular(x, transitions, start, ends)) - num_true = true_model.solveAll(display=lambda : true_sols.add(tuple(argvals(x)))) - num_false = false_model.solveAll(display=lambda : false_sols.add(tuple(argvals(x)))) + num_true = true_model.solveAll(solver=self.solver, display=lambda : true_sols.add(tuple(argvals(x)))) + num_false = false_model.solveAll(solver=self.solver, display=lambda : false_sols.add(tuple(argvals(x)))) self.assertEqual(num_true, len(solutions)) self.assertSetEqual(true_sols, set(solutions)) @@ -561,11 +564,11 @@ def test_minimum(self): iv = cp.intvar(-8, 8, 3) constraints = [cp.Minimum(iv) + 9 == 8] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertEqual(str(min(iv.value())), '-1') model = cp.Model(cp.Minimum(iv).decompose_comparison('==', 4)) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertEqual(str(min(iv.value())), '4') def test_minimum_onearg(self): @@ -583,11 +586,11 @@ def test_maximum(self): iv = cp.intvar(-8, 8, 3) constraints = [cp.Maximum(iv) + 9 <= 8] model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(max(iv.value()) <= -1) model = cp.Model(cp.Maximum(iv).decompose_comparison('!=', 4)) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertNotEqual(str(max(iv.value())), '4') def test_maximum_onearg(self): @@ -606,25 +609,25 @@ def test_abs(self): iv = cp.intvar(-8, 8, name="x") constraints = [cp.Abs(iv) + 9 <= 8] model = cp.Model(constraints) - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) constraints = [cp.Abs(iv - 4) + 1 > 12] model = cp.Model(constraints) - self.assertTrue(model.solve()) - self.assertTrue(cp.Model(decompose_in_tree(constraints)).solve()) #test with decomposition + self.assertTrue(model.solve(solver=self.solver)) + self.assertTrue(cp.Model(decompose_in_tree(constraints)).solve(solver=self.solver)) #test with decomposition model = cp.Model(cp.Abs(iv).decompose_comparison('!=', 4)) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertNotEqual(str(abs(iv.value())), '4') - self.assertEqual(model.solveAll(display=iv), 15) + self.assertEqual(model.solveAll(solver=self.solver, display=iv), 15) pos = cp.intvar(0,8, name="x") constraints = [cp.Abs(pos) != 4] - self.assertEqual(cp.Model(decompose_in_tree(constraints)).solveAll(), 8) + self.assertEqual(cp.Model(decompose_in_tree(constraints)).solveAll(solver=self.solver), 8) neg = cp.intvar(-8,0, name="x") constraints = [cp.Abs(neg) != 4] - self.assertEqual(cp.Model(decompose_in_tree(constraints)).solveAll(), 8) + self.assertEqual(cp.Model(decompose_in_tree(constraints)).solveAll(solver=self.solver), 8) def test_element(self): @@ -634,13 +637,13 @@ def test_element(self): # test directly the constraint cons = cp.Element(iv,idx) == 8 model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(cons.value()) self.assertEqual(iv.value()[idx.value()], 8) # test through __get_item__ cons = iv[idx] == 8 model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(cons.value()) self.assertEqual(iv.value()[idx.value()], 8) # test 2-D @@ -648,13 +651,13 @@ def test_element(self): a,b = cp.intvar(0, 2, shape=2) cons = iv[a,b] == 8 model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(cons.value()) self.assertEqual(iv.value()[a.value(), b.value()], 8) arr = cp.cpm_array([[1, 2, 3], [4, 5, 6]]) cons = arr[a,b] == 1 model = cp.Model(cons) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(cons.value()) self.assertEqual(arr[a.value(), b.value()], 1) # test optimization where 1 dim is index @@ -691,7 +694,7 @@ def test_element_onearg(self): def test_xor(self): bv = cp.boolvar(5) - self.assertTrue(cp.Model(cp.Xor(bv)).solve()) + self.assertTrue(cp.Model(cp.Xor(bv)).solve(solver=self.solver)) self.assertTrue(cp.Xor(bv).value()) def test_xor_with_constants(self): @@ -709,43 +712,43 @@ def test_xor_with_constants(self): expr = cp.Xor(args) model = cp.Model(expr) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(expr.value()) # also check with decomposition model = cp.Model(expr.decompose()) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertTrue(expr.value()) # edge case with False constants - self.assertFalse(cp.Model(cp.Xor([False, False])).solve()) - self.assertFalse(cp.Model(cp.Xor([False, False, False])).solve()) + self.assertFalse(cp.Model(cp.Xor([False, False])).solve(solver=self.solver)) + self.assertFalse(cp.Model(cp.Xor([False, False, False])).solve(solver=self.solver)) def test_ite_with_constants(self): x,y,z = cp.boolvar(shape=3) expr = cp.IfThenElse(True, y, z) - self.assertTrue(cp.Model(expr).solve()) + self.assertTrue(cp.Model(expr).solve(solver=self.solver)) self.assertTrue(expr.value()) expr = cp.IfThenElse(False, y, z) - self.assertTrue(cp.Model(expr).solve()) + self.assertTrue(cp.Model(expr).solve(solver=self.solver)) expr = cp.IfThenElse(x, y, z) - self.assertTrue(cp.Model(~expr).solve()) + self.assertTrue(cp.Model(~expr).solve(solver=self.solver)) self.assertFalse(expr.value()) x,y, z = x.value(), y.value(), z.value() - self.assertTrue((x and z) or (not x and y)) + self.assertTrue((x and not y) or (not x and not z)) def test_not_xor(self): bv = cp.boolvar(5) - self.assertTrue(cp.Model(~cp.Xor(bv)).solve()) + self.assertTrue(cp.Model(~cp.Xor(bv)).solve(solver=self.solver)) self.assertFalse(cp.Xor(bv).value()) - nbNotModels = cp.Model(~cp.Xor(bv)).solveAll(display=lambda: self.assertFalse(cp.Xor(bv).value())) - nbModels = cp.Model(cp.Xor(bv)).solveAll(display=lambda: self.assertTrue(cp.Xor(bv).value())) - nbDecompModels = cp.Model(cp.Xor(bv).decompose()).solveAll(display=lambda: self.assertTrue(cp.Xor(bv).value())) + nbNotModels = cp.Model(~cp.Xor(bv)).solveAll(solver=self.solver, display=lambda: self.assertFalse(cp.Xor(bv).value())) + nbModels = cp.Model(cp.Xor(bv)).solveAll(solver=self.solver, display=lambda: self.assertTrue(cp.Xor(bv).value())) + nbDecompModels = cp.Model(cp.Xor(bv).decompose()).solveAll(solver=self.solver, display=lambda: self.assertTrue(cp.Xor(bv).value())) self.assertEqual(nbDecompModels,nbModels) - total = cp.Model(bv == bv).solveAll() + total = cp.Model(bv == bv).solveAll(solver=self.solver) self.assertEqual(str(total), str(nbModels + nbNotModels)) def test_minimax_python(self): @@ -762,7 +765,7 @@ def test_minimax_cpm(self): self.assertIsInstance(ma, GlobalFunction) def solve_return(model): - model.solve() + model.solve(solver=self.solver) return model.objective_value() self.assertEqual( solve_return(cp.Model([], minimize=mi)), 1) self.assertEqual( solve_return(cp.Model([], minimize=ma)), 1) @@ -791,7 +794,7 @@ def test_cumulative_single_demand(self): demand = 1 capacity = 1 m += cp.Cumulative(start, duration, end, demand, capacity) - self.assertTrue(m.solve()) + self.assertTrue(m.solve(solver=self.solver)) def test_cumulative_decomposition_capacity(self): import numpy as np @@ -803,9 +806,9 @@ def test_cumulative_decomposition_capacity(self): demand = 10 # tasks cannot be scheduled capacity = np.int64(5) # bug only happened with numpy ints cons = cp.Cumulative(start, duration, end, demand, capacity) - self.assertFalse(cp.Model(cons).solve()) # this worked fine + self.assertFalse(cp.Model(cons).solve(solver=self.solver)) # this worked fine # also test decomposition - self.assertFalse(cp.Model(cons.decompose()).solve()) # capacity was not taken into account and this failed + self.assertFalse(cp.Model(cons.decompose()).solve(solver=self.solver)) # capacity was not taken into account and this failed def test_cumulative_nested_expressions(self): import numpy as np @@ -817,9 +820,9 @@ def test_cumulative_nested_expressions(self): demand = 10 # tasks cannot be scheduled capacity = np.int64(5) # bug only happened with numpy ints cons = cp.Cumulative(start, duration, end, demand, capacity) - self.assertFalse(cp.Model(cons).solve()) # this worked fine + self.assertFalse(cp.Model(cons).solve(solver=self.solver)) # this worked fine # also test decomposition - self.assertFalse(cp.Model(cons.decompose()).solve()) # capacity was not taken into account and this failed + self.assertFalse(cp.Model(cons.decompose()).solve(solver=self.solver)) # capacity was not taken into account and this failed @pytest.mark.skipif(not CPM_minizinc.supported(), reason="Minizinc not installed") @@ -862,10 +865,10 @@ def test_cumulative_no_np(self): demand = 1 capacity = 1 cons = cp.Cumulative(start, duration, end, demand, capacity) - self.assertTrue(cp.Model(cons).solve()) + self.assertTrue(cp.Model(cons).solve(solver=self.solver)) self.assertTrue(cons.value()) # also test decomposition - self.assertTrue(cp.Model(cons.decompose()).solve()) + self.assertTrue(cp.Model(cons.decompose()).solve(solver=self.solver)) self.assertTrue(cons.value()) def test_cumulative_no_np2(self): @@ -875,43 +878,43 @@ def test_cumulative_no_np2(self): demand = [1,1,1,1] capacity = 1 cons = cp.Cumulative(start, duration, end, demand, capacity) - self.assertTrue(cp.Model(cons).solve()) + self.assertTrue(cp.Model(cons).solve(solver=self.solver)) self.assertTrue(cons.value()) # also test decomposition - self.assertTrue(cp.Model(cons.decompose()).solve()) + self.assertTrue(cp.Model(cons.decompose()).solve(solver=self.solver)) self.assertTrue(cons.value()) def test_ite(self): x = cp.intvar(0, 5, shape=3, name="x") iter = cp.IfThenElse(x[0] > 2, x[1] > x[2], x[1] == x[2]) constraints = [iter] - self.assertTrue(cp.Model(constraints).solve()) + self.assertTrue(cp.Model(constraints).solve(solver=self.solver)) constraints = [iter, x == [0, 4, 4]] - self.assertTrue(cp.Model(constraints).solve()) + self.assertTrue(cp.Model(constraints).solve(solver=self.solver)) constraints = [iter, x == [4, 4, 3]] - self.assertTrue(cp.Model(constraints).solve()) + self.assertTrue(cp.Model(constraints).solve(solver=self.solver)) constraints = [iter, x == [4, 4, 4]] - self.assertFalse(cp.Model(constraints).solve()) + self.assertFalse(cp.Model(constraints).solve(solver=self.solver)) constraints = [iter, x == [1, 3, 2]] - self.assertFalse(cp.Model(constraints).solve()) + self.assertFalse(cp.Model(constraints).solve(solver=self.solver)) def test_global_cardinality_count(self): iv = cp.intvar(-8, 8, shape=5) val = cp.intvar(-3, 3, shape=3) occ = cp.intvar(0, len(iv), shape=3) - self.assertTrue(cp.Model([cp.GlobalCardinalityCount(iv, val, occ), cp.AllDifferent(val)]).solve()) + self.assertTrue(cp.Model([cp.GlobalCardinalityCount(iv, val, occ), cp.AllDifferent(val)]).solve(solver=self.solver)) self.assertTrue(cp.GlobalCardinalityCount(iv, val, occ).value()) self.assertTrue(all(cp.Count(iv, val[i]).value() == occ[i].value() for i in range(len(val)))) val = [1, 4, 5] - self.assertTrue(cp.Model([cp.GlobalCardinalityCount(iv, val, occ)]).solve()) + self.assertTrue(cp.Model([cp.GlobalCardinalityCount(iv, val, occ)]).solve(solver=self.solver)) self.assertTrue(cp.GlobalCardinalityCount(iv, val, occ).value()) self.assertTrue(all(cp.Count(iv, val[i]).value() == occ[i].value() for i in range(len(val)))) occ = [2, 3, 0] - self.assertTrue(cp.Model([cp.GlobalCardinalityCount(iv, val, occ)]).solve()) + self.assertTrue(cp.Model([cp.GlobalCardinalityCount(iv, val, occ)]).solve(solver=self.solver)) self.assertTrue(cp.GlobalCardinalityCount(iv, val, occ).value()) self.assertTrue(all(cp.Count(iv, val[i]).value() == occ[i] for i in range(len(val)))) self.assertTrue(cp.GlobalCardinalityCount([iv[0],iv[2],iv[1],iv[4],iv[3]], val, occ).value()) @@ -920,15 +923,15 @@ def test_not_global_cardinality_count(self): iv = cp.intvar(-8, 8, shape=5) val = cp.intvar(-3, 3, shape=3) occ = cp.intvar(0, len(iv), shape=3) - self.assertTrue(cp.Model([~cp.GlobalCardinalityCount(iv, val, occ), cp.AllDifferent(val)]).solve()) + self.assertTrue(cp.Model([~cp.GlobalCardinalityCount(iv, val, occ), cp.AllDifferent(val)]).solve(solver=self.solver)) self.assertTrue(~cp.GlobalCardinalityCount(iv, val, occ).value()) self.assertFalse(all(cp.Count(iv, val[i]).value() == occ[i].value() for i in range(len(val)))) val = [1, 4, 5] - self.assertTrue(cp.Model([~cp.GlobalCardinalityCount(iv, val, occ)]).solve()) + self.assertTrue(cp.Model([~cp.GlobalCardinalityCount(iv, val, occ)]).solve(solver=self.solver)) self.assertTrue(~cp.GlobalCardinalityCount(iv, val, occ).value()) self.assertFalse(all(cp.Count(iv, val[i]).value() == occ[i].value() for i in range(len(val)))) occ = [2, 3, 0] - self.assertTrue(cp.Model([~cp.GlobalCardinalityCount(iv, val, occ)]).solve()) + self.assertTrue(cp.Model([~cp.GlobalCardinalityCount(iv, val, occ)]).solve(solver=self.solver)) self.assertTrue(~cp.GlobalCardinalityCount(iv, val, occ).value()) self.assertFalse(all(cp.Count(iv, val[i]).value() == occ[i] for i in range(len(val)))) self.assertTrue(~cp.GlobalCardinalityCount([iv[0],iv[2],iv[1],iv[4],iv[3]], val, occ).value()) @@ -946,20 +949,20 @@ def test_gcc_onearg(self): def test_count(self): iv = cp.intvar(-8, 8, shape=3) - self.assertTrue(cp.Model([iv[0] == 0, iv[1] != 1, iv[2] != 2, cp.Count(iv, 0) == 3]).solve()) + self.assertTrue(cp.Model([iv[0] == 0, iv[1] != 1, iv[2] != 2, cp.Count(iv, 0) == 3]).solve(solver=self.solver)) self.assertEqual(str(iv.value()),'[0 0 0]') x = cp.intvar(-8,8) y = cp.intvar(0,5) - self.assertTrue(cp.Model(cp.Count(iv, x) == y).solve()) + self.assertTrue(cp.Model(cp.Count(iv, x) == y).solve(solver=self.solver)) self.assertEqual(str(cp.Count(iv, x).value()), str(y.value())) - self.assertTrue(cp.Model(cp.Count(iv, x) != y).solve()) - self.assertTrue(cp.Model(cp.Count(iv, x) >= y).solve()) - self.assertTrue(cp.Model(cp.Count(iv, x) <= y).solve()) - self.assertTrue(cp.Model(cp.Count(iv, x) < y).solve()) - self.assertTrue(cp.Model(cp.Count(iv, x) > y).solve()) + self.assertTrue(cp.Model(cp.Count(iv, x) != y).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.Count(iv, x) >= y).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.Count(iv, x) <= y).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.Count(iv, x) < y).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.Count(iv, x) > y).solve(solver=self.solver)) - self.assertTrue(cp.Model(cp.Count([iv[0],iv[2],iv[1]], x) > y).solve()) + self.assertTrue(cp.Model(cp.Count([iv[0],iv[2],iv[1]], x) > y).solve(solver=self.solver)) def test_count_onearg(self): @@ -977,15 +980,15 @@ def test_nvalue(self): iv = cp.intvar(-8, 8, shape=3) cnt = cp.intvar(0,10) - self.assertFalse(cp.Model(cp.all(iv == 1), cp.NValue(iv) > 1).solve()) - self.assertTrue(cp.Model(cp.all(iv == 1), cp.NValue(iv) > cnt).solve()) + self.assertFalse(cp.Model(cp.all(iv == 1), cp.NValue(iv) > 1).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.all(iv == 1), cp.NValue(iv) > cnt).solve(solver=self.solver)) self.assertGreater(len(set(iv.value())), cnt.value()) - self.assertTrue(cp.Model(cp.NValue(iv) != cnt).solve()) - self.assertTrue(cp.Model(cp.NValue(iv) >= cnt).solve()) - self.assertTrue(cp.Model(cp.NValue(iv) <= cnt).solve()) - self.assertTrue(cp.Model(cp.NValue(iv) < cnt).solve()) - self.assertTrue(cp.Model(cp.NValue(iv) > cnt).solve()) + self.assertTrue(cp.Model(cp.NValue(iv) != cnt).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.NValue(iv) >= cnt).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.NValue(iv) <= cnt).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.NValue(iv) < cnt).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.NValue(iv) > cnt).solve(solver=self.solver)) # test nested bv = cp.boolvar() @@ -993,11 +996,11 @@ def test_nvalue(self): def check_true(): self.assertTrue(cons.value()) - cp.Model(cons).solveAll(display=check_true) + cp.Model(cons).solveAll(solver=self.solver, display=check_true) # test not contiguous iv = cp.intvar(0, 10, shape=(3, 3)) - self.assertTrue(cp.Model([cp.NValue(i) == 3 for i in iv.T]).solve()) + self.assertTrue(cp.Model([cp.NValue(i) == 3 for i in iv.T]).solve(solver=self.solver)) def test_nvalue_except(self): @@ -1005,18 +1008,18 @@ def test_nvalue_except(self): cnt = cp.intvar(0, 10) - self.assertFalse(cp.Model(cp.all(iv == 1), cp.NValueExcept(iv, 6) > 1).solve()) - self.assertTrue(cp.Model(cp.NValueExcept(iv, 10) > 1).solve()) - self.assertTrue(cp.Model(cp.all(iv == 1), cp.NValueExcept(iv, 1) == 0).solve()) - self.assertTrue(cp.Model(cp.all(iv == 1), cp.NValueExcept(iv, 6) > cnt).solve()) + self.assertFalse(cp.Model(cp.all(iv == 1), cp.NValueExcept(iv, 6) > 1).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.NValueExcept(iv, 10) > 1).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.all(iv == 1), cp.NValueExcept(iv, 1) == 0).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.all(iv == 1), cp.NValueExcept(iv, 6) > cnt).solve(solver=self.solver)) self.assertGreater(len(set(iv.value())), cnt.value()) val = 6 - self.assertTrue(cp.Model(cp.NValueExcept(iv, val) != cnt).solve()) - self.assertTrue(cp.Model(cp.NValueExcept(iv, val) >= cnt).solve()) - self.assertTrue(cp.Model(cp.NValueExcept(iv, val) <= cnt).solve()) - self.assertTrue(cp.Model(cp.NValueExcept(iv, val) < cnt).solve()) - self.assertTrue(cp.Model(cp.NValueExcept(iv, val) > cnt).solve()) + self.assertTrue(cp.Model(cp.NValueExcept(iv, val) != cnt).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.NValueExcept(iv, val) >= cnt).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.NValueExcept(iv, val) <= cnt).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.NValueExcept(iv, val) < cnt).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.NValueExcept(iv, val) > cnt).solve(solver=self.solver)) # test nested bv = cp.boolvar() @@ -1025,13 +1028,13 @@ def test_nvalue_except(self): def check_true(): self.assertTrue(cons.value()) - cp.Model(cons).solveAll(display=check_true) + cp.Model(cons).solveAll(solver=self.solver, display=check_true) # test not contiguous iv = cp.intvar(0, 10, shape=(3, 3)) - self.assertTrue(cp.Model([cp.NValueExcept(i, val) == 3 for i in iv.T]).solve()) - + self.assertTrue(cp.Model([cp.NValueExcept(i, val) == 3 for i in iv.T]).solve(solver=self.solver)) + @pytest.mark.requires_solver("minizinc") @pytest.mark.skipif(not CPM_minizinc.supported(), reason="Minizinc not installed") def test_nvalue_minizinc(self): @@ -1062,35 +1065,36 @@ def test_precedence(self): iv = cp.intvar(0,5, shape=6, name="x") cons = cp.Precedence(iv, [0,2,1]) - self.assertTrue(cp.Model([cons, iv == [5,0,2,0,0,1]]).solve()) + self.assertTrue(cp.Model([cons, iv == [5,0,2,0,0,1]]).solve(solver=self.solver)) self.assertTrue(cons.value()) - self.assertTrue(cp.Model([cons, iv == [0,0,0,0,0,0]]).solve()) + self.assertTrue(cp.Model([cons, iv == [0,0,0,0,0,0]]).solve(solver=self.solver)) self.assertTrue(cons.value()) - self.assertFalse(cp.Model([cons, iv == [0,1,2,0,0,0]]).solve()) + self.assertFalse(cp.Model([cons, iv == [0,1,2,0,0,0]]).solve(solver=self.solver)) cons = cp.Precedence([iv[0], iv[1], 4], [0, 1, 2]) # python list in stead of cpm_array - self.assertTrue(cp.Model([cons]).solve()) + self.assertTrue(cp.Model([cons]).solve(solver=self.solver)) # Check bug fix pull request #742 # - ensure first constraint from paper is satisfied cons = cp.Precedence(iv, [0, 1, 2]) - self.assertFalse(cp.Model([cons, (iv[0] == 1) | (iv[0] == 2)]).solve()) + self.assertFalse(cp.Model([cons, (iv[0] == 1) | (iv[0] == 2)]).solve(solver=self.solver)) def test_no_overlap(self): start = cp.intvar(0,5, shape=3) end = cp.intvar(0,5, shape=3) cons = cp.NoOverlap(start, [2,1,1], end) - self.assertTrue(cp.Model(cons).solve()) + self.assertTrue(cp.Model(cons).solve(solver=self.solver)) self.assertTrue(cons.value()) - self.assertTrue(cp.Model(cons.decompose()).solve()) + self.assertTrue(cp.Model(cons.decompose()).solve(solver=self.solver)) self.assertTrue(cons.value()) def check_val(): assert cons.value() is False - cp.Model(~cons).solveAll(display=check_val) + cp.Model(~cons).solveAll(solver=self.solver, display=check_val) +@pytest.mark.usefixtures("solver") class TestBounds(unittest.TestCase): def test_bounds_minimum(self): x = cp.intvar(-8, 8) @@ -1100,8 +1104,8 @@ def test_bounds_minimum(self): lb,ub = expr.get_bounds() self.assertEqual(lb,-8) self.assertEqual(ub,-1) - self.assertFalse(cp.Model(exprub).solve()) + self.assertFalse(cp.Model(exprub).solve(solver=self.solver)) def test_bounds_maximum(self): @@ -1112,8 +1116,8 @@ def test_bounds_maximum(self): lb,ub = expr.get_bounds() self.assertEqual(lb,1) self.assertEqual(ub,9) - self.assertFalse(cp.Model(exprub).solve()) + self.assertFalse(cp.Model(exprub).solve(solver=self.solver)) def test_bounds_abs(self): x = cp.intvar(-8, 5) @@ -1132,8 +1136,8 @@ def test_bounds_element(self): lb, ub = expr.get_bounds() self.assertEqual(lb,-8) self.assertEqual(ub,9) - self.assertFalse(cp.Model(expr < lb).solve()) - self.assertFalse(cp.Model(expr > ub).solve()) + self.assertFalse(cp.Model(expr < lb).solve(solver=self.solver)) + self.assertFalse(cp.Model(expr > ub).solve(solver=self.solver)) def test_bounds_count(self): x = cp.intvar(-8, 8) @@ -1144,14 +1148,15 @@ def test_bounds_count(self): lb, ub = expr.get_bounds() self.assertEqual(lb,0) self.assertEqual(ub,3) - self.assertFalse(cp.Model(expr < lb).solve()) - self.assertFalse(cp.Model(expr > ub).solve()) + self.assertFalse(cp.Model(expr < lb).solve(solver=self.solver)) + self.assertFalse(cp.Model(expr > ub).solve(solver=self.solver)) def test_bounds_xor(self): # just one case of a Boolean global constraint expr = cp.Xor(cp.boolvar(3)) self.assertEqual(expr.get_bounds(),(0,1)) +@pytest.mark.usefixtures("solver") @skip_on_missing_pblib(skip_on_exception_only=True) class TestTypeChecks(unittest.TestCase): def test_AllDiff(self): @@ -1159,44 +1164,44 @@ def test_AllDiff(self): y = cp.intvar(-7, -1) b = cp.boolvar() a = cp.boolvar() - self.assertTrue(cp.Model([cp.AllDifferent(x,y)]).solve()) - self.assertTrue(cp.Model([cp.AllDifferent(a,b)]).solve()) - self.assertTrue(cp.Model([cp.AllDifferent(x,y,b)]).solve()) + self.assertTrue(cp.Model([cp.AllDifferent(x,y)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.AllDifferent(a,b)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.AllDifferent(x,y,b)]).solve(solver=self.solver)) def test_allDiffEx0(self): x = cp.intvar(-8, 8) y = cp.intvar(-7, -1) b = cp.boolvar() a = cp.boolvar() - self.assertTrue(cp.Model([cp.AllDifferentExcept0(x,y)]).solve()) - self.assertTrue(cp.Model([cp.AllDifferentExcept0(a,b)]).solve()) - #self.assertTrue(cp.Model([cp.AllDifferentExcept0(x,y,b)]).solve()) + self.assertTrue(cp.Model([cp.AllDifferentExcept0(x,y)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.AllDifferentExcept0(a,b)]).solve(solver=self.solver)) + #self.assertTrue(cp.Model([cp.AllDifferentExcept0(x,y,b)]).solve(solver=self.solver)) def test_allEqual(self): x = cp.intvar(-8, 8) y = cp.intvar(-7, -1) b = cp.boolvar() a = cp.boolvar() - self.assertTrue(cp.Model([cp.AllEqual(x,y,-1)]).solve()) - self.assertTrue(cp.Model([cp.AllEqual(a,b,False, a | b)]).solve()) - self.assertFalse(cp.Model([cp.AllEqual(x,y,b)]).solve()) + self.assertTrue(cp.Model([cp.AllEqual(x,y,-1)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.AllEqual(a,b,False, a | b)]).solve(solver=self.solver)) + self.assertFalse(cp.Model([cp.AllEqual(x,y,b)]).solve(solver=self.solver)) def test_allEqualExceptn(self): x = cp.intvar(-8, 8) y = cp.intvar(-7, -1) b = cp.boolvar() a = cp.boolvar() - self.assertTrue(cp.Model([cp.AllEqualExceptN([x,y,-1],211)]).solve()) - self.assertTrue(cp.Model([cp.AllEqualExceptN([x,y,-1,4],4)]).solve()) - self.assertTrue(cp.Model([cp.AllEqualExceptN([x,y,-1,4],-1)]).solve()) - self.assertTrue(cp.Model([cp.AllEqualExceptN([a,b,False, a | b], 4)]).solve()) - self.assertTrue(cp.Model([cp.AllEqualExceptN([a,b,False, a | b], 0)]).solve()) - self.assertTrue(cp.Model([cp.AllEqualExceptN([a,b,False, a | b, y], -1)]).solve()) + self.assertTrue(cp.Model([cp.AllEqualExceptN([x,y,-1],211)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.AllEqualExceptN([x,y,-1,4],4)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.AllEqualExceptN([x,y,-1,4],-1)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.AllEqualExceptN([a,b,False, a | b], 4)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.AllEqualExceptN([a,b,False, a | b], 0)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.AllEqualExceptN([a,b,False, a | b, y], -1)]).solve(solver=self.solver)) # test with list of n iv = cp.intvar(0, 4, shape=7) - self.assertFalse(cp.Model([cp.AllEqualExceptN([iv], [7,8]), iv[0] != iv[1]]).solve()) - self.assertTrue(cp.Model([cp.AllEqualExceptN([iv], [4, 1]), iv[0] != iv[1]]).solve()) + self.assertFalse(cp.Model([cp.AllEqualExceptN([iv], [7,8]), iv[0] != iv[1]]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.AllEqualExceptN([iv], [4, 1]), iv[0] != iv[1]]).solve(solver=self.solver)) def test_not_allEqualExceptn(self): x = cp.intvar(lb=0, ub=3, shape=3) @@ -1204,21 +1209,21 @@ def test_not_allEqualExceptn(self): constr = cp.AllEqualExceptN(x,n) model = cp.Model([~constr, x == [1, 2, 1]]) - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) model = cp.Model([~constr]) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertFalse(constr.value()) - self.assertFalse(cp.Model([constr, ~constr]).solve()) + self.assertFalse(cp.Model([constr, ~constr]).solve(solver=self.solver)) all_sols = set() not_all_sols = set() - circuit_models = cp.Model(constr).solveAll(display=lambda: all_sols.add(tuple(x.value()))) - not_circuit_models = cp.Model(~constr).solveAll(display=lambda: not_all_sols.add(tuple(x.value()))) + circuit_models = cp.Model(constr).solveAll(solver=self.solver, display=lambda: all_sols.add(tuple(x.value()))) + not_circuit_models = cp.Model(~constr).solveAll(solver=self.solver, display=lambda: not_all_sols.add(tuple(x.value()))) - total = cp.Model(x == x).solveAll() + total = cp.Model(x == x).solveAll(solver=self.solver) for sol in all_sols: for var, val in zip(x, sol): @@ -1238,58 +1243,58 @@ def test_increasing(self): 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()) + self.assertTrue(cp.Model([cp.Increasing(x,y)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.Increasing(a,b)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.Increasing(x,y,b)]).solve(solver=self.solver)) z = cp.intvar(2,5) - self.assertFalse(cp.Model([cp.Increasing(z,b)]).solve()) + self.assertFalse(cp.Model([cp.Increasing(z,b)]).solve(solver=self.solver)) 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()) + self.assertTrue(cp.Model([cp.Decreasing(x,y)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.Decreasing(a,b)]).solve(solver=self.solver)) + self.assertFalse(cp.Model([cp.Decreasing(x,y,b)]).solve(solver=self.solver)) z = cp.intvar(2,5) - self.assertTrue(cp.Model([cp.Decreasing(z,b)]).solve()) + self.assertTrue(cp.Model([cp.Decreasing(z,b)]).solve(solver=self.solver)) 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()) + self.assertTrue(cp.Model([cp.IncreasingStrict(x,y)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.IncreasingStrict(a,b)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.IncreasingStrict(x,y,b)]).solve(solver=self.solver)) z = cp.intvar(1,5) - self.assertFalse(cp.Model([cp.IncreasingStrict(z,b)]).solve()) + self.assertFalse(cp.Model([cp.IncreasingStrict(z,b)]).solve(solver=self.solver)) 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()) + self.assertTrue(cp.Model([cp.DecreasingStrict(x,y)]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.DecreasingStrict(a,b)]).solve(solver=self.solver)) + self.assertFalse(cp.Model([cp.DecreasingStrict(x,y,b)]).solve(solver=self.solver)) z = cp.intvar(1,5) - self.assertTrue(cp.Model([cp.DecreasingStrict(z,b)]).solve()) + self.assertTrue(cp.Model([cp.DecreasingStrict(z,b)]).solve(solver=self.solver)) def test_circuit(self): x = cp.intvar(-8, 8) y = cp.intvar(-7, -1) b = cp.boolvar() a = cp.boolvar() - self.assertTrue(cp.Model([cp.Circuit(x+2,2,0)]).solve()) + self.assertTrue(cp.Model([cp.Circuit(x+2,2,0)]).solve(solver=self.solver)) self.assertRaises(TypeError,cp.Circuit,(a,b)) self.assertRaises(TypeError,cp.Circuit,(x,y,b)) def test_multicicruit(self): c1 = cp.Circuit(cp.intvar(0,4, shape=5)) c2 = cp.Circuit(cp.intvar(0,2, shape=3)) - self.assertTrue(cp.Model(c1 & c2).solve()) + self.assertTrue(cp.Model(c1 & c2).solve(solver=self.solver)) def test_inverse(self): @@ -1297,7 +1302,7 @@ def test_inverse(self): y = cp.intvar(-7, -1) b = cp.boolvar() a = cp.boolvar() - self.assertFalse(cp.Model([cp.Inverse([x,y,x],[x,y,x])]).solve()) + self.assertFalse(cp.Model([cp.Inverse([x,y,x],[x,y,x])]).solve(solver=self.solver)) self.assertRaises(TypeError,cp.Inverse,[a,b],[x,y]) self.assertRaises(TypeError,cp.Inverse,[a,b],[b,False]) @@ -1306,7 +1311,7 @@ def test_ITE(self): y = cp.intvar(-7, -1) b = cp.boolvar() a = cp.boolvar() - self.assertTrue(cp.Model([cp.IfThenElse(b,b&a,False)]).solve()) + self.assertTrue(cp.Model([cp.IfThenElse(b,b&a,False)]).solve(solver=self.solver)) self.assertRaises(TypeError, cp.IfThenElse,a,b,0) self.assertRaises(TypeError, cp.IfThenElse,1,x,y) @@ -1315,35 +1320,35 @@ def test_min(self): y = cp.intvar(-7, -1) b = cp.boolvar() a = cp.boolvar() - self.assertTrue(cp.Model([cp.Minimum([x,y]) == x]).solve()) - self.assertTrue(cp.Model([cp.Minimum([a,b | a]) == b]).solve()) - self.assertTrue(cp.Model([cp.Minimum([x,y,b]) == -2]).solve()) + self.assertTrue(cp.Model([cp.Minimum([x,y]) == x]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.Minimum([a,b | a]) == b]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.Minimum([x,y,b]) == -2]).solve(solver=self.solver)) def test_max(self): x = cp.intvar(-8, 8) y = cp.intvar(-7, -1) b = cp.boolvar() a = cp.boolvar() - self.assertTrue(cp.Model([cp.Maximum([x,y]) == x]).solve()) - self.assertTrue(cp.Model([cp.Maximum([a,b | a]) == b]).solve()) - self.assertTrue(cp.Model([cp.Maximum([x,y,b]) == 2 ]).solve()) + self.assertTrue(cp.Model([cp.Maximum([x,y]) == x]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.Maximum([a,b | a]) == b]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.Maximum([x,y,b]) == 2 ]).solve(solver=self.solver)) def test_element(self): x = cp.intvar(-8, 8) y = cp.intvar(-7, -1) b = cp.boolvar() a = cp.boolvar() - self.assertTrue(cp.Model([cp.Element([x,y],x) == x]).solve()) - self.assertTrue(cp.Model([cp.Element([a,b | a],x) == b]).solve()) + self.assertTrue(cp.Model([cp.Element([x,y],x) == x]).solve(solver=self.solver)) + self.assertTrue(cp.Model([cp.Element([a,b | a],x) == b]).solve(solver=self.solver)) self.assertRaises(TypeError,cp.Element,[x,y],b) - self.assertTrue(cp.Model([cp.Element([y,a],x) == False]).solve()) + self.assertTrue(cp.Model([cp.Element([y,a],x) == False]).solve(solver=self.solver)) def test_xor(self): x = cp.intvar(-8, 8) y = cp.intvar(-7, -1) b = cp.boolvar() a = cp.boolvar() - self.assertTrue(cp.Model([cp.Xor([a,b,b])]).solve()) + self.assertTrue(cp.Model([cp.Xor([a,b,b])]).solve(solver=self.solver)) self.assertRaises(TypeError, cp.Xor, (x, b)) self.assertRaises(TypeError, cp.Xor, (x, y)) @@ -1355,7 +1360,7 @@ def test_cumulative(self): b = cp.boolvar() a = cp.boolvar() - self.assertTrue(cp.Model([cp.Cumulative([x,y],[x,2],[z,q],1,x)]).solve()) + self.assertTrue(cp.Model([cp.Cumulative([x,y],[x,2],[z,q],1,x)]).solve(solver=self.solver)) self.assertRaises(TypeError, cp.Cumulative, [x,y],[x,y],[a,y],1,x) self.assertRaises(TypeError, cp.Cumulative, [x,y],[x,y],[x,y],1,x) self.assertRaises(TypeError, cp.Cumulative, [x,y],[x,y],[x,y],x,False) @@ -1394,7 +1399,7 @@ def test_count(self): b = cp.boolvar() a = cp.boolvar() - self.assertTrue(cp.Model([cp.Count([x,y],z) == 1]).solve()) + self.assertTrue(cp.Model([cp.Count([x,y],z) == 1]).solve(solver=self.solver)) self.assertRaises(TypeError, cp.Count, [x,y],[x,False]) def test_among(self): @@ -1420,7 +1425,7 @@ def test_table(self): constraints = [cp.Table([iv[0], [iv[1], iv[2]]], [ (5, 2, 2)])] # not flatlist, should work model = cp.Model(constraints) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertRaises(TypeError, cp.Table, [iv[0], iv[1], iv[2], 5], [(5, 2, 2)]) self.assertRaises(TypeError, cp.Table, [iv[0], iv[1], iv[2], [5]], [(5, 2, 2)]) @@ -1439,9 +1444,9 @@ def test_issue627(self): def test_issue_699(self): x,y = cp.intvar(0,10, shape=2, name=tuple("xy")) - self.assertTrue(cp.Model(cp.AllDifferentExcept0([x,0,y,0]).decompose()).solve()) - self.assertTrue(cp.Model(cp.AllDifferentExceptN([x,3,y,0], 3).decompose()).solve()) - self.assertTrue(cp.Model(cp.AllDifferentExceptN([x,3,y,0], [3,0]).decompose()).solve()) + self.assertTrue(cp.Model(cp.AllDifferentExcept0([x,0,y,0]).decompose()).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.AllDifferentExceptN([x,3,y,0], 3).decompose()).solve(solver=self.solver)) + self.assertTrue(cp.Model(cp.AllDifferentExceptN([x,3,y,0], [3,0]).decompose()).solve(solver=self.solver)) def test_element_index_dom_mismatched(self): diff --git a/tests/test_model.py b/tests/test_model.py index 9b917d8fa..1e8c3686f 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -9,7 +9,7 @@ from cpmpy.expressions.utils import flatlist from cpmpy.expressions.variables import NullShapeError, _IntVarImpl, _BoolVarImpl, NegBoolView, NDVarArray - +@pytest.mark.usefixtures("solver") class TestModel(unittest.TestCase): def setUp(self) -> None: @@ -25,7 +25,7 @@ def test_ndarray(self): iv = cp.intvar(1,9, shape=3) m = cp.Model( iv > 3 ) m += (iv[0] == 5) - self.assertTrue(m.solve()) + self.assertTrue(m.solve(solver=self.solver)) def test_empty(self): m = cp.Model() @@ -41,7 +41,7 @@ def test_io_nempty(self): with pytest.warns(UserWarning): loaded = cp.Model.from_file(fname) - self.assertTrue(loaded.solve()) + self.assertTrue(loaded.solve(solver=self.solver)) os.remove(fname) def test_io_counters(self): @@ -73,12 +73,12 @@ def test_copy(self): memodict = dict() m_dcopy = m.copy() print(memodict) - m_dcopy.solve() + m_dcopy.solve(solver=self.solver) self.assertTrue(cons1.value()) self.assertTrue(cons2.value()) - m.solve() + m.solve(solver=self.solver) m2 = m.copy() @@ -97,13 +97,13 @@ def test_deepcopy(self): memodict = dict() m_dcopy = copy.deepcopy(m, memodict) - m_dcopy.solve() + m_dcopy.solve(solver=self.solver) self.assertIsNone(cons1.value()) self.assertIsNone(cons2.value()) self.assertIsNone(cons3.value()) - m.solve() + m.solve(solver=self.solver) m2 = copy.deepcopy(m) diff --git a/tests/test_pysat_cardinality.py b/tests/test_pysat_cardinality.py index 828478da8..80c9fac5b 100644 --- a/tests/test_pysat_cardinality.py +++ b/tests/test_pysat_cardinality.py @@ -10,8 +10,7 @@ SOLVER = "pysat" -@pytest.mark.skipif(not CPM_pysat.supported(), - reason="PySAT not installed") +@pytest.mark.requires_solver("pysat") class TestCardinality(unittest.TestCase): def setUp(self): diff --git a/tests/test_pysat_interrupt.py b/tests/test_pysat_interrupt.py index 08ec6fb7f..8844dc1a8 100644 --- a/tests/test_pysat_interrupt.py +++ b/tests/test_pysat_interrupt.py @@ -24,8 +24,7 @@ def frietkot(): model = Model(allwishes) return model, [mayo, ketchup, curry, andalouse, samurai] -@pytest.mark.skipif(not CPM_pysat.supported(), - reason="PySAT not installed") +@pytest.mark.requires_solver("pysat") class TestPySATInterrupt(unittest.TestCase): def test_small_isntance_no_interrupt(self): """Check if the instance still returns the expected results diff --git a/tests/test_pysat_wsum.py b/tests/test_pysat_wsum.py index 1de62dde3..8d7a37b15 100644 --- a/tests/test_pysat_wsum.py +++ b/tests/test_pysat_wsum.py @@ -1,14 +1,12 @@ import unittest import pytest +import importlib import cpmpy as cp from cpmpy import * from cpmpy.solvers.pysat import CPM_pysat -import importlib # can check for modules *without* importing them -pysat_available = CPM_pysat.supported() -pblib_available = importlib.util.find_spec("pypblib") is not None - -@pytest.mark.skipif(not (pysat_available and not pblib_available), reason="`pysat` is not installed" if not pysat_available else "`pypblib` is installed") +@pytest.mark.requires_solver("pysat") +@pytest.mark.skipif(importlib.util.find_spec("pypblib") is not None, reason="Test requires pypblib to be NOT installed") def test_pypblib_error(): # NOTE if you want to run this but pypblib is already installed, run `pip uninstall pypblib && pip install -e .[pysat]` unittest.TestCase().assertRaises( @@ -19,7 +17,8 @@ def test_pypblib_error(): # this one should still work without `pypblib` assert CPM_pysat(cp.Model(1*cp.boolvar() + 1 * cp.boolvar() + 1 * cp.boolvar() <= 2)).solve() -@pytest.mark.skipif(not (pysat_available and pblib_available), reason="`pysat` is not installed" if not pysat_available else "`pypblib` not installed") +@pytest.mark.requires_solver("pysat") +@pytest.mark.requires_dependency("pypblib") class TestEncodePseudoBooleanConstraint(unittest.TestCase): def setUp(self): self.bv = boolvar(shape=3) diff --git a/tests/test_solvers.py b/tests/test_solvers.py index 21697b82b..62ba83e4a 100644 --- a/tests/test_solvers.py +++ b/tests/test_solvers.py @@ -1,6 +1,4 @@ import inspect -import importlib -import inspect import unittest import tempfile import pytest @@ -23,9 +21,7 @@ from utils import skip_on_missing_pblib -pysat_available = CPM_pysat.supported() -pblib_available = importlib.util.find_spec("pypblib") is not None - +@pytest.mark.usefixtures("solver") class TestSolvers(unittest.TestCase): @@ -52,7 +48,7 @@ def test_tsp(self): objective = (x*distance_matrix).sum() model = cp.Model(constraint, minimize=objective) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertEqual(model.objective_value(), 214) self.assertEqual(x.value().tolist(), [[0, 0, 0, 0, 0, 1], @@ -62,6 +58,7 @@ def test_tsp(self): [0, 0, 0, 1, 0, 0], [1, 0, 0, 0, 0, 0]]) + @pytest.mark.requires_solver("ortools") def test_ortools(self): b = cp.boolvar() x = cp.intvar(1,13, shape=3) @@ -79,16 +76,17 @@ def test_ortools(self): t = cp.Table([x[0],x[1]], [[2,6],[7,3]]) m = cp.Model(t, minimize=x[0]) - self.assertTrue(m.solve()) + self.assertTrue(m.solve(solver=self.solver)) self.assertEqual( m.objective_value(), 2 ) m = cp.Model(t, maximize=x[0]) - self.assertTrue(m.solve()) + self.assertTrue(m.solve(solver=self.solver)) self.assertEqual( m.objective_value(), 7 ) # modulo - self.assertTrue( cp.Model([ x[0] == x[1] % x[2] ]).solve() ) + self.assertTrue( cp.Model([ x[0] == x[1] % x[2] ]).solve(solver=self.solver) ) + @pytest.mark.requires_solver("ortools") def test_ortools_inverse(self): from cpmpy.solvers.ortools import CPM_ortools @@ -106,7 +104,7 @@ def test_ortools_inverse(self): self.assertTrue(solver.solve()) self.assertEqual(list(rev.value()), expected_inverse) - + @pytest.mark.requires_solver("ortools") def test_ortools_direct_solver(self): """ Test direct solver access. @@ -237,6 +235,7 @@ def on_solution_callback(self): self.assertFalse(s.solve(assumptions=bv)) self.assertTrue(len(s.get_core()) > 0) + @pytest.mark.requires_solver("ortools") # this test fails on OR-tools version <9.6 def test_ortools_version(self): @@ -276,6 +275,7 @@ def test_ortools_version(self): self.assertTrue(model.solve(solver="ortools")) # this is a bug in ortools version 9.5, upgrade to version >=9.6 using pip install --upgrade ortools + @pytest.mark.requires_solver("ortools") def test_ortools_real_coeff(self): m = cp.Model() @@ -288,8 +288,7 @@ def test_ortools_real_coeff(self): m += 0.7 * x + 0.8 * y >= 1 self.assertRaises(TypeError, m.solve) - @pytest.mark.skipif(not CPM_pysat.supported(), - reason="PySAT not installed") + @pytest.mark.requires_solver("pysat") def test_pysat(self): # Construct the model. @@ -332,8 +331,7 @@ def test_pysat(self): self.assertFalse(ps2.solve(assumptions=[mayo]+[v for v in inds])) self.assertSetEqual(set(ps2.get_core()), set([mayo,inds[6],inds[9]])) - @pytest.mark.skipif(not CPM_pysat.supported(), - reason="PySAT not installed") + @pytest.mark.requires_solver("pysat") def test_pysat_subsolver(self): pysat = CPM_pysat(subsolver="pysat:cadical195") x, y = cp.boolvar(shape=2) @@ -341,8 +339,8 @@ def test_pysat_subsolver(self): pysat += ~x | ~y assert pysat.solve() - @pytest.mark.skipif(not (pysat_available and pblib_available), reason="`pysat` is not installed" if not pysat_available else "`pypblib` not installed") - @skip_on_missing_pblib() + @pytest.mark.requires_solver("pysat") + @pytest.mark.requires_dependency("pypblib") def test_pysat_card(self): b = cp.boolvar() x = cp.boolvar(shape=5) @@ -353,8 +351,7 @@ def test_pysat_card(self): self.assertTrue(cp.Model(c).solve("pysat")) self.assertTrue(c.value()) - @pytest.mark.skipif(not CPM_pindakaas.supported(), - reason="pindakaas not installed") + @pytest.mark.requires_solver("pindakaas") def test_pindakaas(self): # Construct the model. @@ -377,9 +374,7 @@ def test_pindakaas(self): ps = CPM_pindakaas(model) self.assertTrue(ps.solve()) - - @pytest.mark.skipif(not CPM_minizinc.supported(), - reason="MiniZinc not installed") + @pytest.mark.requires_solver("minizinc") def test_minizinc(self): # (from or-tools) b = cp.boolvar() @@ -407,9 +402,7 @@ def test_minizinc(self): # modulo self.assertTrue( cp.Model([ x[0] == x[1] % x[2] ]).solve(solver="minizinc") ) - - @pytest.mark.skipif(not CPM_minizinc.supported(), - reason="MiniZinc not installed") + @pytest.mark.requires_solver("minizinc") def test_minizinc_names(self): a = cp.boolvar(name='5var')#has to start with alphabetic character b = cp.boolvar(name='va+r')#no special characters @@ -421,8 +414,7 @@ def test_minizinc_names(self): with self.assertRaises(MinizincNameException): cp.Model(c == 0).solve(solver="minizinc") - @pytest.mark.skipif(not CPM_minizinc.supported(), - reason="MiniZinc not installed") + @pytest.mark.requires_solver("minizinc") def test_minizinc_inverse(self): from cpmpy.solvers.minizinc import CPM_minizinc @@ -440,8 +432,7 @@ def test_minizinc_inverse(self): self.assertTrue(solver.solve()) self.assertEqual(list(rev.value()), expected_inverse) - @pytest.mark.skipif(not CPM_minizinc.supported(), - reason="MiniZinc not installed") + @pytest.mark.requires_solver("minizinc") def test_minizinc_gcc(self): from cpmpy.solvers.minizinc import CPM_minizinc @@ -461,8 +452,7 @@ def test_minizinc_gcc(self): self.assertTrue(all(cp.Count(iv, val[i]).value() == occ[i] for i in range(len(val)))) self.assertTrue(cp.GlobalCardinalityCount([iv[0],iv[2],iv[1],iv[4],iv[3]], val, occ).value()) - @pytest.mark.skipif(not CPM_z3.supported(), - reason="Z3 not installed") + @pytest.mark.requires_solver("z3") def test_z3(self): bv = cp.boolvar(shape=3) iv = cp.intvar(0, 9, shape=3) @@ -506,9 +496,7 @@ def test_only_objective(self): self.assertTrue(model.solve()) self.assertEqual(v.value(), 1) - - @pytest.mark.skipif(not CPM_exact.supported(), - reason="Exact not installed") + @pytest.mark.requires_solver("exact") def test_exact(self): bv = cp.boolvar(shape=3) iv = cp.intvar(0, 9, shape=3) @@ -542,8 +530,7 @@ def _trixor_callback(): s = cp.SolverLookup.get("exact", m) self.assertEqual(s.solveAll(display=_trixor_callback),7) - @pytest.mark.skipif(not CPM_exact.supported(), - reason="Exact not installed") + @pytest.mark.requires_solver("exact") def test_parameters_to_exact(self): # php with 5 pigeons, 4 holes @@ -566,8 +553,7 @@ def test_parameters_to_exact(self): with open(proof_file+".proof", "r") as f: self.assertEqual(f.readline()[:-1], "pseudo-Boolean proof version 1.1") # check header of proof-file - @pytest.mark.skipif(not CPM_choco.supported(), - reason="pychoco not installed") + @pytest.mark.requires_solver("choco") def test_choco(self): bv = cp.boolvar(shape=3) iv = cp.intvar(0, 9, shape=3) @@ -594,8 +580,7 @@ def test_choco(self): s = cp.SolverLookup.get("choco", m) self.assertTrue(s.solve()) - @pytest.mark.skipif(not CPM_choco.supported(), - reason="pychoco not installed") + @pytest.mark.requires_solver("choco") def test_choco_element(self): # test 1-D @@ -625,8 +610,7 @@ def test_choco_element(self): self.assertTrue(s.solve()) self.assertTrue(iv.value()[idx.value(), idx2.value()] == 8) - @pytest.mark.skipif(not CPM_choco.supported(), - reason="pychoco not installed") + @pytest.mark.requires_solver("choco") def test_choco_gcc_alldiff(self): iv = cp.intvar(-8, 8, shape=5) @@ -645,8 +629,7 @@ def test_choco_gcc_alldiff(self): self.assertTrue(all(cp.Count(iv, val[i]).value() == occ[i] for i in range(len(val)))) self.assertTrue(cp.GlobalCardinalityCount([iv[0],iv[2],iv[1],iv[4],iv[3]], val, occ).value()) - @pytest.mark.skipif(not CPM_choco.supported(), - reason="pychoco not installed") + @pytest.mark.requires_solver("choco") def test_choco_inverse(self): from cpmpy.solvers.ortools import CPM_ortools @@ -664,8 +647,7 @@ def test_choco_inverse(self): self.assertTrue(solver.solve()) self.assertEqual(list(rev.value()), expected_inverse) - @pytest.mark.skipif(not CPM_choco.supported(), - reason="pychoco not installed") + @pytest.mark.requires_solver("choco") def test_choco_objective(self): iv = cp.intvar(0,10, shape=2) m = cp.Model(iv >= 1, iv <= 5, maximize=sum(iv)) @@ -678,8 +660,7 @@ def test_choco_objective(self): self.assertTrue( s.solve() ) self.assertEqual(s.objective_value(), 2) - @pytest.mark.skipif(not CPM_gurobi.supported(), - reason="Gurobi not installed") + @pytest.mark.requires_solver("gurobi") def test_gurobi_element(self): # test 1-D iv = cp.intvar(-8, 8, 3) @@ -708,8 +689,7 @@ def test_gurobi_element(self): self.assertTrue(s.solve()) self.assertTrue(iv.value()[idx.value(), idx2.value()] == 8) - @pytest.mark.skipif(not CPM_gurobi.supported(), - reason="Gurobi not installed") + @pytest.mark.requires_solver("gurobi") def test_gurobi_float_objective(self): """Test that Gurobi properly handles float objective values.""" # Test case with float coefficients that should result in a float objective value @@ -731,9 +711,7 @@ def test_gurobi_float_objective(self): self.assertIsInstance(actual_obj, float) self.assertAlmostEqual(actual_obj, expected_obj, places=5) - - @pytest.mark.skipif(not CPM_cplex.supported(), - reason="cplex not installed") + @pytest.mark.requires_solver("cplex") def test_cplex(self): bv = cp.boolvar(shape=3) iv = cp.intvar(0, 10, shape=3) @@ -759,9 +737,7 @@ def test_cplex(self): s = cp.SolverLookup.get("cplex", m) self.assertTrue(s.solve()) - - @pytest.mark.skipif(not CPM_cplex.supported(), - reason="cplex not installed") + @pytest.mark.requires_solver("cplex") def test_cplex_float_objective(self): """Test that cplex properly handles float objective values.""" # Test case with float coefficients that should result in a float objective value @@ -783,8 +759,7 @@ def test_cplex_float_objective(self): self.assertIsInstance(actual_obj, float) self.assertAlmostEqual(actual_obj, expected_obj, places=5) - @pytest.mark.skipif(not CPM_cplex.supported(), - reason="cplex not installed") + @pytest.mark.requires_solver("cplex") def test_cplex_solveAll(self): iv = cp.intvar(0,5, shape=3) m = cp.Model(cp.AllDifferent(iv)) @@ -793,8 +768,7 @@ def test_cplex_solveAll(self): self.assertTrue(sol_count == 10) self.assertEqual(s.status().exitstatus, ExitStatus.FEASIBLE) - @pytest.mark.skipif(not CPM_cplex.supported(), - reason="cplex not installed") + @pytest.mark.requires_solver("cplex") def test_cplex_objective(self): iv = cp.intvar(0,10, shape=2) m = cp.Model(iv >= 1, iv <= 5, maximize=sum(iv)) @@ -807,8 +781,7 @@ def test_cplex_objective(self): self.assertTrue( s.solve() ) self.assertEqual(s.objective_value(), 2) - @pytest.mark.skipif(not CPM_minizinc.supported(), - reason="Minizinc not installed") + @pytest.mark.requires_solver("minizinc") def test_count_mzn(self): # bug #461 from cpmpy.expressions.core import Operator diff --git a/tests/test_tocnf.py b/tests/test_tocnf.py index eefef07bb..63ca70cf7 100644 --- a/tests/test_tocnf.py +++ b/tests/test_tocnf.py @@ -1,4 +1,5 @@ import unittest +import pytest import numpy as np from cpmpy import * from cpmpy.solvers import CPM_ortools @@ -60,6 +61,7 @@ def test_tocnf(self): # Expressions should not be decomposed at the to_cnf level! self.assertEqual(len(cnf), 1) + @pytest.mark.requires_solver("ortools") def allsols(self, cons, vs): sols = [] diff --git a/tests/test_tool_dimacs.py b/tests/test_tool_dimacs.py index 57e43f587..b8265f625 100644 --- a/tests/test_tool_dimacs.py +++ b/tests/test_tool_dimacs.py @@ -8,6 +8,7 @@ from cpmpy.transformations.get_variables import get_variables_model from cpmpy.solvers.solver_interface import ExitStatus +@pytest.mark.usefixtures("solver") class CNFTool(unittest.TestCase): def setUp(self) -> None: @@ -32,12 +33,12 @@ def test_read_cnf(self): def test_empty_formula(self): model = self.dimacs_to_model("p cnf 0 0") - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertEqual(model.status().exitstatus, ExitStatus.FEASIBLE) def test_empty_clauses(self): model = self.dimacs_to_model("p cnf 0 2\n0\n0") - self.assertFalse(model.solve()) + self.assertFalse(model.solve(solver=self.solver)) self.assertEqual(model.status().exitstatus, ExitStatus.UNSATISFIABLE) def test_with_comments(self): @@ -47,7 +48,7 @@ def test_with_comments(self): sols = set() addsol = lambda : sols.add(tuple([v.value() for v in vars])) - self.assertEqual(model.solveAll(display=addsol), 2) + self.assertEqual(model.solveAll(solver=self.solver, display=addsol), 2) self.assertSetEqual(sols, {(False, False, True), (False, True, False)}) def test_write_cnf(self): diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index eb2e24c70..7327ddd55 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -1,11 +1,12 @@ import unittest +import pytest from unittest import TestCase import cpmpy as cp from cpmpy.tools import mss_opt, marco, OCUSException from cpmpy.tools.explain import mus, mus_naive, quickxplain, quickxplain_naive, optimal_mus, optimal_mus_naive, mss, mcs, ocus, ocus_naive - +@pytest.mark.usefixtures("solver") class MusTests(TestCase): def __init__(self, *args, **kwargs): @@ -81,10 +82,10 @@ def test_wglobal(self): #self.assertEqual(set(mus(cons)), set(cons[1:3])) ms = self.mus_func(cons) self.assertLess(len(ms), len(cons)) - self.assertFalse(cp.Model(ms).solve()) + self.assertFalse(cp.Model(ms).solve(solver=self.solver)) ms = self.naive_func(cons) self.assertLess(len(ms), len(cons)) - self.assertFalse(cp.Model(ms).solve()) + self.assertFalse(cp.Model(ms).solve(solver=self.solver)) # self.assertEqual(set(self.naive_func(cons)), set(cons[:2])) diff --git a/tests/test_trans_linearize.py b/tests/test_trans_linearize.py index ba42cae70..4b3ba8ae9 100644 --- a/tests/test_trans_linearize.py +++ b/tests/test_trans_linearize.py @@ -1,4 +1,5 @@ import unittest +import pytest import cpmpy as cp from cpmpy.expressions import boolvar, intvar @@ -9,7 +10,7 @@ from cpmpy.transformations.linearize import linearize_constraint, canonical_comparison, only_positive_bv, only_positive_coefficients, only_positive_bv_wsum_const, only_positive_bv_wsum from cpmpy.expressions.variables import _IntVarImpl, _BoolVarImpl - +@pytest.mark.usefixtures("solver") class TestTransLinearize(unittest.TestCase): def setUp(self): @@ -35,6 +36,7 @@ def test_linearize(self): cons = linearize_constraint([a.implies(b)])[0] self.assertEqual("sum([1, -1] * [a, b]) <= 0", str(cons)) + @pytest.mark.requires_solver("gurobi") def test_bug_168(self): from cpmpy.solvers import CPM_gurobi if CPM_gurobi.supported(): @@ -44,7 +46,8 @@ def test_bug_168(self): s1 = cp.Model(e1).solve("gurobi") self.assertTrue(s1) self.assertEqual([bv[0].value(), bv[1].value(), iv.value()],[True, True, 1]) - + + @pytest.mark.requires_solver("gurobi", "exact") def test_bug_468(self): from cpmpy.solvers import CPM_exact, CPM_gurobi a, b, c = boolvar(shape=3) @@ -159,8 +162,8 @@ def test_abs(self): for lhs in (pos,neg,x): cons = cp.Abs(lhs) == y - cnt = cp.Model(cons).solveAll() - lcnt = cp.Model(linearize_constraint([cons])).solveAll(display=self.assertTrue(cons.value())) + cnt = cp.Model(cons).solveAll(solver=self.solver) + lcnt = cp.Model(linearize_constraint([cons])).solveAll(solver=self.solver, display=self.assertTrue(cons.value())) self.assertEqual(cnt, lcnt) @@ -174,7 +177,7 @@ def test_alldiff(self): def cb(): assert cons.value() - n_sols = cp.Model(lincons).solveAll(display=cb) + n_sols = cp.Model(lincons).solveAll(solver=self.solver, display=cb) self.assertEqual(n_sols, 5 * 4 * 3) # should also work with constants in arguments @@ -185,25 +188,26 @@ def cb(): def cb(): assert cons.value() - n_sols = cp.Model(lincons).solveAll(display=cb) + n_sols = cp.Model(lincons).solveAll(solver=self.solver, display=cb) self.assertEqual(n_sols, 3 * 2 * 1) # 1 and 3 not allowed def test_issue_580(self): x = cp.intvar(1, 5, name='x') lin_mod = linearize_constraint([x % 2 == 0], supported={"mul","sum", "wsum"}) - self.assertTrue(cp.Model(lin_mod).solve()) + self.assertTrue(cp.Model(lin_mod).solve(solver=self.solver)) self.assertIn(x.value(),{2,4}) lin_mod = linearize_constraint([x % 2 <= 0], supported={"mul", "sum", "wsum"}) self.assertEqual(str(lin_mod), '[IV7 <= 0, sum([2, -1] * [IV8, IV9]) == 0, sum([1, 1, -1] * [IV9, IV7, x]) == 0]') - self.assertTrue(cp.Model(lin_mod).solve()) + self.assertTrue(cp.Model(lin_mod).solve(solver=self.solver)) self.assertIn(x.value(), {2, 4}) # can never be < 0 lin_mod = linearize_constraint([x % 2 == 1], supported={"mul", "sum", "wsum"}) - self.assertTrue(cp.Model(lin_mod).solve()) + self.assertTrue(cp.Model(lin_mod).solve(solver=self.solver)) self.assertIn(x.value(), {1,3,5}) + @pytest.mark.requires_solver("gurobi", "exact") def test_issue_546(self): # https://github.com/CPMpy/cpmpy/issues/546 arr = cp.cpm_array([cp.intvar(0, 5), cp.intvar(0, 5), 5, 4]) # combination of decision variables and constants @@ -357,7 +361,7 @@ def test_others(self): cons = linearize_constraint(cons, supported={"alldifferent"})[0] self.assertEqual("alldifferent(a,b,c)", str(cons)) - +@pytest.mark.usefixtures("solver") class TestVarsLhs(unittest.TestCase): def setUp(self): # reset counters @@ -414,14 +418,14 @@ def test_pow(self): cons = a ** 5 == b lin_cons = linearize_constraint([cons], supported={"sum", "wsum", "mul"}) model = cp.Model(lin_cons + [a == 2]) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertEqual(b.value(), 32) # 2^5 = 32 # Test x^0 == y (should equal 1) cons = a ** 0 == b lin_cons = linearize_constraint([cons], supported={"sum", "wsum", "mul"}) model = cp.Model(lin_cons + [a == 3]) - self.assertTrue(model.solve()) + self.assertTrue(model.solve(solver=self.solver)) self.assertEqual(b.value(), 1) # not supported pow with exponent being a variable @@ -457,11 +461,11 @@ def test_mod(self): # disallows 2 mod 3 = 2 cons = (x % y) <= 1 sols = set() - cp.Model(cons).solveAll(display=lambda : sols.add((x.value(), y.value()))) + cp.Model(cons).solveAll(solver=self.solver, display=lambda : sols.add((x.value(), y.value()))) lincons = linearize_constraint([cons], supported={"sum", "wsum", "mul"}) linsols = set() - cp.Model(lincons).solveAll(display=lambda : linsols.add((x.value(), y.value()))) + cp.Model(lincons).solveAll(solver=self.solver, display=lambda : linsols.add((x.value(), y.value()))) self.assertSetEqual(sols, linsols) # check special cases of supported sets @@ -471,11 +475,11 @@ def test_mod(self): cons = (x % 2) == 1 sols = set() - cp.Model(cons).solveAll(display=lambda : sols.add((x.value(), y.value()))) + cp.Model(cons).solveAll(solver=self.solver, display=lambda : sols.add((x.value(), y.value()))) lincons = linearize_constraint([cons], supported={"sum", "wsum"}) linsols = set() - cp.Model(lincons).solveAll(display=lambda : linsols.add((x.value(), y.value()))) + cp.Model(lincons).solveAll(solver=self.solver, display=lambda : linsols.add((x.value(), y.value()))) self.assertSetEqual(sols, linsols) @@ -493,11 +497,11 @@ def test_mod(self): bv = cp.boolvar(name="bv") cons = bv.implies((x % y) <= 1) sols = set() - cp.Model(cons).solveAll(display=lambda: sols.add((bv.value(), x.value(), y.value()))) + cp.Model(cons).solveAll(solver=self.solver, display=lambda: sols.add((bv.value(), x.value(), y.value()))) lincons = linearize_constraint([cons], supported={"sum", "wsum", "mod"}) linsols = set() - cp.Model(lincons).solveAll(display=lambda: linsols.add((bv.value(), x.value(), y.value()))) + cp.Model(lincons).solveAll(solver=self.solver, display=lambda: linsols.add((bv.value(), x.value(), y.value()))) self.assertSetEqual(sols, linsols) def test_others(self): diff --git a/tests/test_trans_safen.py b/tests/test_trans_safen.py index 9d9af392f..a83b0dc30 100644 --- a/tests/test_trans_safen.py +++ b/tests/test_trans_safen.py @@ -1,11 +1,12 @@ import unittest +import pytest import cpmpy as cp from cpmpy.transformations.normalize import toplevel_list from cpmpy.transformations.safening import no_partial_functions from cpmpy.expressions.utils import argval - +@pytest.mark.usefixtures("solver") class TestTransLinearize(unittest.TestCase): def test_division_by_zero(self): @@ -14,11 +15,11 @@ def test_division_by_zero(self): expr = (a // b) == 3 safe_expr = no_partial_functions([expr], safen_toplevel={"div"}) - self.assertTrue(cp.Model(safe_expr).solve()) + self.assertTrue(cp.Model(safe_expr).solve(solver=self.solver)) self.assertTrue(argval(safe_expr)) safened = no_partial_functions([expr | ~expr]) - solcount = cp.Model(safened).solveAll() + solcount = cp.Model(safened).solveAll(solver=self.solver) self.assertEqual(solcount, 110) # check with reification @@ -26,7 +27,7 @@ def test_division_by_zero(self): reif_expr = bv == expr def check(): self.assertTrue(reif_expr.value()) - solcount = cp.Model(no_partial_functions([reif_expr])).solveAll(display=check) + solcount = cp.Model(no_partial_functions([reif_expr])).solveAll(solver=self.solver, display=check) self.assertEqual(solcount, 110) def test_division_by_zero_proper_hole(self): @@ -35,18 +36,18 @@ def test_division_by_zero_proper_hole(self): expr = (a // b) <= 3 safe_expr = no_partial_functions([expr], safen_toplevel={"div"}) - self.assertTrue(cp.Model(safe_expr).solve()) + self.assertTrue(cp.Model(safe_expr).solve(solver=self.solver)) self.assertTrue(argval(safe_expr)) safened = no_partial_functions([expr | ~expr]) - solcount = cp.Model(safened).solveAll() + solcount = cp.Model(safened).solveAll(solver=self.solver) self.assertEqual(solcount, 120) bv = cp.boolvar(name="bv") reif_expr = bv == expr def check(): self.assertTrue(reif_expr.value()) - solcount = cp.Model(no_partial_functions([reif_expr])).solveAll(display=check) + solcount = cp.Model(no_partial_functions([reif_expr])).solveAll(solver=self.solver, display=check) self.assertEqual(solcount, 120) def test_division_by_constant_zero(self): @@ -55,7 +56,7 @@ def test_division_by_constant_zero(self): expr = (a // b) <= 3 safe_expr = no_partial_functions([expr], safen_toplevel={"div"}) - self.assertFalse(cp.Model(safe_expr).solve()) + self.assertFalse(cp.Model(safe_expr).solve(solver=self.solver)) safened = no_partial_functions([~expr]) self.assertEqual(str(safened[0]), "not([boolval(False)])") @@ -66,18 +67,18 @@ def test_element_out_of_bounds(self): expr = arr[idx] == 2 safe_expr = no_partial_functions([expr]) - self.assertTrue(cp.Model(safe_expr).solve()) + self.assertTrue(cp.Model(safe_expr).solve(solver=self.solver)) self.assertTrue(argval(safe_expr)) safened = no_partial_functions([expr | ~expr]) - solcount = cp.Model(safened).solveAll() + solcount = cp.Model(safened).solveAll(solver=self.solver) self.assertEqual(solcount, 162) bv = cp.boolvar(name="bv") reif_expr = bv == expr def check(): self.assertTrue(reif_expr.value()) - solcount = cp.Model(no_partial_functions([reif_expr])).solveAll(display=check) + solcount = cp.Model(no_partial_functions([reif_expr])).solveAll(solver=self.solver, display=check) self.assertEqual(solcount, 162) def test_multiple_partial_functions(self): @@ -89,18 +90,18 @@ def test_multiple_partial_functions(self): expr = (a / b + arr[idx]) == 2 safe_expr = no_partial_functions([expr], safen_toplevel={"div"}) - self.assertTrue(cp.Model(safe_expr).solve()) + self.assertTrue(cp.Model(safe_expr).solve(solver=self.solver)) self.assertTrue(argval(safe_expr)) safened = no_partial_functions([expr | ~expr]) - solcount = cp.Model(safened).solveAll() + solcount = cp.Model(safened).solveAll(solver=self.solver) self.assertEqual(solcount, 15*162) bv = cp.boolvar(name="bv") reif_expr = bv == expr def check(): self.assertTrue(reif_expr.value()) - solcount = cp.Model(no_partial_functions([reif_expr])).solveAll(display=check) + solcount = cp.Model(no_partial_functions([reif_expr])).solveAll(solver=self.solver, display=check) self.assertEqual(solcount, 15*162) def test_nested_partial_functions(self): @@ -111,18 +112,18 @@ def test_nested_partial_functions(self): expr = (a / arr[idx]) == 2 safe_expr = no_partial_functions([expr], safen_toplevel={"div"}) - self.assertTrue(cp.Model(safe_expr).solve()) + self.assertTrue(cp.Model(safe_expr).solve(solver=self.solver)) self.assertTrue(argval(safe_expr)) safened = no_partial_functions([expr | ~expr]) - solcount = cp.Model(safened).solveAll() + solcount = cp.Model(safened).solveAll(solver=self.solver) self.assertEqual(solcount, 10*(4**3)*6) bv = cp.boolvar(name="bv") reif_expr = bv == expr def check(): self.assertTrue(reif_expr.value()) - solcount = cp.Model(no_partial_functions([reif_expr])).solveAll(display=check) + solcount = cp.Model(no_partial_functions([reif_expr])).solveAll(solver=self.solver, display=check) self.assertEqual(solcount, 10*(4**3)*6) def test_nested_partial_functions2(self): @@ -133,4 +134,4 @@ def test_nested_partial_functions2(self): expr = ~((a * arr[idx]) == 0) safe_expr = no_partial_functions([expr], safen_toplevel={"div"}) - self.assertTrue(cp.Model([safe_expr, idx == 4]).solve()) \ No newline at end of file + self.assertTrue(cp.Model([safe_expr, idx == 4]).solve(solver=self.solver)) \ No newline at end of file diff --git a/tests/test_trans_simplify.py b/tests/test_trans_simplify.py index d733bec15..3a8e470b1 100644 --- a/tests/test_trans_simplify.py +++ b/tests/test_trans_simplify.py @@ -1,10 +1,11 @@ import unittest +import pytest import cpmpy as cp from cpmpy.expressions.core import Operator, BoolVal, Comparison from cpmpy.transformations.normalize import simplify_boolean, toplevel_list - +@pytest.mark.usefixtures("solver") class TransSimplify(unittest.TestCase): def setUp(self) -> None: @@ -122,7 +123,7 @@ def test_nested_boolval(self): x = cp.intvar(0, 3, name="x") cons = (x == 2) == (bv == 4) self.assertEqual(str(self.transform(cons)), "[x != 2]") - self.assertTrue(cp.Model(cons).solve()) + self.assertTrue(cp.Model(cons).solve(solver=self.solver)) # Simplify boolean expressions nested within a weighted sum # wsum([1, 2], [bv[0] != 0, bv[1] != 1]) ----> wsum([1, 2], [bv[0], ~bv[1]]) @@ -131,5 +132,5 @@ def test_nested_boolval(self): bool_as_ints = cp.cpm_array([0, 1]) cons = sum( weights * (bv != bool_as_ints) ) == 1 self.assertEqual(str(self.transform(cons)), "[sum([1, 2] * [bv[0], ~bv[1]]) == 1]") - self.assertTrue(cp.Model(cons).solve()) + self.assertTrue(cp.Model(cons).solve(solver=self.solver)) diff --git a/tests/test_transf_comp.py b/tests/test_transf_comp.py index 8c36fce69..f629235c4 100644 --- a/tests/test_transf_comp.py +++ b/tests/test_transf_comp.py @@ -1,11 +1,12 @@ import unittest -import numpy as np +import pytest import cpmpy as cp from cpmpy.transformations.flatten_model import flatten_constraint from cpmpy.transformations.comparison import only_numexpr_equality from cpmpy.expressions.variables import _IntVarImpl, _BoolVarImpl # to reset counters +@pytest.mark.usefixtures("solver") class TestTransfComp(unittest.TestCase): def setUp(self): _IntVarImpl.counter = 0 @@ -27,6 +28,6 @@ def test_only_numexpr_eq(self): for (expr, strexpr) in cases: self.assertSetEqual( set([str(c) for c in transform(expr)]), set(strexpr) ) - self.assertTrue(cp.Model(expr).solve()) + self.assertTrue(cp.Model(expr).solve(solver=self.solver)) diff --git a/tests/test_transf_reif.py b/tests/test_transf_reif.py index 9dcea9540..ed9b3b9f8 100644 --- a/tests/test_transf_reif.py +++ b/tests/test_transf_reif.py @@ -1,4 +1,5 @@ import unittest +import pytest import numpy as np from cpmpy import * from cpmpy.transformations.decompose_global import decompose_in_tree @@ -7,6 +8,7 @@ from cpmpy.transformations.reification import only_implies, reify_rewrite, only_bv_reifies from cpmpy.expressions.variables import _IntVarImpl, _BoolVarImpl # to reset counters +@pytest.mark.usefixtures("solver") class TestTransfReif(unittest.TestCase): def setUp(self): _IntVarImpl.counter = 0 @@ -30,7 +32,7 @@ def test_only_implies(self): # test transformation for (expr, strexpr) in cases: self.assertEqual( str(only_implies(only_bv_reifies((expr,)))), strexpr ) - self.assertTrue(Model(expr).solve()) + self.assertTrue(Model(expr).solve(solver=self.solver)) def test_reif_element(self): bvs = boolvar(shape=5, name="bvs") @@ -45,7 +47,7 @@ def test_reif_element(self): e1 = (bvs[iv] == rv) e2 = (cpm_array([1,0,1,1])[iv] == rv) for e in [e1,e2]: - self.assertTrue(Model(e).solve()) + self.assertTrue(Model(e).solve(solver=self.solver)) # Another case to be careful with: @@ -68,11 +70,11 @@ def test_reif_element(self): for (lb,ub,cnt) in cases: idx = intvar(lb,ub, name="idx") e = (rv == (arr[idx] != 1)) - self.assertEqual(Model(e).solveAll(), cnt) + self.assertEqual(Model(e).solveAll(solver=self.solver), cnt) # Another case, with a more specific check... if the element-wise decomp is empty e = bvs[0].implies(Element([1,2,3], iv) < 1) - self.assertFalse(Model(e, bvs[0]==True).solve()) + self.assertFalse(Model(e, bvs[0]==True).solve(solver=self.solver)) def test_reif_rewrite(self): diff --git a/tests/test_variables.py b/tests/test_variables.py index 730d7214d..539a31662 100644 --- a/tests/test_variables.py +++ b/tests/test_variables.py @@ -1,9 +1,10 @@ import unittest +import pytest import cpmpy as cp import numpy as np from cpmpy.expressions.variables import NullShapeError, _IntVarImpl, _BoolVarImpl, NegBoolView, NDVarArray, _gen_var_names - +@pytest.mark.usefixtures("solver") class TestSolvers(unittest.TestCase): def test_zero_boolvar(self): with self.assertRaises(NullShapeError): @@ -91,7 +92,7 @@ def n_none(v): iv = cp.intvar(1,9, shape=9) m = cp.Model(cp.AllDifferent(iv)) self.assertEqual(n_none(iv), 9) - m.solve() + m.solve(solver=self.solver) self.assertEqual(n_none(iv), 0) iv.clear() self.assertEqual(n_none(iv), 9) @@ -99,7 +100,7 @@ def n_none(v): bv = cp.boolvar(9) m = cp.Model(sum(bv) > 3) self.assertEqual(n_none(bv), 9) - m.solve() + m.solve(solver=self.solver) self.assertEqual(n_none(bv), 0) bv.clear() self.assertEqual(n_none(bv), 9)