Skip to content

Commit

Permalink
"Strict integrality check" parameter for column generation (#1128)
Browse files Browse the repository at this point in the history
* Added "strict integrality check" parameter for the column generation algorithm

* Remove unnecessary `_is_integer` method
  • Loading branch information
rrsadykov committed Feb 2, 2024
1 parent a70ad0a commit 79b4092
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 20 deletions.
14 changes: 10 additions & 4 deletions docs/src/api/colgen.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,15 +331,21 @@ Go back to the [column generation iteration overview](#Column-generation-iterati
The algorithm checks the integrality of
the primal solution to the master LP to improve the global primal bound of the branch-cut-price algorithm.

In the default implementation, the integrality check is done using the `MathProg.proj_cols_is_integer` method.
It implements the procedure described in the paper (TODO).
Basically, it sorts the column used in the master LP primal solution
in lexicographic order.
By default, the integrality check is done using the `MathProg.proj_cols_is_integer` method.
It implements the mapping procedure from the paper "F. Vanderbeck, Branching in branch-and-price: a generic scheme, Math.Prog. (2011)".
Basically, it sorts the column used in the master LP primal solution in lexicographic order.
It assigns a weight to each column equal to the value of the column in the master LP solution.
It then forms columns of weight one by accumulating the columns of the fractional solution.
If columns are integral, the solution is integral.
This is a heuristic procedure so it can miss some integer solutions.

In the case the pricing subproblems are solved by a callback, and some subproblem integer variables are "hidden" from _Coluna_
(values of these variables are usually stored in `CustomData` associated with the pricing problem solution),
the mapping procedure may not be valid. In this case, the integrality should be checked in the "strict" way, i.e.,
by explicitly verifying that all columns are integer.

Integrality check procedure is set using parameter `strict_integrality_check` (`false` by default) of the `ColumnGenerationAlgorithm`.

If the solution is integral, the essential cut callback is called to make sure it is feasible.

**References**:
Expand Down
5 changes: 5 additions & 0 deletions src/Algorithm/branchcutprice.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
colgen_stabilization::Float64 = 0.0,
colgen_cleanup_threshold::Int = 10000,
colgen_stages_pricing_solvers::Vector{Int} = [1],
colgen_strict_integrality_check::Bool,
stbranch_phases_num_candidates::Vector{Int} = Int[],
stbranch_intrmphase_stages::Vector{NamedTuple{(:userstage, :solverid, :maxiters), Tuple{Int64, Int64, Int64}}}
)
Expand All @@ -35,6 +36,7 @@ Parameters :
the number of column generation stages is equal to the length of this vector,
column generation stages are executed in the reverse order,
the first stage should be exact to ensure the optimality of the BCP algorithm
- `colgen_strict_integrality_check` : see description in `Coluna.Algorithm.ColumnGeneration`
- `stbranch_phases_num_candidates` : maximum number of candidates for each strong branching phase,
strong branching is activated if this vector is not empty,
the number of phases in strong branching is equal to min{3, length(stbranch_phases_num_candidates)},
Expand Down Expand Up @@ -65,6 +67,7 @@ function BranchCutAndPriceAlgorithm(;
colgen_stabilization::Float64 = 0.0,
colgen_cleanup_threshold::Int = 10000,
colgen_stages_pricing_solvers::Vector{Int64} = [1],
colgen_strict_integrality_check::Bool = false,
stbranch_phases_num_candidates::Vector{Int64} = Int[],
stbranch_intrmphase_stages::Vector{NamedTuple{(:userstage, :solverid, :maxiters), Tuple{Int64, Int64, Int64}}} = [(userstage=1, solverid=1, maxiters=100)]
)
Expand All @@ -86,6 +89,7 @@ function BranchCutAndPriceAlgorithm(;
enforce_integrality = false
)
),
strict_integrality_check = colgen_strict_integrality_check,
stages_pricing_solver_ids = colgen_stages_pricing_solvers,
smoothing_stabilization = colgen_stabilization,
cleanup_threshold = colgen_cleanup_threshold,
Expand Down Expand Up @@ -119,6 +123,7 @@ function BranchCutAndPriceAlgorithm(;
enforce_integrality = false
)
),
strict_integrality_check = colgen_strict_integrality_check,
stages_pricing_solver_ids = map(t -> t.solverid, stbranch_intrmphase_stages),
smoothing_stabilization = colgen_stabilization,
cleanup_threshold = colgen_cleanup_threshold,
Expand Down
14 changes: 2 additions & 12 deletions src/Algorithm/branching/branchingalgo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,6 @@ Branching.get_int_tol(ctx::BranchingContext) = ctx.int_tol
Branching.get_selection_criterion(ctx::BranchingContext) = ctx.selection_criterion
Branching.get_rules(ctx::BranchingContext) = ctx.rules

function _is_integer(sol::PrimalSolution)
for (varid, val) in sol
integer_val = abs(val - round(val)) < 1e-5
if !integer_val
return false
end
end
return true
end

function _has_identical_sps(master::Formulation{DwMaster}, reform::Reformulation)
for (sp_id, sp) in get_dw_pricing_sps(reform)
lm_constr_id = sp.duty_data.lower_multiplicity_constr_id
Expand All @@ -100,8 +90,8 @@ function _has_identical_sps(master::Formulation{DwMaster}, reform::Reformulation
end

function _why_no_candidate(master::Formulation{DwMaster}, reform, input, extended_sol, original_sol)
integer_orig_sol = _is_integer(original_sol)
integer_ext_sol = _is_integer(extended_sol)
integer_orig_sol = isinteger(original_sol)
integer_ext_sol = isinteger(extended_sol)
identical_sp = _has_identical_sps(master, reform)
if integer_orig_sol && !integer_ext_sol && identical_sp
message = """
Expand Down
9 changes: 9 additions & 0 deletions src/Algorithm/colgen.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
)
),
essential_cut_gen_alg = CutCallbacks(call_robust_facultative = false),
strict_integrality_check = false,
max_nb_iterations = 1000,
log_print_frequency = 1,
redcost_tol = 1e-4,
Expand Down Expand Up @@ -38,6 +39,11 @@ reaches the maximum number of iterations.
**Options:**
- `max_nb_iterations`: maximum number of iterations
- `log_print_frequency`: display frequency of iterations statistics
- `strict_integrality_check`: by default (value `false`) the integrality check in column generation is performed by the mapping procedure
from "F. Vanderbeck, Branching in branch-and-price: a generic scheme, Math.Prog. (2011)";
in the case the pricing subproblems are solved by a callback, and some subproblem integer variables
are "hidden" from _Coluna_, the mapping procedure may not be valid, and the integrality should be checked
in the "strict" way (explicitly verifying that all columns are integer)
Undocumented parameters are in alpha version.
Expand Down Expand Up @@ -65,6 +71,7 @@ struct ColumnGeneration <: AbstractOptimizationAlgorithm
pricing_prob_solve_alg::SolveIpForm
stages_pricing_solver_ids::Vector{Int}
essential_cut_gen_alg::CutCallbacks
strict_integrality_check::Bool
max_nb_iterations::Int64
log_print_frequency::Int64
store_all_ip_primal_sols::Bool
Expand All @@ -89,6 +96,7 @@ struct ColumnGeneration <: AbstractOptimizationAlgorithm
),
stages_pricing_solver_ids = [1],
essential_cut_gen_alg = CutCallbacks(call_robust_facultative=false),
strict_integrality_check = false,
max_nb_iterations = 1000,
log_print_frequency = 1,
store_all_ip_primal_sols = false,
Expand All @@ -108,6 +116,7 @@ struct ColumnGeneration <: AbstractOptimizationAlgorithm
pricing_prob_solve_alg,
stages_pricing_solver_ids,
essential_cut_gen_alg,
strict_integrality_check,
max_nb_iterations,
log_print_frequency,
store_all_ip_primal_sols,
Expand Down
6 changes: 5 additions & 1 deletion src/Algorithm/colgen/default.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ mutable struct ColGenContext <: ColGen.AbstractColGenContext

stages_pricing_solver_ids::Vector{Int}

strict_integrality_check::Bool

reduced_cost_helper::ReducedCostsCalculationHelper
subgradient_helper::SubgradientCalculationHelper
sp_var_redcosts::Union{Nothing,Any} # TODO: type
Expand Down Expand Up @@ -42,6 +44,7 @@ mutable struct ColGenContext <: ColGen.AbstractColGenContext
alg.restr_master_solve_alg,
alg.restr_master_optimizer_id,
alg.stages_pricing_solver_ids,
alg.strict_integrality_check,
rch,
sh,
nothing,
Expand Down Expand Up @@ -453,7 +456,8 @@ function ColGen.check_primal_ip_feasibility!(master_lp_primal_sol, ctx::ColGenCo
return nothing, false
end
# Check if integral.
primal_sol_is_integer = MathProg.proj_cols_is_integer(master_lp_primal_sol)
primal_sol_is_integer = ctx.strict_integrality_check ? isinteger(master_lp_primal_sol) :
MathProg.proj_cols_is_integer(master_lp_primal_sol)
if !primal_sol_is_integer
return nothing, false
end
Expand Down
9 changes: 6 additions & 3 deletions test/e2e/gap/generalized_assignment.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ function gap_toy_instance()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver=ClA.BranchCutAndPriceAlgorithm(
branchingtreefile="playgap.dot",
), local_art_var_cost=10000.0,
"params" => CL.Params(
solver = ClA.BranchCutAndPriceAlgorithm(
branchingtreefile = "playgap.dot",
colgen_strict_integrality_check = true, # only for testing purposes, not really needed here
),
local_art_var_cost=10000.0,
global_art_var_cost=100000.0),
"default_optimizer" => GLPK.Optimizer
)
Expand Down

0 comments on commit 79b4092

Please sign in to comment.