package dr.evolution.tree;
import dr.evolution.util.Taxon;
import dr.evolution.util.TaxonList;
import jebl.evolution.graphs.Node;
import jebl.evolution.trees.SimpleRootedTree;
import java.text.NumberFormat;
import java.util.*;
/**
* Static utility functions for trees.
*/
public class TreeUtils {
/**
* Count number of leaves in subtree whose root is node.
*
* @param tree the tree
* @param node the node to get leaf count below
* @return the number of leaves under this node.
*/
public static int getLeafCount(Tree tree, NodeRef node) {
int childCount = tree.getChildCount(node);
if (childCount == 0) return 1;
int leafCount = 0;
for (int i = 0; i < childCount; i++) {
leafCount += getLeafCount(tree, tree.getChild(node, i));
}
return leafCount;
}
public static double getTreeLength(Tree tree, NodeRef node) {
int childCount = tree.getChildCount(node);
if (childCount == 0) return tree.getBranchLength(node);
double length = 0;
for (int i = 0; i < childCount; i++) {
length += getTreeLength(tree, tree.getChild(node, i));
}
if (node != tree.getRoot())
length += tree.getBranchLength(node);
return length;
}
public static double getMinNodeHeight(Tree tree, NodeRef node) {
int childCount = tree.getChildCount(node);
if (childCount == 0) return tree.getNodeHeight(node);
double minNodeHeight = Double.MAX_VALUE;
for (int i = 0; i < childCount; i++) {
double height = getMinNodeHeight(tree, tree.getChild(node, i));
if (height < minNodeHeight) {
minNodeHeight = height;
}
}
return minNodeHeight;
}
/**
* @param tree the tree to test fo ultrametricity
* @return true only if all tips have height 0.0
*/
public static boolean isUltrametric(Tree tree) {
for (int i = 0; i < tree.getExternalNodeCount(); i++) {
if (tree.getNodeHeight(tree.getExternalNode(i)) != 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 (int i = 0; i < tree.getInternalNodeCount(); i++) {
if (tree.getChildCount(tree.getInternalNode(i)) > 2)
return false;
}
return true;
}
/**
* @param tree the tree to retrieve leaf set of
* @return a set of strings which are the taxa of the tree.
*/
public static Set<String> getLeafSet(Tree tree) {
HashSet<String> leafSet = new HashSet<String>();
int m = tree.getTaxonCount();
for (int i = 0; i < m; i++) {
Taxon taxon = tree.getTaxon(i);
leafSet.add(taxon.getId());
}
return leafSet;
}
/**
* @param tree the tree
* @param taxa the taxa
* @return Set of taxon names (id's) associated with the taxa in taxa.
* @throws MissingTaxonException
* if a taxon in taxa is not contained in the tree
*/
public static Set<String> getLeavesForTaxa(Tree tree, TaxonList taxa) throws MissingTaxonException {
HashSet<String> leafNodes = new HashSet<String>();
int m = taxa.getTaxonCount();
int n = tree.getExternalNodeCount();
for (int i = 0; i < m; i++) {
Taxon taxon = taxa.getTaxon(i);
boolean found = false;
for (int j = 0; j < n; j++) {
NodeRef node = tree.getExternalNode(j);
if (tree.getNodeTaxon(node).getId().equals(taxon.getId())) {
found = true;
break;
}
}
if (!found) {
throw new MissingTaxonException(taxon);
}
leafNodes.add(taxon.getId());
}
return leafNodes;
}
/**
* @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, NodeRef node) {
HashSet<String> set = new HashSet<String>();
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, NodeRef node, Set<String> set) {
if (tree.isExternal(node)) {
set.add(tree.getTaxonId(node.getNumber()));
} else {
for (int i = 0; i < tree.getChildCount(node); i++) {
NodeRef node1 = tree.getChild(node, i);
getDescendantLeaves(tree, node1, set);
}
}
}
/**
* @param tree the tree
* @param node the node to get external nodes below
* @return a set of noderefs of the leaf nodes descended from the given node.
*/
public static Set<NodeRef> getExternalNodes(Tree tree, NodeRef node) {
HashSet<NodeRef> set = new HashSet<NodeRef>();
getExternalNodes(tree, node, set);
return set;
}
/**
* @param tree the tree
* @param node the node to get external nodes below
* @param set is populated with noderefs of the leaf nodes descended from the given node.
*/
private static void getExternalNodes(Tree tree, NodeRef node, Set<NodeRef> set) {
if (tree.isExternal(node)) {
set.add(node);
} else {
for (int i = 0; i < tree.getChildCount(node); i++) {
NodeRef node1 = tree.getChild(node, i);
getExternalNodes(tree, node1, set);
}
}
}
/**
* 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 NodeRef getCommonAncestorNode(Tree tree, Set<String> leafNodes) {
int cardinality = leafNodes.size();
if (cardinality == 0) {
throw new IllegalArgumentException("No leaf nodes selected");
}
NodeRef[] mrca = {null};
getCommonAncestorNode(tree, tree.getRoot(), leafNodes, cardinality, mrca);
return mrca[0];
}
/*
* Private recursive function used by getCommonAncestorNode.
*/
private static int getCommonAncestorNode(Tree tree, NodeRef node,
Set<String> leafNodes, int cardinality,
NodeRef[] mrca) {
if (tree.isExternal(node)) {
if (leafNodes.contains(tree.getTaxonId(node.getNumber()))) {
if (cardinality == 1) {
mrca[0] = node;
}
return 1;
} else {
return 0;
}
}
int matches = 0;
for (int i = 0; i < tree.getChildCount(node); i++) {
NodeRef node1 = tree.getChild(node, i);
matches += getCommonAncestorNode(tree, node1, 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 the tree
* @param taxa the taxa
* @return A bitset with the node numbers set.
* @throws MissingTaxonException
* if a taxon in taxa is not contained in the tree
*/
public static BitSet getTipsBitSetForTaxa(Tree tree, TaxonList taxa) throws MissingTaxonException {
BitSet tips = new BitSet();
for (int n: getTipsForTaxa(tree, taxa)) {
tips.set(n);
}
return tips;
}
/**
* @param tree the tree
* @param taxa the taxa
* @return A HashSet of node numbers.
* @throws MissingTaxonException
* if a taxon in taxa is not contained in the tree
*/
public static Set<Integer> getTipsForTaxa(Tree tree, TaxonList taxa) throws MissingTaxonException {
Set<Integer> tips = new LinkedHashSet<Integer>();
for (int i = 0; i < taxa.getTaxonCount(); i++) {
Taxon taxon = taxa.getTaxon(i);
boolean found = false;
for (int j = 0; j < tree.getExternalNodeCount(); j++) {
NodeRef node = tree.getExternalNode(j);
if (tree.getNodeTaxon(node).getId().equals(taxon.getId())) {
tips.add(node.getNumber());
found = true;
break;
}
}
if (!found) {
throw new MissingTaxonException(taxon);
}
}
return tips;
}
public static boolean isMonophyletic(Tree tree, Set<String> leafNodes) {
return isMonophyletic(tree, leafNodes, Collections.<String>emptySet());
}
/**
* Performs a monophyly test on a set of leaf nodes. The nodes are monophyletic
* if there is a node in the tree which subtends all the taxa in the set (and
* only those taxa).
*
* @param tree a tree object to perform test on
* @param leafNodes a set of leaf node ids
* @param ignore a set of ids to ignore in monophyly assessment
* @return boolean is monophyletic?
*/
public static boolean isMonophyletic(Tree tree, Set<String> leafNodes, Set<String> ignore) {
int cardinality = leafNodes.size();
if (cardinality == 1) {
// A single selected leaf is always monophyletic
return true;
}
if (cardinality == tree.getExternalNodeCount()) {
// All leaf nodes are selected
return true;
}
if (cardinality == 0) {
throw new IllegalArgumentException("No leaf nodes selected");
}
int[] matchCount = {0};
int[] leafCount = {0};
boolean[] isMono = {false};
isMonophyletic(tree, tree.getRoot(), leafNodes, ignore, cardinality, matchCount, leafCount, isMono);
return isMono[0];
}
/**
* Private recursive function used by isMonophyletic.
*
* @param tree a tree object to perform test on
* @param node the node that is currently being assessed in recursive procedure
* @param leafNodes a set of leaf node ids
* @param ignore a set of ids to ignore in monophyly assessment
* @param cardinality the size of leafNodes set
* @return boolean is monophyletic?
*/
private static boolean isMonophyletic(Tree tree, NodeRef node,
Set<String> leafNodes, Set<String> ignore,
int cardinality,
int[] matchCount, int[] leafCount,
boolean[] isMono) {
if (tree.isExternal(node)) {
String id = tree.getNodeTaxon(node).getId();
if (leafNodes.contains(id)) {
matchCount[0] = 1;
} else {
matchCount[0] = 0;
}
if (!ignore.contains(id)) {
leafCount[0] = 1;
} else {
leafCount[0] = 0;
}
return false;
}
int mc = 0;
int lc = 0;
for (int i = 0; i < tree.getChildCount(node); i++) {
NodeRef node1 = tree.getChild(node, i);
boolean done = isMonophyletic(tree, node1, leafNodes, ignore, cardinality, matchCount, leafCount, isMono);
mc += matchCount[0];
lc += leafCount[0];
if (done) {
return true;
}
}
matchCount[0] = mc;
leafCount[0] = lc;
// If we haven't already found the MRCA, test this node
if (mc == lc && lc == cardinality) {
isMono[0] = true;
return true;
}
return false;
}
public static NodeRef getCommonAncestor(Tree tree, NodeRef n1, NodeRef n2) {
while( n1 != n2 ) {
if( tree.getNodeHeight(n1) < tree.getNodeHeight(n2) ) {
n1 = tree.getParent(n1);
} else {
n2 = tree.getParent(n2);
}
}
return n1;
}
// A lightweight version for finding the most recent common ancestor of a group of taxa.
// return the node-ref of the MRCA.
// would be nice to use nodeRef's, but they are not preserved :(
public static NodeRef getCommonAncestor(Tree tree, int[] nodes) {
NodeRef cur = tree.getNode(nodes[0]);
for(int k = 1; k < nodes.length; ++k) {
cur = getCommonAncestor(tree, cur, tree.getNode(nodes[k]));
}
return cur;
}
/**
* @param tree
* @param range
* @return the size of the largest clade with tips in the given range of times.
*/
public static int largestClade(Tree tree, double range) {
return largestClade(tree, tree.getRoot(), range, new double[]{0.0, 0.0});
}
/**
* @return the size of the largest clade with tips in the given range of times.
*/
private static int largestClade(Tree tree, NodeRef node, double range, double[] currentBounds) {
if (tree.isExternal(node)) {
currentBounds[0] = tree.getNodeHeight(node);
currentBounds[1] = tree.getNodeHeight(node);
return 1;
} else {
// get the bounds and max clade size of the left clade
int cladeSize1 = largestClade(tree, tree.getChild(node, 0), range, currentBounds);
double min = currentBounds[0];
double max = currentBounds[1];
// get the bounds and max clade size of the right clade
int cladeSize2 = largestClade(tree, tree.getChild(node, 1), range, currentBounds);
min = Math.min(min, currentBounds[0]);
max = Math.max(max, currentBounds[1]);
// update the joint bounds
currentBounds[0] = min;
currentBounds[1] = max;
// if the joint clade is valid return the joint size
if (max - min < range) {
return cladeSize1 + cladeSize2;
}
// if the joint clade is not valid return the max of the two
return Math.max(cladeSize1, cladeSize2);
}
}
/**
* Calculates the minimum number of steps for the parsimony reconstruction of a
* binary character defined by leafStates.
*
* @param tree a tree object to perform test on
* @param leafStates a set of booleans, one for each leaf node
* @return number of parsimony steps
*/
public static int getParsimonySteps(Tree tree, Set leafStates) {
int[] score = {0};
getParsimonySteps(tree, tree.getRoot(), leafStates, score);
return score[0];
}
private static int getParsimonySteps(Tree tree, NodeRef node, Set leafStates, int[] score) {
if (tree.isExternal(node)) {
return (leafStates.contains(tree.getTaxonId(node.getNumber())) ? 1 : 2);
} else {
int uState = getParsimonySteps(tree, tree.getChild(node, 0), leafStates, score);
int iState = uState;
for (int i = 1; i < tree.getChildCount(node); i++) {
int state = getParsimonySteps(tree, tree.getChild(node, i), leafStates, score);
uState = state | uState;
iState = state & iState;
}
if (iState == 0) {
score[0] += 1;
}
return uState;
}
}
/**
* Calculates the parsimony reconstruction of a binary character defined
* by leafStates at a given node.
*
* @param tree a tree object to perform test on
* @param node a NodeRef object from tree
* @param leafStates a set of booleans, one for each leaf node
* @return number of parsimony steps
*/
public static double getParsimonyState(Tree tree, NodeRef node, Set leafStates) {
int state = getParsimonyStateAtNode(tree, node, leafStates);
switch (state) {
case 1:
return 0.0;
case 2:
return 1.0;
default:
return 0.5;
}
}
private static int getParsimonyStateAtNode(Tree tree, NodeRef node, Set leafStates) {
if (tree.isExternal(node)) {
return (leafStates.contains(tree.getTaxonId(node.getNumber())) ? 1 : 2);
} else {
int uState = getParsimonyStateAtNode(tree, tree.getChild(node, 0), leafStates);
int iState = uState;
for (int i = 1; i < tree.getChildCount(node); i++) {
int state = getParsimonyStateAtNode(tree, tree.getChild(node, i), leafStates);
uState = state | uState;
iState = state & iState;
}
return uState;
}
}
/**
* determine preorder successor of this node
*
* @return next node
*/
public static NodeRef preorderSuccessor(Tree tree, NodeRef node) {
NodeRef next = null;
if (tree.isExternal(node)) {
NodeRef cn = node, ln = null; // Current and last node
// Go up
do {
if (tree.isRoot(cn)) {
next = cn;
break;
}
ln = cn;
cn = tree.getParent(cn);
}
while (tree.getChild(cn, tree.getChildCount(cn) - 1) == ln);
// Determine next node
if (next == null) {
// Go down one node
for (int i = 0; i < tree.getChildCount(cn) - 1; i++) {
if (tree.getChild(cn, i) == ln) {
next = tree.getChild(cn, i + 1);
break;
}
}
}
} else {
next = tree.getChild(node, 0);
}
return next;
}
/**
* determine a postorder traversal list of nodes in a tree
*
*/
public static void postOrderTraversalList(Tree tree, int[] postOrderList) {
final int nodeCount = tree.getNodeCount();
if (postOrderList.length != nodeCount) {
throw new IllegalArgumentException("Illegal list length");
}
int idx = nodeCount - 1;
int cidx = nodeCount - 1;
postOrderList[idx] = tree.getRoot().getNumber();
while (cidx > 0) {
NodeRef cNode = tree.getNode(postOrderList[idx]);
for(int i = 0; i < tree.getChildCount(cNode); ++i) {
cidx -= 1;
postOrderList[cidx] = tree.getChild(cNode, i).getNumber();
}
idx -= 1;
}
}
// 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().getNumber();
preOrderTraversalList(tree, 0, postOrderList);
}
static int preOrderTraversalList(Tree tree, int idx, int[] postOrderList) {
final NodeRef node = tree.getNode(postOrderList[idx]);
for(int i = 0; i < tree.getChildCount(node); ++i) {
final NodeRef child = tree.getChild(node, i);
idx += 1;
postOrderList[idx] = child.getNumber();
if( ! tree.isExternal(child) ) {
idx = preOrderTraversalList(tree, idx, postOrderList);
}
}
return idx;
}
/**
* determine postorder successor of a node
*
* @return next node
*/
public static NodeRef postorderSuccessor(Tree tree, NodeRef node) {
NodeRef cn = null;
NodeRef parent = tree.getParent(node);
if (tree.getRoot() == node) {
cn = node;
} else {
// Go up one node
if (tree.getChild(parent, tree.getChildCount(parent) - 1) == node) {
return parent;
}
// Go down one node
for (int i = 0; i < tree.getChildCount(parent) - 1; i++) {
if (tree.getChild(parent, i) == node) {
cn = tree.getChild(parent, i + 1);
break;
}
}
}
// Go down until leaf
while (tree.getChildCount(cn) > 0) {
cn = tree.getChild(cn, 0);
}
return cn;
}
/**
* Gets finds the most ancestral node with attribute set.
*/
public static NodeRef findNodeWithAttribute(Tree tree, String attribute) {
NodeRef root = tree.getRoot();
NodeRef node = root;
do {
if (tree.getNodeAttribute(node, attribute) != null) {
return node;
}
node = TreeUtils.preorderSuccessor(tree, node);
} while (node != root);
return null;
}
/**
* Gets finds the most recent date amongst the external nodes.
*/
public static dr.evolution.util.Date findMostRecentDate(Tree tree) {
dr.evolution.util.Date mostRecent = null;
for (int i = 0; i < tree.getExternalNodeCount(); i++) {
Taxon taxon = tree.getNodeTaxon(tree.getExternalNode(i));
dr.evolution.util.Date date = (dr.evolution.util.Date) taxon.getAttribute(dr.evolution.util.Date.DATE);
if ((date != null) && (mostRecent == null || date.after(mostRecent))) {
mostRecent = date;
}
}
return mostRecent;
}
/**
* Recursive function for constructing a newick tree representation in the given buffer.
*/
public static String newick(Tree tree) {
StringBuffer buffer = new StringBuffer();
newick(tree, tree.getRoot(), true, BranchLengthType.LENGTHS_AS_TIME, null, null, null, null, buffer);
buffer.append(";");
return buffer.toString();
}
/**
* @param tree tree to return in newick format
* @param dp the decimal places for branch lengths
* @return a string representation of the tree in newick format with branch lengths expressed with the given
* number of decimal places
*/
public static String newick(Tree tree, int dp) {
StringBuffer buffer = new StringBuffer();
// use the English locale to ensure there are no commas in the number!
NumberFormat format = NumberFormat.getNumberInstance(Locale.ENGLISH);
format.setMaximumFractionDigits(dp);
newick(tree, tree.getRoot(), true, BranchLengthType.LENGTHS_AS_TIME, format, null, null, null, buffer);
buffer.append(";");
return buffer.toString();
}
public static String newick(Tree tree, BranchRates branchRates) {
StringBuffer buffer = new StringBuffer();
newick(tree, tree.getRoot(), true, BranchLengthType.LENGTHS_AS_SUBSTITUTIONS, null, branchRates, null, null, buffer);
buffer.append(";");
return buffer.toString();
}
public static String newick(Tree tree,
TreeTraitProvider[] treeTraitProviders
) {
StringBuffer buffer = new StringBuffer();
newick(tree, tree.getRoot(), true, BranchLengthType.LENGTHS_AS_TIME, null, null, treeTraitProviders, null, buffer);
buffer.append(";");
return buffer.toString();
}
/**
* Recursive function for constructing a newick tree representation in the given buffer.
*/
public static String newickNoLengths(Tree tree) {
StringBuffer buffer = new StringBuffer();
newick(tree, tree.getRoot(), true, BranchLengthType.NO_BRANCH_LENGTHS, null, null, null, null, buffer);
buffer.append(";");
return buffer.toString();
}
/**
* Recursive function for constructing a newick tree representation in the given buffer.
*
* @param tree The tree
* @param node The node [tree.getRoot()]
* @param labels whether labels or numbers should be used
* @param lengths What type of branch lengths: NO_BRANCH_LENGTHS, LENGTHS_AS_TIME, LENGTHS_AS_SUBSTITUTIONS
* @param branchRates An optional BranchRates (or null) used to scale branch times into substitutions
* @param treeTraitProviders An array of TreeTraitProvider
* @param format formatter for branch lengths
* @param idMap A map if id names to integers that is used to overide node labels when present
* @param buffer The StringBuffer
*/
public static void newick(Tree tree, NodeRef node, boolean labels, BranchLengthType lengths, NumberFormat format,
BranchRates branchRates,
TreeTraitProvider[] treeTraitProviders,
Map<String, Integer> idMap, StringBuffer buffer) {
NodeRef parent = tree.getParent(node);
if (tree.isExternal(node)) {
if (!labels) {
int k = node.getNumber();
if (idMap != null) {
buffer.append(idMap.get(tree.getTaxonId(k)));
} else {
buffer.append((k + 1));
}
} else {
String label = tree.getTaxonId(node.getNumber());
if (label.contains(" ") || label.contains(":") || label.contains(";") || label.contains(",")) {
buffer.append("\"");
buffer.append(label);
buffer.append("\"");
} else {
buffer.append(label);
}
}
} else {
buffer.append("(");
newick(tree, tree.getChild(node, 0), labels, lengths, format,
branchRates,
treeTraitProviders, idMap,
buffer);
for (int i = 1; i < tree.getChildCount(node); i++) {
buffer.append(",");
newick(tree, tree.getChild(node, i), labels, lengths, format,
branchRates,
treeTraitProviders, idMap,
buffer);
}
buffer.append(")");
}
writeTreeTraits(buffer, tree, node, treeTraitProviders, TreeTrait.Intent.NODE);
if (parent != null && lengths != BranchLengthType.NO_BRANCH_LENGTHS) {
buffer.append(":");
writeTreeTraits(buffer, tree, node, treeTraitProviders, TreeTrait.Intent.BRANCH);
if (lengths != BranchLengthType.NO_BRANCH_LENGTHS) {
double length = tree.getNodeHeight(parent) - tree.getNodeHeight(node);
if (lengths == BranchLengthType.LENGTHS_AS_SUBSTITUTIONS) {
if (branchRates == null) {
throw new IllegalArgumentException("No BranchRates provided");
}
length *= branchRates.getBranchRate(tree, node);
}
String lengthString;
if (format != null) {
lengthString = format.format(length);
} else {
lengthString = String.valueOf(length);
}
buffer.append(lengthString);
}
}
}
private static void writeTreeTraits(StringBuffer buffer, Tree tree, NodeRef node, TreeTraitProvider[] treeTraitProviders, TreeTrait.Intent intent) {
if (treeTraitProviders != null) {
boolean hasAttribute = false;
for (TreeTraitProvider ttp : treeTraitProviders) {
TreeTrait[] tts = ttp.getTreeTraits();
for (TreeTrait treeTrait: tts) {
if (treeTrait.getLoggable() && treeTrait.getIntent() == intent) {
String value = treeTrait.getTraitString(tree, node);
if (value != null) {
if (!hasAttribute) {
buffer.append("[&");
hasAttribute = true;
} else {
buffer.append(",");
}
buffer.append(treeTrait.getTraitName());
buffer.append("=");
buffer.append(value);
// if (values.length > 1) {
// buffer.append("{");
// buffer.append(values[0]);
// for (int i = 1; i < values.length; i++) {
// buffer.append(",");
// buffer.append(values[i]);
// }
// buffer.append("}");
// } else {
// buffer.append(values[0]);
// }
}
}
}
}
if (hasAttribute) {
buffer.append("]");
}
}
}
/**
* Recursive function for constructing a newick tree representation in the given buffer.
*/
public static String uniqueNewick(Tree tree, NodeRef node) {
if (tree.isExternal(node)) {
//buffer.append(tree.getNodeTaxon(node).getId());
return tree.getNodeTaxon(node).getId();
} else {
StringBuffer buffer = new StringBuffer("(");
ArrayList<String> subtrees = new ArrayList<String>();
for (int i = 0; i < tree.getChildCount(node); i++) {
NodeRef child = tree.getChild(node, i);
subtrees.add(uniqueNewick(tree, child));
}
Collections.sort(subtrees);
for (int i = 0; i < subtrees.size(); i++) {
buffer.append(subtrees.get(i));
if (i < subtrees.size() - 1) {
buffer.append(",");
}
}
buffer.append(")");
return buffer.toString();
}
}
/**
* Recursive function for constructing a newick tree representation in the given buffer.
*/
public static Tree rotateByName(Tree tree) {
return new SimpleTree(rotateNodeByName(tree, tree.getRoot()));
}
/**
* Recursive function for constructing a newick tree representation in the given buffer.
*/
private static SimpleNode rotateNodeByName(Tree tree, NodeRef node) {
if (tree.isExternal(node)) {
return new SimpleNode(tree, node);
} else {
SimpleNode parent = new SimpleNode(tree, node);
NodeRef child1 = tree.getChild(node, 0);
NodeRef child2 = tree.getChild(node, 1);
String subtree1 = uniqueNewick(tree, child1);
String subtree2 = uniqueNewick(tree, child2);
if (subtree1.compareTo(subtree2) > 0) {
parent.addChild(rotateNodeByName(tree, child2));
parent.addChild(rotateNodeByName(tree, child1));
} else {
parent.addChild(rotateNodeByName(tree, child1));
parent.addChild(rotateNodeByName(tree, child2));
}
return parent;
}
}
public static MutableTree rotateTreeByComparator(Tree tree, Comparator<NodeRef> comparator) {
return new SimpleTree(rotateTreeByComparator(tree, tree.getRoot(), comparator));
}
/**
* Recursive function for constructing a newick tree representation in the given buffer.
*/
private static SimpleNode rotateTreeByComparator(Tree tree, NodeRef node, Comparator<NodeRef> comparator) {
SimpleNode newNode = new SimpleNode();
newNode.setHeight(tree.getNodeHeight(node));
newNode.setRate(tree.getNodeRate(node));
newNode.setId(tree.getTaxonId(node.getNumber()));
newNode.setNumber(node.getNumber());
newNode.setTaxon(tree.getNodeTaxon(node));
if (!tree.isExternal(node)) {
NodeRef child1 = tree.getChild(node, 0);
NodeRef child2 = tree.getChild(node, 1);
if (comparator.compare(child1, child2) > 0) {
newNode.addChild(rotateTreeByComparator(tree, child2, comparator));
newNode.addChild(rotateTreeByComparator(tree, child1, comparator));
} else {
newNode.addChild(rotateTreeByComparator(tree, child1, comparator));
newNode.addChild(rotateTreeByComparator(tree, child2, comparator));
}
}
return newNode;
}
public static Comparator<NodeRef> createNodeDensityComparator(final Tree tree) {
return new Comparator<NodeRef>() {
public int compare(NodeRef node1, NodeRef node2) {
return getLeafCount(tree, node2) - getLeafCount(tree, node1);
}
public boolean equals(NodeRef node1, NodeRef node2) {
return getLeafCount(tree, node1) == getLeafCount(tree, node2);
}
};
}
public static Comparator<NodeRef> createNodeDensityMinNodeHeightComparator(final Tree tree) {
return new Comparator<NodeRef>() {
public int compare(NodeRef node1, NodeRef node2) {
int larger = getLeafCount(tree, node1) - getLeafCount(tree, node2);
if (larger != 0) return larger;
double tipRecent = getMinNodeHeight(tree, node2) - getMinNodeHeight(tree, node1);
if (tipRecent > 0.0) return 1;
if (tipRecent < 0.0) return -1;
return 0;
}
};
}
public static boolean allDisjoint(SimpleNode[] nodes) {
// check with java 1.6
Set<String>[] ids = new Set[nodes.length];
for (int k = 0; k < nodes.length; ++k) {
ids[k] = TreeUtils.getLeafSet(new SimpleTree(nodes[k]));
for (int j = 0; j < k; ++j) {
Set<String> intersection = new HashSet<String>(ids[j]);
intersection.retainAll(ids[k]);
if (intersection.size() > 0) {
return false;
}
}
}
return true;
}
/**
* Compares 2 trees and returns true if they have the same topology (same taxon
* order is assumed).
*/
public static boolean equal(Tree tree1, Tree tree2) {
return uniqueNewick(tree1, tree1.getRoot()).equals(uniqueNewick(tree2, tree2.getRoot()));
}
private static Node convertToJebl(Tree tree, NodeRef node, SimpleRootedTree jtree) {
if (tree.isExternal(node)) {
String taxonId = tree.getTaxonId(node.getNumber());
Node externalNode = jtree.createExternalNode(jebl.evolution.taxa.Taxon.getTaxon(taxonId));
jtree.setHeight(externalNode, tree.getNodeHeight(node));
return externalNode;
}
List<Node> jchildren = new ArrayList<Node>();
for (int nc = 0; nc < tree.getChildCount(node); ++nc) {
NodeRef child = tree.getChild(node, nc);
Node node1 = convertToJebl(tree, child, jtree);
jtree.setHeight(node1, tree.getNodeHeight(child));
jchildren.add(node1);
}
return jtree.createInternalNode(jchildren);
}
/**
* Convert from beast tree to JEBL tree.
* Note that currently only topology and branch lengths are preserved.
* Can add attributes later if needed.
*
* @param tree beast
* @return jebl tree
*/
static public SimpleRootedTree asJeblTree(Tree tree) {
SimpleRootedTree jtree = new SimpleRootedTree();
convertToJebl(tree, tree.getRoot(), jtree);
jtree.setHeight(jtree.getRootNode(), tree.getNodeHeight(tree.getRoot()));
return jtree;
}
/**
* Gets the set of clades in a tree
*
* @param tree the tree
* @return the set of clades
*/
public static Set<Set<String>> getClades(Tree tree) {
Set<Set<String>> clades = new HashSet<Set<String>>();
getClades(tree, tree.getRoot(), null, clades);
return clades;
}
private static void getClades(Tree tree, NodeRef node, Set<String> leaves, Set<Set<String>> clades) {
if (tree.isExternal(node)) {
leaves.add(tree.getTaxonId(node.getNumber()));
} else {
Set<String> ls = new HashSet<String>();
for (int i = 0; i < tree.getChildCount(node); i++) {
NodeRef node1 = tree.getChild(node, i);
getClades(tree, node1, ls, clades);
}
if (leaves != null) {
// except for the root clade...
leaves.addAll(ls);
clades.add(ls);
}
}
}
/**
* Tests whether the given tree is compatible with a set of clades
*
* @param tree the test tree
* @param clades the set of clades
* @return
*/
public static boolean isCompatible(Tree tree, Set<Set<String>> clades) {
return isCompatible(tree, tree.getRoot(), null, clades);
}
private static boolean isCompatible(Tree tree, NodeRef node, Set<String> leaves, Set<Set<String>> clades) {
if (tree.isExternal(node)) {
leaves.add(tree.getTaxonId(node.getNumber()));
return true;
} else {
Set<String> ls = new HashSet<String>();
for (int i = 0; i < tree.getChildCount(node); i++) {
NodeRef node1 = tree.getChild(node, i);
if (!isCompatible(tree, node1, ls, clades)) {
// as soon as we have an incompatibility break out...
return false;
}
}
if (leaves != null) {
// except for the root clade...
for (Set<String> clade : clades) {
Set<String> intersection = new HashSet<String>(clade);
intersection.retainAll(ls);
if (intersection.size() != 0 &&
intersection.size() != ls.size() &&
intersection.size() != clade.size()) {
return false;
}
}
leaves.addAll(ls);
}
}
return true;
}
public static void correctBranchLengthToGetUltrametricTree(Tree tree, double givenLength) {
}
private void setHeight(Tree tree, NodeRef node, double givenLength) {
if (tree.getChildCount(node) == 0) {
}
for (int i = 0; i < tree.getChildCount(node); i++) {
}
}
public enum BranchLengthType {
NO_BRANCH_LENGTHS, LENGTHS_AS_TIME, LENGTHS_AS_SUBSTITUTIONS
}
public static class MissingTaxonException extends Exception {
/**
*
*/
private static final long serialVersionUID = 8468656622238269963L;
public MissingTaxonException(Taxon taxon) {
super(taxon.getId());
}
}
}