/*
* TreeLoggerParser.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.evomodelxml.tree;
import dr.evolution.colouring.TreeColouringProvider;
import dr.evolution.tree.*;
import dr.evomodel.tree.TreeLogger;
import dr.inference.loggers.LogFormatter;
import dr.inference.loggers.Loggable;
import dr.inference.loggers.TabDelimitedFormatter;
import dr.inference.model.Likelihood;
import dr.inference.model.Model;
import dr.inference.model.Parameter;
import dr.inferencexml.loggers.LoggerParser;
import dr.util.Identifiable;
import dr.xml.*;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* @author Alexei Drummond
* @author Andrew Rambaut
*/
public class TreeLoggerParser extends LoggerParser {
public static final String LOG_TREE = "logTree";
public static final String NEXUS_FORMAT = "nexusFormat";
// public static final String USING_RATES = "usingRates";
public static final String BRANCH_LENGTHS = "branchLengths";
public static final String TIME = "time";
public static final String SUBSTITUTIONS = "substitutions";
public static final String SORT_TRANSLATION_TABLE = "sortTranslationTable";
public static final String MAP_NAMES = "mapNamesToNumbers";
public static final String DECIMAL_PLACES = "dp";
// public static final String NORMALISE_MEAN_RATE_TO = "normaliseMeanRateTo";
public static final String FILTER_TRAITS = "traitFilter";
public static final String TREE_TRAIT = "trait";
public static final String NAME = "name";
public static final String TAG = "tag";
public String getParserName() {
return LOG_TREE;
}
protected void parseXMLParameters(XMLObject xo) throws XMLParseException
{
// reset this every time...
branchRates = null;
tree = (Tree) xo.getChild(Tree.class);
title = xo.getAttribute(TITLE, "");
nexusFormat = xo.getAttribute(NEXUS_FORMAT, false);
sortTranslationTable = xo.getAttribute(SORT_TRANSLATION_TABLE, true);
boolean substitutions = xo.getAttribute(BRANCH_LENGTHS, "").equals(SUBSTITUTIONS);
List<TreeAttributeProvider> taps = new ArrayList<TreeAttributeProvider>();
List<TreeTraitProvider> ttps = new ArrayList<TreeTraitProvider>();
// ttps2 are for TTPs that are not specified within a Trait element. These are only
// included if not already added through a trait element to avoid duplication of
// (in particular) the BranchRates which is required for substitution trees.
List<TreeTraitProvider> ttps2 = new ArrayList<TreeTraitProvider>();
for (int i = 0; i < xo.getChildCount(); i++) {
Object cxo = xo.getChild(i);
// This needs to be refactored into using a TreeTrait if Colouring is resurrected...
// if (cxo instanceof TreeColouringProvider) {
// final TreeColouringProvider colouringProvider = (TreeColouringProvider) cxo;
// baps.add(new BranchAttributeProvider() {
//
// public String getBranchAttributeLabel() {
// return "deme";
// }
//
// public String getAttributeForBranch(Tree tree, NodeRef node) {
// TreeColouring colouring = colouringProvider.getTreeColouring(tree);
// BranchColouring bcol = colouring.getBranchColouring(node);
// StringBuilder buffer = new StringBuilder();
// if (bcol != null) {
// buffer.append("{");
// buffer.append(bcol.getChildColour());
// for (int i = 1; i <= bcol.getNumEvents(); i++) {
// buffer.append(",");
// buffer.append(bcol.getBackwardTime(i));
// buffer.append(",");
// buffer.append(bcol.getBackwardColourAbove(i));
// }
// buffer.append("}");
// }
// return buffer.toString();
// }
// });
//
// } else
if (cxo instanceof Likelihood) {
final Likelihood likelihood = (Likelihood) cxo;
taps.add(new TreeAttributeProvider() {
public String[] getTreeAttributeLabel() {
return new String[] {"lnP"};
}
public String[] getAttributeForTree(Tree tree) {
return new String[] {Double.toString(likelihood.getLogLikelihood())};
}
});
}
if (cxo instanceof TreeAttributeProvider) {
taps.add((TreeAttributeProvider) cxo);
}
if (cxo instanceof TreeTraitProvider) {
if (xo.hasAttribute(FILTER_TRAITS)) {
String[] matches = ((String) xo.getAttribute(FILTER_TRAITS)).split("[\\s,]+");
TreeTraitProvider ttp = (TreeTraitProvider) cxo;
TreeTrait[] traits = ttp.getTreeTraits();
List<TreeTrait> filteredTraits = new ArrayList<TreeTrait>();
for (String match : matches) {
for (TreeTrait trait : traits) {
if (trait.getTraitName().startsWith(match)) {
filteredTraits.add(trait);
}
}
}
if (filteredTraits.size() > 0) {
ttps2.add(new TreeTraitProvider.Helper(filteredTraits));
}
} else {
// Add all of them
ttps2.add((TreeTraitProvider) cxo);
}
}
if (cxo instanceof XMLObject) {
XMLObject xco = (XMLObject)cxo;
if (xco.getName().equals(TREE_TRAIT)) {
TreeTraitProvider ttp = (TreeTraitProvider)xco.getChild(TreeTraitProvider.class);
if (xco.hasAttribute(NAME)) {
// a specific named trait is required (optionally with a tag to name it in the tree file)
String name = xco.getStringAttribute(NAME);
final TreeTrait trait = ttp.getTreeTrait(name);
if (trait == null) {
String childName = "TreeTraitProvider";
if (ttp instanceof Likelihood) {
childName = ((Likelihood)ttp).prettyName();
} else if (ttp instanceof Model) {
childName = ((Model)ttp).getModelName();
}
throw new XMLParseException("Trait named, " + name + ", not found for " + childName);
}
final String tag;
if (xco.hasAttribute(TAG)) {
tag = xco.getStringAttribute(TAG);
} else {
tag = name;
}
ttps.add(new TreeTraitProvider.Helper(tag, new TreeTrait() {
public String getTraitName() {
return tag;
}
public Intent getIntent() {
return trait.getIntent();
}
public Class getTraitClass() {
return trait.getTraitClass();
}
public Object getTrait(Tree tree, NodeRef node) {
return trait.getTrait(tree, node);
}
public String getTraitString(Tree tree, NodeRef node) {
return trait.getTraitString(tree, node);
}
public boolean getLoggable() {
return trait.getLoggable();
}
}));
} else if (xo.hasAttribute(FILTER_TRAITS)) {
// else a filter attribute is given to ask for all traits that starts with a specific
// string
String[] matches = ((String) xo.getAttribute(FILTER_TRAITS)).split("[\\s,]+");
TreeTrait[] traits = ttp.getTreeTraits();
List<TreeTrait> filteredTraits = new ArrayList<TreeTrait>();
for (String match : matches) {
for (TreeTrait trait : traits) {
if (trait.getTraitName().startsWith(match)) {
filteredTraits.add(trait);
}
}
}
if (filteredTraits.size() > 0) {
ttps.add(new TreeTraitProvider.Helper(filteredTraits));
}
} else {
// neither named or filtered traits so just add them all
ttps.add(ttp);
}
}
}
// Without this next block, branch rates get ignored :-(
// BranchRateModels are now TreeTraitProviders so this is not needed.
// if (cxo instanceof TreeTrait) {
// final TreeTrait trait = (TreeTrait)cxo;
// TreeTraitProvider ttp = new TreeTraitProvider() {
// public TreeTrait[] getTreeTraits() {
// return new TreeTrait[] { trait };
// }
//
// public TreeTrait getTreeTrait(String key) {
// if (key.equals(trait.getTraitName())) {
// return trait;
// }
// return null;
// }
// };
// ttps.add(ttp);
// }
//}
// be able to put arbitrary statistics in as tree attributes
if (cxo instanceof Loggable) {
final Loggable loggable = (Loggable) cxo;
taps.add(new TreeAttributeProvider() {
public String[] getTreeAttributeLabel() {
String[] labels = new String[loggable.getColumns().length];
for (int i = 0; i < loggable.getColumns().length; i++) {
labels[i] = loggable.getColumns()[i].getLabel();
}
return labels;
}
public String[] getAttributeForTree(Tree tree) {
String[] values = new String[loggable.getColumns().length];
for (int i = 0; i < loggable.getColumns().length; i++) {
values[i] = loggable.getColumns()[i].getFormatted();
}
return values;
}
});
}
}
// if we don't have any of the newer trait elements but we do have some tree trait providers
// included directly then assume the user wanted to log these as tree traits (it may be an older
// form XML).
// if (ttps.size() == 0 && ttps2.size() > 0) {
// ttps.addAll(ttps2);
// }
// The above code destroyed the logging of complete histories - which need to be logged by direct
// inclusion of the codon partitioned robust counting TTP...
if (ttps2.size() > 0) {
ttps.addAll(ttps2);
}
if (substitutions) {
branchRates = (BranchRates) xo.getChild(BranchRates.class);
}
if (substitutions && branchRates == null) {
throw new XMLParseException("To log trees in units of substitutions a BranchRateModel must be provided");
}
// logEvery of zero only displays at the end
logEvery = xo.getAttribute(LOG_EVERY, 0);
// double normaliseMeanRateTo = xo.getAttribute(NORMALISE_MEAN_RATE_TO, Double.NaN);
// decimal places
final int dp = xo.getAttribute(DECIMAL_PLACES, -1);
if (dp != -1) {
format = NumberFormat.getNumberInstance(Locale.ENGLISH);
format.setMaximumFractionDigits(dp);
}
final PrintWriter pw = getLogFile(xo, getParserName());
formatter = new TabDelimitedFormatter(pw);
treeAttributeProviders = new TreeAttributeProvider[taps.size()];
taps.toArray(treeAttributeProviders);
treeTraitProviders = new TreeTraitProvider[ttps.size()];
ttps.toArray(treeTraitProviders);
// I think the default should be to have names rather than numbers, thus the false default - AJD
// I think the default should be numbers - using names results in larger files and end user never
// sees the numbers anyway as any software loading the nexus files does the translation - JH
mapNames = xo.getAttribute(MAP_NAMES, true);
condition = logEvery == 0 ? (TreeLogger.LogUpon) xo.getChild(TreeLogger.LogUpon.class) : null;
}
/**
* @return an object based on the XML element it was passed.
*/
public Object parseXMLObject(XMLObject xo) throws XMLParseException {
parseXMLParameters(xo);
TreeLogger logger = new TreeLogger(tree, branchRates,
treeAttributeProviders, treeTraitProviders,
formatter, logEvery, nexusFormat, sortTranslationTable, mapNames, format, condition/*,
normaliseMeanRateTo*/);
if (title != null) {
logger.setTitle(title);
}
return logger;
}
protected Tree tree;
protected String title;
protected boolean nexusFormat;
protected boolean sortTranslationTable;
protected BranchRates branchRates;
protected NumberFormat format = null;
protected TreeLogger.LogUpon condition;
protected boolean mapNames;
protected LogFormatter formatter;
protected TreeAttributeProvider[] treeAttributeProviders;
protected TreeTraitProvider[] treeTraitProviders;
protected int logEvery;
//************************************************************************
// AbstractXMLObjectParser implementation
//************************************************************************
public XMLSyntaxRule[] getSyntaxRules() {
return rules;
}
private final XMLSyntaxRule[] rules = {
AttributeRule.newIntegerRule(LOG_EVERY, true),
AttributeRule.newBooleanRule(ALLOW_OVERWRITE_LOG, true),
new StringAttributeRule(FILE_NAME,
"The name of the file to send log output to. " +
"If no file name is specified then log is sent to standard output", true),
new StringAttributeRule(TITLE, "The title of the log", true),
AttributeRule.newBooleanRule(NEXUS_FORMAT, true,
"Whether to use the NEXUS format for the tree log"),
AttributeRule.newBooleanRule(SORT_TRANSLATION_TABLE, true,
"Whether the translation table is sorted."),
/*AttributeRule.newDoubleRule(NORMALISE_MEAN_RATE_TO, true,
"Value to normalise the mean rate to."),*/
new StringAttributeRule(BRANCH_LENGTHS, "What units should the branch lengths be in",
new String[]{TIME, SUBSTITUTIONS}, true),
AttributeRule.newStringRule(FILTER_TRAITS, true),
AttributeRule.newBooleanRule(MAP_NAMES, true),
AttributeRule.newIntegerRule(DECIMAL_PLACES, true),
new ElementRule(Tree.class, "The tree which is to be logged"),
// new ElementRule(BranchRates.class, true),
// new ElementRule(TreeColouringProvider.class, true),
new ElementRule(TREE_TRAIT,
new XMLSyntaxRule[] {
AttributeRule.newStringRule(NAME, false, "The name of the trait"),
AttributeRule.newStringRule(TAG, true, "The label of the trait to be used in the tree"),
new ElementRule(TreeAttributeProvider.class, "The trait provider")
}, 0, Integer.MAX_VALUE),
new ElementRule(Likelihood.class, true),
new ElementRule(Loggable.class, 0, Integer.MAX_VALUE),
new ElementRule(TreeAttributeProvider.class, 0, Integer.MAX_VALUE),
new ElementRule(TreeTraitProvider.class, 0, Integer.MAX_VALUE),
new ElementRule(TreeLogger.LogUpon.class, true)
};
public String getParserDescription() {
return "Logs a tree to a file";
}
public String getExample() {
final String name = getParserName();
return
"<!-- The " + name + " element takes a treeModel to be logged -->\n" +
"<" + name + " " + LOG_EVERY + "=\"100\" " + FILE_NAME + "=\"log.trees\" "
+ NEXUS_FORMAT + "=\"true\">\n" +
" <treeModel idref=\"treeModel1\"/>\n" +
"</" + name + ">\n";
}
public Class getReturnType() {
return TreeLogger.class;
}
}