Add XpressPyomoSolver

master
Alinson S. Xavier 5 years ago
parent 34e1711081
commit 4b8672870a

@ -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:
* `CplexPyomoSolver`: [IBM ILOG CPLEX](https://www.ibm.com/products/ilog-cplex-optimization-studio) with Pyomo.
* `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")

@ -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,7 +43,8 @@ def test_internal_solver_warm_starts():
}
)
stats = solver.solve(tee=True)
assert stats["Warm start value"] == 725.0
if "Warm start value" in stats:
assert stats["Warm start value"] == 725.0
solver.set_warm_start(
{
@ -56,7 +57,8 @@ def test_internal_solver_warm_starts():
}
)
stats = solver.solve(tee=True)
assert stats["Warm start value"] is None
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

Loading…
Cancel
Save