Add customizable branch priority; add more metrics to BenchmarkRunner

pull/1/head
Alinson S. Xavier 6 years ago
parent 99ac7aa718
commit f7d20ed52b

@ -18,15 +18,21 @@ class BenchmarkRunner:
solver.load(filename)
solver.fit()
def parallel_solve(self, instances, n_jobs=1):
def parallel_solve(self, instances, n_jobs=1, n_trials=1):
if self.results is None:
self.results = pd.DataFrame(columns=["Solver",
"Instance",
"Wallclock Time",
"Obj Value",
"Lower Bound",
"Upper Bound",
"Gap",
"Nodes",
])
instances = instances * n_trials
for (name, solver) in self.solvers.items():
results = solver.parallel_solve(instances, n_jobs=n_jobs, label=name)
results = solver.parallel_solve(instances,
n_jobs=n_jobs,
label=name)
for i in range(len(instances)):
wallclock_time = None
for key in ["Time", "Wall time", "Wallclock time"]:
@ -35,19 +41,35 @@ class BenchmarkRunner:
if str(results[i]["Solver"][0][key]) == "<undefined>":
continue
wallclock_time = float(results[i]["Solver"][0][key])
nodes = results[i]["Solver"][0]["Nodes"]
lb = results[i]["Problem"][0]["Lower bound"]
ub = results[i]["Problem"][0]["Upper bound"]
gap = (ub - lb) / lb
self.results = self.results.append({
"Solver": name,
"Instance": i,
"Wallclock Time": wallclock_time,
"Obj Value": results[i]["Problem"][0]["Lower bound"]
"Lower Bound": lb,
"Upper Bound": ub,
"Gap": gap,
"Nodes": nodes,
}, ignore_index=True)
groups = self.results.groupby("Instance")
best_obj_value = groups["Obj Value"].transform("max")
best_lower_bound = groups["Lower Bound"].transform("max")
best_upper_bound = groups["Upper Bound"].transform("min")
best_gap = groups["Gap"].transform("min")
best_nodes = groups["Nodes"].transform("min")
best_wallclock_time = groups["Wallclock Time"].transform("min")
self.results["Relative Obj Value"] = \
self.results["Obj Value"] / best_obj_value
self.results["Relative Lower Bound"] = \
self.results["Lower Bound"] / best_lower_bound
self.results["Relative Upper Bound"] = \
self.results["Upper Bound"] / best_upper_bound
self.results["Relative Wallclock Time"] = \
self.results["Wallclock Time"] / best_wallclock_time
self.results["Relative Gap"] = \
self.results["Gap"] / best_gap
self.results["Relative Nodes"] = \
self.results["Nodes"] / best_nodes
def raw_results(self):
return self.results

@ -3,19 +3,21 @@
# Written by Alinson S. Xavier <axavier@anl.gov>
from .transformers import PerVariableTransformer
from .warmstart import KnnWarmStartPredictor
from .warmstart import KnnWarmStartPredictor, LogisticWarmStartPredictor
import pyomo.environ as pe
import numpy as np
from copy import copy, deepcopy
import pickle
from tqdm import tqdm
from joblib import Parallel, delayed
from scipy.stats import randint
import multiprocessing
def _gurobi_factory():
solver = pe.SolverFactory('gurobi_persistent')
solver.options["threads"] = 4
solver.options["Seed"] = randint(low=0, high=1000).rvs()
return solver
class LearningSolver:
@ -27,7 +29,8 @@ class LearningSolver:
def __init__(self,
threads=4,
internal_solver_factory=_gurobi_factory,
ws_predictor=KnnWarmStartPredictor(),
ws_predictor=LogisticWarmStartPredictor(),
branch_priority=None,
mode="exact"):
self.internal_solver_factory = internal_solver_factory
self.internal_solver = self.internal_solver_factory()
@ -36,10 +39,14 @@ class LearningSolver:
self.y_train = {}
self.ws_predictors = {}
self.ws_predictor_prototype = ws_predictor
self.branch_priority = branch_priority
def solve(self, instance, tee=False):
# Convert instance into concrete model
# Load model into solver
model = instance.to_model()
is_solver_persistent = hasattr(self.internal_solver, "set_instance")
if is_solver_persistent:
self.internal_solver.set_instance(model)
# Split decision variables according to their category
transformer = PerVariableTransformer()
@ -56,10 +63,11 @@ class LearningSolver:
else:
self.x_train[category] = np.vstack([self.x_train[category], x])
# Predict warm start
for category in var_split.keys():
var_index_pairs = var_split[category]
# Predict warm starts
if category in self.ws_predictors.keys():
var_index_pairs = var_split[category]
ws = self.ws_predictors[category].predict(x_test[category])
assert ws.shape == (len(var_index_pairs), 2)
for i in range(len(var_index_pairs)):
@ -75,9 +83,23 @@ class LearningSolver:
elif ws[i,1] == 1:
var[index].value = 1
# Solve MILP
solve_results = self._solve(model, tee=tee)
# Set custom branch priority
if self.branch_priority is not None:
assert is_solver_persistent
from gurobipy import GRB
for (i, (var, index)) in enumerate(var_index_pairs):
gvar = self.internal_solver._pyomo_var_to_solver_var_map[var[index]]
#priority = randint(low=0, high=1000).rvs()
gvar.setAttr(GRB.Attr.BranchPriority, self.branch_priority[index])
if is_solver_persistent:
solve_results = self.internal_solver.solve(tee=tee, warmstart=True)
else:
solve_results = self.internal_solver.solve(model, tee=tee, warmstart=True)
solve_results["Solver"][0]["Nodes"] = self.internal_solver._solver_model.getAttr("NodeCount")
# Update y_train
for category in var_split.keys():
var_index_pairs = var_split[category]
@ -113,7 +135,7 @@ class LearningSolver:
results = Parallel(n_jobs=n_jobs)(
delayed(_process)(instance)
for instance in tqdm(instances, desc=label)
for instance in tqdm(instances, desc=label, ncols=80)
)
x_train, y_train, results = _merge(results)
@ -148,10 +170,3 @@ class LearningSolver:
self.x_train = data["x_train"]
self.y_train = data["y_train"]
self.ws_predictors = self.ws_predictors
def _solve(self, model, tee=False):
if hasattr(self.internal_solver, "set_instance"):
self.internal_solver.set_instance(model)
return self.internal_solver.solve(tee=tee, warmstart=True)
else:
return self.internal_solver.solve(model, tee=tee, warmstart=True)

@ -28,12 +28,12 @@ def test_benchmark():
}
benchmark = BenchmarkRunner(test_solvers)
benchmark.load_fit("data.bin")
benchmark.parallel_solve(test_instances, n_jobs=2)
assert benchmark.raw_results().values.shape == (6,6)
benchmark.parallel_solve(test_instances, n_jobs=2, n_trials=2)
assert benchmark.raw_results().values.shape == (12,12)
benchmark.save_results("/tmp/benchmark.csv")
assert os.path.isfile("/tmp/benchmark.csv")
benchmark = BenchmarkRunner(test_solvers)
benchmark.load_results("/tmp/benchmark.csv")
assert benchmark.raw_results().values.shape == (6,6)
assert benchmark.raw_results().values.shape == (12,12)

@ -40,4 +40,11 @@ def test_parallel_solve():
solver = LearningSolver()
solver.parallel_solve(instances, n_jobs=3)
assert len(solver.x_train[0]) == 10
assert len(solver.y_train[0]) == 10
assert len(solver.y_train[0]) == 10
def test_solver_random_branch_priority():
instance = KnapsackInstance2(weights=[23., 26., 20., 18.],
prices=[505., 352., 458., 220.],
capacity=67.)
solver = LearningSolver(branch_priority=[1, 2, 3, 4])
solver.solve(instance, tee=True)
Loading…
Cancel
Save