DTree

A DTree is a generalization of trees that devide space into regular partitions in D dimensions, such as (2D) and Octrees (3D).

DTrees

The regularity of DTrees makes especially suitable for spatial information that is also regular in space such as points in a lattice, because any node in the tree is associated with a specific part of space, and thus unique associated with a point in the lattice.

Components and Defintions

DTrees consists of at least one node, the root node. Each node is of either one of two classes: a leaf-node or a branch-node. A leaf-node contains data, but has no children. A branch-node has children, but contains no data. Every node knows it own depth in the tree. Every node except the root node knows its parent and its own index in the parent, the index being a number between 0 and N. The root node has no parent, and consequently no index.

Note that given a node's depth, index in the parent and the parent itself, it is possible to derive its corresponding partition of space. In fact, each node has a unique identifier that indicates its position in the tree based on this information. These identifiers can be used to quickly retrieve nodes from the tree.

Interface

A tree is the holder of the root node. It supports most methods of a node, forwarding them to the root node. Exceptions are the methods to obtain the index or parent of the root node because these don't exist by definition.

template <typename T, std::size_t D>
class DTree;
template <typename T, std::size_t D>
class DNode;

Asking the root node for its parent or index will throw an exception:

class NoParentException : public std::exception;
Asking a branch-node for its value will throw an exception:
class BranchNodeException : public std::exception;
Asking a leaf-node for a child node will throw an exception:
class LeafNodeException : public std::exception;
Reading a DTree from invalid input:
class InvalidDTreeXML : public std::exception;

DTree

  typedef DNode  
The type of the nodes of this tree.
  typedef index_t  
The type of indices in a branch-node.
  static const std::size_t N  
The number of children of a branch-node.
  DTree(const T value)  
Constructor with initial value for the root node. The root node is a leaf-node.
  DNode operator[](const index_t i)  
	throw(LeafNodeException)  
Obtain a child node of the root node. The root node must be a branch-node.
  const DNode operator[](const index_t i) const  
	throw(LeafNodeException)  
Obtain a read-only child node of the root node. The root node must be a branch-node.
  DNode root()  
Obtain the root node.
  operator DNode()  
Implicitly convert the tree to the root node.
  operator const DNode() const  
Implicitly convert the tree to the root node, read-only.
  T& operator()() throw(BranchNodeException)  
Explicitly convert the root node to a reference to the value it holds.
  const T& operator()() const throw(BranchNodeException)  
Explicitly convert the root node to a read-only reference to the value it holds.
  bool isLeaf() const  
Returns true if the root node is a leaf-node.
  operator const T&() const throw(BranchNodeException)  
Implicitly convert the root node into a read-only reference to its value. The root node must be a leaf-node.
  template <typename U>
  void collapse(const U value) throw(LeafNodeException)  
Turn the root node from a branch-node into a leaf-node with the given value, discarding all children.
  void expand() throw(BranchNodeException)  
Expand the root node from a leaf-node into a branch-node. The value of all children will be the current value of the leaf-node.
  template <typename U>
  void expand(const U value) throw(BranchNodeException)  
Expand the root node from a leaf-node into a branch-node. The value of all children will be the given value.
  template <typename It>
  void expand(It first, It last) throw(BranchNodeException)  
Expand the root node from a leaf-node into a branch-node. The value of all children will be read from the first elements of the range first to last.
  std::size_t depth() const  
Return the depth of the tree at the root node, which is by definition zero.
  bool operator==(const DTree &that) const  
Test if two DTrees refer to the same data - not if they actually contain the same data.
  bool operator!=(const DTree &that) const  
Negation of operator==.
  DTree<T, D> clone() const  
Return DTree containg a copy of this tree.
  std::size_t id() const  
Return a unique identifier associated with the root node. By definition, that is zero.
  const DNode retrieve(std::size_t id) const throw (LeafNodeException)  
Return the DNode associated with the unique identifier id. Providing an invalid identifier might cause an exception to be thrown.
  DNode retrieve(std::size_t id) throw (LeafNodeException)  
Return the DNode associated with the unique identifier id. Providing an invalid identifier might cause an exception to be thrown.

DTree::DNode

  typedef index_t  
The type of indices in a branch-node.
  static const std::size_t N  
The number of children of a branch-node.
  T& operator()() throw(BranchNodeException)  
Explicitly convert a node to a reference to the value it holds.
  const T& operator()() const throw(BranchNodeException)  
Explicitly convert a node to a read-only reference to the value it holds.
  DNode operator[](const index_t i)  
	throw(LeafNodeException)  
Obtain a child node of the node. The node must be a branch-node.
  const DNode operator[](const index_t i) const
	throw(LeafNodeException)  
Obtain a read-only child node of the node. The node must be a branch-node.
  bool isLeaf() const  
Returns true if the node is a leaf-node.
  template <typename U>
  void collapse(const U value) throw(LeafNodeException)  
Turn the node from a branch-node into a leaf-node with the given value, discarding all children.
  void expand() throw(BranchNodeException)  
Expand a leaf-node into a branch-node. The value of all children will be the current value of the leaf-node.
  template <typename U>
  void expand(const U value) throw(BranchNodeException)  
Expand a leaf-node into a branch-node. The value of all children will be the given value.
  template <typename It>
  void expand(It first, It last) throw(BranchNodeException)  
Expand a leaf-node into a branch-node. The value of all children will be read from the first elements of the range first to last.
  bool operator==(const DTree &that) const  
Test if to DNodes are equal DNodes frmo the same tree - not if they actually contain the same data.
  bool operator!=(const DTree &that) const  
Negation of operator==.
  index_t index() const throw (NoParentException)  
Return the index of this node in its parent node.
  std::size_t depth() const  
Return the depth of the tree at this node.
  DNode parent() throw (NoParentException)  
Return the parent node. The root node has no parent and requesting it will throw an exception.
  const DNode parent() const throw (NoParentException)  
Return the parent node, read-only. The root node has no parent and requesting it will throw an exception.
  DTree<T, D> clone() const  
Return DTree containg a copy of part of the tree using the copy of this node as its root.
  std::size_t id() const  
Return a unique identifier associated with this node.
  std::vector<index_t> index_trail() const  
Return a `trail' of indices to take from the root node to the current node.

Comparison

To support interaction with datatypes that require comparison, such as std::map, a less-than-comparable operator is defined on DNodes in a DTree. It compares the nodes by their id(), which guaratees that the values are unique if the nodes are part of the same tree.

  template <typename T, std::size_t D>
  bool operator<(const DTree<T, D>::DNode &lhs, const DTree<T, D>::DNode &rhs);  
Returns true if the id() of lhs is smaller than that of rhs.

I / O

There are output operators for both entire DTrees and DNodes. An input operator only exists for entire DTrees, as an input operator for DNodes could lead to inconsistencies.

  template <typename T, std::size_t D>
  std::istream& operator>>(std::istream& i_stream,  
			 DTree<T, D> &tree)
		throw(cvmlcpp::InvalidDTreeXML)  
Write a DTree to o_stream in formatted XML-like syntax.
  template <typename T, std::size_t D>
  std::ostream& operator<<(std::ostream& o_stream,  
		 const DNode<T, D> &node)  
Write a DNode, potentially with sub-nodes, to o_stream in formatted XML-like syntax.
  template <typename T, std::size_t D>
  std::istream& operator>>(std::istream& i_stream,  
			 DTree<T, D> &tree)
	throw(cvmlcpp::InvalidDTreeXML)  
Read a DTree in XML format from i_stream.

Examples

For an example of a DTree holding items from a class-hierarchy, see Holder.

A test-program

#include <cvmlcpp/volume/DTree>

int main()
{
	typedef cvmlcpp::DTree<int, 3> OctTreeInt;

	// Initialize the tree with a value
	OctTreeInt tree(7);
	assert(tree.root()() == 7);

	// The tree implicitly converts to its root node
	assert(tree.root() == tree);

	// Assign new value
	tree() = 1; // implicitly assigned to the root node
	tree.root()() = 5; // explicitly assigned to the root node
	assert(tree.root()() == 5);

	// Expanding a node without giving a value copies the current value
	// to all new children.
	tree.root().expand();
	for (unsigned i = 0; i < OctTreeInt::N; ++i)
		assert(tree[i]() == 5);

	// Change one leaf node of the root and expand it to a branch node.
	// The children of that node now all contain "3", the other children of
	// the root node still have their original value
	tree[0]() = 3;
	tree[0].expand();
	for (OctTreeInt::index_t i = 1; i < OctTreeInt::N; ++i)
		assert(tree[i]() == 5);
	for (OctTreeInt::index_t i = 0; i < OctTreeInt::N; ++i)
		assert(tree[0][i]() == 3);

	// Test if we can find a node by walking its trail of indices from
	// the root node.
	for (OctTreeInt::index_t i = 0; i < OctTreeInt::N; ++i)
	{
		const std::vector<OctTreeInt::index_t> trail = tree[0][i].index_trail();
		OctTreeInt::DNode node = tree.root();
		for (OctTreeInt::index_t j = 0; j < trail.size(); ++j)
			node = node[ trail[j] ];
		assert(node == tree[0][i]);
	}

	// Collapse the expanded node and assign it a value of 6
	tree[0].collapse(6);
	assert(tree[0]() == 6);

	// Collapse the whole tree and assign it a value of 6
	tree.collapse(4);
	assert(tree.root()() == 4);

	// Collapse nodes with grand-children
	tree.expand();
	tree[0].expand();
	tree[0][0]() = 2;
	tree.collapse(-2);
	assert(tree.root()() == -2);
	// Expand the tree, assign all children different values.
	int values [] = {0, 1, 2, 3, 4, 5, 6, 7};
	tree.expand(values, values+OctTreeInt::N);
	assert(tree[3]() == 3);

	// Grab a node in the tree
	typedef OctTreeInt::DNode DNode8i;
	DNode8i node = tree[3];

	// See if all is consistent
	assert(node != tree[2]);
	assert(node == tree[3]);
	assert(node.parent() == node.parent());
	assert(node.parent() == tree.root());
	assert(node != tree.root());

	// Wrong things should throw
	try { // Root node has no index!
		assert(node.parent().index() >= 0);
		assert(false); // previous should throw
	}
	catch(cvmlcpp::NoParentException &e) {
	}

	try { // Root node has no parent
		DNode8i n = node.parent().parent();
		assert(false); // previous should throw
	}
	catch(cvmlcpp::NoParentException &e) {
	}

	// Expand the node and fill with N different values
	assert(node.isLeaf());
	node.expand(values, values+OctTreeInt::N);
	assert(!node.isLeaf());

	// Verify that the i-th child of the node is the i-th child of the
	// 3th child of the root.
	for (OctTreeInt::index_t i = 0; i < OctTreeInt::N; ++i)
	{
		// This compares the nodes - NOT the values
		assert(node[i] == tree[3][i]);

		// Although the nodes are different ...
		assert(node[i] != tree[i]);

		// ... the values should be identical, except the expanded node.
		if (i != 3)
		{
			// A value is obtained with operator()
			assert(node[i]() == tree[i]());
		}
	}

	// Copying data
	OctTreeInt tree2 = tree.clone(); // Full tree copy
	assert(tree2 != tree);

	OctTreeInt tree3 = tree[3].clone(); // Partial tree copy;
	for (OctTreeInt::index_t i = 0; i < OctTreeInt::N; ++i)
	{
		assert(tree [3][i]() == tree2[3][i]()); // all values equal
		assert(tree3[i]() == tree2[3][i]()); // all values equal
		assert(tree3[i].index() == tree2[3][i].index());
		assert(tree3[i].index() == int(i));
		assert(tree [3][i].depth() == 2u);
		assert(tree2[3][i].depth() == 2u);
		assert(tree3[i].depth() == 1u);
	}

	// Nodes can be identified with their ID. This ID indicates a
	// position, it is valid in a copy of the tree.
	for (OctTreeInt::index_t i = 0; i < OctTreeInt::N; ++i)
	{
		// Can we find ourselves ?
		assert( tree[3][i] == tree.retrieve(tree[3][i].id()) );

		// Equal in other tree (by value) ?
		assert( tree[3][i]() == tree2.retrieve(tree[3][i].id())() );
	}

	// I/O
	std::ofstream out("/tmp/tree.xml");
	out << tree;
	out.close();

	OctTreeInt tree4;
	std::ifstream in("/tmp/tree.xml");
	in >> tree4;
	in.close();
	// Equal to other tree (by value) ?
	for (OctTreeInt::index_t i = 0; i < OctTreeInt::N; ++i)
	{
		if (i != 3)
			assert( tree[i]() == tree4.retrieve(tree[i].id())() );
		assert( tree[3][i]() == tree4.retrieve(tree[3][i].id())() );
	}

	return 0;
}

A DTree in XML format.

The XML fragment below encodes a DTree<int, 3>, i.e. an Octree filled with integers:

<branch>
        <leaf>
                <index> 0 </index>
                <value> 0 </value>
        </leaf>
        <leaf>
                <index> 1 </index>
                <value> 1 </value>
        </leaf>
        <leaf>
                <index> 2 </index>
                <value> 2 </value>
        </leaf>
        <branch>
                <index> 3 </index>
                <leaf>
                        <index> 0 </index>
                        <value> 0 </value>
                </leaf>
                <leaf>
                        <index> 1 </index>
                        <value> 1 </value>
                </leaf>
                <leaf>
                        <index> 2 </index>
                        <value> 2 </value>
                </leaf>
                <leaf>
                        <index> 3 </index>
                        <value> 3 </value>
                </leaf>
 		<leaf>
			<index> 4 </index>
			<value> 4 </value>
		</leaf>
		<leaf>
			<index> 5 </index>
			<value> 5 </value>
		</leaf>
		<leaf>
			<index> 6 </index>
			<value> 6 </value>
		</leaf>
		<leaf>
			<index> 7 </index>
			<value> 7 </value>
		</leaf>
	</branch>
	<leaf>
		<index> 4 </index>
		<value> 4 </value>
	</leaf>
	<leaf>
		<index> 5 </index>
		<value> 5 </value>
	</leaf>
	<leaf>
		<index> 6 </index>
		<value> 6 </value>
	</leaf>
	<leaf>
		<index> 7 </index>
		<value> 7 </value>
	</leaf>
</branch>
Note that the format is not fully XML-compliant:
  • the introductory "Doctype" header etc. is missing;
  • spaces are required between tags and the values between them;
  • a DTD is missing.