diff --git a/docs/src/man/attributes.md b/docs/src/man/attributes.md index e7900dd9..b4c4b85b 100644 --- a/docs/src/man/attributes.md +++ b/docs/src/man/attributes.md @@ -84,6 +84,7 @@ getancestors getchildren getdescendants getsiblings +renamenode! ``` ## Methods on Branches diff --git a/src/API.jl b/src/API.jl index a7c4e6c5..596f0da4 100644 --- a/src/API.jl +++ b/src/API.jl @@ -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) = @@ -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) @@ -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). @@ -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]) @@ -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]) diff --git a/src/Interface.jl b/src/Interface.jl index b3fbcb98..fa2919e6 100644 --- a/src/Interface.jl +++ b/src/Interface.jl @@ -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)) @@ -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)) @@ -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. """ @@ -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 @@ -1053,7 +1062,7 @@ 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) """ @@ -1061,12 +1070,12 @@ getleafnames(tree::AbstractTree, order::TraversalOrder = preorder) = 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)) @@ -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}} diff --git a/src/Phylo.jl b/src/Phylo.jl index b7fd5978..5c9c0052 100644 --- a/src/Phylo.jl +++ b/src/Phylo.jl @@ -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 @@ -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 diff --git a/src/RecursiveTree.jl b/src/RecursiveTree.jl index dcfe35a1..e07ad78c 100644 --- a/src/RecursiveTree.jl +++ b/src/RecursiveTree.jl @@ -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 + diff --git a/src/show.jl b/src/show.jl index f87faa98..309ad7f0 100644 --- a/src/show.jl +++ b/src/show.jl @@ -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], ", ") * diff --git a/test/test_API.jl b/test/test_API.jl index a6c45b35..426463b0 100644 --- a/test/test_API.jl +++ b/test/test_API.jl @@ -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(); @@ -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 \ No newline at end of file diff --git a/test/test_LinkTree.jl b/test/test_LinkTree.jl index 7076651d..88fb6930 100644 --- a/test/test_LinkTree.jl +++ b/test/test_LinkTree.jl @@ -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} @@ -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 diff --git a/test/test_RecursiveTree.jl b/test/test_RecursiveTree.jl index 7d2cba1a..4531824a 100644 --- a/test/test_RecursiveTree.jl +++ b/test/test_RecursiveTree.jl @@ -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} @@ -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} @@ -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) diff --git a/test/test_Tree.jl b/test/test_Tree.jl index 294c3b2c..5e47bff6 100644 --- a/test/test_Tree.jl +++ b/test/test_Tree.jl @@ -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 diff --git a/test/test_rand.jl b/test/test_rand.jl index df879331..168f787b 100644 --- a/test/test_rand.jl +++ b/test/test_rand.jl @@ -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 diff --git a/test/test_routes.jl b/test/test_routes.jl index ac0873df..3b46f6d8 100644 --- a/test/test_routes.jl +++ b/test/test_routes.jl @@ -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)) @@ -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))