/*
Copyright (C) 2011 Diego Darriba, David Posada
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package es.uvigo.darwin.jmodeltest.tree;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import pal.io.FormattedOutput;
import pal.misc.IdGroup;
import pal.misc.Identifier;
import pal.tree.Node;
import pal.tree.NodeUtils;
import pal.tree.ReadTree;
import pal.tree.SplitSystem;
import pal.tree.SplitUtils;
import pal.tree.Tree;
import pal.tree.TreeParseException;
public class TreeUtilities {
public static final int DEFAULT_COLUMN_WIDTH = 70;
public static final String TREE_WEIGHT_ATTRIBUTE = "weight";
public static final String TREE_CLADE_SUPPORT_ATTRIBUTE = "support";
public static final String TREE_NAME_ATTRIBUTE = "treeName";
public TreeUtilities() {
}
/****************************
* readTree ********************************** * Reads a tree in Newick
* format, rooted or unroored, binary or with * polytomies, and with or
* without branch lengths. Returns the root * node *
*
* @throws IOException
* @throws TreeParseException
* *
************************************************************************/
public static Tree readTree(String treefilename) throws IOException,
TreeParseException {
Tree tree;
try {
tree = new ReadTree(treefilename);
} catch (TreeParseException e) {
throw e;
} catch (IOException e) {
throw e;
}
return tree;
}
/**
* Make sure subtree below node has consistent heights, i.e. node height is higher than it's descendants
*
* @param tree the tree
* @param node the node
*
* @return height of node
*/
public static double insureConsistency(Tree tree, Node node) {
double height = TreeUtilities.safeNodeHeight(tree, node);
if (node.isLeaf()) {
return height;
} else {
for (int i = 0; i < node.getChildCount(); i++) {
Node n = node.getChild(i);
final double childHeight = insureConsistency(tree, n);
height = Math.max(height, childHeight);
}
}
node.setNodeHeight(height);
return height;
}
/**
* Calculates the number of branches from node to most remote tip.
*
* @param node the starting node
*
* @return the node distance
*/
public static int nodeDistance(final Node node) {
if (node.isLeaf()) {
return 0;
}
int d = 0;
for (int i = 0; i < node.getChildCount(); i++) {
Node n = node.getChild(i);
d = Math.max(d, nodeDistance(n));
}
return d + 1;
}
/**
* Calculates the safe node height.
*
* @param tree the tree
* @param node the node
*
* @return the height of the node
*/
public static double safeNodeHeight(final Tree tree, final Node node) {
if (node.getNodeHeight() > 0.0) {
return node.getNodeHeight();
}
return TreeUtilities.nodeDistance(node);
}
private static void putCharAtLevel(PrintWriter out, int level, char c,
int[] position) {
int n = position[level] - 1;
for (int i = 0; i < n; i++) {
out.print(' ');
}
out.print(c);
}
private static void printlnNodeWithNumberAndLabel(PrintWriter out, Node node, int level,
int numExternalNodes, boolean[] umbrella, int[] position) {
for (int i = 0; i < level - 1; i++) {
if (umbrella[i]) {
putCharAtLevel(out, i, '|', position);
} else {
putCharAtLevel(out, i, ' ', position);
}
}
putCharAtLevel(out, level - 1, '+', position);
int branchNumber;
if (node.isLeaf()) {
branchNumber = node.getNumber() + 1;
} else {
branchNumber = node.getNumber() + 1 + numExternalNodes;
}
String numberAsString = Integer.toString(branchNumber);
int numDashs = position[level] - numberAsString.length();
for (int i = 0; i < numDashs ; i++) {
out.print('-');
}
out.print(numberAsString);
if (node.isLeaf()) {
out.println(" " + node.getIdentifier());
} else {
if (!node.getIdentifier().equals(Identifier.ANONYMOUS)) {
out.print("(" + node.getIdentifier() + ")");
}
out.println();
}
}
private static void printNodeInASCII(PrintWriter out, Node node, int level, int m, int maxm,
int numExternalNodes, boolean[] umbrella, int[] position, double proportion, int minLength) {
position[level] = (int) (node.getBranchLength() * proportion);
if (position[level] < minLength) {
position[level] = minLength;
}
if (node.isLeaf()) // external branch
{
if (m == maxm - 1) {
umbrella[level - 1] = true;
}
printlnNodeWithNumberAndLabel(out, node, level, numExternalNodes, umbrella, position);
if (m == 0) {
umbrella[level - 1] = false;
}
} else // internal branch
{
for (int n = node.getChildCount() - 1; n > -1; n--) {
printNodeInASCII(out, node.getChild(n), level + 1, n, node.getChildCount(),
numExternalNodes, umbrella, position, proportion, minLength);
if (m == maxm - 1 && n == node.getChildCount() / 2) {
umbrella[level - 1] = true;
}
if (n != 0) {
if (n == node.getChildCount() / 2) {
printlnNodeWithNumberAndLabel(out, node, level, numExternalNodes, umbrella, position);
} else {
for (int i = 0; i < level + 1; i++) {
if (umbrella[i]) {
putCharAtLevel(out, i, '|', position);
} else {
putCharAtLevel(out, i, ' ', position);
}
}
out.println();
}
}
if (m == 0 && n == node.getChildCount() / 2) {
umbrella[level - 1] = false;
}
}
}
}
// Print picture of current tree in ASCII
public static void printASCII(Tree tree, PrintWriter out) {
tree.createNodeList();
int numExternalNodes = tree.getExternalNodeCount();
int numInternalNodes = tree.getInternalNodeCount();
int numBranches = numInternalNodes + numExternalNodes - 1;
boolean[] umbrella = new boolean[numExternalNodes];
int[] position = new int[numExternalNodes];
int minLength = (Integer.toString(numBranches)).length() + 1;
int MAXCOLUMN = 40;
Node root = tree.getRoot();
if (root.getNodeHeight() == 0.0) {
NodeUtils.lengths2Heights(root);
}
double proportion = (double) MAXCOLUMN / root.getNodeHeight();
for (int n = 0; n < numExternalNodes; n++) {
umbrella[n] = false;
}
position[0] = 1;
for (int i = root.getChildCount() - 1; i > -1; i--) {
printNodeInASCII(out, root.getChild(i), 1, i, root.getChildCount(),
numExternalNodes, umbrella, position, proportion, minLength);
if (i != 0) {
putCharAtLevel(out, 0, '|', position);
out.println();
}
}
}
public static void printNH(PrintWriter out, Tree tree, Node node,
boolean printLengths, boolean printInternalLabels,
boolean printCladeSupport) {
if (!node.isLeaf()) {
out.print("(");
for (int i = 0; i < node.getChildCount(); i++) {
if (i != 0) {
out.print(",");
}
printNH(out, tree, node.getChild(i), printLengths,
printInternalLabels, printCladeSupport);
}
out.print(")");
}
if (!node.isRoot()) {
if (node.isLeaf() || printInternalLabels) {
String id = node.getIdentifier().toString();
out.print(id);
}
if (printCladeSupport) {
if (tree.getAttribute(node, TREE_CLADE_SUPPORT_ATTRIBUTE) != null) {
double support = (Double) tree.getAttribute(node,
TREE_CLADE_SUPPORT_ATTRIBUTE);
out.printf(":" + FormattedOutput.getInstance().getDecimalString(support, 4));
}
}
if (printLengths) {
out.printf(":" + FormattedOutput.getInstance().getDecimalString(node.getBranchLength(), 10));
}
}
}
public static String toNewick(Tree tree, boolean printLengths,
boolean printInternalLabels, boolean printCladeSupport) {
StringWriter sw = new StringWriter();
PrintWriter mp = new PrintWriter(sw);
printNH(mp, tree, tree.getRoot(), printLengths, printInternalLabels,
printCladeSupport);
sw.append(';');
return sw.toString();
}
public static double getEuclideanTreeDistance(Tree t1, Tree t2) {
double sum = 0.0;
int numberOfInternalNodes = t1.getInternalNodeCount();
if (numberOfInternalNodes != t2.getInternalNodeCount()) {
throw new RuntimeException("Different number of internal nodes: " +
t1.getInternalNodeCount() + " vs " + t2.getInternalNodeCount());
}
int numberOfExternalNodes = t1.getExternalNodeCount();
if (numberOfExternalNodes != t2.getExternalNodeCount()) {
throw new RuntimeException("Different number of external nodes: " +
t1.getInternalNodeCount() + " vs " + t2.getInternalNodeCount());
}
for (int i = 0; i < numberOfInternalNodes; i++) {
double bl1 = t1.getInternalNode(i).getBranchLength();
double bl2 = t2.getInternalNode(i).getBranchLength();
sum += (bl1 - bl2) * (bl1 - bl2);
}
for (int i = 0; i < numberOfExternalNodes; i++) {
double bl1 = t1.getExternalNode(i).getBranchLength();
double bl2 = t2.getExternalNode(i).getBranchLength();
sum += (bl1 - bl2) * (bl1 - bl2);
}
return Math.sqrt(sum);
}
public static double getRobinsonFouldsTreeDistance(Tree t1, Tree t2) {
SplitSystem s1 = SplitUtils.getSplits(t1);
IdGroup idGroup = s1.getIdGroup();
SplitSystem s2 = SplitUtils.getSplits(idGroup, t2);
if (s1.getLabelCount() != s2.getLabelCount())
throw new IllegalArgumentException("Number of labels must be the same!");
int ns1 = s1.getSplitCount();
int ns2 = s2.getSplitCount();
// number of splits in t1 missing in t2
int fn = 0;
for (int i = 0; i < ns1; i++)
{
if (!s2.hasSplit(s1.getSplit(i))) fn++;
}
// number of splits in t2 missing in t1
int fp = 0;
for (int i = 0; i < ns2; i++)
{
if (!s1.hasSplit(s2.getSplit(i))) fp++;
}
return ((double) fp + (double) fn);
}
} // end of class