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

Toy demo in unit tests for TreeSearch interface #668

Merged
merged 2 commits into from
Jul 19, 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
6 changes: 5 additions & 1 deletion src/Algorithm/Algorithm.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module Algorithm

import DataStructures
using DataStructures
import MathOptInterface
import TimerOutputs

Expand All @@ -17,6 +17,10 @@ import Base: push!
# Utilities to build algorithms
include("utilities/optimizationstate.jl")

# Tree search interface
include("treesearch/interface.jl")
include("treesearch/explore.jl")

include("data.jl")
include("formstorages.jl")

Expand Down
47 changes: 23 additions & 24 deletions src/Algorithm/interface.jl
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
"""
About algorithms
----------------

An algorithm is a procedure with a known interface (input and output) applied to a data.
An algorithm can use storage units inside the data to keep its computed data between different
runs of the algorithm or between runs of different algorithms.
The algorithm itself contains only its parameters.

Parameters of an algorithm may contain its child algorithms which used by it. Therefore,
the algoirthm tree is formed, in which the root is the algorithm called to solver the model
(root algorithm should be an optimization algorithm, see below).

Algorithms are divided into two types : "manager algorithms" and "worker algorithms".
Worker algorithms just continue the calculation. They do not store and restore units
as they suppose it is done by their master algorithms. Manager algorithms may divide
the calculation flow into parts. Therefore, they store and restore units to make sure
that their child worker algorithms have units prepared.
A worker algorithm cannot have child manager algorithms.

Examples of manager algorithms : TreeSearchAlgorithm (which covers both BCP algorithm and
diving algorithm), conquer algorithms, strong branching, branching rule algorithms
(which create child nodes). Examples of worker algorithms : column generation, SolveIpForm,
SolveLpForm, cut separation, pricing algorithms, etc.

About algorithms
----------------

An algorithm is a procedure with a known interface (input and output) applied to a data.
An algorithm can use storage units inside the data to keep its computed data between different
runs of the algorithm or between runs of different algorithms.
The algorithm itself contains only its parameters.

Parameters of an algorithm may contain its child algorithms which used by it. Therefore,
the algoirthm tree is formed, in which the root is the algorithm called to solver the model
(root algorithm should be an optimization algorithm, see below).

Algorithms are divided into two types : "manager algorithms" and "worker algorithms".
Worker algorithms just continue the calculation. They do not store and restore units
as they suppose it is done by their master algorithms. Manager algorithms may divide
the calculation flow into parts. Therefore, they store and restore units to make sure
that their child worker algorithms have units prepared.
A worker algorithm cannot have child manager algorithms.

Examples of manager algorithms : TreeSearchAlgorithm (which covers both BCP algorithm and
diving algorithm), conquer algorithms, strong branching, branching rule algorithms
(which create child nodes). Examples of worker algorithms : column generation, SolveIpForm,
SolveLpForm, cut separation, pricing algorithms, etc.
"""

"""
Expand Down
33 changes: 33 additions & 0 deletions src/Algorithm/treesearch/explore.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
struct DepthFirstExploreStrategy <: AbstractExploreStrategy end

struct BreadthFirstSearch <: AbstractExploreStrategy end

function tree_search(strategy::DepthFirstExploreStrategy, space::AbstractSearchSpace)
tracker = new_tracker(space, strategy)
root_node = new_root(space, tracker)
stack = Stack{typeof(root_node)}()
push!(stack, root_node)
while !isempty(stack) # and stopping criterion
current = pop!(stack)
# conquer
# register solution in manager.
for child in new_children(strategy, current, space, tracker)
push!(stack, child)
end
end
end

function tree_search(strategy::BreadthFirstSearch, space::AbstractSearchSpace)
tracker = new_tracker(space, strategy)
root_node = new_root(space, tracker)
pq = PriorityQueue{typeof(root_node), Float64}()
enqueue!(pq, root_node, cost(strategy, root_node))
while !isempty(pq) # and stopping criterion
current = dequeue!(pq)
# conquer
# register solution in manager
for child in new_children(strategy, current, space, tracker)
enqueue!(pq, child, cost(strategy, child))
end
end
end
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
module TreeSearch

# The definition of a tree search algorithm is based on four concepts.

"Definition of the problem tackled by the tree seach algorithm."
Expand All @@ -22,7 +20,7 @@ abstract type AbstractTracker end

# Interface to implement
"Creates and returns the root node of a search space."
new_root(::AbstractExploreStrategy, space, tracker) = nothing
new_root(::AbstractSearchSpace, ::AbstractTracker) = nothing

"Creates and returns the children of a node associated to a search space."
new_children(::AbstractExploreStrategy, node, space, tracker) = nothing
Expand All @@ -45,6 +43,8 @@ delete_node(::AbstractNode, ::AbstractTracker) = nothing
"Returns the manager which is responsible for handling the kpis and the best know solution."
manager(::AbstractSearchSpace) = nothing

cost(::AbstractExploreStrategy, ::AbstractNode) = nothing

# Composition pattern
"Returns the inner space of search space; nothing if no composition."
inner_space(::AbstractSearchSpace) = nothing
Expand All @@ -55,16 +55,16 @@ inner_space(::AbstractSearchSpace) = nothing
"A data structure that wraps a piece of information to track in the tree."
abstract type AbstractTrackedData end

new_tracker(::AbstractExploreStrategy, ::AbstractSearchSpace) = nothing

"Save a piece of information in the tracker for a given node."
save!(::AbstractTracker, ::AbstractNode, ::AbstractTrackedData) = nothing

"Returns a piece of information from the tracker for a given node."
get(::AbstractTracker, ::AbstractNode, ::Type{AbstractTrackedData}) = nothing
Base.get(::AbstractTracker, ::AbstractNode, ::Type{AbstractTrackedData}) = nothing

"""
Computes data to activate / deactivate information in order to restore the searchspace
to move from a node to another node.
"""
diff(::AbstractTracker, src::AbstractNode, dest::AbstractNode, ::Type{AbstractTrackedData}) = nothing

end
91 changes: 91 additions & 0 deletions test/unit/Algorithm/treesearch_interface.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Ati = Algorithm/treesearch/interface.jl

# In this test, we consider a minimization problem with three binary variables.
# Costs of the variable are [-1, 1, -2].
# The problem has no additional constraints. Therefore, the optimal solution is [1, 0, 1].

# Branching strategy:
# if no branching constraint, the value of each variable is 0.5.
# depth 0: branch on first variable
# depth 1: branch on second variable
# depth 2: branch on third variable

const NB_VARIABLES_ATI1 = 3

mutable struct TrackerAti1 <: ClA.AbstractTracker
node_counter::Int
node_to_var_ubs::Dict{Int, Vector{Int}}
node_to_var_lbs::Dict{Int, Vector{Int}}

function TrackerAti1()
node_to_var_ubs = Dict{Int, Vector{Int}}()
node_to_var_lbs = Dict{Int, Vector{Int}}()
return new(0, node_to_var_ubs, node_to_var_lbs)
end
end

struct SearchSpaceAti1 <: ClA.AbstractSearchSpace
var_domains::Vector{Tuple{Int,Int}}
SearchSpaceAti1() = new(fill((0,1), NB_VARIABLES_ATI1))
end

struct NodeAti1 <: ClA.AbstractNode
uid::Int
depth::Int
fixed_var_index::Union{Nothing,Int}
fixed_var_value::Union{Nothing,Float64}
solution::Vector{Float64}
children::Vector{NodeAti1}
function NodeAti1(
tracker::TrackerAti1,
parent::Union{Nothing, NodeAti1} = nothing,
var_index::Union{Nothing,Int} = nothing,
var_value::Union{Nothing,Real} = 0
)
@assert isnothing(var_index) || 1 <= var_index <= NB_VARIABLES_ATI1
depth = isnothing(parent) ? 0 : parent.depth + 1
solution = if isnothing(parent)
fill(0.5, NB_VARIABLES_ATI1)
else
sol = copy(parent.solution)
sol[var_index] = var_value
sol
end
return new(tracker.node_counter += 1, depth, var_index, var_value, solution, NodeAti1[])
end
end

ClA.new_root(::SearchSpaceAti1, tracker::TrackerAti1) = NodeAti1(tracker)

ClA.new_tracker(::SearchSpaceAti1, ::ClA.AbstractExploreStrategy) = TrackerAti1()

function ClA.new_children(::ClA.AbstractExploreStrategy, node, space, tracker)
var_index = node.depth + 1
if var_index > NB_VARIABLES_ATI1
return NodeAti1[]
end
child1 = NodeAti1(tracker, node, var_index, 0.0)
child2 = NodeAti1(tracker, node, var_index, 1.0)
push!(node.children, child1, child2)
return [child1, child2]
end

ClA.root(node::NodeAti1) = isnothing(node.parent) ? node : ClA.root(node.parent)
ClA.parent(node::NodeAti1) = node.parent
ClA.children(node::NodeAti1) = node.children

ClA.cost(::ClA.BreadthFirstSearch, node::NodeAti1) = -node.depth

# TODO
ClA.delete_node(node::NodeAti1, tracker::TrackerAti1) = nothing
ClA.manager(space::SearchSpaceAti1) = nothing
ClA.inner_space(space::SearchSpaceAti1) = nothing

@testset "Algorithm - treesearch interface" begin
space = SearchSpaceAti1()
@show space
ClA.tree_search(ClA.DepthFirstExploreStrategy(), space)

ClA.tree_search(ClA.BreadthFirstSearch(), space)
exit()
end