diff --git a/src/PhyloNetwork/classes.py b/src/PhyloNetwork/classes.py index c3db8e1..402f778 100644 --- a/src/PhyloNetwork/classes.py +++ b/src/PhyloNetwork/classes.py @@ -14,10 +14,40 @@ from .utils import total_cmp import permutations from .memoize import memoize_method +from .exceptions import MalformedNewickException class PhyloNetwork(DiGraph): """ Main class for Phylogenetic Networks (with nested taxa). + + You can create a PhyloNetwork with its eNewick representation:: + + >>> network = PhyloNetwork(eNewick="((1,2),(3,4)5);") + >>> network.taxa() + ... ['1','2','3','4','5'] + >>> network.leaves() + ... ['_3', '_4', '_6', '_7'] + >>> network.label('_6') + ... '3' + + If your eNewick string is malformed, you'll receive a MalformedNewickException:: + + >>> network = PhyloNetwork(eNewick="(1)") + ... Traceback (most recent call last): + ... (...) + ... PhyloNetwork.classes.MalformedNewickException: Malformed eNewick string + + You can also start with an existing networkx graph:: + + >>> graph = networkx.DiGraph() + >>> graph.add_nodes_from(range(5)) + >>> graph.add_edges_from([(0,1), (0,2), (1,3), (1,4)]) + >>> network = PhyloNetwork(data = graph) + >>> network.leaves() + ... [2, 3, 4] + >>> network.taxa() + ... [] + """ def __init__(self, data=None, name='', eNewick=None, ignore_prefix=None, id_offset=0): @@ -26,19 +56,76 @@ def __init__(self, data=None, name='', eNewick=None, ignore_prefix=None, id_offs self.name=name self._labels={} self._lastlabel=id_offset + self.cache = {} if eNewick != None: - self.from_eNewick(eNewick,ignore_prefix=ignore_prefix) + self._from_eNewick(eNewick, ignore_prefix=ignore_prefix) @memoize_method def is_phylogenetic_network(self): + """ + Returns True if the network is a Phylogenetic Network. False otherwise. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((1,2),(3,4)5);") + >>> network.is_phylogenetic_network() + ... True + >>> graph = networkx.DiGraph() + >>> graph.add_nodes_from(range(4)) + >>> graph.add_edges_from([(0,1), (0,2), (1,2), (2,3), (3,1)]) + >>> PhyloNetwork(data=graph).is_phylogenetic_network() + ... False + + """ if not is_directed_acyclic_graph(self): return False return True + + def set_label(self, node, label): + """ + Set a new label to a node. + + EXAMPLE:: + + >>> graph = networkx.DiGraph() + >>> graph.add_nodes_from(range(5)) + >>> graph.add_edges_from([(0,1), (0,2), (1,3), (1,4)]) + >>> network = PhyloNetwork(data = graph) + >>> network.leaves() + ... [2, 3, 4] + >>> network.taxa() + ... [] + >>> network.set_label(2, 'Label 1') + >>> network.set_label(3, 'Label 2') + >>> network.set_label(4, 'Label 3') + >>> network.taxa() + ... ['Label 1', 'Label 2', 'Label 3'] + >>> network.set_label(2, 'New label') + >>> network.taxa() + ... ['Label 2', 'Label 3', 'New label'] + + """ + if node in self: + self._labels[node] = label + self.cache = {} @memoize_method def taxa(self): """ Returns the taxa (set of labels) of self. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((1,2),(3,4)5);") + >>> network.taxa() + ... ['1', '2', '3', '4', '5'] + >>> network.leaves() + ... ['_3', '_4', '_6', '_7'] + >>> network.label('_6') + ... '3' + >>> network.nodes() + ... ['_7', '_6', '_5', '_4', '_3', '_2', '_1'] + """ taxa = list(set(self._labels.values())) taxa.sort() @@ -47,58 +134,198 @@ def taxa(self): def label(self,node): """ Returns the label of node, or None if not labelled. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((1,2),(3,4)5);") + >>> network.taxa() + ... ['1', '2', '3', '4', '5'] + >>> network.leaves() + ... ['_3', '_4', '_6', '_7'] + >>> network.label('_6') + ... '3' + >>> network.nodes() + ... ['_7', '_6', '_5', '_4', '_3', '_2', '_1'] + >>> network.label('_1') + ... None + """ return self._labels.get(node) @memoize_method - def node_by_taxa(self,taxa): + def node_by_taxa(self, taxa): """ Returns the node labelled by taxa or None if no node is labelled by taxa. - Important: If more than one node is labelleb by taxa, then a random one is - returned. + Important: If more than one node is labelled with taxa, only the first one will be displayed. In order to get all nodes with a fixed label, use nodes_by_taxa. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((1,2),(3,4)1);") + >>> network.taxa() + ... ['1', '2', '3', '4'] + >>> network.nodes() + ... ['_7', '_6', '_5', '_4', '_3', '_2', '_1'] + >>> network.node_by_taxa('3') + ... '_6' + >>> network.node_by_taxa('non-existing taxa') + ... None + >>> network.node_by_taxa('1') + ... '_5' + """ for node in self.labelled_nodes(): if self.label(node) == taxa: return node return None - + @memoize_method - def all_nodes_by_taxa(self,taxa): + def nodes_by_taxa(self,taxa): """ - Returns all nodes labelled by taxa. + Returns all nodes labelled with taxa. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((1,2),(3,4)1);") + >>> network.taxa() + ... ['1', '2', '3', '4'] + >>> network.nodes() + ... ['_7', '_6', '_5', '_4', '_3', '_2', '_1'] + >>> network.node_by_taxa('3') + ... '_6' + >>> network.nodes_by_taxa('3') + ... ['_6'] + >>> network.node_by_taxa('1') + ... '_5' + >>> network.nodes_by_taxa('1') + ... ['_5', '_3'] + >>> network.nodes_by_taxa('non-existing taxa') + ... set([]) + """ - return [node for node in self.labelled_nodes() if self.label(node)==taxa] + tmp = [] + for node in self.labelled_nodes(): + if self.label(node) == taxa: + tmp.append(node) + return set(tmp) def is_tree_node(self,u): """ Returns True if u is a tree node, False otherwise. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((3),(1,2));") + >>> network.nodes() + ... ['_6', '_5', '_4', '_3', '_2', '_1'] + >>> map(network.is_tree_node, network.nodes()) + ... [True, True, True, True, True, True] + >>> network.is_tree_node('non-existing node') + ... False + >>> network = PhyloNetwork(eNewick="((4,5#1)2,(#1,6)3)1;") + >>> network.nodes() + ... ['_5', '_4', '_3', '_2', '_1', '#1'] + >>> map(network.is_tree_node, network.nodes()) + ... [True, True, True, True, True, False] + """ return self.in_degree(u)<=1 def is_hybrid_node(self,u): """ - Returns True if u is a tree node, False otherwise. + Returns True if u is not a tree node, False otherwise. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((3),(1,2));") + >>> network.nodes() + ... ['_6', '_5', '_4', '_3', '_2', '_1'] + >>> map(network.is_hybrid_node, network.nodes()) + ... [False, False, False, False, False, False] + >>> network.is_hybrid_node('non-existing node') + ... False + >>> network = PhyloNetwork(eNewick="((4,5#1)2,(#1,6)3)1;") + >>> network.nodes() + ... ['_5', '_4', '_3', '_2', '_1', '#1'] + >>> map(network.is_hybrid_node, network.nodes()) + ... [False, False, False, False, False, True] + """ return self.in_degree(u)>1 def is_leaf(self,u): """ Returns True if u is a leaf, False otherwise. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((3),(1,2));") + >>> network.nodes() + ... ['_6', '_5', '_4', '_3', '_2', '_1'] + >>> network.leaves() + ... ['_3', '_5', '_6'] + >>> network.is_leaf('_1') + ... False + >>> network.is_leaf('_6') + ... True + >>> network.is_leaf('non-existing node') + ... False + """ return self.out_degree(u)==0 def is_root(self,u): """ Returns True if u is a root, False otherwise. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((3),(1,2));") + >>> network.nodes() + ... ['_6', '_5', '_4', '_3', '_2', '_1'] + >>> network.leaves() + ... ['_3', '_5', '_6'] + >>> network.is_root('_3') + ... False + >>> network.is_root('_1') + ... True + """ return self.in_degree(u)==0 def is_elementary_node(self,u): + """ + Returns True if u is an elementary node, False otherwise. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((1)E,2);") + >>> network.nodes() + ... ['_4', '_3', '_2', '_1'] + >>> map(network.is_elementary_node, network.nodes()) + ... [False, False, True, False] + >>> network.label('_2') + ... 'E' + >>> network.elementary_nodes() + ... '_2' + + """ + return ((self.in_degree(u)<=1) and (self.out_degree(u)==1)) def is_labelled(self,u): """ Returns True if u is a labelled node, False otherwise. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((3),(1,2))4;") + >>> network.nodes() + ... ['_6', '_5', '_4', '_3', '_2', '_1'] + >>> map(network.is_labelled, network.nodes()) + ... [True, True, False, True, False, True] + >>> network.label('_1') + ... '4' + """ return u in self._labels @@ -106,6 +333,17 @@ def is_labelled(self,u): def leaves(self): """ Returns the set of leaves of self. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((3),(1,(5,6)2))4;") + >>> network.nodes() + ... ['_8', '_7', '_6', '_5', '_4', '_3', '_2', '_1'] + >>> network.leaves() + ... ['_3', '_5', '_7', '_8'] + >>> map(network.is_leaf, network.leaves()) + ... [True, True, True, True] + """ leaves = filter(self.is_leaf, self.nodes()) leaves.sort() @@ -115,6 +353,30 @@ def leaves(self): def roots(self): """ Returns the set of roots of self. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="(1,2,3)ROOT;") + >>> network.nodes() + ... ['_4', '_3', '_2', '_1'] + >>> network.roots() + ... ['_1'] + >>> network.label('_1') + ... 'ROOT' + + EXAMPLE:: + + >>> graph = networkx.DiGraph() + >>> graph.add_nodes_from(range(5)) + >>> graph.add_edges_from([(0,1), (2,3), (1,4), (3,4)]) + >>> network = PhyloNetwork(graph) + >>> network.nodes() + ... [0, 1, 2, 3, 4] + >>> network.roots() + ... [0, 2] + >>> network.is_phylogenetic_network() + ... True + """ roots = filter(self.is_root, self.nodes()) roots.sort() @@ -123,31 +385,125 @@ def roots(self): @memoize_method def labelled_nodes(self): """ - Returns de set of labelled nodes. + Returns the set of labelled nodes. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((A,B,C),1);") + >>> network.nodes() + ... ['_6', '_5', '_4', '_3', '_2', '_1'] + >>> network.labelled_nodes() + ... ['_6', '_5', '_4', '_3'] + >>> map(network.label, network.labelled_nodes()) + ... ['1', 'C', 'B', 'A'] + >>> network.unlabelled_nodes() + ... ['_2', '_1'] + >>> map(network.label, network.unlabelled_nodes()) + ... [None, None] + """ return self._labels.keys() @memoize_method def unlabelled_nodes(self): + """ + Returns the set of unlabelled nodes. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((A,B,C),1);") + >>> network.nodes() + ... ['_6', '_5', '_4', '_3', '_2', '_1'] + >>> network.labelled_nodes() + ... ['_6', '_5', '_4', '_3'] + >>> map(network.label, network.labelled_nodes()) + ... ['1', 'C', 'B', 'A'] + >>> network.unlabelled_nodes() + ... ['_2', '_1'] + >>> map(network.label, network.unlabelled_nodes()) + ... [None, None] + + """ return list(set(self.nodes())-set(self.labelled_nodes())) @memoize_method def interior_nodes(self): + """ + Returns the set of non-leaf nodes. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((1,2),(3,4));") + >>> network.nodes() + ... ['_7', '_6', '_5', '_4', '_3', '_2', '_1'] + >>> network.interior_nodes() + ... ['_5', '_2', '_1'] + >>> map(network.is_leaf, network.interior_nodes()) + ... [False, False, False] + + """ return list(set(self.nodes())-set(self.leaves())) @memoize_method def elementary_nodes(self): + """ + Return the set of elementary nodes. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((1)E,2);") + >>> network.nodes() + ... ['_4', '_3', '_2', '_1'] + >>> map(network.is_elementary_node, network.nodes()) + ... [False, False, True, False] + >>> network.label('_2') + ... 'E' + >>> network.elementary_nodes() + ... '_2' + + """ return filter(self.is_elementary_node, self.nodes()) @memoize_method def depth(self,u): + """ + Returns the depth of u. If the node u is not from the + phylogenetic network, then returns None. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((((LEAF#1))),#1);") + >>> network.nodes() + ... ['#1', '_4', '_3', '_2', '_1'] + >>> map(network.depth, network.nodes()) + ... [1, 3, 2, 1, 0] + >>> network.depth('non-existing node') + ... None + + """ + if not u in self: + return None return min([dijkstra_path_length(self,root,u) for root in self.roots()]) @memoize_method def height(self,u): """ - Returns the height of u. + Returns the height of u. If the node u is not from the + phylogenetic network, then returns None. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((((LEAF#1))),#1);") + >>> network.nodes() + ... ['#1', '_4', '_3', '_2', '_1'] + >>> map(network.height, network.nodes()) + ... [0, 1, 2, 3, 4] + >>> network.height('non-existing node') + ... None + """ + if not u in self: + return None if self.is_leaf(u): return 0 else: @@ -155,6 +511,30 @@ def height(self,u): @memoize_method def mu(self,u): + """ + Returns a tuple containing the number of paths from u to all + labelled nodes of the phylogenetic network. + Returns None if the node u is not in the phylogenetic network. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((((LEAF1, LEAF2#1)), #1)INT,#1);") + >>> network.taxa() + ... ['INT', 'LEAF1', LEAF2] + >>> network.roots() + ... '_1' + >>> network.mu('_1') + ... array([1, 1, 3]) + >>> network.successors('_1') + ... ['#1', '_2'] + >>> network.mu('#1') # This is LEAF2 + ... array([0, 0, 1]) + >>> network.mu('_2') # We lost the path root -> LEAF2 + ... array([1, 1, 2]) + + """ + if u not in self: + return None if self.is_leaf(u): mu = numpy.zeros(len(self.taxa()),int) else: @@ -166,59 +546,96 @@ def mu(self,u): @memoize_method def mu_string(self): + """ + Returns a string representing the mu value of all nodes, with the same order as sorted_nodes() + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((((LEAF1, LEAF2#1)), #1)INT,#1);") + >>> network.taxa() + ... ['INT', 'LEAF1', LEAF2] + >>> network.sorted_nodes() + ... ['#1', '_5', '_4', '_3', '_2', '_1'] + >>> network.mu_string() + ... '[0 0 1]-[0 1 0]-[0 1 1]-[0 1 1]-[1 1 2]-[1 1 3]' + >>> network.mu('#1') + ... array([0, 0, 1]) + >>> network.mu('_1') + ... array([1, 1, 3]) + + """ return '-'.join([str(self.mu(u)) for u in self.sorted_nodes()]) - + @memoize_method def sorted_nodes(self): + """ + Returns the set of nodes sorted with the total order over their mu value. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((((LEAF1, LEAF2#1)), #1)INT,#1);") + >>> network.taxa() + ... ['INT', 'LEAF1', LEAF2] + >>> network.sorted_nodes() + ... ['#1', '_5', '_4', '_3', '_2', '_1'] + >>> network.mu_string() + ... '[0 0 1]-[0 1 0]-[0 1 1]-[0 1 1]-[1 1 2]-[1 1 3]' + >>> network.nodes() + ... ['_5', '_4', '_3', '_2', '_1', '#1'] + + """ nodes = self.nodes()[:] nodes.sort(cmp=lambda u,v:total_cmp(self.mu(u),self.mu(v))) return nodes + + def _generate_new_id(self): + """ + For private use, it generates a new identification for every node + when generating the phylogenetic network. + """ - def getlabel(self): try: self._lastlabel += 1 except: self._lastlabel = 1 return '_%d' % (self._lastlabel) + + #getlabel = _generate_new_id # DEPRECATED - - def walk(self,parsed,ignore_prefix=None): + def _walk(self,parsed,ignore_prefix=None): if isinstance(parsed, pyparsing.ParseResults): if 'tag' in parsed: internal_label='#'+str(parsed['tag']) else: - internal_label=self.getlabel() + internal_label=self._generate_new_id() if 'length' in parsed: pass self.add_node(internal_label) if 'label' in parsed: self._labels[internal_label]=parsed['label'] for child in parsed: - child_label=self.walk(child,ignore_prefix=ignore_prefix) + child_label=self._walk(child,ignore_prefix=ignore_prefix) if child_label: self.add_edge(internal_label,child_label) return internal_label - def from_eNewick(self,string,ignore_prefix=None): - #parsed=eNewickParser(string)[0] + def _from_eNewick(self,string,ignore_prefix=None): try: parsed=eNewickParser(string)[0] except pyparsing.ParseException: - raise 'Malformed eNewick' - #raise JoQueSe - return False - self.walk(parsed,ignore_prefix=ignore_prefix) + raise MalformedNewickException("Malformed eNewick string") + self._walk(parsed,ignore_prefix=ignore_prefix) self.cache = {} - def eNewick_node(self,u,visited): + def _eNewick_node(self,u,visited): if self.is_leaf(u): #return self._labels[u] return self._labels.get(u,'') if u in visited: return u visited.append(u) - children=map(lambda x:self.eNewick_node(x,visited),self.successors(u)) + children=map(lambda x:self._eNewick_node(x,visited),self.successors(u)) internal=','.join(children) mylabel=self.label(u) or '' if self.is_hybrid_node(u): @@ -227,25 +644,84 @@ def eNewick_node(self,u,visited): def __str__(self): return self.eNewick() + + def __repr__(self): + return "Phylogenetic Network with taxa [" + ",".join(map(str,self.taxa())) + "]." def eNewick(self): + """ + Returns the eNewick representation of the network. + """ + visited=[] string = '' for root in self.roots(): - string += self.eNewick_node(root,visited)+';' + string += self._eNewick_node(root,visited)+';' return string @memoize_method def descendant_nodes(self,u): + """ + Returns a set with all the descendents of u. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((,(3,4)#1)2,#1)1;") + >>> network.nodes() + ... ['_5', '_4', '_3', '_2', '_1', '#1'] + >>> network.node_by_taxa('2') + ... '_2' + >>> network.descendant_nodes('_2') + ... ['_5', '_4', '#1' '_3', '_2'] + >>> network.strict_descendant_nodes('_2') + ... ['_3', '_2'] + >>> network.descendant_taxa('_2') + ... ['4', '3', '2'] + + """ return sum(dfs_successors(self,u).values(),[]) + [u] #return dfs_successors(self,u) @memoize_method def descendant_taxa(self,u): + """ + Returns a set with all the labelled descendents of u. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((,(3,4)#1)2,#1)1;") + >>> network.nodes() + ... ['_5', '_4', '_3', '_2', '_1', '#1'] + >>> network.node_by_taxa('2') + ... '_2' + >>> network.descendant_taxa('_2') + ... ['4', '3', '2'] + >>> network.strict_descendant_taxa('_2') + ... ['2'] + >>> network.descendant_nodes('_2') + ... ['_5', '_4', '#1', '_3', '_2'] + + """ return [self.label(desc) for desc in self.descendant_nodes(u) if self.is_labelled(desc)] @memoize_method def strict_descendant_nodes(self,u): + """ + Returns a set with all the strict descendents of u. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((,(3,4)#1)2,#1)1;") + >>> network.nodes() + ... ['_5', '_4', '_3', '_2', '_1', '#1'] + >>> network.node_by_taxa('2') + ... '_2' + >>> network.descendant_nodes('_2') + ... ['_5', '_4', '#1', '_3', '_2'] + >>> network.strict_descendant_nodes('_2') + ... ['_3', '_2'] + + """ if self.is_root(u): return self.descendant_nodes(u) pruned = copy.deepcopy(self) @@ -253,23 +729,87 @@ def strict_descendant_nodes(self,u): pruned.remove_node(u) desc_pruned = [] for root in self.roots(): - desc_pruned.extend(dfs_successors(pruned,root)) + desc_pruned.extend(pruned.descendant_nodes(root)) #dfs_successors(pruned, root) return [desc for desc in self.descendant_nodes(u) if not desc in desc_pruned] @memoize_method def strict_descendant_taxa(self,u): + """ + Returns a set with all the strict labelled descendents of u. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((,(3,4)#1)2,#1)1;") + >>> network.nodes() + ... ['_5', '_4', '_3', '_2', '_1', '#1'] + >>> network.node_by_taxa('2') + ... '_2' + >>> network.descendant_taxa('_2') + ... ['4', '3', '2'] + >>> network.strict_descendant_taxa('_2') + ... ['2'] + + """ return [self.label(desc) for desc in self.strict_descendant_nodes(u) if self.is_labelled(desc)] @memoize_method def ancestors(self,taxon): + """ + Returns a set with all nodes that have a fixed descendant taxa. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((3,4)1,(5,6,7)2);") + >>> network.ancestors('3') + ... ['_3', '_2', '_1'] + >>> '3' in network.descendant_taxa('_2') + ... True + + """ + return [u for u in self.sorted_nodes() if taxon in self.descendant_taxa(u)] @memoize_method def strict_ancestors(self,taxon): + """ + Returns a set with all nodes that have a fixed strict descendant taxa. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick=((,(3,4)#1)2,#1)1;) + >>> network.node_by_taxa('2') + ... '_2' + >>> '_2' in network.ancestors('3') + ... True + >>> '_2' in network.strict_ancestors('3') + ... False + + """ return [u for u in self.sorted_nodes() if taxon in self.strict_descendant_taxa(u)] @memoize_method def CSA(self,tax1,tax2): + """ + Returns a set with the common strict ancestors of taxa1 and taxa2. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick=((,(3,4)#1)2,#1)1;) + >>> network.CSA('3', '4') + ... ['#1', '_1'] + >>> network.LCSA('3', '4') + ... '#1' + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="(((1)), 2);") + >>> network.CSA('1', '2') + ... ['_2', '_1'] + >>> network.LCSA('1', '2') + ... '_2' + + """ + return [u for u in self.ancestors(tax1) if (u in self.ancestors(tax2)) and ((u in self.strict_ancestors(tax1)) or @@ -277,6 +817,26 @@ def CSA(self,tax1,tax2): @memoize_method def LCSA(self,tax1,tax2): + """ + Returns a minimum of CSA(taxa1, taxa2) respect the height of nodes. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick=((,(3,4)#1)2,#1)1;) + >>> network.CSA('3', '4') + ... ['#1', '_1'] + >>> network.LCSA('3', '4') + ... '#1' + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="(((1)), 2);") + >>> network.CSA('1', '2') + ... ['_2', '_1'] + >>> network.LCSA('1', '2') + ... '_2' + + """ csa=self.CSA(tax1,tax2) #print self,tax1,tax2,csa csa.sort(lambda x,y:cmp(self.height(x),self.height(y))) @@ -284,6 +844,19 @@ def LCSA(self,tax1,tax2): @memoize_method def nodal_matrix(self): + """ + Returns a matrix containing the nodal 'distance' between all labelled nodes. + + EXAMPLES:: + + >>> network = PhyloNetwork(eNewick="(((1,2), 3), 4);") + >>> network.nodal_matrix() + ... array([[0, 1, 2, 3], + ... [1, 0, 2, 3], + ... [1, 1, 0, 2], + ... [1, 1, 1, 0]) + + """ n=len(self.taxa()) matrix=numpy.zeros((n,n),int) dicdist=all_pairs_shortest_path_length(self) @@ -297,12 +870,40 @@ def nodal_matrix(self): return matrix def nodal_area(self): + """ + Returns the sum of all elements of the nodal matrix. + + EXAMPLES:: + + >>> network = PhyloNetwork(eNewick="(((1,2), 3), 4);") + >>> network.nodal_matrix() + ... array([[0, 1, 2, 3], + ... [1, 0, 2, 3], + ... [1, 1, 0, 2], + ... [1, 1, 1, 0]) + >>> network.nodal_area() + ... 19 + + """ mat=self.nodal_matrix() #mat=mat+mat.transpose() return sum(abs(mat.flatten())) @memoize_method def cophenetic_matrix(self): + """ + Returns a matrix with the cophenetic coeficient of labelled nodes. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="(((1,2), 3), 4);") + >>> network.cophenetic_matrix() + ... array([[3, 2, 1, 0], + ... [0, 3, 1, 0], + ... [0, 0, 2, 0], + ... [0, 0, 0, 1]) + + """ n=len(self.taxa()) matrix=numpy.zeros((n,n),int) for i in range(n): @@ -314,6 +915,20 @@ def cophenetic_matrix(self): return matrix def common_taxa(self,net2): + """ + Returns common taxa between self and net2. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((1,2), 3)4;") + >>> network2 = PhyloNewtwork(eNewick="(1,(4,5)2);") + >>> network.common_taxa(network2) + ... ['1', '2', '4'] + >>> network.common_taxa_leaves(network2) + ... ['1'] + + """ + common=[] taxa1=self.taxa() taxa2=net2.taxa() @@ -323,6 +938,20 @@ def common_taxa(self,net2): return common def common_taxa_leaves(self,net2): + """ + Returns common taxa between self and net2 that are leaves on both networks. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((1,2), 3)4;") + >>> network2 = PhyloNewtwork(eNewick="(1,(4,5)2);") + >>> network.common_taxa(network2) + ... ['1', '2', '4'] + >>> network.common_taxa_leaves(network2) + ... ['1'] + + """ + common=[] taxa1=filter(lambda l:self.is_leaf(self.node_by_taxa(l)),self.taxa()) taxa2=filter(lambda l:net2.is_leaf(net2.node_by_taxa(l)),net2.taxa()) @@ -333,11 +962,33 @@ def common_taxa_leaves(self,net2): def topological_restriction(self,subtaxa): + """ + Returns a minimal subnetwork of self such that it containts a fixed subtaxa. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((1,2), (3,4));") + >>> network.taxa() + ... ['1', '2', '3', '4'] + >>> network.roots() + ... ['_1'] + >>> subnetwork = network.topological_restriction(['1', '2']) + >>> subnetwork.taxa() + ... ['1', '2'] + >>> subnetwork.nodes() # '_1' should not be + ... ['_4', '_3', '_2'] + >>> subnetwork.roots() + ... ['_2'] + >>> subnetwork = network.topological_restriction(['1', '3']) + >>> '_1' in subnetwork.roots() + ... True + + """ restricted=copy.deepcopy(self) for taxon in restricted.taxa(): if not taxon in subtaxa: - for u in restricted.all_nodes_by_taxa(taxon): - del restricted._labels[u] + u=restricted.node_by_taxa(taxon) + del restricted._labels[u] restricted.cache = {} while True: @@ -360,6 +1011,10 @@ def topological_restriction(self,subtaxa): @memoize_method def has_nested_taxa(self): + """ + Returns True is an internal node is labelled. False otherwise. + """ + for node in self.labelled_nodes(): if not self.is_leaf(node): return True @@ -406,6 +1061,13 @@ def matching_permutation(self): return self._matching_permutation def cluster(self,u): + """ + Returns the cluster of u in the network, None if the node is not in the network. + """ + + if not u in self: + return None + cl=[] dictio=single_source_shortest_path_length(self,u) for node in dictio.keys(): @@ -416,11 +1078,31 @@ def cluster(self,u): return cl def cluster_representation(self): + """ + Returns the cluster of all nodes in the network. + """ + cls=map(self.cluster,self.nodes()) cls.sort() return cls def nested_label(self,node): + """ + Returns a string representation of descendants of u. Very useful to identify where is a node located in the network. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((2,3), (4,(5,6)))1;") + >>> network.nodes() + ... ['_9', '_8', '_7', '_6', '_5', '_4', '_3', '_2', '_1'] + >>> network.nested_label('_7') # Where is node '_7' in the network? + ... '{5,6}' # '_7' is the parent of leaves '5' and '6'. + >>> network.nested_label('_6') + ... '4' # '_6' is the leaf '4' + >>> network.nested_label('_5') + ... '{4,{5,6}}' # '_5' is the parent of leaf '4' and node '_7' + """ + try: return self._nested_label[node] except: @@ -431,11 +1113,25 @@ def nested_label(self,node): else: desc_labels = [self.nested_label(u) for u in self.successors(node)] desc_labels.sort() + if desc_labels is None: + # If a leaf doesn't have a label + self._nested_label[node] = "{}" + return self.nested_label[node] self._nested_label[node] = '{'+','.join(desc_labels)+'}' return self._nested_label[node] def nested_label_representation(self): + """ + Returns the nested label of all nodes in the network. + + EXAMPLE:: + + >>> network = PhyloNetwork(eNewick="((1,2), (3,4));") + >>> network.nodes() + ... ['_7', '_6', '_5', '_4', '_3', '_2', '_1'] + >>> network.nested_label_representation() + ... set(['{3,4}', '1', '3', '2', '4', '{1,2}', '{{1,2},{3,4}}']) + + """ nls=map(self.nested_label,self.nodes()) - return set(nls) - - + return set(nls) \ No newline at end of file diff --git a/src/PhyloNetwork/distances.py b/src/PhyloNetwork/distances.py index 9c9ab5e..0f9967d 100644 --- a/src/PhyloNetwork/distances.py +++ b/src/PhyloNetwork/distances.py @@ -5,10 +5,14 @@ ''' from utils import total_cmp +from .exceptions import TaxaException def mu_distance(net1,net2): + """ + Compute the mu distance between two phylogenetic networks. + """ # if net1.taxa() != net2.taxa(): # return nodes1=net1.sorted_nodes()[:] @@ -29,15 +33,23 @@ def mu_distance(net1,net2): del nodes2[0] return d+len(nodes1)+len(nodes2) -def is_isomorphic_fast(net1,net2): - return net1.mu_string() == net2.mu_string() +#def is_isomorphic_fast(net1,net2): +# return net1.mu_string() == net2.mu_string() - -def nodal_distance_splitted(net1,net2,p=1,take_root=False): +def nodal_distance_splitted(net1,net2,p=1,take_root=False, check=False): + """ + Computes the nodal distance splitted between two phylogenetic networks. + If check = True, then it checks if the two networks have the same taxa. Otherwise it will only check if the number of labels is equal. + """ + + if check: + if not net1.taxa() == net2.taxa(): + raise TaxaException("Networks over different set of taxa") + try: mat=net1.nodal_matrix()-net2.nodal_matrix() except: - raise Exception("Networks over different set of taxa") + raise TaxaException("The number of possible labels is not equal") if p==1: return sum(abs(mat.flatten())) else: @@ -46,7 +58,16 @@ def nodal_distance_splitted(net1,net2,p=1,take_root=False): else: return sum(abs(mat.flatten())**p) -def nodal_distance_unsplitted(net1,net2,p=1,take_root=False): +def nodal_distance_unsplitted(net1,net2,p=1,take_root=False, check=False): + """ + Computes the nodal distance unsplitted between two phylogenetic networks. + If check = True, then it checks if the two networks have the same taxa. Otherwise it will only check if the number of labels is equal. + """ + + if check: + if not net1.taxa() == net2.taxa(): + raise TaxaException("Networks over different set of taxa") + mat1=net1.nodal_matrix() mat1=mat1+mat1.transpose() mat2=net2.nodal_matrix() @@ -54,26 +75,33 @@ def nodal_distance_unsplitted(net1,net2,p=1,take_root=False): try: mat=net1.nodal_matrix()-net2.nodal_matrix() except: - raise Exception("Networks over different set of taxa") - n = len(net1.taxa()) - result = sum([ (mat1[i,j]+mat1[j,i]-mat2[i,j]-mat2[j,i])**p for i in range(n) for j in range(i+1,n)]) + raise TaxaException("The number of possible labels is not equal") if p==1: - return result + return sum(abs(mat.flatten()))/2 else: if take_root: - return (result)**(1.0/p) + return (sum(abs(mat.flatten())**p)/2)**(1.0/p) else: - return result + return (sum(abs(mat.flatten())**p)/2) -def cophenetic_distance(net1,net2,p=1,take_root=False): +def cophenetic_distance(net1,net2,p=1,take_root=False, check=False): + """ + Computes the nodal distance unsplitted between two phylogenetic networks. + If check = True, then it checks if the two networks have the same taxa. Otherwise it will only check if the number of labels is equal. + """ + + if check: + if not net1.taxa() == net2.taxa(): + raise TaxaException("Networks over different set of taxa") + mat1=net1.cophenetic_matrix() mat2=net2.cophenetic_matrix() try: mat=mat1-mat2 except: - raise Exception("Networks over different set of taxa") + raise TaxaException("The number of possible labels is not equal") if p==1: - return sum(abs(mat.flatten())) + return sum(abs(mat.flatten()))/2 else: if take_root: return (sum(abs(mat.flatten())**p))**(1.0/p) @@ -81,6 +109,10 @@ def cophenetic_distance(net1,net2,p=1,take_root=False): return (sum(abs(mat.flatten())**p)) def transposition_distance(net1,net2): + """ + Computes the transposition distance between two phylogenetic networks. + """ + # pi1=permutations.Permutation(net1.matching_permutation()) # pi2=permutations.Permutation(net2.matching_permutation()) pi1=net1.matching_permutation() @@ -93,7 +125,15 @@ def transposition_distance(net1,net2): return dist/2 def RF_distance(net1,net2): + """ + Computes the RF distance between two phylogenetic networks. + """ + return len(set(net1.cluster_representation())^set(net2.cluster_representation())) def nested_label_distance(net1,net2): - return len(net1.nested_label_representation() ^ net2.nested_label_representation()) + """ + Computes the nested label distance between two phylogenetic networks. + """ + + return len(net1.nested_label_representation() ^ net2.nested_label_representation()) \ No newline at end of file diff --git a/src/PhyloNetwork/exceptions.py b/src/PhyloNetwork/exceptions.py new file mode 100644 index 0000000..1fa31a6 --- /dev/null +++ b/src/PhyloNetwork/exceptions.py @@ -0,0 +1,13 @@ + + +class MalformedNewickException(Exception): + """ + Raised when creating a new network from a malformed eNewick string. + """ + pass + +class TaxaException(Exception): + """ + Raised when trying to compare to networks with incompatible Taxa. + """ + pass diff --git a/src/PhyloNetwork/generators.py b/src/PhyloNetwork/generators.py index 978bb7f..cb6f08e 100644 --- a/src/PhyloNetwork/generators.py +++ b/src/PhyloNetwork/generators.py @@ -14,12 +14,43 @@ # Sequential generator -def Tree_generator(taxa,binary=False,nested_taxa=True): - """Returns a generator for trees with given taxa. +def all_trees(taxa,binary=False,nested_taxa=True): + """ + Returns a generator for trees with given taxa. If nested_taxa is True, then trees with internal nodes labeled will also be produced. If binary is True, then only binary trees will be generated; otherwise trees will have internal nodes with arbitrary out-degree. + + EXAMPLE:: + + >>> generator = all_trees(['A', 'B', 'C'], binary=True) + >>> generator.next().eNewick() + ... '((C,B),A);' + >>> generator.next().eNewick() + ... '(C,(B,A));' + >>> tmp = 2 + >>> for tree in generator: tmp = tmp+1 + >>> tmp + ... 21 + + EXAMPLE:: + + >>> for tree in all_trees(['A', 'B', 'C', binary=True, nested_taxa=False): + >>> print tree.eNewick() + ... ((C,B),A); + ... (C,(B,A)); + ... ((C,A),B); + + EXAMPLE:: + + >>> for tree in all_trees(['A', 'B', 'C', binary=False, nested_taxa=False): + >>> print tree.eNewick() + ... ((C,B),A); + ... (C,(B,A)); + ... ((C,A),B); + ... (A,B,C); + """ n=len(taxa) if n==1: @@ -27,7 +58,7 @@ def Tree_generator(taxa,binary=False,nested_taxa=True): return taxon=taxa[-1] parent_taxa=taxa[0:-1] - parent_generator=Tree_generator(parent_taxa, + parent_generator=all_trees(parent_taxa, binary=binary,nested_taxa=nested_taxa) for parent in parent_generator: for u in parent.nodes(): @@ -56,7 +87,8 @@ def Tree_generator(taxa,binary=False,nested_taxa=True): @memoize_function def number_of_trees_bin_nont_partial(n,l,N): - """Gives the number of phylogenetic trees on n taxa with l leaves and N nodes. + """ + Gives the number of phylogenetic trees on n taxa with l leaves and N nodes. Assume binary trees without nested taxa. """ if (l != n) or (N != 2*n-1) or (n < 0): @@ -67,12 +99,27 @@ def number_of_trees_bin_nont_partial(n,l,N): @memoize_function def number_of_trees_bin_nont_global(n): - """Gives the number of phylogenetic trees on n taxa. + """ + Gives the number of phylogenetic trees on n taxa. Assume binary trees and without nested taxa. """ return number_of_trees_bin_nont_partial(n,n,2*n-1) -def random_tree_bin_nont_global(taxa,id_offset): +def random_tree_bin_nont_global(taxa, id_offset=0): + """ + Returns a random binary tree without nested taxa. + + EXAMPLE:: + + >>> network = random_tree_bin_nont_global(['A', 'B', 'C', 'D']) + >>> network.nested_label(network.roots()[0]) + ... '{D,{C,{A,B}}}' # random + >>> network = random_tree_bin_nont_global(['A', 'B', 'C', 'D']) + >>> network.nested_label(network.roots()[0]) + ... '{A,{C,{B,D}}}' # random + + """ + n = len(taxa) if n == 1: return PhyloNetwork(eNewick=('%s;' % taxa[0]),id_offset=id_offset) @@ -85,7 +132,8 @@ def random_tree_bin_nont_global(taxa,id_offset): @memoize_function def number_of_trees_nobin_nont_partial(n,l,N): - """Gives the number of phylogenetic trees on n taxa with l leaves and N nodes. + """ + Gives the number of phylogenetic trees on n taxa with l leaves and N nodes. Assume not necessarily binary trees and without nested taxa. """ if (l != n) or (N < n) or (n < 0) or (N >= 2*n): @@ -97,14 +145,15 @@ def number_of_trees_nobin_nont_partial(n,l,N): @memoize_function def number_of_trees_nobin_nont_global(n): - """Gives the number of phylogenetic trees on n taxa. + """ + Gives the number of phylogenetic trees on n taxa. Assume not necessarily binary trees and without nested taxa. """ if n == 1: return 1 return sum([number_of_trees_nobin_nont_partial(n,n,N) for N in range(n+1,2*n)]) -def random_tree_nobin_nont_partial(taxa,l,N,id_offset): +def random_tree_nobin_nont_partial(taxa,l,N,id_offset=0): n = len(taxa) if (l != n) or (N < n) or (n < 0) or (N >= 2*n): return None @@ -121,7 +170,20 @@ def random_tree_nobin_nont_partial(taxa,l,N,id_offset): newtree = operation(parent,u,taxa[-1]) return newtree -def random_tree_nobin_nont_global(taxa,id_offset): +def random_tree_nobin_nont_global(taxa,id_offset=0): + """ + Returns a random tree without nested taxa. + + EXAMPLE:: + + >>> network = random_tree_nobin_nont_global(['A', 'B', 'C', 'D']) + >>> network.nested_label(network.roots()[0]) + ... '{A,B,{C,D}}' # random, not binary + >>> network = random_tree_nobin_nont_global(['A', 'B', 'C', 'D']) + >>> network.nested_label(network.roots()[0]) + ... '{D,{A,{B,C}}}' # random + + """ n = len(taxa) if n == 1: return PhyloNetwork(eNewick=('%s;' % taxa[0]),id_offset=id_offset) @@ -136,7 +198,8 @@ def random_tree_nobin_nont_global(taxa,id_offset): @memoize_function def number_of_trees_bin_nt_partial(n,l,N,e): - """Gives the number of phylogenetic trees on n taxa with l leaves, N nodes, e of them being elementary. + """ + Gives the number of phylogenetic trees on n taxa with l leaves, N nodes, e of them being elementary. Assume binary trees with nested taxa. """ #print "n=%d, l=%d, N=%d, e=%d" % (n,l,N,e) @@ -190,7 +253,21 @@ def random_tree_bin_nt_partial(taxa,l,N,e,id_offset): newtree = operation(parent,u,taxa[-1]) return newtree -def random_tree_bin_nt_global(taxa,id_offset): +def random_tree_bin_nt_global(taxa,id_offset=0): + """ + Returns a random binary tree with nested taxa. + + EXAMPLE:: + + >>> network = random_tree_bin_nt_global(['A', 'B', 'C', 'D']) + >>> network.eNewick() + ... '((D,A),B)C;' # random + >>> network = random_tree_bin_nt_global(['A', 'B', 'C', 'D']) + >>> network.eNewick() + ... '((D,A),(B)C);' # random + + """ + n = len(taxa) if n == 1: return PhyloNetwork(eNewick=('%s;' % taxa[0]),id_offset=id_offset) @@ -206,7 +283,8 @@ def random_tree_bin_nt_global(taxa,id_offset): @memoize_function def number_of_trees_nobin_nt_partial(n,l,N): - """Gives the number of phylogenetic trees on n taxa with l leaves, N nodes, e of them being elementary. + """ + Gives the number of phylogenetic trees on n taxa with l leaves, N nodes, e of them being elementary. Assume binary trees with nested taxa. """ #print "n=%d, l=%d, N=%d, e=%d" % (n,l,N,e) @@ -262,6 +340,20 @@ def random_tree_nobin_nt_partial(taxa,l,N,id_offset): return newtree def random_tree_nobin_nt_global(taxa,id_offset): + """ + Returns a random tree with nested taxa. + + EXAMPLE:: + + >>> network = random_tree_nobin_nt_global(['A', 'B', 'C', 'D']) + >>> network.eNewick() + ... '((D,A),C,B);' # random, non-binary + >>> network = random_tree_nobin_nt_global(['A', 'B', 'C', 'D']) + >>> network.eNewick() + ... '(((D,C),B),A);' # random + + """ + n = len(taxa) if n == 1: return PhyloNetwork(eNewick=('%s;' % taxa[0]),id_offset=id_offset) @@ -274,7 +366,23 @@ def random_tree_nobin_nt_global(taxa,id_offset): #print (l,N) return random_tree_nobin_nt_partial(taxa,l,N,id_offset) -def Tree_generator_random(taxa,binary=False,nested_taxa=True,id_offset=0): +def random_tree_generator(taxa,binary=False,nested_taxa=True,id_offset=0): + """ + Returns generator of random trees. + If nested_taxa = True, then trees with internal labels will also be produced. + If binary = True, then only binary trees will be produced. + + EXAMPLE:: + + >>> generator = random_tree_generator(['a', 'b', 'c', 'd', 'e', 'f'], binary=True) + >>> for i in range(3): + >>> print generator.next().eNewick() + ... '((d,b),(c,((e)f)a));' + ... '(c,(((a,e),d),f)b);' + ... '(((c,f),((b)e)d))a;' + + """ + if binary: if nested_taxa: f = random_tree_bin_nt_global @@ -288,24 +396,8 @@ def Tree_generator_random(taxa,binary=False,nested_taxa=True,id_offset=0): while True: yield f(taxa,id_offset) -def tree_generator_random_yule(taxa): - n = len(taxa) - if n == 1: - return PhyloNetwork(eNewick=('%s;' % taxa[0])) - tree = PhyloNetwork(eNewick=('(%s,%s);' % (taxa[0],taxa[1]))) - for i in range(2,n): - leaf = random.choice(tree.leaves()) - tree = push_and_hang(tree,leaf,taxa[i]) - labels = tree._labels - keys = labels.keys() - values = labels.values() - random.shuffle(values) - tree._labels = dict(zip(keys,values)) - tree.cache = {} - return tree - if __name__ == "__main__": - tg = Tree_generator(['1','2','3']) + tg = all_trees(['1','2','3']) while tg: print tg.next() diff --git a/src/PhyloNetwork/memoize.py b/src/PhyloNetwork/memoize.py index 0423d91..3877576 100644 --- a/src/PhyloNetwork/memoize.py +++ b/src/PhyloNetwork/memoize.py @@ -40,6 +40,8 @@ def __get__(self, obj, objtype): """Support instance methods.""" #print "Get", obj, objtype fn = functools.partial(self.__call__, obj) + fn.__doc__ = self.func.__doc__ + fn.__name__ = self.func.__name__ try: self.cache = obj.cache except: @@ -47,7 +49,7 @@ def __get__(self, obj, objtype): self.cache = obj.cache #print self.cache return fn - + class memoize_function(object): """Decorator that caches a function's return value each time it is called. If called later with the same arguments, the cached value is returned, and diff --git a/src/PhyloNetwork/operations.py b/src/PhyloNetwork/operations.py index 09f66b0..c5de56c 100644 --- a/src/PhyloNetwork/operations.py +++ b/src/PhyloNetwork/operations.py @@ -7,7 +7,8 @@ import copy def push_and_hang(net,u,newtaxa): - """Builds a new Phylogenetic Tree from net and u by hanging a leaf labeled newtaxa + """ + Builds a new Phylogenetic Tree from net and u by hanging a leaf labeled newtaxa between u and its parent. | | | => |\ @@ -16,12 +17,12 @@ def push_and_hang(net,u,newtaxa): """ parents=net.predecessors(u) newnet=copy.deepcopy(net) - w=newnet.getlabel() + w=newnet._generate_new_id() for parent in parents: newnet.remove_edge(parent,u) newnet.add_edge(parent,w) newnet.add_edge(w,u) - newleaf=newnet.getlabel() + newleaf=newnet._generate_new_id() newnet.add_edge(w,newleaf) newnet._labels[newleaf]=newtaxa #newnet.forget() @@ -29,7 +30,8 @@ def push_and_hang(net,u,newtaxa): return newnet def hold_and_hang(net,u,newtaxa): - """Builds a new Phylogenetic Tree from net and u by hanging a leaf labeled newtaxa + """ + Builds a new Phylogenetic Tree from net and u by hanging a leaf labeled newtaxa from u | => | u u @@ -37,7 +39,7 @@ def hold_and_hang(net,u,newtaxa): \newtaxa """ newnet=copy.deepcopy(net) - newleaf = newnet.getlabel() + newleaf = newnet._generate_new_id() newnet.add_edge(u,newleaf) newnet._labels[newleaf]=newtaxa #newnet.forget() @@ -45,7 +47,8 @@ def hold_and_hang(net,u,newtaxa): return newnet def push_and_label(net,u,newtaxa): - """Builds a new Phylogenetic Tree from net and u by inserting an elementary node + """ + Builds a new Phylogenetic Tree from net and u by inserting an elementary node between u and its parent and labeling it with newtaxa | | | newtaxa @@ -55,7 +58,7 @@ def push_and_label(net,u,newtaxa): """ parents=net.predecessors(u) newnet=copy.deepcopy(net) - newnode = newnet.getlabel() + newnode = newnet._generate_new_id() for parent in parents: newnet.remove_edge(parent,u) newnet.add_edge(parent,newnode) @@ -66,7 +69,8 @@ def push_and_label(net,u,newtaxa): return newnet def hold_and_label(net,u,newtaxa): - """Builds a new Phylogenetic Tree from net and u by labeling u with newtaxa + """ + Builds a new Phylogenetic Tree from net and u by labeling u with newtaxa | | | => | u u=newtaxa