Skip to content

Commit

Permalink
Merge pull request #91 from EcoJulia/rr/extensions
Browse files Browse the repository at this point in the history
Allow node renaming for default tree types
  • Loading branch information
richardreeve authored Jan 5, 2024
2 parents 80c6141 + fb143a5 commit 4817752
Show file tree
Hide file tree
Showing 12 changed files with 124 additions and 43 deletions.
1 change: 1 addition & 0 deletions docs/src/man/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ getancestors
getchildren
getdescendants
getsiblings
renamenode!
```

## Methods on Branches
Expand Down
32 changes: 19 additions & 13 deletions src/API.jl
Original file line number Diff line number Diff line change
Expand Up @@ -194,18 +194,17 @@ Returns a tree - either itself if it is a single tree, or the single tree
in a set with label id. Must be implemented for any ManyTrees type.
"""
function _gettree end
@traitfn function _gettree(tree::T,
id::TN) where {T <: AbstractTree{OneTree}, TN;
MatchTreeNameType{T, TN}}
function _gettree(tree::T, id) where {T <: AbstractTree{OneTree}}
id == _gettreename(tree) || throw(BoundsError(tree, id))
return tree
end

"""
_getnodes(tree::AbstractTree{OneTree})
_getnodes(tree::AbstractTree{OneTree}[, order::TraversalOrder])
Returns a vector of nodes for a OneTree tree. Either _getnodes() must be
implemented for any OneTree tree type.
Returns an interable collection of nodes for a OneTree tree. _getnodes(tree) must be
implemented for a OneTree tree type as a base mechanisms for extracting the
node list.
"""
function _getnodes end
_getnodes(tree::AbstractTree{OneTree}, order::TraversalOrder) =
Expand All @@ -214,16 +213,16 @@ _getnodes(tree::AbstractTree{OneTree}, order::TraversalOrder) =
"""
_getnodenames(tree::AbstractTree{OneTree})
Returns a vector of node names for a OneTree tree. Can
Returns an iterable collection of node names for a OneTree tree. Can
be implemented for any OneTree tree type, especially PreferNodeObjects trees.
"""
function _getnodenames end
_getnodenames(::T) where T <: AbstractTree = error("No _getnodes() method for tree type $T")
@traitfn _getnodenames(tree::T, order::TraversalOrder) where
{T <: AbstractTree{OneTree}; !PreferNodeObjects{T}} = _getnodes(tree, order)
{T <: AbstractTree{OneTree}; !PreferNodeObjects{T}} = _getnodes(tree, order)
@traitfn _getnodenames(tree::T, order::TraversalOrder) where
{T <: AbstractTree{OneTree}; PreferNodeObjects{T}} =
_getnodename.(tree, _getnodes(tree, order))
{T <: AbstractTree{OneTree}; PreferNodeObjects{T}} =
_getnodename.(Ref(tree), _getnodes(tree, order))

"""
_nnodes(::AbstractTree)
Expand All @@ -234,7 +233,7 @@ be implemented for any OneTree tree type (otherwise infers from _getnodes()).
_nnodes(tree::AbstractTree{OneTree}) = length(_getnodes(tree, anyorder))

"""
_getleafnames(::AbstractTree)
_getleafnames(::AbstractTree, ::TraversalOrder)
Returns the leaf names of a tree. May be implemented for any
tree type (otherwise determined from _getnodenames() and _isleaf() functions).
Expand Down Expand Up @@ -335,6 +334,14 @@ _hasnode(tree::AbstractTree{OneTree, RT, NL},
_hasnode(tree::AbstractTree{OneTree, RT, NL, N},
node::N) where {RT, NL, N} = node _getnodes(tree)

"""
_renamenode!(tree::AbstractTree, oldnode[name], newname)
Renames a node in a tree. Optional - not implemented for most tree types.
"""
function _renamenode! end
_renamenode!(_::AbstractTree, _, _) = false

"""
_createnode!(tree::AbstractTree, nodename[, data])
Expand Down Expand Up @@ -411,8 +418,7 @@ function _getbranchnames end
_getbranches(tree)
@traitfn _getbranchnames(tree::T) where {T <: AbstractTree{OneTree};
PreferBranchObjects{T}} =
[_getbranchname(tree, branch)
for branch in _getbranches(tree)]
_getbranchname.(Ref(tree), _getbranches(tree))

"""
_hasbranch(tree::AbstractTree, node[name])
Expand Down
45 changes: 27 additions & 18 deletions src/Interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,12 @@ Returns the vector of nodes of a single tree, or a Dict of vectors of nodes
for multiple trees.
"""
function getnodes end
getnodes(tree::AbstractTree{OneTree}, order::TraversalOrder = preorder) =
_getnodes(tree, order)
getnodes(tree::AbstractTree{OneTree}, order::TraversalOrder = anyorder) =
collect(_getnodes(tree, order))
getnodes(trees::AbstractTree{ManyTrees}, name,
order::TraversalOrder = preorder) =
order::TraversalOrder = anyorder) =
getnodes(gettree(trees, name), order)
getnodes(trees::AbstractTree{ManyTrees}, order::TraversalOrder = preorder) =
getnodes(trees::AbstractTree{ManyTrees}, order::TraversalOrder = anyorder) =
Dict(name => getnodes(gettree(trees, name), order)
for name in _gettreenames(trees))

Expand Down Expand Up @@ -250,11 +250,11 @@ Return a vector of node names of a single tree (identified by id for a
ManyTrees tree), or a Dict of vectors of node names for multiple trees.
"""
function getnodenames end
getnodenames(tree::AbstractTree{OneTree}, order::TraversalOrder = preorder) =
_getnodenames(tree, order)
getnodenames(trees::AbstractTree{ManyTrees}, name, order::TraversalOrder = preorder) =
getnodenames(tree::AbstractTree{OneTree}, order::TraversalOrder = anyorder) =
collect(_getnodenames(tree, order))
getnodenames(trees::AbstractTree{ManyTrees}, name, order::TraversalOrder = anyorder) =
getnodenames(gettree(trees, name), order)
getnodenames(trees::AbstractTree{ManyTrees}, order::TraversalOrder = preorder) =
getnodenames(trees::AbstractTree{ManyTrees}, order::TraversalOrder = anyorder) =
Dict(name => getnodenames(gettree(trees, name), order)
for name in _gettreenames(trees))

Expand Down Expand Up @@ -477,7 +477,7 @@ Returns whether a tree has a given node (or node name) or not.
hasnode(tree::AbstractTree{OneTree}, node) = _hasnode(tree, node)

"""
getnode(tree::AbstractTree, nodename)
getnode(tree::AbstractTree, node[name])
Returns a node from a tree.
"""
Expand All @@ -486,12 +486,21 @@ function getnode(tree::AbstractTree{OneTree}, node)
return _getnode(tree, node)
end

"""
renamenode!(tree::AbstractTree, oldnode[name], newname)
Renames a node in a tree. Optional - not implemented for most tree types and returns false.
"""
function renamenode!(tree::AbstractTree{OneTree}, oldnode, newname)
_hasnode(tree, oldnode) || error("Node $oldnode does not exist")
_hasnode(tree, newname) || return _renamenode!(tree, _getnode(tree, oldnode), newname)
return newname == getnodename(tree, oldnode)
end

"""
getnodename(::AbstractTree, node)
Returns the node name associated with a node from a tree. For some
node types, it will be able to extract the node name without reference to
the tree.
Returns the node name associated with a node from a tree.
"""
function getnodename end
@traitfn function getnodename(tree::T, node::N) where
Expand Down Expand Up @@ -1053,20 +1062,20 @@ end
Retrieve the leaf names from the tree (in some specific order).
"""
getleafnames(tree::AbstractTree, order::TraversalOrder = preorder) =
getleafnames(tree::AbstractTree, order::TraversalOrder = anyorder) =
_getleafnames(tree, order)

"""
getleaves(::AbstractTree[, ::TraversalOrder])
Retrieve the leaves from the tree.
"""
getleaves(tree::AbstractTree{OneTree}, order::TraversalOrder = preorder) =
getleaves(tree::AbstractTree{OneTree}, order::TraversalOrder = anyorder) =
_getleaves(tree, order)
getleaves(trees::AbstractTree{ManyTrees}, name,
order::TraversalOrder = preorder) =
order::TraversalOrder = anyorder) =
_getleaves(gettree(trees, name), order)
getleaves(trees::AbstractTree{ManyTrees}, order::TraversalOrder = preorder) =
getleaves(trees::AbstractTree{ManyTrees}, order::TraversalOrder = anyorder) =
Dict(name => _getleaves(gettree(trees, name), order)
for name in _gettreenames(trees))

Expand Down Expand Up @@ -1241,10 +1250,10 @@ invalidate!(tree::AbstractTree, state = missing) = _invalidate!(tree, state)
traversal(::AbstractTree, ::TraversalOrder, init)
Return an iterable object for a tree containing nodes in given order -
preorder, inorder, postorder or breadthfirst - optionally starting from init.
anyorder, preorder, inorder, postorder or breadthfirst - optionally starting from init.
"""
function traversal end
traversal(tree::AbstractTree{OneTree}, order::TraversalOrder = preorder) =
traversal(tree::AbstractTree{OneTree}, order::TraversalOrder = anyorder) =
_traversal(tree, order)
@traitfn function traversal(tree::T, order::TraversalOrder, init::NL) where
{NL, RT, T <: AbstractTree{OneTree, RT, NL}; !MatchNodeType{T, NL}}
Expand Down
4 changes: 2 additions & 2 deletions src/Phylo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export _isleaf, _isroot, _isinternal, _isunattached
export _indegree, _hasinboundspace, _outdegree, _hasoutboundspace, _hasspace, _degree
export _hasinbound, _getinbound, _addinbound!, _removeinbound!
export _getoutbounds, _addoutbound!, _removeoutbound!
export _getconnections, _addconnection!, _removeconnection!
export _getconnections, _addconnection!, _removeconnection!, _renamenode!
export MatchNodeType, MatchNodeTypes, PreferNodeObjects, _prefernodeobjects

# AbstractBranch methods
Expand Down Expand Up @@ -127,7 +127,7 @@ export isleaf, isroot, isinternal, isunattached
export degree, indegree, outdegree, hasinbound, getconnections, getinbound, getoutbounds
export hasoutboundspace, hasinboundspace
export getleafinfo, setleafinfo!, leafinfotype
export getnodedata, setnodedata!
export getnodedata, setnodedata!, renamenode!
export getparent, getancestors #, hasparent # unimplemented
export getchildren, getdescendants #, haschildren # unimplemented
export getsiblings
Expand Down
34 changes: 34 additions & 0 deletions src/RecursiveTree.jl
Original file line number Diff line number Diff line change
Expand Up @@ -724,3 +724,37 @@ function show(io::IO, branch::RecursiveBranch{RT}) where RT <: Rooted
" to node '$(branch.conns[1].name)'" *
(ismissing(branch.length) ? "" : " (length $(branch.length))"))
end

import Phylo.API: _renamenode!
# Only currently implemented where there is no tip data, as tip data
# references will have to be relabelled
function _renamenode!(tree::RecursiveTree{RT, NL, ND, BD, BT, LU, TD},
oldnode::RecursiveNode{RT, NL}, newname::NL) where
{RT, NL, ND, BD, BT, LU, TD}
_renametipdata!(tree, oldnode.name, newname) || return false
oldname = oldnode.name
id = tree.nodedict[oldname]
oldnode.name = newname
delete!(tree.nodedict, oldname)
tree.nodedict[newname] = id
return true
end

_renametipdata!(tree::RecursiveTree{RT, NL, ND, BD, BT, LU, Nothing},
oldname, newname) where {RT, NL, ND, BD, BT, LU} = true

function _renametipdata!(tree::RecursiveTree{RT, NL, ND, BD, BT, LU, <: Dict},
oldname, newname) where {RT, NL, ND, BD, BT, LU}
li = _getleafinfo(tree)
# Fine if there's no name change or no data
oldname == newname && return true
haskey(li, oldname) || return true
# Fails if there's already data for the new name
haskey(li, newname) && return false
# Otherwise just go ahead
nli = li[oldname]
delete!(li, oldname)
li[newname] = nli
return true
end

2 changes: 1 addition & 1 deletion src/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ function outputtree!(io::IO, tree::TREE, ::StandardOutput) where TREE <: Abstrac
b = nbranches(tree)
bs = collect(getbranches(tree))
print(io, "\n$b branches: [")
if n < 10
if b < 10
println(io, join(bs, ", ", " and ") * "]")
else
println(io, join(bs[1:5], ", ") *
Expand Down
3 changes: 2 additions & 1 deletion test/test_API.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct TestTree <: Phylo.AbstractTree{OneTree, OneRoot, String, TestNode, TestBr
end

import Phylo.API: _preferbranchobjects
_preferbranchobjects(::Type{<: TestTree}) = false
_preferbranchobjects(::Type{<: TestBranch}) = false

@testset "Check errors" begin
tt = TestTree();
Expand All @@ -40,6 +40,7 @@ _preferbranchobjects(::Type{<: TestTree}) = false
@test_throws ErrorException _dst(tt, tb)
@test_throws ErrorException _getnodedata(tt, tn)
@test_throws ErrorException _getbranchdata(tt, tb)
@test !_renamenode!(tt, tn, "New")
end

end
3 changes: 2 additions & 1 deletion test/test_LinkTree.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jdb = DataFrame(species = observations, count = 1:4)
@test createbranch!(ltdf, name, species[1]) getbranches(ltdf)
end

@testset "UnrootedTree()" begin
@testset "Unrooted Trees" begin
name = "internal"
LB = LinkBranch{Unrooted, String, Nothing, Float64}
LN = LinkNode{Unrooted, String, Vector{Int}, LB}
Expand All @@ -32,6 +32,7 @@ end
@test leafinfotype(typeof(ltjdb)) typeof(jdb)
@test_nowarn createnode!(ltjdb, name)
@test createbranch!(ltjdb, name, observations[1], data = nothing) getbranches(ltjdb)
@test createbranch!(ltjdb, getnode(ltjdb, name), getnode(ltjdb, observations[2]), data = nothing) getbranches(ltjdb)
end

end
31 changes: 27 additions & 4 deletions test/test_RecursiveTree.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,29 @@ jdb = DataFrame(species = observations, count = 1:4)

@testset "RootedTree()" begin
name = "internal"
nts = RecursiveTree{OneRoot, String, Vector{Int}, Nothing, BinaryBranching, Float64, Nothing}(species)
@test !renamenode!(nts, species[1], species[2])
@test hasnode(nts, species[1])
@test renamenode!(nts, species[1], "new " * species[1])
@test_throws ErrorException renamenode!(nts, species[1], "new " * species[1])
@test hasnode(nts, "new " * species[1])
@test renamenode!(nts, "new " * species[1], species[1])
@test Set(species) == Set(getleafnames(nts))

rts = RootedTree(species)
li = getleafinfo(rts)
data = [1, 2]
li[species[1]] = data
@test !renamenode!(rts, species[1], species[2])
@test renamenode!(rts, species[1], "new " * species[1])
@test !haskey(li, species[1])
@test haskey(li, "new " * species[1])
@test li["new " * species[1]] == data
@test_throws ErrorException renamenode!(rts, species[1], "new " * species[1])
@test renamenode!(rts, "new " * species[1], species[1])
@test haskey(li, species[1])
@test li[species[1]] == data
@test Set(species) == Set(getleafnames(rts))
@test nodedatatype(typeof(rts)) Dict{String, Any}
@test branchdatatype(typeof(rts)) Dict{String, Any}
@test leafinfotype(typeof(rts)) Dict{String, Any}
Expand Down Expand Up @@ -44,7 +66,7 @@ jdb = DataFrame(species = observations, count = 1:4)
@test_throws ErrorException createbranch!(rtdfp, name, species[2])
end

@testset "UnrootedTree()" begin
@testset "Unrooted Trees" begin
name = "internal"
urts = Phylo.ReTD{Unrooted, BinaryBranching, Float64}(species)
@test nodedatatype(typeof(urts)) Dict{String, Any}
Expand All @@ -62,14 +84,15 @@ end
@test leafinfotype(typeof(urtsp)) Dict{String, Any}
@test_nowarn createnode!(urtsp, name)
@test createbranch!(urtsp, name, species[1]) getbranches(urtsp)
@test createbranch!(urtsp, name, species[2]) getbranches(urtsp)
@test createbranch!(urtsp, name, species[3]) getbranches(urtsp)
@test createbranch!(urtsp, name, species[4]) getbranches(urtsp)
@test createbranch!(urtsp, getnode(urtsp, name), getnode(urtsp, species[2])) getbranches(urtsp)
@test createbranch!(urtsp, getnode(urtsp, name), species[3]) getbranches(urtsp)
@test createbranch!(urtsp, name, getnode(urtsp, species[4])) getbranches(urtsp)
@test nroots(urtsp) == 0

LB = RecursiveBranch{Unrooted, String, Vector{Int}, Nothing, BinaryBranching, Float64}
LN = RecursiveNode{Unrooted, String, Vector{Int}, Nothing, BinaryBranching, Float64}
rtjdb = RecursiveTree{Unrooted, String, Vector{Int}, Nothing, BinaryBranching, Float64, typeof(jdb)}(jdb)
@test_throws MethodError renamenode!(rtjdb, observations[1], "new " * observations[1])
@test nodedatatype(typeof(rtjdb)) Vector{Int}
@test branchdatatype(typeof(rtjdb)) Nothing
@test leafinfotype(typeof(rtjdb)) typeof(jdb)
Expand Down
2 changes: 2 additions & 0 deletions test/test_Tree.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ end

@testset "BinaryTree()" begin
btn = BinaryTree{OneRoot, DataFrame, Vector{Float64}}(ntips)
io = IOBuffer()
show(io, btn)
@test length(nodefilter(isroot, btn)) == ntips
@test length(nodefilter(isleaf, btn)) == ntips
@test length(nodefilter(isinternal, btn)) == 0
Expand Down
4 changes: 4 additions & 0 deletions test/test_rand.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ using Test
t5 = rand!(BrownianTrait(t, "trait32", 0.0f0, σ² = 1.0f0), t)
@test typeof(getnodedata(t5, species[1])["trait32"]) typeof(1.0f0)
@test t5 t

@test !renamenode!(t, species[1], species[2])
@test all(renamenode!.(Ref(t), species, "new " .* species))
@test Set(getleafnames(t)) == Set("new " .* species)
end

@testset "Ultrametric()" begin
Expand Down
6 changes: 3 additions & 3 deletions test/test_routes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ using Test
tree = TreeType(species)
nr = createnode!(tree)
n2 = createnode!(tree)
createbranch!(tree, nr, n2)
createbranch!(tree, getnodename(tree, nr), getnodename(tree, n2))
n3 = createnode!(tree)
createbranch!(tree, n2, n3)
createbranch!(tree, getnode(tree, n2), getnode(tree, n3))
nh = nodehistory(tree, n2)
@test nr nh
@test Set(nh) == Set(push!(getancestors(tree, n2), n2))
Expand Down Expand Up @@ -44,7 +44,7 @@ using Test
getnodename(tree, root))[1]))
Set(getdescendants(tree, getnodename(tree, root)))
Set(getnodename.(tree, traversal(tree)))
@test length(traversal(tree)) == nnodes(tree)
@test length(collect(traversal(tree))) == nnodes(tree)
@test length(getdescendants(tree, root)) ==
length(branchfuture(tree, root))
nn = first(nodenamefilter(isleaf, tree))
Expand Down

0 comments on commit 4817752

Please sign in to comment.