diff --git a/cpmpy/solvers/choco.py b/cpmpy/solvers/choco.py index db9b0afee..1cf4574c2 100644 --- a/cpmpy/solvers/choco.py +++ b/cpmpy/solvers/choco.py @@ -98,6 +98,13 @@ def __init__(self, cpm_model=None, subsolver=None): # initialise everything else and post the constraints/objective super().__init__(name="choco", cpm_model=cpm_model) + @property + def native_model(self): + """ + Returns the solver's underlying native model (for direct solver access). + """ + return self.chc_model + def solve(self, time_limit=None, **kwargs): """ Call the Choco solver diff --git a/cpmpy/solvers/exact.py b/cpmpy/solvers/exact.py index 5f0598cfb..05549ca3e 100644 --- a/cpmpy/solvers/exact.py +++ b/cpmpy/solvers/exact.py @@ -113,6 +113,12 @@ def __init__(self, cpm_model=None, subsolver=None): # initialise everything else and post the constraints/objective super().__init__(name="exact", cpm_model=cpm_model) + @property + def native_model(self): + """ + Returns the solver's underlying native model (for direct solver access). + """ + return self.xct_solver def _fillObjAndVars(self): if not self.xct_solver.hasSolution(): diff --git a/cpmpy/solvers/gurobi.py b/cpmpy/solvers/gurobi.py index dbe3618bf..c43761dd1 100644 --- a/cpmpy/solvers/gurobi.py +++ b/cpmpy/solvers/gurobi.py @@ -96,6 +96,13 @@ def __init__(self, cpm_model=None, subsolver=None): # it is sufficient to implement __add__() and minimize/maximize() below super().__init__(name="gurobi", cpm_model=cpm_model) + @property + def native_model(self): + """ + Returns the solver's underlying native model (for direct solver access). + """ + return self.grb_model + def solve(self, time_limit=None, solution_callback=None, **kwargs): """ diff --git a/cpmpy/solvers/minizinc.py b/cpmpy/solvers/minizinc.py index 111ea3e61..b01420a1c 100644 --- a/cpmpy/solvers/minizinc.py +++ b/cpmpy/solvers/minizinc.py @@ -174,6 +174,14 @@ def __init__(self, cpm_model=None, subsolver=None): # initialise everything else and post the constraints/objective super().__init__(name="minizinc:"+subsolver, cpm_model=cpm_model) + @property + def native_model(self): + """ + Returns the solver's underlying native model (for direct solver access). + """ + return self.mzn_model + + def _pre_solve(self, time_limit=None, **kwargs): """ shared by solve() and solveAll() """ import minizinc diff --git a/cpmpy/solvers/ortools.py b/cpmpy/solvers/ortools.py index b15e12243..b5b55627d 100644 --- a/cpmpy/solvers/ortools.py +++ b/cpmpy/solvers/ortools.py @@ -98,6 +98,12 @@ def __init__(self, cpm_model=None, subsolver=None): # initialise everything else and post the constraints/objective super().__init__(name="ortools", cpm_model=cpm_model) + @property + def native_model(self): + """ + Returns the solver's underlying native model (for direct solver access). + """ + return self.ort_model def solve(self, time_limit=None, assumptions=None, solution_callback=None, **kwargs): diff --git a/cpmpy/solvers/pysat.py b/cpmpy/solvers/pysat.py index 181444efc..8ca520fa8 100644 --- a/cpmpy/solvers/pysat.py +++ b/cpmpy/solvers/pysat.py @@ -125,6 +125,13 @@ def __init__(self, cpm_model=None, subsolver=None): # initialise everything else and post the constraints/objective super().__init__(name="pysat:"+subsolver, cpm_model=cpm_model) + @property + def native_model(self): + """ + Returns the solver's underlying native model (for direct solver access). + """ + return self.pysat_solver + def solve(self, time_limit=None, assumptions=None): """ diff --git a/cpmpy/solvers/solver_interface.py b/cpmpy/solvers/solver_interface.py index 9e905fc17..75e0436aa 100644 --- a/cpmpy/solvers/solver_interface.py +++ b/cpmpy/solvers/solver_interface.py @@ -88,6 +88,13 @@ def __init__(self, name="dummy", cpm_model=None, subsolver=None): else: self.maximize(cpm_model.objective_) + @property + def native_model(self): + """ + Returns the solver's underlying native model (for direct solver access). + """ + raise NotImplementedError("Solver does not support direct solver access. Look at the solver's API for alternative native objects to access directly.") + # instead of overloading minimize/maximize, better just overload 'objective()' def minimize(self, expr): """ diff --git a/cpmpy/solvers/z3.py b/cpmpy/solvers/z3.py index 52776ee1e..d02f1c3e3 100644 --- a/cpmpy/solvers/z3.py +++ b/cpmpy/solvers/z3.py @@ -82,6 +82,13 @@ def __init__(self, cpm_model=None, subsolver="sat"): # initialise everything else and post the constraints/objective super().__init__(name="z3", cpm_model=cpm_model) + @property + def native_model(self): + """ + Returns the solver's underlying native model (for direct solver access). + """ + return self.z3_solver + def solve(self, time_limit=None, assumptions=[], **kwargs): """ diff --git a/docs/modeling.md b/docs/modeling.md index 8bf897bc8..3fb1fe16a 100644 --- a/docs/modeling.md +++ b/docs/modeling.md @@ -697,10 +697,10 @@ iv = cp.intvar(1,9, shape=3) s = cp.SolverLookup.get("ortools") s += AllDifferent(iv) # the traditional way, equivalent to: -s.ort_model.AddAllDifferent(s.solver_vars(iv)) # directly calling the API, has to be with native variables +s.native_model.AddAllDifferent(s.solver_vars(iv)) # directly calling the API (OR-Tools' python library), has to be with native variables ``` -observe how we first map the CPMpy variables to native variables by calling `s.solver_vars()`, and then give these to the native solver API directly. This is in fact what happens behind the scenes when posting a DirectConstraint, or any CPMpy constraint. +Observe how we first map the CPMpy variables to native variables by calling `s.solver_vars()`, and then give these to the native solver API directly (in the case of OR-Tools, the `native_model` property returns a `CpModel` instance). This is in fact what happens behind the scenes when posting a DirectConstraint, or any CPMpy constraint. Consult [the solver API documentation](api/solvers.html) for more information on the available solver specific objects which can be accessed directly. While directly calling the solver offers a lot of freedom, it is a bit more cumbersome as you have to map the variables manually each time. Also, you no longer have a declarative model that you can pass along, print or inspect. In contrast, a `DirectConstraint` is a CPMpy expression so it can be part of a model like any other CPMpy constraint. Note that it can only be used as top-level (non-nested, non-reified) constraint.