Skip to content

Commit f730187

Browse files
authored
new features: getlevel, istreechild, isgalled (#219)
also: permissions for TagBot to work --------- Co-authored-by: Nathan Kolbow
1 parent 752f516 commit f730187

12 files changed

+588
-115
lines changed

.github/workflows/TagBot.yml

+16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ on:
44
types:
55
- created
66
workflow_dispatch:
7+
inputs:
8+
lookback:
9+
default: "3"
10+
permissions:
11+
actions: read
12+
checks: read
13+
contents: write
14+
deployments: read
15+
issues: read
16+
discussions: read
17+
packages: read
18+
pages: read
19+
pull-requests: read
20+
repository-projects: read
21+
security-events: read
22+
statuses: read
723
jobs:
824
TagBot:
925
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
docs/build/
55
docs/site/
66
docs/*.toml
7+
Manifest.toml
78

89
# benchmark tuning parameters #
910
benchmark/params.json

docs/make.jl

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
using Documenter
22

3-
# fixit: this installs the dev version of PhyloPlots for compatibility.
4-
# edit back to install the "master" version of PhyloPlots
3+
# this installs the dev version of PhyloPlots for compatibility.
54
using Pkg
6-
Pkg.add(PackageSpec(name="PhyloPlots", rev="dev11"))
5+
Pkg.add(PackageSpec(name="PhyloPlots", rev="master"))
76

87

98
using PhyloNetworks

docs/src/man/netmanipulation.md

+12-4
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,23 @@ each hybrid node has exactly 2 parents (never more).
3030
- [`displayedtrees`](@ref) or [`majortree`](@ref) to get the displayed trees
3131
or major tree, respectively
3232
- [`hardwiredclusters`](@ref) to get all clusters of taxa on a network
33-
- [`biconnectedcomponents`](@ref) and [`PhyloNetworks.blobinfo`](@ref)
34-
give information about the blobs found within a network;
33+
- [`checkroot!`](@ref) to check that the graph is a valid semidirected
34+
network with a root node in a admissible rooting position
35+
36+
on the network complexity:
37+
- [`istreechild`](@ref)
38+
- [`hashybridladder`](@ref) -- a hybrid ladder is also called a "stack":
39+
one hybrid parent of another
40+
- [`isgalled`](@ref) in the sense of a galled *network*
41+
- [`getlevel`](@ref)
42+
- [`biconnectedcomponents`](@ref) and
43+
[`PhyloNetworks.process_biconnectedcomponents!`](@ref)
44+
to calculate (and store) the blobs in a network;
3545
also
3646
[`PhyloNetworks.biconnectedcomponent_entrynodes`](@ref),
3747
[`PhyloNetworks.biconnectedcomponent_exitnodes`](@ref),
3848
and [`blobdecomposition`](@ref)
3949
- [`treeedgecomponents`](@ref) are the components after removing hybrid edges
40-
- [`checkroot!`](@ref) to check that the graph is a valid semidirected
41-
network with a root node in a admissible rooting position
4250

4351
The following functions all compute distances from the root to each node.
4452
Their differ in how they handle time inconsistency: when the distance from

src/PhyloNetworks.jl

+4
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ module PhyloNetworks
7474
getparentedgeminor,
7575
getpartneredge,
7676
tiplabels,
77+
hashybridladder,
78+
istreechild,
79+
isgalled,
80+
getlevel,
7781
## Network Manipulation
7882
nameinternalnodes!,
7983
rootatnode!,

src/auxiliary.jl

+128
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,134 @@ function hashybridladder(net::HybridNetwork)
16301630
return false
16311631
end
16321632

1633+
"""
1634+
istreechild(net::HybridNetwork)
1635+
1636+
Tuple `(rooted, semi_weakly, semi_strongly)` in which each element is true
1637+
or false, to indicate if `net` is / is not
1638+
- tree-child as a rooted network
1639+
- weakly tree-child as a semidirected network
1640+
- strongly tree-child as a semidirected network
1641+
1642+
A rooted network is tree-child if all of its internal nodes have at least one
1643+
child that is a tree node (or equivalently, one child edge that is a tree edge).
1644+
A semidirected network is strongly (resp. weakly) tree-child if all (resp. at
1645+
least one) of its rooted partners are tree-child.
1646+
1647+
Note that degree-2 nodes are not suppressed: a degree-2 node with a single
1648+
hybrid child causes the network to *not* be tree-child, despite the fact that
1649+
suppressing this node may render the reduced network tree-child.
1650+
1651+
Assumes that tree edges are directly correctly according to the current root.
1652+
"""
1653+
function istreechild(net::HybridNetwork)
1654+
getroot(net).leaf && error("istreechild requires a root that is not a leaf.")
1655+
hybparent_visited = Set{Int}()
1656+
# not tree-child if 2+ weak nodes, weakly/strongly tree-child if 1/0 weak node
1657+
hasWatroot = false # did we already find 1 weak node?
1658+
isTCrooted = true
1659+
for h in net.hybrid
1660+
par = getparents(h)
1661+
for n in par
1662+
n.number hybparent_visited && continue
1663+
push!(hybparent_visited, n.number)
1664+
ntreechild = 0
1665+
for e in n.edge
1666+
if !e.hybrid && getparent(e) == n
1667+
ntreechild += 1
1668+
end
1669+
end
1670+
# a hybrid ladder could be okay if polytomy: tree child + hybrid child(ren)
1671+
if ntreechild > 1 || (n.hybrid && ntreechild > 0)
1672+
continue
1673+
end
1674+
if n.hybrid # hybrid ladder with no tree child
1675+
return (false, false, false)
1676+
end
1677+
# at this point, n is a tree node
1678+
atroot = (getroot(net) == n)
1679+
ntreechild==1 && !atroot && continue # incident to 2 tree edges
1680+
if ntreechild == 0
1681+
isTCrooted = false
1682+
# check if no parent tree edge could be a child under another rooting
1683+
if atroot || !getparentedge(n).containroot
1684+
return (false, false, false)
1685+
end
1686+
end
1687+
# now n is a weak node: parent to hybrid edge(s) + incident 1 root component edge
1688+
# (0 tree child but parent that may contain the root, or root with 1 tree child)
1689+
if hasWatroot # another weak node was found before
1690+
!isTCrooted || error("the network should have already been flagged as not tree-child")
1691+
return (false, false, false)
1692+
else
1693+
hasWatroot = true
1694+
end
1695+
end
1696+
end
1697+
return (isTCrooted, true, !hasWatroot) # weakly tree-child
1698+
end
1699+
1700+
"""
1701+
isgalled(net::HybridNetwork, checkpreorder::Bool=true)
1702+
1703+
`true` (resp. `false`) if `net` is (resp. is not) a galled network, assuming
1704+
that `net` is bicombining (every hybrid has exactly 2 parents).
1705+
A network is galled if every hybrid node is contained in a cycle that has its
1706+
2 hybrid parent edges and otherwise tree edges only. See for example
1707+
[Huson, Rupp & Scornavacca (2010)](https://doi.org/10.1017/CBO9780511974076) and
1708+
[Gunawan, DasGupta & Zhang (2017)](https://doi.org/10.1016/j.ic.2016.11.001).
1709+
Equivalently, a bicombining network is galled if, for any hybrid node `n`,
1710+
both of its parent edges originate from the same tree component.
1711+
We get the tree components by deleting all hybrid edges from the network:
1712+
we are left with a forest of trees.
1713+
1714+
The network is traversed in preorder (from the root to the leaves).
1715+
Turn `checkpreorder` to `false` if the pre-ordering was run and is known to be
1716+
up-to-date.
1717+
Assumption: tree edges are directly correctly according to the current root.
1718+
"""
1719+
function isgalled(net::HybridNetwork, checkpreorder::Bool=true)
1720+
checkpreorder && preorder!(net)
1721+
isG = trues(1) # mutable
1722+
# uses a vector node preorder index => integer unique to each tree component
1723+
# a tree component ID = number of its node
1724+
traversal_preorder(
1725+
net.vec_node,
1726+
init_isgalled, # initialize vector node index => tree component ID
1727+
traversalupdate_default!, # do nothing at the root
1728+
updatetree_isgalled,
1729+
updatehybrid_isgalled,
1730+
isG
1731+
)
1732+
return isG[1]
1733+
end
1734+
function init_isgalled(nodes::Vector{Node}, isG)
1735+
V = zeros(Int,length(nodes))
1736+
V[1] = nodes[1].number # root = 1st in preorder
1737+
return V
1738+
end
1739+
function updatetree_isgalled(V::Vector, i::Int, pari::Int, ::Edge, isG)
1740+
V[i] = V[pari] # node i is same tree component as its parent
1741+
return true
1742+
end
1743+
function updatehybrid_isgalled(
1744+
V::Vector,
1745+
i::Int,
1746+
parindx::AbstractVector{Int},
1747+
paredge::AbstractVector{Edge},
1748+
isG
1749+
)
1750+
# check bicombining, and both parents from same TC
1751+
length(parindx) == 2 || error("the network is not bicombining")
1752+
if V[parindx[1]] != V[parindx[2]]
1753+
isG[1] = false
1754+
return false # stop the traversal
1755+
else
1756+
V[i] = getchild(paredge[1]).number # start new tree component
1757+
end
1758+
return true
1759+
end
1760+
16331761
"""
16341762
shrinkedge!(net::HybridNetwork, edge::Edge)
16351763

src/compareNetworks.jl

+7-5
Original file line numberDiff line numberDiff line change
@@ -361,11 +361,13 @@ tuple of 2 vectors (node) of vector (clade) of vectors (taxon in clade):
361361
for the right child of the node (unless the node is of degree 2 or is a polytomy).
362362
`above[n]` contains the grade of clades above node number `n`.
363363
364-
WARNING: assumes that
365-
1. node numbers and edge numbers can be used as indices, that is,
366-
be all distinct, positive, covering exactly 1:#nodes and 1:#edges.
367-
2. edges are corrected directed (`ischild1` is up-to-date) and
368-
nodes have been pre-ordered already (field `vec_node` up-to-date).
364+
Warning: assumes that
365+
- the network is a tree (this is checked)
366+
- node numbers and edge numbers can be used as indices, that is,
367+
be all distinct, positive, covering exactly 1:#nodes and 1:#edges
368+
(this is checked for nodes).
369+
- edges are corrected directed (`ischild1` is up-to-date) and
370+
nodes have been pre-ordered already (field `vec_node` up-to-date).
369371
370372
# examples
371373
```jldoctest

0 commit comments

Comments
 (0)