-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathHostTreeGen.py
More file actions
193 lines (161 loc) · 6.3 KB
/
Copy pathHostTreeGen.py
File metadata and controls
193 lines (161 loc) · 6.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
from ete3 import Tree
import numpy as np
import stats
#####################################
# #
# Host Tree Generation #
# #
#####################################
def assignBranchLengths(tree, treeHeight, heightDistribution):
"""
Assigns branch lengths to all nodes of the tree such that the sum of branch lengths
on any root to leaf path is equal to hostLength. Only used on a whole tree (host
trees here, for the full version see shilpa/scripts/simulation.py).
"""
leaves = [leaf for leaf in tree]
treeHeight = float(treeHeight)
for node in tree.traverse():
node.dist = 0
lset = []
for leaf in leaves:
if node.get_common_ancestor(leaf) == node:
lset.append(leaf)
height = np.min([leaf.get_distance(node, topology_only=True) for leaf in lset])
node.add_feature('height', height)
for node in tree.traverse():
remainingDist = treeHeight - (node.get_distance(tree) + tree.dist)
if node.height == 0:
node.dist = remainingDist
else:
node.dist = heightDistribution(remainingDist / (node.height + 1.))
def createRandomTopology(numLeaves, treeHeight, heightDistribution):
"""
Generates a host tree with the specified number of leaves and approximate overall height.
Args:
numLeaves (int ): The number of leaves desired in the host tree
treeHeight (float): The average overall length of a root to leaf path
heightDistribution (func):
Output:
host (Tree): The host tree in ete3 Tree format
"""
host = Tree()
host.populate(numLeaves)
nameCounter = 0
for node in host.traverse():
node.name = "h" + str(nameCounter)
nameCounter += 1
assignBranchLengths(host, treeHeight, heightDistribution)
return host
def birthDeathTree(birthRate, deathRate, treeHeight):
"""
Generates a tree topology according to the birth-death model.
Args:
birthRate (float): birth rate
deathRate (float): death rate
treeHeight (float): The average overall length of a root to leaf path
numLeaves (int): The number of leaves desired at the end of the run. If
the input is <= 0, this parameter is ignored and
"""
birthRate = float(birthRate)
deathRate = float(deathRate)
host = Tree()
host.dist = 0
lineages = [(host, treeHeight)]
while lineages != []:
#waiting time is exp(b + d), P(b) = b/(b+d), P(d) = 1 - P(b)
node, height = lineages.pop(0)
eventTime = stats.exp(1./(1./birthRate + 1./deathRate))
#event occurs
if eventTime <= height:
#duplication
if np.random.random() < birthRate / (birthRate + deathRate):
left = node.add_child(dist=eventTime)
right = node.add_child(dist=eventTime)
lineages.append((left, height - eventTime))
lineages.append((right, height - eventTime))
#loss: Remove from queue, delete node later (cleanup process)
else:
node.dist *= -1
#If no event occurs, credit remaining branch length to this node
else:
node.dist += height
if host.children == []:
host.name = "h0"
return host
#remove lost nodes
for node in host.traverse():
if node.dist < 0:
node.up.remove_child(node)
#remove nodes with only one child (ensure full binary tree)
for node in [a for a in host.traverse()]:
if len(node.children) == 1:
#This is the root node
if node.up == None:
host.children[0].dist += host.dist
host = host.children[0]
host.up = None
else:
parent = node.up
child = node.children[0]
child.dist += node.dist
child.up = parent
parent.remove_child(node)
parent.children.append(child)
nameCounter = 0
for node in host.traverse():
node.name = "h" + str(nameCounter)
nameCounter += 1
return host
def BDMinMax(birthRate, deathRate, treeHeight, minLeaves = 0, maxLeaves = float('inf')):
"""
Creates a birth death tree within the desired range of leaves. Produces a warning if
the input birth/death rates are unlikely to produce a tree with the desired number of
leaves.
"""
#TODO: ADD WARNING IF PARAMETERS ARE MISMATCHED -> sum of exps is gaussian (goddammit)
"""
mean = 1./(1./birthRate - 1./deathRate)
if minLeaves > 2 * mean or maxLeaves < 0:
print
"""
incorrect = True
while(incorrect):
host = birthDeathTree(birthRate, deathRate, treeHeight)
numLeaves = len([leaf for leaf in host])
incorrect = numLeaves <= minLeaves or numLeaves >= maxLeaves
return host
def readHostTree(treeFile, treeHeight = -1):
"""
Reads input file and ensures that every node has a label (or labels it otherwise) and
optionally rescales the branch lengths so that the average height is the desired one.
Unlike readTree in TreeUtils, this function is intended to read trees not created by
this package.
Args:
treeFile (str ): name of file to read
treeHeight (float ): Rescales branch lengths such that the mean root to leaf path
matches the desired tree height. Ignored if treeHeight <= 0
Output:
host (Tree): The host tree in ete3 Tree format
"""
host = Tree(treeFile)
#Ensure all nodes are named
nameCounter = 0
for node in host.traverse():
if node.name == '':
node.name = "h" + str(nameCounter)
nameCounter += 1
#If rescaling is desired
if treeHeight > 0:
height = 0.0
for leaf in host:
height += leaf.get_distance(host)
height /= len([leaf for leaf in host])
height += host.dist #the "stem" before the root isn't included in a root to leaf path
for node in host.traverse():
node.dist /= height
return host
#Test cases
if __name__ == "__main__":
t = birthDeathTree(0.3,0.1,1)
print t.get_ascii()
print t.get_ascii(attributes=['dist'])