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

dual bound of tree search is dual bound of open nodes #598

Merged
merged 3 commits into from
Sep 8, 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
30 changes: 7 additions & 23 deletions src/Algorithm/treesearch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,15 @@ push!(tree::SearchTree, node::Node) = DS.enqueue!(tree.nodes, node, getnodevalue
popnode!(tree::SearchTree) = DS.dequeue!(tree.nodes)
nb_open_nodes(tree::SearchTree) = length(tree.nodes)

"""
Data used by the tree search algorithm while running. Destroyed after each run.
"""
mutable struct TreeSearchRuntimeData{Sense}
"Data used by the tree search algorithm while running. Destroyed after each run."
mutable struct TreeSearchRuntimeData
primary_tree::SearchTree
max_primary_tree_size::Int64
secondary_tree::SearchTree
tree_order::Int64
optstate::OptimizationState
exploitsprimalsolutions::Bool
Sense::Type{<:Coluna.AbstractSense}
conquer_units_to_restore::UnitsUsage
worst_db_of_pruned_node::DualBound{Sense}
end

function TreeSearchRuntimeData(algo::TreeSearchAlgorithm, reform::Reformulation, input::OptimizationInput)
Expand All @@ -100,12 +96,9 @@ function TreeSearchRuntimeData(algo::TreeSearchAlgorithm, reform::Reformulation,
collect_units_to_restore!(conquer_units_to_restore, algo.conqueralg, reform)
# divide algorithms are always manager algorithms, so we do not need to restore storage units for them

Sense = getobjsense(reform)

tsdata = TreeSearchRuntimeData{Sense}(
tsdata = TreeSearchRuntimeData(
SearchTree(algo.explorestrategy), algo.opennodeslimit, SearchTree(DepthFirstStrategy()),
1, treestate, exploitsprimalsols, Sense, conquer_units_to_restore,
-DualBound{Sense}()
1, treestate, exploitsprimalsols, conquer_units_to_restore
)
master = getmaster(reform)
push!(tsdata, RootNode(master, getoptstate(input), store_records!(reform), algo.skiprootnodeconquer))
Expand Down Expand Up @@ -305,10 +298,9 @@ function run_divide_algorithm!(
return
end

function updatedualbound!(data::TreeSearchRuntimeData)
function updatedualbound!(data::TreeSearchRuntimeData, reform::Reformulation)
treestate = getoptstate(data)
bound_value = getvalue(get_ip_primal_bound(treestate))
worst_bound = DualBound{data.Sense}(bound_value)
worst_bound = DualBound(reform, getvalue(get_ip_primal_bound(treestate)))
for (node, _) in getnodes(data.primary_tree)
db = get_ip_dual_bound(getoptstate(node))
if isbetter(worst_bound, db)
Expand All @@ -323,10 +315,6 @@ function updatedualbound!(data::TreeSearchRuntimeData)
end
end

if isbetter(worst_bound, data.worst_db_of_pruned_node)
worst_bound = data.worst_db_of_pruned_node
end

set_ip_dual_bound!(treestate, worst_bound)
return
end
Expand All @@ -349,15 +337,11 @@ function run!(
if nodestatus == OPTIMAL || nodestatus == INFEASIBLE ||
ip_gap_closed(node.optstate, rtol = algo.opt_rtol, atol = algo.opt_atol)
println("Node is already conquered. No children will be generated.")
db = get_ip_dual_bound(node.optstate)
if isbetter(tsdata.worst_db_of_pruned_node, db)
tsdata.worst_db_of_pruned_node = db
end
elseif nodestatus != TIME_LIMIT
run_divide_algorithm!(algo, env, tsdata, reform, node)
end

updatedualbound!(tsdata)
updatedualbound!(tsdata, reform)

remove_records!(node.recordids)
# we delete solutions from the node optimization state, as they are not needed anymore
Expand Down
25 changes: 25 additions & 0 deletions test/full_instances_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,31 @@ function generalized_assignment_tests()
@test CLD.GeneralizedAssignment.print_and_check_sol(data, model, x)
end


@testset "gap - node limit" begin
data = CLD.GeneralizedAssignment.data("mediumgapcuts3.txt")

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

model, x, dec = CLD.GeneralizedAssignment.model(data, coluna)
BD.objectiveprimalbound!(model, 2000.0)
BD.objectivedualbound!(model, 0.0)

JuMP.optimize!(model)

@test JuMP.objective_bound(model) ≈ 1547.3889
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure that this test is reproducible? Branching decisions may be different depending on the fractional solution (which depends on the LP solver).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm not satisfied with this test, I'm leaving it until we think about better tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it fails for some reason, we can replace @test by @test_broken

@test JuMP.termination_status(model) == MathOptInterface.OTHER_LIMIT
return
end

@testset "gap - ColGen max nb iterations" begin
data = CLD.GeneralizedAssignment.data("smallgap3.txt")

Expand Down