Refactor to hold dataset internally (for serialization).
This commit is contained in:
@@ -24,8 +24,7 @@ namespace tree /** Trees and tree-building procedures. */ {
|
||||
* the constructor with the dataset to build the tree on, and the entire tree
|
||||
* will be built.
|
||||
*
|
||||
* This tree does allow growth, so you can add and delete nodes
|
||||
* from it.
|
||||
* This tree does allow growth, so you can add and delete nodes from it.
|
||||
*
|
||||
* @tparam MetricType This *must* be EuclideanDistance, but the template
|
||||
* parameter is required to satisfy the TreeType API.
|
||||
@@ -98,7 +97,10 @@ class RectangleTree
|
||||
//! The discance to the furthest descendant, cached to speed things up.
|
||||
double furthestDescendantDistance;
|
||||
//! The dataset.
|
||||
const MatType& dataset;
|
||||
const MatType* dataset;
|
||||
//! Whether or not we are responsible for deleting the dataset. This is
|
||||
//! probably not aligned well...
|
||||
bool ownsDataset;
|
||||
//! The mapping to the dataset
|
||||
std::vector<size_t> points;
|
||||
//! The local dataset
|
||||
@@ -155,6 +157,14 @@ class RectangleTree
|
||||
*/
|
||||
RectangleTree(const RectangleTree& other, const bool deepCopy = true);
|
||||
|
||||
/**
|
||||
* Construct the tree from a boost::serialization archive.
|
||||
*/
|
||||
template<typename Archive>
|
||||
RectangleTree(
|
||||
Archive& ar,
|
||||
const typename boost::enable_if<typename Archive::is_loading>::type* = 0);
|
||||
|
||||
/**
|
||||
* Deletes this node, deallocating the memory for the children and calling
|
||||
* their destructors in turn. This will invalidate any younters or references
|
||||
@@ -307,9 +317,9 @@ class RectangleTree
|
||||
RectangleTree*& Parent() { return parent; }
|
||||
|
||||
//! Get the dataset which the tree is built on.
|
||||
const MatType& Dataset() const { return dataset; }
|
||||
const MatType& Dataset() const { return *dataset; }
|
||||
//! Modify the dataset which the tree is built on. Be careful!
|
||||
MatType& Dataset() { return const_cast<MatType&>(dataset); }
|
||||
MatType& Dataset() { return const_cast<MatType&>(*dataset); }
|
||||
|
||||
//! Get the points vector for this node.
|
||||
const std::vector<size_t>& Points() const { return points; }
|
||||
@@ -511,6 +521,18 @@ class RectangleTree
|
||||
*/
|
||||
void SplitNode(std::vector<bool>& relevels);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* A default constructor. This is meant to only be used with
|
||||
* boost::serialization, which is allowed with the friend declaration below.
|
||||
* This does not return a valid tree! This method must be protected, so that
|
||||
* the serialization shim can work with the default constructor.
|
||||
*/
|
||||
RectangleTree();
|
||||
|
||||
//! Friend access is given for the default constructor.
|
||||
friend class boost::serialization::access;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Condense the bounding rectangles for this node based on the removal of the
|
||||
|
||||
@@ -41,7 +41,8 @@ RectangleTree(const MatType& data,
|
||||
bound(data.n_rows),
|
||||
splitHistory(bound.Dim()),
|
||||
parentDistance(0),
|
||||
dataset(data),
|
||||
dataset(new MatType(data)),
|
||||
ownsDataset(true),
|
||||
points(maxLeafSize + 1), // Add one to make splitting the node simpler.
|
||||
localDataset(new MatType(data.n_rows, static_cast<int> (maxLeafSize) + 1))
|
||||
{
|
||||
@@ -75,7 +76,8 @@ RectangleTree(
|
||||
bound(parentNode->Bound().Dim()),
|
||||
splitHistory(bound.Dim()),
|
||||
parentDistance(0),
|
||||
dataset(parentNode->Dataset()),
|
||||
dataset(&parentNode->Dataset()),
|
||||
ownsDataset(false),
|
||||
points(maxLeafSize + 1), // Add one to make splitting the node simpler.
|
||||
localDataset(new MatType(static_cast<int> (parentNode->Bound().Dim()),
|
||||
static_cast<int> (maxLeafSize) + 1))
|
||||
@@ -108,7 +110,8 @@ RectangleTree(
|
||||
bound(other.bound),
|
||||
splitHistory(other.SplitHistory()),
|
||||
parentDistance(other.ParentDistance()),
|
||||
dataset(other.dataset),
|
||||
dataset(new MatType(*other.dataset)),
|
||||
ownsDataset(true),
|
||||
points(other.Points()),
|
||||
localDataset(NULL)
|
||||
{
|
||||
@@ -134,6 +137,25 @@ RectangleTree(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the tree from a boost::serialization archive.
|
||||
*/
|
||||
template<typename MetricType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
typename SplitType,
|
||||
typename DescentType>
|
||||
template<typename Archive>
|
||||
RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>::
|
||||
RectangleTree(
|
||||
Archive& ar,
|
||||
const typename boost::enable_if<typename Archive::is_loading>::type*) :
|
||||
RectangleTree() // Use default constructor.
|
||||
{
|
||||
// Now serialize.
|
||||
ar >> data::CreateNVP(*this, "tree");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this node, deallocating the memory for the children and calling
|
||||
* their destructors in turn. This will invalidate any pointers or references
|
||||
@@ -150,6 +172,9 @@ RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>::
|
||||
for (size_t i = 0; i < numChildren; i++)
|
||||
delete children[i];
|
||||
|
||||
if (ownsDataset)
|
||||
delete dataset;
|
||||
|
||||
delete localDataset;
|
||||
}
|
||||
|
||||
@@ -201,7 +226,7 @@ void RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>::
|
||||
InsertPoint(const size_t point)
|
||||
{
|
||||
// Expand the bound regardless of whether it is a leaf node.
|
||||
bound |= dataset.col(point);
|
||||
bound |= dataset->col(point);
|
||||
|
||||
std::vector<bool> lvls(TreeDepth());
|
||||
for (size_t i = 0; i < lvls.size(); i++)
|
||||
@@ -210,7 +235,7 @@ void RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>::
|
||||
// If this is a leaf node, we stop here and add the point.
|
||||
if (numChildren == 0)
|
||||
{
|
||||
localDataset->col(count) = dataset.col(point);
|
||||
localDataset->col(count) = dataset->col(point);
|
||||
points[count++] = point;
|
||||
SplitNode(lvls);
|
||||
return;
|
||||
@@ -219,7 +244,7 @@ void RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>::
|
||||
// If it is not a leaf node, we use the DescentHeuristic to choose a child
|
||||
// to which we recurse.
|
||||
const size_t descentNode = DescentType::ChooseDescentNode(this,
|
||||
dataset.col(point));
|
||||
dataset->col(point));
|
||||
children[descentNode]->InsertPoint(point, lvls);
|
||||
}
|
||||
|
||||
@@ -238,12 +263,12 @@ void RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>::
|
||||
InsertPoint(const size_t point, std::vector<bool>& relevels)
|
||||
{
|
||||
// Expand the bound regardless of whether it is a leaf node.
|
||||
bound |= dataset.col(point);
|
||||
bound |= dataset->col(point);
|
||||
|
||||
// If this is a leaf node, we stop here and add the point.
|
||||
if (numChildren == 0)
|
||||
{
|
||||
localDataset->col(count) = dataset.col(point);
|
||||
localDataset->col(count) = dataset->col(point);
|
||||
points[count++] = point;
|
||||
SplitNode(relevels);
|
||||
return;
|
||||
@@ -252,7 +277,7 @@ void RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>::
|
||||
// If it is not a leaf node, we use the DescentHeuristic to choose a child
|
||||
// to which we recurse.
|
||||
const size_t descentNode = DescentType::ChooseDescentNode(this,
|
||||
dataset.col(point));
|
||||
dataset->col(point));
|
||||
children[descentNode]->InsertPoint(point, relevels);
|
||||
}
|
||||
|
||||
@@ -320,14 +345,14 @@ bool RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>::
|
||||
localDataset->col(i) = localDataset->col(--count); // Decrement count.
|
||||
points[i] = points[count];
|
||||
// This function wil ensure that minFill is satisfied.
|
||||
CondenseTree(dataset.col(point), lvls, true);
|
||||
CondenseTree(dataset->col(point), lvls, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < numChildren; i++)
|
||||
if (children[i]->Bound().Contains(dataset.col(point)))
|
||||
if (children[i]->Bound().Contains(dataset->col(point)))
|
||||
if (children[i]->DeletePoint(point, lvls))
|
||||
return true;
|
||||
|
||||
@@ -355,14 +380,14 @@ bool RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>::
|
||||
localDataset->col(i) = localDataset->col(--count);
|
||||
points[i] = points[count];
|
||||
// This function will ensure that minFill is satisfied.
|
||||
CondenseTree(dataset.col(point), relevels, true);
|
||||
CondenseTree(dataset->col(point), relevels, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < numChildren; i++)
|
||||
if (children[i]->Bound().Contains(dataset.col(point)))
|
||||
if (children[i]->Bound().Contains(dataset->col(point)))
|
||||
if (children[i]->DeletePoint(point, relevels))
|
||||
return true;
|
||||
|
||||
@@ -591,6 +616,31 @@ void RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>::
|
||||
}
|
||||
}
|
||||
|
||||
//! Default constructor for boost::serialization.
|
||||
template<typename MetricType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
typename SplitType,
|
||||
typename DescentType>
|
||||
RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>::
|
||||
RectangleTree() :
|
||||
maxNumChildren(0), // Try to give sensible defaults, but it shouldn't matter
|
||||
minNumChildren(0), // because this tree isn't valid anyway and is only used
|
||||
numChildren(0), // by boost::serialization.
|
||||
parent(NULL),
|
||||
begin(0),
|
||||
count(0),
|
||||
maxLeafSize(0),
|
||||
minLeafSize(0),
|
||||
parentDistance(0.0),
|
||||
furthestDescendantDistance(0.0),
|
||||
dataset(NULL),
|
||||
ownsDataset(false),
|
||||
localDataset(NULL)
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Condense the tree. This shrinks the bounds and moves up the tree if
|
||||
* applicable. If a node goes below minimum fill, this code will deal with it.
|
||||
|
||||
@@ -467,12 +467,16 @@ BOOST_AUTO_TEST_CASE(PointDynamicAdd)
|
||||
arma::mat> TreeType;
|
||||
TreeType tree(dataset, 20, 6, 5, 2, 0);
|
||||
|
||||
// Add numIter new points to the dataset.
|
||||
// Add numIter new points to the dataset. The tree copies the dataset, so we
|
||||
// must modify both the original dataset and the one that the tree holds.
|
||||
// (This API is clunky. It should be redone sometime.)
|
||||
tree.Dataset().reshape(8, 1000 + numIter);
|
||||
dataset.reshape(8, 1000 + numIter);
|
||||
arma::mat tmpData;
|
||||
tmpData.randu(8, numIter);
|
||||
for (int i = 0; i < numIter; i++)
|
||||
{
|
||||
tree.Dataset().col(1000 + i) = tmpData.col(i);
|
||||
dataset.col(1000 + i) = tmpData.col(i);
|
||||
tree.InsertPoint(1000 + i);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user