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

Removing branching group #679

Merged
merged 2 commits into from
Aug 9, 2022
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 src/Algorithm/Algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ include("divide.jl") # TODO: DivideInput and DivideOutput are already implementa
include("branching/selectioncriteria.jl")
include("branching/branchinggroup.jl")
include("branching/branchingrule.jl")
include("branching/varbranching.jl")
include("branching/candidates.jl")
include("branching/branchingalgo.jl")

include("treesearch.jl")
Expand Down
69 changes: 35 additions & 34 deletions src/Algorithm/branching/branchingalgo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ function exploits_primal_solutions(algo::StrongBranching)
end

function perform_strong_branching_with_phases!(
algo::StrongBranching, env::Env, reform::Reformulation, input::DivideInput, groups::Vector{BranchingGroup}
)::OptimizationState
algo::StrongBranching, env::Env, reform::Reformulation, input::DivideInput, candidates::Vector{C}
)::OptimizationState where {C<:AbstractBranchingCandidate}

parent = getparent(input)
exploitsprimalsolutions::Bool = exploits_primal_solutions(algo)
Expand All @@ -144,12 +144,12 @@ function perform_strong_branching_with_phases!(
# children for each branching candidate.
if phase_index < length(algo.phases)
nb_candidates_for_next_phase = algo.phases[phase_index + 1].max_nb_candidates
if phase_index > 1 && length(groups) <= nb_candidates_for_next_phase
if phase_index > 1 && length(candidates) <= nb_candidates_for_next_phase
continue
end
# In phase 1, we make sure that the number of candidates for the next phase is
# at least equal to the number of initial candidates
nb_candidates_for_next_phase = min(nb_candidates_for_next_phase, length(groups))
nb_candidates_for_next_phase = min(nb_candidates_for_next_phase, length(candidates))
end

conquer_units_to_restore = UnitsUsage()
Expand All @@ -162,32 +162,35 @@ function perform_strong_branching_with_phases!(

#for nice printing, we compute the maximum description length
max_descr_length::Int64 = 0
for group in groups
description = getdescription(group.candidate)
for candidate in candidates
description = getdescription(candidate)
if (max_descr_length < length(description))
max_descr_length = length(description)
end
end

for (group_index,group) in enumerate(groups)
for (candidate_index, candidate) in enumerate(candidates)
#TO DO: verify if time limit is reached
# TODO: start

if phase_index == 1
generate_children!(group, env, reform, parent)
generate_children!(candidate, env, reform, parent)
else
regenerate_children!(group, parent)
regenerate_children!(candidate, parent)
end

if phase_index > 1
sort!(group.children, by = x -> get_lp_primal_bound(getoptstate(x)))
sort!(candidate.children, by = x -> get_lp_primal_bound(getoptstate(x)))
end
# TODO: end

# Here, we avoid the removal of pruned nodes at this point to let them
# appear in the branching tree file
for (node_index, node) in enumerate(group.children)
for (node_index, node) in enumerate(candidate.children)
if isverbose(current_phase.conquer_algo)
print(
"**** SB phase ", phase_index, " evaluation of candidate ",
group_index, " (branch ", node_index, " : ", node.branchdescription
candidate_index, " (branch ", node_index, " : ", node.branchdescription
)
@printf "), value = %6.2f\n" getvalue(get_lp_primal_bound(getoptstate(node)))
end
Expand Down Expand Up @@ -215,28 +218,28 @@ function perform_strong_branching_with_phases!(

if phase_index < length(algo.phases)
# not the last phase, thus we compute the product score
group.score = product_score(group, getoptstate(parent))
candidate.score = product_score(candidate.children, getoptstate(parent))
else
# the last phase, thus we compute the tree size score
group.score = tree_depth_score(group, getoptstate(parent))
candidate.score = tree_depth_score(candidate.children, getoptstate(parent))
end
print_bounds_and_score(group, phase_index, max_descr_length)
print_bounds_and_score(candidate, phase_index, max_descr_length)
end

sort!(groups, rev = true, by = x -> (x.isconquered, x.score))
sort!(candidates, rev = true, by = x -> (x.isconquered, x.score))

if groups[1].isconquered
if candidates[1].isconquered
nb_candidates_for_next_phase = 1
end

# before deleting branching groups which are not kept for the next phase
# we need to remove record kept in these nodes
for group_index = nb_candidates_for_next_phase + 1 : length(groups)
for (node_index, node) in enumerate(groups[group_index].children)
for candidate_index = nb_candidates_for_next_phase + 1 : length(candidates)
for (node_index, node) in enumerate(candidates[candidate_index].children)
remove_records!(node.recordids)
end
end
resize!(groups, nb_candidates_for_next_phase)
resize!(candidates, nb_candidates_for_next_phase)
end
return sbstate
end
Expand All @@ -246,7 +249,7 @@ end
# - stopping criterion
# - what happens when original_solution or extended_solution are nothing
function _select_candidates_with_branching_rule(rules, phases, selection_criterion, int_tol, parent_is_root, reform, env, original_solution, extended_solution)
kept_branch_groups = BranchingGroup[]
kept_branch_candidates = AbstractBranchingCandidate[]

# We sort branching rules by their root/non-root priority.
sorted_rules = sort(rules, rev = true, by = x -> getpriority(x, parent_is_root))
Expand All @@ -270,7 +273,7 @@ function _select_candidates_with_branching_rule(rules, phases, selection_criteri
# Priority of the current branching rule.
priority = getpriority(prioritised_rule, parent_is_root)

nb_candidates_found = length(kept_branch_groups)
nb_candidates_found = length(kept_branch_candidates)

# Before selecting new candidates with the current branching rule, check if generation
# of candidates stops. Generation of candidates stops when:
Expand All @@ -295,7 +298,7 @@ function _select_candidates_with_branching_rule(rules, phases, selection_criteri
local_id, int_tol, priority
)
)
append!(kept_branch_groups, output.groups)
append!(kept_branch_candidates, output.groups)
local_id = output.local_id

if projection_is_possible(getmaster(reform)) && !isnothing(extended_solution)
Expand All @@ -305,15 +308,13 @@ function _select_candidates_with_branching_rule(rules, phases, selection_criteri
local_id, int_tol, priority
)
)
append!(kept_branch_groups, output.groups)
append!(kept_branch_candidates, output.groups)
local_id = output.local_id
end

select_candidates!(kept_branch_groups, selection_criterion, max_nb_candidates)

select_candidates!(kept_branch_candidates, selection_criterion, max_nb_candidates)
priority_of_last_generated_groups = priority
end
return kept_branch_groups
return kept_branch_candidates
end

function run!(algo::StrongBranching, env::Env, reform::Reformulation, input::DivideInput)::DivideOutput
Expand Down Expand Up @@ -341,21 +342,21 @@ function run!(algo::StrongBranching, env::Env, reform::Reformulation, input::Div
end

parent_is_root = iszero(getdepth(parent))
kept_branch_groups = _select_candidates_with_branching_rule(
kept_branch_candidates = _select_candidates_with_branching_rule(
algo.rules, algo.phases, algo.selection_criterion, algo.int_tol, parent_is_root, reform, env, original_solution, extended_solution
)

if isempty(kept_branch_groups)
if isempty(kept_branch_candidates)
@logmsg LogLevel(0) "No branching candidates found. No children will be generated."
return DivideOutput(Node[], optstate)
end

# in the case of simple branching, it remains to generate the children
if isempty(algo.phases)
generate_children!(kept_branch_groups[1], env, reform, parent)
return DivideOutput(kept_branch_groups[1].children, OptimizationState(getmaster(reform)))
children = generate_children!(kept_branch_candidates[1], env, reform, parent)
return DivideOutput(children, OptimizationState(getmaster(reform)))
end

sbstate = perform_strong_branching_with_phases!(algo, env, reform, input, kept_branch_groups)
return DivideOutput(kept_branch_groups[1].children, sbstate)
sbstate = perform_strong_branching_with_phases!(algo, env, reform, input, kept_branch_candidates)
return DivideOutput(kept_branch_candidates[1].children, sbstate)
end
57 changes: 10 additions & 47 deletions src/Algorithm/branching/branchinggroup.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,16 @@
############################################################################################


function BranchingGroup(
candidate::AbstractBranchingCandidate, local_id::Int64, lhs::Float64
)
return BranchingGroup(candidate, local_id, lhs, SbNode[], false, typemin(Float64))
end

get_lhs_distance_to_integer(group::BranchingGroup) =
min(group.lhs - floor(group.lhs), ceil(group.lhs) - group.lhs)

function generate_children!(
group::BranchingGroup, env::Env, reform::Reformulation, parent::Node
)
group.children = generate_children(group.candidate, group.lhs, env, reform, parent)
return
end

# TODO : it does not look like a regeneration but more like a new vector where we
# reassign children
function regenerate_children!(group::BranchingGroup, parent::Node)
new_children = SbNode[]
for child in group.children
push!(new_children, SbNode(parent, child))
end
group.children = new_children
return
end

# TODO : this method needs code documentation & context
function product_score(group::BranchingGroup, parent_optstate::OptimizationState)
# TODO : unit tests
function product_score(children::Vector{SbNode}, parent_optstate::OptimizationState)
# TO DO : we need to mesure the gap to the cut-off value
parent_lp_dual_bound = get_lp_dual_bound(parent_optstate)
parent_delta = diff(get_ip_primal_bound(parent_optstate), parent_lp_dual_bound)

all_branches_above_delta = true
deltas = zeros(Float64, length(group.children))
for (i, node) in enumerate(group.children)
deltas = zeros(Float64, length(children))
for (i, node) in enumerate(children)
node_delta = diff(get_lp_primal_bound(getoptstate(node)), parent_lp_dual_bound)
if node_delta < parent_delta
all_branches_above_delta = false
Expand Down Expand Up @@ -69,6 +43,7 @@ function product_score(group::BranchingGroup, parent_optstate::OptimizationState
end

# TODO : this method needs code documentation & context
# TODO ; unit tests
function number_of_leaves(gap::Float64, deltas::Vector{Float64})
inf::Float64 = 0.0
sup::Float64 = 1e20
Expand All @@ -95,18 +70,19 @@ function number_of_leaves(gap::Float64, deltas::Vector{Float64})
end

# TODO : this method needs code documentation & context
function tree_depth_score(group::BranchingGroup, parent_optstate::OptimizationState)
if length(group.children) == 0
# TODO : this method needs unit tests
function tree_depth_score(children::Vector{SbNode}, parent_optstate::OptimizationState)
if iszero(length(children))
return 0.0
end

# TO DO : we need to mesure the gap to the cut-off value
parent_lp_dual_bound = get_lp_dual_bound(parent_optstate)
parent_delta = diff(get_ip_primal_bound(parent_optstate), parent_lp_dual_bound)

deltas = zeros(Float64, length(group.children))
deltas = zeros(Float64, length(children))
nb_zero_deltas = 0
for (i, node) in enumerate(group.children)
for (i, node) in enumerate(children)
node_delta = diff(get_lp_primal_bound(getoptstate(node)), parent_lp_dual_bound)
if node_delta < 1e-6 # TO DO : use tolerance here
nb_zero_deltas += 1
Expand Down Expand Up @@ -134,16 +110,3 @@ function tree_depth_score(group::BranchingGroup, parent_optstate::OptimizationSt
end
return score
end

function print_bounds_and_score(group::BranchingGroup, phase_index::Int64, max_description_length::Int64)
lengthdiff = max_description_length - length(getdescription(group.candidate))
print("SB phase ", phase_index, " branch on ", getdescription(group.candidate))
@printf " (lhs=%.4f)" group.lhs
print(repeat(" ", lengthdiff), " : [")
for (node_index, node) in enumerate(group.children)
node_index > 1 && print(",")
@printf "%10.4f" getvalue(get_lp_primal_bound(getoptstate(node)))
end
@printf "], score = %10.4f\n" group.score
return
end
10 changes: 5 additions & 5 deletions src/Algorithm/branching/branchingrule.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function run!(
)::BranchingRuleOutput
# variable branching works only for the original solution
if !input.isoriginalsol
return BranchingRuleOutput(input.local_id, BranchingGroup[])
return BranchingRuleOutput(input.local_id, [])
end

master = getmaster(reform)
Expand All @@ -43,19 +43,19 @@ function run!(
end

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

groups = BranchingGroup[]
groups = SingleVarBranchingCandidate[]
for (var_id, val) in input.solution
continuous_var = getperenkind(master, var_id) == Continuous
int_val = abs(round(val) - val) < input.int_tol
br_priority = getbranchingpriority(master, var_id)
if !continuous_var && !int_val && br_priority == max_priority
# Description string of the candidate is the variable name
candidate = SingleVarBranchingCandidate(getname(master, var_id), var_id)
local_id += 1
push!(groups, BranchingGroup(candidate, local_id, val))
candidate = SingleVarBranchingCandidate(getname(master, var_id), var_id, local_id, val)
push!(groups, candidate)
end
end

Expand Down
Loading