-
Notifications
You must be signed in to change notification settings - Fork 275
Extend Functionality to Use the Presolver Plugin and Add a Tutorial #1076
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
Open
fvz185
wants to merge
26
commits into
scipopt:master
Choose a base branch
from
fvz185:feature/extend-presolver-plugin
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
70d39e2
Add SCIPvarIsActive function and corresponding test for variable acti…
9b69d6f
Add SCIPaggregateVars function and aggregateVars method for variable …
5100b76
Add knapsack function for modeling the knapsack problem
fd41439
Add parameter settings to disable automatic presolvers and propagator…
1c9ab3c
Add ShiftboundPresolver for variable domain transformation in SCIP
aa037f7
Add tests for Shiftbound presolver with parametrised knapsack instances
94983e6
Update docstring in shiftbound.py to clarify presolver example and it…
908b47b
Add tests for Model.aggregateVars to verify aggregation functionality
e7d34cb
Add test for aggregation infeasibility in binary variables
7560e5f
Remove Shiftbound presolver tests from test_shiftbound.py
0565bcb
Refactor TODO comment in test_isActive to clarify missing test cases …
2f02bfa
Update CHANGELOG to include new features: isActive(), aggregateVars()…
b3a8d0a
Add tutorial for writing a custom presolver using PySCIPOpt
5ce2777
Add tutorial for presolver plugin to CHANGELOG
3b2ac0c
Apply suggestions from code review
Joao-Dionisio 62e9b03
Merge branch 'master' into feature/extend-presolver-plugin
mmghannam 561d0b2
Apply suggestions from code review
Joao-Dionisio 9b3e54c
Clarify comments in the Shiftbound presolver example for better under…
8d8924d
Merge branch 'feature/extend-presolver-plugin' of https://github.com/…
8381c72
Merge branch 'master' of https://github.com/fvz185/PySCIPOpt into fea…
2e64c5f
Merge branch 'master' into feature/extend-presolver-plugin
Joao-Dionisio cf397bb
change file name
Joao-Dionisio 926a4c7
wrap new methods and add tests
Joao-Dionisio 599a8f1
slight changes in docs and example
Joao-Dionisio 3e1103b
Merge branch 'master' into feature/extend-presolver-plugin
Joao-Dionisio 4e42e47
add missing method stubs for adjustedVarLb, adjustedVarUb, aggregateV…
Joao-Dionisio File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| ########### | ||
| Presolvers | ||
| ########### | ||
|
|
||
| For the following, let us assume that a Model object is available, which is created as follows: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| from pyscipopt import Model, Presol, SCIP_RESULT, SCIP_PRESOLTIMING | ||
|
|
||
| scip = Model() | ||
|
|
||
| .. contents:: Contents | ||
| ---------------------- | ||
|
|
||
|
|
||
| What is Presolving? | ||
| =================== | ||
|
|
||
| Presolving simplifies a problem before the actual search starts. Typical | ||
| transformations include: | ||
|
|
||
| - tightening bounds, | ||
| - removing redundant variables/constraints, | ||
| - aggregating variables, | ||
| - detecting infeasibility early. | ||
|
|
||
| This can reduce numerical issues and simplify constraints and objective | ||
| expressions without changing the solution space. | ||
|
|
||
|
|
||
| The Presol Plugin Interface (Python) | ||
| ==================================== | ||
|
|
||
| A presolver in PySCIPOpt is a subclass of ``pyscipopt.Presol`` that implements the method: | ||
|
|
||
| - ``presolexec(self, nrounds, presoltiming)`` | ||
|
|
||
| and is registered on a ``pyscipopt.Model`` via | ||
| the class method ``pyscipopt.Model.includePresol``. | ||
|
|
||
| Here is a high-level flow: | ||
|
|
||
| 1. Create subclass ``MyPresolver`` and capture any parameters in ``__init__``. | ||
| 2. Implement ``presolexec``: inspect variables, compute transformations, call SCIP aggregation APIs, and return a result code. | ||
| 3. Register your presolver using ``includePresol`` with a priority, maximal rounds, and timing. | ||
| 4. Solve the model, e.g. by calling ``presolve`` or ``optimize``. | ||
|
|
||
|
|
||
| A Minimal Skeleton | ||
| ------------------ | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| from pyscipopt import Presol, SCIP_RESULT | ||
|
|
||
| class MyPresolver(Presol): | ||
| def __init__(self, someparam=123): | ||
| self.someparam = someparam | ||
|
|
||
| def presolexec(self, nrounds, presoltiming): | ||
| scip = self.model | ||
|
|
||
| # ... inspect model, change bounds, aggregate variables, etc. ... | ||
|
|
||
| return {"result": SCIP_RESULT.SUCCESS} # or DIDNOTFIND, DIDNOTRUN, CUTOFF | ||
|
|
||
|
|
||
| Example: Writing a Custom Presolver | ||
| =================================== | ||
|
|
||
| This tutorial shows how to write a presolver entirely in Python using | ||
| PySCIPOpt's ``Presol`` plugin interface. We will implement a small | ||
| presolver that shifts variable bounds from ``[a, b]`` to ``[0, b - a]`` | ||
| and optionally flips signs to reduce constant offsets. | ||
|
|
||
| For educational purposes, we keep our example as close as possible to SCIP's implementation, which can be found `here <https://scipopt.org/doc-5.0.1/html/presol__boundshift_8c_source.php>`__. However, one may implement Boundshift differently, as SCIP's logic does not translate perfectly to Python. To avoid any confusion with the already implemented version of Boundshift, we will call our custom presolver *Shiftbound*. | ||
|
|
||
| A complete working example can be found in the directory: | ||
|
|
||
| - ``examples/finished/presol_shiftbound.py`` | ||
|
|
||
|
|
||
| Implementing Shiftbound | ||
| ----------------------- | ||
|
|
||
| Below we walk through the important parts to illustrate design decisions to translate the Boundshift presolver to PySCIPOpt. | ||
|
|
||
| We want to provide parameters to control the presolver's behaviour: | ||
|
|
||
| - ``maxshift``: maximum length of interval ``b - a`` we are willing to shift, | ||
| - ``flipping``: allow sign flips for better numerics, | ||
| - ``integer``: only shift integer-ranged variables if true. | ||
|
|
||
| We will put these parameters into the ``__init__`` method to help us initialise the attributes of the presolver class. Then, in ``presolexec``, we implement the algorithm our custom presolver must follow. | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| from pyscipopt import SCIP_RESULT, Presol | ||
|
|
||
| class ShiftboundPresolver(Presol): | ||
| def __init__(self, maxshift=float("inf"), flipping=True, integer=True): | ||
| self.maxshift = maxshift | ||
| self.flipping = flipping | ||
| self.integer = integer | ||
|
|
||
| def presolexec(self, nrounds, presoltiming): | ||
| scip = self.model | ||
|
|
||
| # Respect global presolve switches (here, if aggregation disabled) | ||
| if scip.getParam("presolving/donotaggr"): | ||
| return {"result": SCIP_RESULT.DIDNOTRUN} | ||
|
|
||
| # We want to operate on non-binary active variables only | ||
| scipvars = scip.getVars() | ||
| nbin = scip.getNBinVars() | ||
| vars = scipvars[nbin:] # SCIP orders by type: binaries first | ||
|
|
||
| result = SCIP_RESULT.DIDNOTFIND | ||
|
|
||
| for var in reversed(vars): | ||
| if var.vtype() == "BINARY": | ||
| continue | ||
| if not var.isActive(): | ||
| continue | ||
|
|
||
| lb = var.getLbGlobal() | ||
| ub = var.getUbGlobal() | ||
|
|
||
| # For integral types: round to feasible integers to avoid noise | ||
| if var.vtype() != "CONTINUOUS": | ||
| assert scip.isIntegral(lb) | ||
| assert scip.isIntegral(ub) | ||
| lb = scip.adjustedVarLb(var, lb) | ||
| ub = scip.adjustedVarUb(var, ub) | ||
|
|
||
| # Is the variable already fixed? | ||
| if scip.isEQ(lb, ub): | ||
| continue | ||
|
|
||
| # If demanded by the parameters, restrict to integral-length intervals | ||
| if self.integer and not scip.isIntegral(ub - lb): | ||
| continue | ||
|
|
||
| # Only shift "reasonable" finite bounds | ||
| MAXABSBOUND = 1000.0 | ||
mmghannam marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| shiftable = all(( | ||
| not scip.isEQ(lb, 0.0), | ||
| scip.isLT(ub, scip.infinity()), | ||
| scip.isGT(lb, -scip.infinity()), | ||
| scip.isLT(ub - lb, self.maxshift), | ||
| scip.isLE(abs(lb), MAXABSBOUND), | ||
| scip.isLE(abs(ub), MAXABSBOUND), | ||
| )) | ||
| if not shiftable: | ||
| continue | ||
|
|
||
| # Create a new variable y with bounds [0, ub-lb], and same type | ||
| newvar = scip.addVar( | ||
| name=f"{var.name}_shift", | ||
| vtype=var.vtype(), | ||
| lb=0.0, | ||
| ub=(ub - lb), | ||
| obj=0.0, | ||
| ) | ||
|
|
||
| # Aggregate old variable with new variable: | ||
| # 1.0 * var + 1.0 * newvar = ub (flip), whichever yields smaller |offset|, or | ||
| # 1.0 * var + (-1.0) * newvar = lb (no flip) | ||
| if self.flipping and (abs(ub) < abs(lb)): | ||
| infeasible, redundant, aggregated = scip.aggregateVars(var, newvar, 1.0, 1.0, ub) | ||
| else: | ||
| infeasible, redundant, aggregated = scip.aggregateVars(var, newvar, 1.0, -1.0, lb) | ||
|
|
||
| # Has the problem become infeasible? | ||
| if infeasible: | ||
| return {"result": SCIP_RESULT.CUTOFF} | ||
|
|
||
| # Aggregation succeeded; SCIP marks var as redundant and keeps newvar for further search | ||
| assert redundant | ||
| assert aggregated | ||
| result = SCIP_RESULT.SUCCESS | ||
|
|
||
| return {"result": result} | ||
|
|
||
| Registering the Presolver | ||
| ------------------------- | ||
|
|
||
| After having initialised our ``model``, we instantiate an object based on our ``ShiftboundPresolver`` including the parameters we wish our presolver's behaviour to be set to. | ||
| Lastly, we register the custom presolver by including ``presolver``, followed by a name and a description, as well as specifying its priority, maximum rounds to be called (where ``-1`` specifies no limit), and timing mode. | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| from pyscipopt import Model, SCIP_PRESOLTIMING, SCIP_PARAMSETTING | ||
|
|
||
| model = Model() | ||
|
|
||
| presolver = ShiftboundPresolver(maxshift=float("inf"), flipping=True, integer=True) | ||
| model.includePresol( | ||
| presolver, | ||
| "shiftbound", | ||
| "converts variables with domain [a,b] to variables with domain [0,b-a]", | ||
| priority=7900000, | ||
| maxrounds=-1, | ||
| timing=SCIP_PRESOLTIMING.FAST, | ||
| ) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.