Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-threading on solving subproblems #345

Merged
merged 5 commits into from
May 26, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 50 additions & 34 deletions src/Algorithm/colgen.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
Base.@kwdef struct ColumnGeneration <: AbstractOptimizationAlgorithm
restr_master_solve_alg = SolveLpForm(get_dual_solution = true)
#TODO : pricing problem solver may be different depending on the
#TODO : pricing problem solver may be different depending on the
# pricing subproblem
pricing_prob_solve_alg = SolveIpForm(deactivate_artificial_vars = false,
enforce_integrality = false,
pricing_prob_solve_alg = SolveIpForm(deactivate_artificial_vars = false,
enforce_integrality = false,
log_level = 2)
max_nb_iterations::Int = 1000
optimality_tol::Float64 = 1e-5
log_print_frequency::Int = 1
store_all_ip_primal_sols::Bool = false
redcost_tol::Float64 = 1e-5
solve_subproblems_parallel::Bool = false
end

function get_storages_usage!(
Expand All @@ -25,7 +26,7 @@ end

function get_storages_to_restore!(
algo::ColumnGeneration, reform::Reformulation, storages_to_restore::StoragesToRestoreDict
)
)
master = getmaster(reform)
add_storage!(storages_to_restore, master, MasterColumnsStorage, READ_AND_WRITE)
get_storages_to_restore!(algo.restr_master_solve_alg, master, storages_to_restore)
Expand Down Expand Up @@ -70,7 +71,7 @@ end

getoptstate(data::ColGenRuntimeData) = data.optstate

function run!(algo::ColumnGeneration, rfdata::ReformData, input::OptimizationInput)::OptimizationOutput
function run!(algo::ColumnGeneration, rfdata::ReformData, input::OptimizationInput)::OptimizationOutput
reform = getreform(rfdata)
cgdata = ColGenRuntimeData(algo, reform, getoptstate(input))
optstate = getoptstate(cgdata)
Expand All @@ -80,7 +81,7 @@ function run!(algo::ColumnGeneration, rfdata::ReformData, input::OptimizationInp
stop = cg_main_loop!(algo, cgdata, rfdata)

if !stop && should_do_ph_1(masterform, cgdata)
set_ph1!(masterform, cgdata)
set_ph1!(masterform, cgdata)
stop = cg_main_loop!(algo, cgdata, rfdata)
if !stop
set_ph2!(masterform, cgdata) # pure ph2
Expand Down Expand Up @@ -147,13 +148,13 @@ end

function insert_cols_in_master!(
data::ColGenRuntimeData, masterform::Formulation, spform::Formulation, sp_solution_ids::Vector{VarId}
)
)
sp_uid = getuid(spform)
nb_of_gen_col = 0

for sol_id in sp_solution_ids
nb_of_gen_col += 1
name = string("MC_", getsortuid(sol_id))
name = string("MC_", getsortuid(sol_id))
lb = 0.0
ub = Inf
kind = Continuous
Expand Down Expand Up @@ -182,7 +183,7 @@ function contrib_improves_mlp(algo::ColumnGeneration, sp_primal_bound::PrimalBou
return sp_primal_bound < 0.0 - algo.redcost_tol
end

function contrib_improves_mlp(algo::ColumnGeneration, sp_primal_bound::PrimalBound{MaxSense})
function contrib_improves_mlp(algo::ColumnGeneration, sp_primal_bound::PrimalBound{MaxSense})
return sp_primal_bound > 0.0 + algo.redcost_tol
end

Expand All @@ -204,7 +205,7 @@ function solve_sp_to_gencol!(
algo::ColumnGeneration, masterform::Formulation, spdata::ModelData, dual_sol::DualSolution,
sp_lb::Float64, sp_ub::Float64
)::Tuple{Bool,Vector{VarId},Vector{VarId},Float64}

spform = getmodel(spdata)
recorded_solution_ids = Vector{VarId}()
sp_solution_ids_to_activate = Vector{VarId}()
Expand Down Expand Up @@ -236,15 +237,13 @@ function solve_sp_to_gencol!(

# Solve sub-problem and insert generated columns in master
# @logmsg LogLevel(-3) "optimizing pricing prob"
TO.@timeit Coluna._to "Pricing subproblem" begin
output = run!(algo.pricing_prob_solve_alg, spdata, OptimizationInput(OptimizationState(spform)))
end
output = run!(algo.pricing_prob_solve_alg, spdata, OptimizationInput(OptimizationState(spform)))
sp_optstate = getoptstate(output)

pricing_db_contrib = compute_pricing_db_contrib(algo, spform, get_ip_primal_bound(sp_optstate), sp_lb, sp_ub)

if !isfeasible(sp_optstate)
sp_is_feasible = false
sp_is_feasible = false
# @logmsg LogLevel(-3) "pricing prob is infeasible"
return sp_is_feasible, recorded_solution_ids, PrimalBound(spform)
end
Expand Down Expand Up @@ -327,9 +326,10 @@ function updatereducedcosts!(reform::Reformulation, redcostsvec::ReducedCostsVec
end

function solve_sps_to_gencols!(
algo::ColumnGeneration, cgdata::ColGenRuntimeData, rfdata::ReformData, redcostsvec::ReducedCostsVector,
algo::ColumnGeneration, cgdata::ColGenRuntimeData, rfdata::ReformData, redcostsvec::ReducedCostsVector,
dual_sol::DualSolution, sp_lbs::Dict{FormId, Float64}, sp_ubs::Dict{FormId, Float64}
)
threadstasks = Task[]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks unused

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, we don't need it anymore. I removed it.

reform = getreform(rfdata)
masterform = getmaster(reform)
nb_new_cols = 0
Expand All @@ -344,15 +344,31 @@ function solve_sps_to_gencols!(
updatereducedcosts!(reform, redcostsvec, dual_sol)

### BEGIN LOOP TO BE PARALLELIZED
for (spuid, spdata) in spsdatas
gen_status, new_sp_sol_ids, sp_sol_ids_to_activate, sp_dual_contrib = solve_sp_to_gencol!(
algo, masterform, spdata, dual_sol, sp_lbs[spuid], sp_ubs[spuid]
)
if gen_status # else Sp is infeasible: contrib = Inf
recorded_sp_solution_ids[spuid] = new_sp_sol_ids
sp_solution_to_activate[spuid] = sp_sol_ids_to_activate
if algo.solve_subproblems_parallel
spuids = collect(keys(spsdatas))
Threads.@threads for key in 1:length(spuids)
spuid = spuids[key]
spdata = spsdatas[spuid]
gen_status, new_sp_sol_ids, sp_sol_ids_to_activate, sp_dual_contrib = solve_sp_to_gencol!(
algo, masterform, spdata, dual_sol, sp_lbs[spuid], sp_ubs[spuid]
)
if gen_status # else Sp is infeasible: contrib = Inf
recorded_sp_solution_ids[spuid] = new_sp_sol_ids
sp_solution_to_activate[spuid] = sp_sol_ids_to_activate
end
sp_dual_bound_contribs[spuid] = sp_dual_contrib #float(contrib)
end
else
for (spuid, spdata) in spsdatas
gen_status, new_sp_sol_ids, sp_sol_ids_to_activate, sp_dual_contrib = solve_sp_to_gencol!(
algo, masterform, spdata, dual_sol, sp_lbs[spuid], sp_ubs[spuid]
)
if gen_status # else Sp is infeasible: contrib = Inf
recorded_sp_solution_ids[spuid] = new_sp_sol_ids
sp_solution_to_activate[spuid] = sp_sol_ids_to_activate
end
sp_dual_bound_contribs[spuid] = sp_dual_contrib #float(contrib)
end
sp_dual_bound_contribs[spuid] = sp_dual_contrib #float(contrib)
end
### END LOOP TO BE PARALLELIZED

Expand All @@ -364,7 +380,7 @@ function solve_sps_to_gencols!(
activate!(masterform, colid)
nb_new_cols += 1
end
end
end
return (nb_new_cols, dual_bound_contrib)
end

Expand All @@ -387,7 +403,7 @@ end

#stopped here
function generatecolumns!(
algo::ColumnGeneration, cgdata::ColGenRuntimeData, rfdata::ReformData, redcostsvec::ReducedCostsVector,
algo::ColumnGeneration, cgdata::ColGenRuntimeData, rfdata::ReformData, redcostsvec::ReducedCostsVector,
master_val, dual_sol, sp_lbs, sp_ubs
)
cg_optstate = getoptstate(cgdata)
Expand Down Expand Up @@ -453,7 +469,7 @@ function cg_main_loop!(algo::ColumnGeneration, cgdata::ColGenRuntimeData, rfdata
status = getfeasibilitystatus(rm_optstate)
@warn string("Solver returned that LP restricted master is infeasible or unbounded ",
"(feasibility status = " , status, ") during phase != 1.")
setfeasibilitystatus!(cg_optstate, status)
setfeasibilitystatus!(cg_optstate, status)
return true
end

Expand All @@ -464,9 +480,9 @@ function cg_main_loop!(algo::ColumnGeneration, cgdata::ColGenRuntimeData, rfdata
@error string("Solver returned that the LP restricted master is feasible but ",
"did not return a dual solution. ",
"Please open an issue (https://github.com/atoptima/Coluna.jl/issues).")
end
end

if nb_lp_primal_sols(rm_optstate) > 0
if nb_lp_primal_sols(rm_optstate) > 0
set_lp_primal_sol!(cg_optstate, get_best_lp_primal_sol(rm_optstate))
set_lp_primal_bound!(cg_optstate, get_lp_primal_bound(rm_optstate))
else
Expand All @@ -477,7 +493,7 @@ function cg_main_loop!(algo::ColumnGeneration, cgdata::ColGenRuntimeData, rfdata

update_all_ip_primal_solutions!(cg_optstate, rm_optstate)

# TODO: cleanup restricted master columns
# TODO: cleanup restricted master columns

cgdata.nb_iterations += 1

Expand All @@ -490,7 +506,7 @@ function cg_main_loop!(algo::ColumnGeneration, cgdata::ColGenRuntimeData, rfdata

if nb_new_col < 0
@error "Infeasible subproblem."
setfeasibilitystatus!(cg_optstate, INFEASIBLE)
setfeasibilitystatus!(cg_optstate, INFEASIBLE)
return true
end

Expand All @@ -499,11 +515,11 @@ function cg_main_loop!(algo::ColumnGeneration, cgdata::ColGenRuntimeData, rfdata
# TODO: update colgen stabilization

dual_bound = get_ip_dual_bound(cg_optstate)
primal_bound = get_lp_primal_bound(cg_optstate)
primal_bound = get_lp_primal_bound(cg_optstate)
ip_primal_bound = get_ip_primal_bound(cg_optstate)

if ip_gap(cg_optstate) < algo.optimality_tol
setterminationstatus!(cg_optstate, OPTIMAL)
setterminationstatus!(cg_optstate, OPTIMAL)
@logmsg LogLevel(0) "Dual bound reached primal bound."
return true
end
Expand All @@ -512,13 +528,13 @@ function cg_main_loop!(algo::ColumnGeneration, cgdata::ColGenRuntimeData, rfdata
pb = - getvalue(PrimalBound(reform))
set_lp_dual_bound!(cg_optstate, DualBound(reform, db))
set_lp_primal_bound!(cg_optstate, PrimalBound(reform, pb))
setfeasibilitystatus!(cg_optstate, INFEASIBLE)
setfeasibilitystatus!(cg_optstate, INFEASIBLE)
@logmsg LogLevel(0) "Phase one determines infeasibility."
return true
end
if nb_new_col == 0 || lp_gap(cg_optstate) < algo.optimality_tol
@logmsg LogLevel(0) "Column Generation Algorithm has converged."
setterminationstatus!(cg_optstate, OPTIMAL)
setterminationstatus!(cg_optstate, OPTIMAL)
return false
end
if cgdata.nb_iterations > algo.max_nb_iterations
Expand Down
1 change: 1 addition & 0 deletions src/Coluna.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import MathOptInterface
import MathOptInterface.Utilities
import Distributed
import TimerOutputs
import Base.Threads

using Logging
using Printf
Expand Down
4 changes: 1 addition & 3 deletions src/MathProg/optimizerwrappers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,7 @@ end
function optimize!(form::Formulation, optimizer::MoiOptimizer)
@logmsg LogLevel(-4) "MOI formulation before synch: "
@logmsg LogLevel(-4) getoptimizer(form)
TO.@timeit Coluna._to "Sync solver" begin
sync_solver!(getoptimizer(form), form)
end
sync_solver!(getoptimizer(form), form)
@logmsg LogLevel(-3) "MOI formulation after synch: "
@logmsg LogLevel(-3) getoptimizer(form)
nbvars = MOI.get(form.optimizer.inner, MOI.NumberOfVariables())
Expand Down