Refactor JuMPSolver; capture log output

pull/3/head
Alinson S. Xavier 5 years ago
parent e73e6c462e
commit 869e4b4161

@ -9,9 +9,10 @@ module MIPLearn
Instance = miplearn.Instance Instance = miplearn.Instance
LearningSolver = miplearn.LearningSolver LearningSolver = miplearn.LearningSolver
InternalSolver = miplearn.solvers.internal.InternalSolver InternalSolver = miplearn.solvers.internal.InternalSolver
BenchmarkRunner = miplearn.BenchmarkRunner
include("jump_solver.jl") include("jump_solver.jl")
include("knapsack.jl") include("knapsack.jl")
export Instance, LearningSolver, InternalSolver, JuMPSolver export Instance, LearningSolver, InternalSolver, JuMPSolver, BenchmarkRunner
end end

@ -8,6 +8,7 @@ using MathOptInterface
const MOI = MathOptInterface const MOI = MathOptInterface
using TimerOutputs using TimerOutputs
mutable struct JuMPSolverData mutable struct JuMPSolverData
basename_idx_to_var basename_idx_to_var
var_to_basename_idx var_to_basename_idx
@ -18,6 +19,7 @@ mutable struct JuMPSolverData
solution::Union{Nothing,Dict{String,Dict{String,Float64}}} solution::Union{Nothing,Dict{String,Dict{String,Float64}}}
end end
function varname_split(varname::String) function varname_split(varname::String)
m = match(r"([^[]*)\[(.*)\]", varname) m = match(r"([^[]*)\[(.*)\]", varname)
if m == nothing if m == nothing
@ -27,188 +29,204 @@ function varname_split(varname::String)
end end
function set_solver_verbosity!(model::JuMP.Model, tee::Bool) """
if tee optimize_and_capture_output!(model; tee=tee)
JuMP.unset_silent(model)
else Optimizes a given JuMP model while capturing the solver log, then returns that log.
JuMP.set_silent(model) If tee=true, prints the solver log to the standard output as the optimization takes place.
end """
function optimize_and_capture_output!(model; tee::Bool=false)
original_stdout = stdout
rd, wr = redirect_stdout()
task = @async begin
log = ""
while true
line = String(readavailable(rd))
isopen(rd) || break
log *= String(line)
if tee
print(original_stdout, line)
flush(original_stdout)
end
end
return log
end
JuMP.unset_silent(model)
JuMP.optimize!(model)
sleep(1)
redirect_stdout(original_stdout)
close(rd)
return fetch(task)
end end
@pydef mutable struct JuMPSolver <: InternalSolver function solve(data::JuMPSolverData; tee::Bool=false)
function __init__(self; optimizer) instance, model = data.instance, data.model
self.data = JuMPSolverData(nothing, # basename_idx_to_var wallclock_time = 0
nothing, # var_to_basename_idx found_lazy = []
optimizer, log = ""
nothing, # instance while true
nothing, # model log *= optimize_and_capture_output!(model, tee=tee)
nothing, # bin_vars wallclock_time += JuMP.solve_time(model)
nothing, # solution violations = instance.find_violated_lazy_constraints(model)
) if length(violations) == 0
break
end
append!(found_lazy, violations)
for v in violations
instance.build_lazy_constraint(data.model, v)
end
end end
update_solution!(data)
instance.found_violated_lazy_constraints = found_lazy
instance.found_violated_user_cuts = []
primal_bound = JuMP.objective_value(model)
dual_bound = JuMP.objective_bound(model)
if JuMP.objective_sense(model) == MOI.MIN_SENSE
sense = "min"
lower_bound = dual_bound
upper_bound = primal_bound
else
sense = "max"
lower_bound = primal_bound
upper_bound = dual_bound
end
return Dict("Lower bound" => lower_bound,
"Upper bound" => upper_bound,
"Sense" => sense,
"Wallclock time" => wallclock_time,
"Nodes" => 1,
"Log" => log,
"Warm start value" => nothing)
end
function add_constraint(self, constraint)
@warn "JuMPSolver: add_constraint not implemented"
end
function set_warm_start(self, solution) function solve_lp(data::JuMPSolverData; tee::Bool=false)
basename_idx_to_var = self.data.basename_idx_to_var model, bin_vars = data.model, data.bin_vars
for (basename, subsolution) in solution for var in bin_vars
for (idx, value) in subsolution JuMP.unset_binary(var)
value != nothing || continue JuMP.set_upper_bound(var, 1.0)
var = basename_idx_to_var[basename, idx] JuMP.set_lower_bound(var, 0.0)
JuMP.set_start_value(var, value)
end
end
end end
log = optimize_and_capture_output!(model, tee=tee)
function clear_warm_start(self) update_solution!(data)
@error "JuMPSolver: clear_warm_start not implemented" obj_value = JuMP.objective_value(model)
for var in bin_vars
JuMP.set_binary(var)
end end
return Dict("Optimal value" => obj_value,
"Log" => log)
end
function fix(self, solution)
@timeit "fix" begin
basename_idx_to_var = self.data.basename_idx_to_var
for (basename, subsolution) in solution
for (idx, value) in subsolution
value != nothing || continue
var = basename_idx_to_var[basename, idx]
JuMP.fix(var, value, force=true)
end
end
end
end
function set_instance(self, instance, model) function update_solution!(data::JuMPSolverData)
@timeit "set_instance" begin var_to_basename_idx, model = data.var_to_basename_idx, data.model
self.data.instance = instance solution = Dict{String,Dict{String,Float64}}()
self.data.model = model for var in JuMP.all_variables(model)
self.data.var_to_basename_idx = Dict(var => varname_split(JuMP.name(var)) var in keys(var_to_basename_idx) || continue
for var in JuMP.all_variables(model)) basename, idx = var_to_basename_idx[var]
self.data.basename_idx_to_var = Dict(varname_split(JuMP.name(var)) => var if !haskey(solution, basename)
for var in JuMP.all_variables(model)) solution[basename] = Dict{String,Float64}()
self.data.bin_vars = [var
for var in JuMP.all_variables(model)
if JuMP.is_binary(var)]
if self.data.optimizer != nothing
JuMP.set_optimizer(model, self.data.optimizer)
end
end end
solution[basename][idx] = JuMP.value(var)
end end
data.solution = solution
end
function solve(self; tee=false)
@timeit "solve" begin
instance, model = self.data.instance, self.data.model
set_solver_verbosity!(model, tee)
wallclock_time = 0
found_lazy = []
while true
@timeit "optimize!" begin
JuMP.optimize!(model)
end
wallclock_time += JuMP.solve_time(model)
@timeit "find_violated_lazy_constraints" begin
violations = instance.find_violated_lazy_constraints(model)
end
#@info "$(length(violations)) violations found"
if length(violations) == 0
break
end
append!(found_lazy, violations)
for v in violations
instance.build_lazy_constraint(self.data.model, v)
end
end
@timeit "update solution" begin
self._update_solution()
instance.found_violated_lazy_constraints = found_lazy
instance.found_violated_user_cuts = []
end
primal_bound = JuMP.objective_value(model)
dual_bound = JuMP.objective_bound(model)
if JuMP.objective_sense(model) == MOI.MIN_SENSE
sense = "min"
lower_bound = dual_bound
upper_bound = primal_bound
else
sense = "max"
lower_bound = primal_bound
upper_bound = dual_bound
end
end
return Dict("Lower bound" => lower_bound,
"Upper bound" => upper_bound,
"Sense" => sense,
"Wallclock time" => wallclock_time,
"Nodes" => 1,
"Log" => nothing,
"Warm start value" => nothing)
end
function solve_lp(self; tee=false) function set_instance!(data::JuMPSolverData, instance, model)
@timeit "solve_lp" begin data.instance = instance
model = self.data.model data.model = model
set_solver_verbosity!(model, tee) data.var_to_basename_idx = Dict(var => varname_split(JuMP.name(var))
bin_vars = self.data.bin_vars for var in JuMP.all_variables(model))
@timeit "unset_binary" begin data.basename_idx_to_var = Dict(varname_split(JuMP.name(var)) => var
for var in bin_vars for var in JuMP.all_variables(model))
JuMP.unset_binary(var) data.bin_vars = [var
JuMP.set_upper_bound(var, 1.0) for var in JuMP.all_variables(model)
JuMP.set_lower_bound(var, 0.0) if JuMP.is_binary(var)]
end if data.optimizer != nothing
end JuMP.set_optimizer(model, data.optimizer)
@timeit "optimize" begin end
JuMP.optimize!(model) end
end
@timeit "update solution" begin
self._update_solution() function fix!(data::JuMPSolverData, solution)
end count = 0
obj_value = JuMP.objective_value(model) for (basename, subsolution) in solution
@timeit "set_binary" begin for (idx, value) in subsolution
for var in bin_vars value != nothing || continue
JuMP.set_binary(var) var = data.basename_idx_to_var[basename, idx]
end JuMP.fix(var, value, force=true)
end count += 1
end end
return Dict("Optimal value" => obj_value)
end end
@info "Fixing $count variables"
end
function get_solution(self)
return self.data.solution
end
function _update_solution(self) function set_warm_start!(data::JuMPSolverData, solution)
var_to_basename_idx, model = self.data.var_to_basename_idx, self.data.model count = 0
solution = Dict{String,Dict{String,Float64}}() for (basename, subsolution) in solution
for var in JuMP.all_variables(model) for (idx, value) in subsolution
var in keys(var_to_basename_idx) || continue value != nothing || continue
basename, idx = var_to_basename_idx[var] var = data.basename_idx_to_var[basename, idx]
if !haskey(solution, basename) JuMP.set_start_value(var, value)
solution[basename] = Dict{String,Float64}() count += 1
end
solution[basename][idx] = JuMP.value(var)
end end
self.data.solution = solution
end end
@info "Setting warm start values for $count variables"
end
function set_gap_tolerance(self, gap_tolerance)
@warn "JuMPSolver: set_gap_tolerance not implemented"
end
function set_node_limit(self) @pydef mutable struct JuMPSolver <: InternalSolver
@warn "JuMPSolver: set_node_limit not implemented" function __init__(self; optimizer)
self.data = JuMPSolverData(nothing, # basename_idx_to_var
nothing, # var_to_basename_idx
optimizer,
nothing, # instance
nothing, # model
nothing, # bin_vars
nothing, # solution
)
end end
function set_threads(self, threads) set_warm_start(self, solution) =
@warn "JuMPSolver: set_threads not implemented" set_warm_start!(self.data, solution)
end
fix(self, solution) =
fix!(self.data, solution)
set_instance(self, instance, model) =
set_instance!(self.data, instance, model)
solve(self; tee=false) =
solve(self.data, tee=tee)
solve_lp(self; tee=false) =
solve_lp(self.data, tee=tee)
get_solution(self) =
self.data.solution
set_time_limit(self, time_limit) =
JuMP.set_time_limit_sec(self.data.model, time_limit)
function set_branching_priorities(self, priorities) set_gap_tolerance(self, gap_tolerance) =
@warn "JuMPSolver: set_gap_tolerance not implemented"
set_node_limit(self) =
@warn "JuMPSolver: set_node_limit not implemented"
set_threads(self, threads) =
@warn "JuMPSolver: set_threads not implemented"
set_branching_priorities(self, priorities) =
@warn "JuMPSolver: set_branching_priorities not implemented" @warn "JuMPSolver: set_branching_priorities not implemented"
end
add_constraint(self, constraint) =
error("JuMPSolver.add_constraint should never be called")
clear_warm_start(self) =
error("JuMPSolver.clear_warm_start should never be called")
function set_time_limit(self, time_limit)
JuMP.set_time_limit_sec(self.data.model, time_limit)
end
end end

@ -34,6 +34,7 @@ end
@test stats["Upper bound"] == 1183.0 @test stats["Upper bound"] == 1183.0
@test stats["Sense"] == "max" @test stats["Sense"] == "max"
@test stats["Wallclock time"] > 0 @test stats["Wallclock time"] > 0
@test length(stats["Log"]) > 100
solution = solver.get_solution() solution = solver.get_solution()
@test solution["x"]["1"] == 1.0 @test solution["x"]["1"] == 1.0
@ -43,6 +44,7 @@ end
stats = solver.solve_lp() stats = solver.solve_lp()
@test round(stats["Optimal value"], digits=3) == 1287.923 @test round(stats["Optimal value"], digits=3) == 1287.923
@test length(stats["Log"]) > 100
solution = solver.get_solution() solution = solver.get_solution()
@test round(solution["x"]["1"], digits=3) == 1.000 @test round(solution["x"]["1"], digits=3) == 1.000

Loading…
Cancel
Save