tripvote 1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tripVote/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env python
2
+
3
+ #############################################################################
4
+ ## this file is part of the TripletVoting package
5
+ ## see LICENSE for terms and conditions of usage.
6
+ #############################################################################
7
+
8
+ PROGRAM_NAME = "TripletVoting"
9
+ PROGRAM_AUTHOR = ["Uyen Mai","Siavash Mirarab"]
10
+ PROGRAM_LICENSE = "GNU General Public License, version 3"
11
+ PROGRAM_VERSION = "1.3"
12
+ PROGRAM_YEAR = "2021"
13
+ PROGRAM_INSTITUTE = "Department of Computer Science and Engineering, University of California at San Diego"
@@ -0,0 +1,323 @@
1
+ from tqdist import *
2
+ from triproot import *
3
+ from tripVote.tripvote_lib import tripVote
4
+ from tripVote.utils import reroot_at_edge, sample_by_depth, prune_long, sample_by_brlen
5
+ from treeswift import *
6
+ from math import log2, ceil, sqrt, exp
7
+ from random import choices
8
+
9
+ MIN_SMPL_SIZE = 3
10
+
11
+ def __local_search__(tree_obj,refTrees,diam,nsample=1,sample_size='sqrt'):
12
+ # tree_obj is a TreeSwift object; refTrees are Newick strings
13
+ # Assume: 1. tree_obj has been rerooted at the desired center of the local search
14
+ # 2. all ref trees have been preprocessed to be rooted at the missing taxon and removed it out
15
+ subtrees = []
16
+ close_leaves = []
17
+ search_space = []
18
+ for node in tree_obj.traverse_preorder():
19
+ if node.is_root():
20
+ node.d = 0
21
+ else:
22
+ node.d = node.parent.d + 1
23
+ if node.d <= diam+1:
24
+ search_space.append(node.label)
25
+ if node.is_leaf():
26
+ close_leaves.append(node.label)
27
+ elif node.d == diam+1:
28
+ subtrees.append(node)
29
+
30
+ #print(diam,search_space)
31
+ sample_refTrees = []
32
+ for i in range(nsample):
33
+ sample = [x for x in close_leaves]
34
+ for u in subtrees:
35
+ T = tree_obj.extract_subtree(u)
36
+ nleaf =len(list(T.traverse_leaves())) if sample_size=='full' else ceil(sqrt(len(list(T.traverse_leaves()))))
37
+ s = sample_by_depth(T,nleaf,1,do_extract=False)
38
+ sample += s
39
+
40
+ #nleaf = ceil(sqrt(len(list(tree_obj.traverse_leaves()))))
41
+ #sample = sample_by_depth(tree_obj,nleaf,1,do_extract=False)
42
+
43
+ for treeStr in refTrees:
44
+ r_tree = read_tree_newick(treeStr)
45
+ sample_refTrees.append(r_tree.extract_tree_with(sample,suppress_unifurcations=True).newick())
46
+
47
+ _,placement_label,tripScore = tripVote(tree_obj.newick(),sample_refTrees,search_space=set(search_space))
48
+ placement_node = None
49
+ for node in tree_obj.traverse_preorder():
50
+ if node.label == placement_label:
51
+ placement_node = node
52
+ break
53
+ if placement_node is None:
54
+ placement_edge = (None,None)
55
+ elif placement_node.is_root():
56
+ u,v = placement_node.children
57
+ placement_edge = (v.label,u.label)
58
+ else:
59
+ placement_edge = (placement_label,placement_node.parent.label)
60
+ return placement_edge, tripScore
61
+
62
+ def compute_d2root(myTree,placement_edge):
63
+ v_lb,u_lb = placement_edge
64
+ tree_obj = read_tree_newick(myTree)
65
+ placement_node = None
66
+
67
+ C = tree_obj.root.children
68
+ if len(C) == 2 and ((C[0].label == v_lb and C[1].label == u_lb) or (C[1].label == v_lb and C[0].label == u_lb)):
69
+ return 0
70
+
71
+ for root in C:
72
+ for node in root.traverse_preorder():
73
+ if (node.label == v_lb and (u_lb is None or node.parent.label == u_lb)) or (node.label == u_lb and node.parent.label == v_lb):
74
+ placement_node = node
75
+ found = True
76
+ break
77
+ if placement_node is not None:
78
+ break
79
+
80
+ node = placement_node
81
+ d = 0
82
+
83
+ if node is None:
84
+ print("Warning: Could not find optimal placement by tripVote. Aborting ...")
85
+ return None
86
+
87
+ while not node.is_root() and not node.parent.is_root():
88
+ d += 1
89
+ node = node.get_parent()
90
+ return d
91
+
92
+ def place_one_taxon_iter(myTree,refTrees,missing_taxon,max_depth='max',sample_size='sqrt',nsample=None):
93
+ # get initial guess
94
+ placement_label,score,d,rerooted_refTrees,_ = place_one_taxon(myTree,refTrees,missing_taxon,max_depth=max_depth,sample_size=sample_size,nsample=nsample,use_brlen=False,alpha=0)
95
+ print("Initial guess: " + placement_label + " " + str(score) + " " + str(d))
96
+
97
+ tree_obj = read_tree_newick(myTree)
98
+ diam = ceil(log2(len(list(tree_obj.traverse_leaves())))/2)
99
+
100
+ placement_edge = None
101
+ if placement_label == tree_obj.root.label:
102
+ u,v = tree_obj.root.children # assume the root has exactly 2 children
103
+ placement_edge = (u.label,v.label)
104
+ else:
105
+ for node in tree_obj.traverse_preorder():
106
+ if node.label == placement_label:
107
+ placement_edge = (node.label,node.parent.label)
108
+ break
109
+ if placement_edge is None:
110
+ print("Warning: could not find placement by tripVote. Aborting ...")
111
+ return placement_edge,d
112
+
113
+ # iterate
114
+ while diam > 0:
115
+ tree_obj = read_tree_newick(myTree)
116
+ placement_label = placement_edge[0]
117
+ # reroot at the previous placement site
118
+ new_root_node = None
119
+ for node in tree_obj.traverse_preorder():
120
+ if node.label == placement_label:
121
+ new_root_node = node
122
+ break
123
+ if new_root_node is None:
124
+ print("Warning: could not find placement by tripVote. Aborting ...")
125
+ return placement_edge,d
126
+ if not new_root_node is tree_obj.root:
127
+ reroot_at_edge(tree_obj,new_root_node,new_root_node.edge_length/2,root_label="ROOT")
128
+ # find new placement
129
+ placement_edge,_ = __local_search__(tree_obj,refTrees,diam,nsample=nsample,sample_size=sample_size)
130
+ d = compute_d2root(myTree,placement_edge)
131
+ print("Diameter " + str(diam) + ": " + str(placement_edge) + " " + str(d))
132
+ # reduce d to go to the next iteration
133
+ diam -= 1
134
+
135
+ return placement_edge,d
136
+
137
+ def __reroot_ref_tree__(tree_obj,missing_taxon,active_leafset):
138
+ # tree_obj: a treeswift object for a reference tree
139
+ # reroot the tree at the missing_taxon
140
+ new_root = None
141
+ for leaf in tree_obj.traverse_leaves():
142
+ if leaf.label == missing_taxon:
143
+ new_root = leaf
144
+ break
145
+
146
+ if new_root is None:
147
+ return False, tree_obj
148
+
149
+ reroot_at_edge(tree_obj,new_root,new_root.edge_length/2 if new_root.edge_length is not None else None)
150
+ C = tree_obj.root.children
151
+ c_star = None
152
+
153
+ for c in C:
154
+ if c.label == missing_taxon:
155
+ c_star = c
156
+ break
157
+ if c_star is None:
158
+ return False, tree_obj
159
+
160
+ tree_obj.root.remove_child(c_star)
161
+ tree_obj.suppress_unifurcations()
162
+
163
+ tree_obj = tree_obj.extract_tree_with(active_leafset,suppress_unifurcations=True)
164
+
165
+ return True, tree_obj
166
+
167
+ def __label_tree__(tree_obj):
168
+ i = 0
169
+ for node in tree_obj.traverse_preorder():
170
+ if not node.is_leaf():
171
+ node.label = 'I' + str(i)
172
+ i += 1
173
+
174
+ def place_one_taxon(myTree,refTrees,missing_taxon,max_depth='max',sample_size='sqrt',nsample='default',use_brlen=False,pseudo=1e-3,alpha=0):
175
+ # remove all trees in refTrees that do not have the missing_taxon
176
+ # and assume myTree is missing the missing_taxon
177
+ # myTree: a newick string. Assume it has unique labeling for all nodes
178
+ # refTrees: a list of newick strings
179
+ # missing_taxon: a leaf label (string)
180
+ # IMPORTANT: assumming all trees have unique labeling for each node
181
+ # max_depth: only count the triplets with depth up to max_depth.
182
+ # Special case: max_depth = 'max': include all triplets (default)
183
+ # max_depth = 'log2': ceil(log2(n)) where n is the number of leaves
184
+ tree_obj = read_tree_newick(myTree)
185
+ active_leafset = set(leaf.label for leaf in tree_obj.traverse_leaves())
186
+
187
+ rerooted_refTrees = []
188
+ sample_refTrees = []
189
+ W = []
190
+ i = 0
191
+ for rstr in refTrees:
192
+ tree_obj = read_tree_newick(rstr)
193
+ n = len(list(tree_obj.traverse_leaves()))
194
+
195
+ if max_depth == 'max':
196
+ D = n
197
+ elif max_depth == 'log2':
198
+ D = ceil(log2(n))
199
+ else:
200
+ D = int(max_depth)
201
+
202
+ success_flag, tree_obj = __reroot_ref_tree__(tree_obj,missing_taxon,active_leafset)
203
+ if success_flag:
204
+ rerooted_refTrees.append(tree_obj.newick())
205
+
206
+ if alpha != 0:
207
+ qdist = quartet_distance(myTree,tree_obj.newick())
208
+ w = exp(-alpha*qdist)
209
+ else:
210
+ w = 1
211
+
212
+ if D < n:
213
+ prune_long(tree_obj,D)
214
+
215
+ sample_trees = []
216
+ if (len(list(tree_obj.traverse_leaves())) >= 3):
217
+ if not nsample:
218
+ sample_trees = [tree_obj.newick()]
219
+ else:
220
+ nleaf = len(list(tree_obj.traverse_leaves()))
221
+ if sample_size == 'full':
222
+ sample_size = nleaf
223
+ elif sample_size == 'sqrt':
224
+ sample_size = ceil(sqrt(nleaf))
225
+ sample_size = max(MIN_SMPL_SIZE,sample_size)
226
+ if nsample == 'default':
227
+ nsample = ceil(90/sample_size)
228
+ if use_brlen:
229
+ sample_trees = sample_by_brlen(tree_obj,sample_size,nsample,pseudo=1e-3)
230
+ else:
231
+ sample_trees = sample_by_depth(tree_obj,sample_size,nsample)
232
+ sample_refTrees += sample_trees
233
+ W += [w]*len(sample_trees)
234
+
235
+ #with open("temp_refs.trees",'w') as f:
236
+ # f.write('\n'.join(sample_refTrees))
237
+
238
+ _,placement_label,tripScore = tripVote(myTree,sample_refTrees,W,do_indexing=False)
239
+ tree_obj = read_tree_newick(myTree)
240
+ placement_node = None
241
+ for node in tree_obj.traverse_preorder():
242
+ if node.label == placement_label:
243
+ placement_node = node
244
+ break
245
+ node = placement_node
246
+ d = 0
247
+ if node is None:
248
+ print("Warning: Could not find optimal placement by tripVote. Aborting ...")
249
+ return None,None
250
+ while not node.is_root() and not node.parent.is_root():
251
+ d += 1
252
+ node = node.get_parent()
253
+
254
+ # insert the missing_taxon onto the tree
255
+ new_node = Node()
256
+ new_node.label = missing_taxon
257
+ if placement_node.is_root():
258
+ new_root = Node()
259
+ new_root.label = "p_" + missing_taxon
260
+ new_root.add_child(placement_node)
261
+ new_root.add_child(new_node)
262
+ tree_obj.root = new_root
263
+ else:
264
+ v = placement_node
265
+ u = v.parent
266
+ u.remove_child(v)
267
+ w = Node()
268
+ w.label = "p_" + missing_taxon
269
+ u.add_child(w)
270
+ w.add_child(v)
271
+ w.add_child(new_node)
272
+
273
+ return placement_label, tripScore, d, rerooted_refTrees, tree_obj.newick()
274
+
275
+ def place_taxa(myTree,refTrees,missing_taxa,sample_size='sqrt',nsample='default'):
276
+ tree_obj = read_tree_newick(myTree)
277
+ __label_tree__(tree_obj)
278
+ updated_tree = tree_obj.newick()
279
+ for taxon in missing_taxa:
280
+ _,_,_,_,updated_tree = place_one_taxon(updated_tree,refTrees,taxon,max_depth='max',sample_size=sample_size,nsample=nsample,use_brlen=False,pseudo=1e-3,alpha=0)
281
+ return updated_tree
282
+
283
+
284
+ def complete_gene_trees(myTrees,refTrees=None,sample_size='sqrt',nsample='default',placement_taxa=None):
285
+ # If refTrees is None, then use the other trees in myTrees as references
286
+ myTrees_labeled = []
287
+
288
+ for treeStr in myTrees:
289
+ tree_obj = read_tree_newick(treeStr)
290
+ __label_tree__(tree_obj)
291
+ myTrees_labeled.append(tree_obj.newick())
292
+
293
+ taxon_dict = {} # mapping taxon name to frequency
294
+ refs = refTrees if refTrees is not None else myTrees_labeled
295
+
296
+ if placement_taxa is None:
297
+ for tr in refs:
298
+ tree_obj = read_tree_newick(tr)
299
+ for taxon in tree_obj.traverse_leaves():
300
+ x = taxon.label
301
+ taxon_dict[x] = taxon_dict[x] + 1 if x in taxon_dict else 1
302
+ taxon_list = sorted(taxon_dict.items(), key=lambda item: -item[1])
303
+ else:
304
+ taxon_list = [(x,1) for x in placement_taxa]
305
+
306
+ completed_trees = []
307
+ for i,treeStr in enumerate(myTrees_labeled):
308
+ # preprocess
309
+ refs = refTrees if refTrees is not None else myTrees_labeled[:i] + myTrees_labeled[i+1:]
310
+ tree_obj = read_tree_newick(treeStr)
311
+ curr_leaf_set = set([leaf.label for leaf in tree_obj.traverse_leaves()])
312
+
313
+ # complete this tree
314
+ updated_tree = treeStr
315
+ for x,c in taxon_list:
316
+ if x not in curr_leaf_set:
317
+ # x is missing in this tree, now we insert it
318
+ print("Adding " + x + " to tree " + str(i+1)) # + ". Present in " + str(c) + " reference trees")
319
+ _,_,_,_,updated_tree = place_one_taxon(updated_tree,refs,x,max_depth='max',sample_size=sample_size,nsample=nsample,use_brlen=False,pseudo=1e-3,alpha=0)
320
+ completed_trees.append(updated_tree)
321
+ print("Completed tree " + str(i+1))
322
+ print(updated_tree)
323
+ return completed_trees
@@ -0,0 +1,224 @@
1
+ from treeswift import *
2
+ from tqdist import *
3
+ from triproot import *
4
+ from math import exp,log,log2,ceil,sqrt
5
+ import random
6
+ from tripVote.utils import sample_by_depth, prune_long, reroot_at_edge
7
+
8
+ def entropy(C):
9
+ # compute entropy
10
+ # first normalize
11
+ s = sum(C)
12
+ C_norm = [x/s for x in C]
13
+ return sum(-x*log(x) for x in C_norm)
14
+
15
+ def avg_all_pairs_dtrpl(trees):
16
+ # compute the average triplet distances of all pairs
17
+ # trees are a list of newick strings
18
+ count = 0
19
+ Sum = 0
20
+ n = len(trees)
21
+ for i in range(n):
22
+ for j in range(i):
23
+ count += 1
24
+ Sum += triplet_distance(trees[i],trees[j])
25
+ return Sum/count
26
+
27
+ def tripVote_crossValidate(refTrees,fraction_test=0.1,n_vote_groups=10,alphas=list(range(1,21))):
28
+ # inputs: the refTrees are all newick strings
29
+
30
+ # indexing
31
+ refTrees_idx = []
32
+ for treestr in refTrees:
33
+ tree_obj = read_tree_newick(treestr)
34
+ idx = 0
35
+ for node in tree_obj.traverse_preorder():
36
+ if not node.is_leaf():
37
+ ID = 'I' + str(idx)
38
+ node.label = ID
39
+ idx += 1
40
+ refTrees_idx.append(tree_obj.newick())
41
+
42
+ # shuffle
43
+ temp = refTrees_idx[1:]
44
+ random.shuffle(temp)
45
+ refTrees_idx = [refTrees_idx[0]] + temp
46
+ #random.shuffle(refTrees_idx)
47
+
48
+ # split out into 1 testing group and multiple voting groups
49
+ ntrees = len(refTrees_idx)
50
+ test_size = round(fraction_test * ntrees)
51
+ nv = (ntrees - test_size) // n_vote_groups
52
+ rv = (ntrees - test_size) % n_vote_groups
53
+
54
+ test_group = refTrees_idx[ntrees-test_size:]
55
+ vote_groups = []
56
+ j = 0
57
+ for i in range(rv):
58
+ vote_groups.append(refTrees_idx[j:j+nv+1])
59
+ j += nv+1
60
+ for i in range(n_vote_groups-rv):
61
+ vote_groups.append(refTrees_idx[j:j+nv])
62
+ j += nv
63
+
64
+ # compute weight matrices
65
+ Ws = []
66
+ for vTrees in vote_groups:
67
+ W = [ [0]*len(vTrees) for i in range(test_size) ]
68
+ for i,tT in enumerate(test_group):
69
+ for j,vT in enumerate(vTrees):
70
+ W[i][j] = quartet_distance(tT,vT)
71
+ Ws.append(W)
72
+
73
+ # voting
74
+ alpha_scoring = {alpha:[]*test_size for alpha in alphas}
75
+ for alpha in alphas:
76
+ for i,myTree in enumerate(test_group):
77
+ #votes = {}
78
+ rerooted_trees = []
79
+ myTree_obj = read_tree_newick(myTree)
80
+ root_ids = [x.label for x in myTree_obj.root.children + [myTree_obj.root]]
81
+ for j,vTrees in enumerate(vote_groups):
82
+ weights = Ws[j][i]
83
+ rT,best_id,_ = tripVote(myTree,vTrees,weights,alpha=alpha,do_indexing=False)
84
+ rerooted_trees.append(rT)
85
+ #if best_id in root_ids:
86
+ # best_id = 'I0'
87
+ #votes[best_id] = 1 if best_id not in votes else votes[best_id]+1
88
+ #e = entropy(votes.values())
89
+ #e = max(votes.values())
90
+ #alpha_scoring[alpha].append(e)
91
+ #alpha_scoring[alpha].append(votes)
92
+ S = avg_all_pairs_dtrpl(rerooted_trees)
93
+ alpha_scoring[alpha].append(S)
94
+
95
+ return alpha_scoring
96
+
97
+ def tripVote(myTree,refTrees,W=None,do_indexing=True,search_space=None):
98
+ myTree_obj = read_tree_newick(myTree)
99
+ if W is None:
100
+ W = [1.0]*len(refTrees)
101
+
102
+ # indexing
103
+ id2lb = {}
104
+ if do_indexing:
105
+ idx = 0
106
+ for node in myTree_obj.traverse_preorder():
107
+ if not node.is_leaf():
108
+ ID = 'I' + str(idx)
109
+ id2lb[ID] = node.label
110
+ node.label = ID
111
+ idx += 1
112
+ treestr = myTree_obj.newick()
113
+
114
+ # calling tripRootScore
115
+ id2score = {}
116
+ #count=1
117
+ for rtree,w in zip(refTrees,W):
118
+ mystr = tripRootScore(rtree,treestr)
119
+ if not mystr: # empty string returned by tripRootScore; usually because the refTree is too small after pruning to pair with the query tree
120
+ continue
121
+ for item in mystr.split(','):
122
+ ID,s = item.split(':')
123
+ score = float(s)
124
+ if ID not in id2score:
125
+ id2score[ID] = w*score
126
+ else:
127
+ id2score[ID] += w*score
128
+ best_id = None
129
+ max_score = -1
130
+ for ID in id2score:
131
+ if id2score[ID] > max_score:
132
+ lb = id2lb[ID] if ID in id2lb else ID
133
+ if (search_space is None) or (lb in search_space):
134
+ best_id = ID
135
+ max_score = id2score[ID]
136
+
137
+ #for ID in id2score:
138
+ # if abs(id2score[ID]-max_score) < 1e-4 and ID != best_id:
139
+ # break
140
+
141
+ # turn back the original label and find the rooting position
142
+ optimal_root = None
143
+ for node in myTree_obj.traverse_preorder():
144
+ if node.label == best_id and not node.is_root():
145
+ optimal_root = node
146
+ if not node.is_leaf() and do_indexing:
147
+ node.label = id2lb[node.label]
148
+ if best_id is None:
149
+ raise ValueError("Could not find an optimal root for the input tree")
150
+ return best_id,id2lb[best_id] if best_id in id2lb else best_id, max_score
151
+
152
+ def tripVote_root(myTree,refTrees,max_depth='max',sample_size='full',nsample=None): #,alpha=0):
153
+ new_refTrees = []
154
+ #W = []
155
+ for rstr in refTrees:
156
+ #qdist = quartet_distance(rstr,myTree)
157
+ #w = exp(-alpha*qdist)
158
+ tree_obj = read_tree_newick(rstr)
159
+ n = len(list(tree_obj.traverse_leaves()))
160
+ if max_depth == 'max':
161
+ d = n
162
+ elif max_depth == 'log2':
163
+ d = ceil(log2(n))
164
+ else:
165
+ d = int(max_depth)
166
+ # prune deep nodes
167
+ if d < n:
168
+ prune_long(tree_obj,d)
169
+ # sampling
170
+ if (len(list(tree_obj.traverse_leaves())) >= 3):
171
+ if nsample is None:
172
+ new_tree = tree_obj.newick()
173
+ new_refTrees.append(new_tree)
174
+ #W.append(w)
175
+ else:
176
+ nleaf = len(list(tree_obj.traverse_leaves()))
177
+ if sample_size == 'full':
178
+ sampled_nleaf = nleaf
179
+ elif sample_size == 'sqrt':
180
+ sampled_nleaf = ceil(sqrt(nleaf))
181
+ else:
182
+ sampled_nleaf = int(sample_size)
183
+ S = sample_by_depth(tree_obj,sampled_nleaf,nsample)
184
+ new_refTrees += S
185
+ #W += [w]*len(S)
186
+ # read and label myTree
187
+ tree_obj = read_tree_newick(myTree)
188
+ idx = 0
189
+ for node in tree_obj.traverse_preorder():
190
+ if not node.is_leaf():
191
+ ID = 'I' + str(idx)
192
+ node.label = ID
193
+ idx += 1
194
+ _,root_label,score = tripVote(tree_obj.newick(),new_refTrees,do_indexing=False) #,W=W)
195
+ print("TripVote score: " + str(score))
196
+ #tree_obj = read_tree_newick(myTree)
197
+ root_node = None
198
+ for node in tree_obj.traverse_preorder():
199
+ if node.label == root_label:
200
+ root_node = node
201
+ break
202
+ node = root_node
203
+ d2root = 0
204
+ if root_node is None:
205
+ raise ValueError("Could not find optimal root node in the input tree")
206
+ while not node.is_root() and not node.parent.is_root():
207
+ d2root += 1
208
+ node = node.get_parent()
209
+
210
+ if not root_node.is_root():
211
+ root_length = root_node.edge_length/2 if root_node.edge_length is not None else None
212
+ reroot_at_edge(tree_obj,root_node,root_length)
213
+
214
+ rerooted_tree = tree_obj.newick()
215
+
216
+ return rerooted_tree, d2root
217
+
218
+ if __name__ == "__main__":
219
+ infile = "test_cases/MV.trees"
220
+ with open(infile,'r') as fin:
221
+ refTrees = fin.read().split("\n")[:-1]
222
+ alpha_scoring = tripVote_crossValidate(refTrees,fraction_test=0.01,n_vote_groups=10,alphas=list(x for x in range(0,21)))
223
+ for alpha in alpha_scoring:
224
+ print(alpha,sum(alpha_scoring[alpha]))