19.2 Adding the PolytomyUpdater class

(Mac version)

< 19.1 | 19.2 | 19.3 >

We need a new updater that either adds an edge to an existing polytomy or deletes an edge to create a new polytomy (or expand an existing one). Create the PolytomyUpdater class declaration below in the file polytomy_updater.hpp.

#pragma once    

#include "updater.hpp"

namespace strom {

    class Chain;

    class PolytomyUpdater : public Updater {

        friend class Chain;

        public:

            typedef std::vector&lt;double&gt;                         _partition_vect_t;
            typedef std::map&lt;unsigned, _partition_vect_t &gt;      _partition_map_t;
            typedef std::vector&lt;Node *&gt;                         _polytomy_vect_t;
            typedef std::shared_ptr&lt; PolytomyUpdater &gt;          SharedPtr;

                                                PolytomyUpdater();
                                                ~PolytomyUpdater();

            virtual double                      calcLogPrior();
            
        private:

            virtual void                        revert();
            virtual void                        proposeNewState();
            virtual void                        reset();
            
            void                                proposeAddEdgeMove(Node * nd);
            void                                proposeDeleteEdgeMove(Node * nd);
            
            _partition_vect_t &                 computePolytomyDistribution(unsigned nspokes);
            void                                refreshPolytomies();

            _partition_map_t                    _poly_prob;
            _polytomy_vect_t                    _polytomies;
            
            Node *                              _orig_par;
            Node *                              _orig_lchild;
            
            Node *                              _first_child;
            Node *                              _chosen_node;
            double                              _chosen_edgelen;
            double                              _chosen_proportion;
            double                              _remainder_proportion;
            double                              _fraction;

            bool                                _add_edge_proposed;
            double                              _new_edge_proportion;
            double                              _orig_edge_proportion;
            double                              _tree_length;
            double                              _phi;
            unsigned                            _polytomy_size;
            unsigned                            _num_polytomies;
    };

    // Member function bodies go here
    
}   

The constructor and destructor

This class uses the base class (Updater) version of the clear function in the constructor and, as usual, the destructor has no work to do.

    inline PolytomyUpdater::PolytomyUpdater() { 
        // std::cout &lt;&lt; "Creating a PolytomyUpdater" &lt;&lt; std::endl;
        Updater::clear();
        _name = "Polytomies";
        reset();
    }   

    inline PolytomyUpdater::~PolytomyUpdater() {    
        // std::cout &lt;&lt; "Destroying a PolytomyUpdater" &lt;&lt; std::endl;
    }   

The reset member function

The reset function is called at the end of the Updater::update function to prepare the updater for the next update. Any temporary variables needed by an updater during the update process need to be re-initialized in this function, and this PolytomyUpdater has several of these temporary variables, most of which are needed to keep track of how to revert if the proposed tree is rejected.

    inline void PolytomyUpdater::reset() {  
        _tree_length          = 0.0;
        _new_edge_proportion  = 0.0;
        _orig_edge_proportion = 0.0;
        _orig_par             = 0;
        _orig_lchild          = 0;
        _chosen_node          = 0;
        _first_child          = 0;
        _polytomy_size        = 0;
        _num_polytomies       = 0;
        _add_edge_proposed    = false;
        _polytomies.clear();
    }   

The calcLogPrior member function

The calcLogPrior function calls two functions in the base class (Updater) and returns the sum of the values they calculate. This function is an abstract function in the base class (along with proposeNewState and revert) and so must be defined in this derived class.

    inline double PolytomyUpdater::calcLogPrior() {   
        double log_prior = 0.0;
        log_prior += Updater::calcLogTopologyPrior();
        log_prior += Updater::calcLogEdgeLengthPrior();
        return log_prior;
    }   

The proposeNewState member function

The proposeNewState function, which is an abstract base class member function that must be defined in any derived class, proposes either an add-edge or a delete-edge move.

The first part of this function is involved in determining whether the current tree is the star tree (in which case add-edge is the only move possible) or a fully-resolved, binary tree (in which case delete-edge is the only option). If both add-edge and delete-edge are possible, then a coin flip is used to decide which of the two will be attempted.

The member functions proposeAddEdgeMove and proposeDeleteEdgeMove handle most of the work, once the decision is made which move to attempt. The calculation of the Hastings ratio and Jacobian determinant will be discussed when these functions are introduced.

    inline void PolytomyUpdater::proposeNewState() {    
        Tree::SharedPtr tree = _tree_manipulator-&gt;getTree();
        
        // probability of choosing and add-edge move if both add-edge and delete-edge are possible
        const double _prob_add_edge = 0.5;

        // Compute number of internal nodes in a fully resolved tree
        unsigned num_internals_in_fully_resolved_tree = 0;
        if (tree-&gt;isRooted()) {
            // remember: root node is internal because it has a left child
            num_internals_in_fully_resolved_tree = tree-&gt;numLeaves();
        }
        else
            num_internals_in_fully_resolved_tree = tree-&gt;numLeaves() - 2;
            
        // Compute tree length before proposed move
        _tree_length = _tree_manipulator-&gt;calcTreeLength();

        // Determine whether starting tree is fully resolved or the star tree
        unsigned num_internals_before = tree-&gt;numInternals();
        //unsigned num_leaves_before = tree-&gt;numLeaves();
        unsigned num_internal_edges_before = num_internals_before - (tree-&gt;isRooted() ? 2 : 1);
        bool fully_resolved_before = (num_internals_in_fully_resolved_tree == num_internals_before);
        bool star_tree_before = (tree-&gt;numInternals() == 1);
        
        // Rebuild _polytomies vector, which is a vector of pointers to Node objects having more than 2 children
        refreshPolytomies();
        _num_polytomies = (unsigned)_polytomies.size();
        
        // Determine whether an add edge move is proposed (alternative is a delete edge move)
        if (star_tree_before)
            _add_edge_proposed = true;
        else if (fully_resolved_before)
            _add_edge_proposed = false;
        else
            _add_edge_proposed = (_lot-&gt;uniform() &lt; _prob_add_edge);

        Node * nd = 0;
        if (_add_edge_proposed) {
            // Choose a polytomy at random to split
            unsigned i = (unsigned)_lot-&gt;randint(0, _num_polytomies-1);
            nd = _polytomies[i];
            _polytomy_size = 1 + _tree_manipulator-&gt;countChildren(nd);

            // Add an edge to split up polytomy at nd, moving a random subset
            // of the spokes to the (new) left child of nd
            proposeAddEdgeMove(nd);

            double TL_after_add_edge = _tree_manipulator-&gt;calcTreeLength();
            assert(std::fabs(_tree_length - TL_after_add_edge) &lt; 1.e-8);

            // Compute the log of the Hastings ratio
            _log_hastings_ratio  = 0.0;
            _log_hastings_ratio += std::log(_num_polytomies);
            _log_hastings_ratio += std::log(pow(2.0, _polytomy_size - 1) - _polytomy_size - 1);
            _log_hastings_ratio -= std::log(num_internal_edges_before + 1);

            // Now multiply by the value of the quantity labeled gamma_b in the Lewis-Holder-Holsinger (2005) paper
            unsigned num_internals_after = tree-&gt;numInternals();
            assert(num_internals_after == num_internals_before + 1);
            const bool fully_resolved_after = (num_internals_after == num_internals_in_fully_resolved_tree);
            double tmp = 0.0;
            if (star_tree_before && !fully_resolved_after)
                tmp += log(1.0 - _prob_add_edge);
            else if (fully_resolved_after && !star_tree_before)
                tmp -= log(_prob_add_edge);
            else if (!star_tree_before && !fully_resolved_after) {
                tmp += log(1.0 - _prob_add_edge);
                tmp -= log(_prob_add_edge);
            }
            _log_hastings_ratio += tmp;

            // Compute the log of the Jacobian
            //_log_jacobian = std::log(_orig_edge_proportion) - std::log(1.-_fraction);
            _log_jacobian = std::log(_orig_edge_proportion);    //POLTMP temporary!

            // flag partials for recalculation
            _tree_manipulator-&gt;selectPartialsHereToRoot(_orig_lchild);

            // These two nodes have had their edge lengths modified, so their transition matrices need recalculating
            _orig_lchild-&gt;selectTMatrix();
            _chosen_node-&gt;selectTMatrix();
        }
        else {
            // Choose an internal edge at random and delete it to create a polytomy
            // (or a bigger polytomy if there is already a polytomy)
            nd = _tree_manipulator-&gt;randomInternalEdge(_lot);
            assert(nd-&gt;getParent() && nd-&gt;getParent()-&gt;getParent());
            
            proposeDeleteEdgeMove(nd);

            double TL_after_del_edge = _tree_manipulator-&gt;calcTreeLength();
            assert(std::fabs(_tree_length - TL_after_del_edge) &lt; 1.e-8);

            // Compute the log of the Hastings ratio
            _log_hastings_ratio  = 0.0;
            _log_hastings_ratio += std::log(num_internal_edges_before);
            _log_hastings_ratio -= std::log(_num_polytomies);
            _log_hastings_ratio -= std::log(pow(2.0, _polytomy_size - 1) - _polytomy_size - 1);

            // Now multiply by the value of the quantity labeled gamma_b in the Lewis-Holder-Holsinger (2005) paper
            // Now multiply by the value of the quantity labeled gamma_d in the paper
            unsigned num_internals_after = tree-&gt;numInternals();
            assert(num_internals_after == num_internals_before - 1);
            const bool star_tree_after = (num_internals_after == (tree-&gt;isRooted() ? 2 : 1));
            double tmp = 0.0;
            if (fully_resolved_before && !star_tree_after)
                tmp += log(_prob_add_edge);
            else if (star_tree_after && !fully_resolved_before)
                tmp -= log(1.0 - _prob_add_edge);
            else if (!fully_resolved_before && !star_tree_after) {
                tmp += log(_prob_add_edge);
                tmp -= log(1.0 - _prob_add_edge);
            }
            _log_hastings_ratio += tmp;

            // Compute the log Jacobian
            _log_jacobian = -std::log(_new_edge_proportion);

            // flag partials and transition matrices for recalculation
            _tree_manipulator-&gt;selectPartialsHereToRoot(_orig_par);
            _chosen_node-&gt;selectTMatrix();
            
            // This node is no longer in the tree, so we can't include it in selectPartialsHereToRoot;
            // however, this node's tmatrix and partials will be trashed because it will be employed
            // as a polytomy helper in the next likelihood calculation, so it needs to be flipped
            // so that a revert will retain the original tmatrix and partial
            _orig_lchild-&gt;selectTMatrix();
            _orig_lchild-&gt;selectPartial();
        }
    }   

The proposeAddEdgeMove member function

The add-edge move is illustrated below. This move involves choosing a polytomy uniformly at random (1/2 in the example because there are 2 polytomies to choose from), drawing a new edge proportion by multiplying the tuning parameter (_phi) by a uniform(0,1) random deviate, squeezing the previous edge proportions down to make room for the new edge, and, finally, deciding how to partition the spokes of the original polytomy between the two nodes at the ends of the newly created edge (there are 3 possible choices for a polytomy with 4 spokes: 9,10 was chosen in the example, but 8,10 and 7,10 were also possibilities).

The add-edge move

The full acceptance ratio comprises 5 terms: The add-edge acceptance ratio

The probability of accepting the proposed add-edge move is either 1.0 or R, whichever is smaller.

The first term (left-to-right) is the likelihood ratio, which is the likelihood of the tree after the add-edge move divided by the likelihood of the tree before the move. This term is straightforward (although, as you will see shortly, we will have to modify our Likelihood class to handle computing likelihoods for polytomous trees).

The second term is the ratio of the prior density after the move divided by the prior density before the move. The prior in both cases is a product of the (discrete) topology prior probability and the edge proportions prior density. The topology prior is new, but will be discussed when we modify the Updater base class to add the member function calcLogTopologyPrior. The PolytomyUpdater::calcLogPrior function (see above) computes the joint prior, and the Updater::update function is where this function is called (before and after the proposed move).

The third term from the left is the ratio of the probability of proposing the reverse move (a delete-edge move that exactly reverses the proposed add-edge move) divided by the probability of proposing the add-edge move itself. If the current tree (before the add-edge move) is the star tree, then this ratio equals 0.5/1 = 0.5. If the current tree is a fully-resolved tree, this ratio equals 1/0.5 = 2. Otherwise, it equals 1.0.

The fourth term is the probability of the reverse move divided by the probability of the forward (add-edge) move. The reverse move involves only a choice of which edge to delete, the probability of which is just 1 divided by the number of edges in the tree (one more than the number of edges n_e in the starting tree for the add-edge move).

The forward move is more complex. First, we must choose one of n_p polytomies to break up (probability 1/n_p). Next, we must decide which of the s spokes (edges radiating from this polytomous node) to move to the newly created node. This probability is 1 divided by the number of ways of partitioning s spokes into 2 groups, ensuring that each group contains at least 2 spokes. This number of possible partitionings is quantity 2^(s-1)-s-1. Finally, we must draw a Uniform(0,1) random deviate, u, to help us choose an edge proportion to go along with the newly created node. The probability density of this is the f(u) term, which equals 1.

The final term is the absolute value of the determinant of the Jacobian matrix for the transformation of u and the n_e edge proportions before the add-edge move into the n_e+1 edge proportions in the tree after the new edge has been inserted. The calculation of the Jacobian determinant for the simplest case (4-taxon star tree) is illustrated below:

The Jacobian for the 4-taxon star tree add-edge case

Note that the Jacobian matrix is 4x4, not 5x5, as one edge proportion can be determined from the others. I’ve arbitrarily chosen edge proportion 1 as the one that is determined by the others.

Hopefully, I’ve provided enough background for you to now understand the proposeAddEdgeMove function (and the section of proposeNewState in which it appears).

    inline void PolytomyUpdater::proposeAddEdgeMove(Node * u) {    
        Tree::SharedPtr tree = _tree_manipulator-&gt;getTree();

        // Split up the polytomy at `u' by creating a new internal node v and a new edge
        // connecting u with v. Node u is saved as _orig_par and node v is saved
        // as _orig_lchild in case we need to revert the proposed move.
        assert(u);

        // Create the new node that will receive the k randomly-chosen spokes
        Node * v = _tree_manipulator-&gt;getUnusedNode();
        _tree_manipulator-&gt;insertSubtreeOnLeft(v, u);
        assert(u-&gt;getLeftChild() == v);
        _tree_manipulator-&gt;rectifyNumInternals(+1);

        // Save u and v. If revert is necessary, all of orig_lchild's nodes will be returned
        // to orig_par, and orig_lchild will be deleted.
        _orig_par    = u;
        _orig_lchild = v;

        // Calculate (if necessary) the probability of each possible partitioning of the chosen polytomy
        // Select number of spokes to move over to new node
        // Note that 0 and 1 are not allowed because they
        // would leave the tree in an invalid state
        const _partition_vect_t & prob_n = computePolytomyDistribution(_polytomy_size);
        double p = _lot-&gt;uniform();
        double cum = 0.0;
        unsigned k = 0;
        bool found = false;
        for (; k &lt; prob_n.size(); ++k) {
            double prob_k_given_n = prob_n[k];
            cum += prob_k_given_n;
            if (p &lt; cum) {
                found = true;
                break;
            }
        }
        assert(found);
        k += 2;

        // After the move, either _orig_par or _orig_lchild should have k spokes
        // with the other node having _polytomy_size - k spokes
        // (_orig_par and _orig_lchild will each have 1 additional connector spoke).
        //
        // Choose k spokes randomly out of the _polytomy_size available.
        // If _orig_par-&gt;par is included, let _orig_par retain the k spokes and move
        // _polytomy_size - k spokes to _orig_lchild.
        // Otherwise, move the k spokes to _orig_lchild leaving _polytomy_size - k spokes behind.
        
        // Create vector of valid spokes (parent and all children of _orig_par except _orig_lchild)
        std::vector&lt;Node *&gt; uspokes;
        uspokes.push_back(_orig_par-&gt;getParent());
        for (Node * child = _orig_par-&gt;getLeftChild(); child; child = child-&gt;getRightSib()) {
            if (child != _orig_lchild)
                uspokes.push_back(child);
        }
        assert (uspokes.size() == _polytomy_size);
        
        // Choose one of the candidates as the edge to break
        unsigned i = (unsigned)_lot-&gt;randint(0, (int)uspokes.size()-1);
        _chosen_node = uspokes[i];
        if (_chosen_node == _orig_par-&gt;getParent())
            _chosen_node = _orig_par;

        // Break _chosen_node's edge in a random place
        _orig_edge_proportion = _chosen_node-&gt;getEdgeLength()/_tree_length;

        _fraction = _lot-&gt;uniform();
        _new_edge_proportion = _fraction*_orig_edge_proportion;
        _remainder_proportion = _orig_edge_proportion - _new_edge_proportion;

        _chosen_node-&gt;setEdgeLength(_new_edge_proportion*_tree_length);
        _orig_lchild-&gt;setEdgeLength(_remainder_proportion*_tree_length);

        bool reverse_polarity = false;
        std::vector&lt;Node *&gt; vspokes;
        typedef std::vector&lt;Node *&gt;::iterator::difference_type vec_it_diff;
        for (unsigned i = 0; i &lt; k; ++i) {
            unsigned num_u_spokes = (unsigned)uspokes.size();
            assert(num_u_spokes &gt; 0);
            unsigned j = (unsigned)_lot-&gt;randint(0, num_u_spokes-1);
            Node * s = uspokes[j];
            if (s == _orig_par-&gt;getParent())
                reverse_polarity = true;
            vspokes.push_back(s);
            uspokes.erase(uspokes.begin() + (vec_it_diff)j);
        }
        assert(uspokes.size() + vspokes.size() == _polytomy_size);

        if (reverse_polarity) {
            // transfer nodes in uspokes to _orig_lchild
            for (auto s = uspokes.begin(); s != uspokes.end(); ++s) {
                _tree_manipulator-&gt;detachSubtree(*s);
                _tree_manipulator-&gt;insertSubtreeOnRight(*s, _orig_lchild);
            }
        }
        else {
            // transfer nodes in vspokes to _orig_lchild
            for (auto s = vspokes.begin(); s != vspokes.end(); ++s) {
                _tree_manipulator-&gt;detachSubtree(*s);
                _tree_manipulator-&gt;insertSubtreeOnRight(*s, _orig_lchild);
            }
        }
        
        _tree_manipulator-&gt;refreshNavigationPointers();
    }   

The proposeDeleteEdgeMove member function

The delete-edge move is illustrated below.

The delete-edge move

The acceptance ratio for the delete-edge move is: The delete-edge acceptance ratio

The explanation of terms is largely the same as above for the add-edge move except: *p* now refers to the parameters after the delete-edge move and p to the parameters before the delete-edge move

    inline void PolytomyUpdater::proposeDeleteEdgeMove(Node * v) {    
        // Delete the edge associated with `v' to create a polytomy (or a bigger polytomy if `v-&gt;par' was already a polytomy).
        // The supplied node v should not be the only child of the root node.
        //
        //       b       c
        //        \     /
        //         \   /
        //          \ /
        //   a       v           a   b   c
        //    \     /            \  |  /
        //     \   /              \ | /
        //      \ /                \|/
        //       u                  u
        //      /                  /
        //
        //     Before           After
        //
        // Returns the number of polytomies in the tree after the proposed delete-edge move.
        // The return value will be incorrect if the polytomies vector is not up-to-date.
        
        // This operation should not leave the root node (which is a tip) with more than one
        // child, so check to make sure that the supplied node is not the root nor a child of root
        _orig_lchild = v;
        _orig_par = _orig_lchild-&gt;getParent();
        assert(_orig_par);
        assert(_orig_par-&gt;getParent());

        // Save _orig_lchild's edge length in case we need to revert
        _orig_edge_proportion = _orig_lchild-&gt;getEdgeLength()/_tree_length;

        // Compute size of polytomy after the delete-edge move, a quantity needed
        // for computing the Hastings ratio. Note that one of u's children
        // (i.e. _orig_lchild) is deleted but this is made up for by considering u-&gt;par,
        // which is also a spoke that counts.
        unsigned v_children = _tree_manipulator-&gt;countChildren(_orig_lchild);
        unsigned u_children = _tree_manipulator-&gt;countChildren(_orig_par);
        _polytomy_size = v_children + u_children;

        bool u_polytomy_before = (u_children &gt; 2);
        bool v_polytomy_before = (v_children &gt; 2);
        if (u_polytomy_before && v_polytomy_before) {
            // No. polytomies will decrease by one as a result of this delete-edge move
            --_num_polytomies;
        }
        else if (!u_polytomy_before && !v_polytomy_before) {
            // No. polytomies will increase by one as a result of this delete-edge move
            ++_num_polytomies;
        }

        // Make all of _orig_lchild's children children of _orig_par
        _first_child = _orig_lchild-&gt;getLeftChild();
        while (_orig_lchild-&gt;getLeftChild() != NULL) {
            Node * tmp = _orig_lchild-&gt;getLeftChild();
            _tree_manipulator-&gt;detachSubtree(tmp);
            _tree_manipulator-&gt;insertSubtreeOnRight(tmp, _orig_par);
        }

        _tree_manipulator-&gt;detachSubtree(_orig_lchild);
        _tree_manipulator-&gt;putUnusedNode(_orig_lchild);
        _tree_manipulator-&gt;rectifyNumInternals(-1);
        
        _tree_manipulator-&gt;refreshNavigationPointers();
        
        // Create vector of valid spokes
        std::vector&lt;Node *&gt; spokes;
        spokes.push_back(_orig_par-&gt;getParent());
        for (Node * child = _orig_par-&gt;getLeftChild(); child; child = child-&gt;getRightSib()) {
            if (child != _orig_par)
                spokes.push_back(child);
        }
        assert (spokes.size() == _polytomy_size);
        
        // Choose one of the candidates as the edge to absorb the deleted edge
        unsigned i = (unsigned)_lot-&gt;randint(0, (int)spokes.size()-1);
        _chosen_node = spokes[i];
        if (_chosen_node == _orig_par-&gt;getParent())
            _chosen_node = _orig_par;
        _chosen_edgelen = _chosen_node-&gt;getEdgeLength();
        _chosen_proportion = _chosen_edgelen/_tree_length;
        _chosen_node-&gt;setEdgeLength(_chosen_edgelen + _orig_edge_proportion*_tree_length);
        _new_edge_proportion = _chosen_node-&gt;getEdgeLength()/_tree_length;
        _fraction = _chosen_proportion/_new_edge_proportion;
    }   

The computePolytomyDistribution member function

The add-edge move splits the edges of a polytomy across a new edge. This function computes the probability distribution of possible partitionings and stores it in the map member variable _poly_prob under a key equal to the number of “spokes” in the polytomy where a spoke is an edge radiating from the polytomous node. The value of _poly_prob[6], as an example, is the following vector of length 2:

                     6!
_poly_prob[6][0] = ----- C   = 3/5
                   2! 4!

                      6!
_poly_prob[6][1] = ------- C = 2/5
                   3! 3! 2

where C is the normalizing constant, which equals 1/(2^5 - 6 - 1) = 1/25. Note that the final probability must be divided by 2 in the case of an even number of spokes. This means that 2 of 6 spokes will be moved to the new node with probability 0.6 and 3 spokes will be moved with probability 0.4.

    inline PolytomyUpdater::_partition_vect_t & PolytomyUpdater::computePolytomyDistribution(unsigned nspokes) {    
        assert(nspokes &gt; 2);
                
        // Only compute it if it isn't already stored in the _poly_prob map
        auto iter = _poly_prob.find(nspokes);
        if (iter == _poly_prob.end()) {
            // There is no existing probability distribution vector corresponding to nspokes
            double ln_denom = std::log(pow(2.0,nspokes-1) - nspokes - 1.0);
            _partition_vect_t v(nspokes - 3);
            unsigned first = 2;
            unsigned last = nspokes/2;
            bool nspokes_even = nspokes % 2 == 0;
            double total_prob = 0.0;
            for (unsigned x = first; x &lt;= last; ++x) {
                double ln_numer = std::lgamma(nspokes + 1) - std::lgamma(x + 1) - std::lgamma(nspokes - x + 1);
                if (nspokes_even && x == last)
                    ln_numer -= std::log(2);
                double prob_x = exp(ln_numer - ln_denom);
                if (prob_x &gt; 1.0)
                    prob_x = 1.0;
                total_prob += prob_x;
                v[x-first] = prob_x;
                }
            assert(std::fabs(total_prob - 1.0) &lt; 1.e-8);
            _poly_prob[nspokes] = v;
        }
        return _poly_prob[nspokes];
    } 

The revert member function

This function simply reverses the add-edge or delete-edge move performed. It is called by Updater::update if the proposed move is not accepted.

    inline void PolytomyUpdater::revert() { 
        if (_add_edge_proposed) {
            // Return all of _orig_lchild's child nodes to _orig_par, then return orig_lchild to storage
            Node * child = _orig_lchild-&gt;getLeftChild();
            while (child) {
                Node * rsib = child-&gt;getRightSib();
                _tree_manipulator-&gt;detachSubtree(child);
                _tree_manipulator-&gt;insertSubtreeOnRight(child, _orig_par);
                child = rsib;
            }
            _tree_manipulator-&gt;detachSubtree(_orig_lchild);
            _tree_manipulator-&gt;putUnusedNode(_orig_lchild);
            _tree_manipulator-&gt;rectifyNumInternals(-1);
            
            _tree_manipulator-&gt;refreshNavigationPointers();
            _chosen_node-&gt;setEdgeLength(_orig_edge_proportion*_tree_length);
            double TL_after_revert_add_edge = _tree_manipulator-&gt;calcTreeLength();
            assert(std::fabs(_tree_length -TL_after_revert_add_edge) &lt; 1.e-8);
        }
        else {
            Node * v = _tree_manipulator-&gt;getUnusedNode(_orig_lchild);
            _tree_manipulator-&gt;rectifyNumInternals(+1);
            _chosen_node-&gt;setEdgeLength(_chosen_edgelen);
            v-&gt;setEdgeLength(_orig_edge_proportion*_tree_length);
            for (Node * child = _first_child; child;) {
                Node * child_rsib = child-&gt;getRightSib();
                _tree_manipulator-&gt;detachSubtree(child);
                _tree_manipulator-&gt;insertSubtreeOnRight(child, v);
                child = child_rsib;
            }
            _tree_manipulator-&gt;insertSubtreeOnRight(v, _orig_par);
            _tree_manipulator-&gt;refreshNavigationPointers();
            
            double TL_after_revert_del_edge = _tree_manipulator-&gt;calcTreeLength();
            assert(std::fabs(_tree_length - TL_after_revert_del_edge) &lt; 1.e-8);
        }
    }   

The refreshPolytomies member function

This function creates a vector (stored in _polytomies) of nodes that are polytomous. This is used by the add-edge move to choose a focal polytomy to split up.

    inline void PolytomyUpdater::refreshPolytomies() {  
        Tree::SharedPtr tree = _tree_manipulator-&gt;getTree();
        _polytomies.clear();
        for (auto nd : tree-&gt;_preorder) {
            unsigned s = _tree_manipulator-&gt;countChildren(nd);
            if (s &gt; 2)
                _polytomies.push_back(nd);
        }
    }   

< 19.1 | 19.2 | 19.3 >