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
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
python-version: [3.6, 3.7, 3.8]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out source code
|
- name: Check out source code
|
||||||
@@ -19,6 +19,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install xpress
|
||||||
python -m pip install -i https://pypi.gurobi.com gurobipy
|
python -m pip install -i https://pypi.gurobi.com gurobipy
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,11 @@
|
|||||||
|
|
||||||
### Selecting the internal MIP solver
|
### 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.
|
* `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.
|
* `GurobiSolver`: Gurobi without any modeling language.
|
||||||
|
|
||||||
To switch between solvers, provide the desired class using the `solver` argument:
|
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)
|
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
|
## 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:
|
`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:
|
if not should_repeat:
|
||||||
break
|
break
|
||||||
log = streams[0].getvalue()
|
log = streams[0].getvalue()
|
||||||
return {
|
stats = {
|
||||||
"Lower bound": results["Problem"][0]["Lower bound"],
|
"Lower bound": results["Problem"][0]["Lower bound"],
|
||||||
"Upper bound": results["Problem"][0]["Upper bound"],
|
"Upper bound": results["Problem"][0]["Upper bound"],
|
||||||
"Wallclock time": total_wallclock_time,
|
"Wallclock time": total_wallclock_time,
|
||||||
"Nodes": self._extract_node_count(log),
|
|
||||||
"Sense": self._obj_sense,
|
"Sense": self._obj_sense,
|
||||||
"Log": log,
|
"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
|
@staticmethod
|
||||||
def __extract(log, regexp, default=None):
|
def __extract(log, regexp, default=None):
|
||||||
|
if regexp is None:
|
||||||
|
return default
|
||||||
value = default
|
value = default
|
||||||
for line in log.splitlines():
|
for line in log.splitlines():
|
||||||
matches = re.findall(regexp, line)
|
matches = re.findall(regexp, line)
|
||||||
@@ -208,18 +217,16 @@ class BasePyomoSolver(InternalSolver):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def _extract_node_count(self, log):
|
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):
|
def get_constraint_ids(self):
|
||||||
return list(self._cname_to_constr.keys())
|
return list(self._cname_to_constr.keys())
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _get_warm_start_regexp(self):
|
def _get_warm_start_regexp(self):
|
||||||
pass
|
return None
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _get_node_count_regexp(self):
|
def _get_node_count_regexp(self):
|
||||||
pass
|
return None
|
||||||
|
|
||||||
def extract_constraint(self, cid):
|
def extract_constraint(self, cid):
|
||||||
raise Exception("Not implemented")
|
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 inspect import isclass
|
||||||
from miplearn import BasePyomoSolver, GurobiSolver, GurobiPyomoSolver
|
from miplearn import BasePyomoSolver, GurobiSolver, GurobiPyomoSolver
|
||||||
from miplearn.problems.knapsack import KnapsackInstance, GurobiKnapsackInstance
|
from miplearn.problems.knapsack import KnapsackInstance, GurobiKnapsackInstance
|
||||||
|
from miplearn.solvers.pyomo.xpress import XpressPyomoSolver
|
||||||
|
|
||||||
|
|
||||||
def _get_instance(solver):
|
def _get_instance(solver):
|
||||||
@@ -31,4 +32,4 @@ def _get_instance(solver):
|
|||||||
|
|
||||||
|
|
||||||
def _get_internal_solvers():
|
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)
|
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(
|
solver.set_warm_start(
|
||||||
{
|
{
|
||||||
@@ -56,7 +57,8 @@ def test_internal_solver_warm_starts():
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
stats = solver.solve(tee=True)
|
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(
|
solver.fix(
|
||||||
{
|
{
|
||||||
@@ -97,7 +99,6 @@ def test_internal_solver():
|
|||||||
assert stats["Upper bound"] == 1183.0
|
assert stats["Upper bound"] == 1183.0
|
||||||
assert stats["Sense"] == "max"
|
assert stats["Sense"] == "max"
|
||||||
assert isinstance(stats["Wallclock time"], float)
|
assert isinstance(stats["Wallclock time"], float)
|
||||||
assert isinstance(stats["Nodes"], int)
|
|
||||||
|
|
||||||
solution = solver.get_solution()
|
solution = solver.get_solution()
|
||||||
assert solution["x"][0] == 1.0
|
assert solution["x"][0] == 1.0
|
||||||
|
|||||||
Reference in New Issue
Block a user