Parse warm start value from log files

pull/3/head
Alinson S. Xavier 6 years ago
parent 042929772b
commit 9a95bd552a

@ -3,8 +3,9 @@
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
import logging import logging
import re
import sys import sys
from abc import ABC from abc import ABC, abstractmethod
from copy import deepcopy from copy import deepcopy
from io import StringIO from io import StringIO
@ -246,8 +247,8 @@ class InternalSolver(ABC):
------- -------
dict dict
A dictionary of solver statistics containing the following keys: A dictionary of solver statistics containing the following keys:
"Lower bound", "Upper bound", "Wallclock time", "Nodes", "Sense" "Lower bound", "Upper bound", "Wallclock time", "Nodes", "Sense",
and "Log". "Log" and "Warm start value".
""" """
total_wallclock_time = 0 total_wallclock_time = 0
streams = [StringIO()] streams = [StringIO()]
@ -257,7 +258,8 @@ class InternalSolver(ABC):
while True: while True:
logger.debug("Solving MIP...") logger.debug("Solving MIP...")
with RedirectOutput(streams): 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"] total_wallclock_time += results["Solver"][0]["Wallclock time"]
if not hasattr(self.instance, "find_violations"): if not hasattr(self.instance, "find_violations"):
break break
@ -271,15 +273,35 @@ class InternalSolver(ABC):
cut = self.instance.build_lazy_constraint(self.model, v) cut = self.instance.build_lazy_constraint(self.model, v)
self.add_constraint(cut) self.add_constraint(cut)
log = streams[0].getvalue()
return { return {
"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": 1, "Nodes": 1,
"Sense": self._obj_sense, "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): class GurobiSolver(InternalSolver):
def __init__(self): def __init__(self):
@ -324,15 +346,21 @@ class GurobiSolver(InternalSolver):
warmstart=self._is_warm_start_available) warmstart=self._is_warm_start_available)
self._pyomo_solver.set_callback(None) self._pyomo_solver.set_callback(None)
node_count = int(self._pyomo_solver._solver_model.getAttr("NodeCount")) node_count = int(self._pyomo_solver._solver_model.getAttr("NodeCount"))
log = streams[0].getvalue()
return { return {
"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": results["Solver"][0]["Wallclock time"], "Wallclock time": results["Solver"][0]["Wallclock time"],
"Nodes": max(1, node_count), "Nodes": max(1, node_count),
"Sense": self._obj_sense, "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): class CPLEXSolver(InternalSolver):
def __init__(self, def __init__(self,
@ -373,7 +401,10 @@ class CPLEXSolver(InternalSolver):
return { return {
"Optimal value": results["Problem"][0]["Lower bound"], "Optimal value": results["Problem"][0]["Lower bound"],
} }
def _get_warm_start_regexp(self):
return "MIP start .* with objective ([0-9.e+-]*)\\."
class LearningSolver: 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)]: for solver in [GurobiSolver(), CPLEXSolver(presolve=False)]:
instance = _get_instance() instance = _get_instance()
model = instance.to_model() model = instance.to_model()
@ -29,11 +29,31 @@ def test_internal_solver():
"x": { "x": {
0: 1.0, 0: 1.0,
1: 0.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, 2: 1.0,
3: 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) stats = solver.solve(tee=True)
assert len(stats["Log"]) > 100 assert len(stats["Log"]) > 100
assert stats["Lower bound"] == 1183.0 assert stats["Lower bound"] == 1183.0

Loading…
Cancel
Save