mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 01:18:52 -06:00
Parse warm start value from log files
This commit is contained in:
@@ -3,8 +3,9 @@
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
from abc import ABC
|
||||
from abc import ABC, abstractmethod
|
||||
from copy import deepcopy
|
||||
from io import StringIO
|
||||
|
||||
@@ -246,8 +247,8 @@ class InternalSolver(ABC):
|
||||
-------
|
||||
dict
|
||||
A dictionary of solver statistics containing the following keys:
|
||||
"Lower bound", "Upper bound", "Wallclock time", "Nodes", "Sense"
|
||||
and "Log".
|
||||
"Lower bound", "Upper bound", "Wallclock time", "Nodes", "Sense",
|
||||
"Log" and "Warm start value".
|
||||
"""
|
||||
total_wallclock_time = 0
|
||||
streams = [StringIO()]
|
||||
@@ -257,7 +258,8 @@ class InternalSolver(ABC):
|
||||
while True:
|
||||
logger.debug("Solving MIP...")
|
||||
with RedirectOutput(streams):
|
||||
results = self._pyomo_solver.solve(tee=True)
|
||||
results = self._pyomo_solver.solve(tee=True,
|
||||
warmstart=self._is_warm_start_available)
|
||||
total_wallclock_time += results["Solver"][0]["Wallclock time"]
|
||||
if not hasattr(self.instance, "find_violations"):
|
||||
break
|
||||
@@ -271,15 +273,35 @@ class InternalSolver(ABC):
|
||||
cut = self.instance.build_lazy_constraint(self.model, v)
|
||||
self.add_constraint(cut)
|
||||
|
||||
log = streams[0].getvalue()
|
||||
return {
|
||||
"Lower bound": results["Problem"][0]["Lower bound"],
|
||||
"Upper bound": results["Problem"][0]["Upper bound"],
|
||||
"Wallclock time": total_wallclock_time,
|
||||
"Nodes": 1,
|
||||
"Sense": self._obj_sense,
|
||||
"Log": streams[0].getvalue()
|
||||
"Log": log,
|
||||
"Warm start value": self.extract_warm_start_value(log),
|
||||
}
|
||||
|
||||
def extract_warm_start_value(self, log):
|
||||
"""
|
||||
Extracts and returns the objective value of the user-provided MIP start
|
||||
from the provided solver log. If more than one value is found, returns
|
||||
the last one. If no value is present in the logs, returns None.
|
||||
"""
|
||||
ws_value = None
|
||||
for line in log.splitlines():
|
||||
matches = re.findall(self._get_warm_start_regexp(), line)
|
||||
if len(matches) == 0:
|
||||
continue
|
||||
ws_value = float(matches[0])
|
||||
return ws_value
|
||||
|
||||
@abstractmethod
|
||||
def _get_warm_start_regexp(self):
|
||||
pass
|
||||
|
||||
|
||||
class GurobiSolver(InternalSolver):
|
||||
def __init__(self):
|
||||
@@ -324,15 +346,21 @@ class GurobiSolver(InternalSolver):
|
||||
warmstart=self._is_warm_start_available)
|
||||
self._pyomo_solver.set_callback(None)
|
||||
node_count = int(self._pyomo_solver._solver_model.getAttr("NodeCount"))
|
||||
|
||||
log = streams[0].getvalue()
|
||||
return {
|
||||
"Lower bound": results["Problem"][0]["Lower bound"],
|
||||
"Upper bound": results["Problem"][0]["Upper bound"],
|
||||
"Wallclock time": results["Solver"][0]["Wallclock time"],
|
||||
"Nodes": max(1, node_count),
|
||||
"Sense": self._obj_sense,
|
||||
"Log": streams[0].getvalue(),
|
||||
}
|
||||
|
||||
"Log": log,
|
||||
"Warm start value": self.extract_warm_start_value(log),
|
||||
}
|
||||
|
||||
def _get_warm_start_regexp(self):
|
||||
return "MIP start with objective ([0-9.e+-]*)"
|
||||
|
||||
|
||||
class CPLEXSolver(InternalSolver):
|
||||
def __init__(self,
|
||||
@@ -373,7 +401,10 @@ class CPLEXSolver(InternalSolver):
|
||||
return {
|
||||
"Optimal value": results["Problem"][0]["Lower bound"],
|
||||
}
|
||||
|
||||
|
||||
def _get_warm_start_regexp(self):
|
||||
return "MIP start .* with objective ([0-9.e+-]*)\\."
|
||||
|
||||
|
||||
class LearningSolver:
|
||||
"""
|
||||
|
||||
@@ -19,7 +19,7 @@ def _get_instance():
|
||||
)
|
||||
|
||||
|
||||
def test_internal_solver():
|
||||
def test_internal_solver_warm_starts():
|
||||
for solver in [GurobiSolver(), CPLEXSolver(presolve=False)]:
|
||||
instance = _get_instance()
|
||||
model = instance.to_model()
|
||||
@@ -29,11 +29,31 @@ def test_internal_solver():
|
||||
"x": {
|
||||
0: 1.0,
|
||||
1: 0.0,
|
||||
2: 0.0,
|
||||
3: 1.0,
|
||||
}
|
||||
})
|
||||
stats = solver.solve(tee=True)
|
||||
assert stats["Warm start value"] == 725.0
|
||||
|
||||
solver.set_warm_start({
|
||||
"x": {
|
||||
0: 1.0,
|
||||
1: 1.0,
|
||||
2: 1.0,
|
||||
3: 1.0,
|
||||
}
|
||||
})
|
||||
stats = solver.solve(tee=True)
|
||||
assert stats["Warm start value"] is None
|
||||
|
||||
|
||||
def test_internal_solver():
|
||||
for solver in [GurobiSolver(), CPLEXSolver(presolve=False)]:
|
||||
instance = _get_instance()
|
||||
model = instance.to_model()
|
||||
|
||||
solver.set_instance(instance, model)
|
||||
stats = solver.solve(tee=True)
|
||||
assert len(stats["Log"]) > 100
|
||||
assert stats["Lower bound"] == 1183.0
|
||||
|
||||
Reference in New Issue
Block a user