/*
* File Node.java
*
* Copyright (C) 2010 Remco Bouckaert remco@cs.auckland.ac.nz
*
* This file is part of BEAST2.
* 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 beast.evolution.tree;
import beast.core.BEASTObject;
import beast.core.Description;
import beast.util.HeapSort;
import java.util.*;
@Description("Nodes in building beast.tree data structure.")
public class Node extends BEASTObject {
/**
* label nr of node, used mostly when this is a leaf.
*/
protected int labelNr;
/**
* height of this node.
*/
protected double height = Double.MAX_VALUE;
/**
* Arbitrarily labeled metadata on this node. Not currently implemented as part of state!
*/
protected Map<String, Object> metaData = new TreeMap<>();
/**
* Length metadata for the edge above this node. Not currently implemented as part of state!
*/
protected Map<String, Object> lengthMetaData = new TreeMap<>();
/**
* list of children of this node *
* Don't use m_left and m_right directly
* Use getChildCount() and getChild(x) or getChildren() instead
*/
List<Node> children = new ArrayList<>();
// @Deprecated
// private Node m_left;
// @Deprecated
// private Node m_right;
/**
* parent node in the beast.tree, null if root *
*/
Node parent = null;
/**
* status of this node after an operation is performed on the state *
*/
int isDirty = Tree.IS_CLEAN;
/**
* meta-data contained in square brackets in Newick *
*/
public String metaDataString, lengthMetaDataString;
/**
* The Tree that this node is a part of.
* This allows e.g. access to the State containing the Tree *
*/
protected Tree m_tree;
public Node() {
}
public Node(final String id) {
setID(id);
initAndValidate();
}
public Tree getTree() {
return m_tree;
}
@Override
public void initAndValidate() {
// do nothing
}
/**
* @return number uniquely identifying the node in the tree.
* This is a number between 0 and the total number of nodes in the tree
* Leaf nodes are number 0 to #leaf nodes -1
* Internal nodes are numbered #leaf nodes up to #nodes-1
* The root node is always numbered #nodes-1
*/
public int getNr() {
return labelNr;
}
public void setNr(final int labelIndex) {
labelNr = labelIndex;
}
public double getHeight() {
return height;
}
public double getDate() {
return m_tree.getDate(height);
}
public void setHeight(final double height) {
startEditing();
this.height = height;
isDirty |= Tree.IS_DIRTY;
if (!isLeaf()) {
getLeft().isDirty |= Tree.IS_DIRTY;
if (getRight() != null) {
getRight().isDirty |= Tree.IS_DIRTY;
}
}
}
/**
* @return length of branch between this node and its parent in the beast.tree
*/
public final double getLength() {
if (isRoot()) {
return 0;
} else {
return getParent().height - height;
}
}
/**
* methods for accessing the dirtiness state of the Node.
* A Node is Tree.IS_DIRTY if its value (like height) has changed
* A Node Tree.IS_if FILTHY if its parent or child has changed.
* Otherwise the node is Tree.IS_CLEAN *
*/
public int isDirty() {
return isDirty;
}
public void makeDirty(final int dirty) {
isDirty |= dirty;
}
public void makeAllDirty(final int dirty) {
isDirty = dirty;
if (!isLeaf()) {
getLeft().makeAllDirty(dirty);
if (getRight() != null) {
getRight().makeAllDirty(dirty);
}
}
}
/**
* @return parent node, or null if this is root *
*/
public Node getParent() {
return parent;
}
/**
* Calls setParent(parent, true)
*
* @param parent the new parent to be set, must be called from within an operator.
*/
public void setParent(final Node parent) {
setParent(parent, true);
}
/**
* Sets the parent of this node
*
* @param parent the node to become parent
* @param inOperator if true, then startEditing() is called and setting the parent will make tree "filthy"
*/
void setParent(final Node parent, final boolean inOperator) {
if (inOperator) startEditing();
if (this.parent != parent) {
this.parent = parent;
if (inOperator) isDirty = Tree.IS_FILTHY;
}
}
/**
* Sets the parent of this node. No overhead, no side effects like setting dirty flags etc.
*
* @param parent the node to become parent
*/
void setParentImmediate(final Node parent) {
this.parent = parent;
}
/**
* @return unmodifiable list of children of this node
*/
public List<Node> getChildren() {
return Collections.unmodifiableList(children);
}
/**
* get all child node under this node, if this node is leaf then list.size() = 0.
* This returns all child nodes including this node.
*
* @return all child nodes including this node
*/
public List<Node> getAllChildNodes() {
final List<Node> childNodes = new ArrayList<>();
if (!this.isLeaf()) getAllChildNodes(childNodes);
return childNodes;
}
// recursive
public void getAllChildNodes(final List<Node> childNodes) {
childNodes.add(this);
for (Node child : children)
child.getAllChildNodes(childNodes);
}
/**
* get all leaf node under this node, if this node is leaf then list.size() = 0.
*
* @return
*/
public List<Node> getAllLeafNodes() {
final List<Node> leafNodes = new ArrayList<>();
if (!this.isLeaf()) getAllLeafNodes(leafNodes);
return leafNodes;
}
// recursive
public void getAllLeafNodes(final List<Node> leafNodes) {
if (this.isLeaf()) {
leafNodes.add(this);
}
for (Node child : children)
child.getAllLeafNodes(leafNodes);
}
/**
* @return true if current node is root node *
*/
public boolean isRoot() {
return parent == null;
}
/**
* @return true if current node is a leaf node *
*/
public boolean isLeaf() {
return children.size() == 0;
//return getLeft() == null && getRight() == null;
}
public void removeChild(final Node child) {
startEditing();
children.remove(child);
}
/**
* Removes all children from this node.
*
* @param inOperator if true then startEditing() is called. For operator uses, called removeAllChildren(true), otherwise
* use set to false.
*/
public void removeAllChildren(final boolean inOperator) {
if (inOperator) startEditing();
children.clear();
}
public void addChild(final Node child) {
child.setParent(this);
children.add(child);
}
/**
* @return count number of nodes in beast.tree, starting with current node *
*/
public int getNodeCount() {
int nodes = 1;
for (final Node child : children) {
nodes += child.getNodeCount();
}
return nodes;
}
public int getLeafNodeCount() {
if (isLeaf()) {
return 1;
}
int nodes = 0;
for (final Node child : children) {
nodes += child.getLeafNodeCount();
}
return nodes;
}
public int getInternalNodeCount() {
if (isLeaf()) {
return 0;
}
int nodes = 1;
for (final Node child : children) {
nodes += child.getInternalNodeCount();
}
return nodes;
}
/**
* @return beast.tree in Newick format, with length and meta data
* information. Unlike toNewick(), here Nodes are numbered, instead of
* using the node labels.
* If there are internal nodes with non-null IDs then their numbers are also printed.
* Also, all internal nodes are labelled if printInternalNodeNumbers
* is set true. This is useful for example when storing a State to file
* so that it can be restored.
*/
public String toShortNewick(final boolean printInternalNodeNumbers) {
final StringBuilder buf = new StringBuilder();
if (!isLeaf()) {
buf.append("(");
boolean isFirst = true;
for (Node child : getChildren()) {
if (isFirst)
isFirst = false;
else
buf.append(",");
buf.append(child.toShortNewick(printInternalNodeNumbers));
}
buf.append(")");
}
if (isLeaf() || getID() != null || printInternalNodeNumbers) {
buf.append(getNr());
}
buf.append(getNewickMetaData());
buf.append(":").append(getNewickLengthMetaData()).append(getLength());
return buf.toString();
}
/**
* prints newick string where it orders by highest leaf number
* in a clade. Print node numbers (m_iLabel) incremented by 1
* for leaves and internal nodes with non-null IDs.
*/
String toSortedNewick(final int[] maxNodeInClade) {
return toSortedNewick(maxNodeInClade, false);
}
public String toSortedNewick(int[] maxNodeInClade, boolean printMetaData) {
StringBuilder buf = new StringBuilder();
if (!isLeaf()) {
if (getChildCount() <= 2) {
// Computationally cheap method for special case of <=2 children
buf.append("(");
String child1 = getChild(0).toSortedNewick(maxNodeInClade, printMetaData);
int child1Index = maxNodeInClade[0];
if (getChildCount() > 1) {
String child2 = getChild(1).toSortedNewick(maxNodeInClade, printMetaData);
int child2Index = maxNodeInClade[0];
if (child1Index > child2Index) {
buf.append(child2);
buf.append(",");
buf.append(child1);
} else {
buf.append(child1);
buf.append(",");
buf.append(child2);
maxNodeInClade[0] = child1Index;
}
} else {
buf.append(child1);
}
buf.append(")");
if (getID() != null) {
buf.append(labelNr+1);
}
} else {
// General method for >2 children
String[] childStrings = new String[getChildCount()];
int[] maxNodeNrs = new int[getChildCount()];
Integer[] indices = new Integer[getChildCount()];
for (int i = 0; i < getChildCount(); i++) {
childStrings[i] = getChild(i).toSortedNewick(maxNodeInClade, printMetaData);
maxNodeNrs[i] = maxNodeInClade[0];
indices[i] = i;
}
Arrays.sort(indices, (i1, i2) -> {
if (maxNodeNrs[i1] < maxNodeNrs[i2])
return -1;
if (maxNodeNrs[i1] > maxNodeNrs[i2])
return 1;
return 0;
});
maxNodeInClade[0] = maxNodeNrs[maxNodeNrs.length - 1];
buf.append("(");
for (int i = 0; i < indices.length; i++) {
if (i > 0)
buf.append(",");
buf.append(childStrings[indices[i]]);
}
buf.append(")");
if (getID() != null) {
buf.append(labelNr + 1);
}
}
} else {
maxNodeInClade[0] = labelNr;
buf.append(labelNr + 1);
}
if (printMetaData) {
buf.append(getNewickMetaData());
}
buf.append(":");
if (printMetaData)
buf.append(getNewickLengthMetaData());
buf.append(getLength());
return buf.toString();
}
@Deprecated
public String toNewick(final List<String> labels) {
throw new UnsupportedOperationException("Please use toNewick(). Labels will come from node.getId() or node.getNr().");
}
/**
*
* @param onlyTopology if true, only print topology
* @return
*/
public String toNewick(boolean onlyTopology) {
final StringBuilder buf = new StringBuilder();
if (!isLeaf()) {
buf.append("(");
boolean isFirst = true;
for (Node child : getChildren()) {
if (isFirst)
isFirst = false;
else
buf.append(",");
buf.append(child.toNewick(onlyTopology));
}
buf.append(")");
if (getID() != null)
buf.append(getID());
} else {
if (getID() != null)
buf.append(getID());
else
buf.append(labelNr);
}
if (!onlyTopology) {
buf.append(getNewickMetaData());
buf.append(":").append(getNewickLengthMetaData()).append(getLength());
}
return buf.toString();
}
/**
* @return beast.tree in Newick format with taxon labels for labelled tip nodes
* and labeled (having non-null ID) internal nodes.
* If a tip node doesn't have an ID (taxon label) then node number (m_iLabel) is printed.
*/
public String toNewick() {
return toNewick(false);
}
public String getNewickMetaData() {
if (metaDataString != null)
return "[&" + metaDataString + ']';
else
return "";
}
public String getNewickLengthMetaData() {
if (lengthMetaDataString != null)
return "[&" + lengthMetaDataString + "]";
else
return "";
}
/**
* @param labels
* @return beast.tree in long Newick format, with all length and meta data
* information, but with leafs labelled with their names
*/
public String toString(final List<String> labels) {
final StringBuilder buf = new StringBuilder();
if (isLeaf()) {
buf.append(labels.get(labelNr));
} else {
buf.append("(");
boolean isFirst = true;
for (Node child : getChildren()) {
if (isFirst)
isFirst = false;
else
buf.append(",");
buf.append(child.toString(labels));
}
buf.append(")");
}
if (isLeaf())
buf.append(labels.get(labelNr));
if (metaDataString != null) {
buf.append('[');
buf.append(metaDataString);
buf.append(']');
}
buf.append(":");
if (lengthMetaDataString != null) {
buf.append('[');
buf.append(lengthMetaDataString);
buf.append(']');
}
buf.append(getLength());
return buf.toString();
}
@Override
public String toString() {
return toShortNewick(true);
}
/**
* sorts nodes in children according to lowest numbered label in subtree
*
* @return
*/
public int sort() {
if (isLeaf()) {
return labelNr;
}
final int childCount = getChildCount();
if (childCount == 1) return getChild(0).sort();
final List<Integer> lowest = new ArrayList<>();
final int[] indices = new int[childCount];
// relies on this being a copy of children list
final List<Node> children = new ArrayList<>(getChildren());
for (final Node child : children) {
lowest.add(child.sort());
}
HeapSort.sort(lowest, indices);
for (int i = 0; i < childCount; i++) {
setChild(i, children.get(indices[i]));
}
return lowest.get(indices[0]);
} // sort
/**
* during parsing, leaf nodes are numbered 0...m_nNrOfLabels-1
* but internal nodes are left to zero. After labeling internal
* nodes, m_iLabel uniquely identifies a node in a beast.tree.
*
* @param labelIndex
* @return
*/
public int labelInternalNodes(int labelIndex) {
if (isLeaf()) {
return labelIndex;
} else {
labelIndex = getLeft().labelInternalNodes(labelIndex);
if (getRight() != null) {
labelIndex = getRight().labelInternalNodes(labelIndex);
}
labelNr = labelIndex++;
}
return labelIndex;
} // labelInternalNodes
/**
* @return (deep) copy of node
*/
public Node copy() {
final Node node = new Node();
node.height = height;
node.labelNr = labelNr;
node.metaDataString = metaDataString;
node.lengthMetaDataString = lengthMetaDataString;
node.metaData = new TreeMap<>(metaData);
node.lengthMetaData = new TreeMap<>(lengthMetaData);
node.parent = null;
node.setID(getID());
for (final Node child : getChildren()) {
node.addChild(child.copy());
}
return node;
} // copy
/**
* assign values to a tree in array representation *
*/
public void assignTo(final Node[] nodes) {
final Node node = nodes[getNr()];
node.height = height;
node.labelNr = labelNr;
node.metaDataString = metaDataString;
node.lengthMetaDataString = lengthMetaDataString;
node.metaData = new TreeMap<>(metaData);
node.lengthMetaData = new TreeMap<>(lengthMetaData);
node.parent = null;
node.setID(getID());
if (getLeft() != null) {
node.setLeft(nodes[getLeft().getNr()]);
getLeft().assignTo(nodes);
node.getLeft().parent = node;
if (getRight() != null) {
node.setRight(nodes[getRight().getNr()]);
getRight().assignTo(nodes);
node.getRight().parent = node;
}
}
}
/**
* assign values from a tree in array representation *
*/
public void assignFrom(final Node[] nodes, final Node node) {
height = node.height;
labelNr = node.labelNr;
metaDataString = node.metaDataString;
lengthMetaDataString = node.lengthMetaDataString;
metaData = new TreeMap<>(node.metaData);
lengthMetaData = new TreeMap<>(node.lengthMetaData);
parent = null;
setID(node.getID());
if (node.getLeft() != null) {
setLeft(nodes[node.getLeft().getNr()]);
getLeft().assignFrom(nodes, node.getLeft());
getLeft().parent = this;
if (node.getRight() != null) {
setRight(nodes[node.getRight().getNr()]);
getRight().assignFrom(nodes, node.getRight());
getRight().parent = this;
}
}
}
/**
* set meta-data according to pattern.
* Only heights are recognised, but derived classes could deal with
* richer meta data patterns.
*/
public void setMetaData(final String pattern, final Object value) {
startEditing();
if (pattern.equals(TraitSet.DATE_TRAIT) ||
pattern.equals(TraitSet.DATE_FORWARD_TRAIT) ||
pattern.equals(TraitSet.DATE_BACKWARD_TRAIT)) {
height = (Double) value;
isDirty |= Tree.IS_DIRTY;
} else {
metaData.put(pattern, value);
}
}
/**
* Add edge length metadata with given key and value.
*
* @param key key for metadata
* @param value value of metadata for this edge length
*/
public void setLengthMetaData(String key, Object value) {
startEditing();
lengthMetaData.put(key, value);
}
public Object getMetaData(final String pattern) {
if (pattern.equals(TraitSet.DATE_TRAIT) ||
pattern.equals(TraitSet.DATE_FORWARD_TRAIT) ||
pattern.equals(TraitSet.DATE_BACKWARD_TRAIT)) {
return height;
} else {
final Object d = metaData.get(pattern);
if (d != null) return d;
}
return 0;
}
public Object getLengthMetaData(String key) {
return lengthMetaData.get(key);
}
public Set<String> getMetaDataNames() {
return metaData.keySet();
}
public Set<String> getLengthMetaDataNames() {
return lengthMetaData.keySet();
}
/**
* scale height of this node and all its descendants
*
* @param scale scale factor
*/
public void scale(final double scale) {
startEditing();
isDirty |= Tree.IS_DIRTY;
if (!isLeaf() && !isFake()) {
height *= scale;
}
if (!isLeaf()) {
getLeft().scale(scale);
if (getRight() != null) {
getRight().scale(scale);
}
if (height < getLeft().height || height < getRight().height) {
throw new IllegalArgumentException("Scale gives negative branch length");
}
}
}
// /**
// * Used for sampled ancestor trees
// * Scales this node and all its descendants (either all descendants, or only non-sampled descendants)
// *
// * @param scale the scalar to multiply each scaled node age by
// * @param scaleSNodes true if sampled nodes should be scaled as well as internal nodes, false if only non-sampled
// * internal nodes should be scaled.
// */
// public void scale(double scale, boolean scaleSNodes) {
// startEditing();
// isDirty |= Tree.IS_DIRTY;
// if (scaleSNodes || (!isLeaf() && !isFake())) {
// height *= scale;
// }
// if (!isLeaf()) {
// (getLeft()).scale(scale, scaleSNodes);
// if (getRight() != null) {
// (getRight()).scale(scale, scaleSNodes);
// }
// if (height < getLeft().height || height < getRight().height) {
// throw new IllegalArgumentException("Scale gives negative branch length");
// }
// }
// }
protected void startEditing() {
if (m_tree != null && m_tree.getState() != null) {
m_tree.startEditing(null);
}
}
/**
* some methods that are useful for porting from BEAST 1 *
*/
public int getChildCount() {
return children.size();
}
public Node getChild(final int childIndex) {
return children.get(childIndex);
}
public void setChild(final int childIndex, final Node node) {
while (children.size() <= childIndex) {
children.add(null);
}
children.set(childIndex, node);
}
/**
* @param m_left new left child
* @deprecated trees should not be assumed to be binary. One child and more than two are both valid in some models.
*/
@Deprecated
public void setLeft(final Node m_left) {
if (children.size() == 0) {
children.add(m_left);
} else {
children.set(0, m_left);
}
}
/**
* @return left child
* @deprecated trees should not be assumed to be binary. One child and more than two are both valid in some models.
*/
@Deprecated
public Node getLeft() {
if (children.size() == 0) {
return null;
}
return children.get(0);
}
/**
* @param m_right new right child
* @deprecated trees should not be assumed to be binary. One child and more than two are both valid in some models.
*/
@Deprecated
public void setRight(final Node m_right) {
switch (children.size()) {
case 0:
children.add(null);
case 1:
children.add(m_right);
break;
default:
children.set(1, m_right);
break;
}
}
/**
* @return right child
* @deprecated trees should not be assumed to be binary. One child and more than two are both valid in some models.
*/
@Deprecated
public Node getRight() {
if (children.size() <= 1) {
return null;
}
return children.get(1);
}
public static Node connect(final Node left, final Node right, final double h) {
final Node n = new Node();
n.setHeight(h);
n.setLeft(left);
n.setRight(right);
left.parent = n;
right.parent = n;
return n;
}
/**
* @return true if this leaf actually represents a direct ancestor
* (i.e. is on the end of a zero-length branch)
*/
public boolean isDirectAncestor() {
return (isLeaf() && !isRoot() && this.getParent().getHeight() == this.getHeight());
}
/**
* @return true if this is a "fake" internal node (i.e. one of its children is a direct ancestor)
*/
public boolean isFake() {
if (this.isLeaf())
return false;
return ((this.getLeft()).isDirectAncestor() || (this.getRight() != null && (this.getRight()).isDirectAncestor()));
}
public Node getDirectAncestorChild() {
if (!this.isFake()) {
return null;
}
if (this.getLeft().isDirectAncestor()) {
return this.getLeft();
}
return this.getRight();
}
public Node getNonDirectAncestorChild(){
if (!this.isFake()) {
return null;
}
if ((this.getLeft()).isDirectAncestor()){
return getRight();
}
if ((this.getRight()).isDirectAncestor()){
return getLeft();
}
return null;
}
public Node getFakeChild(){
if ((this.getLeft()).isFake()){
return getLeft();
}
if ((this.getRight()).isFake()){
return getRight();
}
return null;
}
} // class Node