diff --git a/Project.toml b/Project.toml index 63f100f..4090b50 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ParallelUtilities" uuid = "fad6cfc8-4f83-11e9-06cc-151124046ad0" authors = ["Jishnu Bhattacharya "] -version = "0.7.1" +version = "0.7.2" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" diff --git a/src/errors.jl b/src/errors.jl index 1fc03d6..986cac6 100644 --- a/src/errors.jl +++ b/src/errors.jl @@ -3,7 +3,7 @@ struct ProcessorNumberError <: Exception np :: Int end function Base.showerror(io::IO,err::ProcessorNumberError) - print(io,"processor id $(err.p) does not line in the range $(1:err.np)") + print(io,"processor id $(err.p) does not lie in the range $(1:err.np)") end struct DecreasingIteratorError <: Exception diff --git a/src/mapreduce.jl b/src/mapreduce.jl index 13633b5..80074de 100644 --- a/src/mapreduce.jl +++ b/src/mapreduce.jl @@ -90,20 +90,33 @@ function reducedvalue(freduce::Function,rank, N = nchildren(pipe) leftchild = N > 0 - vals = Vector{Tred}(undef,N+1) + selfvalpresent = rank > 0 + vals = Vector{Tred}(undef,N + selfvalpresent) @sync begin @async begin - selfval = take!(pipe.selfchannels.out)::Tmap - selfvalred = freduce((value(selfval),)) - ind = 1 + leftchild - v = pval(rank,selfvalred) - vals[ind] = v + if selfvalpresent + selfval = take!(pipe.selfchannels.out)::Tmap + selfvalred = freduce((value(selfval),)) + pv = pval(rank,selfvalred) + ind = selfvalpresent + leftchild + vals[ind] = pv + end end - @async for i=2:N+1 - pv = take!(pipe.childrenchannels.out) :: Tred - shift = pv.rank > rank ? 1 : -1 - ind = shift + leftchild + 1 - vals[ind] = pv + @async begin + if selfvalpresent + for i=1:N + pv = take!(pipe.childrenchannels.out) :: Tred + shift = pv.rank > rank ? 1 : -1 + ind = shift + leftchild + 1 + vals[ind] = pv + end + else + for i=1:N + pv = take!(pipe.childrenchannels.out) :: Tred + vals[i] = pv + end + sort!(vals,by=pv->pv.rank) + end end end @@ -125,7 +138,7 @@ function reduceTreeNode(freduce::Function,rank,pipe::BranchChannel{Tmap,Tred}, anyerr = take!(pipe.selfchannels.err) else anyerr = false - end + end anyerr = anyerr || any(take!(pipe.childrenchannels.err) for i=1:nchildren(pipe)) @@ -542,7 +555,7 @@ function pmapreduce(fmap::Function,Tmap::Type,freduce::Function,Tred::Type, iterators::Tuple,args...;kwargs...) tree,branches = createbranchchannels(pval{Tmap},pval{Tred}, - iterators,OrderedBinaryTree) + iterators, SegmentedOrderedBinaryTree) pmapreduceworkers(fmap,freduce,iterators,tree, branches,Sorted(),args...;kwargs...) end diff --git a/src/trees.jl b/src/trees.jl index b89c233..4ee703b 100644 --- a/src/trees.jl +++ b/src/trees.jl @@ -4,7 +4,7 @@ const RemoteChannelContainer{T} = NamedTuple{(:out, :err),Tuple{RemoteChannel{Ch function RemoteChannelContainer{T}(n::Int,p::Int) where {T} out = RemoteChannel(()->Channel{T}(n),p) - err = RemoteChannel(()->Channel{Bool}(n),p) + err = RemoteChannel(()->Channel{Bool}(n),p) RemoteChannelContainer{T}((out,err)) end RemoteChannelContainer{T}(n::Int) where {T} = RemoteChannelContainer{T}(n,myid()) @@ -14,53 +14,6 @@ RemoteChannelContainer(n::Int) = RemoteChannelContainer{Any}(n,myid()) abstract type Tree end abstract type BinaryTree <: Tree end -function leavesateachlevelfulltree(Nleaves) - Nnodes = 2Nleaves-1 - Nlevels = floor(Int,log2(Nnodes)) + 1 - Nleaves_lowestlevel = Nnodes - ((1 << (Nlevels - 1)) - 1) - - return Nleaves_lowestlevel, Nnodes, Nlevels -end - -function leafrankfoldedtree(Nleaves,leafno) - - @assert(leafno <= Nleaves,"leafno needs to be ⩽ Nleaves") - - Nleaves_lowestlevel, Nnodes, Nlevels = - leavesateachlevelfulltree(Nleaves) - - if leafno <= Nleaves_lowestlevel - leafrank = (1 << (Nlevels - 1)) - 1 + leafno - else - leafrank = Nnodes - Nleaves + leafno - Nleaves_lowestlevel - end - - return leafrank -end - -function foldedbinarytreefromleaves(T,leaves::AbstractVector{<:Integer}) - - Nleaves = length(leaves) - Nleaves_lowestlevel,Nnodes = leavesateachlevelfulltree(Nleaves) - - treeprocs = Vector{Int}(undef,Nnodes) - - # fill in the leaves - @views treeprocs[end - Nleaves_lowestlevel + 1:end] .= - leaves[1:Nleaves_lowestlevel] - @views treeprocs[end - Nleaves + 1:end - Nleaves_lowestlevel] .= - leaves[Nleaves_lowestlevel+1:end] - - # fill in the parent nodes - for rank in Nnodes-1:-2:2 - p = treeprocs[rank] - parentrank = rank >> 1 - treeprocs[parentrank] = p - end - - T(treeprocs) -end - struct SequentialBinaryTree{T<:AbstractVector{<:Integer}} <: BinaryTree #= Tree of the form 1 @@ -83,7 +36,7 @@ struct SequentialBinaryTree{T<:AbstractVector{<:Integer}} <: BinaryTree Ninternalnodes = (1 << h) - 1 Nleaf = N - Ninternalnodes Nonechildinternalnodes = (Ninternalnodes > 0) ? rem(Nleaf,2) : 0 - twochildendind = (N-1) >> 1 + twochildendind = div(N-1, 2) onechildstartind = twochildendind + 1 onechildendind = onechildstartind + Nonechildinternalnodes - 1 @@ -116,8 +69,10 @@ struct OrderedBinaryTree{T<:AbstractVector{<:Integer}} <: BinaryTree end end +abstract type SegmentedBinaryTree <: BinaryTree end + struct SegmentedSequentialBinaryTree{T<:AbstractVector{<:Integer}, - D<:AbstractDict} <: BinaryTree + D<:AbstractDict} <: SegmentedBinaryTree #= Each node on the cluster will have its own tree that carries out a local reduction. There will be one master node on the cluster that @@ -132,6 +87,107 @@ struct SegmentedSequentialBinaryTree{T<:AbstractVector{<:Integer}, nodetreestartindices :: Vector{Int} end +struct SegmentedOrderedBinaryTree{T<:AbstractVector{<:Integer}, + D<:AbstractDict} <: SegmentedBinaryTree + #= + Each node on the cluster will have its own tree that carries out + a local reduction. There will be one master node on the cluster that + will acquire the reduced value on each node. This will be followed + by a tree to carry out reduction among the master nodes. The + eventual reduced result will be returned to the calling process. + =# + N :: Int + procs :: T + workersonhosts :: D + toptree :: OrderedBinaryTree{Vector{Int}} + nodetreestartindices :: Vector{Int} +end + +function leavesateachlevelfulltree(::Type{<:SequentialBinaryTree},Nleaves) + Nnodes = 2Nleaves-1 + Nlevels = levels(Nnodes) + Nleaves_lowestlevel = Nnodes - ((1 << (Nlevels - 1)) - 1) + + return Nleaves_lowestlevel, Nnodes, Nlevels +end + +function leafrankfoldedtree(::SequentialBinaryTree,Nleaves,leafno) + + @assert(leafno <= Nleaves,"leafno needs to be ⩽ Nleaves") + + Nleaves_lowestlevel, Nnodes, Nlevels = + leavesateachlevelfulltree(SequentialBinaryTree,Nleaves) + + if leafno <= Nleaves_lowestlevel + leafrank = (1 << (Nlevels - 1)) - 1 + leafno + else + leafrank = Nnodes - Nleaves + leafno - Nleaves_lowestlevel + end + + return leafrank +end + +function leafrankfoldedtree(::OrderedBinaryTree,Nleaves,leafno) + @assert(leafno <= Nleaves,"leafno needs to be ⩽ Nleaves") + leafrank = 2leafno - 1 +end + +function foldedbinarytreefromleaves(::Type{SequentialBinaryTree},leaves) + Nleaves = length(leaves) + Nleaves_lowestlevel,Nnodes = + leavesateachlevelfulltree(SequentialBinaryTree,Nleaves) + + treeprocs = Vector{Int}(undef,Nnodes) + + # fill in the leaves + @views treeprocs[end - Nleaves_lowestlevel + 1:end] .= + leaves[1:Nleaves_lowestlevel] + @views treeprocs[end - Nleaves + 1:end - Nleaves_lowestlevel] .= + leaves[Nleaves_lowestlevel+1:end] + + # fill in the parent nodes + for rank in Nnodes-1:-2:2 + p = treeprocs[rank] + parentrank = div(rank,2) + treeprocs[parentrank] = p + end + + SequentialBinaryTree(treeprocs) +end + +function foldedbinarytreefromleaves(::Type{OrderedBinaryTree},leaves) + Nleaves = length(leaves) + Nnodes = 2Nleaves-1 + + allnodes = Vector{Int}(undef,Nnodes) + foldedbinarytreefromleaves!(OrderedBinaryTree,allnodes,leaves) + + OrderedBinaryTree(allnodes) +end + +function foldedbinarytreefromleaves!(::Type{OrderedBinaryTree},allnodes,leaves) + top = topnoderank(OrderedBinaryTree(1:length(allnodes))) + allnodes[top] = first(leaves) + + length(allnodes) == 1 && return + + Nnodes_left = top - 1 + Nleaves_left = div( Nnodes_left + 1 , 2) + Nleaves_right = length(leaves) - Nleaves_left + + if Nleaves_left > 0 + leaves_left = @view leaves[1:Nleaves_left] + leftnodes = @view allnodes[1:Nnodes_left] + foldedbinarytreefromleaves!(OrderedBinaryTree,leftnodes,leaves_left) + end + + if Nleaves_right > 0 + leaves_right = @view leaves[end - Nleaves_right + 1:end] + rightnodes = @view allnodes[top + 1:end] + foldedbinarytreefromleaves!(OrderedBinaryTree,rightnodes,leaves_right) + end +end + # Tree with a distribution of hosts specified by workersonhosts # workersonhosts is a Dict that maps (host=>workers) function SegmentedSequentialBinaryTree(procs::AbstractVector{<:Integer}, @@ -172,10 +228,61 @@ function SegmentedSequentialBinaryTree(procs::AbstractVector{<:Integer}) SegmentedSequentialBinaryTree(procs,workersonhosts) end +function SegmentedOrderedBinaryTree(procs::AbstractVector{<:Integer}, + workersonhosts::AbstractDict{String,<:AbstractVector{<:Integer}}) + + Np = length(procs) + Np >= 1 || throw(DomainError(Np, + "need at least one node to create a BinaryTree")) + + nodes = collect(keys(workersonhosts)) + masternodes = Vector{Int}(undef,length(nodes)) + for (nodeind,node) in enumerate(nodes) + workersnode = workersonhosts[node] + nodetree = OrderedBinaryTree(workersnode) + masternodes[nodeind] = topnode(nodetree).p + end + Nleaves = length(masternodes) + toptree = foldedbinarytreefromleaves(OrderedBinaryTree,masternodes) + + toptreenonleafnodes = length(toptree) - Nleaves + Nnodestotal = toptreenonleafnodes + length(procs) + + nodetreestartindices = Vector{Int}(undef,length(nodes)) + nodetreestartindices[1] = toptreenonleafnodes + 1 + for (nodeno,node) in enumerate(nodes) + nodeno == 1 && continue + prevnode = nodes[nodeno-1] + nodetreestartindices[nodeno] = nodetreestartindices[nodeno-1] + + length(workersonhosts[prevnode]) + end + + SegmentedOrderedBinaryTree(Nnodestotal,procs,workersonhosts, + toptree,nodetreestartindices) +end + +function SegmentedOrderedBinaryTree(procs::AbstractVector{<:Integer}) + workersonhosts = procs_node(procs) + SegmentedOrderedBinaryTree(procs,workersonhosts) +end + +# for a single host there are no segments +function unsegmentedtree(::Type{<:SegmentedSequentialBinaryTree}) + SequentialBinaryTree +end +function unsegmentedtree(::Type{<:SegmentedOrderedBinaryTree}) + OrderedBinaryTree +end +function unsegmentedtree(tree::SegmentedBinaryTree) + T = unsegmentedtree(typeof(tree)) + T(tree.procs) +end + @inline Base.length(tree::BinaryTree) = tree.N function levels(tree::Union{SequentialBinaryTree,OrderedBinaryTree}) - floor(Int,log2(length(tree))) + 1 + levels(length(tree)) end +levels(n::Integer) = floor(Int,log2(n)) + 1 Base.summary(io::IO,b::Tree) = print(io,length(b),"-node ",typeof(b)) @@ -242,10 +349,10 @@ function parentnoderank(tree::SequentialBinaryTree,i::Integer) # only one node i == 1 && return 1 - i >> 1 # div(i,2) + div(i,2) end -function subtree_rank(tree::SegmentedSequentialBinaryTree,i::Integer) +function subtree_rank(tree::SegmentedBinaryTree,i::Integer) Nmasternodes = length(keys(tree.workersonhosts)) toptreenonleafnodes = length(tree.toptree) - Nmasternodes @@ -260,17 +367,21 @@ function subtree_rank(tree::SegmentedSequentialBinaryTree,i::Integer) np = length(procs) if subnodeno <= nptotalprevhosts + np rankinsubtree = subnodeno - nptotalprevhosts - subtree = SequentialBinaryTree(tree.workersonhosts[host]) + T = unsegmentedtree(typeof(tree)) + subtree = T(tree.workersonhosts[host]) return subtree,rankinsubtree,nptotalprevhosts end nptotalprevhosts += np end end -function masternodeindex(tree, p) +function masternodeindex(tree::SegmentedBinaryTree, p) leafno = 0 + T = unsegmentedtree(typeof(tree)) for (ind,w) in enumerate(values(tree.workersonhosts)) - if w[1] == p + subtree = T(w) + top = topnoderank(subtree) + if w[top] == p leafno = ind break end @@ -278,32 +389,53 @@ function masternodeindex(tree, p) return leafno end -function parentnoderank(tree::SegmentedSequentialBinaryTree,i::Integer) +toptree_to_fulltree_index(::SequentialBinaryTree, i) = i +toptree_to_fulltree_index(::OrderedBinaryTree, i) = div(i,2) + +fulltree_to_toptree_index(::SequentialBinaryTree, i) = i +fulltree_to_toptree_index(::OrderedBinaryTree, i) = 2i + +function parentnoderank(tree::SegmentedBinaryTree,i::Integer) 1 <= i <= length(tree) || throw(BoundsError(tree,i)) Nmasternodes = length(keys(tree.workersonhosts)) toptreenonleafnodes = length(tree.toptree) - Nmasternodes if toptreenonleafnodes == 0 - # equivalent to a SequentialBinaryTree - SBT = SequentialBinaryTree(tree.procs) - pr = parentnoderank(SBT,i) + pr = parentnoderank(unsegmentedtree(tree),i) elseif i <= toptreenonleafnodes - p = tree.toptree.procs[i] - pr = parentnoderank(tree.toptree,i) + #= In a SegmentedSequentialBinaryTree the leading indices + are the parent nodes of the top tree, so ind = i + In a SegmentedOrderedBinaryTree, the leaves are removed + from the top tree, so only even numbers are left. + In this case, index i of the full tree refers to index 2i of the + top tree, so ind = 2i + =# + ind = fulltree_to_toptree_index(tree.toptree,i) + p = tree.toptree[ind].p + #= Compute the parent of the node with rank ind on the top tree. + In a SegmentedSequentialBinaryTree this is what we want. + In a SegmentedOrderedBinaryTree, we need to convert this back to + the index of the full tree, that is div(pr,2) + =# + pr_top = parentnoderank(tree.toptree,ind) + pr = toptree_to_fulltree_index(tree.toptree, pr_top) else subtree,rankinsubtree,nptotalprevhosts = subtree_rank(tree,i) - if rankinsubtree == 1 + if rankinsubtree == topnoderank(subtree) # masternode # parent will be on the top-tree p = subtree[rankinsubtree].p leafno = masternodeindex(tree,p) Nmasternodes = length(keys(tree.workersonhosts)) - leafrank = leafrankfoldedtree(Nmasternodes,leafno) - pr = parentnoderank(tree.toptree,leafrank) + leafrank = leafrankfoldedtree(tree.toptree, Nmasternodes,leafno) + pr_top = parentnoderank(tree.toptree, leafrank) + # Convert back to the rank on the full tree where the + # leaves of the top tree aren't stored. + pr = toptree_to_fulltree_index(tree.toptree, pr_top) else # node on a sub-tree pr = parentnoderank(subtree,rankinsubtree) @@ -336,19 +468,20 @@ function nchildren(tree::SequentialBinaryTree,i::Integer) 0 end end -function nchildren(tree::SegmentedSequentialBinaryTree,i::Integer) +function nchildren(tree::SegmentedBinaryTree,i::Integer) 1 <= i <= length(tree) || throw(BoundsError(tree,i)) Nmasternodes = length(keys(tree.workersonhosts)) toptreenonleafnodes = length(tree.toptree) - Nmasternodes if toptreenonleafnodes == 0 - SBT = SequentialBinaryTree(tree.procs) - n = nchildren(SBT,i) + n = nchildren(unsegmentedtree(tree),i) elseif i <= toptreenonleafnodes - n = nchildren(tree.toptree,i) - + # The top-tree is a full binary tree. + # Since the leaves aren't stored, every parent node + # has 2 children + n = 2 else subtree,rankinsubtree = subtree_rank(tree,i) n = nchildren(subtree,rankinsubtree) @@ -359,8 +492,19 @@ end topnoderank(::BinaryTree) = 1 function topnoderank(tree::OrderedBinaryTree) - levels = floor(Int,log2(length(tree))) - 1 << levels # 2^levels + 1 << (levels(tree) - 1) +end +function topnoderank(tree::SegmentedOrderedBinaryTree) + Nmasternodes = length(keys(tree.workersonhosts)) + toptreenonleafnodes = length(tree.toptree) - Nmasternodes + + if toptreenonleafnodes > 0 + tnr_top = topnoderank(tree.toptree) + tnr = toptree_to_fulltree_index(tree.toptree, tnr_top) + else + tnr = topnoderank(OrderedBinaryTree(tree.procs)) + end + return tnr end topnode(tree::Tree) = tree[topnoderank(tree)] @@ -380,9 +524,15 @@ struct BinaryTreeNode end end +function Base.show(io::IO,b::BinaryTreeNode) + print(io, + "BinaryTreeNode(p = $(b.p),"* + " parent = $(b.parent), nchildren = $(b.nchildren))") +end + @inline nchildren(b::BinaryTreeNode) = b.nchildren -function Base.getindex(tree::BinaryTree,i::Integer) +function Base.getindex(tree::Tree,i::Integer) 1 <= i <= length(tree) || throw(BoundsError(tree,i)) procs = tree.procs @@ -395,40 +545,47 @@ function Base.getindex(tree::BinaryTree,i::Integer) BinaryTreeNode(p,p_parent,n) end -function Base.getindex(tree::SegmentedSequentialBinaryTree,i::Integer) +function Base.getindex(tree::SegmentedBinaryTree,i::Integer) 1 <= i <= length(tree) || throw(BoundsError(tree,i)) Nmasternodes = length(keys(tree.workersonhosts)) toptreenonleafnodes = length(tree.toptree) - Nmasternodes if toptreenonleafnodes == 0 - # equivalent to a SequentialBinaryTree - SBT = SequentialBinaryTree(tree.procs) - return SBT[i] + return unsegmentedtree(tree)[i] + elseif i <= toptreenonleafnodes - p = tree.toptree.procs[i] - pr = parentnoderank(tree.toptree,i) - p_parent = tree.toptree.procs[pr] - n = nchildren(tree.toptree,i) + #= In a SegmentedSequentialBinaryTree the leading indices + are the parent nodes of the top tree, so ind = i + In a SegmentedOrderedBinaryTree, the leaves are removed + from the top tree, so only even numbers are left. + In this case, index i of the full tree refers to index 2i of the + top tree, so ind = 2i + =# + ind = fulltree_to_toptree_index(tree.toptree,i) + p = tree.toptree[ind].p + pr_top = parentnoderank(tree.toptree,ind) + p_parent = tree.toptree[pr_top].p + n = 2 return BinaryTreeNode(p,p_parent,n) else subtree,rankinsubtree = subtree_rank(tree,i) - p = subtree.procs[rankinsubtree] + p = subtree[rankinsubtree].p n = nchildren(subtree,rankinsubtree) - if rankinsubtree == 1 + if rankinsubtree == topnoderank(subtree) # masternode # parent will be on the top tree Nmasternodes = length(keys(tree.workersonhosts)) leafno = masternodeindex(tree,p) - leafrank = leafrankfoldedtree(Nmasternodes,leafno) - pr = parentnoderank(tree.toptree,leafrank) - p_parent = tree.toptree.procs[pr] + leafrank = leafrankfoldedtree(tree.toptree, Nmasternodes,leafno) + pr_top = parentnoderank(tree.toptree, leafrank) + p_parent = tree.toptree[pr_top].p else # node on a sub-tree pr = parentnoderank(subtree,rankinsubtree) - p_parent = subtree.procs[pr] + p_parent = subtree[pr].p end return BinaryTreeNode(p,p_parent,n) end @@ -457,26 +614,42 @@ struct BranchChannel{Tmap,Tred} end @inline nchildren(b::BranchChannel) = b.nchildren -function BranchChannel(p::Int,::Type{Tmap}, +function BranchChannel(p::Integer,Tmap, parentchannels::RemoteChannelContainer{Tred}, - nchildren::Int) where {Tmap,Tred} + nchildren::Int) where {Tred} (0 <= nchildren <= 2) || throw(DomainError(nchildren, "attempt to construct a binary tree with $nchildren children")) - selfchannels = RemoteChannelContainer{Tmap}(1,p) - childrenchannels = RemoteChannelContainer{Tred}(nchildren,p) + Texp = Tuple{RemoteChannelContainer{Tmap}, + RemoteChannelContainer{Tred}} + + selfchannels, childrenchannels = @sync begin + selftask = @async RemoteChannelContainer{Tmap}(1,p) + childtask = @async RemoteChannelContainer{Tred}(nchildren,p) + fetch.((selftask,childtask)) :: Texp + end BranchChannel(p,selfchannels,parentchannels,childrenchannels,nchildren) end -function BranchChannel{Tmap,Tred}(p::Int,nchildren::Int) where {Tmap,Tred} +function BranchChannel{Tmap,Tred}(p::Integer,nchildren::Integer) where {Tmap,Tred} (0 <= nchildren <= 2) || throw(DomainError(nchildren, "attempt to construct a binary tree with $nchildren children")) - parentchannels = RemoteChannelContainer{Tred}(1,p) - BranchChannel(p,Tmap,parentchannels,nchildren) + Texp = Tuple{RemoteChannelContainer{Tred}, + RemoteChannelContainer{Tmap}, + RemoteChannelContainer{Tred}} + + parentchannels, selfchannels, childrenchannels = + @sync begin + parenttask = @async RemoteChannelContainer{Tred}(1,p) + selftask = @async RemoteChannelContainer{Tmap}(1,p) + childtask = @async RemoteChannelContainer{Tred}(nchildren,p) + fetch.((parenttask,selftask,childtask)) :: Texp + end + BranchChannel(p,selfchannels,parentchannels,childrenchannels,nchildren) end function Base.show(io::IO, b::BranchChannel) @@ -513,8 +686,8 @@ function Base.finalize(bc::BranchChannel) finalize_except_wherewhence(bc.parentchannels) end -function createbranchchannels!(branches,::Type{Tmap},::Type{Tred},tree::OrderedBinaryTree, - superbranch::BranchChannel) where {Tmap,Tred} +function createbranchchannels!(branches,Tmap,Tred,tree::OrderedBinaryTree, + superbranch::BranchChannel) top = topnoderank(tree) topnode = tree[top] @@ -527,16 +700,22 @@ function createbranchchannels!(branches,::Type{Tmap},::Type{Tred},tree::OrderedB length(tree) == 1 && return left_inds = 1:top-1 - left_child = OrderedBinaryTree(@view tree.procs[left_inds]) - createbranchchannels!(@view(branches[left_inds]),Tmap,Tred,left_child,topbranchchannels) + right_inds = top+1:length(tree) - if top < length(tree) - right_inds = top+1:length(tree) - right_child = OrderedBinaryTree(@view tree.procs[right_inds]) - createbranchchannels!(@view(branches[right_inds]),Tmap,Tred,right_child,topbranchchannels) + @sync begin + @async if !isempty(left_inds) + left_child = OrderedBinaryTree(@view tree.procs[left_inds]) + createbranchchannels!(@view(branches[left_inds]), + Tmap,Tred,left_child,topbranchchannels) + end + @async if !isempty(right_inds) + right_child = OrderedBinaryTree(@view tree.procs[right_inds]) + createbranchchannels!(@view(branches[right_inds]),Tmap,Tred,right_child,topbranchchannels) + end end + nothing end -function createbranchchannels(::Type{Tmap},::Type{Tred},tree::OrderedBinaryTree) where {Tmap,Tred} +function createbranchchannels(Tmap,Tred,tree::OrderedBinaryTree) branches = Vector{BranchChannel{Tmap,Tred}}(undef,length(tree)) @@ -551,32 +730,55 @@ function createbranchchannels(::Type{Tmap},::Type{Tred},tree::OrderedBinaryTree) length(tree) == 1 && return branches - left_child = OrderedBinaryTree(@view tree.procs[1:top-1]) - createbranchchannels!(@view(branches[1:top-1]),Tmap,Tred,left_child,topmostbranch) - - if top < length(tree) - right_child = OrderedBinaryTree(@view tree.procs[top+1:end]) - createbranchchannels!(@view(branches[top+1:end]),Tmap,Tred,right_child,topmostbranch) + left_inds = 1:top-1 + right_inds = top+1:length(tree) + + @sync begin + @async if !isempty(left_inds) + left_child = OrderedBinaryTree(@view tree.procs[left_inds]) + createbranchchannels!(@view(branches[left_inds]), + Tmap,Tred,left_child,topmostbranch) + end + @async if !isempty(right_inds) + right_child = OrderedBinaryTree(@view tree.procs[right_inds]) + createbranchchannels!(@view(branches[right_inds]), + Tmap,Tred,right_child,topmostbranch) + end end return branches end -function createbranchchannels!(branches,::Type{Tmap}, - ::Type{Tred},tree::SequentialBinaryTree, - finalnoderank = length(tree)) where {Tmap,Tred} + +function createbranchchannels!(branches,Tmap,Tred,tree::SequentialBinaryTree, + finalnoderank = length(tree)) length(branches) < 2 && return - for noderank = 2:finalnoderank - node = tree[noderank] - p = node.p - parentnodebranches = branches[parentnoderank(tree,noderank)] - parentchannels = parentnodebranches.childrenchannels - b = BranchChannel(p,Tmap,parentchannels,nchildren(node)) - branches[noderank] = b + # make sure that the parent nodes are populated + parentfilled = [Base.Event() for i=1:tree.onechildendind] + + @sync for noderank in 2:finalnoderank + @async begin + node = tree[noderank] + p = node.p + pnr = parentnoderank(tree,noderank) + # The first node is filled, no need to wait for it + if pnr > 1 + # Wait otherwise for the parent to get filled + wait(parentfilled[pnr]) + end + parentnodebranches = branches[pnr] + parentchannels = parentnodebranches.childrenchannels + b = BranchChannel(p,Tmap,parentchannels,nchildren(node)) + branches[noderank] = b + # If this is a parent node then notify that it's filled + if noderank <= tree.onechildendind + notify(parentfilled[noderank]) + end + end end end -function createbranchchannels(::Type{Tmap},::Type{Tred},tree::SequentialBinaryTree) where {Tmap,Tred} +function createbranchchannels(Tmap,Tred,tree::SequentialBinaryTree) branches = Vector{BranchChannel{Tmap,Tred}}(undef,length(tree)) @@ -593,11 +795,12 @@ function createbranchchannels(::Type{Tmap},::Type{Tred},tree::SequentialBinaryTr return branches end -function createbranchchannels(::Type{Tmap},::Type{Tred},tree::SegmentedSequentialBinaryTree) where {Tmap,Tred} - nodes = collect(keys(tree.workersonhosts)) +function createbranchchannels(Tmap,Tred,tree::SegmentedSequentialBinaryTree) + + nodes = keys(tree.workersonhosts) toptree = tree.toptree - Nmasternodes = length(keys(tree.workersonhosts)) + Nmasternodes = length(nodes) toptreenonleafnodes = length(toptree) - Nmasternodes branches = Vector{BranchChannel{Tmap,Tred}}(undef,length(tree)) @@ -606,61 +809,164 @@ function createbranchchannels(::Type{Tmap},::Type{Tred},tree::SegmentedSequentia # This is only run if there are multiple hosts if toptreenonleafnodes > 0 top = topnoderank(toptree) - topbranch = toptree[top] - N = nchildren(topbranch) - p = topbranch.p - topmostbranch = BranchChannel{Tmap,Tred}(p,N) + topnode_toptree = toptree[top] + N = nchildren(topnode_toptree) + topmostbranch = BranchChannel{Tmap,Tred}(topnode_toptree.p,N) branches[top] = topmostbranch createbranchchannels!(branches,Tmap,Tred,toptree, toptreenonleafnodes) end - for (nodeno,node) in enumerate(nodes) - # Top node for each subtree (a masternode) - workersnode = tree.workersonhosts[node] - nodetree = SequentialBinaryTree(workersnode) - topbranch = nodetree[topnoderank(nodetree)] - p = topbranch.p - - if toptreenonleafnodes > 0 - # inherit from the parent node - leafno = masternodeindex(tree,p) - leafrank = leafrankfoldedtree(Nmasternodes,leafno) - parentrank = parentnoderank(toptree,leafrank) - parentnodebranches = branches[parentrank] - parentchannels = parentnodebranches.childrenchannels - else - # This happens if there is only one host, - # in which case there's nothing to inherit. - # In this case there's no difference between a - # SegmentedSequentialBinaryTree and a SequentialBinaryTree - # The top node is created separately as it is its own parent - parentchannels = RemoteChannelContainer{Tred}(1,p) + @sync for (nodeno,node) in enumerate(nodes) + @async begin + # Top node for each subtree (a masternode) + workersnode = tree.workersonhosts[node] + nodetree = SequentialBinaryTree(workersnode) + topnode_nodetree = topnode(nodetree) + p = topnode_nodetree.p + + if toptreenonleafnodes > 0 + # inherit from the parent node + leafno = masternodeindex(tree,p) + leafrank = leafrankfoldedtree(tree.toptree, Nmasternodes,leafno) + parentrank = parentnoderank(toptree,leafrank) + parentnodebranches = branches[parentrank] + parentchannels = parentnodebranches.childrenchannels + else + # This happens if there is only one host, + # in which case there's nothing to inherit. + # In this case there's no difference between a + # SegmentedSequentialBinaryTree and a SequentialBinaryTree + # The top node is created separately as it is its own parent + parentchannels = RemoteChannelContainer{Tred}(1,p) + end + + b = BranchChannel(p,Tmap,parentchannels,nchildren(topnode_nodetree)) + nodetreestartindex = tree.nodetreestartindices[nodeno] + branches[nodetreestartindex] = b + + # Populate the rest of the tree + subtreeinds = StepRangeLen(nodetreestartindex,1,length(nodetree)) + branchesnode = @view branches[subtreeinds] + + createbranchchannels!(branchesnode,Tmap,Tred,nodetree) end + end + + return branches +end + +function createbranchchannels(Tmap,Tred,tree::SegmentedOrderedBinaryTree) + + nodes = keys(tree.workersonhosts) + toptree = tree.toptree + Nmasternodes = length(nodes) + toptreenonleafnodes = length(toptree) - Nmasternodes + + branches = Vector{BranchChannel{Tmap,Tred}}(undef,length(tree)) - b = BranchChannel(p,Tmap,parentchannels,nchildren(topbranch)) - nodetreestartindex = tree.nodetreestartindices[nodeno] - branches[nodetreestartindex] = b + # populate the top tree other than the masternodes + # This is only run if there are multiple hosts + if toptreenonleafnodes > 0 + topnoderank_toptree = topnoderank(toptree) + topnode_toptree = toptree[topnoderank_toptree] + N = nchildren(topnode_toptree) + topmostbranch = BranchChannel{Tmap,Tred}(topnode_toptree.p,N) + branches[topnoderank_toptree] = topmostbranch + + left_inds = 1:topnoderank_toptree-1 + right_inds = topnoderank_toptree+1:length(toptree) + + @sync begin + @async if !isempty(left_inds) + left_child = OrderedBinaryTree(@view toptree.procs[left_inds]) + createbranchchannels!(@view(branches[left_inds]), + Tmap,Tred,left_child,topmostbranch) + end + + @async if !isempty(right_inds) + right_child = OrderedBinaryTree(@view toptree.procs[right_inds]) + createbranchchannels!(@view(branches[right_inds]), + Tmap,Tred,right_child,topmostbranch) + end + end - # Populate the rest of the tree - subtreeinds = StepRangeLen(nodetreestartindex,1,length(nodetree)) - branchesnode = @view branches[subtreeinds] + #= Remove the leaves from the top tree (masternodes). + They are the top nodes of the individual trees at the hosts. + They will be created separately and linked to the top tree. + =# + for i = 1:toptreenonleafnodes + branches[i] = branches[2i] + end + end - createbranchchannels!(branchesnode,Tmap,Tred,nodetree) + @sync for (nodeno,node) in enumerate(nodes) + @async begin + # Top node for each subtree (a masternode) + workersnode = tree.workersonhosts[node] + nodetree = OrderedBinaryTree(workersnode) + top = topnoderank(nodetree) + topnode = nodetree[top] + p = topnode.p + + if toptreenonleafnodes > 0 + # inherit from the parent node + leafno = masternodeindex(tree,p) + leafrank = leafrankfoldedtree(tree.toptree, Nmasternodes,leafno) + parentrank = parentnoderank(toptree, leafrank) + parentrankfulltree = toptree_to_fulltree_index(toptree, parentrank) + parentnodebranches = branches[parentrankfulltree] + parentchannels = parentnodebranches.childrenchannels + else + #= This happens if there is only one host, + in which case there's nothing to inherit. + In this case there's no difference between a + SegmentedOrderedBinaryTree and an OrderedBinaryTree + The top node is created separately as it is its own parent + =# + parentchannels = RemoteChannelContainer{Tred}(1,p) + end + + topbranchnode = BranchChannel(p,Tmap,parentchannels,nchildren(topnode)) + nodetreestartindex = tree.nodetreestartindices[nodeno] + branches[nodetreestartindex + top - 1] = topbranchnode + + # Populate the rest of the tree + left_inds_nodetree = (1:top-1) + left_inds_fulltree = (nodetreestartindex - 1) .+ left_inds_nodetree + right_inds_nodetree = top+1:length(nodetree) + right_inds_fulltree = (nodetreestartindex - 1) .+ right_inds_nodetree + + @async if !isempty(left_inds_nodetree) + + left_child = OrderedBinaryTree( + @view nodetree.procs[left_inds_nodetree]) + + createbranchchannels!(@view(branches[left_inds_fulltree]), + Tmap,Tred,left_child,topbranchnode) + end + + @async if !isempty(right_inds_nodetree) + + right_child = OrderedBinaryTree( + @view nodetree.procs[right_inds_nodetree]) + + createbranchchannels!(@view(branches[right_inds_fulltree]), + Tmap,Tred,right_child,topbranchnode) + end + end end return branches end -function createbranchchannels(::Type{Tmap},::Type{Tred}, - iterators::Tuple,::Type{T}) where {Tmap,Tred,T<:Tree} - +function createbranchchannels(Tmap,Tred,iterators::Tuple,T::Type{<:Tree}) w = workersactive(iterators) tree = T(w) branches = createbranchchannels(Tmap,Tred,tree) tree,branches end -function createbranchchannels(iterators::Tuple,::Type{T}) where {T<:Tree} +function createbranchchannels(iterators::Tuple,T::Type{<:Tree}) createbranchchannels(Any,Any,iterators,T) end diff --git a/test/runtests.jl b/test/runtests.jl index 1c24620..516f486 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,1783 +1 @@ -using ParallelUtilities -using Test -using Distributed -using DataStructures - -const workersused = 8 -addprocs(workersused) - -@everywhere begin - using Pkg - Pkg.activate(".") - using ParallelUtilities - import ParallelUtilities: BinaryTreeNode, RemoteChannelContainer, BranchChannel, - Sorted, Unsorted, Ordering, pval, value, reducedvalue, reduceTreeNode, mapTreeNode, - SequentialBinaryTree, OrderedBinaryTree, SegmentedSequentialBinaryTree, - parentnoderank, nchildren, - maybepvalput!, createbranchchannels, nworkersactive, workersactive, - procs_node, leafrankfoldedtree -end - -macro testsetwithinfo(str,ex) - quote - @info "Testing "*$str - @testset $str begin $(esc(ex)); end; - end -end - -# Wrap a testset around this to get the result at the end -@testset "ParallelUtilities" begin - -@testsetwithinfo "ProductSplit" begin - - various_iters = [(1:10,),(1:10,4:6),(1:10,4:6,1:4),(1:2:10,4:1:6), - (1:2,Base.OneTo(4),1:3:10)] - - function split_across_processors_iterators(arr::Iterators.ProductIterator,num_procs,proc_id) - - num_tasks = length(arr); - - num_tasks_per_process,num_tasks_leftover = divrem(num_tasks,num_procs) - - num_tasks_on_proc = num_tasks_per_process + (proc_id <= mod(num_tasks,num_procs) ? 1 : 0 ); - task_start = num_tasks_per_process*(proc_id-1) + min(num_tasks_leftover,proc_id-1) + 1; - - Iterators.take(Iterators.drop(arr,task_start-1),num_tasks_on_proc) - end - - function split_product_across_processors_iterators(arrs_tuple,num_procs,proc_id) - split_across_processors_iterators(Iterators.product(arrs_tuple...),num_procs,proc_id) - end - - @testset "Constructor" begin - - function checkPSconstructor(iters,npmax=10) - ntasks_total = prod(length.(iters)) - for np = 1:npmax, p = 1:np - ps = ProductSplit(iters,np,p) - @test collect(ps) == collect(split_product_across_processors_iterators(iters,np,p)) - @test ntasks(ps) == ntasks_total - @test ntasks(ps.iterators) == ntasks_total - @test eltype(ps) == Tuple{map(eltype,iters)...} - end - - @test_throws ParallelUtilities.ProcessorNumberError ProductSplit(iters,npmax,npmax+1) - end - - @testset "0D" begin - @test_throws ArgumentError ProductSplit((),2,1) - end - - @testset "cumprod" begin - @test ParallelUtilities._cumprod(1,()) == () - @test ParallelUtilities._cumprod(1,(2,)) == (1,) - @test ParallelUtilities._cumprod(1,(2,3)) == (1,2) - @test ParallelUtilities._cumprod(1,(2,3,4)) == (1,2,6) - end - - @testset "1D" begin - iters = (1:10,) - checkPSconstructor(iters) - end - @testset "2D" begin - iters = (1:10,4:6) - checkPSconstructor(iters) - end - @testset "3D" begin - iters = (1:10,4:6,1:4) - checkPSconstructor(iters) - end - @testset "steps" begin - iters = (1:2:10,4:1:6) - checkPSconstructor(iters) - iters = (10:-1:10,6:-2:0) - @test_throws ParallelUtilities.DecreasingIteratorError ProductSplit(iters,3,2) - end - @testset "mixed" begin - for iters in [(1:2,4:2:6),(1:2,Base.OneTo(4),1:3:10)] - checkPSconstructor(iters) - end - end - - @testset "empty" begin - iters = (1:1,) - ps = ProductSplit(iters,10,2) - @test isempty(ps) - @test length(ps) == 0 - end - - @testset "first and last ind" begin - for iters in [(1:10,),(1:2,Base.OneTo(4),1:3:10)] - ps = ProductSplit(iters,2,1) - @test firstindex(ps) == 1 - @test ps.firstind == 1 - @test ps.lastind == div(ntasks(iters),2) - @test lastindex(ps) == div(ntasks(iters),2) - @test lastindex(ps) == length(ps) - ps = ProductSplit(iters,2,2) - @test ps.firstind == div(ntasks(iters),2) + 1 - @test firstindex(ps) == 1 - @test ps.lastind == ntasks(iters) - @test lastindex(ps) == length(ps) - - for np in ntasks(iters)+1:ntasks(iters)+10, - p in ntasks(iters)+1:np - - ps = ProductSplit(iters,np,p) - @test ps.firstind == ntasks(iters) + 1 - @test ps.lastind == ntasks(iters) - end - end - end - - @testset "summary" begin - ps = ProductSplit((1:3, 4:5:19),3,2) - reprstr = "ProductSplit("*repr((1:3, 4:5:19))*",3,2)" - @test ParallelUtilities.mwerepr(ps) == reprstr - - summarystr = "$(length(ps))-element "*reprstr - @test ParallelUtilities.summary(ps) == summarystr - - io = IOBuffer() - summary(io,ps) - @test String(take!(io)) == summarystr - end - end - - @testset "firstlast" begin - @testset "first" begin - - @test ParallelUtilities._first(()) == () - - for iters in various_iters,np=1:5ntasks(iters) - - ps = ProductSplit(iters,np,1) - @test first(ps) == ( isempty(ps) ? nothing : map(first,iters) ) - end - - iters = (1:1,) - ps = ProductSplit(iters,2ntasks(iters),ntasks(iters)+1) # must be empty - @test first(ps) === nothing - end - @testset "last" begin - - @test ParallelUtilities._last(()) == () - - for iters in various_iters,np=1:5ntasks(iters) - - ps = ProductSplit(iters,np,np) - @test last(ps) == ( isempty(ps) ? nothing : map(last,iters) ) - end - - iters = (1:1,) - ps = ProductSplit(iters,2length(iters[1]),length(iters[1])+1) # must be empty - @test last(ps) === nothing - end - end - - @testset "extrema" begin - - @testset "min max extrema" begin - function checkPSextrema(iters,fn::Function,npmax=10) - for np = 1:npmax, p = 1:np - ps = ProductSplit(iters,np,p) - pcol = collect(ps) - for dim in 1:length(iters) - @test begin - res = fn(ps,dim=dim) == fn(x[dim] for x in pcol) - if !res - println(summary(ps)) - end - res - end - end - end - end - - for iters in various_iters, fn in [maximum,minimum,extrema] - checkPSextrema(iters,fn) - end - - @test minimum(ProductSplit((1:5,),2,1)) == 1 - @test maximum(ProductSplit((1:5,),2,1)) == 3 - @test extrema(ProductSplit((1:5,),2,1)) == (1,3) - - @test minimum(ProductSplit((1:5,),2,2)) == 4 - @test maximum(ProductSplit((1:5,),2,2)) == 5 - @test extrema(ProductSplit((1:5,),2,2)) == (4,5) - end - - @testset "extremadims" begin - ps = ProductSplit((1:10,),2,1) - @test ParallelUtilities._extremadims(ps,1,()) == () - for iters in various_iters - - dims = length(iters) - for np = 1:5ntasks(iters), proc_id = 1:np - ps = ProductSplit(iters,np,proc_id) - if isempty(ps) - @test extremadims(ps) == Tuple(nothing for i=1:dims) - else - ext = Tuple(map(extrema,zip(collect(ps)...))) - @test extremadims(ps) == ext - end - end - end - end - - @testset "extrema_commonlastdim" begin - iters = (1:10,4:6,1:4) - ps = ProductSplit(iters,37,8) - @test extrema_commonlastdim(ps) == ([(9,1),(6,1)],[(2,2),(4,2)]) - ps = ProductSplit(iters,ntasks(iters)+1,ntasks(iters)+1) - @test extrema_commonlastdim(ps) === nothing - end - end - - @testset "in" begin - - function checkifpresent(iters,npmax=10) - for np = 1:npmax, p = 1:np - ps = ProductSplit(iters,np,p) - pcol = collect(ps) - - for el in pcol - # It should be contained in this iterator - @test el in ps - for p2 in 1:np - # It should not be contained anywhere else - p2 == p && continue - ps2 = ProductSplit(iters,np,p2) - @test !(el in ps2) - end - end - end - end - - for iters in various_iters - checkifpresent(iters) - end - - @test ParallelUtilities._infullrange((),()) - end - - @testset "whichproc + procrange_recast" begin - np,proc_id = 5,5 - iters = (1:10,4:6,1:4) - ps = ProductSplit(iters,np,proc_id) - @test whichproc(iters,first(ps),1) == 1 - @test whichproc(iters,(100,100,100),1) === nothing - @test procrange_recast(iters,ps,1) == 1:1 - @test procrange_recast(ps,1) == 1:1 - - smalleriter = (1:1,1:1,1:1) - err = ParallelUtilities.TaskNotPresentError(smalleriter,first(ps)) - @test_throws err procrange_recast(smalleriter,ps,1) - smalleriter = (7:9,4:6,1:4) - err = ParallelUtilities.TaskNotPresentError(smalleriter,last(ps)) - @test_throws err procrange_recast(smalleriter,ps,1) - - iters = (1:1,2:2) - ps = ProductSplit(iters,np,proc_id) - @test whichproc(iters,first(ps),np) === nothing - @test whichproc(iters,nothing,np) === nothing - @test procrange_recast(iters,ps,2) == (0:-1) - @test procrange_recast(ps,2) == (0:-1) - - iters = (1:1,2:2) - ps = ProductSplit(iters,1,1) - @test procrange_recast(iters,ps,2) == 1:1 - @test procrange_recast(ps,2) == 1:1 - - iters = (Base.OneTo(2),2:4) - ps = ProductSplit(iters,2,1) - @test procrange_recast(iters,ps,1) == 1:1 - @test procrange_recast(iters,ps,2) == 1:1 - @test procrange_recast(iters,ps,ntasks(iters)) == 1:length(ps) - - for np_new in 1:5ntasks(iters) - for proc_id_new=1:np_new - ps_new = ProductSplit(iters,np_new,proc_id_new) - - for val in ps_new - # Should loop only if ps_new is non-empty - @test whichproc(iters,val,np_new) == proc_id_new - end - end - procid_new_first = whichproc(iters,first(ps),np_new) - proc_new_last = whichproc(iters,last(ps),np_new) - @test procrange_recast(iters,ps,np_new) == (isempty(ps) ? (0:-1) : (procid_new_first:proc_new_last)) - @test procrange_recast(ps,np_new) == (isempty(ps) ? (0:-1) : (procid_new_first:proc_new_last)) - end - - @testset "different set" begin - iters = (1:100,1:4000) - ps = ProductSplit((20:30,1:1),2,1) - @test procrange_recast(iters,ps,700) == 1:1 - ps = ProductSplit((20:30,1:1),2,2) - @test procrange_recast(iters,ps,700) == 1:1 - - iters = (1:1,2:2) - ps = ProductSplit((20:30,2:2),2,1) - @test_throws ParallelUtilities.TaskNotPresentError procrange_recast(iters,ps,3) - ps = ProductSplit((1:30,2:2),2,1) - @test_throws ParallelUtilities.TaskNotPresentError procrange_recast(iters,ps,3) - end - end - - @testset "localindex" begin - - for iters in various_iters - for np=1:5ntasks(iters),proc_id=1:np - ps = ProductSplit(iters,np,proc_id) - for (ind,val) in enumerate(ps) - @test localindex(ps,val) == ind - @test localindex(iters,val,np,proc_id) == ind - end - if isempty(ps) - @test localindex(ps,first(ps)) === nothing - end - end - end - end - - @testset "whichproc_localindex" begin - for iters in various_iters - for np=1:ntasks(iters),proc_id=1:np - ps_col = collect(ProductSplit(iters,np,proc_id)) - ps_col_rev = [reverse(t) for t in ps_col] - for val in ps_col - p,ind = whichproc_localindex(iters,val,np) - @test p == proc_id - ind_in_arr = searchsortedfirst(ps_col_rev,reverse(val)) - @test ind == ind_in_arr - end - end - end - end - - @testset "getindex" begin - - @test ParallelUtilities._getindex((),1) == () - @test ParallelUtilities._getindex((),1,2) == () - - @test ParallelUtilities.childindex((),1) == (1,) - - for iters in various_iters - for np=1:ntasks(iters),p=1:np - ps = ProductSplit(iters,np,p) - ps_col = collect(ps) - for i in 1:length(ps) - @test ps[i] == ps_col[i] - end - @test ps[end] == ps[length(ps)] - for ind in [0,length(ps)+1] - @test_throws ParallelUtilities.BoundsError(ps,ind) ps[ind] - end - end - end - end -end; - -@testsetwithinfo "ReverseLexicographicTuple" begin - @testset "isless" begin - a = ParallelUtilities.ReverseLexicographicTuple((1,2,3)) - b = ParallelUtilities.ReverseLexicographicTuple((2,2,3)) - @test a < b - @test a <= b - b = ParallelUtilities.ReverseLexicographicTuple((1,1,3)) - @test b < a - @test b <= a - b = ParallelUtilities.ReverseLexicographicTuple((2,1,3)) - @test b < a - @test b <= a - b = ParallelUtilities.ReverseLexicographicTuple((2,1,4)) - @test a < b - @test a <= b - end - @testset "equal" begin - a = ParallelUtilities.ReverseLexicographicTuple((1,2,3)) - @test a == a - @test isequal(a,a) - @test a <= a - b = ParallelUtilities.ReverseLexicographicTuple(a.t) - @test a == b - @test isequal(a,b) - @test a <= b - end -end; - -@testsetwithinfo "utilities" begin - @testset "workers active" begin - @test nworkersactive((1:1,)) == 1 - @test nworkersactive((1:2,)) == min(2,nworkers()) - @test nworkersactive((1:1,1:2)) == min(2,nworkers()) - @test nworkersactive(1:2) == min(2,nworkers()) - @test nworkersactive(1:1,1:2) == min(2,nworkers()) - @test nworkersactive((1:nworkers()+1,)) == nworkers() - @test nworkersactive(1:nworkers()+1) == nworkers() - @test workersactive((1:1,)) == workers()[1:1] - @test workersactive(1:1) == workers()[1:1] - @test workersactive(1:1,1:1) == workers()[1:1] - @test workersactive((1:2,)) == workers()[1:min(2,nworkers())] - @test workersactive((1:1,1:2)) == workers()[1:min(2,nworkers())] - @test workersactive(1:1,1:2) == workers()[1:min(2,nworkers())] - @test workersactive((1:nworkers()+1,)) == workers() - @test workersactive(1:nworkers()+1) == workers() - - ps = ProductSplit((1:10,),nworkers(),1) - @test nworkersactive(ps) == min(10,nworkers()) - - iters = (1:1,1:2) - ps = ProductSplit(iters,2,1) - @test nworkersactive(ps) == nworkersactive(iters) - @test workersactive(ps) == workersactive(iters) - end - - @testset "hostnames" begin - hostnames = gethostnames() - nodes = unique(hostnames) - @test hostnames == [@fetchfrom p Libc.gethostname() for p in workers()] - @test nodenames() == nodes - @test nodenames(hostnames) == nodes - np1 = nprocs_node(hostnames,nodes) - np2 = nprocs_node(hostnames) - np3 = nprocs_node() - @test np1 == np2 == np3 - for node in nodes - npnode = count(isequal(node),hostnames) - @test np1[node] == npnode - end - p1 = procs_node() - p2 = procs_node(workers(),hostnames,nodes) - @test p1 == p2 - for node in nodes - pnode = workers()[findall(isequal(node),hostnames)] - @test p1[node] == pnode - end - np4 = nprocs_node(p1) - @test np1 == np4 - end -end; - -@testset "BinaryTree" begin - @testsetwithinfo "BinaryTreeNode" begin - @testset "Constructor" begin - p = workers()[1] - b = BinaryTreeNode(p,p,0) - @test nchildren(b) == 0 - b = BinaryTreeNode(p,p,1) - @test nchildren(b) == 1 - b = BinaryTreeNode(p,p,2) - @test nchildren(b) == 2 - - @test_throws DomainError BinaryTreeNode(p,p,3) - @test_throws DomainError BinaryTreeNode(p,p,-1) - end - end - - @testsetwithinfo "BinaryTree" begin - @testsetwithinfo "SequentialBinaryTree" begin - @testset "pid and parent" begin - for imax = 1:100 - procs = 1:imax - tree = SequentialBinaryTree(procs) - @test length(tree) == length(procs) - topnoderank = ParallelUtilities.topnoderank(tree) - @test topnoderank == 1 - @test tree[topnoderank] == ParallelUtilities.topnode(tree) - @test tree[1].parent == 1 - for rank in 1:length(tree) - node = tree[rank] - @test node.p == procs[rank] - @test node.parent == procs[parentnoderank(tree,rank)] - end - - for ind in [0,imax+1] - @test_throws BoundsError(tree,ind) parentnoderank(tree,ind) - @test_throws BoundsError(tree,ind) tree[ind] - end - end - end - - @testset "nchildren" begin - tree = SequentialBinaryTree(1:1) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,2) nchildren(tree,2) - - tree = SequentialBinaryTree(1:2) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 1 - @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 0 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,3) nchildren(tree,3) - - tree = SequentialBinaryTree(1:8) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 2 - @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 - @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 2 - @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 1 - @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 - @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 0 - @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 - @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 0 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,9) nchildren(tree,9) - end - - @testset "level" begin - tree = SequentialBinaryTree(1:15) - @test ParallelUtilities.levels(tree) == 4 - - @test ParallelUtilities.levelfromtop(tree,1) == 1 - @test ParallelUtilities.levelfromtop.((tree,),2:3) == ones(Int,2)*2 - @test ParallelUtilities.levelfromtop.((tree,),4:7) == ones(Int,4)*3 - @test ParallelUtilities.levelfromtop.((tree,),8:15) == ones(Int,8)*4 - - for p in [0,length(tree)+1] - @test_throws BoundsError(tree,p) ParallelUtilities.levelfromtop(tree,p) - end - end - - @testset "summary" begin - tree = SequentialBinaryTree(1:4) - io = IOBuffer() - summary(io,tree) - strexp = "$(length(tree))-node $(typeof(tree))" - @test String(take!(io)) == strexp - @test summary(tree) == strexp - end - end - - @testsetwithinfo "OrderedBinaryTree" begin - @testset "pid and parent" begin - for imax = 1:100 - procs = 1:imax - tree = OrderedBinaryTree(procs) - @test length(tree) == length(procs) - - topnoderank = ParallelUtilities.topnoderank(tree) - @test tree[topnoderank].parent == topnoderank - for rank in 1:length(tree) - node = tree[rank] - @test node.p == procs[rank] - @test node.parent == procs[parentnoderank(tree,rank)] - end - @test_throws BoundsError(tree,0) parentnoderank(tree,0) - @test_throws BoundsError(tree,imax+1) parentnoderank(tree,imax+1) - end - end - - @testset "nchildren" begin - tree = OrderedBinaryTree(1:1) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,2) nchildren(tree,2) - @test ParallelUtilities.topnoderank(tree) == 1 - - tree = OrderedBinaryTree(1:2) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 - @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 1 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,3) nchildren(tree,3) - @test ParallelUtilities.topnoderank(tree) == 2 - - tree = OrderedBinaryTree(1:8) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 - @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 - @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 0 - @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 2 - @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 - @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 2 - @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 - @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 1 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,9) nchildren(tree,9) - @test ParallelUtilities.topnoderank(tree) == 8 - - tree = OrderedBinaryTree(1:11) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 - @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 - @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 0 - @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 2 - @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 - @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 2 - @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 - @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 2 - @test nchildren(tree,9) == nchildren(tree[9]) == tree[9].nchildren == 0 - @test nchildren(tree,10) == nchildren(tree[10]) == tree[10].nchildren == 2 - @test nchildren(tree,11) == nchildren(tree[11]) == tree[11].nchildren == 0 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,12) nchildren(tree,12) - @test ParallelUtilities.topnoderank(tree) == 8 - - tree = OrderedBinaryTree(1:13) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 - @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 - @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 0 - @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 2 - @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 - @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 2 - @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 - @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 2 - @test nchildren(tree,9) == nchildren(tree[9]) == tree[9].nchildren == 0 - @test nchildren(tree,10) == nchildren(tree[10]) == tree[10].nchildren == 2 - @test nchildren(tree,11) == nchildren(tree[11]) == tree[11].nchildren == 0 - @test nchildren(tree,12) == nchildren(tree[12]) == tree[12].nchildren == 2 - @test nchildren(tree,13) == nchildren(tree[13]) == tree[13].nchildren == 0 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,14) nchildren(tree,14) - @test ParallelUtilities.topnoderank(tree) == 8 - end - - @testset "level" begin - tree = OrderedBinaryTree(1:15) - @test ParallelUtilities.levels(tree) == 4 - - @test ParallelUtilities.levelfromtop.((tree,),1:2:15) == ones(Int,8).*4 - @test ParallelUtilities.levelfromtop.((tree,),(2,6,10,14)) == (3,3,3,3) - @test ParallelUtilities.levelfromtop.((tree,),(4,12)) == (2,2) - @test ParallelUtilities.levelfromtop(tree,8) == 1 - for p in [0,length(tree)+1] - @test_throws BoundsError(tree,p) ParallelUtilities.levelfromtop(tree,p) - end - - tree = OrderedBinaryTree(1:13) - @test ParallelUtilities.levels(tree) == 4 - @test ParallelUtilities.levelfromtop.((tree,),1:2:11) == ones(Int,6).*4 - @test ParallelUtilities.levelfromtop.((tree,),(2,6,10,13)) == (3,3,3,3) - @test ParallelUtilities.levelfromtop.((tree,),(4,12)) == (2,2) - @test ParallelUtilities.levelfromtop(tree,8) == 1 - for p in [0,length(tree)+1] - @test_throws BoundsError(tree,p) ParallelUtilities.levelfromtop(tree,p) - end - end - end - - @testsetwithinfo "SegmentedSequentialBinaryTree" begin - @testsetwithinfo "single host" begin - @testset "pid and parent" begin - for imax = 1:100 - procs = 1:imax - workersonnodes = Dict("host"=>procs) - tree = SegmentedSequentialBinaryTree(procs,workersonnodes) - treeSBT = SequentialBinaryTree(procs) - @test length(tree) == length(procs) == length(treeSBT) - - topnoderank = ParallelUtilities.topnoderank(tree) - @test topnoderank == 1 - @test tree[topnoderank] == ParallelUtilities.topnode(tree) - @test tree[1].parent == 1 - for rank in 1:length(tree) - node = tree[rank] - parentnode = tree[parentnoderank(tree,rank)] - @test node.p == procs[rank] - @test node.parent == procs[parentnoderank(treeSBT,rank)] - @test parentnode.p == node.parent - end - end - end; - - @testset "nchildren" begin - procs = 1:1 - tree = SegmentedSequentialBinaryTree(procs,Dict("host"=>procs)) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,2) nchildren(tree,2) - - procs = 1:2 - tree = SegmentedSequentialBinaryTree(procs,Dict("host"=>procs)) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 1 - @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 0 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,3) nchildren(tree,3) - - procs = 1:8 - tree = SegmentedSequentialBinaryTree(procs,Dict("host"=>procs)) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 2 - @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 - @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 2 - @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 1 - @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 - @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 0 - @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 - @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 0 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,9) nchildren(tree,9) - end; - end; - - @testsetwithinfo "multiple hosts" begin - @testset "length" begin - procs = 1:2 - tree = SegmentedSequentialBinaryTree(procs, - OrderedDict("host1"=>1:1,"host2"=>2:2)) - @test length(tree) == 2 + 1 - - procs = 1:4 - tree = SegmentedSequentialBinaryTree(procs, - OrderedDict("host1"=>1:2,"host2"=>3:4)) - - @test length(tree) == 4 + 1 - - procs = 1:12 - tree = SegmentedSequentialBinaryTree(procs, - OrderedDict( - "host1"=>1:3,"host2"=>4:6, - "host3"=>7:9,"host4"=>10:12)) - - @test length(tree) == 12 + 3 - end; - - @testset "leafrankfoldedtree" begin - @test leafrankfoldedtree(5,1) == 8 - @test leafrankfoldedtree(5,2) == 9 - @test leafrankfoldedtree(5,3) == 5 - @test leafrankfoldedtree(5,4) == 6 - @test leafrankfoldedtree(5,5) == 7 - end; - - @testset "pid and parent" begin - for imax = 2:100 - procs = 1:imax - mid = div(imax,2) - workersonnodes = OrderedDict{String,Vector{Int}}() - workersonnodes["host1"] = procs[1:mid] - workersonnodes["host2"] = procs[mid+1:end] - tree = SegmentedSequentialBinaryTree(procs,workersonnodes) - - topnoderank = ParallelUtilities.topnoderank(tree) - @test topnoderank == 1 - @test tree[topnoderank] == ParallelUtilities.topnode(tree) - @test tree[1].parent == 1 - @test parentnoderank(tree,1) == 1 - for (ind,rank) in enumerate(1:mid) - node = tree[rank+1] - parentnode = tree[parentnoderank(tree,rank+1)] - @test parentnode.p == node.parent - pnodes = workersonnodes["host1"] - @test node.p == pnodes[ind] - SBT = SequentialBinaryTree(pnodes) - if ind == 1 - @test node.parent == 1 - else - @test node.parent == pnodes[parentnoderank(SBT,ind)] - end - end - for (ind,rank) in enumerate(mid+1:imax) - node = tree[rank+1] - parentnode = tree[parentnoderank(tree,rank+1)] - @test parentnode.p == node.parent - pnodes = workersonnodes["host2"] - @test node.p == pnodes[ind] - SBT = SequentialBinaryTree(pnodes) - if ind == 1 - @test node.parent == 1 - else - @test node.parent == pnodes[parentnoderank(SBT,ind)] - end - end - end - end; - - @testset "nchildren" begin - procs = 1:2 - tree = SegmentedSequentialBinaryTree(procs, - OrderedDict("host1"=>1:1,"host2"=>2:2)) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 2 - @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 0 - @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 0 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,4) nchildren(tree,4) - - procs = 1:12 - tree = SegmentedSequentialBinaryTree(procs, - OrderedDict( - "host1"=>1:3,"host2"=>4:6, - "host3"=>7:9,"host4"=>10:12)) - @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 2 - @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 - @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 2 - @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 2 - @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 - @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 0 - @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 2 - @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 0 - @test nchildren(tree,9) == nchildren(tree[9]) == tree[9].nchildren == 0 - @test nchildren(tree,10) == nchildren(tree[10]) == tree[10].nchildren == 2 - @test nchildren(tree,11) == nchildren(tree[11]) == tree[11].nchildren == 0 - @test nchildren(tree,12) == nchildren(tree[12]) == tree[12].nchildren == 0 - @test nchildren(tree,13) == nchildren(tree[13]) == tree[13].nchildren == 2 - @test nchildren(tree,14) == nchildren(tree[14]) == tree[14].nchildren == 0 - @test nchildren(tree,15) == nchildren(tree[15]) == tree[15].nchildren == 0 - @test_throws BoundsError(tree,0) nchildren(tree,0) - @test_throws BoundsError(tree,16) nchildren(tree,16) - end; - end; - end - end - - @testset "RemoteChannelContainer" begin - @testset "Constructor" begin - rc = ParallelUtilities.RemoteChannelContainer{Int}(1,myid()) - @test rc.out.where == myid() - @test rc.err.where == myid() - @test eltype(rc) == Int - for p in workers() - rc = ParallelUtilities.RemoteChannelContainer{Int}(1,p) - @test rc.out.where == p - @test rc.err.where == p - @test eltype(rc) == Int - end - - rc = ParallelUtilities.RemoteChannelContainer{Int}(1) - @test rc.out.where == myid() - @test rc.err.where == myid() - @test eltype(rc) == Int - - rc = ParallelUtilities.RemoteChannelContainer(1,myid()) - @test rc.out.where == myid() - @test rc.err.where == myid() - @test eltype(rc) == Any - - for p in workers() - rc = ParallelUtilities.RemoteChannelContainer(1,p) - @test rc.out.where == p - @test rc.err.where == p - @test eltype(rc) == Any - end - - rc = ParallelUtilities.RemoteChannelContainer(1) - @test rc.out.where == myid() - @test rc.err.where == myid() - @test eltype(rc) == Any - end - - @testset "finalize" begin - rc = ParallelUtilities.RemoteChannelContainer{Int}(1) - finalize(rc) - @test rc.out.where == 0 - @test rc.err.where == 0 - end - - @testset "finalize_except_wherewhence" begin - rc = ParallelUtilities.RemoteChannelContainer{Int}(1) - ParallelUtilities.finalize_except_wherewhence(rc) - @test rc.out.where == myid() - @test rc.err.where == myid() - - @testset "rc on where" begin - # Create on this processor - rc = ParallelUtilities.RemoteChannelContainer{Int}(1) - for p in workers() - rcoutw,rcerrw = @fetchfrom p begin - ParallelUtilities.finalize_except_wherewhence(rc) - rc.out.where,rc.err.where - end - @test rc.out.where == myid() - @test rc.err.where == myid() - @test (rcoutw,rcerrw) == (0,0) - end - end - - @testset "rc on remote" begin - # Create elsewhere - p_rc = workers()[1] - rc = ParallelUtilities.RemoteChannelContainer{Int}(1,p_rc) - for p in procs() - rc_where = @fetchfrom p begin - ParallelUtilities.finalize_except_wherewhence(rc) - (rc.out.where,rc.err.where) - end - if p != myid() && p != p_rc - @test rc_where == (0,0) - else - @test rc_where == (p_rc,p_rc) - end - end - end - end - end - - @testset "BranchChannel" begin - @testset "Constructor" begin - @testset "all channels supplied" begin - rc_self = RemoteChannelContainer{Int}(1) - rc_parent = RemoteChannelContainer{Int}(1) - rc_children = RemoteChannelContainer{Int}(1) - for n=0:2 - b = BranchChannel(1,rc_self,rc_parent,rc_children,n) - @test b isa BranchChannel{Int,Int} - @test b.p == 1 - @test b.selfchannels == rc_self - @test b.parentchannels == rc_parent - @test b.childrenchannels == rc_children - @test nchildren(b) == b.nchildren == n - end - @test_throws ParallelUtilities.DomainError BranchChannel(1,rc_self,rc_parent,rc_children,3) - end - - @testset "only parent channels supplied" begin - rc_parent = RemoteChannelContainer{Int}(1) - for n=0:2 - b = BranchChannel(1,Int,rc_parent,n) - @test b isa BranchChannel{Int,Int} - @test b.p == 1 - @test b.parentchannels == rc_parent - @test b.selfchannels isa RemoteChannelContainer{Int} - @test b.childrenchannels isa RemoteChannelContainer{Int} - @test b.selfchannels.out.where == b.p - @test b.selfchannels.err.where == b.p - @test b.childrenchannels.out.where == b.p - @test b.childrenchannels.err.where == b.p - @test nchildren(b) == b.nchildren == n - end - @test_throws ParallelUtilities.DomainError BranchChannel(1,Int,rc_parent,3) - end - - @testset "no channels supplied" begin - function testbranchchannel(b::BranchChannel{T,T},p,n) where {T} - @test b.p == p - @test b.parentchannels isa RemoteChannelContainer{T} - @test b.selfchannels isa RemoteChannelContainer{T} - @test b.childrenchannels isa RemoteChannelContainer{T} - @test b.parentchannels.out.where == b.p - @test b.parentchannels.err.where == b.p - @test b.selfchannels.out.where == b.p - @test b.selfchannels.err.where == b.p - @test b.childrenchannels.out.where == b.p - @test b.childrenchannels.err.where == b.p - @test nchildren(b) == b.nchildren == n - end - - p = workers()[1] - for n=0:2 - b = BranchChannel{Int,Int}(p,n) - testbranchchannel(b,p,n) - end - @test_throws ParallelUtilities.DomainError BranchChannel{Int,Int}(1,3) - @test_throws ParallelUtilities.DomainError BranchChannel{Int,Int}(1,-1) - end - end - - @testset "finalize" begin - @testset "sameprocessor" begin - parentchannels = RemoteChannelContainer{Int}(1) - b = BranchChannel(1,Int,parentchannels,1) - finalize(b) - @test b.selfchannels.out.where == 0 - @test b.selfchannels.err.where == 0 - @test b.childrenchannels.out.where == 0 - @test b.childrenchannels.err.where == 0 - @test b.parentchannels.out.where == myid() - @test b.parentchannels.err.where == myid() - end - @testset "elsewhere" begin - p = workers()[1] - selfchannels = RemoteChannelContainer{Int}(1,p) - childrenchannels = RemoteChannelContainer{Int}(1,p) - - @testset "parent == whence == where == myid()" begin - parentchannels = RemoteChannelContainer{Int}(1) - b = BranchChannel(1,selfchannels,parentchannels,childrenchannels,1) - self_w,parent_w,child_w = @fetchfrom p begin - finalize(b) - (b.selfchannels.out.where,b.selfchannels.err.where), - (b.parentchannels.out.where,b.parentchannels.err.where), - (b.childrenchannels.out.where,b.childrenchannels.err.where) - end - @test self_w == (0,0) - @test child_w == (0,0) - @test parent_w == (0,0) - end - - @testset "(parent == where) != (whence == myid())" begin - parentchannels = RemoteChannelContainer{Int}(1,p) - b = BranchChannel(1,selfchannels,parentchannels,childrenchannels,1) - self_w,parent_w,child_w = @fetchfrom p begin - finalize(b) - (b.selfchannels.out.where,b.selfchannels.err.where), - (b.parentchannels.out.where,b.parentchannels.err.where), - (b.childrenchannels.out.where,b.childrenchannels.err.where) - end - @test self_w == (0,0) - @test child_w == (0,0) - @test parent_w == (p,p) - end - end - end - - @testset "createbranchchannels" begin - function testbranches(T,tree) - branches = createbranchchannels(T,T,tree) - @test length(branches) == length(tree) - for (rank,branch) in enumerate(branches) - parentrank = parentnoderank(tree,rank) - p = branch.p - p_parent = branches[parentrank].p - @test branch.selfchannels.out.where == p - @test branch.selfchannels.err.where == p - @test branch.childrenchannels.out.where == p - @test branch.childrenchannels.err.where == p - @test branch.parentchannels.out.where == p_parent - @test branch.parentchannels.err.where == p_parent - end - end - - for TreeType in [SequentialBinaryTree, - OrderedBinaryTree, - SegmentedSequentialBinaryTree] - - tree = TreeType(workers()) - for T in [Int,Any,Bool,Vector{Float64},Array{ComplexF64,2}] - testbranches(T,tree) - end - end - - iterators = (1:nworkers()+1,) - tree,branches = createbranchchannels(iterators,SequentialBinaryTree) - @test eltype(first(branches).parentchannels) == Any - tree,branches = createbranchchannels(iterators,SegmentedSequentialBinaryTree) - @test eltype(first(branches).parentchannels) == Any - tree,branches = createbranchchannels(iterators,OrderedBinaryTree) - @test eltype(first(branches).parentchannels) == Any - tree,branches = createbranchchannels(Int,Int,iterators,SequentialBinaryTree) - @test eltype(first(branches).parentchannels) == Int - tree,branches = createbranchchannels(Int,Int,iterators,SegmentedSequentialBinaryTree) - @test eltype(first(branches).parentchannels) == Int - tree,branches = createbranchchannels(Int,Int,iterators,OrderedBinaryTree) - @test eltype(first(branches).parentchannels) == Int - - # Make sure that all branches are defined - for T in [SequentialBinaryTree, - OrderedBinaryTree, - SegmentedSequentialBinaryTree] - - for nmax = 1:nworkers() - iterators = (1:nmax,) - tree,branches = createbranchchannels(iterators,T) - for i in eachindex(branches) - @test isassigned(branches,i) - end - end - end - end - end -end; - -@testset "pmap and reduce" begin - - exceptiontype = RemoteException - if VERSION >= v"1.3" - exceptiontype = CompositeException - end - - @testset "Sorted and Unsorted" begin - @test Sorted() isa Ordering - @test Unsorted() isa Ordering - end; - - @testset "pval" begin - p = pval(2,3) - @test value(p) == 3 - @test value(3) == 3 - @test value(p) == value(value(p)) - - @test convert(pval{Any},p) == pval{Any}(2,3) - @test convert(pval{Float64},p) == pval{Any}(2,3.0) - end; - - @testset "mapTreeNode" begin - - @testset "maybepvalput!" begin - pipe = BranchChannel{Int,Int}(myid(),0) - rank = 1 - maybepvalput!(pipe,rank,0) - @test isready(pipe.selfchannels.out) - @test take!(pipe.selfchannels.out) == 0 - - pipe = BranchChannel{pval,pval}(myid(),0) - maybepvalput!(pipe,rank,0) - @test isready(pipe.selfchannels.out) - @test take!(pipe.selfchannels.out) == pval(rank,0) - - pipe = BranchChannel{pval{Int},pval{Int}}(myid(),0) - maybepvalput!(pipe,rank,0) - @test isready(pipe.selfchannels.out) - @test take!(pipe.selfchannels.out) == pval(rank,0) - - T = Vector{ComplexF64} - pipe = BranchChannel{pval{T},pval{T}}(myid(),1) - - val = ones(1).*im - maybepvalput!(pipe,rank,val) - @test isready(pipe.selfchannels.out) - @test take!(pipe.selfchannels.out) == pval(rank,ComplexF64[im]) - - val = ones(1) - maybepvalput!(pipe,rank,val) - @test isready(pipe.selfchannels.out) - @test take!(pipe.selfchannels.out) == pval(rank,ComplexF64[1]) - - T = Vector{Float64} - pipe = BranchChannel{pval{T},pval{T}}(myid(),1) - - val = ones(1) - maybepvalput!(pipe,rank,val) - @test isready(pipe.selfchannels.out) - @test take!(pipe.selfchannels.out) == pval(rank,Float64[1]) - - val = ones(Int,1) - maybepvalput!(pipe,rank,val) - @test isready(pipe.selfchannels.out) - @test take!(pipe.selfchannels.out) == pval(rank,Float64[1]) - - pipe = BranchChannel{pval,pval}(myid(),1) - - val = ones(1) - maybepvalput!(pipe,rank,val) - @test isready(pipe.selfchannels.out) - @test take!(pipe.selfchannels.out) == pval(rank,Float64[1]) - - val = ones(Int,1) - maybepvalput!(pipe,rank,val) - @test isready(pipe.selfchannels.out) - @test take!(pipe.selfchannels.out) == pval(rank,Int[1]) - end - - function test_on_pipe(fn,iterator,pipe,result_expected) - progressrc = nothing - rank = 1 - @test_throws ErrorException mapTreeNode(x->error(""),iterator,rank,pipe,progressrc) - @test !isready(pipe.selfchannels.out) # should not have any result as there was an error - @test isready(pipe.selfchannels.err) - @test take!(pipe.selfchannels.err) # error flag should be true - @test !isready(pipe.selfchannels.err) # should not hold anything now - @test !isready(pipe.parentchannels.out) - @test !isready(pipe.parentchannels.err) - @test !isready(pipe.childrenchannels.out) - @test !isready(pipe.childrenchannels.err) - - mapTreeNode(fn,iterator,rank,pipe,progressrc) - @test isready(pipe.selfchannels.err) - @test !take!(pipe.selfchannels.err) # error flag should be false - @test !isready(pipe.selfchannels.err) - @test isready(pipe.selfchannels.out) - @test take!(pipe.selfchannels.out) == result_expected - @test !isready(pipe.selfchannels.out) - @test !isready(pipe.parentchannels.out) - @test !isready(pipe.parentchannels.err) - @test !isready(pipe.childrenchannels.out) - @test !isready(pipe.childrenchannels.err) - end - - @testset "range" begin - iterator = 1:10 - - pipe = BranchChannel{Int,Int}(myid(),0) - test_on_pipe(sum,iterator,pipe,sum(iterator)) - end - - @testset "ProductSplit" begin - iterators = (1:10,) - ps = ProductSplit(iterators,1,1) - - pipe = BranchChannel{Int,Int}(myid(),0) - test_on_pipe(x->sum(y[1] for y in x),ps,pipe,sum(iterators[1])) - - pipe = BranchChannel{Int,Int}(myid(),1) - test_on_pipe(x->sum(y[1] for y in x),ps,pipe,sum(iterators[1])) - - pipe = BranchChannel{Int,Int}(myid(),2) - test_on_pipe(x->sum(y[1] for y in x),ps,pipe,sum(iterators[1])) - end - - @testset "progress" begin - @test isnothing(ParallelUtilities.indicatemapprogress!(nothing,1)) - rettype = Tuple{Bool,Bool,Int} - progress = RemoteChannel(()->Channel{rettype}(1)) - ParallelUtilities.indicatemapprogress!(progress,10) - @test take!(progress) == (true,false,10) - end - end; - - @testset "reduce" begin - - # Leaves just push results to the parent - # reduced value at a leaf is simply whatever is stored in the local output channel - @testset "at a leaf" begin - # These do not check for errors - result = 1 - rank = 1 - val = pval(rank,result) - - pipe = BranchChannel{typeof(val),typeof(val)}(myid(),0) - put!(pipe.selfchannels.out,val) - @test ParallelUtilities.reducedvalue(sum,rank,pipe,Sorted()) == val - - pipe = BranchChannel{typeof(result),typeof(result)}(myid(),0) - put!(pipe.selfchannels.out,result) - @test ParallelUtilities.reducedvalue(sum,rank,pipe,Unsorted()) == result - end; - - # # Values are collected at the intermediate nodes - @testset "at parent nodes" begin - - # Put some known values on the self and children channels - function putselfchildren!(pipe::BranchChannel,::Unsorted,rank=1) - if rank >= 1 - put!(pipe.selfchannels.out,0) - put!(pipe.selfchannels.err,false) - end - for i=1:nchildren(pipe) - put!(pipe.childrenchannels.out,i) - put!(pipe.childrenchannels.err,false) - end - end - function putselfchildren!(pipe::BranchChannel{<:pval},::Sorted,rank=1) - put!(pipe.selfchannels.out,pval(2,2)) - put!(pipe.selfchannels.err,false) - N = nchildren(pipe) - - if N > 0 - # left child - put!(pipe.childrenchannels.out,pval(1,1)) - put!(pipe.childrenchannels.err,false) - end - - if N > 1 - # right child - put!(pipe.childrenchannels.out,pval(3,3)) - put!(pipe.childrenchannels.err,false) - end - end - - function clearerrors!(pipe::BranchChannel,rank=1) - if rank >= 1 - take!(pipe.selfchannels.err) - end - for i=1:nchildren(pipe) - take!(pipe.childrenchannels.err) - end - end - - @testset "reducedvalue" begin - - function testreduction(freduce::Function,pipe::BranchChannel, - ifsorted::Ordering,res_exp,rank=2) - - p = pipe.p - - try - putselfchildren!(pipe,ifsorted,rank) - @test value(reducedvalue(freduce,rank,pipe,ifsorted)) == res_exp - clearerrors!(pipe,rank) - - @fetchfrom p putselfchildren!(pipe,ifsorted,rank) - @test value(@fetchfrom p reducedvalue(freduce,rank,pipe,ifsorted)) == res_exp - clearerrors!(pipe,rank) - - @fetchfrom p putselfchildren!(pipe,ifsorted,rank) - @test value(reducedvalue(freduce,rank,pipe,ifsorted)) == res_exp - clearerrors!(pipe,rank) - - putselfchildren!(pipe,ifsorted,rank) - @test value(@fetchfrom p reducedvalue(freduce,rank,pipe,ifsorted)) == res_exp - clearerrors!(pipe,rank) - catch - rethrow() - end - end - - for nchildren = 1:2 - @testset "Unsorted" begin - pipe = BranchChannel{Int,Int}(myid(),nchildren) - res_exp = sum(0:nchildren) - testreduction(sum,pipe,Unsorted(),res_exp,2) - testreduction(sum,pipe,Unsorted(),res_exp,0) - end - @testset "Sorted" begin - pipe = BranchChannel{pval,pval}(myid(),nchildren) - res_exp = collect(1:nchildren+1) - testreduction(x->vcat(x...),pipe,Sorted(),res_exp) - - pipe = BranchChannel{pval,pval}(myid(),nchildren) - res_exp = sum(1:nchildren+1) - testreduction(sum,pipe,Sorted(),res_exp) - end - end - - # The top tree must have children by definition - pipe = BranchChannel{Int,Int}(myid(),0) - putselfchildren!(pipe,Unsorted(),0) - err = ErrorException("nodes with rank <=0 must have children") - @test_throws err reducedvalue(sum,0,pipe,Unsorted()) - clearerrors!(pipe,0) - end - - @testset "reduceTreeNode" begin - - function testreduction(freduce::Function,pipe::BranchChannel, - ifsorted::Ordering,res_exp) - - @test !isready(pipe.parentchannels.out) - @test !isready(pipe.parentchannels.err) - - progressrc = nothing - rank = 2 - - try - wait(@spawnat pipe.p putselfchildren!(pipe,ifsorted)) - reduceTreeNode(freduce,rank,pipe,ifsorted,progressrc) - catch - rethrow() - end - @test isready(pipe.parentchannels.out) - @test isready(pipe.parentchannels.err) - @test !take!(pipe.parentchannels.err) # there should be no error - @test value(take!(pipe.parentchannels.out)) == res_exp - - # The pipe should be finalized at this point - @test pipe.selfchannels.out.where == 0 - @test pipe.selfchannels.err.where == 0 - @test pipe.childrenchannels.out.where == 0 - @test pipe.childrenchannels.err.where == 0 - end - - for nchildren = 1:2 - @testset "Unsorted" begin - pipe = BranchChannel{Int,Int}(myid(),nchildren) - res_exp = sum(0:nchildren) - testreduction(sum,pipe,Unsorted(),res_exp) - - rc_parent = RemoteChannelContainer{Int}(1) - p = workers()[1] - pipe = BranchChannel(p,Int,rc_parent,nchildren) - testreduction(sum,pipe,Unsorted(),res_exp) - end - @testset "Sorted" begin - pipe = BranchChannel{pval,pval}(myid(),nchildren) - res_exp = collect(1:nchildren+1) - testreduction(x->vcat(x...),pipe,Sorted(),res_exp) - - rc_parent = RemoteChannelContainer{pval}(myid(),1) - p = workers()[1] - pipe = BranchChannel(p,pval,rc_parent,nchildren) - testreduction(x->vcat(x...),pipe,Sorted(),res_exp) - - pipe = BranchChannel{pval,pval}(myid(),nchildren) - res_exp = sum(1:nchildren+1) - testreduction(sum,pipe,Sorted(),res_exp) - - rc_parent = RemoteChannelContainer{pval}(1) - p = workers()[1] - pipe = BranchChannel(p,pval,rc_parent,nchildren) - testreduction(sum,pipe,Sorted(),res_exp) - end - end - end - end; - - @testset "progress" begin - @test isnothing(ParallelUtilities.indicatereduceprogress!(nothing,1)) - rettype = Tuple{Bool,Bool,Int} - progress = RemoteChannel(()->Channel{rettype}(1)) - ParallelUtilities.indicatereduceprogress!(progress,10) - @test take!(progress) == (false,true,10) - - @test isnothing(ParallelUtilities.indicatefailure!(nothing,1)) - ParallelUtilities.indicatefailure!(progress,10) - @test take!(progress) == (false,false,10) - end - end; - - @testsetwithinfo "pmapbatch" begin - @testsetwithinfo "batch" begin - @testset "comparison with map" begin - iterable = 1:nworkers() - res = pmapbatch(x->myid(),iterable) - @test res == workers() - res = pmapbatch(x->myid(),(iterable,)) - @test res == workers() - res = pmapbatch(x->myid(),(iterable,1:1)) - @test res == workers() - res = pmapbatch(x->myid(),iterable,num_workers=1) - @test res == workers()[1:1] - - iterable = 1:nworkers()-1 - res = pmapbatch(x->myid(),iterable) - @test res == workersactive(iterable) - - iterable = 1:nworkers() - res = pmapbatch(identity,iterable) - resexp = [ProductSplit((iterable,),nworkersactive(iterable),p) for p=1:nworkersactive(iterable)] - @test res == resexp - - iterable = 1:nworkers() - res = pmapbatch(identity,iterable) - resexp = [ProductSplit((iterable,),nworkers(),p) for p=1:nworkers()] - @test res == resexp - - iterable = 1:2nworkers() - res = pmapbatch(identity,iterable) - resexp = [ProductSplit((iterable,),nworkersactive(iterable),p) for p=1:nworkersactive(iterable)] - @test res == resexp - end - - @testset "errors" begin - @test_throws exceptiontype pmapbatch(x->throw(BoundsError()),1:10) - end - end - - @testsetwithinfo "elementwise" begin - @testset "comparison with map" begin - iterable = 1:nworkers() - res = pmapbatch_elementwise(identity,iterable) - @test res == iterable - - res = pmapbatch_elementwise(identity,iterable,num_workers=1) - @test res == iterable - - iterable = 1:20 - res = pmapbatch_elementwise(x->x^2,iterable) - @test res == iterable.^2 - end - - @testset "errors" begin - @test_throws exceptiontype pmapbatch_elementwise(x->throw(BoundsError()),1:10) - end - end - end; - - @testsetwithinfo "pmapsum" begin - @testsetwithinfo "batch" begin - @testset "rank" begin - res_exp = sum(1:nworkers()) - @testset "without progress" begin - res = pmapsum(x->x[1][1],Int,1:nworkers()) - @test res == res_exp - res = pmapsum(x->x[1][1],1:nworkers()) - @test res == res_exp - end - @testset "with progress" begin - res = pmapsum(x->x[1][1],Int,1:nworkers(),showprogress=true) - @test res == res_exp - res = pmapsum(x->x[1][1],1:nworkers(),showprogress=true) - @test res == res_exp - end - @test pmapsum(x->x[1][1],Int,(1:nworkers(),)) == res_exp - @test pmapsum(x->x[1][1],(1:nworkers(),)) == res_exp - @test pmapsum(x->x[1][1],Int,(1:nworkers(),1:1)) == res_exp - @test pmapsum(x->x[1][1],(1:nworkers(),1:1)) == res_exp - @test pmapsum(x->myid(),1:nworkers()) == sum(workers()) - end - - @testset "one iterator" begin - rng = 1:100 - @test pmapsum(x->sum(y[1] for y in x),rng) == sum(rng) - @test pmapsum(x->sum(y[1] for y in x),(rng,)) == sum(rng) - end - - @testset "array" begin - @test pmapsum(x->ones(2),1:nworkers()) == ones(2).*nworkers() - end - - @testset "stepped iterator" begin - rng = 1:5:100 - @test pmapsum(x->sum(y[1] for y in x),rng) == sum(rng) - end - - @testset "two iterators" begin - iters = (1:100,1:2) - @test pmapsum(x->sum(y[1] for y in x),iters) == sum(iters[1])*length(iters[2]) - end - - @testsetwithinfo "run elsewhere" begin - res_exp = sum(workers()) - for p in workers() - res = @fetchfrom p pmapsum(x->myid(),1:nworkers()) - @test res == res_exp - end - end - - @testset "errors" begin - @test_throws exceptiontype pmapsum(x->error("map"),1:10) - @test_throws exceptiontype pmapsum(x->fmap(x),1:10) - end - end - - @testsetwithinfo "elementwise" begin - @testset "comparison with map" begin - iterable = 1:100 - @testset "without progress" begin - res = pmapsum_elementwise(identity,iterable) - @test res == sum(iterable) - end - @testset "with progress" begin - res = pmapsum_elementwise(identity,iterable,showprogress=true) - @test res == sum(iterable) - end - res = pmapsum_elementwise(identity,(iterable,)) - @test res == sum(iterable) - res = pmapsum_elementwise(identity,Int,iterable) - @test res == sum(iterable) - res = pmapsum_elementwise(identity,Int,(iterable,)) - @test res == sum(iterable) - - iterable = 1:100 - res = pmapsum_elementwise(x->x^2,iterable) - @test res == sum(x->x^2,iterable) - @test res == pmapsum(plist->sum(x[1]^2 for x in plist),iterable) - end - - @testset "run elsewhere" begin - iterable = 1:100 - res_exp = sum(iterable) - for p in workers() - res = @fetchfrom p pmapsum_elementwise(identity,iterable) - @test res == res_exp - end - end - - @testset "errors" begin - @test_throws exceptiontype pmapsum_elementwise(x->error("hi"),1:10) - end - end - - @testset "type coercion" begin - @test_throws exceptiontype pmapsum(x->[1.1],Vector{Int},1:nworkers()) - @test pmapsum(x->ones(2).*myid(),Vector{Int},1:nworkers()) isa Vector{Int} - end - end; - - @testsetwithinfo "pmapreduce_commutative" begin - @testsetwithinfo "batch" begin - @testset "sum" begin - res_exp = sum(workers()) - @testset "without progress" begin - res = pmapreduce_commutative(x->myid(),Int,sum,Int,1:nworkers()) - @test res == res_exp - res = pmapreduce_commutative(x->myid(),sum,1:nworkers()) - @test res == res_exp - end - @testset "with progress" begin - res = pmapreduce_commutative(x->myid(),Int,sum,Int,1:nworkers(),showprogress=true) - @test res == res_exp - res = pmapreduce_commutative(x->myid(),sum,1:nworkers(),showprogress=true) - @test res == res_exp - end - @test pmapreduce_commutative(x->myid(),Int,sum,Int,(1:nworkers(),)) == res_exp - @test pmapreduce_commutative(x->myid(),sum,(1:nworkers(),)) == res_exp - @test pmapreduce_commutative(x->myid(),Int,sum,Int,(1:nworkers(),1:1)) == res_exp - @test pmapreduce_commutative(x->myid(),sum,(1:nworkers(),1:1)) == res_exp - @test pmapreduce_commutative(x->myid(),sum,1:nworkers()) == pmapsum(x->myid(),1:nworkers()) - end - @testset "prod" begin - @test pmapreduce_commutative(x->myid(),prod,1:nworkers()) == prod(workers()) - @test pmapreduce_commutative(x->myid(),prod,(1:nworkers(),)) == prod(workers()) - @test pmapreduce_commutative(x->myid(),prod,(1:nworkers(),1:1)) == prod(workers()) - end - - @testsetwithinfo "run elsewhere" begin - res_exp = prod(workers()) - for p in workers() - res = @fetchfrom p pmapreduce_commutative(x->myid(),prod,1:nworkers()) - @test res == res_exp - end - end - - @testset "errors" begin - @test_throws exceptiontype pmapreduce_commutative( - x->error("map"),sum,1:10) - @test_throws exceptiontype pmapreduce_commutative( - identity,x->error("reduce"),1:10) - @test_throws exceptiontype pmapreduce_commutative( - x->error("map"),x->error("reduce"),1:10) - - @test_throws exceptiontype pmapreduce_commutative( - x->fmap("map"),sum,1:10) - @test_throws exceptiontype pmapreduce_commutative( - x->1,x->fred(x),1:10) - @test_throws exceptiontype pmapreduce_commutative( - x->fmap(x),x->fred(x),1:10) - end - - @testset "type coercion" begin - @test_throws exceptiontype pmapreduce_commutative(x->[1.1],Vector{Int}, - sum,Vector{Int},1:nworkers()) - res = pmapreduce_commutative(x->ones(2).*myid(),Vector{Int},sum,Vector{Int},1:nworkers()) - @test res isa Vector{Int} - end - end; - - @testsetwithinfo "elementwise" begin - @testsetwithinfo "comparison with map" begin - iter = 1:1000 - res_exp = sum(x->x^2,iter) - @testset "without progress" begin - res = pmapreduce_commutative_elementwise(x->x^2,sum,iter) - @test res == res_exp - end - @testset "with progress" begin - res = pmapreduce_commutative_elementwise(x->x^2,sum,iter,showprogress=true) - @test res == res_exp - end - @test res == pmapsum_elementwise(x->x^2,iter) - @test res == pmapsum(plist->sum(x[1]^2 for x in plist),iter) - res = pmapreduce_commutative_elementwise(x->x^2,sum,(iter,)) - @test res == res_exp - res = pmapreduce_commutative_elementwise(x->x^2,Int,sum,Int,(iter,)) - @test res == res_exp - res = pmapreduce_commutative_elementwise(x->x^2,Int,sum,Int,iter) - @test res == res_exp - res = pmapreduce_commutative_elementwise(x->x^2,Int,x->float(sum(x)),Float64,iter) - @test res == float(res_exp) - end - - @testsetwithinfo "run elsewhere" begin - iter = 1:1000 - res_exp = sum(x->x^2,iter) - for p in workers() - res = @fetchfrom p pmapreduce_commutative_elementwise(x->x^2,sum,iter) - @test res == res_exp - end - end - - @testsetwithinfo "errors" begin - @test_throws exceptiontype pmapreduce_commutative_elementwise( - x->error("map"),sum,1:10) - @test_throws exceptiontype pmapreduce_commutative_elementwise( - identity,x->error("reduce"),1:10) - @test_throws exceptiontype pmapreduce_commutative_elementwise( - x->error("map"), - x->error("reduce"),1:10) - end - end; - end; - - @testsetwithinfo "pmapreduce" begin - @testsetwithinfo "batch" begin - @testset "sum" begin - res_exp = sum(workers()) - @testset "without progress" begin - @test pmapreduce(x->myid(),Int,sum,Int,1:nworkers()) == res_exp - @test pmapreduce(x->myid(),sum,1:nworkers()) == res_exp - end - @testset "without progress" begin - res = pmapreduce(x->myid(),Int,sum,Int,1:nworkers(),showprogress=true) - @test res == res_exp - res = pmapreduce(x->myid(),sum,1:nworkers(),showprogress=true) - @test res == res_exp - end - @test pmapreduce(x->myid(),Int,sum,Int,(1:nworkers(),)) == res_exp - @test pmapreduce(x->myid(),sum,(1:nworkers(),)) == res_exp - @test pmapreduce(x->myid(),Int,sum,Int,(1:nworkers(),1:1)) == res_exp - @test pmapreduce(x->myid(),sum,(1:nworkers(),1:1)) == res_exp - - @testset "comparison with pmapsum" begin - res_exp = pmapsum(x->myid(),1:nworkers()) - @test pmapreduce(x->myid(),Int,sum,Int,1:nworkers()) == res_exp - @test pmapreduce(x->myid(),sum,1:nworkers()) == res_exp - end - end - - @testset "concatenation" begin - res_vcat = ones(2*nworkers()) - res_hcat = ones(2,nworkers()) - @test pmapreduce(x->ones(2),Vector{Float64}, - x->vcat(x...),Vector{Float64},1:nworkers()) == res_vcat - @test pmapreduce(x->ones(2),x->vcat(x...),1:nworkers()) == res_vcat - @test pmapreduce(x->ones(2),Vector{Float64}, - x->hcat(x...),Matrix{Float64},1:nworkers()) == res_hcat - @test pmapreduce(x->ones(2),x->hcat(x...),1:nworkers()) == res_hcat - - @testset "sorting" begin - @test pmapreduce(x->ones(2)*x[1][1],x->vcat(x...),1:nworkers()) == - vcat((ones(2).*i for i=1:nworkers())...) - - @test pmapreduce(x->x[1][1],x->vcat(x...),1:nworkers()) == collect(1:nworkers()) - @test pmapreduce(x->myid(),Int,x->vcat(x...),Vector{Int},(1:nworkers(),)) == workers() - @test pmapreduce(x->myid(),x->vcat(x...),1:nworkers()) == workers() - end - end - - @testsetwithinfo "run elsewhere" begin - res_exp = sum(workers()) - for p in workers() - res = @fetchfrom p pmapreduce(x->myid(),sum,1:nworkers()) - @test res == res_exp - end - # concatenation where the rank is used in the mapping function - # Preserves order of the iterators - res_exp = collect(1:nworkers()) - for p in workers() - res = @fetchfrom p pmapreduce(x->x[1][1],x->vcat(x...),1:nworkers()) - @test res == res_exp - end - # concatenation where the rank is ignored in the mapping function - # Preserves order of workers on the remote node where pmapreduce is called - for p in workers() - res = @fetchfrom p pmapreduce(x->myid(),x->vcat(x...),1:nworkers()) - res_exp = @fetchfrom p workers() - @test res == res_exp - end - end - - @testset "errors" begin - @test_throws exceptiontype pmapreduce(x->error("map"),sum,1:10) - @test_throws exceptiontype pmapreduce(identity,x->error("reduce"),1:10) - @test_throws exceptiontype pmapreduce(x->error("map"),x->error("reduce"),1:10) - @test_throws exceptiontype pmapreduce(x->fmap(x),sum,1:10) - @test_throws exceptiontype pmapreduce(x->1,x->fred(x),1:10) - @test_throws exceptiontype pmapreduce(x->fmap(x),x->fred(x),1:10) - end - - @testset "type coercion" begin - @test_throws exceptiontype pmapreduce(x->[1.1],Vector{Int},sum,Vector{Int},1:nworkers()) - @test pmapreduce(x->ones(2).*myid(),Vector{Int},sum,Vector{Int},1:nworkers()) isa Vector{Int} - end - end - end; -end; - -@testset "show" begin - - @testset "error" begin - io = IOBuffer() - - showerror(io,ParallelUtilities.ProcessorNumberError(5,2)) - strexp = "processor id 5 does not line in the range 1:2" - @test String(take!(io)) == strexp - - showerror(io,ParallelUtilities.DecreasingIteratorError()) - strexp = "all the iterators need to be strictly increasing" - @test String(take!(io)) == strexp - - showerror(io,ParallelUtilities.TaskNotPresentError((1:4,),(5,))) - strexp = "could not find the task $((5,)) in the list $((1:4,))" - @test String(take!(io)) == strexp - end - - @testset "BranchChannel" begin - io = IOBuffer() - - b = BranchChannel{Any,Any}(1,0) - show(io,b) - strexp = "Leaf : 1 ← 1" - @test String(take!(io)) == strexp - - b = BranchChannel{Any,Any}(1,1) - show(io,b) - strexp = "Branch: 1 ← 1 ← 1 child" - @test String(take!(io)) == strexp - - b = BranchChannel{Any,Any}(1,2) - show(io,b) - strexp = "Branch: 1 ← 1 ⇇ 2 children" - @test String(take!(io)) == strexp - end -end; - -end; # wrapper - -rmprocs(workers()) +include("singlehost.jl") \ No newline at end of file diff --git a/test/singlehost.jl b/test/singlehost.jl new file mode 100644 index 0000000..30dd03f --- /dev/null +++ b/test/singlehost.jl @@ -0,0 +1,8 @@ +using Distributed + +const workersused = 8 +addprocs(workersused) + +include("tests.jl") + +rmprocs(workers()) diff --git a/test/tests.jl b/test/tests.jl new file mode 100644 index 0000000..68b2459 --- /dev/null +++ b/test/tests.jl @@ -0,0 +1,2250 @@ +using DataStructures +using Test + +@everywhere begin + using ParallelUtilities + import ParallelUtilities: BinaryTreeNode, RemoteChannelContainer, BranchChannel, + Sorted, Unsorted, Ordering, pval, value, reducedvalue, reduceTreeNode, mapTreeNode, + SequentialBinaryTree, OrderedBinaryTree, SegmentedSequentialBinaryTree, + SegmentedOrderedBinaryTree, + parentnoderank, nchildren, + maybepvalput!, createbranchchannels, nworkersactive, workersactive, + procs_node, leafrankfoldedtree +end + +macro testsetwithinfo(str,ex) + quote + @info "Testing "*$str + @testset $str begin $(esc(ex)); end; + end +end + +function showworkernumber(ind,nw) + # Cursor starts off at the beginning of the line + print("\u1b[K") # clear till end of line + print("Testing on worker $ind of $nw") + # return the cursor to the beginning of the line + endchar = ind == nw ? "\n" : "\r" + print(endchar) +end + +@testsetwithinfo "ProductSplit" begin + + various_iters = [(1:10,),(1:10,4:6),(1:10,4:6,1:4),(1:2:10,4:1:6), + (1:2,Base.OneTo(4),1:3:10)] + + function split_across_processors_iterators(arr::Iterators.ProductIterator,num_procs,proc_id) + + num_tasks = length(arr); + + num_tasks_per_process,num_tasks_leftover = divrem(num_tasks,num_procs) + + num_tasks_on_proc = num_tasks_per_process + (proc_id <= mod(num_tasks,num_procs) ? 1 : 0 ); + task_start = num_tasks_per_process*(proc_id-1) + min(num_tasks_leftover,proc_id-1) + 1; + + Iterators.take(Iterators.drop(arr,task_start-1),num_tasks_on_proc) + end + + function split_product_across_processors_iterators(arrs_tuple,num_procs,proc_id) + split_across_processors_iterators(Iterators.product(arrs_tuple...),num_procs,proc_id) + end + + @testset "Constructor" begin + + function checkPSconstructor(iters,npmax=10) + ntasks_total = prod(length.(iters)) + for np = 1:npmax, p = 1:np + ps = ProductSplit(iters,np,p) + @test eltype(ps) == Tuple{eltype.(iters)...} + @test collect(ps) == collect(split_product_across_processors_iterators(iters,np,p)) + @test ntasks(ps) == ntasks_total + @test ntasks(ps.iterators) == ntasks_total + @test eltype(ps) == Tuple{map(eltype,iters)...} + end + + @test_throws ParallelUtilities.ProcessorNumberError ProductSplit(iters,npmax,npmax+1) + end + + @testset "0D" begin + @test_throws ArgumentError ProductSplit((),2,1) + end + + @testset "cumprod" begin + @test ParallelUtilities._cumprod(1,()) == () + @test ParallelUtilities._cumprod(1,(2,)) == (1,) + @test ParallelUtilities._cumprod(1,(2,3)) == (1,2) + @test ParallelUtilities._cumprod(1,(2,3,4)) == (1,2,6) + end + + @testset "1D" begin + iters = (1:10,) + checkPSconstructor(iters) + end + @testset "2D" begin + iters = (1:10,4:6) + checkPSconstructor(iters) + end + @testset "3D" begin + iters = (1:10,4:6,1:4) + checkPSconstructor(iters) + end + @testset "steps" begin + iters = (1:2:10,4:1:6) + checkPSconstructor(iters) + iters = (10:-1:10,6:-2:0) + @test_throws ParallelUtilities.DecreasingIteratorError ProductSplit(iters,3,2) + end + @testset "mixed" begin + for iters in [(1:2,4:2:6),(1:2,Base.OneTo(4),1:3:10)] + checkPSconstructor(iters) + end + end + + @testset "empty" begin + iters = (1:1,) + ps = ProductSplit(iters,10,2) + @test isempty(ps) + @test length(ps) == 0 + end + + @testset "first and last ind" begin + for iters in [(1:10,),(1:2,Base.OneTo(4),1:3:10)] + ps = ProductSplit(iters,2,1) + @test firstindex(ps) == 1 + @test ps.firstind == 1 + @test ps.lastind == div(ntasks(iters),2) + @test lastindex(ps) == div(ntasks(iters),2) + @test lastindex(ps) == length(ps) + ps = ProductSplit(iters,2,2) + @test ps.firstind == div(ntasks(iters),2) + 1 + @test firstindex(ps) == 1 + @test ps.lastind == ntasks(iters) + @test lastindex(ps) == length(ps) + + for np in ntasks(iters)+1:ntasks(iters)+10, + p in ntasks(iters)+1:np + + ps = ProductSplit(iters,np,p) + @test ps.firstind == ntasks(iters) + 1 + @test ps.lastind == ntasks(iters) + end + end + end + + @testset "summary" begin + ps = ProductSplit((1:3, 4:5:19),3,2) + reprstr = "ProductSplit("*repr((1:3, 4:5:19))*",3,2)" + @test ParallelUtilities.mwerepr(ps) == reprstr + + summarystr = "$(length(ps))-element "*reprstr + @test ParallelUtilities.summary(ps) == summarystr + + io = IOBuffer() + summary(io,ps) + @test String(take!(io)) == summarystr + end + end + + @testset "firstlast" begin + @testset "first" begin + + @test ParallelUtilities._first(()) == () + + for iters in various_iters,np=1:5ntasks(iters) + + ps = ProductSplit(iters,np,1) + @test first(ps) == ( isempty(ps) ? nothing : map(first,iters) ) + end + + iters = (1:1,) + ps = ProductSplit(iters,2ntasks(iters),ntasks(iters)+1) # must be empty + @test first(ps) === nothing + end + @testset "last" begin + + @test ParallelUtilities._last(()) == () + + for iters in various_iters,np=1:5ntasks(iters) + + ps = ProductSplit(iters,np,np) + @test last(ps) == ( isempty(ps) ? nothing : map(last,iters) ) + end + + iters = (1:1,) + ps = ProductSplit(iters,2length(iters[1]),length(iters[1])+1) # must be empty + @test last(ps) === nothing + end + end + + @testset "extrema" begin + + @testset "min max extrema" begin + function checkPSextrema(iters,fn::Function,npmax=10) + for np = 1:npmax, p = 1:np + ps = ProductSplit(iters,np,p) + pcol = collect(ps) + for dim in 1:length(iters) + @test begin + res = fn(ps,dim=dim) == fn(x[dim] for x in pcol) + if !res + println(summary(ps)) + end + res + end + end + end + end + + for iters in various_iters, fn in [maximum,minimum,extrema] + checkPSextrema(iters,fn) + end + + @test minimum(ProductSplit((1:5,),2,1)) == 1 + @test maximum(ProductSplit((1:5,),2,1)) == 3 + @test extrema(ProductSplit((1:5,),2,1)) == (1,3) + + @test minimum(ProductSplit((1:5,),2,2)) == 4 + @test maximum(ProductSplit((1:5,),2,2)) == 5 + @test extrema(ProductSplit((1:5,),2,2)) == (4,5) + end + + @testset "extremadims" begin + ps = ProductSplit((1:10,),2,1) + @test ParallelUtilities._extremadims(ps,1,()) == () + for iters in various_iters + + dims = length(iters) + for np = 1:5ntasks(iters), proc_id = 1:np + ps = ProductSplit(iters,np,proc_id) + if isempty(ps) + @test extremadims(ps) == Tuple(nothing for i=1:dims) + else + ext = Tuple(map(extrema,zip(collect(ps)...))) + @test extremadims(ps) == ext + end + end + end + end + + @testset "extrema_commonlastdim" begin + iters = (1:10,4:6,1:4) + ps = ProductSplit(iters,37,8) + @test extrema_commonlastdim(ps) == ([(9,1),(6,1)],[(2,2),(4,2)]) + ps = ProductSplit(iters,ntasks(iters)+1,ntasks(iters)+1) + @test extrema_commonlastdim(ps) === nothing + end + end + + @testset "in" begin + + function checkifpresent(iters,npmax=10) + for np = 1:npmax, p = 1:np + ps = ProductSplit(iters,np,p) + pcol = collect(ps) + + for el in pcol + # It should be contained in this iterator + @test el in ps + for p2 in 1:np + # It should not be contained anywhere else + p2 == p && continue + ps2 = ProductSplit(iters,np,p2) + @test !(el in ps2) + end + end + end + end + + for iters in various_iters + checkifpresent(iters) + end + + @test ParallelUtilities._infullrange((),()) + end + + @testset "whichproc + procrange_recast" begin + np,proc_id = 5,5 + iters = (1:10,4:6,1:4) + ps = ProductSplit(iters,np,proc_id) + @test whichproc(iters,first(ps),1) == 1 + @test whichproc(iters,(100,100,100),1) === nothing + @test procrange_recast(iters,ps,1) == 1:1 + @test procrange_recast(ps,1) == 1:1 + + smalleriter = (1:1,1:1,1:1) + err = ParallelUtilities.TaskNotPresentError(smalleriter,first(ps)) + @test_throws err procrange_recast(smalleriter,ps,1) + smalleriter = (7:9,4:6,1:4) + err = ParallelUtilities.TaskNotPresentError(smalleriter,last(ps)) + @test_throws err procrange_recast(smalleriter,ps,1) + + iters = (1:1,2:2) + ps = ProductSplit(iters,np,proc_id) + @test whichproc(iters,first(ps),np) === nothing + @test whichproc(iters,nothing,np) === nothing + @test procrange_recast(iters,ps,2) == (0:-1) + @test procrange_recast(ps,2) == (0:-1) + + iters = (1:1,2:2) + ps = ProductSplit(iters,1,1) + @test procrange_recast(iters,ps,2) == 1:1 + @test procrange_recast(ps,2) == 1:1 + + iters = (Base.OneTo(2),2:4) + ps = ProductSplit(iters,2,1) + @test procrange_recast(iters,ps,1) == 1:1 + @test procrange_recast(iters,ps,2) == 1:1 + @test procrange_recast(iters,ps,ntasks(iters)) == 1:length(ps) + + for np_new in 1:5ntasks(iters) + for proc_id_new=1:np_new + ps_new = ProductSplit(iters,np_new,proc_id_new) + + for val in ps_new + # Should loop only if ps_new is non-empty + @test whichproc(iters,val,np_new) == proc_id_new + end + end + procid_new_first = whichproc(iters,first(ps),np_new) + proc_new_last = whichproc(iters,last(ps),np_new) + @test procrange_recast(iters,ps,np_new) == (isempty(ps) ? (0:-1) : (procid_new_first:proc_new_last)) + @test procrange_recast(ps,np_new) == (isempty(ps) ? (0:-1) : (procid_new_first:proc_new_last)) + end + + @testset "different set" begin + iters = (1:100,1:4000) + ps = ProductSplit((20:30,1:1),2,1) + @test procrange_recast(iters,ps,700) == 1:1 + ps = ProductSplit((20:30,1:1),2,2) + @test procrange_recast(iters,ps,700) == 1:1 + + iters = (1:1,2:2) + ps = ProductSplit((20:30,2:2),2,1) + @test_throws ParallelUtilities.TaskNotPresentError procrange_recast(iters,ps,3) + ps = ProductSplit((1:30,2:2),2,1) + @test_throws ParallelUtilities.TaskNotPresentError procrange_recast(iters,ps,3) + end + end + + @testset "localindex" begin + + for iters in various_iters + for np=1:5ntasks(iters),proc_id=1:np + ps = ProductSplit(iters,np,proc_id) + for (ind,val) in enumerate(ps) + @test localindex(ps,val) == ind + @test localindex(iters,val,np,proc_id) == ind + end + if isempty(ps) + @test localindex(ps,first(ps)) === nothing + end + end + end + end + + @testset "whichproc_localindex" begin + for iters in various_iters + for np=1:ntasks(iters),proc_id=1:np + ps_col = collect(ProductSplit(iters,np,proc_id)) + ps_col_rev = [reverse(t) for t in ps_col] + for val in ps_col + p,ind = whichproc_localindex(iters,val,np) + @test p == proc_id + ind_in_arr = searchsortedfirst(ps_col_rev,reverse(val)) + @test ind == ind_in_arr + end + end + end + end + + @testset "getindex" begin + + @test ParallelUtilities._getindex((),1) == () + @test ParallelUtilities._getindex((),1,2) == () + + @test ParallelUtilities.childindex((),1) == (1,) + + for iters in various_iters + for np=1:ntasks(iters),p=1:np + ps = ProductSplit(iters,np,p) + ps_col = collect(ps) + for i in 1:length(ps) + @test ps[i] == ps_col[i] + end + @test ps[end] == ps[length(ps)] + for ind in [0,length(ps)+1] + @test_throws ParallelUtilities.BoundsError(ps,ind) ps[ind] + end + end + end + end +end; + +@testset "ReverseLexicographicTuple" begin + @testset "isless" begin + a = ParallelUtilities.ReverseLexicographicTuple((1,2,3)) + b = ParallelUtilities.ReverseLexicographicTuple((2,2,3)) + @test a < b + @test a <= b + b = ParallelUtilities.ReverseLexicographicTuple((1,1,3)) + @test b < a + @test b <= a + b = ParallelUtilities.ReverseLexicographicTuple((2,1,3)) + @test b < a + @test b <= a + b = ParallelUtilities.ReverseLexicographicTuple((2,1,4)) + @test a < b + @test a <= b + end + @testset "equal" begin + a = ParallelUtilities.ReverseLexicographicTuple((1,2,3)) + @test a == a + @test isequal(a,a) + @test a <= a + b = ParallelUtilities.ReverseLexicographicTuple(a.t) + @test a == b + @test isequal(a,b) + @test a <= b + end +end; + +@testset "utilities" begin + @testset "workers active" begin + @test nworkersactive((1:1,)) == 1 + @test nworkersactive((1:2,)) == min(2,nworkers()) + @test nworkersactive((1:1,1:2)) == min(2,nworkers()) + @test nworkersactive(1:2) == min(2,nworkers()) + @test nworkersactive(1:1,1:2) == min(2,nworkers()) + @test nworkersactive((1:nworkers()+1,)) == nworkers() + @test nworkersactive(1:nworkers()+1) == nworkers() + @test workersactive((1:1,)) == workers()[1:1] + @test workersactive(1:1) == workers()[1:1] + @test workersactive(1:1,1:1) == workers()[1:1] + @test workersactive((1:2,)) == workers()[1:min(2,nworkers())] + @test workersactive((1:1,1:2)) == workers()[1:min(2,nworkers())] + @test workersactive(1:1,1:2) == workers()[1:min(2,nworkers())] + @test workersactive((1:nworkers()+1,)) == workers() + @test workersactive(1:nworkers()+1) == workers() + + ps = ProductSplit((1:10,),nworkers(),1) + @test nworkersactive(ps) == min(10,nworkers()) + + iters = (1:1,1:2) + ps = ProductSplit(iters,2,1) + @test nworkersactive(ps) == nworkersactive(iters) + @test workersactive(ps) == workersactive(iters) + end + + @testset "hostnames" begin + hostnames = gethostnames() + nodes = unique(hostnames) + @test hostnames == [@fetchfrom p Libc.gethostname() for p in workers()] + @test nodenames() == nodes + @test nodenames(hostnames) == nodes + np1 = nprocs_node(hostnames,nodes) + np2 = nprocs_node(hostnames) + np3 = nprocs_node() + @test np1 == np2 == np3 + for node in nodes + npnode = count(isequal(node),hostnames) + @test np1[node] == npnode + end + p1 = procs_node() + p2 = procs_node(workers(),hostnames,nodes) + @test p1 == p2 + for node in nodes + pnode = workers()[findall(isequal(node),hostnames)] + @test p1[node] == pnode + end + np4 = nprocs_node(p1) + @test np1 == np4 + end +end; + +@testset "BinaryTree" begin + @testsetwithinfo "BinaryTreeNode" begin + @testset "Constructor" begin + p = workers()[1] + b = BinaryTreeNode(p,p,0) + @test nchildren(b) == 0 + b = BinaryTreeNode(p,p,1) + @test nchildren(b) == 1 + b = BinaryTreeNode(p,p,2) + @test nchildren(b) == 2 + + @test_throws DomainError BinaryTreeNode(p,p,3) + @test_throws DomainError BinaryTreeNode(p,p,-1) + end + end + + @testsetwithinfo "BinaryTree" begin + @testsetwithinfo "SequentialBinaryTree" begin + @testset "pid and parent" begin + for imax = 1:100 + procs = 1:imax + tree = SequentialBinaryTree(procs) + @test length(tree) == length(procs) + topnoderank = ParallelUtilities.topnoderank(tree) + @test topnoderank == 1 + @test tree[topnoderank] == ParallelUtilities.topnode(tree) + @test tree[1].parent == 1 + for rank in 1:length(tree) + node = tree[rank] + @test node.p == procs[rank] + @test node.parent == procs[parentnoderank(tree,rank)] + end + + for ind in [0,imax+1] + @test_throws BoundsError(tree,ind) parentnoderank(tree,ind) + @test_throws BoundsError(tree,ind) tree[ind] + end + end + end + + @testset "nchildren" begin + tree = SequentialBinaryTree(1:1) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,2) nchildren(tree,2) + + tree = SequentialBinaryTree(1:2) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 1 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,3) nchildren(tree,3) + + tree = SequentialBinaryTree(1:8) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 2 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 + @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 2 + @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 1 + @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 + @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 0 + @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 + @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,9) nchildren(tree,9) + end + + @testset "level" begin + tree = SequentialBinaryTree(1:15) + @test ParallelUtilities.levels(tree) == 4 + + @test ParallelUtilities.levelfromtop(tree,1) == 1 + @test ParallelUtilities.levelfromtop.((tree,),2:3) == ones(Int,2)*2 + @test ParallelUtilities.levelfromtop.((tree,),4:7) == ones(Int,4)*3 + @test ParallelUtilities.levelfromtop.((tree,),8:15) == ones(Int,8)*4 + + for p in [0,length(tree)+1] + @test_throws BoundsError(tree,p) ParallelUtilities.levelfromtop(tree,p) + end + end + + @testset "summary" begin + tree = SequentialBinaryTree(1:4) + io = IOBuffer() + summary(io,tree) + strexp = "$(length(tree))-node $(typeof(tree))" + @test String(take!(io)) == strexp + @test summary(tree) == strexp + end + end + + @testsetwithinfo "OrderedBinaryTree" begin + @testset "pid and parent" begin + for imax = 1:100 + procs = 1:imax + tree = OrderedBinaryTree(procs) + @test length(tree) == length(procs) + + topnoderank = ParallelUtilities.topnoderank(tree) + @test tree[topnoderank].parent == topnoderank + for rank in 1:length(tree) + node = tree[rank] + @test node.p == procs[rank] + @test node.parent == procs[parentnoderank(tree,rank)] + end + @test_throws BoundsError(tree,0) parentnoderank(tree,0) + @test_throws BoundsError(tree,imax+1) parentnoderank(tree,imax+1) + end + end + + @testset "nchildren" begin + tree = OrderedBinaryTree(1:1) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,2) nchildren(tree,2) + @test ParallelUtilities.topnoderank(tree) == 1 + + tree = OrderedBinaryTree(1:2) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 1 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,3) nchildren(tree,3) + @test ParallelUtilities.topnoderank(tree) == 2 + + tree = OrderedBinaryTree(1:8) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 + @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 0 + @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 2 + @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 + @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 2 + @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 + @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 1 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,9) nchildren(tree,9) + @test ParallelUtilities.topnoderank(tree) == 8 + + tree = OrderedBinaryTree(1:11) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 + @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 0 + @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 2 + @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 + @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 2 + @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 + @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 2 + @test nchildren(tree,9) == nchildren(tree[9]) == tree[9].nchildren == 0 + @test nchildren(tree,10) == nchildren(tree[10]) == tree[10].nchildren == 2 + @test nchildren(tree,11) == nchildren(tree[11]) == tree[11].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,12) nchildren(tree,12) + @test ParallelUtilities.topnoderank(tree) == 8 + + tree = OrderedBinaryTree(1:13) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 + @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 0 + @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 2 + @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 + @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 2 + @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 + @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 2 + @test nchildren(tree,9) == nchildren(tree[9]) == tree[9].nchildren == 0 + @test nchildren(tree,10) == nchildren(tree[10]) == tree[10].nchildren == 2 + @test nchildren(tree,11) == nchildren(tree[11]) == tree[11].nchildren == 0 + @test nchildren(tree,12) == nchildren(tree[12]) == tree[12].nchildren == 2 + @test nchildren(tree,13) == nchildren(tree[13]) == tree[13].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,14) nchildren(tree,14) + @test ParallelUtilities.topnoderank(tree) == 8 + end + + @testset "level" begin + tree = OrderedBinaryTree(1:15) + @test ParallelUtilities.levels(tree) == 4 + + @test ParallelUtilities.levelfromtop.((tree,),1:2:15) == ones(Int,8).*4 + @test ParallelUtilities.levelfromtop.((tree,),(2,6,10,14)) == (3,3,3,3) + @test ParallelUtilities.levelfromtop.((tree,),(4,12)) == (2,2) + @test ParallelUtilities.levelfromtop(tree,8) == 1 + for p in [0,length(tree)+1] + @test_throws BoundsError(tree,p) ParallelUtilities.levelfromtop(tree,p) + end + + tree = OrderedBinaryTree(1:13) + @test ParallelUtilities.levels(tree) == 4 + @test ParallelUtilities.levelfromtop.((tree,),1:2:11) == ones(Int,6).*4 + @test ParallelUtilities.levelfromtop.((tree,),(2,6,10,13)) == (3,3,3,3) + @test ParallelUtilities.levelfromtop.((tree,),(4,12)) == (2,2) + @test ParallelUtilities.levelfromtop(tree,8) == 1 + for p in [0,length(tree)+1] + @test_throws BoundsError(tree,p) ParallelUtilities.levelfromtop(tree,p) + end + end + end + + @testsetwithinfo "SegmentedSequentialBinaryTree" begin + @testsetwithinfo "single host" begin + @testset "pid and parent" begin + for imax = 1:100 + procs = 1:imax + workersonhosts = Dict("host"=>procs) + tree = SegmentedSequentialBinaryTree(procs,workersonhosts) + SBT = SequentialBinaryTree(procs) + @test length(tree) == length(procs) == length(SBT) + + topnoderank = ParallelUtilities.topnoderank(tree) + @test topnoderank == 1 + @test tree[topnoderank] == ParallelUtilities.topnode(tree) + @test tree[1].parent == 1 + for rank in 1:length(tree) + node = tree[rank] + parentnode = tree[parentnoderank(tree,rank)] + @test length(procs) > 1 ? nchildren(parentnode) > 0 : nchildren(parentnode) == 0 + @test node.p == procs[rank] + @test node.parent == procs[parentnoderank(SBT,rank)] + @test parentnode.p == node.parent + end + end + end; + + @testset "nchildren" begin + procs = 1:1 + tree = SegmentedSequentialBinaryTree(procs,Dict("host"=>procs)) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,2) nchildren(tree,2) + + procs = 1:2 + tree = SegmentedSequentialBinaryTree(procs,Dict("host"=>procs)) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 1 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,3) nchildren(tree,3) + + procs = 1:8 + tree = SegmentedSequentialBinaryTree(procs,Dict("host"=>procs)) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 2 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 + @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 2 + @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 1 + @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 + @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 0 + @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 + @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,9) nchildren(tree,9) + end; + end; + + @testsetwithinfo "multiple hosts" begin + @testset "length" begin + procs = 1:2 + tree = SegmentedSequentialBinaryTree(procs, + OrderedDict("host1"=>1:1,"host2"=>2:2)) + @test length(tree) == 2 + 1 + + procs = 1:4 + tree = SegmentedSequentialBinaryTree(procs, + OrderedDict("host1"=>1:2,"host2"=>3:4)) + + @test length(tree) == 4 + 1 + + procs = 1:12 + tree = SegmentedSequentialBinaryTree(procs, + OrderedDict( + "host1"=>1:3,"host2"=>4:6, + "host3"=>7:9,"host4"=>10:12)) + + @test length(tree) == 12 + 3 + end; + + @testset "leafrankfoldedtree" begin + treeflag = SequentialBinaryTree(1:1) + @test leafrankfoldedtree(treeflag,5,1) == 8 + @test leafrankfoldedtree(treeflag,5,2) == 9 + @test leafrankfoldedtree(treeflag,5,3) == 5 + @test leafrankfoldedtree(treeflag,5,4) == 6 + @test leafrankfoldedtree(treeflag,5,5) == 7 + end; + + @testset "pid and parent" begin + for imax = 2:100 + procs = 1:imax + mid = div(imax,2) + workersonhosts = OrderedDict{String,Vector{Int}}() + workersonhosts["host1"] = procs[1:mid] + workersonhosts["host2"] = procs[mid+1:end] + tree = SegmentedSequentialBinaryTree(procs,workersonhosts) + + topnoderank = ParallelUtilities.topnoderank(tree) + @test topnoderank == 1 + @test tree[topnoderank] == ParallelUtilities.topnode(tree) + @test tree[1].parent == 1 + @test parentnoderank(tree,1) == 1 + for (ind,rank) in enumerate(1:mid) + node = tree[rank+1] + parentnode = tree[parentnoderank(tree,rank+1)] + @test nchildren(parentnode) > 0 + @test parentnode.p == node.parent + pnodes = workersonhosts["host1"] + @test node.p == pnodes[ind] + SBT = SequentialBinaryTree(pnodes) + if ind == 1 + @test node.parent == 1 + else + @test node.parent == pnodes[parentnoderank(SBT,ind)] + end + end + for (ind,rank) in enumerate(mid+1:imax) + node = tree[rank+1] + parentnode = tree[parentnoderank(tree,rank+1)] + @test nchildren(parentnode) > 0 + @test parentnode.p == node.parent + pnodes = workersonhosts["host2"] + @test node.p == pnodes[ind] + SBT = SequentialBinaryTree(pnodes) + if ind == 1 + @test node.parent == 1 + else + @test node.parent == pnodes[parentnoderank(SBT,ind)] + end + end + end + end; + + @testset "nchildren" begin + procs = 1:2 + tree = SegmentedSequentialBinaryTree(procs, + OrderedDict("host1"=>1:1,"host2"=>2:2)) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 2 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 0 + @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,4) nchildren(tree,4) + + procs = 1:12 + tree = SegmentedSequentialBinaryTree(procs, + OrderedDict( + "host1"=>1:3,"host2"=>4:6, + "host3"=>7:9,"host4"=>10:12)) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 2 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 + @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 2 + @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 2 + @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 + @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 0 + @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 2 + @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 0 + @test nchildren(tree,9) == nchildren(tree[9]) == tree[9].nchildren == 0 + @test nchildren(tree,10) == nchildren(tree[10]) == tree[10].nchildren == 2 + @test nchildren(tree,11) == nchildren(tree[11]) == tree[11].nchildren == 0 + @test nchildren(tree,12) == nchildren(tree[12]) == tree[12].nchildren == 0 + @test nchildren(tree,13) == nchildren(tree[13]) == tree[13].nchildren == 2 + @test nchildren(tree,14) == nchildren(tree[14]) == tree[14].nchildren == 0 + @test nchildren(tree,15) == nchildren(tree[15]) == tree[15].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,16) nchildren(tree,16) + end; + end; + end + + @testsetwithinfo "SegmentedOrderedBinaryTree" begin + @testsetwithinfo "single host" begin + @testset "pid and parent" begin + for imax = 1:100 + procs = 1:imax + workersonhosts = Dict("host"=>procs) + tree = SegmentedOrderedBinaryTree(procs,workersonhosts) + treeOBT = OrderedBinaryTree(procs) + @test length(tree) == length(procs) == length(treeOBT) + + topnoderank = ParallelUtilities.topnoderank(tree) + # The top node is its own parent + @test tree[topnoderank].parent == topnoderank + @test tree[topnoderank] == ParallelUtilities.topnode(tree) + for rank in 1:length(tree) + node = tree[rank] + parentnode = tree[parentnoderank(tree,rank)] + @test length(procs) > 1 ? nchildren(parentnode) > 0 : nchildren(parentnode) == 0 + @test node.p == procs[rank] + @test node.parent == procs[parentnoderank(treeOBT,rank)] + @test parentnode.p == node.parent + end + end + end; + + @testset "nchildren" begin + procs = 1:1 + tree = SegmentedOrderedBinaryTree(procs,Dict("host"=>procs)) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,2) nchildren(tree,2) + @test ParallelUtilities.topnoderank(tree) == 1 + + procs = 1:2 + tree = SegmentedOrderedBinaryTree(procs,Dict("host"=>procs)) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 1 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,3) nchildren(tree,3) + @test ParallelUtilities.topnoderank(tree) == 2 + + procs = 1:8 + tree = SegmentedOrderedBinaryTree(procs,Dict("host"=>procs)) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 + @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 0 + @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 2 + @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 + @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 2 + @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 + @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 1 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,9) nchildren(tree,9) + @test ParallelUtilities.topnoderank(tree) == 8 + + procs = 1:11 + tree = SegmentedOrderedBinaryTree(procs,Dict("host"=>procs)) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 + @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 0 + @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 2 + @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 + @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 2 + @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 + @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 2 + @test nchildren(tree,9) == nchildren(tree[9]) == tree[9].nchildren == 0 + @test nchildren(tree,10) == nchildren(tree[10]) == tree[10].nchildren == 2 + @test nchildren(tree,11) == nchildren(tree[11]) == tree[11].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,12) nchildren(tree,12) + @test ParallelUtilities.topnoderank(tree) == 8 + + procs = 1:13 + tree = SegmentedOrderedBinaryTree(procs,Dict("host"=>procs)) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 0 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 + @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 0 + @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 2 + @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 0 + @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 2 + @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 + @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 2 + @test nchildren(tree,9) == nchildren(tree[9]) == tree[9].nchildren == 0 + @test nchildren(tree,10) == nchildren(tree[10]) == tree[10].nchildren == 2 + @test nchildren(tree,11) == nchildren(tree[11]) == tree[11].nchildren == 0 + @test nchildren(tree,12) == nchildren(tree[12]) == tree[12].nchildren == 2 + @test nchildren(tree,13) == nchildren(tree[13]) == tree[13].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,14) nchildren(tree,14) + @test ParallelUtilities.topnoderank(tree) == 8 + end; + end; + + @testsetwithinfo "multiple hosts" begin + @testset "length" begin + procs = 1:2 + tree = SegmentedOrderedBinaryTree(procs, + OrderedDict("host1"=>1:1,"host2"=>2:2)) + @test length(tree) == 2 + 1 + + procs = 1:4 + tree = SegmentedOrderedBinaryTree(procs, + OrderedDict("host1"=>1:2,"host2"=>3:4)) + + @test length(tree) == 4 + 1 + + procs = 1:12 + tree = SegmentedOrderedBinaryTree(procs, + OrderedDict( + "host1"=>1:3,"host2"=>4:6, + "host3"=>7:9,"host4"=>10:12)) + + @test length(tree) == 12 + 3 + end; + + @testset "leafrankfoldedtree" begin + treeflag = OrderedBinaryTree(1:1) + @test leafrankfoldedtree(treeflag,5,1) == 1 + @test leafrankfoldedtree(treeflag,5,2) == 3 + @test leafrankfoldedtree(treeflag,5,3) == 5 + @test leafrankfoldedtree(treeflag,5,4) == 7 + @test leafrankfoldedtree(treeflag,5,5) == 9 + end; + + @testset "pid and parent" begin + for imax = 2:100 + procs = 1:imax + mid = div(imax,2) + workersonhosts = OrderedDict{String,Vector{Int}}() + workersonhosts["host1"] = procs[1:mid] + workersonhosts["host2"] = procs[mid+1:end] + tree = SegmentedOrderedBinaryTree(procs,workersonhosts) + + top = ParallelUtilities.topnoderank(tree) + @test tree[top] == ParallelUtilities.topnode(tree) + for (ind,rank) in enumerate(1:mid) + node = tree[rank+1] + parentnode = tree[parentnoderank(tree,rank+1)] + @test parentnode.p == node.parent + pnodes = workersonhosts["host1"] + @test node.p == pnodes[ind] + OBT = OrderedBinaryTree(pnodes) + if ind == ParallelUtilities.topnoderank(OBT) + # Special check for 2 hosts as + # there's only one node in the top tree + @test node.parent == ParallelUtilities.topnode(tree.toptree).p + else + @test node.parent == pnodes[parentnoderank(OBT,ind)] + end + end + for (ind,rank) in enumerate(mid+1:imax) + node = tree[rank+1] + parentnode = tree[parentnoderank(tree,rank+1)] + @test parentnode.p == node.parent + pnodes = workersonhosts["host2"] + @test node.p == pnodes[ind] + OBT = OrderedBinaryTree(pnodes) + if ind == ParallelUtilities.topnoderank(OBT) + # Special check for 2 hosts as + # there's only one node in the top tree + @test node.parent == ParallelUtilities.topnode(tree.toptree).p + else + @test node.parent == pnodes[parentnoderank(OBT,ind)] + end + end + end + end; + + @testset "nchildren" begin + procs = 1:2 + tree = SegmentedOrderedBinaryTree(procs, + OrderedDict("host1"=>1:1,"host2"=>2:2)) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 2 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 0 + @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,4) nchildren(tree,4) + + procs = 1:12 + tree = SegmentedOrderedBinaryTree(procs, + OrderedDict( + "host1"=>1:3,"host2"=>4:6, + "host3"=>7:9,"host4"=>10:12)) + @test nchildren(tree,1) == nchildren(tree[1]) == tree[1].nchildren == 2 + @test nchildren(tree,2) == nchildren(tree[2]) == tree[2].nchildren == 2 + @test nchildren(tree,3) == nchildren(tree[3]) == tree[3].nchildren == 2 + @test nchildren(tree,4) == nchildren(tree[4]) == tree[4].nchildren == 0 + @test nchildren(tree,5) == nchildren(tree[5]) == tree[5].nchildren == 2 + @test nchildren(tree,6) == nchildren(tree[6]) == tree[6].nchildren == 0 + @test nchildren(tree,7) == nchildren(tree[7]) == tree[7].nchildren == 0 + @test nchildren(tree,8) == nchildren(tree[8]) == tree[8].nchildren == 2 + @test nchildren(tree,9) == nchildren(tree[9]) == tree[9].nchildren == 0 + @test nchildren(tree,10) == nchildren(tree[10]) == tree[10].nchildren == 0 + @test nchildren(tree,11) == nchildren(tree[11]) == tree[11].nchildren == 2 + @test nchildren(tree,12) == nchildren(tree[12]) == tree[12].nchildren == 0 + @test nchildren(tree,13) == nchildren(tree[13]) == tree[13].nchildren == 0 + @test nchildren(tree,14) == nchildren(tree[14]) == tree[14].nchildren == 2 + @test nchildren(tree,15) == nchildren(tree[15]) == tree[15].nchildren == 0 + @test_throws BoundsError(tree,0) nchildren(tree,0) + @test_throws BoundsError(tree,16) nchildren(tree,16) + end; + end; + end + end + + @testsetwithinfo "RemoteChannelContainer" begin + @testsetwithinfo "Constructor" begin + rc = ParallelUtilities.RemoteChannelContainer{Int}(1,myid()) + @test rc.out.where == myid() + @test rc.err.where == myid() + @test eltype(rc) == Int + + c = Channel(nworkers()) + tasks = Vector{Task}(undef,nworkers()) + @sync begin + for (ind,p) in enumerate(workers()) + tasks[ind] = @async begin + try + rc = ParallelUtilities.RemoteChannelContainer{Int}(1,p) + res = (rc.out.where,rc.err.where,eltype(rc)) + put!(c,(ind,p,res,false)) + catch + put!(c,(ind,p,(),true)) + rethrow() + end + end + end + for i = 1:nworkers() + ind,p,res,err = take!(c) + err && wait(tasks[ind]) + @test res == (p,p,Int) + showworkernumber(i,nworkers()) + end + end + + rc = ParallelUtilities.RemoteChannelContainer{Int}(1) + @test rc.out.where == myid() + @test rc.err.where == myid() + @test eltype(rc) == Int + + rc = ParallelUtilities.RemoteChannelContainer(1,myid()) + @test rc.out.where == myid() + @test rc.err.where == myid() + @test eltype(rc) == Any + + c = Channel(nworkers()) + tasks = Vector{Task}(undef,nworkers()) + @sync begin + for (ind,p) in enumerate(workers()) + tasks[ind] = @async begin + try + rc = ParallelUtilities.RemoteChannelContainer(1,p) + res = (rc.out.where,rc.err.where,eltype(rc)) + put!(c,(ind,p,res,false)) + catch + put!(c,(ind,p,(),true)) + rethrow() + end + end + end + for i = 1:nworkers() + ind,p,res,err = take!(c) + err && wait(tasks[ind]) + @test res == (p,p,Any) + showworkernumber(i,nworkers()) + end + end + + rc = ParallelUtilities.RemoteChannelContainer(1) + @test rc.out.where == myid() + @test rc.err.where == myid() + @test eltype(rc) == Any + end + + @testsetwithinfo "finalize" begin + rc = ParallelUtilities.RemoteChannelContainer{Int}(1) + finalize(rc) + @test rc.out.where == 0 + @test rc.err.where == 0 + end + + @testsetwithinfo "finalize_except_wherewhence" begin + rc = ParallelUtilities.RemoteChannelContainer{Int}(1) + ParallelUtilities.finalize_except_wherewhence(rc) + @test rc.out.where == myid() + @test rc.err.where == myid() + + @testset "rc on where" begin + # Create on this processor + rc = ParallelUtilities.RemoteChannelContainer{Int}(1) + c = Channel(nworkers()) + tasks = Vector{Task}(undef,nworkers()) + @sync begin + for (ind,p) in enumerate(workers()) + tasks[ind] = @async begin + try + rcoutw,rcerrw = @fetchfrom p begin + ParallelUtilities.finalize_except_wherewhence(rc) + rc.out.where,rc.err.where + end + res = (rc.out.where,rc.err.where,rcoutw,rcerrw) + put!(c,(ind,res,false)) + catch + put!(c,(ind,(),true)) + rethrow() + end + end + end + for i = 1:nworkers() + ind,res,err = take!(c) + err && wait(tasks[ind]) + @test res == (myid(),myid(),0,0) + showworkernumber(i,nworkers()) + end + end + end + + @testset "rc on remote" begin + # Create elsewhere + p_rc = workers()[1] + rc = ParallelUtilities.RemoteChannelContainer{Int}(1,p_rc) + c = Channel(nprocs()) + tasks = Vector{Task}(undef,nprocs()) + @sync begin + for (ind,p) in enumerate(procs()) + tasks[ind] = @async begin + try + rcw = @fetchfrom p begin + ParallelUtilities.finalize_except_wherewhence(rc) + (rc.out.where,rc.err.where) + end + put!(c,(ind,p,rcw,false)) + catch + put!(c,(ind,p,(),true)) + rethrow() + end + end + end + for i = 1:nworkers() + ind,p,res,err = take!(c) + err && wait(tasks[ind]) + if p != myid() && p != p_rc + @test res == (0,0) + else + @test res == (p_rc,p_rc) + end + showworkernumber(i,nworkers()) + end + end + end + end + end + + @testsetwithinfo "BranchChannel" begin + @testset "Constructor" begin + @testset "all channels supplied" begin + rc_self = RemoteChannelContainer{Int}(1) + rc_parent = RemoteChannelContainer{Int}(1) + rc_children = RemoteChannelContainer{Int}(1) + for n=0:2 + b = BranchChannel(1,rc_self,rc_parent,rc_children,n) + @test b isa BranchChannel{Int,Int} + @test b.p == 1 + @test b.selfchannels == rc_self + @test b.parentchannels == rc_parent + @test b.childrenchannels == rc_children + @test nchildren(b) == b.nchildren == n + end + @test_throws ParallelUtilities.DomainError BranchChannel(1,rc_self,rc_parent,rc_children,3) + end + + @testset "only parent channels supplied" begin + rc_parent = RemoteChannelContainer{Int}(1) + for n=0:2 + b = BranchChannel(1,Int,rc_parent,n) + @test b isa BranchChannel{Int,Int} + @test b.p == 1 + @test b.parentchannels == rc_parent + @test b.selfchannels isa RemoteChannelContainer{Int} + @test b.childrenchannels isa RemoteChannelContainer{Int} + @test b.selfchannels.out.where == b.p + @test b.selfchannels.err.where == b.p + @test b.childrenchannels.out.where == b.p + @test b.childrenchannels.err.where == b.p + @test nchildren(b) == b.nchildren == n + end + @test_throws ParallelUtilities.DomainError BranchChannel(1,Int,rc_parent,3) + end + + @testset "no channels supplied" begin + function testbranchchannel(b::BranchChannel{T,T},p,n) where {T} + @test b.p == p + @test b.parentchannels isa RemoteChannelContainer{T} + @test b.selfchannels isa RemoteChannelContainer{T} + @test b.childrenchannels isa RemoteChannelContainer{T} + @test b.parentchannels.out.where == b.p + @test b.parentchannels.err.where == b.p + @test b.selfchannels.out.where == b.p + @test b.selfchannels.err.where == b.p + @test b.childrenchannels.out.where == b.p + @test b.childrenchannels.err.where == b.p + @test nchildren(b) == b.nchildren == n + end + + p = workers()[1] + for n=0:2 + b = BranchChannel{Int,Int}(p,n) + testbranchchannel(b,p,n) + end + @test_throws ParallelUtilities.DomainError BranchChannel{Int,Int}(1,3) + @test_throws ParallelUtilities.DomainError BranchChannel{Int,Int}(1,-1) + end + end + + @testset "finalize" begin + @testset "sameprocessor" begin + parentchannels = RemoteChannelContainer{Int}(1) + b = BranchChannel(1,Int,parentchannels,1) + finalize(b) + @test b.selfchannels.out.where == 0 + @test b.selfchannels.err.where == 0 + @test b.childrenchannels.out.where == 0 + @test b.childrenchannels.err.where == 0 + @test b.parentchannels.out.where == myid() + @test b.parentchannels.err.where == myid() + end + @testset "elsewhere" begin + p = workers()[1] + selfchannels = RemoteChannelContainer{Int}(1,p) + childrenchannels = RemoteChannelContainer{Int}(1,p) + + @testset "parent == whence == where == myid()" begin + parentchannels = RemoteChannelContainer{Int}(1) + b = BranchChannel(1,selfchannels,parentchannels,childrenchannels,1) + self_w,parent_w,child_w = @fetchfrom p begin + finalize(b) + (b.selfchannels.out.where,b.selfchannels.err.where), + (b.parentchannels.out.where,b.parentchannels.err.where), + (b.childrenchannels.out.where,b.childrenchannels.err.where) + end + @test self_w == (0,0) + @test child_w == (0,0) + @test parent_w == (0,0) + end + + @testset "(parent == where) != (whence == myid())" begin + parentchannels = RemoteChannelContainer{Int}(1,p) + b = BranchChannel(1,selfchannels,parentchannels,childrenchannels,1) + self_w,parent_w,child_w = @fetchfrom p begin + finalize(b) + (b.selfchannels.out.where,b.selfchannels.err.where), + (b.parentchannels.out.where,b.parentchannels.err.where), + (b.childrenchannels.out.where,b.childrenchannels.err.where) + end + @test self_w == (0,0) + @test child_w == (0,0) + @test parent_w == (p,p) + end + end + end + + @testset "createbranchchannels" begin + function testbranches(T,tree) + branches = createbranchchannels(T,T,tree) + @test length(branches) == length(tree) + tnr = ParallelUtilities.topnoderank(tree) + for (rank,branch) in enumerate(branches) + p = branch.p + parentrank = parentnoderank(tree,rank) + parentbranch = branches[parentrank] + # Test channel host + @test branch.selfchannels.out.where == p + @test branch.selfchannels.err.where == p + @test branch.childrenchannels.out.where == p + @test branch.childrenchannels.err.where == p + @test branch.parentchannels.out.where == parentbranch.p + @test branch.parentchannels.err.where == parentbranch.p + # Test link with parent + # Holds for nodes other than the top node + if rank != tnr + @test branch.parentchannels.out === parentbranch.childrenchannels.out + @test branch.parentchannels.err === parentbranch.childrenchannels.err + end + end + end + + @testset "SequentialBinaryTree" begin + tree = SequentialBinaryTree(workers()); + for T in [Int,Any,Bool,Vector{Float64},Array{ComplexF64,2}] + testbranches(T,tree) + end + end + @testset "OrderedBinaryTree" begin + tree = OrderedBinaryTree(workers()) + for T in [Int,Any,Bool,Vector{Float64},Array{ComplexF64,2}] + testbranches(T,tree) + end + end + @testset "SegmentedSequentialBinaryTree" begin + tree = SegmentedSequentialBinaryTree(workers()) + for T in [Int,Any,Bool,Vector{Float64},Array{ComplexF64,2}] + testbranches(T,tree) + end + end + @testset "SegmentedOrderedBinaryTree" begin + tree = SegmentedOrderedBinaryTree(workers()) + for T in [Int,Any,Bool,Vector{Float64},Array{ComplexF64,2}] + testbranches(T,tree) + end + end + + iterators = (1:nworkers()+1,) + tree,branches = createbranchchannels(iterators,SequentialBinaryTree) + @test eltype(first(branches).parentchannels) == Any + tree,branches = createbranchchannels(iterators,SegmentedSequentialBinaryTree) + @test eltype(first(branches).parentchannels) == Any + tree,branches = createbranchchannels(iterators,OrderedBinaryTree) + @test eltype(first(branches).parentchannels) == Any + tree,branches = createbranchchannels(Int,Int,iterators,SequentialBinaryTree) + @test eltype(first(branches).parentchannels) == Int + tree,branches = createbranchchannels(Int,Int,iterators,SegmentedSequentialBinaryTree) + @test eltype(first(branches).parentchannels) == Int + tree,branches = createbranchchannels(Int,Int,iterators,OrderedBinaryTree) + @test eltype(first(branches).parentchannels) == Int + + # Make sure that all branches are defined + for T in [SequentialBinaryTree, + OrderedBinaryTree, + SegmentedSequentialBinaryTree, + SegmentedOrderedBinaryTree] + + for nmax = 1:nworkers() + iterators = (1:nmax,) + tree,branches = createbranchchannels(iterators,T) + for i in eachindex(branches) + @test isassigned(branches,i) + end + end + end + + @testset "multiple hosts" begin + w = workers() + mid = div(nworkers(),2) + w1 = workers()[1:mid] + w2 = workers()[mid+1:end] + workersonhosts = OrderedDict("host1"=>w1,"host2"=>w2) + + @testset "SegmentedSequentialBinaryTree" begin + tree = SegmentedSequentialBinaryTree(w,workersonhosts) + for T in [Int,Any] + testbranches(T,tree) + end + end + @testset "SegmentedOrderedBinaryTree" begin + tree = SegmentedOrderedBinaryTree(w,workersonhosts) + for T in [Int,Any] + testbranches(T,tree) + end + end + end + end + end +end; + +@testset "pmap and reduce" begin + + exceptiontype = RemoteException + if VERSION >= v"1.3" + exceptiontype = CompositeException + end + + @testset "Sorted and Unsorted" begin + @test Sorted() isa Ordering + @test Unsorted() isa Ordering + end; + + @testset "pval" begin + p = pval(2,3) + @test value(p) == 3 + @test value(3) == 3 + @test value(p) == value(value(p)) + + @test convert(pval{Any},p) == pval{Any}(2,3) + @test convert(pval{Float64},p) == pval{Any}(2,3.0) + end; + + @testset "mapTreeNode" begin + + @testset "maybepvalput!" begin + pipe = BranchChannel{Int,Int}(myid(),0) + rank = 1 + maybepvalput!(pipe,rank,0) + @test isready(pipe.selfchannels.out) + @test take!(pipe.selfchannels.out) == 0 + + pipe = BranchChannel{pval,pval}(myid(),0) + maybepvalput!(pipe,rank,0) + @test isready(pipe.selfchannels.out) + @test take!(pipe.selfchannels.out) == pval(rank,0) + + pipe = BranchChannel{pval{Int},pval{Int}}(myid(),0) + maybepvalput!(pipe,rank,0) + @test isready(pipe.selfchannels.out) + @test take!(pipe.selfchannels.out) == pval(rank,0) + + T = Vector{ComplexF64} + pipe = BranchChannel{pval{T},pval{T}}(myid(),1) + + val = ones(1).*im + maybepvalput!(pipe,rank,val) + @test isready(pipe.selfchannels.out) + @test take!(pipe.selfchannels.out) == pval(rank,ComplexF64[im]) + + val = ones(1) + maybepvalput!(pipe,rank,val) + @test isready(pipe.selfchannels.out) + @test take!(pipe.selfchannels.out) == pval(rank,ComplexF64[1]) + + T = Vector{Float64} + pipe = BranchChannel{pval{T},pval{T}}(myid(),1) + + val = ones(1) + maybepvalput!(pipe,rank,val) + @test isready(pipe.selfchannels.out) + @test take!(pipe.selfchannels.out) == pval(rank,Float64[1]) + + val = ones(Int,1) + maybepvalput!(pipe,rank,val) + @test isready(pipe.selfchannels.out) + @test take!(pipe.selfchannels.out) == pval(rank,Float64[1]) + + pipe = BranchChannel{pval,pval}(myid(),1) + + val = ones(1) + maybepvalput!(pipe,rank,val) + @test isready(pipe.selfchannels.out) + @test take!(pipe.selfchannels.out) == pval(rank,Float64[1]) + + val = ones(Int,1) + maybepvalput!(pipe,rank,val) + @test isready(pipe.selfchannels.out) + @test take!(pipe.selfchannels.out) == pval(rank,Int[1]) + end + + function test_on_pipe(fn,iterator,pipe,result_expected) + progressrc = nothing + rank = 1 + @test_throws ErrorException mapTreeNode(x->error(""),iterator,rank,pipe,progressrc) + @test !isready(pipe.selfchannels.out) # should not have any result as there was an error + @test isready(pipe.selfchannels.err) + @test take!(pipe.selfchannels.err) # error flag should be true + @test !isready(pipe.selfchannels.err) # should not hold anything now + @test !isready(pipe.parentchannels.out) + @test !isready(pipe.parentchannels.err) + @test !isready(pipe.childrenchannels.out) + @test !isready(pipe.childrenchannels.err) + + mapTreeNode(fn,iterator,rank,pipe,progressrc) + @test isready(pipe.selfchannels.err) + @test !take!(pipe.selfchannels.err) # error flag should be false + @test !isready(pipe.selfchannels.err) + @test isready(pipe.selfchannels.out) + @test take!(pipe.selfchannels.out) == result_expected + @test !isready(pipe.selfchannels.out) + @test !isready(pipe.parentchannels.out) + @test !isready(pipe.parentchannels.err) + @test !isready(pipe.childrenchannels.out) + @test !isready(pipe.childrenchannels.err) + end + + @testset "range" begin + iterator = 1:10 + + pipe = BranchChannel{Int,Int}(myid(),0) + test_on_pipe(sum,iterator,pipe,sum(iterator)) + end + + @testset "ProductSplit" begin + iterators = (1:10,) + ps = ProductSplit(iterators,1,1) + + pipe = BranchChannel{Int,Int}(myid(),0) + test_on_pipe(x->sum(y[1] for y in x),ps,pipe,sum(iterators[1])) + + pipe = BranchChannel{Int,Int}(myid(),1) + test_on_pipe(x->sum(y[1] for y in x),ps,pipe,sum(iterators[1])) + + pipe = BranchChannel{Int,Int}(myid(),2) + test_on_pipe(x->sum(y[1] for y in x),ps,pipe,sum(iterators[1])) + end + + @testset "progress" begin + @test isnothing(ParallelUtilities.indicatemapprogress!(nothing,1)) + rettype = Tuple{Bool,Bool,Int} + progress = RemoteChannel(()->Channel{rettype}(1)) + ParallelUtilities.indicatemapprogress!(progress,10) + @test take!(progress) == (true,false,10) + end + end; + + @testset "reduce" begin + + # Leaves just push results to the parent + # reduced value at a leaf is simply whatever is stored in the local output channel + @testset "at a leaf" begin + # These do not check for errors + result = 1 + rank = 1 + val = pval(rank,result) + + pipe = BranchChannel{typeof(val),typeof(val)}(myid(),0) + put!(pipe.selfchannels.out,val) + @test ParallelUtilities.reducedvalue(sum,rank,pipe,Sorted()) == val + + pipe = BranchChannel{typeof(result),typeof(result)}(myid(),0) + put!(pipe.selfchannels.out,result) + @test ParallelUtilities.reducedvalue(sum,rank,pipe,Unsorted()) == result + end; + + # # Values are collected at the intermediate nodes + @testset "at parent nodes" begin + + # Put some known values on the self and children channels + function putselfchildren!(pipe::BranchChannel,::Unsorted,rank=1) + if rank >= 1 + put!(pipe.selfchannels.out,0) + put!(pipe.selfchannels.err,false) + end + for i=1:nchildren(pipe) + put!(pipe.childrenchannels.out,i) + put!(pipe.childrenchannels.err,false) + end + end + function putselfchildren!(pipe::BranchChannel{<:pval},::Sorted, + selfrank=2,leftchildrank=1,rightchildrank=3) + if selfrank >= 1 + put!(pipe.selfchannels.out,pval(selfrank,2)) + put!(pipe.selfchannels.err,false) + end + N = nchildren(pipe) + + if N > 0 + # left child + put!(pipe.childrenchannels.out,pval(leftchildrank,1)) + put!(pipe.childrenchannels.err,false) + end + + if N > 1 + # right child + put!(pipe.childrenchannels.out,pval(rightchildrank,3)) + put!(pipe.childrenchannels.err,false) + end + end + + function clearerrors!(pipe::BranchChannel,rank=1) + if rank >= 1 + take!(pipe.selfchannels.err) + end + for i=1:nchildren(pipe) + take!(pipe.childrenchannels.err) + end + end + + @testset "reducedvalue" begin + + function testreduction(freduce::Function,pipe::BranchChannel, + ifsorted::Unsorted,res_exp,rank=2) + + p = pipe.p + + try + putselfchildren!(pipe,ifsorted,rank) + @test value(reducedvalue(freduce,rank,pipe,ifsorted)) == res_exp + clearerrors!(pipe,rank) + + @fetchfrom p putselfchildren!(pipe,ifsorted,rank) + @test value(@fetchfrom p reducedvalue(freduce,rank,pipe,ifsorted)) == res_exp + clearerrors!(pipe,rank) + + @fetchfrom p putselfchildren!(pipe,ifsorted,rank) + @test value(reducedvalue(freduce,rank,pipe,ifsorted)) == res_exp + clearerrors!(pipe,rank) + + putselfchildren!(pipe,ifsorted,rank) + @test value(@fetchfrom p reducedvalue(freduce,rank,pipe,ifsorted)) == res_exp + clearerrors!(pipe,rank) + catch + rethrow() + end + end + + function testreduction(freduce::Function,pipe::BranchChannel, + ifsorted::Sorted,res_exp, + selfrank=2,leftchildrank=1,rightchildrank=3) + + p = pipe.p + ranks = (selfrank,leftchildrank,rightchildrank) + + try + putselfchildren!(pipe,ifsorted,ranks...) + @test value(reducedvalue(freduce,selfrank,pipe,ifsorted)) == res_exp + clearerrors!(pipe,selfrank) + + @fetchfrom p putselfchildren!(pipe,ifsorted,ranks...) + @test value(@fetchfrom p reducedvalue(freduce,selfrank,pipe,ifsorted)) == res_exp + clearerrors!(pipe,selfrank) + + @fetchfrom p putselfchildren!(pipe,ifsorted,ranks...) + @test value(reducedvalue(freduce,selfrank,pipe,ifsorted)) == res_exp + clearerrors!(pipe,selfrank) + + putselfchildren!(pipe,ifsorted,ranks...) + @test value(@fetchfrom p reducedvalue(freduce,selfrank,pipe,ifsorted)) == res_exp + clearerrors!(pipe,selfrank) + catch + rethrow() + end + end + + for n = 1:2 + @testset "Unsorted" begin + pipe = BranchChannel{Int,Int}(myid(),n) + res_exp = sum(0:n) + testreduction(sum,pipe,Unsorted(),res_exp,2) + + @testset "toptree" begin + testreduction(sum,pipe,Unsorted(),res_exp,0) + end + end + @testset "Sorted" begin + pipe = BranchChannel{pval,pval}(myid(),n) + res_exp = collect(1:n+1) + testreduction(x->vcat(x...),pipe,Sorted(),res_exp) + + pipe = BranchChannel{pval,pval}(myid(),n) + res_exp = sum(1:n+1) + testreduction(sum,pipe,Sorted(),res_exp) + + @testset "toptree" begin + pipe = BranchChannel{pval,pval}(myid(),n) + res_exp = n == 1 ? [1] : [1,3] + testreduction(x->vcat(x...),pipe,Sorted(),res_exp,0,1,2) + + pipe = BranchChannel{pval,pval}(myid(),n) + res_exp = n == 1 ? 1 : 1+3 + testreduction(sum,pipe,Sorted(),res_exp,0,1,2) + end + end + end + end + + @testset "reduceTreeNode" begin + + function testreduction(freduce::Function,pipe::BranchChannel, + ifsorted::Ordering,res_exp) + + @test !isready(pipe.parentchannels.out) + @test !isready(pipe.parentchannels.err) + + progressrc = nothing + rank = 2 + + try + wait(@spawnat pipe.p putselfchildren!(pipe,ifsorted)) + reduceTreeNode(freduce,rank,pipe,ifsorted,progressrc) + catch + rethrow() + end + @test isready(pipe.parentchannels.out) + @test isready(pipe.parentchannels.err) + @test !take!(pipe.parentchannels.err) # there should be no error + @test value(take!(pipe.parentchannels.out)) == res_exp + + # The pipe should be finalized at this point + @test pipe.selfchannels.out.where == 0 + @test pipe.selfchannels.err.where == 0 + @test pipe.childrenchannels.out.where == 0 + @test pipe.childrenchannels.err.where == 0 + end + + for nchildren = 1:2 + @testset "Unsorted" begin + pipe = BranchChannel{Int,Int}(myid(),nchildren) + res_exp = sum(0:nchildren) + testreduction(sum,pipe,Unsorted(),res_exp) + + rc_parent = RemoteChannelContainer{Int}(1) + p = workers()[1] + pipe = BranchChannel(p,Int,rc_parent,nchildren) + testreduction(sum,pipe,Unsorted(),res_exp) + end + @testset "Sorted" begin + pipe = BranchChannel{pval,pval}(myid(),nchildren) + res_exp = collect(1:nchildren+1) + testreduction(x->vcat(x...),pipe,Sorted(),res_exp) + + rc_parent = RemoteChannelContainer{pval}(myid(),1) + p = workers()[1] + pipe = BranchChannel(p,pval,rc_parent,nchildren) + testreduction(x->vcat(x...),pipe,Sorted(),res_exp) + + pipe = BranchChannel{pval,pval}(myid(),nchildren) + res_exp = sum(1:nchildren+1) + testreduction(sum,pipe,Sorted(),res_exp) + + rc_parent = RemoteChannelContainer{pval}(1) + p = workers()[1] + pipe = BranchChannel(p,pval,rc_parent,nchildren) + testreduction(sum,pipe,Sorted(),res_exp) + end + end + + # The top tree must have children by definition + pipe = BranchChannel{Int,Int}(myid(),0) + putselfchildren!(pipe,Unsorted(),0) + err = ErrorException("nodes with rank <=0 must have children") + @test_throws err reducedvalue(sum,0,pipe,Unsorted()) + clearerrors!(pipe,0) + end + end; + + @testset "progress" begin + @test isnothing(ParallelUtilities.indicatereduceprogress!(nothing,1)) + rettype = Tuple{Bool,Bool,Int} + progress = RemoteChannel(()->Channel{rettype}(1)) + ParallelUtilities.indicatereduceprogress!(progress,10) + @test take!(progress) == (false,true,10) + + @test isnothing(ParallelUtilities.indicatefailure!(nothing,1)) + ParallelUtilities.indicatefailure!(progress,10) + @test take!(progress) == (false,false,10) + end + end; + + @testsetwithinfo "pmapbatch" begin + @testsetwithinfo "batch" begin + @testset "comparison with map" begin + iterable = 1:nworkers() + res = pmapbatch(x->myid(),iterable) + @test res == workers() + res = pmapbatch(x->myid(),(iterable,)) + @test res == workers() + res = pmapbatch(x->myid(),(iterable,1:1)) + @test res == workers() + res = pmapbatch(x->myid(),iterable,num_workers=1) + @test res == workers()[1:1] + + iterable = 1:nworkers()-1 + res = pmapbatch(x->myid(),iterable) + @test res == workersactive(iterable) + + iterable = 1:nworkers() + res = pmapbatch(identity,iterable) + resexp = [ProductSplit((iterable,),nworkersactive(iterable),p) for p=1:nworkersactive(iterable)] + @test res == resexp + + iterable = 1:nworkers() + res = pmapbatch(identity,iterable) + resexp = [ProductSplit((iterable,),nworkers(),p) for p=1:nworkers()] + @test res == resexp + + iterable = 1:2nworkers() + res = pmapbatch(identity,iterable) + resexp = [ProductSplit((iterable,),nworkersactive(iterable),p) for p=1:nworkersactive(iterable)] + @test res == resexp + end + + @testset "errors" begin + @test_throws exceptiontype pmapbatch(x->throw(BoundsError()),1:10) + end + end + + @testsetwithinfo "elementwise" begin + @testset "comparison with map" begin + iterable = 1:nworkers() + res = pmapbatch_elementwise(identity,iterable) + @test res == iterable + + res = pmapbatch_elementwise(identity,iterable,num_workers=1) + @test res == iterable + + iterable = 1:20 + res = pmapbatch_elementwise(x->x^2,iterable) + @test res == iterable.^2 + end + + @testset "errors" begin + @test_throws exceptiontype pmapbatch_elementwise(x->throw(BoundsError()),1:10) + end + end + end; + + @testsetwithinfo "pmapsum" begin + @testsetwithinfo "batch" begin + @testset "rank" begin + res_exp = sum(1:nworkers()) + @testset "without progress" begin + res = pmapsum(x->x[1][1],Int,1:nworkers()) + @test res == res_exp + res = pmapsum(x->x[1][1],1:nworkers()) + @test res == res_exp + end + @testset "with progress" begin + res = pmapsum(x->x[1][1],Int,1:nworkers(),showprogress=true) + @test res == res_exp + res = pmapsum(x->x[1][1],1:nworkers(),showprogress=true) + @test res == res_exp + end + @test pmapsum(x->x[1][1],Int,(1:nworkers(),)) == res_exp + @test pmapsum(x->x[1][1],(1:nworkers(),)) == res_exp + @test pmapsum(x->x[1][1],Int,(1:nworkers(),1:1)) == res_exp + @test pmapsum(x->x[1][1],(1:nworkers(),1:1)) == res_exp + @test pmapsum(x->myid(),1:nworkers()) == sum(workers()) + end + + @testset "one iterator" begin + rng = 1:100 + @test pmapsum(x->sum(y[1] for y in x),rng) == sum(rng) + @test pmapsum(x->sum(y[1] for y in x),(rng,)) == sum(rng) + end + + @testset "array" begin + @test pmapsum(x->ones(2),1:nworkers()) == ones(2).*nworkers() + end + + @testset "stepped iterator" begin + rng = 1:5:100 + @test pmapsum(x->sum(y[1] for y in x),rng) == sum(rng) + end + + @testset "two iterators" begin + iters = (1:100,1:2) + @test pmapsum(x->sum(y[1] for y in x),iters) == sum(iters[1])*length(iters[2]) + end + + @testsetwithinfo "run elsewhere" begin + res_exp = sum(workers()) + c = Channel{Tuple{Int,Int,Bool}}(nworkers()) + tasks = Vector{Task}(undef,nworkers()) + @sync begin + for (ind,p) in enumerate(workers()) + tasks[ind] = @async begin + try + res = @fetchfrom p pmapsum(x->myid(),1:nworkers()) + put!(c,(ind,res,false)) + catch + put!(c,(ind,0,true)) + rethrow() + end + end + end + for i = 1:nworkers() + ind,res,err = take!(c) + err && wait(tasks[ind]) + @test res == res_exp + showworkernumber(i,nworkers()) + end + end + end; + + @testset "errors" begin + @test_throws exceptiontype pmapsum(x->error("map"),1:10) + @test_throws exceptiontype pmapsum(x->fmap(x),1:10) + end + end + + @testsetwithinfo "elementwise" begin + @testset "comparison with map" begin + iterable = 1:100 + @testset "without progress" begin + res = pmapsum_elementwise(identity,iterable) + @test res == sum(iterable) + end + @testset "with progress" begin + res = pmapsum_elementwise(identity,iterable,showprogress=true) + @test res == sum(iterable) + end + res = pmapsum_elementwise(identity,(iterable,)) + @test res == sum(iterable) + res = pmapsum_elementwise(identity,Int,iterable) + @test res == sum(iterable) + res = pmapsum_elementwise(identity,Int,(iterable,)) + @test res == sum(iterable) + + iterable = 1:100 + res = pmapsum_elementwise(x->x^2,iterable) + @test res == sum(x->x^2,iterable) + @test res == pmapsum(plist->sum(x[1]^2 for x in plist),iterable) + end + + @testset "run elsewhere" begin + iterable = 1:100 + res_exp = sum(iterable) + c = Channel{Tuple{Int,Int,Bool}}(nworkers()) + tasks = Vector{Task}(undef,nworkers()) + @sync begin + for (ind,p) in enumerate(workers()) + tasks[ind] = @async begin + try + res = @fetchfrom p pmapsum_elementwise(identity,iterable) + put!(c,(ind,res,false)) + catch + put!(c,(ind,0,true)) + rethrow() + end + end + end + for i = 1:nworkers() + ind,res,err = take!(c) + err && wait(tasks[ind]) + @test res == res_exp + showworkernumber(i,nworkers()) + end + end + end; + + @testset "errors" begin + @test_throws exceptiontype pmapsum_elementwise(x->error("hi"),1:10) + end + end + + @testset "type coercion" begin + @test_throws exceptiontype pmapsum(x->[1.1],Vector{Int},1:nworkers()) + @test pmapsum(x->ones(2).*myid(),Vector{Int},1:nworkers()) isa Vector{Int} + end + end; + + @testsetwithinfo "pmapreduce_commutative" begin + @testsetwithinfo "batch" begin + @testset "sum" begin + res_exp = sum(workers()) + @testset "without progress" begin + res = pmapreduce_commutative(x->myid(),Int,sum,Int,1:nworkers()) + @test res == res_exp + res = pmapreduce_commutative(x->myid(),sum,1:nworkers()) + @test res == res_exp + end + @testset "with progress" begin + res = pmapreduce_commutative(x->myid(),Int,sum,Int,1:nworkers(),showprogress=true) + @test res == res_exp + res = pmapreduce_commutative(x->myid(),sum,1:nworkers(),showprogress=true) + @test res == res_exp + end + @test pmapreduce_commutative(x->myid(),Int,sum,Int,(1:nworkers(),)) == res_exp + @test pmapreduce_commutative(x->myid(),sum,(1:nworkers(),)) == res_exp + @test pmapreduce_commutative(x->myid(),Int,sum,Int,(1:nworkers(),1:1)) == res_exp + @test pmapreduce_commutative(x->myid(),sum,(1:nworkers(),1:1)) == res_exp + @test pmapreduce_commutative(x->myid(),sum,1:nworkers()) == pmapsum(x->myid(),1:nworkers()) + end + @testset "prod" begin + @test pmapreduce_commutative(x->myid(),prod,1:nworkers()) == prod(workers()) + @test pmapreduce_commutative(x->myid(),prod,(1:nworkers(),)) == prod(workers()) + @test pmapreduce_commutative(x->myid(),prod,(1:nworkers(),1:1)) == prod(workers()) + end + + @testsetwithinfo "run elsewhere" begin + res_exp = prod(workers()) + c = Channel{Tuple{Int,Int,Bool}}(nworkers()) + tasks = Vector{Task}(undef,nworkers()) + @sync begin + for (ind,p) in enumerate(workers()) + tasks[ind] = @async begin + try + res = @fetchfrom p pmapreduce_commutative(x->myid(),prod,1:nworkers()) + put!(c,(ind,res,false)) + catch + put!(c,(ind,0,true)) + rethrow() + end + end + end + for i = 1:nworkers() + ind,res,err = take!(c) + err && wait(tasks[ind]) + @test res == res_exp + showworkernumber(i,nworkers()) + end + end + end + + @testset "errors" begin + @test_throws exceptiontype pmapreduce_commutative( + x->error("map"),sum,1:10) + @test_throws exceptiontype pmapreduce_commutative( + identity,x->error("reduce"),1:10) + @test_throws exceptiontype pmapreduce_commutative( + x->error("map"),x->error("reduce"),1:10) + + @test_throws exceptiontype pmapreduce_commutative( + x->fmap("map"),sum,1:10) + @test_throws exceptiontype pmapreduce_commutative( + x->1,x->fred(x),1:10) + @test_throws exceptiontype pmapreduce_commutative( + x->fmap(x),x->fred(x),1:10) + end + + @testset "type coercion" begin + @test_throws exceptiontype pmapreduce_commutative(x->[1.1],Vector{Int}, + sum,Vector{Int},1:nworkers()) + res = pmapreduce_commutative(x->ones(2).*myid(),Vector{Int},sum,Vector{Int},1:nworkers()) + @test res isa Vector{Int} + end + end; + + @testsetwithinfo "elementwise" begin + @testset "comparison with map" begin + iter = 1:1000 + res_exp = sum(x->x^2,iter) + @testset "without progress" begin + res = pmapreduce_commutative_elementwise(x->x^2,sum,iter) + @test res == res_exp + end + @testset "with progress" begin + res = pmapreduce_commutative_elementwise(x->x^2,sum,iter,showprogress=true) + @test res == res_exp + end + @test res == pmapsum_elementwise(x->x^2,iter) + @test res == pmapsum(plist->sum(x[1]^2 for x in plist),iter) + res = pmapreduce_commutative_elementwise(x->x^2,sum,(iter,)) + @test res == res_exp + res = pmapreduce_commutative_elementwise(x->x^2,Int,sum,Int,(iter,)) + @test res == res_exp + res = pmapreduce_commutative_elementwise(x->x^2,Int,sum,Int,iter) + @test res == res_exp + res = pmapreduce_commutative_elementwise(x->x^2,Int,x->float(sum(x)),Float64,iter) + @test res == float(res_exp) + end + + @testsetwithinfo "run elsewhere" begin + iter = 1:1000 + res_exp = sum(x->x^2,iter) + c = Channel{Tuple{Int,Int,Bool}}(nworkers()) + tasks = Vector{Task}(undef,nworkers()) + @sync begin + for (ind,p) in enumerate(workers()) + tasks[ind] = @async begin + try + res = @fetchfrom p pmapreduce_commutative_elementwise(x->x^2,sum,iter) + put!(c,(ind,res,false)) + catch + put!(c,(ind,0,true)) + rethrow() + end + end + end + for i = 1:nworkers() + ind,res,err = take!(c) + err && wait(tasks[ind]) + @test res == res_exp + showworkernumber(i,nworkers()) + end + end + end + + @testsetwithinfo "errors" begin + @test_throws exceptiontype pmapreduce_commutative_elementwise( + x->error("map"),sum,1:10) + @test_throws exceptiontype pmapreduce_commutative_elementwise( + identity,x->error("reduce"),1:10) + @test_throws exceptiontype pmapreduce_commutative_elementwise( + x->error("map"), + x->error("reduce"),1:10) + end + end; + end; + + @testsetwithinfo "pmapreduce" begin + @testsetwithinfo "batch" begin + @testset "sum" begin + res_exp = sum(workers()) + @testset "without progress" begin + @test pmapreduce(x->myid(),Int,sum,Int,1:nworkers()) == res_exp + @test pmapreduce(x->myid(),sum,1:nworkers()) == res_exp + end + @testset "without progress" begin + res = pmapreduce(x->myid(),Int,sum,Int,1:nworkers(),showprogress=true) + @test res == res_exp + res = pmapreduce(x->myid(),sum,1:nworkers(),showprogress=true) + @test res == res_exp + end + @test pmapreduce(x->myid(),Int,sum,Int,(1:nworkers(),)) == res_exp + @test pmapreduce(x->myid(),sum,(1:nworkers(),)) == res_exp + @test pmapreduce(x->myid(),Int,sum,Int,(1:nworkers(),1:1)) == res_exp + @test pmapreduce(x->myid(),sum,(1:nworkers(),1:1)) == res_exp + + @testset "comparison with pmapsum" begin + res_exp = pmapsum(x->myid(),1:nworkers()) + @test pmapreduce(x->myid(),Int,sum,Int,1:nworkers()) == res_exp + @test pmapreduce(x->myid(),sum,1:nworkers()) == res_exp + end + end; + + @testset "concatenation" begin + res_vcat = ones(2*nworkers()) + res_hcat = ones(2,nworkers()) + @test pmapreduce(x->ones(2),Vector{Float64}, + x->vcat(x...),Vector{Float64},1:nworkers()) == res_vcat + @test pmapreduce(x->ones(2),x->vcat(x...),1:nworkers()) == res_vcat + @test pmapreduce(x->ones(2),Vector{Float64}, + x->hcat(x...),Matrix{Float64},1:nworkers()) == res_hcat + @test pmapreduce(x->ones(2),x->hcat(x...),1:nworkers()) == res_hcat + + @testset "sorting" begin + @test pmapreduce(x->ones(2)*x[1][1],x->vcat(x...),1:nworkers()) == + vcat((ones(2).*i for i=1:nworkers())...) + + @test pmapreduce(x->x[1][1],x->vcat(x...),1:nworkers()) == collect(1:nworkers()) + @test pmapreduce(x->myid(),Int,x->vcat(x...),Vector{Int},(1:nworkers(),)) == workers() + @test pmapreduce(x->myid(),x->vcat(x...),1:nworkers()) == workers() + end + end; + + @testsetwithinfo "run elsewhere" begin + @testsetwithinfo "sum" begin + res_exp = sum(workers()) + c = Channel{Tuple{Int,Int,Bool}}(nworkers()) + tasks = Vector{Task}(undef,nworkers()) + @sync begin + for (ind,p) in enumerate(workers()) + tasks[ind] = @async begin + try + res = @fetchfrom p pmapreduce(x->myid(),sum,1:nworkers()) + put!(c,(ind,res,false)) + catch + put!(c,(ind,0,true)) + rethrow() + end + end + end + for i = 1:nworkers() + ind,res,err = take!(c) + err && wait(tasks[ind]) + @test res == res_exp + showworkernumber(i,nworkers()) + end + end + end + # concatenation where the rank is used in the mapping function + # Preserves order of the iterators + @testsetwithinfo "concatenation using rank" begin + res_exp = collect(1:nworkers()) + c = Channel{Tuple{Int,Vector{Int},Bool}}(nworkers()) + tasks = Vector{Task}(undef,nworkers()) + @sync begin + for (ind,p) in enumerate(workers()) + tasks[ind] = @async begin + try + res = @fetchfrom p pmapreduce(x->x[1][1],x->vcat(x...),1:nworkers()) + put!(c,(ind,res,false)) + catch + put!(c,(ind,Int[],true)) + rethrow() + end + end + end + for i = 1:nworkers() + ind,res,err = take!(c) + err && wait(tasks[ind]) + @test res == res_exp + showworkernumber(i,nworkers()) + end + end + end + end; + + @testset "errors" begin + @test_throws exceptiontype pmapreduce(x->error("map"),sum,1:10) + @test_throws exceptiontype pmapreduce(identity,x->error("reduce"),1:10) + @test_throws exceptiontype pmapreduce(x->error("map"),x->error("reduce"),1:10) + @test_throws exceptiontype pmapreduce(x->fmap(x),sum,1:10) + @test_throws exceptiontype pmapreduce(x->1,x->fred(x),1:10) + @test_throws exceptiontype pmapreduce(x->fmap(x),x->fred(x),1:10) + end; + + @testset "type coercion" begin + @test_throws exceptiontype pmapreduce(x->[1.1],Vector{Int},sum,Vector{Int},1:nworkers()) + @test pmapreduce(x->ones(2).*myid(),Vector{Int},sum,Vector{Int},1:nworkers()) isa Vector{Int} + end; + end; + end; +end; + +@testset "show" begin + + @testset "error" begin + io = IOBuffer() + + showerror(io,ParallelUtilities.ProcessorNumberError(5,2)) + strexp = "processor id 5 does not lie in the range 1:2" + @test String(take!(io)) == strexp + + showerror(io,ParallelUtilities.DecreasingIteratorError()) + strexp = "all the iterators need to be strictly increasing" + @test String(take!(io)) == strexp + + showerror(io,ParallelUtilities.TaskNotPresentError((1:4,),(5,))) + strexp = "could not find the task $((5,)) in the list $((1:4,))" + @test String(take!(io)) == strexp + end; + + @testset "BranchChannel" begin + io = IOBuffer() + + b = BranchChannel{Any,Any}(1,0) + show(io,b) + strexp = "Leaf : 1 ← 1" + @test String(take!(io)) == strexp + + b = BranchChannel{Any,Any}(1,1) + show(io,b) + strexp = "Branch: 1 ← 1 ← 1 child" + @test String(take!(io)) == strexp + + b = BranchChannel{Any,Any}(1,2) + show(io,b) + strexp = "Branch: 1 ← 1 ⇇ 2 children" + @test String(take!(io)) == strexp + end; + + @testset "BinaryTreeNode" begin + io = IOBuffer() + b = BinaryTreeNode(2,3,1) + show(io,b) + strexp = "BinaryTreeNode(p = 2, parent = 3, nchildren = 1)" + @test String(take!(io)) == strexp + end +end; \ No newline at end of file