Skip to content

Commit

Permalink
Alias for a simplified BCP parameterisation (#603)
Browse files Browse the repository at this point in the history
* BranchCutAndPriceAlgorithm alias for a simplified parameterisation of the branch-cut-and-price algorithm

* small correction

* Another correction

* One more small correction to pass the tests

* - A simplification of the BranchCutAndPrice alias
- Modification of the VarBranchingRule algorithm : now all the fractional variables of the maximum branching priority (set in BlockDecomposition) are selected

* Correct docstring for BranchCutAndPrice

* Bug correction

* Apply suggestions from code review

Co-authored-by: Guillaume Marques <guillaume.marques@protonmail.com>

* Taking into account the comment of Guillaume

* More optimizatoin

Co-authored-by: Guillaume Marques <guillaume.marques@protonmail.com>
  • Loading branch information
rrsadykov and guimarqu authored Oct 4, 2021
1 parent 5870353 commit ee59439
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 53 deletions.
1 change: 1 addition & 0 deletions src/Algorithm/Algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ include("branching/varbranching.jl")
include("branching/branchingalgo.jl")

include("treesearch.jl")
include("branchcutprice.jl")

# Algorithm should export only methods usefull to define & parametrize algorithms, and
# data structures from utilities.
Expand Down
166 changes: 166 additions & 0 deletions src/Algorithm/branchcutprice.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
"""
Coluna.Algorithm.BranchCutAndPriceAlgorithm(;
maxnumnodes::Int = 100000,
opt_atol::Float64 = Coluna.DEF_OPTIMALITY_ATOL,
opt_rtol::Float64 = Coluna.DEF_OPTIMALITY_RTOL,
restmastipheur_timelimit::Int = 600,
restmastipheur_frequency::Int = 1,
restmastipheur_maxdepth::Int = 1000,
max_nb_cut_rounds::Int = 3,
colgen_stabilization::Float64 = 0.0,
colgen_cleanup_threshold::Int = 10000,
colgen_stages_pricing_solvers::Vector{Int} = [1],
stbranch_phases_num_candidates::Vector{Int} = Int[],
stbranch_intrmphase_stages::Vector{NamedTuple{(:userstage, :solverid, :maxiters), Tuple{Int64, Int64, Int64}}}
)
Alias for a simplified parameterisation
of the branch-cut-and-price algorithm.
Parameters :
- `maxnumnodes` : maximum number of nodes explored by the algorithm
- `opt_atol` : optimality absolute tolerance
- `opt_rtol` : optimality relative tolerance
- `restmastipheur_timelimit` : time limit in seconds for the restricted master heuristic
(if <= 0 then the heuristic is disabled)
- `restmastipheur_frequency` : frequency of calls to the restricted master heuristic
- `restmastipheur_maxdepth` : maximum depth of the search tree when the restricted master heuristic is called
- `max_nb_cut_rounds` : maximum number of cut generation rounds in every node of the search tree
- `colgen_stabilization` : parameterisation of the dual price smoothing stabilization of column generation
0.0 - disabled, 1.0 - automatic, ∈(0.0,1.0) - fixed smoothing parameter
- `colgen_cleanup_threshold` : threshold (number of active columns) to trigger the restricted master LP clean up
- `colgen_stages_pricing_solvers` : vector of pricing solver ids for every column generation stage,
pricing solvers should be specified using argument `solver` of `BlockDecomposition.specify!()`,
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
- `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)},
in the last phase, the standard column-and-cut generation procedure is run,
in the first phase (if their number is >= 2), only the restricted master LP is resolved,
in the second (intermediate) phase (if their number is >= 3), usually a heuristic pricing is used
or the number of column generation iterations is limited, this is parameterised with the three
next parameters, cut separation is not called in the intermediate strong branching phase,
if the lenth of this vector > 3, then all values except first, second, and last ones are ignored
- `stbranch_intrmphase_stages` : the size of this vector is the number of column generation stages in the intemediate phase of strong branching
each element of the vector is the named triple (userstage, solver, maxiters). "userstage" is the
value of "stage" parameter passed to the pricing callback on this stage, "solverid" is the solver id on this stage,
and "maxiters" is the maximum number of column generation iterations on this stage
"""



function BranchCutAndPriceAlgorithm(;
maxnumnodes::Int = 100000,
branchingtreefile::Union{Nothing, String} = nothing,
opt_atol::Float64 = Coluna.DEF_OPTIMALITY_ATOL,
opt_rtol::Float64 = Coluna.DEF_OPTIMALITY_RTOL,
restmastipheur_timelimit::Int = 600,
restmastipheur_frequency::Int = 1,
restmastipheur_maxdepth::Int = 1000,
max_nb_cut_rounds::Int = 3,
colgen_stabilization::Float64 = 0.0,
colgen_cleanup_threshold::Int = 10000,
colgen_stages_pricing_solvers::Vector{Int64} = [1],
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)]
)
heuristics = ParameterisedHeuristic[]
if restmastipheur_timelimit > 0
heuristic = ParameterisedHeuristic(
SolveIpForm(moi_params = MoiOptimize(
get_dual_bound = false,
time_limit = restmastipheur_timelimit
)),
1.0, 1.0, restmastipheur_frequency,
restmastipheur_maxdepth, "Restricted Master IP"
)
push!(heuristics, heuristic)
end

colgen_stages = ColumnGeneration[]

for (stage, solver_id) in enumerate(colgen_stages_pricing_solvers)
colgen = ColumnGeneration(
pricing_prob_solve_alg = SolveIpForm(
optimizer_id = solver_id,
user_params = UserOptimize(stage = stage),
moi_params = MoiOptimize(
deactivate_artificial_vars = false,
enforce_integrality = false
)
),
smoothing_stabilization = colgen_stabilization,
cleanup_threshold = colgen_cleanup_threshold,
opt_atol = opt_atol,
opt_rtol = opt_rtol
)
push!(colgen_stages, colgen)
end

conquer = ColCutGenConquer(
stages = colgen_stages,
max_nb_cut_rounds = max_nb_cut_rounds,
primal_heuristics = heuristics,
opt_atol = opt_atol,
opt_rtol = opt_rtol
)

branching = NoBranching()
branching_rules = PrioritisedBranchingRule[PrioritisedBranchingRule(VarBranchingRule(), 1.0, 1.0)]

if !isempty(stbranch_phases_num_candidates)
branching_phases = BranchingPhase[]
if length(stbranch_phases_num_candidates) >= 2
push!(branching_phases,
BranchingPhase(first(stbranch_phases_num_candidates), RestrMasterLPConquer())
)
if length(stbranch_phases_num_candidates) >= 3
intrmphase_stages = ColumnGeneration[]
for tuple in stbranch_intrmphase_stages
colgen = ColumnGeneration(
pricing_prob_solve_alg = SolveIpForm(
optimizer_id = tuple.solverid,
user_params = UserOptimize(stage = tuple.userstage),
moi_params = MoiOptimize(
deactivate_artificial_vars = false,
enforce_integrality = false
)
),
smoothing_stabilization = colgen_stabilization,
cleanup_threshold = colgen_cleanup_threshold,
max_nb_iterations = tuple.maxiters,
opt_atol = opt_atol,
opt_rtol = opt_rtol
)
push!(intrmphase_stages, colgen)
end
intrmphase_conquer = ColCutGenConquer(
stages = intrmphase_stages,
max_nb_cut_rounds = 0,
primal_heuristics = [],
opt_atol = opt_atol,
opt_rtol = opt_rtol
)
push!(branching_phases,
BranchingPhase(stbranch_phases_num_candidates[2], intrmphase_conquer)
)
end
end
push!(branching_phases, BranchingPhase(last(stbranch_phases_num_candidates), conquer))
branching = StrongBranching(rules = branching_rules, phases = branching_phases)
else
branching = StrongBranching(rules = branching_rules)
end

return TreeSearchAlgorithm(
conqueralg = conquer,
dividealg = branching,
maxnumnodes = maxnumnodes,
branchingtreefile = branchingtreefile,
opt_atol = opt_atol;
opt_rtol = opt_rtol
)
end
18 changes: 16 additions & 2 deletions src/Algorithm/branching/varbranching.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,27 @@ function run!(
!input.isoriginalsol && return BranchingRuleOutput(input.local_id, Vector{BranchingGroup}())

master = getmaster(reform)
groups = Vector{BranchingGroup}()
local_id = input.local_id
max_priority = -Inf
for (var_id, val) in input.solution
# Do not consider continuous variables as branching candidates
getperenkind(master, var_id) == Continuous && continue
getbranchingpriority(master, var_id) < input.minimum_priority && continue
if !isinteger(val, input.int_tol)
brpriority = getbranchingpriority(master, var_id)
if max_priority < brpriority
max_priority = brpriority
end
end
end

if max_priority == -Inf
return BranchingRuleOutput(local_id, BranchingGroup[])
end

groups = BranchingGroup[]
for (var_id, val) in input.solution
getperenkind(master, var_id) == Continuous && continue
if !isinteger(val, input.int_tol) && getbranchingpriority(master, var_id) == max_priority
#description string is just the variable name
candidate = VarBranchingCandidate(getname(master, var_id), var_id)
local_id += 1
Expand Down
2 changes: 1 addition & 1 deletion test/bound_callback_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function bound_callback_tests()
coluna = JuMP.optimizer_with_attributes(
CL.Optimizer,
"default_optimizer" => GLPK.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm(maxnumnodes = 2))
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm(maxnumnodes = 2))
)

model, x, dec = CLD.GeneralizedAssignment.model_without_knp_constraints(data, coluna)
Expand Down
57 changes: 23 additions & 34 deletions test/full_instances_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function generalized_assignment_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm(
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm(
branchingtreefile = "playgap.dot"
)),
"default_optimizer" => GLPK.Optimizer
Expand All @@ -39,7 +39,7 @@ function generalized_assignment_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm()),
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm()),
"default_optimizer" => GLPK.Optimizer
)

Expand All @@ -56,24 +56,15 @@ function generalized_assignment_tests()
@testset "gap - strong branching" begin
data = CLD.GeneralizedAssignment.data("mediumgapcuts3.txt")

conquer_with_small_cleanup_threshold = ClA.ColCutGenConquer(
stages = [ClA.ColumnGeneration(cleanup_threshold = 150, smoothing_stabilization = 1.0)]
)

branching = ClA.StrongBranching(
phases = [ClA.BranchingPhase(5, ClA.RestrMasterLPConquer()),
ClA.BranchingPhase(1, conquer_with_small_cleanup_threshold)],
rules = [ClA.PrioritisedBranchingRule(ClA.VarBranchingRule(), 2.0, 2.0),
ClA.PrioritisedBranchingRule(ClA.VarBranchingRule(), 1.0, 1.0)]
)

coluna = JuMP.optimizer_with_attributes(
CL.Optimizer,
"params" => CL.Params(
solver = ClA.TreeSearchAlgorithm(
conqueralg = conquer_with_small_cleanup_threshold,
dividealg = branching,
maxnumnodes = 300
solver = ClA.BranchCutAndPriceAlgorithm(
maxnumnodes = 300,
colgen_stabilization = 1.0,
colgen_cleanup_threshold = 150,
stbranch_phases_num_candidates = [10, 3, 1],
stbranch_intrmphase_stages = [(userstage=1, solverid=1, maxiters=2)]
)
),
"default_optimizer" => GLPK.Optimizer
Expand Down Expand Up @@ -105,7 +96,7 @@ function generalized_assignment_tests()
coluna = JuMP.optimizer_with_attributes(
CL.Optimizer,
"params" => CL.Params(
solver = ClA.TreeSearchAlgorithm(
solver = ClA.BranchCutAndPriceAlgorithm(
maxnumnodes = 5
)
),
Expand Down Expand Up @@ -151,7 +142,7 @@ function generalized_assignment_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm()),
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm()),
"default_optimizer" => GLPK.Optimizer
)

Expand All @@ -166,7 +157,7 @@ function generalized_assignment_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm()),
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm()),
"default_optimizer" => GLPK.Optimizer
)

Expand All @@ -181,7 +172,7 @@ function generalized_assignment_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm()),
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm()),
"default_optimizer" => GLPK.Optimizer
)

Expand All @@ -197,7 +188,7 @@ function generalized_assignment_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm()),
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm()),
"default_optimizer" => GLPK.Optimizer
)

Expand All @@ -212,7 +203,7 @@ function generalized_assignment_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm()),
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm()),
"default_optimizer" => GLPK.Optimizer
)

Expand Down Expand Up @@ -250,10 +241,8 @@ function generalized_assignment_tests()
coluna = JuMP.optimizer_with_attributes(
CL.Optimizer,
"params" => CL.Params(
solver = ClA.TreeSearchAlgorithm(
conqueralg = ClA.ColCutGenConquer(
stages = [ClA.ColumnGeneration(smoothing_stabilization = 1.0)]
),
solver = ClA.BranchCutAndPriceAlgorithm(
colgen_stabilization = 1.0,
maxnumnodes = 300
)
),
Expand All @@ -273,7 +262,7 @@ function generalized_assignment_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm()),
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm()),
"default_optimizer" => GLPK.Optimizer
)

Expand All @@ -289,7 +278,7 @@ function generalized_assignment_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm())
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm())
)

problem, x, dec = CLD.GeneralizedAssignment.model(data, coluna)
Expand All @@ -308,8 +297,8 @@ function generalized_assignment_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm(
conqueralg = ClA.ColCutGenConquer(max_nb_cut_rounds = 1000)
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm(
max_nb_cut_rounds = 1000
)),
"default_optimizer" => GLPK.Optimizer
)
Expand Down Expand Up @@ -393,7 +382,7 @@ function capacitated_lot_sizing_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm()),
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm()),
"default_optimizer" => GLPK.Optimizer
)

Expand Down Expand Up @@ -428,7 +417,7 @@ function cutting_stock_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm()),
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm()),
"default_optimizer" => GLPK.Optimizer
)

Expand All @@ -445,7 +434,7 @@ function cvrp_tests()

coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm(
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm(
maxnumnodes = 10000,
branchingtreefile = "cvrp.dot"
)),
Expand Down
2 changes: 1 addition & 1 deletion test/optimizer_with_attributes_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function optimizer_with_attributes_test()
println(GLPK.Optimizer)
coluna = JuMP.optimizer_with_attributes(
Coluna.Optimizer,
"params" => CL.Params(solver = ClA.TreeSearchAlgorithm(
"params" => CL.Params(solver = ClA.BranchCutAndPriceAlgorithm(
branchingtreefile = "playgap.dot"
)),
"default_optimizer" => JuMP.optimizer_with_attributes(GLPK.Optimizer, "tm_lim" => 60 * 1_100, "msg_lev" => GLPK.GLP_MSG_OFF)
Expand Down
Loading

0 comments on commit ee59439

Please sign in to comment.