#! /usr/bin/env python
# -*- coding: utf-8 -*-
from io import StringIO
from dendropy.datamodel import basemodel
from dendropy.datamodel.treemodel import _bipartition
[docs]
class Edge(basemodel.DataObject, basemodel.Annotable):
"""
An :term:``edge`` on a :term:``tree``.
"""
def __init__(self, **kwargs):
"""
Keyword Arguments
-----------------
head_node : |Node|, optional
Node from to which this edge links, i.e., the child node of this
node ``tail_node``.
length : numerical, optional
A value representing the weight of the edge.
rootedge : boolean, optional
Is the child node of this edge the root or seed node of the tree?
label : string, optional
Label for this edge.
"""
basemodel.DataObject.__init__(self, label=kwargs.pop("label", None))
self._head_node = kwargs.pop("head_node", None)
if "tail_node" in kwargs:
raise TypeError(
"Setting the tail node directly is no longer supported: instead, set"
" the parent node of the head node"
)
self.rootedge = kwargs.pop("rootedge", None)
self.length = kwargs.pop("length", None)
if kwargs:
raise TypeError("Unsupported keyword arguments: {}".format(kwargs))
self._bipartition = None
self.comments = []
def __copy__(self, memo=None):
raise TypeError("Cannot directly copy Edge")
[docs]
def taxon_namespace_scoped_copy(self, memo=None):
raise TypeError("Cannot directly copy Edge")
def __deepcopy__(self, memo=None):
# call Annotable.__deepcopy__()
return basemodel.Annotable.__deepcopy__(self, memo=memo)
# return super(Edge, self).__deepcopy__(memo=memo)
def __hash__(self):
return id(self)
def __eq__(self, other):
return self is other
def __lt__(self, other):
return id(self) < id(other)
def _get_tail_node(self):
if self._head_node is None:
return None
return self._head_node._parent_node
def _set_tail_node(self, node):
if self._head_node is None:
raise ValueError("'_head_node' is 'None': cannot assign 'tail_node'")
# Go through managed property instead of
# setting attribute to ensure book-keeping
self._head_node.parent_node = node
tail_node = property(_get_tail_node, _set_tail_node)
def _get_head_node(self):
return self._head_node
def _set_head_node(self, node):
# Go through managed property instead of setting attribute to ensure
# book-keeping; following should also set ``_head_node`` of ``self``
node.edge = self
head_node = property(_get_head_node, _set_head_node)
[docs]
def is_leaf(self):
"Returns True if the head node has no children"
return self.head_node and self.head_node.is_leaf()
def is_terminal(self):
return self.is_leaf()
[docs]
def is_internal(self):
"Returns True if the head node has children"
return self.head_node and not self.head_node.is_leaf()
[docs]
def get_adjacent_edges(self):
"""
Returns a list of all edges that "share" a node with ``self``.
"""
he = [i for i in self.head_node.incident_edges() if i is not self]
te = [i for i in self.tail_node.incident_edges() if i is not self]
he.extend(te)
return he
adjacent_edges = property(get_adjacent_edges)
[docs]
def collapse(self, adjust_collapsed_head_children_edge_lengths=False):
"""
Inserts all children of the head_node of self as children of the
tail_node of self in the same place in the child_node list that
head_node had occupied. The edge length and head_node will no longer be
part of the tree unless ``adjust_collapsed_head_children_edge_lengths``.
is True.
"""
to_del = self.head_node
parent = self.tail_node
if not parent:
return
children = to_del.child_nodes()
if not children:
raise ValueError("collapse_self called with a terminal.")
pos = parent.child_nodes().index(to_del)
parent.remove_child(to_del)
for child in children:
parent.insert_child(pos, child)
pos += 1
if adjust_collapsed_head_children_edge_lengths and self.length is not None:
# print id(child), child.edge.length, self.length
if child.edge.length is None:
child.edge.length = self.length
else:
child.edge.length += self.length
[docs]
def invert(self, update_bipartitions=False):
"""
Changes polarity of edge.
"""
# self.head_node, self.tail_node = self.tail_node, self.head_node
if not self.head_node:
raise ValueError("Cannot invert edge with 'None' for head node")
if not self.tail_node:
raise ValueError("Cannot invert edge with 'None' for tail node")
old_head_node = self.head_node
new_tail_node = old_head_node
old_tail_node = self.tail_node
new_head_node = old_tail_node
grandparent = old_tail_node._parent_node
if grandparent is not None:
for idx, ch in enumerate(grandparent._child_nodes):
if ch is old_tail_node:
grandparent._child_nodes[idx] = old_head_node
break
else:
# we did not break loop: force insertion of old_head_node if
# not already there
if old_head_node not in grandparent._child_nodes:
grandparent._child_nodes.append(old_head_node)
assert old_head_node in old_tail_node._child_nodes
old_tail_node.remove_child(old_head_node)
assert old_head_node not in old_tail_node._child_nodes
old_head_node.add_child(old_tail_node)
old_tail_node.edge.length, old_head_node.edge.length = (
old_head_node.edge.length,
old_tail_node.edge_length,
)
def _get_bipartition(self):
if self._bipartition is None:
self._bipartition = _bipartition.Bipartition(
edge=self,
is_mutable=True,
)
return self._bipartition
def _set_bipartition(self, v=None):
self._bipartition = v
bipartition = property(_get_bipartition, _set_bipartition)
def _get_split_bitmask(self):
return self.bipartition._split_bitmask
def _set_split_bitmask(self, h):
self.bipartition._split_bitmask = h
split_bitmask = property(_get_split_bitmask, _set_split_bitmask)
def _get_leafset_bitmask(self):
return self.bipartition._leafset_bitmask
def _set_leafset_bitmask(self, h):
self.bipartition._leafset_bitmask = h
leafset_bitmask = property(_get_leafset_bitmask, _set_leafset_bitmask)
def _get_tree_leafset_bitmask(self):
return self.bipartition._tree_leafset_bitmask
def _set_tree_leafset_bitmask(self, h):
self.bipartition._tree_leafset_bitmask = h
tree_leafset_bitmask = property(
_get_tree_leafset_bitmask, _set_tree_leafset_bitmask
)
def split_as_bitstring(self):
return self.bipartition.split_as_bitstring()
def leafset_as_bitstring(self):
return self.bipartition.leafset_as_bitstring()
[docs]
def description(
self, depth=1, indent=0, itemize="", output=None, taxon_namespace=None
):
"""
Returns description of object, up to level ``depth``.
"""
if depth is None or depth < 0:
return
output_strio = StringIO()
if self.label is None:
label = " (%s, Length=%s)" % (id(self), str(self.length))
else:
label = " (%s: '%s', Length=%s)" % (id(self), self.label, str(self.length))
output_strio.write(
"%s%sEdge object at %s%s" % (indent * " ", itemize, hex(id(self)), label)
)
if depth >= 1:
leader1 = " " * (indent + 4)
leader2 = " " * (indent + 8)
output_strio.write("\n%s[Length]" % leader1)
if self.length is not None:
length = self.length
else:
length = "None"
output_strio.write("\n%s%s" % (leader2, length))
output_strio.write("\n%s[Tail Node]" % leader1)
if self.tail_node is not None:
tn = self.tail_node.description(0)
else:
tn = "None"
output_strio.write("\n%s%s" % (leader2, tn))
output_strio.write("\n%s[Head Node]" % leader1)
if self.head_node is not None:
hn = self.head_node.description(0)
else:
hn = "None"
output_strio.write("\n%s%s" % (leader2, hn))
s = output_strio.getvalue()
if output is not None:
output.write(s)
return s
def _format_edge(self, **kwargs):
ef = kwargs.get('edge_formatter', None)
if ef:
return ef(self)
return str(self)