University of Connecticut University of UC Title Fallback Connecticut

Create the TreeManip class

There are benefits to keeping the Tree and Node classes simple, so we will create a TreeManip class that will own, manage and manipulate a Tree object. Rather than call the createTestTree() function of Tree, we will move that function to TreeManip. TreeManip will also eventually have functions to create a standard Newick description string, build a Tree from a Newick description, and perform modifications to a Tree needed during Metropolis (MCMC) proposals.

First, create the TreeManip class by creating a new header file named tree_manip.hpp and populating it with the following code:

#pragma once

#include <memory>
#include "tree.hpp"

namespace strom
{

    class TreeManip
        {
        public:
                                        TreeManip();
                                        TreeManip(Tree::SharedPtr t);
                                        ~TreeManip();

            void                        setTree(Tree::SharedPtr t);
            Tree::SharedPtr             getTree();
            void                        createTestTree();
            void                        clear();

        private:

            Tree::SharedPtr             _tree;

        public:

            typedef std::shared_ptr< TreeManip > SharedPtr;
        };

    // This is where function bodies go
    }

Class declarations like this should look familiar because there are many similarities of this class declaration to those we created for Tree and Node. Inside the strom namespace, we have created a class named TreeManip that has two constructors, a destructor, four public member functions, a private data member named _tree, and a type definition that will allow us to create shared pointers to TreeManip objects.

Constructors and destructor functions

We still need to define bodies for each of the functions in the class declaration above. Here are the bodies for the two constructors and the destructor. These functions currently do little more than announce that a TreeManip object is being created or destroyed, except that the constructor that takes a tree shared pointer argument calls the member function setTree, which is defined below. These three member function definitions should go after the semicolon (;) ending the class declaration but before the curly bracket (}) that closes the namespace:

inline TreeManip::TreeManip()
    {
    std::cerr << "Constructing a TreeManip" << std::endl;
    clear();
    }

inline TreeManip::TreeManip(Tree::SharedPtr t)
    {
    std::cerr << "Constructing a TreeManip with a supplied tree" << std::endl;
    clear();
    setTree(t);
    }

inline TreeManip::~TreeManip()
    {
    std::cerr << "Destroying a TreeManip" << std::endl;
    }

The clear function

This function resets the _tree data member. Resetting a shared pointer causes it to no longer point to any object. If this shared pointer was still pointing to a Tree object, and if this was the last shared pointer holding onto that Tree object, the Tree object will be deleted (and you should see its destructor report that it has been called):

inline void TreeManip::clear()
    {
    _tree.reset();
    }

The setTree(Tree::SharedPtr t) function

This function sets the data member _tree to point to the supplied tree t. This extra constructor is not essential: we could use the default constructor (that takes no arguments) and then call setTree immediately afterwards and accomplish the same thing. This extra constructor just makes it possible to accomplish both actions (creating the TreeManip object and assigning a tree to it) in the same line of code.

inline void TreeManip::setTree(Tree::SharedPtr t)
    {
    assert(t);
    _tree = t;
    }

The getTree function

This function simply returns a shared pointer to the Tree object being managed by this TreeManip object:

inline Tree::SharedPtr TreeManip::getTree()
    {
    return _tree;
    }

The createTestTree function

This function is nearly identical to the function of the same name in the Tree class. One difference is that we must create a Tree first, and data members of the Tree class (such as _nodes, _preorder, and _is_rooted) must be accessed through the TreeManip object’s _tree pointer:

inline void TreeManip::createTestTree()
    {
    clear();
    _tree = Tree::SharedPtr(new Tree());
    _tree->_nodes.resize(6);

    Node * root_node       = &_tree->_nodes[0];
    Node * first_internal  = &_tree->_nodes[1];
    Node * second_internal = &_tree->_nodes[2];
    Node * first_leaf      = &_tree->_nodes[3];
    Node * second_leaf     = &_tree->_nodes[4];
    Node * third_leaf      = &_tree->_nodes[5];

    // Here is the structure of the tree (numbers in
    // parentheses are node numbers, other numbers
    // are edge lengths):
    //
    // first_leaf (0)   second_leaf (1)   third_leaf (2)
    //      \              /                  /
    //       \ 0.1        / 0.1              /
    //        \          /                  /
    //     second_internal (3)             / 0.2
    //             \                      /
    //              \ 0.1                /
    //               \                  /
    //                first_internal (4)
    //                        |
    //                        | 0.1
    //                        |
    //                    root_node (5)
    //
    root_node->_parent = 0;
    root_node->_left_child = first_internal;
    root_node->_right_sib = 0;
    root_node->_number = 5;
    root_node->_name = "root node";
    root_node->_edge_length = 0.0;

    first_internal->_parent = root_node;
    first_internal->_left_child = second_internal;
    first_internal->_right_sib = 0;
    first_internal->_number = 4;
    first_internal->_name = "first internal node";
    first_internal->_edge_length = 0.1;

    second_internal->_parent = first_internal;
    second_internal->_left_child = first_leaf;
    second_internal->_right_sib = third_leaf;
    second_internal->_number = 3;
    second_internal->_name = "second internal node";
    second_internal->_edge_length = 0.1;

    first_leaf->_parent = second_internal;
    first_leaf->_left_child = 0;
    first_leaf->_right_sib = second_leaf;
    first_leaf->_number = 0;
    first_leaf->_name = "first leaf";
    first_leaf->_edge_length = 0.1;

    second_leaf->_parent = second_internal;
    second_leaf->_left_child = 0;
    second_leaf->_right_sib = 0;
    second_leaf->_number = 1;
    second_leaf->_name = "second leaf";
    second_leaf->_edge_length = 0.1;

    third_leaf->_parent = first_internal;
    third_leaf->_left_child = 0;
    third_leaf->_right_sib = 0;
    third_leaf->_number = 2;
    third_leaf->_name = "third leaf";
    third_leaf->_edge_length = 0.1;

    _tree->_is_rooted = true;
    _tree->_root = root_node;   
    _tree->_nleaves = 3;

    // Note that root node is not included in _preorder
    _tree->_preorder.push_back(first_internal);
    _tree->_preorder.push_back(second_internal);
    _tree->_preorder.push_back(first_leaf);
    _tree->_preorder.push_back(second_leaf);
    _tree->_preorder.push_back(third_leaf);
}

Before moving on…

Edit your Tree class (file tree.hpp) and delete all traces of createTestTree(). We no longer need Tree to have this capability because we can now ask TreeManip to create a test tree.

Also, edit your Node class (file node.hpp) and uncomment the lines making TreeManip a friend class of Node. This will just involve removing the initial // from two lines in tree.hpp.

The Tree class will need to be similarly modified so that TreeManip is a friend of Tree. I’ve indicated the two lines in tree.hpp that need to be uncommented below in bold, blue text:

#pragma once

#include <iostream>
#include <vector>

namespace strom
    {

    class TreeManip;
    //class Likelihood;

    class Tree
	{

        friend class TreeManip;
        //friend class Likelihood;

	public:
		                          Tree();
		                          ~Tree();

	private:

		void                      clear();

		bool                      _is_rooted;
                Node *                    _root;
		unsigned                  _nleaves;
		std::vector<Node *>       _preorder;
		std::vector<Node>         _nodes;

	public:
		typedef std::shared_ptr< Tree > SharedPtr;
	};
   .
   .
   .