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

Implementation of column generation stages #525

Merged
merged 4 commits into from
May 15, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion docs/src/dev/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Base.diff
Coluna.MathProg.update_ip_primal_bound!
Coluna.Algorithm.OptimizationState
Coluna.MathProg.update_lp_dual_bound!
Coluna.MathProg.getoptimizer
Coluna.MathProg.getmoioptimizer
Coluna.MathProg.update_ip_dual_bound!
Coluna.MathProg.ip_gap_closed
Coluna.Algorithm.add_ip_primal_sol!
Expand Down
8 changes: 5 additions & 3 deletions src/Algorithm/Algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ include("interface.jl")
include("basic/solveipform.jl")
include("basic/solvelpform.jl")
include("basic/cutcallback.jl")
include("basic/pricingcallback.jl")

# Child algorithms used by conquer algorithms
include("pricing.jl")
include("colgenstabilization.jl")
include("colgen.jl")
include("benders.jl")
Expand Down Expand Up @@ -70,9 +72,9 @@ export getterminationstatus, setterminationstatus!,

# Algorithm's types
export AbstractOptimizationAlgorithm, TreeSearchAlgorithm, ColCutGenConquer, ColumnGeneration,
BendersConquer, BendersCutGeneration, SolveIpForm, SolveLpForm, ExactBranchingPhase,
OnlyRestrictedMasterBranchingPhase, PreprocessAlgorithm, RestrictedMasterIPHeuristic,
OptimizationInput, OptimizationOutput, OptimizationState,
DefaultPricing, PricingCallback, BendersConquer, BendersCutGeneration, SolveIpForm,
SolveLpForm, ExactBranchingPhase, OnlyRestrictedMasterBranchingPhase, PreprocessAlgorithm,
RestrictedMasterIPHeuristic, OptimizationInput, OptimizationOutput, OptimizationState,
EmptyInput

# Units
Expand Down
50 changes: 50 additions & 0 deletions src/Algorithm/basic/pricingcallback.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
todo
"""
@with_kw struct PricingCallback <: AbstractOptimizationAlgorithm
stage::Int = 1 # stage 1 is the exact stage by convention,
# any other stage is heuristic
max_nb_ip_primal_sols = 50
end

function get_units_usage(algo::PricingCallback, spform::Formulation{DwSp})
units_usage = Tuple{AbstractModel, UnitType, UnitAccessMode}[]
push!(units_usage, (spform, StaticVarConstrUnit, READ_ONLY))
return units_usage
end

function run!(algo::PricingCallback, env::Env, spform::Formulation{DwSp}, input::OptimizationInput)::OptimizationOutput
result = OptimizationState(
spform,
ip_primal_bound = get_ip_primal_bound(getoptstate(input)),
max_length_ip_primal_sols = algo.max_nb_ip_primal_sols
)

@logmsg LogLevel(-2) "Calling user-defined optimization function."

optimizer = getuseroptimizer(spform)
cbdata = MathProg.PricingCallbackData(spform, algo.stage)
optimizer.user_oracle(cbdata)

if length(cbdata.primal_solutions) > 0
for primal_sol in cbdata.primal_solutions
add_ip_primal_sol!(result, primal_sol)
end

if algo.stage == 1 # stage 1 is exact by convention
dual_bound = getvalue(get_ip_primal_bound(result))
set_ip_dual_bound!(result, DualBound(spform, dual_bound))
setterminationstatus!(result, OPTIMAL)
else
setterminationstatus!(result, OTHER_LIMIT)
end
else
if algo.stage == 1
setterminationstatus!(result, INFEASIBLE)
else
setterminationstatus!(result, OTHER_LIMIT)
end
end

return OptimizationOutput(result)
end
25 changes: 5 additions & 20 deletions src/Algorithm/basic/solveipform.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ get_units_usage(algo::SolveIpForm, reform::Reformulation) =
function check_if_optimizer_supports_ip(optimizer::MoiOptimizer)
return MOI.supports_constraint(optimizer.inner, MOI.SingleVariable, MOI.Integer)
end
check_if_optimizer_supports_ip(optimizer::UserOptimizer) = true
check_if_optimizer_supports_ip(optimizer::UserOptimizer) = false
check_if_optimizer_supports_ip(optimizer::NoOptimizer) = false

function run!(algo::SolveIpForm, env::Env, form::Formulation, input::OptimizationInput)::OptimizationOutput
result = OptimizationState(
Expand All @@ -55,15 +56,15 @@ function run!(algo::SolveIpForm, env::Env, form::Formulation, input::Optimizatio
max_length_ip_primal_sols = algo.max_nb_ip_primal_sols
)

ip_supported = check_if_optimizer_supports_ip(getoptimizer(form))
ip_supported = check_if_optimizer_supports_ip(getmoioptimizer(form))
if !ip_supported
@warn "Optimizer of formulation with id =", getuid(form),
" does not support integer variables. Skip SolveIpForm algorithm."
setterminationstatus!(result, UNKNOWN_TERMINATION_STATUS)
return OptimizationOutput(result)
end

primal_sols = optimize_ip_form!(algo, getoptimizer(form), form, result)
primal_sols = optimize_ip_form!(algo, getmoioptimizer(form), form, result)

partial_sol = nothing
partial_sol_value = 0.0
Expand Down Expand Up @@ -133,7 +134,7 @@ end

function optimize_with_moi!(optimizer::MoiOptimizer, form::Formulation, result::OptimizationState)
sync_solver!(optimizer, form)
nbvars = MOI.get(form.optimizer.inner, MOI.NumberOfVariables())
nbvars = MOI.get(form.moioptimizer.inner, MOI.NumberOfVariables())
if nbvars <= 0
@warn "No variable in the formulation."
end
Expand Down Expand Up @@ -168,19 +169,3 @@ function optimize_ip_form!(
end
return primal_sols
end

function optimize_ip_form!(
::SolveIpForm, optimizer::UserOptimizer, form::Formulation, result::OptimizationState
)
@logmsg LogLevel(-2) "Calling user-defined optimization function."

cbdata = MathProg.PricingCallbackData(form)
optimizer.user_oracle(cbdata)

if length(cbdata.primal_solutions) > 0
setterminationstatus!(result, OPTIMAL)
else
setterminationstatus!(result, INFEASIBLE) # TODO : what if no solution found ?
end
return cbdata.primal_solutions
end
4 changes: 2 additions & 2 deletions src/Algorithm/basic/solvelpform.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ end
function optimize_lp_form!(
algo::SolveLpForm, optimizer::MoiOptimizer, form::Formulation, result::OptimizationState
)
MOI.set(form.optimizer.inner, MOI.Silent(), algo.silent)
MOI.set(form.moioptimizer.inner, MOI.Silent(), algo.silent)
optimize_with_moi!(optimizer, form, result)
return
end
Expand All @@ -68,7 +68,7 @@ function run!(algo::SolveLpForm, env::Env, form::Formulation, input::Optimizatio
partial_sol_val = getvalue(partial_sol)
end

optimizer = getoptimizer(form)
optimizer = getmoioptimizer(form)
optimize_lp_form!(algo, optimizer, form, result)
primal_sols = get_primal_solutions(form, optimizer)

Expand Down
16 changes: 5 additions & 11 deletions src/Algorithm/colgen.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
"""
Coluna.Algorithm.ColumnGeneration(
restr_master_solve_alg = SolveLpForm(get_dual_solution = true)
pricing_prob_solve_alg = SolveIpForm(
deactivate_artificial_vars = false,
enforce_integrality = false,
log_level = 2
),
pricing_prob_solve_alg = DefaultPricing(),
essential_cut_gen_alg = CutCallbacks(call_robust_facultative = false)
max_nb_iterations::Int = 1000
log_print_frequency::Int = 1
Expand All @@ -25,11 +21,7 @@ restricted master and `pricing_prob_solve_alg` to solve the subproblems.
restr_master_solve_alg = SolveLpForm(get_dual_solution=true)
# TODO : pricing problem solver may be different depending on the
# pricing subproblem
pricing_prob_solve_alg = SolveIpForm(
deactivate_artificial_vars=false,
enforce_integrality=false,
log_level=2
)
pricing_prob_solve_alg = DefaultPricing()
essential_cut_gen_alg = CutCallbacks(call_robust_facultative=false)
max_nb_iterations::Int64 = 1000
log_print_frequency::Int64 = 1
Expand Down Expand Up @@ -760,7 +752,9 @@ function cg_main_loop!(
if nb_new_columns == 0 && !essential_cuts_separated
@logmsg LogLevel(0) "No new column generated by the pricing problems."
setterminationstatus!(cg_optstate, OTHER_LIMIT)
return false, false
# if no columns are generated and lp gap is not closed then this col.gen. stage
# is a heuristic one, so we do not run phase 1 to save time
return true, false
end
if iteration > algo.max_nb_iterations
setterminationstatus!(cg_optstate, OTHER_LIMIT)
Expand Down
13 changes: 11 additions & 2 deletions src/Algorithm/colgenstabilization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,16 @@ function update_stab_after_rm_solve!(

unit.curalpha = smoothparam == 1.0 ? unit.basealpha : smoothparam

return linear_combination(unit.stabcenter, lp_dual_sol, unit.curalpha)
if !smoothing_is_active(unit)
# in this case Lagrangian bound calculation is simplified in col.gen.
# (we use the fact that the contribution of pure master variables
# is included in the value of the LP dual solution)
# thus, LP dual solution should be retured, as linear_combination()
# does not include pure master variables contribution to the bound
return lp_dual_sol
end
return linear_combination(unit.stabcenter, lp_dual_sol, unit.curalpha)
end

function norm(dualsol::DualSolution)
product_sum = 0.0
Expand Down Expand Up @@ -175,7 +183,7 @@ function update_alpha_automatically!(

# we modify the alpha parameter based on the calculated angle
if nb_new_col == 0 || angle > 1e-12
unit.basealpha -= 0.1
unit.basealpha = max(0.0, unit.basealpha - 0.1)
elseif angle < -1e-12 && unit.basealpha < 0.999
unit.basealpha += (1.0 - unit.basealpha) * 0.1
end
Expand Down Expand Up @@ -210,6 +218,7 @@ function update_stab_after_gencols!(

if unit.nb_misprices > 10 || unit.curalpha <= 0.0
unit.curalpha = 0.0
# stabilization is deactivated, thus we need to return the original LP dual solution
return lp_dual_sol
end

Expand Down
Loading