-
Notifications
You must be signed in to change notification settings - Fork 35
add native mus computation for exact #909
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,7 +56,7 @@ | |
| from .solver_interface import SolverInterface, SolverStatus, ExitStatus, Callback | ||
| from ..expressions.core import Expression, Comparison, Operator, BoolVal | ||
| from ..expressions.globalfunctions import Multiplication | ||
| from ..expressions.variables import intvar, _BoolVarImpl, NegBoolView, _IntVarImpl, _NumVarImpl | ||
| from ..expressions.variables import intvar, boolvar, _BoolVarImpl, NegBoolView, _IntVarImpl, _NumVarImpl | ||
| from ..transformations.comparison import only_numexpr_equality | ||
| from ..transformations.flatten_model import flatten_constraint, flatten_objective | ||
| from ..transformations.get_variables import get_variables | ||
|
|
@@ -67,6 +67,7 @@ | |
| from ..expressions.globalconstraints import DirectConstraint | ||
| from ..expressions.utils import flatlist, argvals, argval, is_any_list, is_num | ||
| from ..exceptions import NotSupportedError | ||
| from .utils import SolverLookup | ||
|
|
||
| import numpy as np | ||
| import numbers | ||
|
|
@@ -652,6 +653,26 @@ def get_core(self): | |
|
|
||
| # return cpm_variables corresponding to Exact core | ||
| return [self.assumption_dict[i][1] for i in self.xct_solver.getLastCore()] | ||
|
|
||
| @staticmethod | ||
| def _native_mus(soft, hard=[]): | ||
| # Create assumption variables and model with hard + (assumption -> soft) | ||
| from cpmpy.tools.explain.utils import make_assump_model # avoid circular import | ||
| m, soft, assumptions = make_assump_model(soft, hard) | ||
|
|
||
| s = SolverLookup.get("exact", m) | ||
|
|
||
| assert not s.solve(assumptions=assumptions), "MUS: model must be UNSAT" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. did you check whether exact can detect this itself? maybe put a comment if exact cannot detect it |
||
|
|
||
| # set up assumptions for exact | ||
| s.xct_solver.setAssumptions([(s.solver_var(v), 1) for v in assumptions]) | ||
|
|
||
| # call native MUS extractor | ||
| _, res_xct = s.xct_solver.extractMUS() | ||
|
|
||
| # get the constraints back from the assumption variables | ||
| dmap = dict(zip(assumptions, soft)) | ||
| return [dmap[boolvar(name=c)] for c in res_xct] | ||
|
|
||
|
|
||
| def solution_hint(self, cpm_vars:List[_NumVarImpl], vals:List[int|bool]): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -327,6 +327,19 @@ def get_core(self): | |
| Setting these literals to True makes the model UNSAT, setting any to False makes it SAT | ||
| """ | ||
| raise NotSupportedError("Solver does not support unsat core extraction") | ||
|
|
||
| @staticmethod | ||
| def _native_mus(self, soft, hard=[]): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove |
||
| """ | ||
| For using the solver's internal MUS extractor | ||
|
|
||
| Args: | ||
| soft: List of soft constraints over which a MUS needs to be found | ||
| hard: List of hard constraints that always need to be satisfied | ||
|
|
||
| Returns a MUS. | ||
| """ | ||
| raise NotSupportedError("Solver does not support MUS extraction") | ||
|
|
||
|
|
||
| # shared helper functions | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| - QuickXplain | ||
| - Optimal MUS | ||
| """ | ||
| import sys | ||
| import warnings | ||
| import numpy as np | ||
| import cpmpy as cp | ||
|
|
@@ -60,6 +61,21 @@ def mus(soft, hard=[], solver="ortools"): | |
|
|
||
| return [dmap[avar] for avar in core] | ||
|
|
||
| def mus_native(soft, hard=[], solver="exact"): | ||
| """ | ||
| Compute a MUS using a solver's native MUS extractor. | ||
|
|
||
| :param soft: soft constraints, list of expressions | ||
| :param hard: hard constraints, optional, list of expressions | ||
| :param solver: which solver to use (only `gurobi` and `exact` supported) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make this an assert, no idea what error we will get if I call it with the wrong name
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. related, there's a SolverLookup method out there that will get you the class (it may also return a nice error which would avoid the assert). |
||
| """ | ||
|
|
||
| # get solver class | ||
| class_name = f"CPM_{solver}" | ||
| solver_class = getattr(sys.modules[__name__], class_name) | ||
|
|
||
| return solver_class._native_mus(soft, hard) | ||
|
|
||
|
|
||
| def quickxplain(soft, hard=[], solver="ortools"): | ||
| """ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps it could be a classmethod, in which case the first argument becomes
clsand you could replace this withcls(m). This shows the intention of the native_mus being executed with the current class.