Skip to content

Commit

Permalink
stop colgen when db reaches ipb (#1020)
Browse files Browse the repository at this point in the history
* fix bug in colgen when db reaches ipb

* fix a last bug
  • Loading branch information
guimarqu authored Jul 26, 2023
1 parent 9fc5583 commit 8fd92b8
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 52 deletions.
51 changes: 35 additions & 16 deletions src/Algorithm/colgen/default.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,9 @@ struct ColGenPhaseOutput <: ColGen.AbstractColGenPhaseOutput
master_lp_primal_sol::Union{Nothing,PrimalSolution}
master_ip_primal_sol::Union{Nothing,PrimalSolution}
master_lp_dual_sol::Union{Nothing,DualSolution}
mlp::Union{Nothing, Float64}
db::Union{Nothing, Float64}
ipb::Union{Nothing,Float64}
mlp::Union{Nothing,Float64}
db::Union{Nothing,Float64}
new_cut_in_master::Bool
no_more_columns::Bool
infeasible::Bool
Expand All @@ -103,8 +104,9 @@ struct ColGenOutput <: ColGen.AbstractColGenOutput
master_lp_primal_sol::Union{Nothing,PrimalSolution}
master_ip_primal_sol::Union{Nothing,PrimalSolution}
master_lp_dual_sol::Union{Nothing,DualSolution}
mlp::Union{Nothing, Float64}
db::Union{Nothing, Float64}
ipb::Union{Nothing,Float64}
mlp::Union{Nothing,Float64}
db::Union{Nothing,Float64}
infeasible::Bool
end

Expand All @@ -113,6 +115,7 @@ function ColGen.new_output(::Type{<:ColGenOutput}, output::ColGenPhaseOutput)
output.master_lp_primal_sol,
output.master_ip_primal_sol,
output.master_lp_dual_sol,
output.ipb,
output.mlp,
output.db,
output.infeasible
Expand Down Expand Up @@ -193,12 +196,23 @@ end

colgen_master_has_new_cuts(output::ColGenPhaseOutput) = output.new_cut_in_master
colgen_uses_exact_stage(output::ColGenPhaseOutput) = output.exact_stage
colgen_has_converged(output::ColGenPhaseOutput) = !isnothing(output.mlp) &&
!isnothing(output.db) && (
( abs(output.mlp - output.db) < 1e-5 ) ||
( (output.min_sense) && (output.db >= output.mlp) ) ||
( !(output.min_sense) && (output.db <= output.mlp) )
)

function colgen_has_converged(output::ColGenPhaseOutput)
# Check if master LP and dual bound converged.
db_mlp = !isnothing(output.mlp) && !isnothing(output.db) && (
abs(output.mlp - output.db) < 1e-5 ||
(output.min_sense && output.db >= output.mlp) ||
(!output.min_sense && output.db <= output.mlp)
)
# Check is global IP bound and dual bound converged.
db_ipb = !isnothing(output.ipb) && !isnothing(output.db) && (
abs(output.ipb - output.db) < 1e-5 ||
(output.min_sense && output.db >= output.ipb) ||
(!output.min_sense && output.db <= output.ipb)
)
return db_mlp || db_ipb
end

colgen_has_no_new_cols(output::ColGenPhaseOutput) = output.no_more_columns

## Implementation of `next_phase`.
Expand Down Expand Up @@ -810,8 +824,9 @@ end
"Object for the output of an iteration of the column generation default implementation."
struct ColGenIterationOutput <: ColGen.AbstractColGenIterationOutput
min_sense::Bool
mlp::Union{Nothing, Float64}
db::Union{Nothing, Float64}
ipb::Union{Nothing,Float64}
mlp::Union{Nothing,Float64}
db::Union{Nothing,Float64}
nb_new_cols::Int
new_cut_in_master::Bool
# Equals `true` if the master subsolver returns infeasible.
Expand Down Expand Up @@ -845,6 +860,7 @@ function ColGen.new_iteration_output(::Type{<:ColGenIterationOutput},
)
return ColGenIterationOutput(
min_sense,
get_global_primal_bound(master_ip_primal_sol),
mlp,
db,
nb_new_cols,
Expand Down Expand Up @@ -872,9 +888,10 @@ ColGen.get_dual_bound(output::ColGenIterationOutput) = output.db
_gap(mlp, db) = (mlp - db) / abs(db)
_colgen_gap_closed(mlp, db, atol, rtol) = _gap(mlp, db) < 0 || isapprox(mlp, db, atol = atol, rtol = rtol)

ColGen.stop_colgen_phase(ctx::ColGenContext, phase, env, ::Nothing, inc_dual_bound, colgen_iteration) = false
function ColGen.stop_colgen_phase(ctx::ColGenContext, phase, env, colgen_iter_output::ColGenIterationOutput, inc_dual_bound, colgen_iteration)
ColGen.stop_colgen_phase(ctx::ColGenContext, phase, env, ::Nothing, inc_dual_bound, ip_primal_sol, colgen_iteration) = false
function ColGen.stop_colgen_phase(ctx::ColGenContext, phase, env, colgen_iter_output::ColGenIterationOutput, inc_dual_bound, ip_primal_sol, colgen_iteration)
mlp = colgen_iter_output.mlp
pb = getvalue(get_global_primal_bound(ip_primal_sol))
db = inc_dual_bound
sc = colgen_iter_output.min_sense ? 1 : -1
return colgen_iteration >= ctx.nb_colgen_iteration_limit ||
Expand All @@ -885,7 +902,8 @@ function ColGen.stop_colgen_phase(ctx::ColGenContext, phase, env, colgen_iter_ou
colgen_iter_output.unbounded_subproblem ||
colgen_iter_output.nb_new_cols <= 0 ||
colgen_iter_output.new_cut_in_master ||
_colgen_gap_closed(sc * mlp, sc * db, 1e-8, 1e-5)
_colgen_gap_closed(sc * mlp, sc * db, 1e-8, 1e-5) ||
_colgen_gap_closed(sc * pb, sc * db, 1e-8, 1e-5)
end

ColGen.before_colgen_iteration(ctx::ColGenContext, phase) = nothing
Expand All @@ -898,6 +916,7 @@ function ColGen.new_phase_output(::Type{<:ColGenPhaseOutput}, min_sense, phase,
colgen_iter_output.master_lp_primal_sol,
colgen_iter_output.master_ip_primal_sol,
colgen_iter_output.master_lp_dual_sol,
colgen_iter_output.ipb,
colgen_iter_output.mlp,
inc_dual_bound,
colgen_iter_output.new_cut_in_master,
Expand All @@ -915,6 +934,7 @@ function ColGen.new_phase_output(::Type{<:ColGenPhaseOutput}, min_sense, phase::
colgen_iter_output.master_lp_primal_sol,
colgen_iter_output.master_ip_primal_sol,
colgen_iter_output.master_lp_dual_sol,
colgen_iter_output.ipb,
colgen_iter_output.mlp,
inc_dual_bound,
colgen_iter_output.new_cut_in_master,
Expand All @@ -929,7 +949,6 @@ end

ColGen.get_master_ip_primal_sol(output::ColGenPhaseOutput) = output.master_ip_primal_sol


ColGen.update_stabilization_after_pricing_optim!(::NoColGenStab, ctx::ColGenContext, generated_columns, master, valid_db, pseudo_db, mast_dual_sol) = nothing
function ColGen.update_stabilization_after_pricing_optim!(stab::ColGenStab, ctx::ColGenContext, generated_columns, master, valid_db, pseudo_db, mast_dual_sol)
# At each iteration, we always update α after the first pricing optimization.
Expand Down
9 changes: 5 additions & 4 deletions src/Algorithm/colgen/printer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,14 @@ function ColGen.colgen_iteration_output_type(ctx::ColGenPrinterContext)
return ColGen.colgen_iteration_output_type(ctx.inner)
end

function ColGen.stop_colgen_phase(ctx::ColGenPrinterContext, phase, env, colgen_iter_output, inc_dual_bound, colgen_iteration)
return ColGen.stop_colgen_phase(ctx.inner, phase, env, colgen_iter_output, inc_dual_bound, colgen_iteration)
function ColGen.stop_colgen_phase(ctx::ColGenPrinterContext, phase, env, colgen_iter_output, inc_dual_bound, ip_primal_sol, colgen_iteration)
return ColGen.stop_colgen_phase(ctx.inner, phase, env, colgen_iter_output, inc_dual_bound, ip_primal_sol, colgen_iteration)
end

ColGen.before_colgen_iteration(ctx::ColGenPrinterContext, phase) = nothing

function _colgen_iter_str(
colgen_iteration, colgen_iter_output::ColGenIterationOutput, phase::Int, stage::Int, sp_time::Float64, mst_time::Float64, optim_time::Float64, alpha, pb
colgen_iteration, colgen_iter_output::ColGenIterationOutput, phase::Int, stage::Int, sp_time::Float64, mst_time::Float64, optim_time::Float64, alpha
)
phase_string = " "
if phase == 1
Expand Down Expand Up @@ -173,6 +173,7 @@ function _colgen_iter_str(

mlp::Float64 = colgen_iter_output.mlp
db::Float64 = colgen_iter_output.db
pb::Float64 = colgen_iter_output.ipb

nb_new_col::Int = ColGen.get_nb_new_cols(colgen_iter_output)

Expand All @@ -183,7 +184,7 @@ function _colgen_iter_str(
end

function ColGen.after_colgen_iteration(ctx::ColGenPrinterContext, phase, stage, env, colgen_iteration, stab, ip_primal_sol, colgen_iter_output)
println(_colgen_iter_str(colgen_iteration, colgen_iter_output, ctx.phase, ColGen.stage_id(stage), ctx.sp_elapsed_time, ctx.mst_elapsed_time, elapsed_optim_time(env), ColGen.get_output_str(stab), getvalue(get_global_primal_bound(ip_primal_sol))))
println(_colgen_iter_str(colgen_iteration, colgen_iter_output, ctx.phase, ColGen.stage_id(stage), ctx.sp_elapsed_time, ctx.mst_elapsed_time, elapsed_optim_time(env), ColGen.get_output_str(stab)))
return
end

Expand Down
3 changes: 2 additions & 1 deletion src/Algorithm/treesearch/branch_and_bound.jl
Original file line number Diff line number Diff line change
Expand Up @@ -227,13 +227,14 @@ function after_conquer!(space::BaBSearchSpace, current, conquer_output)

# TODO: remove later but we currently need it to print information in the json file.
current.conquer_output = conquer_output
current.ip_dual_bound = get_lp_dual_bound(conquer_output)
return
end

# Conquer
function is_pruned(space::BaBSearchSpace, current::Node)
return MathProg.gap_closed(
get_global_primal_bound(space.inc_primal_manager),
get_global_primal_bound(space.inc_primal_manager),
current.ip_dual_bound,
atol = space.opt_atol,
rtol = space.opt_rtol
Expand Down
2 changes: 1 addition & 1 deletion src/ColGen/ColGen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function run_colgen_phase!(context, phase, stage, env, ip_primal_sol, stab; iter
iteration = iter
colgen_iter_output = nothing
incumbent_dual_bound = nothing
while !stop_colgen_phase(context, phase, env, colgen_iter_output, incumbent_dual_bound, iteration)
while !stop_colgen_phase(context, phase, env, colgen_iter_output, incumbent_dual_bound, ip_primal_sol, iteration)
before_colgen_iteration(context, phase)
colgen_iter_output = run_colgen_iteration!(context, phase, stage, env, ip_primal_sol, stab)
dual_bound = ColGen.get_dual_bound(colgen_iter_output)
Expand Down
2 changes: 1 addition & 1 deletion src/ColGen/phases.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ Returns `nothing` if the algorithm must stop.
@mustimplement "ColGenPhase" setup_context!(context, ::AbstractColGenPhase) = nothing

"Returns `true` if the column generation phase must stop."
@mustimplement "ColGenPhase" stop_colgen_phase(context, phase, env, colgen_iter_output, inc_dual_bound, colgen_iteration) = nothing
@mustimplement "ColGenPhase" stop_colgen_phase(context, phase, env, colgen_iter_output, inc_dual_bound, ip_primal_sol, colgen_iteration) = nothing
1 change: 0 additions & 1 deletion test/e2e/TreeSearch/treesearch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ function test_treesearch_gap_1()

optimize!(model)
@test_broken JuMP.termination_status(model) == MOI.INFEASIBLE

end
register!(e2e_tests, "treesearch", test_treesearch_gap_1)

Expand Down
Loading

0 comments on commit 8fd92b8

Please sign in to comment.