/* * ReRootedTree.java * * Copyright (C) 2006-2014 Andrew Rambaut * * 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 2 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package figtree.treeviewer; import jebl.evolution.graphs.*; import jebl.evolution.taxa.Taxon; import jebl.evolution.trees.*; import jebl.util.AttributableHelper; import jebl.util.HashPair; import java.util.*; /** * A rooted tree concrete class that wraps another tree and provides a differently * rooted view of that tree. * * @author Andrew Rambaut * @version $Id$ * * $HeadURL$ * * $LastChangedBy$ * $LastChangedDate$ * $LastChangedRevision$ */ final public class ReRootedTree implements RootedTree { /** * Make a copy of the given unrooted tree * @param source an unrooted source tree * @param ingroupNode the node on one side of the root * @param outgroupNode the node on the other side of the root * @param ingroupBranchLength the branch length from the root to the ingroup node * @throws jebl.evolution.graphs.Graph.NoEdgeException */ public ReRootedTree(RootedTree source, Node ingroupNode, Node outgroupNode, double ingroupBranchLength) throws NoEdgeException { this.source = source; List<Node> children = new ArrayList<Node>(); Node node1 = createNodes(source, outgroupNode, ingroupNode); setLength(node1, ingroupBranchLength); children.add(node1); Node node2 = createNodes(source, ingroupNode, outgroupNode); double l = source.getEdgeLength(ingroupNode, outgroupNode); if (outgroupNode == source.getRootNode()) { // the tree is already rooted at the required location for (Node adj : source.getAdjacencies(outgroupNode)) { if (adj != ingroupNode) { l += source.getEdgeLength(outgroupNode, adj); } } } setLength(node2, Math.max(l - ingroupBranchLength, 0.0)); children.add(node2); createInternalNode(null, children); } /** * Clones the entire tree structure from the given (unrooted) Tree. * @param tree the unrooted tree * @param parent the parent node * @param child the child node */ public Node createNodes(RootedTree tree, Node parent, Node child) throws NoEdgeException { Node newNode = null; double length; if (tree.isExternal(child)) { newNode = createExternalNode(child, tree.getTaxon(child)); length = tree.getEdgeLength(parent, child); } else { List<Node> adjacencies = tree.getAdjacencies(child); if (adjacencies.size() == 2) { // this is the root node so skip over it... if (adjacencies.get(0) == parent) { newNode = createNodes(tree, child, adjacencies.get(1)); } else { newNode = createNodes(tree, child, adjacencies.get(0)); } length = tree.getEdgeLength(adjacencies.get(0), child) + tree.getEdgeLength(adjacencies.get(1), child); } else { List<Node> children = new ArrayList<Node>(); for (Node child2 : adjacencies) { if (child2 != parent) { children.add(createNodes(tree, child, child2)); } } if (tree.getParent(parent) == child) { newNode = createInternalNode(parent, children); } else { newNode = createInternalNode(child, children); } length = tree.getEdgeLength(parent, child); } } setLength(newNode, length); return newNode; } /** * Creates a new external node with the given taxon. See createInternalNode * for a description of how to use these methods. * @param source the source node * @return the created node reference */ private Node createExternalNode(Node source, Taxon taxon) { ReRootedNode node = new ReRootedNode(source, taxon); externalNodes.put(taxon, node); return node; } /** * Once a SimpleRootedTree has been created, the node stucture can be created by * calling createExternalNode and createInternalNode. First of all createExternalNode * is called giving Taxon objects for the external nodes. Then these are put into * sets and passed to createInternalNode to create a parent of these nodes. The * last node created using createInternalNode is automatically the root so when * all the nodes are created, the tree is complete. * * @param children the child nodes of this nodes * @return the created node reference */ private ReRootedNode createInternalNode(Node source, List<? extends Node> children) { ReRootedNode node = new ReRootedNode(source, children); for (Node child : children) { ((ReRootedNode)child).setParent(node); } internalNodes.add(node); rootNode = node; return node; } public Node getSourceNode(Node node) { return ((ReRootedNode)node).source; } /** * @param node the node whose height is being set * @param height the height */ public void setHeight(Node node, double height) { lengthsKnown = false; heightsKnown = true; // If a single height of a single node is set then // assume that all nodes have heights and by extension, // branch lengths as well as these will be calculated // from the heights hasLengths = true; hasHeights = true; ((ReRootedNode)node).setHeight(height); } /** * @param node the node whose branch length (to its parent) is being set * @param length the length */ public void setLength(Node node, double length) { heightsKnown = false; lengthsKnown = true; // If a single length of a single branch is set then // assume that all branch have lengths and by extension, // node heights as well as these will be calculated // from the lengths hasLengths = true; hasHeights = true; ((ReRootedNode)node).setLength(length); } /** * @param node the node whose children are being requested. * @return the list of nodes that are the children of the given node. * The list may be empty for a terminal node (a tip). */ public List<Node> getChildren(Node node) { return new ArrayList<Node>(((ReRootedNode)node).getChildren()); } /** * @return Whether this tree has node heights available */ public boolean hasHeights() { return hasHeights; } /** * @param node the node whose height is being requested. * @return the height of the given node. The height will be * less than the parent's height and greater than it children's heights. */ public double getHeight(Node node) { if (!hasHeights) throw new IllegalArgumentException("This tree has no node heights"); if (!heightsKnown) calculateNodeHeights(); return ((ReRootedNode)node).getHeight(); } /** * @return Whether this tree has branch lengths available */ public boolean hasLengths() { return hasLengths; } /** * @param node the node whose branch length (to its parent) is being requested. * @return the length of the branch to the parent node (0.0 if the node is the root). */ public double getLength(Node node) { if (!hasLengths) throw new IllegalArgumentException("This tree has no branch lengths"); if (!lengthsKnown) calculateBranchLengths(); return ((ReRootedNode)node).getLength(); } /** * @param node the node whose parent is requested * @return the parent node of the given node, or null * if the node is the root node. */ public Node getParent(Node node) { if (!(node instanceof ReRootedNode)) { throw new IllegalArgumentException("Node, " + node.toString() + " is not an instance of SimpleRootedNode"); } return ((ReRootedNode)node).getParent(); } public Edge getParentEdge(Node node) { if (!(node instanceof ReRootedNode)) { throw new IllegalArgumentException("Node, " + node.toString() + " is not an instance of SimpleRootedNode"); } return ((ReRootedNode)node).getEdge(); } /** * The root of the tree has the largest node height of * all nodes in the tree. * * @return the root of the tree. */ public Node getRootNode() { return rootNode; } /** * @return a set of all nodes that have degree 1. * These nodes are often refered to as 'tips'. */ public Set<Node> getExternalNodes() { return new LinkedHashSet<Node>(externalNodes.values()); } /** * @return a set of all nodes that have degree 2 or more. * These nodes are often refered to as internal nodes. */ public Set<Node> getInternalNodes() { return new LinkedHashSet<Node>(internalNodes); } /** * @return the set of taxa associated with the external * nodes of this tree. The size of this set should be the * same as the size of the external nodes set. */ public Set<Taxon> getTaxa() { return new LinkedHashSet<Taxon>(externalNodes.keySet()); } /** * @param node the node whose associated taxon is being requested. * @return the taxon object associated with the given node, or null * if the node is an internal node. */ public Taxon getTaxon(Node node) { if (!(node instanceof ReRootedNode)) { throw new IllegalArgumentException("Node, " + node.toString() + " is not an instance of SimpleRootedNode. It is an instance of "+node.getClass().getName()); } return ((ReRootedNode)node).getTaxon(); } /** * @param node the node * @return true if the node is of degree 1. */ public boolean isExternal(Node node) { if (!(node instanceof ReRootedNode)) { throw new IllegalArgumentException("Node, " + node.toString() + " is not an instance of SimpleRootedNode. It is an instance of "+node.getClass().getName()); } return ((ReRootedNode)node).getChildren().size() == 0; } /** * @param taxon the taxon * @return the external node associated with the given taxon, or null * if the taxon is not a member of the taxa set associated with this tree. */ public Node getNode(Taxon taxon) { return externalNodes.get(taxon); } public void renameTaxa(Taxon from, Taxon to) { ReRootedNode node = (ReRootedNode)externalNodes.get(from); // TT: The javadoc doesn't specify whether renameTaxa() should fail or silently do nothing // if Taxon from doesn't exist. But the code already threw a NullPointerException before (bug 4824), // so it's probably ok to throw a more informative IllegalArgumentException instead. if (node == null) { throw new IllegalArgumentException("Unknown taxon " + from + "; can't rename to " + to); } node.setTaxon(to); externalNodes.remove(from); externalNodes.put(to, node); } /** * Returns a list of edges connected to this node * * @param node * @return the set of nodes that are attached by edges to the given node. */ public List<Edge> getEdges(Node node) { List<Edge> edges = new ArrayList<Edge>(); for (Node adjNode : getAdjacencies(node)) { edges.add(((ReRootedNode)adjNode).getEdge()); } return edges; } /** * @param node * @return the set of nodes that are attached by edges to the given node. */ public List<Node> getAdjacencies(Node node) { return ((ReRootedNode)node).getAdjacencies(); } /** * Returns the Edge that connects these two nodes * * @param node1 * @param node2 * @return the edge object. * @throws jebl.evolution.graphs.Graph.NoEdgeException * if the nodes are not directly connected by an edge. */ public Edge getEdge(Node node1, Node node2) throws NoEdgeException { if (((ReRootedNode)node1).getParent() == node2) { return ((ReRootedNode)node1).getEdge(); } else if (((ReRootedNode)node2).getParent() == node1) { return ((ReRootedNode)node2).getEdge(); } else { throw new NoEdgeException(); } } /** * @param node1 * @param node2 * @return the length of the edge connecting node1 and node2. * @throws jebl.evolution.graphs.Graph.NoEdgeException * if the nodes are not directly connected by an edge. */ public double getEdgeLength(Node node1, Node node2) throws NoEdgeException { if (((ReRootedNode)node1).getParent() == node2) { if (heightsKnown) { return ((ReRootedNode)node2).getHeight() - ((ReRootedNode)node1).getHeight(); } else { return ((ReRootedNode)node1).getLength(); } } else if (((ReRootedNode)node2).getParent() == node1) { if (heightsKnown) { return ((ReRootedNode)node1).getHeight() - ((ReRootedNode)node2).getHeight(); } else { return ((ReRootedNode)node2).getLength(); } } else { throw new NoEdgeException(); } } /** * Returns an array of 2 nodes which are the nodes at either end of the edge. * * @param edge * @return an array of 2 edges */ public Node[] getNodes(Edge edge) { for (Node node : getNodes()) { if (((ReRootedNode)node).getEdge() == edge) { return new Node[] { node, ((ReRootedNode)node).getParent() }; } } return null; } /** * @return the set of all nodes in this graph. */ public Set<Node> getNodes() { Set<Node> nodes = new LinkedHashSet<Node>(internalNodes); nodes.addAll(externalNodes.values()); return nodes; } /** * @return the set of all edges in this graph. */ public Set<Edge> getEdges() { Set<Edge> edges = new LinkedHashSet<Edge>(); for (Node node : getNodes()) { if (node != getRootNode()) { edges.add(((ReRootedNode)node).getEdge()); } } return edges; } /** * The set of external edges. This is a pretty inefficient implementation because * a new set is constructed each time this is called. * @return the set of external edges. */ public Set<Edge> getExternalEdges() { Set<Edge> edges = new LinkedHashSet<Edge>(); for (Node node : getExternalNodes()) { edges.add(((ReRootedNode)node).getEdge()); } return edges; } /** * The set of internal edges. This is a pretty inefficient implementation because * a new set is constructed each time this is called. * @return the set of internal edges. */ public Set<Edge> getInternalEdges() { Set<Edge> edges = new LinkedHashSet<Edge>(); for (Node node : getInternalNodes()) { if (node != getRootNode()) { edges.add(((ReRootedNode)node).getEdge()); } } return edges; } /** * @param degree the number of edges connected to a node * @return a set containing all nodes in this graph of the given degree. */ public Set<Node> getNodes(int degree) { Set<Node> nodes = new LinkedHashSet<Node>(); for (Node node : getNodes()) { // Account for no anncesstor of root, assumed by default in getDegree final int deg = node.getDegree() ; if (deg == degree) nodes.add(node); } return nodes; } /** * Set the node heights from the current branch lengths. */ private void calculateNodeHeights() { if (!lengthsKnown) { throw new IllegalArgumentException("Can't calculate node heights because branch lengths not known"); } nodeLengthsToHeights(rootNode, 0.0); double maxHeight = 0.0; for (Node externalNode : getExternalNodes()) { if (((ReRootedNode)externalNode).getHeight() > maxHeight) { maxHeight = ((ReRootedNode)externalNode).getHeight(); } } for (Node node : getNodes()) { ((ReRootedNode)node).setHeight(maxHeight - ((ReRootedNode)node).getHeight()); } heightsKnown = true; } /** * Set the node heights from the current node branch lengths. Actually * sets distance from root so the heights then need to be reversed. */ private void nodeLengthsToHeights(ReRootedNode node, double height) { double newHeight = height; if (node.getLength() > 0.0) { newHeight += node.getLength(); } node.setHeight(newHeight); for (Node child : node.getChildren()) { nodeLengthsToHeights((ReRootedNode)child, newHeight); } } /** * Calculate branch lengths from the current node heights. */ protected void calculateBranchLengths() { if (!hasLengths) { throw new IllegalArgumentException("Can't calculate branch lengths because node heights not known"); } nodeHeightsToLengths(rootNode, getHeight(rootNode)); lengthsKnown = true; } /** * Calculate branch lengths from the current node heights. */ private void nodeHeightsToLengths(ReRootedNode node, double height) { final double h = node.getHeight(); node.setLength(h >= 0 ? height - h : 1); for (Node child : node.getChildren()) { nodeHeightsToLengths((ReRootedNode)child, node.getHeight()); } } public boolean conceptuallyUnrooted() { return false; } public boolean isRoot(Node node) { return node == rootNode; } // Attributable IMPLEMENTATION public void setAttribute(String name, Object value) { source.setAttribute(name, value); } public Object getAttribute(String name) { return source.getAttribute(name); } public void removeAttribute(String name) { source.removeAttribute(name); } public Set<String> getAttributeNames() { return source.getAttributeNames(); } public Map<String, Object> getAttributeMap() { return source.getAttributeMap(); } /** * Root any tree by locating the "center" of tree and adding a new root node at that point * <p/> * for any point on the tree x let D(x) = Max{distance between x and t : for all tips t} * The "center" c is the point with the smallest distance, i.e. D(c) = min{ D(x) : x in tree } * * @param tree to root * @return rooted tree */ public static RootedTree rootTreeAtCenter(RootedTree tree) { // Method - find the pair of tips with the longest distance. It is easy to see that the center // is at the midpoint of the path between them. HashMap<HashPair<Node>, Double> dists = new LinkedHashMap<HashPair<Node>, Double>(); try { double maxDistance = -Double.MAX_VALUE; // node on maximal path Node current = null; // next node on maximal path Node direction = null; // locate one terminal node of longest path for (Node e : tree.getExternalNodes()) { for (Node n : tree.getAdjacencies(e)) { final double d = dist(tree, e, n, dists); if (d > maxDistance) { maxDistance = d; current = e; direction = n; } } } // traverse along maximal path to it's middle double distanceLeft = maxDistance / 2.0; while (true) { final double len = tree.getEdgeLength(current, direction); if (distanceLeft <= len) { //System.out.println(toNewick(rtree)); return new ReRootedTree(tree, current, direction, distanceLeft); } distanceLeft -= len; maxDistance = -Double.MAX_VALUE; Node next = null; for (Node n : tree.getAdjacencies(direction)) { if (n == current) continue; final double d = dist(tree, direction, n, dists); if (d > maxDistance) { maxDistance = d; next = n; } } current = direction; direction = next; } } catch (Graph.NoEdgeException e1) { return null; // serious bug, should not happen } } private static double dist(Tree tree, Node root, Node node, Map<HashPair<Node>, Double> dists) throws Graph.NoEdgeException { HashPair<Node> p = new HashPair<Node>(root, node); if (dists.containsKey(p)) { return dists.get(p); } // assume positive branches double maxDist = 0; for (Node n : tree.getAdjacencies(node)) { if (n != root) { double d = dist(tree, node, n, dists); maxDist = Math.max(maxDist, d); } } double dist = tree.getEdgeLength(node, root) + maxDist; dists.put(p, dist); return dist; } // PRIVATE members private RootedTree source = null; private ReRootedNode rootNode = null; private final Set<Node> internalNodes = new LinkedHashSet<Node>(); private final Map<Taxon, Node> externalNodes = new LinkedHashMap<Taxon, Node>(); private boolean heightsKnown = false; private boolean lengthsKnown = false; private boolean hasHeights = false; private boolean hasLengths = false; private class ReRootedNode implements Node { public ReRootedNode(Node source, Taxon taxon) { this.source = source; this.children = Collections.unmodifiableList(new ArrayList<Node>()); this.taxon = taxon; } public ReRootedNode(Node source, List<? extends Node> children) { this.source = source; this.children = Collections.unmodifiableList(new ArrayList<Node>(children)); this.taxon = null; } public Node getParent() { return parent; } public void setParent(Node parent) { this.parent = parent; } public List<Node> getChildren() { return children; } public double getHeight() { return height; } // height above latest tip public void setHeight(double height) { this.height = height; } // length of branch to parent public double getLength() { return length; } public void setLength(double length) { this.length = length; } public int getDegree() { return children.size() +(this==rootNode?0:1); } public void setTaxon(Taxon to) { taxon = to; } /** * returns the edge connecting this node to the parent node * @return the edge */ public Edge getEdge() { if (edge == null) { edge = new BaseEdge() { public double getLength() { return length; } }; } return edge; } /** * For a rooted tree, getting the adjacencies is not the most efficient * operation as it makes a new set containing the children and the parent. * @return the adjacaencies */ public List<Node> getAdjacencies() { List<Node> adjacencies = new ArrayList<Node>(); if (children != null) adjacencies.addAll(children); if (parent != null) adjacencies.add(parent); return adjacencies; } public Taxon getTaxon() { return taxon; } // Attributable IMPLEMENTATION public void setAttribute(String name, Object value) { if (source == null) { if (helper == null) { helper = new AttributableHelper(); } helper.setAttribute(name, value); } else { source.setAttribute(name, value); } } public Object getAttribute(String name) { if (source == null) { if (helper == null) { return null; } return helper.getAttribute(name); } return source.getAttribute(name); } public void removeAttribute(String name) { if (source == null) { if( helper != null ) { helper.removeAttribute(name); } } else { source.removeAttribute(name); } } public Set<String> getAttributeNames() { if (source == null) { if (helper == null) { return Collections.emptySet(); } return helper.getAttributeNames(); } return source.getAttributeNames(); } public Map<String, Object> getAttributeMap() { if (source == null) { if (helper == null) { return Collections.emptyMap(); } return helper.getAttributeMap(); } return source.getAttributeMap(); } private final Node source; private List<Node> children; private Taxon taxon; private Node parent; private double height; private double length; private Edge edge = null; private AttributableHelper helper = null; } }