From 3b8859e92fd742e4cfe12e3822f330378e58c848 Mon Sep 17 00:00:00 2001 From: dianabarros Date: Tue, 19 May 2020 15:44:16 -0300 Subject: [PATCH 1/4] Solving sps in parallel --- src/Algorithm/colgen.jl | 79 +++++++++++++++++++++++++---------------- src/Coluna.jl | 1 + src/globals.jl | 1 + 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/src/Algorithm/colgen.jl b/src/Algorithm/colgen.jl index 56dd9bdfa..30e7b3ad9 100644 --- a/src/Algorithm/colgen.jl +++ b/src/Algorithm/colgen.jl @@ -1,9 +1,9 @@ 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 @@ -25,7 +25,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) @@ -70,7 +70,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) @@ -80,7 +80,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 @@ -147,13 +147,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 @@ -182,7 +182,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 @@ -204,7 +204,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}() @@ -244,7 +244,7 @@ function solve_sp_to_gencol!( 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 @@ -327,9 +327,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[] reform = getreform(rfdata) masterform = getmaster(reform) nb_new_cols = 0 @@ -344,15 +345,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 Coluna.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 @@ -364,7 +381,7 @@ function solve_sps_to_gencols!( activate!(masterform, colid) nb_new_cols += 1 end - end + end return (nb_new_cols, dual_bound_contrib) end @@ -387,7 +404,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) @@ -453,7 +470,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 @@ -464,9 +481,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 @@ -477,7 +494,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 @@ -490,7 +507,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 @@ -499,11 +516,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 @@ -512,13 +529,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 diff --git a/src/Coluna.jl b/src/Coluna.jl index b69fea3e2..3d5ec0640 100644 --- a/src/Coluna.jl +++ b/src/Coluna.jl @@ -5,6 +5,7 @@ import MathOptInterface import MathOptInterface.Utilities import Distributed import TimerOutputs +import Base.Threads using Logging using Printf diff --git a/src/globals.jl b/src/globals.jl index 8bf6d0a49..c93a54661 100644 --- a/src/globals.jl +++ b/src/globals.jl @@ -3,5 +3,6 @@ Base.@kwdef mutable struct GlobalValues end global const _to = TO.TimerOutput() +global const parallel = true _elapsed_solve_time() = (time() - _globals_.initial_solve_time) From 9b047f89845d96cffdec46d92a5fdd16d545a927 Mon Sep 17 00:00:00 2001 From: dianabarros Date: Tue, 19 May 2020 17:08:04 -0300 Subject: [PATCH 2/4] Removing TimerOutputs not thread-safe --- src/Algorithm/colgen.jl | 4 +--- src/MathProg/optimizerwrappers.jl | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Algorithm/colgen.jl b/src/Algorithm/colgen.jl index 30e7b3ad9..b14cc97ab 100644 --- a/src/Algorithm/colgen.jl +++ b/src/Algorithm/colgen.jl @@ -236,9 +236,7 @@ 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) diff --git a/src/MathProg/optimizerwrappers.jl b/src/MathProg/optimizerwrappers.jl index d887f2bcc..9af498213 100644 --- a/src/MathProg/optimizerwrappers.jl +++ b/src/MathProg/optimizerwrappers.jl @@ -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()) From c40fd879d546a1de9f22db78f79eccb3b815b895 Mon Sep 17 00:00:00 2001 From: dianabarros Date: Fri, 22 May 2020 21:43:35 -0300 Subject: [PATCH 3/4] Adding parallel field in ColumnGeneration --- src/Algorithm/colgen.jl | 3 ++- src/globals.jl | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Algorithm/colgen.jl b/src/Algorithm/colgen.jl index b14cc97ab..37644f197 100644 --- a/src/Algorithm/colgen.jl +++ b/src/Algorithm/colgen.jl @@ -10,6 +10,7 @@ Base.@kwdef struct ColumnGeneration <: AbstractOptimizationAlgorithm 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!( @@ -343,7 +344,7 @@ function solve_sps_to_gencols!( updatereducedcosts!(reform, redcostsvec, dual_sol) ### BEGIN LOOP TO BE PARALLELIZED - if Coluna.parallel + if algo.solve_subproblems_parallel spuids = collect(keys(spsdatas)) Threads.@threads for key in 1:length(spuids) spuid = spuids[key] diff --git a/src/globals.jl b/src/globals.jl index c93a54661..8bf6d0a49 100644 --- a/src/globals.jl +++ b/src/globals.jl @@ -3,6 +3,5 @@ Base.@kwdef mutable struct GlobalValues end global const _to = TO.TimerOutput() -global const parallel = true _elapsed_solve_time() = (time() - _globals_.initial_solve_time) From 41fc1314eaf863306b921d2583771a9cea6b5c28 Mon Sep 17 00:00:00 2001 From: dianabarros Date: Mon, 25 May 2020 19:28:08 -0300 Subject: [PATCH 4/4] Removing unused task variable --- src/Algorithm/colgen.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Algorithm/colgen.jl b/src/Algorithm/colgen.jl index 37644f197..ff15ee63f 100644 --- a/src/Algorithm/colgen.jl +++ b/src/Algorithm/colgen.jl @@ -329,7 +329,6 @@ function solve_sps_to_gencols!( algo::ColumnGeneration, cgdata::ColGenRuntimeData, rfdata::ReformData, redcostsvec::ReducedCostsVector, dual_sol::DualSolution, sp_lbs::Dict{FormId, Float64}, sp_ubs::Dict{FormId, Float64} ) - threadstasks = Task[] reform = getreform(rfdata) masterform = getmaster(reform) nb_new_cols = 0