package beast.evolution.tree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author Alexei Drummond and Alexandra Gavryushkina
*/
public class TreeUtils {
/**
* Recursively order.
*/
public static void rotateNodeByComparator(Node node, Comparator<Node> comparator) {
if (node.getChildCount() > 2) throw new RuntimeException("Not implemented yet!");
for (Node child : node.getChildren()) {
rotateNodeByComparator(child, comparator);
}
if (node.getChildCount() > 1) {
if (comparator.compare(node.getLeft(), node.getRight()) > 0) {
Node temp = node.getLeft();
node.setLeft(node.getRight());
node.setRight(temp);
}
}
}
public static Comparator<Node> createNodeDensityComparator() {
return (node1, node2) -> node2.getLeafNodeCount() - node1.getLeafNodeCount();
}
public static Comparator<Node> createNodeDensityMinNodeHeightComparator() {
return (node1, node2) -> {
int larger = node1.getLeafNodeCount() - node2.getLeafNodeCount();
if (larger != 0) return larger;
double tipRecent = getMinNodeHeight(node1) - getMinNodeHeight(node2);
if (tipRecent > 0.0) return -1;
if (tipRecent < 0.0) return 1;
return 0;
};
}
public static Comparator<Node> createReverseNodeDensityMinNodeHeightComparator() {
return (node1, node2) -> {
int larger = node2.getLeafNodeCount() - node1.getLeafNodeCount();
if (larger != 0) return larger;
double tipRecent = getMinNodeHeight(node2) - getMinNodeHeight(node1);
if (tipRecent > 0.0) return -1;
if (tipRecent < 0.0) return 1;
return 0;
};
}
public static double getMinNodeHeight(Node node) {
if (!node.isLeaf()) {
double minNodeHeight = Double.MAX_VALUE;
for (Node child : node.getChildren()) {
double childMinHeight = getMinNodeHeight(child);
if (childMinHeight < minNodeHeight) {
minNodeHeight = childMinHeight;
}
}
return minNodeHeight;
} else return node.getHeight();
}
public static double getDoubleMetaData(Node node, String metaDataName) {
Object metaData = node.getMetaData(metaDataName);
if (metaData instanceof Integer) return ((Integer) metaData);
if (metaData instanceof Double) return (Double) metaData;
if (metaData instanceof String) return Double.parseDouble((String) metaData);
return -1;
}
/**
* Gets the most recent common ancestor (MRCA) node of a set of leaf nodes.
*
* @param tree the Tree
* @param leafNodes a set of names
* @return the NodeRef of the MRCA
*/
public static Node getCommonAncestorNode(Tree tree, Set<String> leafNodes) {
int cardinality = leafNodes.size();
if (cardinality == 0) {
throw new IllegalArgumentException("No leaf nodes selected");
}
Node[] mrca = {null};
getCommonAncestorNode(tree, tree.getRoot(), leafNodes, cardinality, mrca);
return mrca[0];
}
/*
* Private recursive function used by getCommonAncestorNode.
*/
private static int getCommonAncestorNode(Tree tree, Node node,
Set<String> leafNodes, int cardinality,
Node[] mrca) {
if (node.isLeaf()) {
if (leafNodes.contains(node.getID())) {
if (cardinality == 1) {
mrca[0] = node;
}
return 1;
} else {
return 0;
}
}
int matches = 0;
for (Node child : node.getChildren()) {
matches += getCommonAncestorNode(tree, child, leafNodes, cardinality, mrca);
if (mrca[0] != null) {
break;
}
}
if (mrca[0] == null) {
// If we haven't already found the MRCA, test this node
if (matches == cardinality) {
mrca[0] = node;
}
}
return matches;
}
/**
* @param tree
* @param node
* @return the length of the (sub)tree below the given node.
*/
public static double getTreeLength(Tree tree, Node node) {
int childCount = node.getChildCount();
if (childCount == 0) return node.getLength();
double length = 0;
for (Node child : node.getChildren()) {
length += getTreeLength(tree, child);
}
if (node != tree.getRoot())
length += node.getLength();
return length;
}
/**
* @param tree
* @return the sum of the external branch lengths of the given tree
*/
public static double getExternalLength(Tree tree) {
double length = 0.0;
for (Node node : tree.getExternalNodes()) {
length += node.getLength();
while (true) {
if (!node.isDirectAncestor() && node.getParent() != null && node.getParent().isFake()) {
node = node.getParent();
length += node.getLength();
} else break;
}
}
return length;
}
/**
* @param tree
* @return the sum of the internal branch lengths of the given tree
*/
public static double getInternalLength(Tree tree) {
double length = 0.0;
for (Node node : tree.getInternalNodes()) {
length += node.getLength();
if (node.isFake() && node.getNonDirectAncestorChild().isLeaf()) {
while (true) {
length -= node.getLength();
if (node.getParent() != null && node.getParent().isFake()) {
node = node.getParent();
} else break;
}
}
}
return length;
}
/**
* @param tree
* @param node
* @return the sum of the branch lengths on the subtree relating all
* contemporaneous descendants of the node
*/
public static double getTrunkLength(Tree tree, Node node) {
int childCount = node.getChildCount();
if (childCount == 0) {
if (node.getHeight() == 0.0) {
return node.getLength();
} else {
return 0.0;
}
}
double length = 0;
for (Node child : node.getChildren()) {
length += getTrunkLength(tree, child);
}
if (node != tree.getRoot() && length > 0.0)
length += node.getLength();
return length;
}
/**
* @return the intervals in an ultrametric tree in order from root to tips.
*/
public static double[] getIntervals(Tree tree) {
List<Double> heights = new ArrayList<>();
for (Node node : tree.getInternalNodes()) {
heights.add(node.getHeight());
}
Collections.sort(heights, Collections.reverseOrder());
double[] intervals = new double[heights.size()];
for (int i = 0; i < intervals.length - 1; i++) {
double height1 = heights.get(i);
double height2 = heights.get(i + 1);
intervals[i] = height1 - height2;
}
intervals[intervals.length - 1] = heights.get(intervals.length - 1);
return intervals;
}
/**
* @param tree the tree
* @param node the node to get names of leaves below
* @return a set of taxa names (as strings) of the leaf nodes descended from the given node.
*/
public static Set<String> getDescendantLeaves(Tree tree, Node node) {
HashSet<String> set = new HashSet<>();
getDescendantLeaves(tree, node, set);
return set;
}
/**
* @param tree the tree
* @param node the node to get name of leaves below
* @param set will be populated with taxa names (as strings) of the leaf nodes descended from the given node.
*/
private static void getDescendantLeaves(Tree tree, Node node, Set<String> set) {
if (node.isLeaf()) {
set.add(tree.getTaxonId(node));
} else {
for (Node child : node.getChildren()) {
getDescendantLeaves(tree, child, set);
}
}
}
/**
* @param tree the tree to test fo ultrametricity
* @param threshold the largest absolute value of height that a leaf node can have
* and the tree still be regarded as ultrametric
* @return true only if all tips have height 0.0
*/
public static boolean isUltrametric(Tree tree, double threshold) {
for (Node node : tree.getExternalNodes()) {
if (Math.abs(node.getHeight()) > threshold)
return false;
}
return true;
}
/**
* @param tree the tree to test fo ultrametricity
* @return true only if all tips have height exactly 0.0. Since newick is expressed in branch lengths
* it may be necessary to use isUltrametric(tree, threshold) to allow for numerical precision issues.
*/
public static boolean isUltrametric(Tree tree) {
for (Node node : tree.getExternalNodes()) {
if (node.getHeight() != 0.0)
return false;
}
return true;
}
/**
* @param tree the tree to test if binary
* @return true only if internal nodes have 2 children
*/
public static boolean isBinary(Tree tree) {
for (Node node : tree.getInternalNodes()) {
if (node.getChildCount() != 2)
return false;
}
return true;
}
/**
* get tree topology in Newick that is sorted by taxa labels or node indexes.
*
* @param node
* @param isTaxaLabel if true, then print taxa label instead of node index
* @return
*/
public static String sortedNewickTopology(Node node, boolean isTaxaLabel) {
if (node.isLeaf()) {
if (isTaxaLabel) {
return String.valueOf(node.getID());
} else {
return String.valueOf(node.getNr());
}
} else {
StringBuilder builder = new StringBuilder("(");
List<String> subTrees = new ArrayList<>();
for (int i = 0; i < node.getChildCount(); i++) {
subTrees.add(sortedNewickTopology(node.getChild(i), isTaxaLabel));
}
Collections.sort(subTrees);
for (int i = 0; i < subTrees.size(); i++) {
builder.append(subTrees.get(i));
if (i < subTrees.size() - 1) {
builder.append(",");
}
}
builder.append(")");
return builder.toString();
}
}
// populate postOrderList with node numbers in posorder: parent before children.
// postOrderList is pre-allocated with the right size
public static void preOrderTraversalList(Tree tree, int[] postOrderList) {
final int nodeCount = tree.getNodeCount();
if (postOrderList.length != nodeCount) {
throw new IllegalArgumentException("Illegal list length");
}
postOrderList[0] = tree.getRoot().getNr();
preOrderTraversalList(tree, 0, postOrderList);
}
public static int preOrderTraversalList(Tree tree, int idx, int[] postOrderList) {
final Node node = tree.getNode(postOrderList[idx]);
for(int i = 0; i < node.getChildCount(); ++i) {
final Node child = node.getChild(i);
idx += 1;
postOrderList[idx] = child.getNr();
if( ! child.isLeaf() ) {
idx = preOrderTraversalList(tree, idx, postOrderList);
}
}
return idx;
}
}