mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 09:28:51 -06:00
Add XpressPyomoSolver
This commit is contained in:
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
@@ -5,7 +5,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
||||
python-version: [3.6, 3.7, 3.8]
|
||||
|
||||
steps:
|
||||
- name: Check out source code
|
||||
@@ -19,6 +19,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install xpress
|
||||
python -m pip install -i https://pypi.gurobi.com gurobipy
|
||||
pip install -r requirements.txt
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
### Selecting the internal MIP solver
|
||||
|
||||
By default, `LearningSolver` uses [Gurobi](https://www.gurobi.com/) as its internal MIP solver, and expects models to be provided using the Pyomo modeling language. Other supported solvers and modeling languages include:
|
||||
By default, `LearningSolver` uses [Gurobi](https://www.gurobi.com/) as its internal MIP solver, and expects models to be provided using the Pyomo modeling language. Supported solvers and modeling languages include:
|
||||
|
||||
* `GurobiPyomoSolver`: Gurobi with Pyomo (default).
|
||||
* `CplexPyomoSolver`: [IBM ILOG CPLEX](https://www.ibm.com/products/ilog-cplex-optimization-studio) with Pyomo.
|
||||
* `XpressPyomoSolver`: [FICO XPRESS Solver](https://www.fico.com/en/products/fico-xpress-solver) with Pyomo.
|
||||
* `GurobiSolver`: Gurobi without any modeling language.
|
||||
|
||||
To switch between solvers, provide the desired class using the `solver` argument:
|
||||
@@ -16,6 +18,22 @@ from miplearn import LearningSolver, CplexPyomoSolver
|
||||
solver = LearningSolver(solver=CplexPyomoSolver)
|
||||
```
|
||||
|
||||
To configure a particular solver, use the `params` constructor argument, as shown below.
|
||||
|
||||
```python
|
||||
from miplearn import LearningSolver, GurobiPyomoSolver
|
||||
solver = LearningSolver(
|
||||
solver=lambda: GurobiPyomoSolver(
|
||||
params={
|
||||
"TimeLimit": 900,
|
||||
"MIPGap": 1e-3,
|
||||
"NodeLimit": 1000,
|
||||
}
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
## Customizing solver components
|
||||
|
||||
`LearningSolver` is composed by a number of individual machine-learning components, each targeting a different part of the solution process. Each component can be individually enabled, disabled or customized. The following components are enabled by default:
|
||||
|
||||
@@ -181,18 +181,27 @@ class BasePyomoSolver(InternalSolver):
|
||||
if not should_repeat:
|
||||
break
|
||||
log = streams[0].getvalue()
|
||||
return {
|
||||
stats = {
|
||||
"Lower bound": results["Problem"][0]["Lower bound"],
|
||||
"Upper bound": results["Problem"][0]["Upper bound"],
|
||||
"Wallclock time": total_wallclock_time,
|
||||
"Nodes": self._extract_node_count(log),
|
||||
"Sense": self._obj_sense,
|
||||
"Log": log,
|
||||
"Warm start value": self._extract_warm_start_value(log),
|
||||
}
|
||||
node_count = self._extract_node_count(log)
|
||||
if node_count is not None:
|
||||
stats["Nodes"] = node_count
|
||||
|
||||
ws_value = self._extract_warm_start_value(log)
|
||||
if ws_value is not None:
|
||||
stats["Warm start value"] = ws_value
|
||||
|
||||
return stats
|
||||
|
||||
@staticmethod
|
||||
def __extract(log, regexp, default=None):
|
||||
if regexp is None:
|
||||
return default
|
||||
value = default
|
||||
for line in log.splitlines():
|
||||
matches = re.findall(regexp, line)
|
||||
@@ -208,18 +217,16 @@ class BasePyomoSolver(InternalSolver):
|
||||
return value
|
||||
|
||||
def _extract_node_count(self, log):
|
||||
return int(self.__extract(log, self._get_node_count_regexp(), default=1))
|
||||
return self.__extract(log, self._get_node_count_regexp())
|
||||
|
||||
def get_constraint_ids(self):
|
||||
return list(self._cname_to_constr.keys())
|
||||
|
||||
@abstractmethod
|
||||
def _get_warm_start_regexp(self):
|
||||
pass
|
||||
return None
|
||||
|
||||
@abstractmethod
|
||||
def _get_node_count_regexp(self):
|
||||
pass
|
||||
return None
|
||||
|
||||
def extract_constraint(self, cid):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
34
miplearn/solvers/pyomo/xpress.py
Normal file
34
miplearn/solvers/pyomo/xpress.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
import sys
|
||||
import logging
|
||||
from io import StringIO
|
||||
from pyomo import environ as pe
|
||||
from scipy.stats import randint
|
||||
|
||||
from .base import BasePyomoSolver
|
||||
from .. import RedirectOutput
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XpressPyomoSolver(BasePyomoSolver):
|
||||
"""
|
||||
An InternalSolver that uses XPRESS and the Pyomo modeling language.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
params: dict
|
||||
Dictionary of options to pass to the Pyomo solver. For example,
|
||||
{"Threads": 4} to set the number of threads.
|
||||
"""
|
||||
|
||||
def __init__(self, params=None):
|
||||
super().__init__(
|
||||
solver_factory=pe.SolverFactory("xpress_persistent"),
|
||||
params={
|
||||
"randomseed": randint(low=0, high=1000).rvs(),
|
||||
},
|
||||
)
|
||||
@@ -5,6 +5,7 @@
|
||||
from inspect import isclass
|
||||
from miplearn import BasePyomoSolver, GurobiSolver, GurobiPyomoSolver
|
||||
from miplearn.problems.knapsack import KnapsackInstance, GurobiKnapsackInstance
|
||||
from miplearn.solvers.pyomo.xpress import XpressPyomoSolver
|
||||
|
||||
|
||||
def _get_instance(solver):
|
||||
@@ -31,4 +32,4 @@ def _get_instance(solver):
|
||||
|
||||
|
||||
def _get_internal_solvers():
|
||||
return [GurobiPyomoSolver, GurobiSolver]
|
||||
return [GurobiPyomoSolver, GurobiSolver, XpressPyomoSolver]
|
||||
|
||||
@@ -43,6 +43,7 @@ def test_internal_solver_warm_starts():
|
||||
}
|
||||
)
|
||||
stats = solver.solve(tee=True)
|
||||
if "Warm start value" in stats:
|
||||
assert stats["Warm start value"] == 725.0
|
||||
|
||||
solver.set_warm_start(
|
||||
@@ -56,6 +57,7 @@ def test_internal_solver_warm_starts():
|
||||
}
|
||||
)
|
||||
stats = solver.solve(tee=True)
|
||||
if "Warm start value" in stats:
|
||||
assert stats["Warm start value"] is None
|
||||
|
||||
solver.fix(
|
||||
@@ -97,7 +99,6 @@ def test_internal_solver():
|
||||
assert stats["Upper bound"] == 1183.0
|
||||
assert stats["Sense"] == "max"
|
||||
assert isinstance(stats["Wallclock time"], float)
|
||||
assert isinstance(stats["Nodes"], int)
|
||||
|
||||
solution = solver.get_solution()
|
||||
assert solution["x"][0] == 1.0
|
||||
|
||||
Reference in New Issue
Block a user