package beast.evolution.tree;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import beast.core.Description;
import beast.core.Input;
import beast.core.Operator;
import beast.core.StateNode;
import beast.core.StateNodeInitialiser;
import beast.core.util.Log;
import beast.evolution.alignment.TaxonSet;
import beast.util.TreeParser;
@Description("Tree (the T in BEAST) representing gene beast.tree, species"
+ " beast.tree, language history, or other time-beast.tree"
+ " relationships among sequence data.")
public class Tree extends StateNode implements TreeInterface {
final public Input<Tree> m_initial = new Input<>("initial", "tree to start with");
final public Input<List<TraitSet>> m_traitList = new Input<>("trait",
"trait information for initializing traits (like node dates) in the tree",
new ArrayList<>());
final public Input<TaxonSet> m_taxonset = new Input<>("taxonset",
"set of taxa that correspond to the leafs in the tree");
final public Input<String> nodeTypeInput = new Input<>("nodetype",
"type of the nodes in the beast.tree", Node.class.getName());
/**
* state of dirtiness of a node in the tree
* DIRTY means a property on the node has changed, but not the topology. "property" includes the node height
* and that branch length to its parent.
* FILTHY means the nodes' parent or child has changed.
*/
public static final int IS_CLEAN = 0, IS_DIRTY = 1, IS_FILTHY = 2;
/**
* counters of number of nodes, nodeCount = internalNodeCount + leafNodeCount *
*/
protected int nodeCount = -1;
protected int internalNodeCount = -1;
protected int leafNodeCount = -1;
/**
* node representation of the beast.tree *
*/
protected beast.evolution.tree.Node root;
protected beast.evolution.tree.Node storedRoot;
/**
* array of all nodes in the tree *
*/
protected Node[] m_nodes = null;
protected Node[] m_storedNodes = null;
/**
* array of taxa names for the nodes in the tree
* such that m_sTaxaNames[node.getNr()] == node.getID()*
*/
protected String[] m_sTaxaNames = null;
/**
* Trait set which specifies leaf node times.
*/
protected TraitSet timeTraitSet = null;
/*
* Whether or not TraitSets have been processed.
*/
protected boolean traitsProcessed = false;
@Override
public void initAndValidate() {
if (m_initial.get() != null && !(this instanceof StateNodeInitialiser)) {
throw new RuntimeException("initial-input should be specified for tree that is not a StateNodeInitialiser");
// final Tree other = m_initial.get();
// root = other.root.copy();
// nodeCount = other.nodeCount;
// internalNodeCount = other.internalNodeCount;
// leafNodeCount = other.leafNodeCount;
}
if (nodeCount < 0) {
if (m_taxonset.get() != null) {
makeCaterpillar(0, 1, false);
} else {
// make dummy tree with a single root node
root = newNode();
root.labelNr = 0;
root.height = 0;
root.m_tree = this;
nodeCount = 1;
internalNodeCount = 0;
leafNodeCount = 1;
}
}
if (nodeCount >= 0) {
initArrays();
}
processTraits(m_traitList.get());
// Ensure tree is compatible with time trait.
if (timeTraitSet != null)
adjustTreeNodeHeights(root);
// ensure all nodes have their taxon names set up
String [] taxa = getTaxaNames();
for (int i = 0; i < getNodeCount() && i < taxa.length; i++) {
if( taxa[i] != null ) {
if( m_nodes[i].getID() == null ) {
m_nodes[i].setID(taxa[i]);
}
}
}
}
public void makeCaterpillar(final double minInternalHeight, final double step, final boolean finalize) {
// make a caterpillar
final List<String> taxa = m_taxonset.get().asStringList();
Node left = newNode();
left.labelNr = 0;
left.height = 0;
left.setID(taxa.get(0));
for (int i = 1; i < taxa.size(); i++) {
final Node right = newNode();
right.labelNr = i;
right.height = 0;
right.setID(taxa.get(i));
final Node parent = newNode();
parent.labelNr = taxa.size() + i - 1;
parent.height = minInternalHeight + i * step;
left.parent = parent;
parent.setLeft(left);
right.parent = parent;
parent.setRight(right);
left = parent;
}
root = left;
leafNodeCount = taxa.size();
nodeCount = leafNodeCount * 2 - 1;
internalNodeCount = leafNodeCount - 1;
if (finalize) {
initArrays();
}
}
/**
* Process trait sets.
*
* @param traitList List of trait sets.
*/
protected void processTraits(List<TraitSet> traitList) {
for (TraitSet traitSet : traitList) {
for (Node node : getExternalNodes()) {
String id = node.getID();
if (id != null) {
node.setMetaData(traitSet.getTraitName(), traitSet.getValue(id));
}
}
if (traitSet.isDateTrait())
timeTraitSet = traitSet;
}
traitsProcessed = true;
}
/**
* Overridable method to construct new node object of the specific type
* defined by nodeTypeInput.
*
* @return new node object.
*/
protected Node newNode() {
try {
return (Node) Class.forName(nodeTypeInput.get()).newInstance();
} catch (Exception e) {
throw new RuntimeException("Cannot create node of type "
+ nodeTypeInput.get() + ": " + e.getMessage());
}
//return new NodeData();
}
protected void initArrays() {
// initialise tree-as-array representation + its stored variant
m_nodes = new Node[nodeCount];
listNodes(root, m_nodes);
m_storedNodes = new Node[nodeCount];
final Node copy = root.copy();
listNodes(copy, m_storedNodes);
postCache = null;
}
public Tree() {
}
public Tree(final Node rootNode) {
setRoot(rootNode);
initArrays();
}
/**
* Construct a tree from newick string -- will not automatically adjust tips to zero.
*/
public Tree(final String newick) {
this(new TreeParser(newick).getRoot());
}
/**
* Ensure no negative branch lengths exist in tree. This can occur if
* leaf heights given as a trait are incompatible with the existing tree.
*/
final static double EPSILON = 0.0000001;
protected void adjustTreeNodeHeights(final Node node) {
if (!node.isLeaf()) {
for (final Node child : node.getChildren()) {
adjustTreeNodeHeights(child);
}
for (final Node child : node.getChildren()) {
final double minHeight = child.getHeight() + EPSILON;
if (node.height < minHeight) {
node.height = minHeight;
}
}
}
}
/**
* getters and setters
*
* @return the number of nodes in the beast.tree
*/
@Override
public int getNodeCount() {
if (nodeCount < 0) {
nodeCount = this.root.getNodeCount();
}
//System.out.println("nodeCount=" + nodeCount);
return nodeCount;
}
@Override
public int getInternalNodeCount() {
if (internalNodeCount < 0) {
internalNodeCount = root.getInternalNodeCount();
}
return internalNodeCount;
}
@Override
public int getLeafNodeCount() {
//TODO will this caching work if trees can have random numbers of tips during MCMC
if (leafNodeCount < 0) {
leafNodeCount = root.getLeafNodeCount();
}
return leafNodeCount;
}
/**
* @return a list of external (leaf) nodes contained in this tree
*/
@Override
public List<Node> getExternalNodes() {
final ArrayList<Node> externalNodes = new ArrayList<>();
for (int i = 0; i < getNodeCount(); i++) {
final Node node = getNode(i);
if (node.isLeaf()) externalNodes.add(node);
}
return externalNodes;
}
/**
* @return a list of internal (ancestral) nodes contained in this tree, including the root node
*/
@Override
public List<Node> getInternalNodes() {
final ArrayList<Node> internalNodes = new ArrayList<>();
for (int i = 0; i < getNodeCount(); i++) {
final Node node = getNode(i);
if (!node.isLeaf()) internalNodes.add(node);
}
return internalNodes;
}
@Override
public Node getRoot() {
return root;
}
public void setRoot(final Node root) {
this.root = root;
nodeCount = this.root.getNodeCount();
// ensure root is the last node
if (m_nodes != null && root.labelNr != m_nodes.length - 1) {
final int rootPos = m_nodes.length - 1;
Node tmp = m_nodes[rootPos];
m_nodes[rootPos] = root;
m_nodes[root.labelNr] = tmp;
tmp.labelNr = root.labelNr;
m_nodes[rootPos].labelNr = rootPos;
}
}
/**
* Sets root without recalculating nodeCount or ensuring that root is the last node in the internal array.
* Currently only used by sampled ancestor tree operators. Use carefully!
*
* @param root the new root node
*/
public void setRootOnly(final Node root) {
//TODO should we flag this with startEditing since it is an operator call?
this.root = root;
}
@Override
public Node getNode(final int nodeNr) {
return m_nodes[nodeNr];
//return getNode(nodeNr, root);
}
/**
* @returns an array of taxon names in order of their node numbers.
* Note that in general no special order of taxa is assumed, just the order
* assumed in this tree. Consider using tree.m_taxonset.get().asStringList()
* instead.
*/
public String [] getTaxaNames() {
if (m_sTaxaNames == null || (m_sTaxaNames.length == 1 && m_sTaxaNames[0] == null) || m_sTaxaNames.length == 0) {
// take taxa from tree if one exists
if( root != null ) {
m_sTaxaNames = new String[getNodeCount()];
collectTaxaNames(getRoot());
List<String> taxaNames = new ArrayList<>();
for (String name : m_sTaxaNames) {
if (name != null) {
taxaNames.add(name);
}
}
m_sTaxaNames = taxaNames.toArray(new String[]{});
} else {
// no tree? use taxon set.
final TaxonSet taxonSet = m_taxonset.get();
if (taxonSet != null) {
final List<String> txs = taxonSet.asStringList();
m_sTaxaNames = txs.toArray(new String[txs.size()]);
} else {
Log.err("No taxa specified");
}
}
}
// sanity check
if (m_sTaxaNames.length == 1 && m_sTaxaNames[0] == null) {
Log.warning("WARNING: tree interrogated for taxa, but the tree was not initialised properly. To fix this, specify the taxonset input");
}
return m_sTaxaNames;
}
void collectTaxaNames(final Node node) {
if (node.getID() != null) {
m_sTaxaNames[node.getNr()] = node.getID();
}
if (node.isLeaf()) {
if (node.getID() == null) {
node.setID("node" + node.getNr());
}
} else {
for (Node child : node.getChildren()) {
collectTaxaNames(child);
}
}
}
/**
* copy meta data matching pattern to double array
*
* @param node the node
* @param t the double array to be filled with meta data
* @param pattern the name of the meta data
*/
@Override
public void getMetaData(final Node node, final Double[] t, final String pattern) {
t[Math.abs(node.getNr())] = (Double) node.getMetaData(pattern);
if (!node.isLeaf()) {
getMetaData(node.getLeft(), t, pattern);
if (node.getRight() != null) {
getMetaData(node.getRight(), t, pattern);
}
}
}
/**
* copy meta data matching pattern to double array
*
* @param node the node
* @param t the integer array to be filled with meta data
* @param pattern the name of the meta data
*/
public void getMetaData(final Node node, final Integer[] t, final String pattern) {
t[Math.abs(node.getNr())] = (Integer) node.getMetaData(pattern);
if (!node.isLeaf()) {
getMetaData(node.getLeft(), t, pattern);
if (node.getRight() != null) {
getMetaData(node.getRight(), t, pattern);
}
}
}
/**
* traverse tree and assign meta-data values in t to nodes in the
* tree to the meta-data field represented by the given pattern.
* This only has an effect when setMetadata() in a subclass
* of Node know how to process such value.
*/
@Override
public void setMetaData(final Node node, final Double[] t, final String pattern) {
node.setMetaData(pattern, t[Math.abs(node.getNr())]);
if (!node.isLeaf()) {
setMetaData(node.getLeft(), t, pattern);
if (node.getRight() != null) {
setMetaData(node.getRight(), t, pattern);
}
}
}
/**
* convert tree to array representation *
*/
void listNodes(final Node node, final Node[] nodes) {
nodes[node.getNr()] = node;
node.m_tree = this; //(JH) I don't understand this code
// (JH) why not node.children, we don't keep it around??
for (final Node child : node.getChildren()) {
listNodes(child, nodes);
}
}
// private int
// getNodesPostOrder(final Node node, final Node[] nodes, int pos) {
// node.m_tree = this;
// for (final Node child : node.children) {
// pos = getNodesPostOrder(child, nodes, pos);
// }
// nodes[pos] = node;
// return pos + 1;
// }
// /**
// * @param node top of tree/sub tree (null defaults to whole tree)
// * @param nodes array to fill (null will result in creating a new one)
// * @return tree nodes in post-order, children before parents
// */
// public Node[] listNodesPostOrder(Node node, Node[] nodes) {
// if (node == null) {
// node = root;
// }
// if (nodes == null) {
// final int n = (node == root) ? nodeCount : node.getNodeCount();
// nodes = new Node[n];
// }
// getNodesPostOrder(node, nodes, 0);
// return nodes;
// }
protected Node[] postCache = null;
@Override
public Node[] listNodesPostOrder(Node node, Node[] nodes) {
if( node != null ) {
return TreeInterface.super.listNodesPostOrder(node, nodes);
}
if( postCache == null ) {
postCache = TreeInterface.super.listNodesPostOrder(node, nodes);
}
return postCache;
}
/**
* @return list of nodes in array format.
* *
*/
@Override
public Node[] getNodesAsArray() {
return m_nodes;
}
/**
* deep copy, returns a completely new tree
*
* @return a deep copy of this beast.tree.
*/
@Override
public Tree copy() {
Tree tree = new Tree();
tree.setID(getID());
tree.index = index;
tree.root = root.copy();
tree.nodeCount = nodeCount;
tree.internalNodeCount = internalNodeCount;
tree.leafNodeCount = leafNodeCount;
return tree;
}
/**
* copy of all values into existing tree *
*/
@Override
public void assignTo(final StateNode other) {
final Tree tree = (Tree) other;
final Node[] nodes = new Node[nodeCount];
listNodes(tree.root, nodes);
tree.setID(getID());
//tree.index = index;
root.assignTo(nodes);
tree.root = nodes[root.getNr()];
tree.nodeCount = nodeCount;
tree.internalNodeCount = internalNodeCount;
tree.leafNodeCount = leafNodeCount;
}
/**
* copy of all values from existing tree *
*/
@Override
public void assignFrom(final StateNode other) {
final Tree tree = (Tree) other;
final Node[] nodes = new Node[tree.getNodeCount()];//tree.getNodesAsArray();
for (int i = 0; i < tree.getNodeCount(); i++) {
nodes[i] = newNode();
}
setID(tree.getID());
//index = tree.index;
root = nodes[tree.root.getNr()];
root.assignFrom(nodes, tree.root);
root.parent = null;
nodeCount = tree.nodeCount;
internalNodeCount = tree.internalNodeCount;
leafNodeCount = tree.leafNodeCount;
initArrays();
}
/**
* as assignFrom, but only copy tree structure *
*/
@Override
public void assignFromFragile(final StateNode other) {
// invalidate cache
postCache = null;
final Tree tree = (Tree) other;
if (m_nodes == null) {
initArrays();
}
root = m_nodes[tree.root.getNr()];
final Node[] otherNodes = tree.m_nodes;
final int rootNr = root.getNr();
assignFrom(0, rootNr, otherNodes);
root.height = otherNodes[rootNr].height;
root.parent = null;
if (otherNodes[rootNr].getLeft() != null) {
root.setLeft(m_nodes[otherNodes[rootNr].getLeft().getNr()]);
} else {
root.setLeft(null);
}
if (otherNodes[rootNr].getRight() != null) {
root.setRight(m_nodes[otherNodes[rootNr].getRight().getNr()]);
} else {
root.setRight(null);
}
assignFrom(rootNr + 1, nodeCount, otherNodes);
}
/**
* helper to assignFromFragile *
*/
private void assignFrom(final int start, final int end, final Node[] otherNodes) {
for (int i = start; i < end; i++) {
Node sink = m_nodes[i];
Node src = otherNodes[i];
sink.height = src.height;
sink.parent = m_nodes[src.parent.getNr()];
if (src.getLeft() != null) {
sink.setLeft(m_nodes[src.getLeft().getNr()]);
if (src.getRight() != null) {
sink.setRight(m_nodes[src.getRight().getNr()]);
} else {
sink.setRight(null);
}
}
}
}
@Override
public String toString() {
return root.toString();
}
/**
* StateNode implementation
*/
@Override
public void setEverythingDirty(final boolean isDirty) {
setSomethingIsDirty(isDirty);
if (!isDirty) {
for( Node n : m_nodes ) {
n.isDirty = IS_CLEAN;
}
// root.makeAllDirty(IS_CLEAN);
} else {
for( Node n : m_nodes ) {
n.isDirty = IS_FILTHY;
}
// root.makeAllDirty(IS_FILTHY);
}
}
@Override
public int scale(final double scale) {
root.scale(scale);
return getInternalNodeCount()- getDirectAncestorNodeCount();
}
// /**
// * The same as scale but with option to scale all sampled nodes
// * @param scale
// * @param scaleSNodes if true all sampled nodes are scaled. Note, the most recent node is considered to
// * have height 0.
// * @return
// */
// public int scale(double scale, boolean scaleSNodes) {
// ((ZeroBranchSANode)root).scale(scale, scaleSNodes);
// if (scaleSNodes) {
// return getNodeCount() - 1 - getDirectAncestorNodeCount();
// } else {
// return getInternalNodeCount() - getDirectAncestorNodeCount();
// }
// }
/** Loggable interface implementation follows **/
/**
* print translate block for NEXUS beast.tree file
*/
public static void printTranslate(final Node node, final PrintStream out, final int nodeCount) {
final List<String> translateLines = new ArrayList<>();
printTranslate(node, translateLines, nodeCount);
Collections.sort(translateLines);
for (final String line : translateLines) {
out.println(line);
}
}
static public int taxaTranslationOffset = 1;
/**
* need this helper so that we can sort list of entries *
*/
static void printTranslate(Node node, List<String> translateLines, int nodeCount) {
if (node.isLeaf()) {
final String nr = (node.getNr() + taxaTranslationOffset) + "";
String line = "\t\t" + " ".substring(nr.length()) + nr + " " + node.getID();
if (node.getNr() < nodeCount) {
line += ",";
}
translateLines.add(line);
} else {
printTranslate(node.getLeft(), translateLines, nodeCount);
if (node.getRight() != null) {
printTranslate(node.getRight(), translateLines, nodeCount);
}
}
}
public static void printTaxa(final Node node, final PrintStream out, final int nodeCount) {
final List<String> translateLines = new ArrayList<>();
printTranslate(node, translateLines, nodeCount);
Collections.sort(translateLines);
for (String line : translateLines) {
line = line.split("\\s+")[2];
out.println("\t\t\t" + line.replace(',', ' '));
}
}
@Override
public void init(PrintStream out) {
Node node = getRoot();
out.println("#NEXUS\n");
out.println("Begin taxa;");
out.println("\tDimensions ntax=" + getLeafNodeCount() + ";");
out.println("\t\tTaxlabels");
printTaxa(node, out, getNodeCount() / 2);
out.println("\t\t\t;");
out.println("End;");
out.println("Begin trees;");
out.println("\tTranslate");
printTranslate(node, out, getNodeCount() / 2);
out.print(";");
}
@Override
public void log(int sample, PrintStream out) {
Tree tree = (Tree) getCurrent();
out.print("tree STATE_" + sample + " = ");
// Don't sort, this can confuse CalculationNodes relying on the tree
//tree.getRoot().sort();
final int[] dummy = new int[1];
final String newick = tree.getRoot().toSortedNewick(dummy);
out.print(newick);
out.print(";");
}
/**
* @see beast.core.Loggable *
*/
@Override
public void close(PrintStream out) {
out.print("End;");
}
/**
* reconstruct tree from XML fragment in the form of a DOM node *
*/
@Override
public void fromXML(final org.w3c.dom.Node node) {
final String newick = node.getTextContent();
final TreeParser parser = new TreeParser();
try {
parser.thresholdInput.setValue(1e-10, parser);
} catch (Exception e1) {
e1.printStackTrace();
}
try {
parser.offsetInput.setValue(0, parser);
setRoot(parser.parseNewick(newick));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
initArrays();
}
/**
* Valuable implementation *
*/
@Override
public int getDimension() {
return getNodeCount();
}
@Override
public double getArrayValue() {
return root.height;
}
@Override
public double getArrayValue(int value) {
return m_nodes[value].height;
}
/**
* StateNode implementation *
*/
@Override
protected void store() {
// this condition can only be true for sampled ancestor trees
if (m_storedNodes.length != nodeCount) {
final Node[] tmp = new Node[nodeCount];
System.arraycopy(m_storedNodes, 0, tmp, 0, m_storedNodes.length - 1);
if (nodeCount > m_storedNodes.length) {
tmp[m_storedNodes.length - 1] = m_storedNodes[m_storedNodes.length - 1];
tmp[nodeCount - 1] = newNode();
tmp[nodeCount - 1].setNr(nodeCount - 1);
}
m_storedNodes = tmp;
}
storeNodes(0, nodeCount);
storedRoot = m_storedNodes[root.getNr()];
}
/**
* Stores nodes with index i, for start <= i < end
* (i.e. including start but not including end)
*
* @param start the first index to be stored
* @param end nodes are stored up to but not including this index
*/
private void storeNodes(final int start, final int end) {
// Use direct members for speed (we are talking 5-7% or more from total time for large trees :)
for (int i = start; i < end; i++) {
final Node sink = m_storedNodes[i];
final Node src = m_nodes[i];
sink.height = src.height;
if ( src.parent != null ) {
sink.parent = m_storedNodes[src.parent.getNr()];
} else {
// currently only called in the case of sampled ancestor trees
// where root node is not always last in the list
sink.parent = null;
}
final List<Node> children = sink.children;
final List<Node> srcChildren = src.children;
if( children.size() == srcChildren.size() ) {
// shave some more time by avoiding list clear and add
for (int k = 0; k < children.size(); ++k) {
final Node srcChild = srcChildren.get(k);
// don't call addChild, which calls setParent(..., true);
final Node c = m_storedNodes[srcChild.getNr()];
c.parent = sink;
children.set(k, c);
}
} else {
children.clear();
//sink.removeAllChildren(false);
for (final Node srcChild : srcChildren) {
// don't call addChild, which calls setParent(..., true);
final Node c = m_storedNodes[srcChild.getNr()];
c.parent = sink;
children.add(c);
//sink.addChild(c);
}
}
}
}
@Override
public void startEditing(final Operator operator) {
super.startEditing(operator);
postCache = null;
}
@Override
public void restore() {
// necessary for sampled ancestor trees
nodeCount = m_storedNodes.length;
final Node[] tmp = m_storedNodes;
m_storedNodes = m_nodes;
m_nodes = tmp;
root = m_nodes[storedRoot.getNr()];
// necessary for sampled ancestor trees,
// we have the nodes, no need for expensive recursion
leafNodeCount = 0;
for( Node n : m_nodes ) {
leafNodeCount += n.isLeaf() ? 1 : 0;
}
//leafNodeCount = root.getLeafNodeCount();
hasStartedEditing = false;
for( Node n : m_nodes ) {
n.isDirty = Tree.IS_CLEAN;
}
postCache = null;
}
/**
* @return Date trait set if available, null otherwise.
*/
public TraitSet getDateTrait() {
if (!traitsProcessed)
processTraits(m_traitList.get());
return timeTraitSet;
}
/**
* Determine whether tree has a date/time trait set associated with it.
*
* @return true if so
*/
public boolean hasDateTrait() {
return getDateTrait() != null;
}
/**
* Specifically set the date trait set for this tree. A null value simply
* removes the existing trait set.
*
* @param traitSet
*/
public void setDateTrait(TraitSet traitSet) {
if (hasDateTrait()) {
m_traitList.get().remove(timeTraitSet);
}
if (traitSet != null)
m_traitList.get().add(traitSet);
timeTraitSet = traitSet;
}
/**
* Convert age/height to the date time scale given by a trait set,
* if one exists. Otherwise just return the unconverted height.
*
* @param height
* @return date specified by height
*/
public double getDate(final double height) {
if (hasDateTrait()) {
return timeTraitSet.getDate(height);
} else
return height;
}
/**
* This method allows the retrieval of the taxon label of a node without using the node number.
*
* @param node
* @return the name of the given node, or null if the node is unlabelled
*/
public String getTaxonId(final Node node) {
//TODO should be implemented to avoid using deprecated methods
return getTaxaNames()[node.getNr()]; //To change body of created methods use File | Settings | File Templates.
}
/**
* Removes the i'th node in the tree. Results in a renumbering of the remaining nodes so that their numbers
* faithfully describe their new position in the array. nodeCount and leafNodeCount are recalculated.
* Use with care!
*
* @param i the index of the node to be removed.
*/
public void removeNode(final int i) {
final Node[] tmp = new Node[nodeCount - 1];
System.arraycopy(m_nodes, 0, tmp, 0, i);
for (int j = i; j < nodeCount - 1; j++) {
tmp[j] = m_nodes[j + 1];
tmp[j].setNr(j);
}
m_nodes = tmp;
nodeCount--;
if (i < leafNodeCount)
leafNodeCount--;
else
internalNodeCount--;
}
/**
* Adds a node to the end of the node array. nodeCount and leafNodeCount are recalculated.
* Use with care!
*/
public void addNode(final Node newNode) {
final Node[] tmp = new Node[nodeCount + 1];
System.arraycopy(m_nodes, 0, tmp, 0, nodeCount);
tmp[nodeCount] = newNode;
newNode.setNr(nodeCount);
m_nodes = tmp;
nodeCount++;
if (newNode.getChildCount() > 0)
internalNodeCount++;
else
leafNodeCount++;
}
/**
* @return the number of direct ancestors in this tree. Direct ancestor nodes conceptually have a single child. However
* this is determined by whether the node's parent has the same height as the node, since that signifies a zero branch length
* and the parent node with the same height as the child node is regarded as "fake".
*/
public int getDirectAncestorNodeCount() {
int directAncestorNodeCount = 0;
for (int i = 0; i < leafNodeCount; i++) {
if (this.getNode(i).isDirectAncestor()) {
directAncestorNodeCount += 1;
}
}
return directAncestorNodeCount;
}
@Override
public TaxonSet getTaxonset() {
return m_taxonset.get();
}
/**
* Determine whether a child was replaced in a binary tree
**/
public boolean childrenChanged(int nodeNr) {
Node node = m_nodes[nodeNr];
Node old = m_storedNodes[nodeNr];
if (node.getLeft().getNr() == old.getLeft().getNr() &&
node.getRight().getNr() == old.getRight().getNr()) {
return false;
}
if (node.getLeft().getNr() == old.getRight().getNr() &&
node.getRight().getNr() == old.getLeft().getNr()) {
return false;
}
return true;
}
} // class Tree