Move python files to root folder; remove built docs

This commit is contained in:
2020-08-29 11:42:02 -05:00
parent 741af8506b
commit 5663ced0be
116 changed files with 8 additions and 12408 deletions

View File

@@ -0,0 +1,3 @@
# 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.

View File

@@ -0,0 +1,223 @@
# 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 re
import sys
import logging
import pyomo
from abc import abstractmethod
from io import StringIO
from pyomo import environ as pe
from pyomo.core import Var
from .. import RedirectOutput
from ..internal import InternalSolver
from ...instance import Instance
logger = logging.getLogger(__name__)
class BasePyomoSolver(InternalSolver):
"""
Base class for all Pyomo solvers.
"""
def __init__(self):
self.instance = None
self.model = None
self._all_vars = None
self._bin_vars = None
self._is_warm_start_available = False
self._pyomo_solver = None
self._obj_sense = None
self._varname_to_var = {}
def solve_lp(self, tee=False):
for var in self._bin_vars:
lb, ub = var.bounds
var.setlb(lb)
var.setub(ub)
var.domain = pyomo.core.base.set_types.Reals
self._pyomo_solver.update_var(var)
results = self._pyomo_solver.solve(tee=tee)
for var in self._bin_vars:
var.domain = pyomo.core.base.set_types.Binary
self._pyomo_solver.update_var(var)
return {
"Optimal value": results["Problem"][0]["Lower bound"],
}
def get_solution(self):
solution = {}
for var in self.model.component_objects(Var):
solution[str(var)] = {}
for index in var:
solution[str(var)][index] = var[index].value
return solution
def get_variables(self):
variables = {}
for var in self.model.component_objects(Var):
variables[str(var)] = []
for index in var:
variables[str(var)] += [index]
return variables
def set_warm_start(self, solution):
self.clear_warm_start()
count_total, count_fixed = 0, 0
for var_name in solution:
var = self._varname_to_var[var_name]
for index in solution[var_name]:
count_total += 1
var[index].value = solution[var_name][index]
if solution[var_name][index] is not None:
count_fixed += 1
if count_fixed > 0:
self._is_warm_start_available = True
logger.info("Setting start values for %d variables (out of %d)" %
(count_fixed, count_total))
def clear_warm_start(self):
for var in self._all_vars:
if not var.fixed:
var.value = None
self._is_warm_start_available = False
def set_instance(self, instance, model=None):
if model is None:
model = instance.to_model()
assert isinstance(instance, Instance)
assert isinstance(model, pe.ConcreteModel)
self.instance = instance
self.model = model
self._pyomo_solver.set_instance(model)
# Update objective sense
self._obj_sense = "max"
if self._pyomo_solver._objective.sense == pyomo.core.kernel.objective.minimize:
self._obj_sense = "min"
# Update variables
self._all_vars = []
self._bin_vars = []
self._varname_to_var = {}
for var in model.component_objects(Var):
self._varname_to_var[var.name] = var
for idx in var:
self._all_vars += [var[idx]]
if var[idx].domain == pyomo.core.base.set_types.Binary:
self._bin_vars += [var[idx]]
def fix(self, solution):
count_total, count_fixed = 0, 0
for varname in solution:
for index in solution[varname]:
var = self._varname_to_var[varname]
count_total += 1
if solution[varname][index] is None:
continue
count_fixed += 1
var[index].fix(solution[varname][index])
self._pyomo_solver.update_var(var[index])
logger.info("Fixing values for %d variables (out of %d)" %
(count_fixed, count_total))
def add_constraint(self, constraint):
self._pyomo_solver.add_constraint(constraint)
def solve(self, tee=False):
total_wallclock_time = 0
streams = [StringIO()]
if tee:
streams += [sys.stdout]
self.instance.found_violated_lazy_constraints = []
self.instance.found_violated_user_cuts = []
while True:
logger.debug("Solving MIP...")
with RedirectOutput(streams):
results = self._pyomo_solver.solve(tee=True,
warmstart=self._is_warm_start_available)
total_wallclock_time += results["Solver"][0]["Wallclock time"]
logger.debug("Finding violated constraints...")
violations = self.instance.find_violated_lazy_constraints(self.model)
if len(violations) == 0:
break
self.instance.found_violated_lazy_constraints += violations
logger.debug(" %d violations found" % len(violations))
for v in violations:
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": self._extract_node_count(log),
"Sense": self._obj_sense,
"Log": log,
"Warm start value": self._extract_warm_start_value(log),
}
@staticmethod
def __extract(log, regexp, default=None):
value = default
for line in log.splitlines():
matches = re.findall(regexp, line)
if len(matches) == 0:
continue
value = matches[0]
return value
def _extract_warm_start_value(self, log):
value = self.__extract(log, self._get_warm_start_regexp())
if value is not None:
value = float(value)
return value
def _extract_node_count(self, log):
return int(self.__extract(log,
self._get_node_count_regexp(),
default=1))
def set_threads(self, threads):
key = self._get_threads_option_name()
self._pyomo_solver.options[key] = threads
def set_time_limit(self, time_limit):
key = self._get_time_limit_option_name()
self._pyomo_solver.options[key] = time_limit
def set_node_limit(self, node_limit):
key = self._get_node_limit_option_name()
self._pyomo_solver.options[key] = node_limit
def set_gap_tolerance(self, gap_tolerance):
key = self._get_gap_tolerance_option_name()
self._pyomo_solver.options[key] = gap_tolerance
@abstractmethod
def _get_warm_start_regexp(self):
pass
@abstractmethod
def _get_node_count_regexp(self):
pass
@abstractmethod
def _get_threads_option_name(self):
pass
@abstractmethod
def _get_time_limit_option_name(self):
pass
@abstractmethod
def _get_node_limit_option_name(self):
pass
@abstractmethod
def _get_gap_tolerance_option_name(self):
pass

View File

@@ -0,0 +1,49 @@
# 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.
from pyomo import environ as pe
from scipy.stats import randint
from .base import BasePyomoSolver
class CplexPyomoSolver(BasePyomoSolver):
def __init__(self, options=None):
"""
Creates a new CPLEX solver, accessed through Pyomo.
Parameters
----------
options: dict
Dictionary of options to pass to the Pyomo solver. For example,
{"mip_display": 5} to increase the log verbosity.
"""
super().__init__()
self._pyomo_solver = pe.SolverFactory('cplex_persistent')
self._pyomo_solver.options["randomseed"] = randint(low=0, high=1000).rvs()
self._pyomo_solver.options["mip_display"] = 4
if options is not None:
for (key, value) in options.items():
self._pyomo_solver.options[key] = value
def _get_warm_start_regexp(self):
return "MIP start .* with objective ([0-9.e+-]*)\\."
def _get_node_count_regexp(self):
return "^[ *] *([0-9]+)"
def _get_threads_option_name(self):
return "threads"
def _get_time_limit_option_name(self):
return "timelimit"
def _get_node_limit_option_name(self):
return "mip_limits_nodes"
def _get_gap_tolerance_option_name(self):
return "mip_tolerances_mipgap"
def set_branching_priorities(self, priorities):
raise NotImplementedError

View File

@@ -0,0 +1,129 @@
# 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 GurobiPyomoSolver(BasePyomoSolver):
def __init__(self,
use_lazy_callbacks=True,
options=None):
"""
Creates a new Gurobi solver, accessed through Pyomo.
Parameters
----------
use_lazy_callbacks: bool
If true, lazy constraints will be enforced via lazy callbacks.
Otherwise, they will be enforced via a simple solve-check loop.
options: dict
Dictionary of options to pass to the Pyomo solver. For example,
{"Threads": 4} to set the number of threads.
"""
super().__init__()
self._use_lazy_callbacks = use_lazy_callbacks
self._pyomo_solver = pe.SolverFactory('gurobi_persistent')
self._pyomo_solver.options["Seed"] = randint(low=0, high=1000).rvs()
if options is not None:
for (key, value) in options.items():
self._pyomo_solver.options[key] = value
def solve(self, tee=False):
if self._use_lazy_callbacks:
return self._solve_with_callbacks(tee)
else:
return super().solve(tee)
def _solve_with_callbacks(self, tee):
from gurobipy import GRB
def cb(cb_model, cb_opt, cb_where):
try:
# User cuts
if cb_where == GRB.Callback.MIPNODE:
logger.debug("Finding violated cutting planes...")
cb_opt.cbGetNodeRel(self._all_vars)
violations = self.instance.find_violated_user_cuts(cb_model)
self.instance.found_violated_user_cuts += violations
logger.debug(" %d found" % len(violations))
for v in violations:
cut = self.instance.build_user_cut(cb_model, v)
cb_opt.cbCut(cut)
# Lazy constraints
if cb_where == GRB.Callback.MIPSOL:
cb_opt.cbGetSolution(self._all_vars)
logger.debug("Finding violated lazy constraints...")
violations = self.instance.find_violated_lazy_constraints(cb_model)
self.instance.found_violated_lazy_constraints += violations
logger.debug(" %d found" % len(violations))
for v in violations:
cut = self.instance.build_lazy_constraint(cb_model, v)
cb_opt.cbLazy(cut)
except Exception as e:
logger.error(e)
self._pyomo_solver.options["LazyConstraints"] = 1
self._pyomo_solver.options["PreCrush"] = 1
self._pyomo_solver.set_callback(cb)
self.instance.found_violated_lazy_constraints = []
self.instance.found_violated_user_cuts = []
streams = [StringIO()]
if tee:
streams += [sys.stdout]
with RedirectOutput(streams):
results = self._pyomo_solver.solve(tee=True,
warmstart=self._is_warm_start_available)
self._pyomo_solver.set_callback(None)
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": self._extract_node_count(log),
"Sense": self._obj_sense,
"Log": log,
"Warm start value": self._extract_warm_start_value(log),
}
def _extract_node_count(self, log):
return max(1, int(self._pyomo_solver._solver_model.getAttr("NodeCount")))
def _get_warm_start_regexp(self):
return "MIP start with objective ([0-9.e+-]*)"
def _get_node_count_regexp(self):
return None
def _get_threads_option_name(self):
return "Threads"
def _get_time_limit_option_name(self):
return "TimeLimit"
def _get_node_limit_option_name(self):
return "NodeLimit"
def _get_gap_tolerance_option_name(self):
return "MIPGap"
def set_branching_priorities(self, priorities):
from gurobipy import GRB
for varname in priorities.keys():
var = self._varname_to_var[varname]
for (index, priority) in priorities[varname].items():
gvar = self._pyomo_solver._pyomo_var_to_solver_var_map[var[index]]
gvar.setAttr(GRB.Attr.BranchPriority, int(round(priority)))