/*
* NewickParser.java
*
* Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
*
* This file is part of BEAST.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* BEAST 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package dr.evoxml;
import dr.evolution.io.Importer;
import dr.evolution.io.NewickImporter;
import dr.evolution.tree.*;
import dr.evolution.util.Taxon;
import dr.evolution.util.Units;
import dr.evoxml.util.XMLUnits;
import dr.xml.*;
import java.io.IOException;
/**
* @author Alexei Drummond
* @author Andrew Rambaut
* @version $Id: NewickParser.java,v 1.7 2006/04/25 14:41:08 rambaut Exp $
*/
public class NewickParser extends AbstractXMLObjectParser {
public static final String NEWICK = "newick";
public static final String UNITS = "units";
public static final String RESCALE_HEIGHT = "rescaleHeight";
public static final String RESCALE_LENGTH = "rescaleLength";
public static final String USING_DATES = SimpleTreeParser.USING_DATES;
public static final String USING_HEIGHTS = "usingHeights";
public NewickParser() {
rules = new XMLSyntaxRule[]{
AttributeRule.newBooleanRule(USING_DATES, true),
AttributeRule.newBooleanRule(USING_HEIGHTS, true),
AttributeRule.newDoubleRule(RESCALE_HEIGHT, true, "Attempt to rescale the tree to the given root height"),
AttributeRule.newDoubleRule(RESCALE_LENGTH, true, "Attempt to rescale the tree to the given total length"),
new StringAttributeRule(UNITS, "The branch length units of this tree", Units.UNIT_NAMES, true),
new ElementRule(String.class, "The NEWICK format tree. Tip labels are taken to be Taxon IDs")
};
}
public String getParserName() {
return NEWICK;
}
public Object parseXMLObject(XMLObject xo) throws XMLParseException {
final Units.Type units = XMLUnits.Utils.getUnitsAttr(xo);
// boolean usingDates = xo.getAttribute(USING_DATES, true);
boolean usingDates = true;
if (xo.hasAttribute(USING_DATES)) {
usingDates = xo.getAttribute(USING_DATES, true);
}
boolean usingHeights = false;
if (xo.hasAttribute(USING_HEIGHTS)) {
usingHeights = xo.getAttribute(USING_HEIGHTS, true);
}
// System.out.println("UsingDates=" + usingDates + " usingHeights= " + usingHeights);
if (usingDates && usingHeights) {
throw new XMLParseException("Unable to use both dates and node heights. Specify value of usingDates attribute.");
}
// else if (!usingDates && !usingHeights) {
// System.out.println("Tree is assumed to be ultrametric");
// }
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < xo.getChildCount(); i++) {
if (xo.getChild(i) instanceof String) {
buffer.append((String) xo.getChild(i));
} else {
throw new XMLParseException("illegal element in newick element");
}
}
java.io.Reader reader = new java.io.StringReader(buffer.toString());
NewickImporter importer = new NewickImporter(reader);
FlexibleTree tree;
try {
tree = (FlexibleTree) importer.importTree(null);
} catch (IOException ioe) {
throw new XMLParseException("error parsing tree in newick element");
} catch (NewickImporter.BranchMissingException bme) {
throw new XMLParseException("branch missing in tree in newick element");
} catch (Importer.ImportException ime) {
throw new XMLParseException("error parsing tree in newick element - " + ime.getMessage());
}
if (tree == null) {
throw new XMLParseException("Failed to read tree");
}
tree.setUnits(units);
for (int i = 0; i < tree.getTaxonCount(); i++) {
FlexibleNode node = (FlexibleNode) tree.getExternalNode(i);
String id = node.getTaxon().getId();
Taxon taxon = null;
XMLObject obj = getStore().get(id);
if (obj != null && obj.getNativeObject() instanceof Taxon) {
taxon = (Taxon) obj.getNativeObject();
}
if (taxon != null) {
node.setTaxon(taxon);
} else {
throw new XMLParseException("unknown taxon, " + id + ", in newick tree");
}
}
if (usingDates) {
for (int i = 0; i < tree.getTaxonCount(); i++) {
NodeRef node = tree.getExternalNode(i);
dr.evolution.util.Date date = (dr.evolution.util.Date) tree.getTaxonAttribute(i, dr.evolution.util.Date.DATE);
if (date == null) {
date = (dr.evolution.util.Date) tree.getNodeAttribute(tree.getExternalNode(i), dr.evolution.util.Date.DATE);
}
double height = 0.0;
double nodeHeight = tree.getNodeHeight(node);
if (date != null) {
height = Taxon.getHeightFromDate(date);
}
if (Math.abs(nodeHeight - height) > 1e-5) {
System.out.println(" Changing height of node " + tree.getTaxon(node.getNumber()) + " from " + nodeHeight + " to " + height);
tree.setNodeHeight(node, height);
}
}
for (int i = 0; i < tree.getInternalNodeCount(); i++) {
dr.evolution.util.Date date = (dr.evolution.util.Date) tree.getNodeAttribute(tree.getInternalNode(i), dr.evolution.util.Date.DATE);
if (date != null) {
double height = Taxon.getHeightFromDate(date);
tree.setNodeHeight(tree.getInternalNode(i), height);
}
}// END: i loop
MutableTree.Utils.correctHeightsForTips(tree);
} else if (!usingDates && !usingHeights) {
System.out.println("Tree is assumed to be ultrametric");
// not using dates or heights
for (int i = 0; i < tree.getTaxonCount(); i++) {
final NodeRef leaf = tree.getExternalNode(i);
final double h = tree.getNodeHeight(leaf);
if (h != 0.0) {
double zero = 0.0;
System.out.println(" Changing height of leaf node " + tree.getTaxon(leaf.getNumber()) + " from " + h + " to " + zero);
tree.setNodeHeight(leaf, zero);
}
}// END: i loop
} else {
System.out.println("Using node heights.");
}// END: usingDates check
if (xo.hasAttribute(RESCALE_HEIGHT)) {
double rescaleHeight = xo.getDoubleAttribute(RESCALE_HEIGHT);
double scale = rescaleHeight / tree.getNodeHeight(tree.getRoot());
for (int i = 0; i < tree.getInternalNodeCount(); i++) {
NodeRef n = tree.getInternalNode(i);
tree.setNodeHeight(n, tree.getNodeHeight(n) * scale);
}
}
if (xo.hasAttribute(RESCALE_LENGTH)) {
double rescaleLength = xo.getDoubleAttribute(RESCALE_LENGTH);
double scale = rescaleLength / TreeUtils.getTreeLength(tree, tree.getRoot());
for (int i = 0; i < tree.getInternalNodeCount(); i++) {
NodeRef n = tree.getInternalNode(i);
tree.setNodeHeight(n, tree.getNodeHeight(n) * scale);
}
}
//System.out.println("Constructed newick tree = " + Tree.Utils.uniqueNewick(tree, tree.getRoot()));
return tree;
}
//************************************************************************
// AbstractXMLObjectParser implementation
//************************************************************************
public String getParserDescription() {
return "Constructs a tree from a NEWICK format tree description";
}
public String getExample() {
return "<" + getParserName() + " " + UNITS + "=\"" + Units.Utils.getDefaultUnitName(Units.Type.YEARS) + "\">" + " ((A:1.0, B:1.0):1.0,(C:2.0, D:2.0):1.0); </" + getParserName() + ">";
}
public XMLSyntaxRule[] getSyntaxRules() {
return rules;
}
private final XMLSyntaxRule[] rules;
public Class getReturnType() {
return Tree.class;
}
}