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

Remove useless args in ColGen interface & fix stabilization #948

Merged
merged 5 commits into from
Jun 22, 2023
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
31 changes: 27 additions & 4 deletions src/Algorithm/colgen/default.jl
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ ColGen.get_primal_sol(master_res::ColGenMasterResult) = get_best_lp_primal_sol(m
ColGen.get_dual_sol(master_res::ColGenMasterResult) = get_best_lp_dual_sol(master_res.result)
ColGen.get_obj_val(master_res::ColGenMasterResult) = get_lp_primal_bound(master_res.result)

function ColGen.update_master_constrs_dual_vals!(ctx::ColGenContext, phase, reform, master_lp_dual_sol)
function ColGen.update_master_constrs_dual_vals!(ctx::ColGenContext, master_lp_dual_sol)
master = ColGen.get_master(ctx)
# Set all dual value of all constraints to 0.
for constr in Iterators.values(getconstrs(master))
Expand All @@ -415,9 +415,9 @@ function _violates_essential_cuts!(master, master_lp_primal_sol, env)
return cutcb_output.nb_cuts_added > 0
end

ColGen.check_primal_ip_feasibility!(_, ctx::ColGenContext, ::ColGenPhase1, _, _) = nothing, false
ColGen.check_primal_ip_feasibility!(_, ctx::ColGenContext, ::ColGenPhase1, _) = nothing, false

function ColGen.check_primal_ip_feasibility!(master_lp_primal_sol, ctx::ColGenContext, phase, reform, env)
function ColGen.check_primal_ip_feasibility!(master_lp_primal_sol, ctx::ColGenContext, phase, env)
# Check if feasible.
if contains(master_lp_primal_sol, varid -> isanArtificialDuty(getduty(varid)))
return nothing, false
Expand Down Expand Up @@ -461,7 +461,8 @@ end
_set_column_cost!(master, col_id, phase) = nothing
_set_column_cost!(master, col_id, ::ColGenPhase1) = setcurcost!(master, col_id, 0.0)

function ColGen.insert_columns!(reform, ctx::ColGenContext, phase, columns)
function ColGen.insert_columns!(ctx::ColGenContext, phase, columns)
reform = ColGen.get_reform(ctx)
primal_sols_to_insert = PrimalSolution{Formulation{DwSp}}[]
col_ids_to_activate = Set{VarId}()
master = ColGen.get_master(ctx)
Expand Down Expand Up @@ -846,3 +847,25 @@ ColGen.get_master_ip_primal_sol(output::ColGenPhaseOutput) = output.master_ip_pr
ColGen.get_best_ip_primal_master_sol_found(output::ColGenPhaseOutput) = output.master_lp_primal_sol
ColGen.get_final_lp_primal_master_sol_found(output::ColGenPhaseOutput) = output.master_ip_primal_sol
ColGen.get_final_db(output::ColGenPhaseOutput) = output.db

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.
# We don't update α if we are in a misprice sequence.
if stab.automatic && stab.nb_misprices == 0
is_min = ColGen.is_minimization(ctx)
primal_sol = _primal_solution(master, generated_columns, is_min)
α = _dynamic_alpha_schedule(stab.base_α, mast_dual_sol, stab.cur_stab_center, subgradient_helper(ctx), primal_sol, is_min)
stab.base_α = α
end

if isbetter(DualBound(master, valid_db), stab.valid_dual_bound)
stab.cur_stab_center = mast_dual_sol
stab.valid_dual_bound = DualBound(master, valid_db)
end
if isbetter(DualBound(master, pseudo_db), stab.pseudo_dual_bound)
stab.stab_center_for_next_iteration = mast_dual_sol
stab.pseudo_dual_bound = DualBound(master, pseudo_db)
end
return
end
13 changes: 8 additions & 5 deletions src/Algorithm/colgen/printer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ ColGen.is_minimization(ctx::ColGenPrinterContext) = ColGen.is_minimization(ctx.i
ColGen.get_pricing_subprobs(ctx::ColGenPrinterContext) = ColGen.get_pricing_subprobs(ctx.inner)

ColGen.setup_stabilization!(ctx::ColGenPrinterContext, master) = ColGen.setup_stabilization!(ctx.inner, master)
function ColGen.update_stabilization_after_pricing_optim!(stab, ctx::ColGenPrinterContext, generated_columns, master, valid_db, pseudo_db, mast_dual_sol)
return ColGen.update_stabilization_after_pricing_optim!(stab, ctx.inner, generated_columns, master, valid_db, pseudo_db, mast_dual_sol)
end

ColGen.new_phase_iterator(ctx::ColGenPrinterContext) = ColGen.new_phase_iterator(ctx.inner)
ColGen.new_stage_iterator(ctx::ColGenPrinterContext) = ColGen.new_stage_iterator(ctx.inner)
Expand All @@ -41,11 +44,11 @@ function ColGen.optimize_master_lp_problem!(master, ctx::ColGenPrinterContext, e
return output
end

function ColGen.update_master_constrs_dual_vals!(ctx::ColGenPrinterContext, phase, reform, master_lp_dual_sol)
return ColGen.update_master_constrs_dual_vals!(ctx.inner, phase, reform, master_lp_dual_sol)
function ColGen.update_master_constrs_dual_vals!(ctx::ColGenPrinterContext, master_lp_dual_sol)
return ColGen.update_master_constrs_dual_vals!(ctx.inner, master_lp_dual_sol)
end

ColGen.check_primal_ip_feasibility!(mast_primal_sol, ctx::ColGenPrinterContext, phase, reform, env) = ColGen.check_primal_ip_feasibility!(mast_primal_sol, ctx.inner, phase, reform, env)
ColGen.check_primal_ip_feasibility!(mast_primal_sol, ctx::ColGenPrinterContext, phase, env) = ColGen.check_primal_ip_feasibility!(mast_primal_sol, ctx.inner, phase, env)
ColGen.update_inc_primal_sol!(ctx::ColGenPrinterContext, ip_primal_sol) = ColGen.update_inc_primal_sol!(ctx.inner, ip_primal_sol)

ColGen.get_subprob_var_orig_costs(ctx::ColGenPrinterContext) = ColGen.get_subprob_var_orig_costs(ctx.inner)
Expand All @@ -57,8 +60,8 @@ end

ColGen.update_reduced_costs!(ctx::ColGenPrinterContext, phase, red_costs) = ColGen.update_reduced_costs!(ctx.inner, phase, red_costs)

function ColGen.insert_columns!(reform, ctx::ColGenPrinterContext, phase, columns)
col_ids = ColGen.insert_columns!(reform, ctx.inner, phase, columns)
function ColGen.insert_columns!(ctx::ColGenPrinterContext, phase, columns)
col_ids = ColGen.insert_columns!(ctx.inner, phase, columns)
if ctx.print_column_reduced_cost
_print_column_reduced_costs(ColGen.get_reform(ctx), col_ids)
end
Expand Down
24 changes: 2 additions & 22 deletions src/Algorithm/colgen/stabilization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ struct NoColGenStab end
ColGen.update_stabilization_after_master_optim!(::NoColGenStab, phase, mast_dual_sol) = false
ColGen.get_master_dual_sol(::NoColGenStab, phase, mast_dual_sol) = mast_dual_sol
ColGen.check_misprice(::NoColGenStab, generated_cols, mast_dual_sol) = false
ColGen.update_stabilization_after_pricing_optim!(::NoColGenStab, master, valid_db, pseudo_db, mast_dual_sol) = nothing
ColGen.update_stabilization_after_misprice!(::NoColGenStab, mast_dual_sol) = nothing
ColGen.update_stabilization_after_iter!(::NoColGenStab, ctx, master, generated_columns, mast_dual_sol) = nothing
ColGen.update_stabilization_after_iter!(::NoColGenStab, mast_dual_sol) = nothing
ColGen.get_output_str(::NoColGenStab) = 0.0
"""
Implementation of the "Smoothing with a self adjusting parameter" described in the paper of
Expand Down Expand Up @@ -55,18 +54,6 @@ function ColGen.get_master_dual_sol(stab::ColGenStab, phase, mast_dual_sol)
return stab.cur_α * stab.cur_stab_center + (1 - stab.cur_α) * mast_dual_sol
end

function ColGen.update_stabilization_after_pricing_optim!(stab::ColGenStab, master, valid_db, pseudo_db, mast_dual_sol)
if isbetter(DualBound(master, valid_db), stab.valid_dual_bound)
stab.cur_stab_center = mast_dual_sol
stab.valid_dual_bound = DualBound(master, valid_db)
end
if isbetter(DualBound(master, pseudo_db), stab.pseudo_dual_bound)
stab.stab_center_for_next_iteration = mast_dual_sol
stab.pseudo_dual_bound = DualBound(master, pseudo_db)
end
return
end

ColGen.check_misprice(stab::ColGenStab, generated_cols, mast_dual_sol) = length(generated_cols.columns) == 0 && stab.cur_α > 0.0

function _misprice_schedule(automatic, nb_misprices, base_α)
Expand Down Expand Up @@ -159,14 +146,7 @@ function _dynamic_alpha_schedule(
return increase ? f_incr(α) : f_decr(α)
end

function ColGen.update_stabilization_after_iter!(stab::ColGenStab, ctx, master, generated_columns, mast_dual_sol)
if stab.automatic
is_min = ColGen.is_minimization(ctx)
primal_sol = _primal_solution(master, generated_columns, is_min)
α = _dynamic_alpha_schedule(stab.base_α, mast_dual_sol, stab.cur_stab_center, subgradient_helper(ctx), primal_sol, is_min)
stab.base_α = α
end

function ColGen.update_stabilization_after_iter!(stab::ColGenStab, mast_dual_sol)
if !isnothing(stab.stab_center_for_next_iteration)
stab.cur_stab_center = stab.stab_center_for_next_iteration
stab.stab_center_for_next_iteration = nothing
Expand Down
52 changes: 21 additions & 31 deletions src/ColGen/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ See `optimize_master_lp_problem!`.
Returns a primal solution expressed in the original problem variables if the current master
LP solution is integer feasible; `nothing` otherwise.
"""
@mustimplement "ColGenMaster" check_primal_ip_feasibility!(mast_lp_primal_sol, ::AbstractColGenContext, phase, reform, env) = nothing
@mustimplement "ColGenMaster" check_primal_ip_feasibility!(mast_lp_primal_sol, ::AbstractColGenContext, phase, env) = nothing

"""
Returns `true` if the new master IP primal solution is better than the current; `false` otherwise.
Expand All @@ -173,7 +173,7 @@ Updates the current master IP primal solution.
Updates dual value of the master constraints.
Dual values of the constraints can be used when the pricing solver supports non-robust cuts.
"""
@mustimplement "ColGenReducedCosts" update_master_constrs_dual_vals!(ctx, phase, reform, mast_lp_dual_sol) = nothing
@mustimplement "ColGenReducedCosts" update_master_constrs_dual_vals!(ctx, mast_lp_dual_sol) = nothing

"""
Updates reduced costs of the master variables.
Expand Down Expand Up @@ -207,16 +207,6 @@ if something unexpected happens.
@mustimplement "ColGen" insert_columns!(reform, ctx, phase, columns) = nothing


function check_master_termination_status(mast_result)
if !is_infeasible(mast_result) && !is_unbounded(mast_result)
@assert !isnothing(get_dual_sol(mast_result))
end
end

function check_pricing_termination_status(pricing_result)
# TODO
end

"""
compute_dual_bound(ctx, phase, master_lp_obj_val, master_dbs, mast_dual_sol) -> Float64

Expand Down Expand Up @@ -265,9 +255,10 @@ _inf(is_min_sense) = is_min_sense ? Inf : -Inf
function run_colgen_iteration!(context, phase, stage, env, ip_primal_sol, stab)
master = get_master(context)
is_min_sense = is_minimization(context)
mast_result = optimize_master_lp_problem!(master, context, env)
O = colgen_iteration_output_type(context)

mast_result = optimize_master_lp_problem!(master, context, env)

# Iteration continues only if master is not infeasible nor unbounded and has dual
# solution.
if is_infeasible(mast_result)
Expand All @@ -276,8 +267,6 @@ function run_colgen_iteration!(context, phase, stage, env, ip_primal_sol, stab)
throw(UnboundedProblemError("Unbounded master problem."))
end

check_master_termination_status(mast_result)

# Master primal solution
mast_primal_sol = get_primal_sol(mast_result)
if !isnothing(mast_primal_sol) && isbetter(mast_primal_sol, ip_primal_sol)
Expand All @@ -290,32 +279,37 @@ function run_colgen_iteration!(context, phase, stage, env, ip_primal_sol, stab)
# If the formulation changes, one needs to restart the column generation to update
# memoization to calculate reduced costs and stabilization.
# TODO: the user can get the reformulation from the context.
new_ip_primal_sol, new_cut_in_master = check_primal_ip_feasibility!(mast_primal_sol, context, phase, get_reform(context), env)
new_ip_primal_sol, new_cut_in_master = check_primal_ip_feasibility!(mast_primal_sol, context, phase, env)
if new_cut_in_master
return new_iteration_output(O, is_min_sense, nothing, nothing, 0, true, false, false, false, false, false, nothing, nothing, nothing)
end
if !isnothing(new_ip_primal_sol)
ip_primal_sol = new_ip_primal_sol
update_inc_primal_sol!(context, ip_primal_sol) # TODO: change method name because the incumbent is maintained by colgen
update_inc_primal_sol!(context, ip_primal_sol)
end
end

mast_dual_sol = get_dual_sol(mast_result)
if isnothing(mast_dual_sol)
error("Cannot continue")
# error or stop? (depends on the context)
# TODO: user friendly error message.
end

# Stores dual solution in the constraint. This is used when the pricing solver supports
# non-robust cuts.
# TODO: the user can get the reformulation from the context.
update_master_constrs_dual_vals!(context, phase, get_reform(context), mast_dual_sol)
update_master_constrs_dual_vals!(context, mast_dual_sol)

# Compute reduced cost (generic operation) by you must support math operations.
# using the master dual solution.
# We always compute the reduced costs of the subproblem variables against the real master
# dual solution because this is the cost of the subproblem variables in the pricing problems
# if we don't use stabilization, or because we use this cost to compute the real reduced cost
# of the columns when using stabilization.
c = get_subprob_var_orig_costs(context)
A = get_subprob_var_coef_matrix(context)
red_costs = c - transpose(A) * mast_dual_sol

# Buffer when using stabilization to compute the real reduced cost
# of the column once generated.
update_reduced_costs!(context, phase, red_costs)

# Stabilization
Expand All @@ -324,8 +318,9 @@ function run_colgen_iteration!(context, phase, stage, env, ip_primal_sol, stab)

# TODO: check the compatibility of the pricing strategy and the stabilization.

# All generated columns will be stored in the following container. We will insert them
# into the master after the optimization of the pricing subproblems.
# All generated columns during this iteration will be stored in the following container.
# We will insert them into the master after the optimization of the pricing subproblems.
# It is empty.
generated_columns = set_of_columns(context)

valid_db = nothing
Expand Down Expand Up @@ -380,8 +375,6 @@ function run_colgen_iteration!(context, phase, stage, env, ip_primal_sol, stab)
throw(UnboundedProblemError("Unbounded subproblem."))
end

check_pricing_termination_status(pricing_result)

primal_sols = get_primal_sols(pricing_result)
nb_cols_pushed = 0
for primal_sol in primal_sols # multi column generation support.
Expand Down Expand Up @@ -412,7 +405,7 @@ function run_colgen_iteration!(context, phase, stage, env, ip_primal_sol, stab)
# pseudo dual bound is used for stabilization only.
pseudo_db = compute_dual_bound(context, phase, sps_pb, cur_mast_dual_sol)

update_stabilization_after_pricing_optim!(stab, master, valid_db, pseudo_db, mast_dual_sol)
update_stabilization_after_pricing_optim!(stab, context, generated_columns, master, valid_db, pseudo_db, mast_dual_sol)

# We have finished to solve all pricing subproblems.
# If we have stabilization, we need to check if we have misprice.
Expand All @@ -423,18 +416,15 @@ function run_colgen_iteration!(context, phase, stage, env, ip_primal_sol, stab)
if misprice
update_stabilization_after_misprice!(stab, mast_dual_sol)
cur_mast_dual_sol = get_master_dual_sol(stab, phase, mast_dual_sol)
generated_columns = set_of_columns(context) # TODO: not sure because seems redoundant: no cols in set => misprice
end
end

# Insert columns into the master.
# The implementation is responsible for checking if the column is "valid".
# TODO: the user can get the reformulation from the context.
col_ids = insert_columns!(get_reform(context), context, phase, generated_columns)
col_ids = insert_columns!(context, phase, generated_columns)
nb_cols_inserted = length(col_ids)

# TODO: remove the context from the arguments.
update_stabilization_after_iter!(stab, context, master, generated_columns, mast_dual_sol)
update_stabilization_after_iter!(stab, mast_dual_sol)

return new_iteration_output(O, is_min_sense, get_obj_val(mast_result), valid_db, nb_cols_inserted, false, false, false, false, false, false, mast_primal_sol, ip_primal_sol, mast_dual_sol)
end
6 changes: 3 additions & 3 deletions src/ColGen/stabilization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ Updates stabilization after pricing optimization where:
- `pseudo_db` is the pseudo dual bound of the problem after optimization of the pricing problems
- `mast_dual_sol` is the dual solution to the master problem
"""
@mustimplement "ColGenStab" update_stabilization_after_pricing_optim!(stab, master, valid_db, pseudo_db, mast_dual_sol) = nothing
@mustimplement "ColGenStab" update_stabilization_after_pricing_optim!(stab, ctx, generated_columns, master, valid_db, pseudo_db, mast_dual_sol) = nothing

"""
Updates stabilization after a misprice.
Updates stabilization after a misprice.
Argument `mast_dual_sol` is the dual solution to the master problem.
"""
@mustimplement "ColGenStab" update_stabilization_after_misprice!(stab, mast_dual_sol) = nothing
Expand All @@ -46,7 +46,7 @@ Updates stabilization after an iteration of the column generation algorithm. Arg
- `generated_columns` is the set of generated columns
- `mast_dual_sol` is the dual solution to the master problem
"""
@mustimplement "ColGenStab" update_stabilization_after_iter!(stab, ctx, master, generated_columns, mast_dual_sol) = nothing
@mustimplement "ColGenStab" update_stabilization_after_iter!(stab, mast_dual_sol) = nothing

"Returns a string with a short information about the stabilization."
@mustimplement "ColGenStab" get_output_str(stab) = nothing
Loading