diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 2ff5ad7..2c895ab 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,5 +1,8 @@ # Changelog +## v0.1.5 +- Implementation of DFS + ## v0.1.4 - Allow DataStructures 0.19 diff --git a/Project.toml b/Project.toml index c966b0e..0d764fd 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Bonobo" uuid = "f7b14807-3d4d-461a-888a-05dd4bca8bc3" authors = ["Ole Kroeger and contributors"] -version = "0.1.4" +version = "0.1.5" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" diff --git a/src/Bonobo.jl b/src/Bonobo.jl index 39a3923..6f1502c 100644 --- a/src/Bonobo.jl +++ b/src/Bonobo.jl @@ -29,12 +29,14 @@ This needs to be added by every `AbstractNode` as `std::BnBNodeInfo` id :: Int lb :: Float64 ub :: Float64 +depth :: Int ``` """ mutable struct BnBNodeInfo id :: Int lb :: Float64 ub :: Float64 + depth :: Int end """ @@ -98,6 +100,8 @@ If there is a tie then the smallest node id is used as a tie breaker. """ struct BestFirstSearch <: AbstractTraverseStrategy end +struct DepthFirstSearch <: AbstractTraverseStrategy end + @deprecate BFS() BestFirstSearch() false """ @@ -272,8 +276,12 @@ function optimize!(tree::BnBTree; callback=(args...; kwargs...)->()) end tree.node_queue[node.id] = (node.lb, node.id) - _ , prio = peek(tree.node_queue) - @assert tree.lb <= prio[1] + _ , prio = first(tree.node_queue) + if tree.lb > prio[1]+1e-6 + @show tree.lb + @show prio[1] + end + @assert tree.lb <= prio[1]+1e-6 tree.lb = prio[1] updated = update_best_solution!(tree, node) @@ -288,16 +296,16 @@ function optimize!(tree::BnBTree; callback=(args...; kwargs...)->()) branch!(tree, node) callback(tree, node) end - sort_solutions!(tree.solutions, tree.sense) + sort_solutions!(tree.solutions) end """ - sort_solutions!(solutions::Vector{<:AbstractSolution}, sense::Symbol) + sort_solutions!(solutions::Vector{<:AbstractSolution}) Sort the solutions vector by objective value such that the best solution is at index 1. """ -function sort_solutions!(solutions::Vector{<:AbstractSolution}, sense::Symbol) - sort!(solutions; rev = sense == :Max, by=s->s.objective) +function sort_solutions!(solutions::Vector{<:AbstractSolution}) + sort!(solutions; by=s->s.objective) end """ diff --git a/src/node.jl b/src/node.jl index ee7ec8d..27e27c2 100644 --- a/src/node.jl +++ b/src/node.jl @@ -53,10 +53,12 @@ For information on that see [`set_root!`](@ref). """ function create_node(Node, node_id::Int, parent::Union{AbstractNode, Nothing}, node_info::NamedTuple) lb = -Inf + depth = 1 if !isnothing(parent) lb = parent.lb + depth = parent.depth + 1 end - bnb_node = structfromnt(BnBNodeInfo, (id = node_id, lb = lb, ub = Inf)) + bnb_node = structfromnt(BnBNodeInfo, (id = node_id, lb = lb, ub = Inf, depth = depth)) bnb_nt = (std = bnb_node,) node_nt = merge(bnb_nt, node_info) return structfromnt(Node, node_nt) @@ -69,7 +71,12 @@ Get the next node of the tree which shall be evaluted next by [`evaluate_node!`] If you want to implement your own traversing strategy check out [`AbstractTraverseStrategy`](@ref). """ function get_next_node(tree::BnBTree, ::BestFirstSearch) - node_id, _ = peek(tree.node_queue) + node_id, _ = first(tree.node_queue) + return tree.nodes[node_id] +end + +function get_next_node(tree::BnBTree, ::DepthFirstSearch) + node_id = argmax(k -> tree.nodes[k].depth, keys(tree.nodes)) return tree.nodes[node_id] end diff --git a/src/util.jl b/src/util.jl index 1961fe8..0a15813 100644 --- a/src/util.jl +++ b/src/util.jl @@ -6,6 +6,7 @@ :id, :lb, :ub, + :depth ) Core.getproperty(Core.getproperty(c, :std), s) else @@ -18,6 +19,7 @@ end :id, :lb, :ub, + :depth ) Core.setproperty!(c.std, s, v) else diff --git a/test/end2end/mip.jl b/test/end2end/mip.jl index c1a7c26..7f5ee52 100644 --- a/test/end2end/mip.jl +++ b/test/end2end/mip.jl @@ -115,6 +115,35 @@ end @test BB.get_objective_value(bnb_model) ≈ 5.2 end +@testset "MIP Problem with 3 variables DFS" begin + m = Model(HiGHS.Optimizer) + set_optimizer_attribute(m, "log_to_console", false) + @variable(m, x[1:3] >= 0) + @constraint(m, 0.5x[1]+3.1x[2]+4.2x[3] <= 6.1) + @constraint(m, 1.9x[1]+0.7x[2]+0.2x[3] <= 8.1) + @constraint(m, 2.9x[1]-2.3x[2]+4.2x[3] <= 10.5) + @objective(m, Max, x[1]+1.2x[2]+3.2x[3]) + + bnb_model = BB.initialize(; + traverse_strategy = BB.DepthFirstSearch(), + Node = MIPNode, + root = m, + sense = objective_sense(m) == MOI.MAX_SENSE ? :Max : :Min + ) + BB.set_root!(bnb_model, ( + lbs = zeros(length(x)), + ubs = fill(Inf, length(x)), + status = MOI.OPTIMIZE_NOT_CALLED + )) + + BB.optimize!(bnb_model) + + sol_x = convert.(Int, BB.get_solution(bnb_model)) + + @test sol_x == [2,0,1] + @test BB.get_objective_value(bnb_model) ≈ 5.2 +end + @testset "MIP Problem with 3 variables minimize" begin m = Model(HiGHS.Optimizer) set_optimizer_attribute(m, "log_to_console", false)