diff --git a/src/mlpack/core/tree/CMakeLists.txt b/src/mlpack/core/tree/CMakeLists.txt index 0399e84cda..a7c62e49d6 100644 --- a/src/mlpack/core/tree/CMakeLists.txt +++ b/src/mlpack/core/tree/CMakeLists.txt @@ -61,6 +61,20 @@ set(SOURCES rectangle_tree/hilbert_r_tree_auxiliary_information_impl.hpp rectangle_tree/discrete_hilbert_value.hpp rectangle_tree/discrete_hilbert_value_impl.hpp + rectangle_tree/r_plus_tree_descent_heuristic.hpp + rectangle_tree/r_plus_tree_descent_heuristic_impl.hpp + rectangle_tree/minimal_coverage_sweep.hpp + rectangle_tree/minimal_coverage_sweep_impl.hpp + rectangle_tree/minimal_splits_number_sweep.hpp + rectangle_tree/minimal_splits_number_sweep_impl.hpp + rectangle_tree/r_plus_tree_split.hpp + rectangle_tree/r_plus_tree_split_impl.hpp + rectangle_tree/r_plus_tree_split_policy.hpp + rectangle_tree/r_plus_plus_tree_descent_heuristic.hpp + rectangle_tree/r_plus_plus_tree_descent_heuristic_impl.hpp + rectangle_tree/r_plus_plus_tree_split_policy.hpp + rectangle_tree/r_plus_plus_tree_auxiliary_information.hpp + rectangle_tree/r_plus_plus_tree_auxiliary_information_impl.hpp statistic.hpp traversal_info.hpp tree_traits.hpp diff --git a/src/mlpack/core/tree/hrectbound.hpp b/src/mlpack/core/tree/hrectbound.hpp index fb189c182a..7a0823ff33 100644 --- a/src/mlpack/core/tree/hrectbound.hpp +++ b/src/mlpack/core/tree/hrectbound.hpp @@ -182,6 +182,26 @@ class HRectBound template bool Contains(const VecType& point) const; + /** + * Determines if this bound partially contains a bound. + */ + bool Contains(const HRectBound& bound) const; + + /** + * Returns the intersection of this bound and another. + */ + HRectBound operator&(const HRectBound& bound) const; + + /** + * Intersects this bound with another. + */ + HRectBound& operator&=(const HRectBound& bound); + + /** + * Returns the volume of overlap of this bound and another. + */ + ElemType Overlap(const HRectBound& bound) const; + /** * Returns the diameter of the hyperrectangle (that is, the longest diagonal). */ diff --git a/src/mlpack/core/tree/hrectbound_impl.hpp b/src/mlpack/core/tree/hrectbound_impl.hpp index 822877c9b8..ccfa0265b4 100644 --- a/src/mlpack/core/tree/hrectbound_impl.hpp +++ b/src/mlpack/core/tree/hrectbound_impl.hpp @@ -143,7 +143,12 @@ inline ElemType HRectBound::Volume() const { ElemType volume = 1.0; for (size_t i = 0; i < dim; ++i) + { + if (bounds[i].Lo() >= bounds[i].Hi()) + return 0; + volume *= (bounds[i].Hi() - bounds[i].Lo()); + } return volume; } @@ -430,6 +435,79 @@ inline bool HRectBound::Contains(const VecType& point) con return true; } +/** + * Determines if this bound partially contains a bound. + */ +template +inline bool HRectBound::Contains( + const HRectBound& bound) const +{ + for (size_t i = 0; i < dim; i++) + { + const math::RangeType& r_a = bounds[i]; + const math::RangeType& r_b = bound.bounds[i]; + + if (r_a.Hi() <= r_b.Lo() || r_a.Lo() >= r_b.Hi()) // If a does not overlap b at all. + return false; + } + + return true; +} + +/** + * Returns the intersection of this bound and another. + */ +template +inline HRectBound HRectBound:: +operator&(const HRectBound& bound) const +{ + HRectBound result(dim); + + for (size_t k = 0; k < dim; k++) + { + result[k].Lo() = std::max(bounds[k].Lo(), bound.bounds[k].Lo()); + result[k].Hi() = std::min(bounds[k].Hi(), bound.bounds[k].Hi()); + } + return result; +} + +/** + * Intersects this bound with another. + */ +template +inline HRectBound& HRectBound:: +operator&=(const HRectBound& bound) +{ + for (size_t k = 0; k < dim; k++) + { + bounds[k].Lo() = std::max(bounds[k].Lo(), bound.bounds[k].Lo()); + bounds[k].Hi() = std::min(bounds[k].Hi(), bound.bounds[k].Hi()); + } + return *this; +} + +/** + * Returns the volume of overlap of this bound and another. + */ +template +inline ElemType HRectBound::Overlap( + const HRectBound& bound) const +{ + ElemType volume = 1.0; + + for (size_t k = 0; k < dim; k++) + { + ElemType lo = std::max(bounds[k].Lo(), bound.bounds[k].Lo()); + ElemType hi = std::min(bounds[k].Hi(), bound.bounds[k].Hi()); + + if ( hi <= lo) + return 0; + + volume *= hi - lo; + } + return volume; +} + /** * Returns the diameter of the hyperrectangle (that is, the longest diagonal). */ diff --git a/src/mlpack/core/tree/rectangle_tree.hpp b/src/mlpack/core/tree/rectangle_tree.hpp index c2ff9bb44b..6ed68ecdd5 100644 --- a/src/mlpack/core/tree/rectangle_tree.hpp +++ b/src/mlpack/core/tree/rectangle_tree.hpp @@ -29,6 +29,14 @@ #include "rectangle_tree/hilbert_r_tree_split.hpp" #include "rectangle_tree/hilbert_r_tree_auxiliary_information.hpp" #include "rectangle_tree/discrete_hilbert_value.hpp" +#include "rectangle_tree/r_plus_tree_descent_heuristic.hpp" +#include "rectangle_tree/r_plus_tree_split_policy.hpp" +#include "rectangle_tree/minimal_coverage_sweep.hpp" +#include "rectangle_tree/minimal_splits_number_sweep.hpp" +#include "rectangle_tree/r_plus_tree_split.hpp" +#include "rectangle_tree/r_plus_plus_tree_auxiliary_information.hpp" +#include "rectangle_tree/r_plus_plus_tree_descent_heuristic.hpp" +#include "rectangle_tree/r_plus_plus_tree_split_policy.hpp" #include "rectangle_tree/typedef.hpp" #endif diff --git a/src/mlpack/core/tree/rectangle_tree/hilbert_r_tree_split_impl.hpp b/src/mlpack/core/tree/rectangle_tree/hilbert_r_tree_split_impl.hpp index 7742e9dda8..5fa7911c2d 100644 --- a/src/mlpack/core/tree/rectangle_tree/hilbert_r_tree_split_impl.hpp +++ b/src/mlpack/core/tree/rectangle_tree/hilbert_r_tree_split_impl.hpp @@ -19,6 +19,8 @@ template void HilbertRTreeSplit::SplitLeafNode(TreeType* tree, std::vector& relevels) { + if (tree->Count() <= tree->MaxLeafSize()) + return; // If we are splitting the root node, we need will do things differently so // that the constructor and other methods don't confuse the end user by giving // an address of another node. diff --git a/src/mlpack/core/tree/rectangle_tree/minimal_coverage_sweep.hpp b/src/mlpack/core/tree/rectangle_tree/minimal_coverage_sweep.hpp new file mode 100644 index 0000000000..ab2f664dc5 --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/minimal_coverage_sweep.hpp @@ -0,0 +1,97 @@ +/** + * @file minimal_coverage_sweep.hpp + * @author Mikhail Lozhnikov + * + * Definition of the MinimalCoverageSweep class, a class that finds a partition + * of a node along an axis. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_MINIMAL_COVERAGE_SWEEP_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_MINIMAL_COVERAGE_SWEEP_HPP + +namespace mlpack { +namespace tree { + +/** + * The MinimalCoverageSweep class finds a partition along which we + * can split a node according to the coverage of two resulting nodes. The class + * finds a partition along a given axis. Moreover, the class evaluates the cost + * of each split. The cost is proportional to the total coverage of resulting + * nodes. If the resulting nodes are overflowed the maximum cost is returned. + * + * @tparam SplitPolicy The class that provides rules for inserting children of + * a node that is being split into two new subtrees. + */ +template +class MinimalCoverageSweep +{ + public: + //! A struct that provides the type of the sweep cost. + template + struct SweepCost + { + typedef typename TreeType::ElemType type; + }; + + /** + * Find a suitable partition of a non-leaf node along the provided axis. + * The method returns the cost of the split. + * + * @param axis The axis along which we are finding a partition. + * @param node The node that is being split. + * @param axisCut The coordinate at which the node may be split. + */ + template + static typename TreeType::ElemType SweepNonLeafNode( + const size_t axis, + const TreeType* node, + typename TreeType::ElemType& axisCut); + + /** + * Find a suitable partition of a leaf node along the provided axis. + * The method returns the cost of the split. + * + * @param axis The axis along which we are finding a partition. + * @param node The node that is being split. + * @param axisCut The coordinate at which the node may be split. + */ + template + static typename TreeType::ElemType SweepLeafNode( + const size_t axis, + const TreeType* node, + typename TreeType::ElemType& axisCut); + + /** + * Check if an intermediate node can be split along the axis at the provided + * coordinate. + * + * @param node The node that is being split. + * @param cutAxis The axis that we want to check. + * @param cut The coordinate that we want to check. + */ + template + static bool CheckNonLeafSweep(const TreeType* node, + const size_t cutAxis, + const ElemType cut); + + /** + * Check if a leaf node can be split along the axis at the provided + * coordinate. + * + * @param node The node that is being split. + * @param cutAxis The axis that we want to check. + * @param cut The coordinate that we want to check. + */ + template + static bool CheckLeafSweep(const TreeType* node, + const size_t cutAxis, + const ElemType cut); +}; + +} // namespace tree +} // namespace mlpack + +// Include implementation +#include "minimal_coverage_sweep_impl.hpp" + +#endif // MLPACK_CORE_TREE_RECTANGLE_TREE_MINIMAL_COVERAGE_SWEEP_HPP + diff --git a/src/mlpack/core/tree/rectangle_tree/minimal_coverage_sweep_impl.hpp b/src/mlpack/core/tree/rectangle_tree/minimal_coverage_sweep_impl.hpp new file mode 100644 index 0000000000..66ceec53ad --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/minimal_coverage_sweep_impl.hpp @@ -0,0 +1,194 @@ +/** + * @file minimal_coverage_sweep_impl.hpp + * @author Mikhail Lozhnikov + * + * Implementation of the MinimalCoverageSweep class, a class that finds a + * partition of a node along an axis. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_MINIMAL_COVERAGE_SWEEP_IMPL_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_MINIMAL_COVERAGE_SWEEP_IMPL_HPP + +#include "minimal_coverage_sweep.hpp" + +namespace mlpack { +namespace tree { + +template +template +typename TreeType::ElemType MinimalCoverageSweep:: +SweepNonLeafNode(const size_t axis, + const TreeType* node, + typename TreeType::ElemType& axisCut) +{ + typedef typename TreeType::ElemType ElemType; + typedef bound::HRectBound BoundType; + + std::vector> sorted(node->NumChildren()); + + for (size_t i = 0; i < node->NumChildren(); i++) + { + sorted[i].first = SplitPolicy::Bound(node->Child(i))[axis].Hi(); + sorted[i].second = i; + } + // Sort high bounds of children. + std::sort(sorted.begin(), sorted.end(), + [] (const std::pair& s1, + const std::pair& s2) + { + return s1.first < s2.first; + }); + + size_t splitPointer = node->NumChildren() / 2; + + axisCut = sorted[splitPointer - 1].first; + + // Check if the midpoint split is suitable. + if (!CheckNonLeafSweep(node, axis, axisCut)) + { + // Find any suitable partition if the default partition is not acceptable. + for (splitPointer = 1; splitPointer < sorted.size(); splitPointer++) + { + axisCut = sorted[splitPointer - 1].first; + if (CheckNonLeafSweep(node, axis, axisCut)) + break; + } + + if (splitPointer == node->NumChildren()) + return std::numeric_limits::max(); + } + + BoundType bound1(node->Bound().Dim()); + BoundType bound2(node->Bound().Dim()); + + // Find bounds of two resulting nodes. + for (size_t i = 0; i < splitPointer; i++) + bound1 |= node->Child(sorted[i].second).Bound(); + + for (size_t i = splitPointer; i < node->NumChildren(); i++) + bound2 |= node->Child(sorted[i].second).Bound(); + + + // Evaluate the cost of the split i.e. calculate the total coverage + // of two resulting nodes. + + ElemType area1 = bound1.Volume(); + ElemType area2 = bound2.Volume(); + + return area1 + area2; +} + +template +template +typename TreeType::ElemType MinimalCoverageSweep:: +SweepLeafNode(const size_t axis, + const TreeType* node, + typename TreeType::ElemType& axisCut) +{ + typedef typename TreeType::ElemType ElemType; + typedef bound::HRectBound BoundType; + + std::vector> sorted(node->Count()); + + sorted.resize(node->Count()); + + for (size_t i = 0; i < node->NumPoints(); i++) + { + sorted[i].first = node->Dataset().col(node->Point(i))[axis]; + sorted[i].second = i; + } + + // Sort high bounds of children. + std::sort(sorted.begin(), sorted.end(), + [] (const std::pair& s1, + const std::pair& s2) + { + return s1.first < s2.first; + }); + + size_t splitPointer = node->Count() / 2; + + axisCut = sorted[splitPointer - 1].first; + + // Check if the partition is suitable. + if (!CheckLeafSweep(node, axis, axisCut)) + return std::numeric_limits::max(); + + BoundType bound1(node->Bound().Dim()); + BoundType bound2(node->Bound().Dim()); + + // Find bounds of two resulting nodes. + for (size_t i = 0; i < splitPointer; i++) + bound1 |= node->Dataset().col(node->Point(sorted[i].second)); + + for (size_t i = splitPointer; i < node->NumChildren(); i++) + bound2 |= node->Dataset().col(node->Point(sorted[i].second)); + + // Evaluate the cost of the split i.e. calculate the total coverage + // of two resulting nodes. + + return bound1.Volume() + bound2.Volume(); +} + +template +template +bool MinimalCoverageSweep:: +CheckNonLeafSweep(const TreeType* node, + const size_t cutAxis, + const ElemType cut) +{ + size_t numTreeOneChildren = 0; + size_t numTreeTwoChildren = 0; + + // Calculate the number of children in the resulting nodes. + for (size_t i = 0; i < node->NumChildren(); i++) + { + const TreeType& child = node->Child(i); + int policy = SplitPolicy::GetSplitPolicy(child, cutAxis, cut); + if (policy == SplitPolicy::AssignToFirstTree) + numTreeOneChildren++; + else if (policy == SplitPolicy::AssignToSecondTree) + numTreeTwoChildren++; + else + { + // The split is required. + numTreeOneChildren++; + numTreeTwoChildren++; + } + } + + if (numTreeOneChildren <= node->MaxNumChildren() && numTreeOneChildren > 0 && + numTreeTwoChildren <= node->MaxNumChildren() && numTreeTwoChildren > 0) + return true; + return false; +} + +template +template +bool MinimalCoverageSweep:: +CheckLeafSweep(const TreeType* node, + const size_t cutAxis, + const ElemType cut) +{ + size_t numTreeOnePoints = 0; + size_t numTreeTwoPoints = 0; + + // Calculate the number of points in the resulting nodes. + for (size_t i = 0; i < node->NumPoints(); i++) + { + if (node->Dataset().col(node->Point(i))[cutAxis] <= cut) + numTreeOnePoints++; + else + numTreeTwoPoints++; + } + + if (numTreeOnePoints <= node->MaxLeafSize() && numTreeOnePoints > 0 && + numTreeTwoPoints <= node->MaxLeafSize() && numTreeTwoPoints > 0) + return true; + return false; +} + +} // namespace tree +} // namespace mlpack + +#endif // MLPACK_CORE_TREE_RECTANGLE_TREE_MINIMAL_COVERAGE_SWEEP_IMPL_HPP + diff --git a/src/mlpack/core/tree/rectangle_tree/minimal_splits_number_sweep.hpp b/src/mlpack/core/tree/rectangle_tree/minimal_splits_number_sweep.hpp new file mode 100644 index 0000000000..7134db8d3d --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/minimal_splits_number_sweep.hpp @@ -0,0 +1,73 @@ +/** + * @file minimal_splits_number_sweep.hpp + * @author Mikhail Lozhnikov + * + * Definition of the MinimalSplitsNumberSweep class, a class that finds a + * partition of a node along an axis. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_MINIMAL_SPLITS_NUMBER_SWEEP_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_MINIMAL_SPLITS_NUMBER_SWEEP_HPP + +namespace mlpack { +namespace tree { + +/** + * The MinimalSplitsNumberSweep class finds a partition along which we + * can split a node according to the number of required splits of the node. + * The class finds a partition along a given axis. Moreover, the class evaluates + * the cost of each split. The cost is proportional to the number of required + * splits and the difference of sizes of resulting nodes. If the resulting nodes + * are overflowed the maximum cost is returned. + * + * @tparam SplitPolicy The class that provides rules for inserting children of + * a node that is being split into two new subtrees. + */ +template +class MinimalSplitsNumberSweep +{ + public: + //! A struct that provides the type of the sweep cost. + template + struct SweepCost + { + typedef size_t type; + }; + + /** + * Find a suitable partition of a non-leaf node along the provided axis. + * The method returns the cost of the split. + * + * @param axis The axis along which we are finding a partition. + * @param node The node that is being split. + * @param axisCut The coordinate at which the node may be split. + */ + template + static size_t SweepNonLeafNode( + const size_t axis, + const TreeType* node, + typename TreeType::ElemType& axisCut); + + /** + * Find a suitable partition of a leaf node along the provided axis. + * The method returns the cost of the split. + * + * @param axis The axis along which we are finding a partition. + * @param node The node that is being split. + * @param axisCut The coordinate at which the node may be split. + */ + template + static size_t SweepLeafNode( + const size_t axis, + const TreeType* node, + typename TreeType::ElemType& axisCut); +}; + +} // namespace tree +} // namespace mlpack + +// Include implementation +#include "minimal_splits_number_sweep_impl.hpp" + +#endif // MLPACK_CORE_TREE_RECTANGLE_TREE_MINIMAL_SPLITS_NUMBER_SWEEP_HPP + + diff --git a/src/mlpack/core/tree/rectangle_tree/minimal_splits_number_sweep_impl.hpp b/src/mlpack/core/tree/rectangle_tree/minimal_splits_number_sweep_impl.hpp new file mode 100644 index 0000000000..f320ac43d5 --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/minimal_splits_number_sweep_impl.hpp @@ -0,0 +1,112 @@ +/** + * @file minimal_splits_number_sweep_impl.hpp + * @author Mikhail Lozhnikov + * + * Implementation of the MinimalSplitsNumberSweep class, a class that finds a + * partition of a node along an axis. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_MINIMAL_SPLITS_NUMBER_SWEEP_IMPL_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_MINIMAL_SPLITS_NUMBER_SWEEP_IMPL_HPP + +#include "minimal_splits_number_sweep.hpp" + +namespace mlpack { +namespace tree { + +template +template +size_t MinimalSplitsNumberSweep::SweepNonLeafNode( + const size_t axis, + const TreeType* node, + typename TreeType::ElemType& axisCut) +{ + typedef typename TreeType::ElemType ElemType; + + std::vector> sorted(node->NumChildren()); + + for (size_t i = 0; i < node->NumChildren(); i++) + { + sorted[i].first = SplitPolicy::Bound(node->Child(i))[axis].Hi(); + sorted[i].second = i; + } + + // Sort candidates in order to check balancing. + std::sort(sorted.begin(), sorted.end(), + [] (const std::pair& s1, + const std::pair& s2) + { + return s1.first < s2.first; + }); + + size_t minCost = SIZE_MAX; + + // Find a split with the minimal cost. + for (size_t i = 0; i < sorted.size(); i++) + { + size_t numTreeOneChildren = 0; + size_t numTreeTwoChildren = 0; + size_t numSplits = 0; + + // Calculate the number of splits. + for (size_t j = 0; j < node->NumChildren(); j++) + { + const TreeType& child = node->Child(j); + int policy = SplitPolicy::GetSplitPolicy(child, axis, sorted[i].first); + if (policy == SplitPolicy::AssignToFirstTree) + numTreeOneChildren++; + else if (policy == SplitPolicy::AssignToSecondTree) + numTreeTwoChildren++; + else + { + numTreeOneChildren++; + numTreeTwoChildren++; + numSplits++; + } + } + + // Check if the split is possible. + if (numTreeOneChildren <= node->MaxNumChildren() && numTreeOneChildren > 0 && + numTreeTwoChildren <= node->MaxNumChildren() && numTreeTwoChildren > 0) + { + // Evaluate the cost using the number of splits and balancing. + size_t balance; + + if (sorted.size() / 2 > i ) + balance = sorted.size() / 2 - i; + else + balance = i - sorted.size() / 2; + + size_t cost = numSplits * balance; + if (cost < minCost) + { + minCost = cost; + axisCut = sorted[i].first; + } + } + } + return minCost; +} + +template +template +size_t MinimalSplitsNumberSweep::SweepLeafNode( + const size_t axis, + const TreeType* node, + typename TreeType::ElemType& axisCut) +{ + // Split along the median. + axisCut = (node->Bound()[axis].Lo() + node->Bound()[axis].Hi()) * 0.5; + + if (node->Bound()[axis].Lo() == axisCut) + return SIZE_MAX; + + return 0; +} + + +} // namespace tree +} // namespace mlpack + +#endif // MLPACK_CORE_TREE_RECTANGLE_TREE_MINIMAL_SPLITS_NUMBER_SWEEP_IMPL_HPP + + diff --git a/src/mlpack/core/tree/rectangle_tree/no_auxiliary_information.hpp b/src/mlpack/core/tree/rectangle_tree/no_auxiliary_information.hpp index e3b5fd455b..e282a4cd11 100644 --- a/src/mlpack/core/tree/rectangle_tree/no_auxiliary_information.hpp +++ b/src/mlpack/core/tree/rectangle_tree/no_auxiliary_information.hpp @@ -32,7 +32,7 @@ class NoAuxiliaryInformation * @param node The node in which the point is being inserted. * @param point The global number of the point being inserted. */ - bool HandlePointInsertion(TreeType* , const size_t) + bool HandlePointInsertion(TreeType* /* node */, const size_t /* point */) { return false; } @@ -98,6 +98,24 @@ class NoAuxiliaryInformation return false; } + /** + * The R++ tree requires to split the maximum bounding rectangle of a node + * that is being split. This method is intended for that. This method is only + * necessary for an AuxiliaryInformationType that is being used in conjunction + * with RPlusTreeSplit. + * + * @param treeOne The first subtree. + * @param treeTwo The second subtree. + * @param axis The axis along which the split is performed. + * @param cut The coordinate at which the node is split. + */ + void SplitAuxiliaryInfo(TreeType* /* treeOne */, + TreeType* /* treeTwo */, + size_t /* axis */, + typename TreeType::ElemType /* cut */) + { } + + /** * Nullify the auxiliary information in order to prevent an invalid free. */ diff --git a/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_auxiliary_information.hpp b/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_auxiliary_information.hpp new file mode 100644 index 0000000000..fbb82a66dd --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_auxiliary_information.hpp @@ -0,0 +1,149 @@ +/** + * @file r_plus_plus_tree_auxiliary_information.hpp + * @author Mikhail Lozhnikov + * + * Definition of the RPlusPlusTreeAuxiliaryInformation class, + * a class that provides some r++-tree specific information + * about the nodes. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_AUXILIARY_INFORMATION_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_AUXILIARY_INFORMATION_HPP + +#include +#include "../hrectbound.hpp" + +namespace mlpack { +namespace tree { + +template +class RPlusPlusTreeAuxiliaryInformation +{ + public: + //! The element type held by the tree. + typedef typename TreeType::ElemType ElemType; + //! The bound type held by the auxiliary information. + typedef bound::HRectBound BoundType; + + //! Construct the auxiliary information object. + RPlusPlusTreeAuxiliaryInformation(); + + /** + * Construct this as an auxiliary information for the given node. + * + * @param node The node that stores this auxiliary information. + */ + RPlusPlusTreeAuxiliaryInformation(const TreeType* /* node */); + + /** + * Create an auxiliary information object by copying from another node. + * + * @param other The auxiliary information object from which the information + * will be copied. + */ + RPlusPlusTreeAuxiliaryInformation( + const RPlusPlusTreeAuxiliaryInformation& other); + + /** + * Some tree types require to save some properties at the insertion process. + * This method allows the auxiliary information the option of manipulating + * the tree in order to perform the insertion process. If the auxiliary + * information does that, then the method should return true; if the method + * returns false the RectangleTree performs its default behavior. + * + * @param node The node in which the point is being inserted. + * @param point The global number of the point being inserted. + */ + bool HandlePointInsertion(TreeType* /* node */, const size_t /* point */); + + /** + * Some tree types require to save some properties at the insertion process. + * This method allows the auxiliary information the option of manipulating + * the tree in order to perform the insertion process. If the auxiliary + * information does that, then the method should return true; if the method + * returns false the RectangleTree performs its default behavior. + * + * @param node The node in which the nodeToInsert is being inserted. + * @param nodeToInsert The node being inserted. + * @param insertionLevel The level of the tree at which the nodeToInsert + * should be inserted. + */ + bool HandleNodeInsertion(TreeType* /* node */, + TreeType* /* nodeToInsert */, + bool /* insertionLevel */); + + /** + * Some tree types require to save some properties at the deletion process. + * This method allows the auxiliary information the option of manipulating + * the tree in order to perform the deletion process. If the auxiliary + * information does that, then the method should return true; if the method + * returns false the RectangleTree performs its default behavior. + * + * @param node The node from which the point is being deleted. + * @param localIndex The local index of the point being deleted. + */ + bool HandlePointDeletion(TreeType* /* node */, const size_t /* localIndex */); + + /** + * Some tree types require to save some properties at the deletion process. + * This method allows the auxiliary information the option of manipulating + * the tree in order to perform the deletion process. If the auxiliary + * information does that, then the method should return true; if the method + * returns false the RectangleTree performs its default behavior. + * + * @param node The node from which the node is being deleted. + * @param nodeIndex The local index of the node being deleted. + */ + bool HandleNodeRemoval(TreeType* /* node */, const size_t /* nodeIndex */); + + + /** + * Some tree types require to propagate the information upward. + * This method should return false if this is not the case. If true is + * returned, the update will be propogated upward. + * + * @param node The node in which the auxiliary information being update. + */ + bool UpdateAuxiliaryInfo(TreeType* /* node */); + + /** + * The R++ tree requires to split the maximum bounding rectangle of a node + * that is being split. This method is intended for that. + * + * @param treeOne The first subtree. + * @param treeTwo The second subtree. + * @param axis The axis along which the split is performed. + * @param cut The coordinate at which the node is split. + */ + void SplitAuxiliaryInfo(TreeType* treeOne, + TreeType* treeTwo, + const size_t axis, + const ElemType cut); + + + /** + * Nullify the auxiliary information in order to prevent an invalid free. + */ + void NullifyData(); + + //! Return the maximum bounding rectangle. + BoundType& OuterBound() { return outerBound; } + + //! Modify the maximum bounding rectangle. + const BoundType& OuterBound() const { return outerBound; } + private: + //! The maximum bounding rectangle. + BoundType outerBound; + public: + /** + * Serialize the information. + */ + template + void Serialize(Archive &, const unsigned int /* version */); +}; + +} // namespace tree +} // namespace mlpack + +#include "r_plus_plus_tree_auxiliary_information_impl.hpp" + +#endif//MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_AUXILIARY_INFORMATION_HPP diff --git a/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_auxiliary_information_impl.hpp b/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_auxiliary_information_impl.hpp new file mode 100644 index 0000000000..683645daf4 --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_auxiliary_information_impl.hpp @@ -0,0 +1,130 @@ +/** + * @file r_plus_plus_tree_auxiliary_information.hpp + * @author Mikhail Lozhnikov + * + * Implementation of the RPlusPlusTreeAuxiliaryInformation class, + * a class that provides some r++-tree specific information + * about the nodes. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_AUXILIARY_INFORMATION_IMPL_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_AUXILIARY_INFORMATION_IMPL_HPP + +#include "r_plus_plus_tree_auxiliary_information.hpp" + +namespace mlpack { +namespace tree { + +template +RPlusPlusTreeAuxiliaryInformation:: +RPlusPlusTreeAuxiliaryInformation() : + outerBound(0) +{ + +} + +template +RPlusPlusTreeAuxiliaryInformation:: +RPlusPlusTreeAuxiliaryInformation(const TreeType* tree) : + outerBound(tree->Parent() ? + tree->Parent()->AuxiliaryInfo().OuterBound() : + tree->Bound().Dim()) +{ + // Initialize the maximum bounding rectangle if the node is the root + if (!tree->Parent()) + for (size_t k = 0; k < outerBound.Dim(); k++) + { + outerBound[k].Lo() = std::numeric_limits::lowest(); + outerBound[k].Hi() = std::numeric_limits::max(); + } +} + +template +RPlusPlusTreeAuxiliaryInformation:: +RPlusPlusTreeAuxiliaryInformation( + const RPlusPlusTreeAuxiliaryInformation& other) : + outerBound(other.OuterBound()) +{ + +} + +template +bool RPlusPlusTreeAuxiliaryInformation::HandlePointInsertion( + TreeType* /* node */, const size_t /* point */) +{ + return false; +} + +template +bool RPlusPlusTreeAuxiliaryInformation::HandleNodeInsertion( + TreeType* /* node */, + TreeType* /* nodeToInsert */, + bool /* insertionLevel */) +{ + assert(false); + return false; +} + +template +bool RPlusPlusTreeAuxiliaryInformation::HandlePointDeletion( + TreeType* /* node */, const size_t /* localIndex */) +{ + return false; +} + +template +bool RPlusPlusTreeAuxiliaryInformation::HandleNodeRemoval( + TreeType* /* node */, const size_t /* nodeIndex */) +{ + return false; +} + +template +bool RPlusPlusTreeAuxiliaryInformation::UpdateAuxiliaryInfo( + TreeType* /* node */) +{ + return false; +} + +template +void RPlusPlusTreeAuxiliaryInformation::SplitAuxiliaryInfo( + TreeType* treeOne, + TreeType* treeTwo, + const size_t axis, + const typename TreeType::ElemType cut) +{ + typedef bound::HRectBound Bound; + Bound& treeOneBound = treeOne->AuxiliaryInfo().OuterBound(); + Bound& treeTwoBound = treeTwo->AuxiliaryInfo().OuterBound(); + + // Copy the maximum bounding rectangle + treeOneBound = outerBound; + treeTwoBound = outerBound; + + // Set proper limits + treeOneBound[axis].Hi() = cut; + treeTwoBound[axis].Lo() = cut; +} + +template +void RPlusPlusTreeAuxiliaryInformation::NullifyData() +{ + +} + +/** + * Serialize the information. + */ +template +template +void RPlusPlusTreeAuxiliaryInformation:: +Serialize(Archive& ar, const unsigned int /* version */) +{ + using data::CreateNVP; + + ar & CreateNVP(outerBound, "outerBound"); +} + +} // namespace tree +} // namespace mlpack + +#endif//MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_AUXILIARY_INFORMATION_IMPL_HPP diff --git a/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_descent_heuristic.hpp b/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_descent_heuristic.hpp new file mode 100644 index 0000000000..18166f4a4f --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_descent_heuristic.hpp @@ -0,0 +1,49 @@ +/** + * @file r_plus_plus_tree_descent_heuristic.hpp + * @author Mikhail Lozhnikov + * + * Definition of RPlusPlusTreeDescentHeuristic, a class that chooses the best child of a + * node in an R++ tree when inserting a new point. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_DESCENT_HEURISTIC_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_DESCENT_HEURISTIC_HPP + +#include + +namespace mlpack { +namespace tree { + +class RPlusPlusTreeDescentHeuristic +{ + public: + /** + * Evaluate the node using a heuristic. Returns the number of the node + * with minimum largest Hilbert value is greater than the Hilbert value of + * the point being inserted. + * + * @param node The node that is being evaluated. + * @param point The number of the point that is being inserted. + */ + template + static size_t ChooseDescentNode(TreeType* node, const size_t point); + + /** + * Evaluate the node using a heuristic. Returns the number of the node + * with minimum largest Hilbert value is greater than the largest + * Hilbert value of the point being inserted. + * + * @param node The node that is being evaluated. + * @param insertedNode The node that is being inserted. + */ + template + static size_t ChooseDescentNode(const TreeType* node, + const TreeType* insertedNode); + +}; + +} // namespace tree +} // namespace mlpack + +#include "r_plus_plus_tree_descent_heuristic_impl.hpp" + +#endif // MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_DESCENT_HEURISTIC_HPP diff --git a/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_descent_heuristic_impl.hpp b/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_descent_heuristic_impl.hpp new file mode 100644 index 0000000000..eca2d0fc4d --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_descent_heuristic_impl.hpp @@ -0,0 +1,49 @@ +/** + * @file r_plus_plus_tree_descent_heuristic_impl.hpp + * @author Mikhail Lozhnikov + * + * Implementation of RPlusPlusTreeDescentHeuristic, a class that chooses the + * best child of a node in an R++ tree when inserting a new point. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_DESCENT_HEURISTIC_IMPL_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_DESCENT_HEURISTIC_IMPL_HPP + +#include "r_plus_plus_tree_descent_heuristic.hpp" +#include "../hrectbound.hpp" + +namespace mlpack { +namespace tree { + +template +size_t RPlusPlusTreeDescentHeuristic::ChooseDescentNode( + TreeType* node, const size_t point) +{ + // Find the node whose maximum bounding rectangle contains the point. + for (size_t bestIndex = 0; bestIndex < node->NumChildren(); bestIndex++) + { + if (node->Child(bestIndex).AuxiliaryInfo().OuterBound().Contains( + node->Dataset().col(point))) + return bestIndex; + } + + // We should never reach this point. + assert(false); + + return 0; +} + +template +size_t RPlusPlusTreeDescentHeuristic::ChooseDescentNode( + const TreeType* /* node */, const TreeType* /* insertedNode */) +{ + // Should never be used. + assert(false); + + return 0; +} + + +} // namespace tree +} // namespace mlpack + +#endif //MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_DESCENT_HEURISTIC_IMPL_HPP diff --git a/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_split_policy.hpp b/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_split_policy.hpp new file mode 100644 index 0000000000..d729153912 --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/r_plus_plus_tree_split_policy.hpp @@ -0,0 +1,75 @@ +/** + * @file r_plus_plus_tree_split_policy.hpp + * @author Mikhail Lozhnikov + * + * Defintion and implementation of the RPlusPlusTreeSplitPolicy class, a class + * that helps to determine the subtree into which we should insert an + * intermediate node. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_SPLIT_POLICY_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_SPLIT_POLICY_HPP + +namespace mlpack { +namespace tree { + +/** + * The RPlusPlusTreeSplitPolicy helps to determine the subtree into which + * we should insert a child of an intermediate node that is being split. + * This class is designed for the R++ tree. + */ +class RPlusPlusTreeSplitPolicy +{ + public: + //! Indicate that the child should be split. + static const int SplitRequired = 0; + //! Indicate that the child should be inserted to the first subtree. + static const int AssignToFirstTree = 1; + //! Indicate that the child should be inserted to the second subtree. + static const int AssignToSecondTree = 2; + + /** + * This method returns SplitRequired if a child of an intermediate node should + * be split, AssignToFirstTree if the child should be inserted to the first + * subtree, AssignToSecondTree if the child should be inserted to the second + * subtree. The method makes desicion according to the maximum bounding + * rectangle of the child, the axis along which the intermediate node is being + * split and the coordinate at which the node is being split. + * + * @param child A child of the node that is being split. + * @param axis The axis along which the node is being split. + * @param cut The coordinate at which the node is being split. + */ + template + static int GetSplitPolicy(const TreeType& child, + const size_t axis, + const typename TreeType::ElemType cut) + { + if (child.AuxiliaryInfo().OuterBound()[axis].Hi() <= cut) + return AssignToFirstTree; + else if (child.AuxiliaryInfo().OuterBound()[axis].Lo() >= cut) + return AssignToSecondTree; + + return SplitRequired; + } + + /** + * Return the maximum bounding rectangle of the node. + * This method should always return the bound that is used for the + * desicion-making in GetSplitPolicy(). + * + * @param node The node whose bound is requested. + */ + template + static const + bound::HRectBound& + Bound(const TreeType& node) + { + return node.AuxiliaryInfo().OuterBound(); + } +}; + +} // namespace tree +} // namespace mlpack +#endif // MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_PLUS_TREE_SPLIT_POLICY_HPP + + diff --git a/src/mlpack/core/tree/rectangle_tree/r_plus_tree_descent_heuristic.hpp b/src/mlpack/core/tree/rectangle_tree/r_plus_tree_descent_heuristic.hpp new file mode 100644 index 0000000000..219c85b7b1 --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/r_plus_tree_descent_heuristic.hpp @@ -0,0 +1,49 @@ +/** + * @file r_plus_tree_descent_heuristic.hpp + * @author Mikhail Lozhnikov + * + * Definition of RPlusTreeDescentHeuristic, a class that chooses the best child + * of a node in an R+ tree when inserting a new point. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_DESCENT_HEURISTIC_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_DESCENT_HEURISTIC_HPP + +#include + +namespace mlpack { +namespace tree { + +class RPlusTreeDescentHeuristic +{ + public: + /** + * Evaluate the node using a heuristic. Returns the number of the node + * with minimum largest Hilbert value is greater than the Hilbert value of + * the point being inserted. + * + * @param node The node that is being evaluated. + * @param point The number of the point that is being inserted. + */ + template + static size_t ChooseDescentNode(TreeType* node, const size_t point); + + /** + * Evaluate the node using a heuristic. Returns the number of the node + * with minimum largest Hilbert value is greater than the largest + * Hilbert value of the point being inserted. + * + * @param node The node that is being evaluated. + * @param insertedNode The node that is being inserted. + */ + template + static size_t ChooseDescentNode(const TreeType* /* node */, + const TreeType* /*insertedNode */); + +}; + +} // namespace tree +} // namespace mlpack + +#include "r_plus_tree_descent_heuristic_impl.hpp" + +#endif // MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_DESCENT_HEURISTIC_HPP diff --git a/src/mlpack/core/tree/rectangle_tree/r_plus_tree_descent_heuristic_impl.hpp b/src/mlpack/core/tree/rectangle_tree/r_plus_tree_descent_heuristic_impl.hpp new file mode 100644 index 0000000000..77312ea363 --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/r_plus_tree_descent_heuristic_impl.hpp @@ -0,0 +1,104 @@ +/** + * @file hilbert_r_tree_descent_heuristic_impl.hpp + * @author Mikhail Lozhnikov + * + * Implementation of HilbertRTreeDescentHeuristic, a class that chooses the best child + * of a node in an R tree when inserting a new point. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_DESCENT_HEURISTIC_IMPL_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_DESCENT_HEURISTIC_IMPL_HPP + +#include "r_plus_tree_descent_heuristic.hpp" +#include "../hrectbound.hpp" + +namespace mlpack { +namespace tree { + +template +size_t RPlusTreeDescentHeuristic:: +ChooseDescentNode(TreeType* node, const size_t point) +{ + typedef typename TreeType::ElemType ElemType; + size_t bestIndex = 0; + bool success; + + // Try to find a node that contains the point. + for (bestIndex = 0; bestIndex < node->NumChildren(); bestIndex++) + { + if (node->Child(bestIndex).Bound().Contains( + node->Dataset().col(point))) + return bestIndex; + } + + // No one node contains the point. Try to enlarge a node in such a way, that + // the resulting node do not overlap other nodes. + for (bestIndex = 0; bestIndex < node->NumChildren(); bestIndex++) + { + bound::HRectBound bound = + node->Child(bestIndex).Bound(); + bound |= node->Dataset().col(point); + + success = true; + + for (size_t j = 0; j < node->NumChildren(); j++) + { + if (j == bestIndex) + continue; + success = false; + // Two nodes overlap if and only if there are no dimension in which + // they do not overlap each other. + for (size_t k = 0; k < node->Bound().Dim(); k++) + { + if (bound[k].Lo() >= node->Child(j).Bound()[k].Hi() || + node->Child(j).Bound()[k].Lo() >= bound[k].Hi()) + { + // We found the dimension in which these nodes do not overlap + // each other. + success = true; + break; + } + } + if (!success) // These two nodes overlap each other. + break; + } + if (success) // We found two nodes that do no overlap each other. + break; + } + + if (!success) // We could not find two nodes that do no overlap each other. + { + size_t depth = node->TreeDepth(); + + // Create a new node into which we will insert the point. + TreeType* tree = node; + while (depth > 1) + { + TreeType* child = new TreeType(tree); + + tree->children[tree->NumChildren()++] = child; + tree = child; + depth--; + } + return node->NumChildren()-1; + } + + assert(bestIndex < node->NumChildren()); + + return bestIndex; +} + +template +size_t RPlusTreeDescentHeuristic::ChooseDescentNode( + const TreeType* /* node */, const TreeType* /*insertedNode */) +{ + // Should never be used. + assert(false); + + return 0; +} + + +} // namespace tree +} // namespace mlpack + +#endif //MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_DESCENT_HEURISTIC_IMPL_HPP diff --git a/src/mlpack/core/tree/rectangle_tree/r_plus_tree_split.hpp b/src/mlpack/core/tree/rectangle_tree/r_plus_tree_split.hpp new file mode 100644 index 0000000000..8e5b8ce731 --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/r_plus_tree_split.hpp @@ -0,0 +1,131 @@ +/** + * @file r_plus_tree_split.hpp + * @author Mikhail Lozhnikov + * + * Defintion of the RPlusTreeSplit class, a class that splits the nodes of an R + * tree, starting at a leaf node and moving upwards if necessary. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_SPLIT_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_SPLIT_HPP + +#include + +namespace mlpack { +namespace tree /** Trees and tree-building procedures. */ { + +/** + * The RPlusTreeSplit class performs the split process of a node on overflow. + * + * @tparam SplitPolicyType The class that helps to determine the subtree into + * which we should insert a child node. + * @tparam SweepType The class that finds the partition of a node along a + * given axis. The partition algorithm tries to find a partition along each + * axis, evaluates each partition and chooses the best one. + */ +template class SweepType> +class RPlusTreeSplit +{ + public: + typedef SplitPolicyType SplitPolicy; + /** + * Split a leaf node using the "default" algorithm. If necessary, this split + * will propagate upwards through the tree. + * @param node. The node that is being split. + * @param relevels Not used. + */ + template + static void SplitLeafNode(TreeType *tree,std::vector& relevels); + + /** + * Split a non-leaf node using the "default" algorithm. If this is a root + * node, the tree increases in depth. + * @param node. The node that is being split. + * @param relevels Not used. + */ + template + static bool SplitNonLeafNode(TreeType *tree,std::vector& relevels); + + private: + /** + * Split a leaf node along an axis. + * + * @param tree The node that is being split into two new nodes. + * @param treeOne The first subtree of two resulting subtrees. + * @param treeOne The second subtree of two resulting subtrees. + * @param cutAxis The axis along which the node is being split. + * @param cut The coordinate at which the node is being split. + */ + template + static void SplitLeafNodeAlongPartition( + TreeType* tree, + TreeType* treeOne, + TreeType* treeTwo, + const size_t cutAxis, + const typename TreeType::ElemType cut); + + /** + * Split a non-leaf node along an axis. This method propagates the split + * downward up to a leaf node if necessary. + * + * @param tree The node that is being split into two new nodes. + * @param treeOne The first subtree of two resulting subtrees. + * @param treeOne The second subtree of two resulting subtrees. + * @param cutAxis The axis along which the node is being split. + * @param cut The coordinate at which the node is being split. + */ + template + static void SplitNonLeafNodeAlongPartition( + TreeType* tree, + TreeType* treeOne, + TreeType* treeTwo, + const size_t cutAxis, + const typename TreeType::ElemType cut); + + /** + * This method is used to make sure that the tree has equivalent maximum depth + * in every branch. The method should be invoked if one of two resulting + * subtrees is empty after the split process + * (i.e. the subtree contains no children). + * The method convert the empty node into an empty subtree (increase the node + * in depth). + * + * @param tree One of two subtrees that is not empty. + * @param emptyTree The empty subtree. + */ + template + static void AddFakeNodes(const TreeType* tree, TreeType* emptyTree); + + /** + * Partition a node using SweepType. This method invokes + * SweepType::Sweep(Non)LeafNode() for each dimension and chooses the + * best one. The method returns false if the node needn't partitioning. + * Overwise, the method returns true. If the method failed in finding + * an acceptable partition, the minCutAxis will be equal to the number of + * dimensions. + * + * @param node The node that is being split. + * @param minCutAxis The axis along which the node will be split. + * @param minCut The coordinate at which the node will be split. + */ + template + static bool PartitionNode(const TreeType* node, + size_t& minCutAxis, + typename TreeType::ElemType& minCut); + + /** + * Insert a node into another node. + */ + template + static void InsertNodeIntoTree(TreeType* destTree, TreeType* srcNode); + +}; + +} // namespace tree +} // namespace mlpack + +// Include implementation +#include "r_plus_tree_split_impl.hpp" + +#endif // MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_SPLIT_HPP + diff --git a/src/mlpack/core/tree/rectangle_tree/r_plus_tree_split_impl.hpp b/src/mlpack/core/tree/rectangle_tree/r_plus_tree_split_impl.hpp new file mode 100644 index 0000000000..6cb4d5c806 --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/r_plus_tree_split_impl.hpp @@ -0,0 +1,358 @@ +/** + * @file r_plus_tree_split_impl.hpp + * @author Mikhail Lozhnikov + * + * Implementation of class (RPlusTreeSplit) to split a RectangleTree. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_SPLIT_IMPL_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_SPLIT_IMPL_HPP + +#include "r_plus_tree_split.hpp" +#include "rectangle_tree.hpp" +#include "r_plus_plus_tree_auxiliary_information.hpp" +#include "r_plus_tree_split_policy.hpp" +#include "r_plus_plus_tree_split_policy.hpp" + +namespace mlpack { +namespace tree { + +template class SweepType> +template +void RPlusTreeSplit:: +SplitLeafNode(TreeType* tree, std::vector& relevels) +{ + if (tree->Count() == 1) + { + // Check if an intermediate node was added during the insertion process. + // i.e. we couldn't enlarge a node of the R+ tree. So, one of intermediate + // nodes may be overflowed. + TreeType* node = tree->Parent(); + + while (node != NULL) + { + if (node->NumChildren() == node->MaxNumChildren() + 1) + { + // Split the overflowed node. + RPlusTreeSplit::SplitNonLeafNode(node,relevels); + return; + } + node = node->Parent(); + } + return; + } + else if (tree->Count() <= tree->MaxLeafSize()) + return; + // If we are splitting the root node, we need will do things differently so + // that the constructor and other methods don't confuse the end user by giving + // an address of another node. + if (tree->Parent() == NULL) + { + // We actually want to copy this way. Pointers and everything. + TreeType* copy = new TreeType(*tree, false); + copy->Parent() = tree; + tree->Count() = 0; + tree->NullifyData(); + // Because this was a leaf node, numChildren must be 0. + tree->children[(tree->NumChildren())++] = copy; + assert(tree->NumChildren() == 1); + + RPlusTreeSplit::SplitLeafNode(copy,relevels); + return; + } + + size_t cutAxis; + typename TreeType::ElemType cut; + + // Try to find a partiotion of the node. + if ( !PartitionNode(tree, cutAxis, cut)) + return; + + // If we could not find a suitable partition. + if (cutAxis == tree->Bound().Dim()) + { + tree->MaxLeafSize()++; + tree->points.resize(tree->MaxLeafSize() + 1); + Log::Warn << "Could not find an acceptable partition." + "The size of the node will be increased."; + return; + } + + TreeType* treeOne = new TreeType(tree->Parent()); + TreeType* treeTwo = new TreeType(tree->Parent()); + treeOne->MinLeafSize() = 0; + treeOne->MinNumChildren() = 0; + treeTwo->MinLeafSize() = 0; + treeTwo->MinNumChildren() = 0; + + // Split the node into two new nodes. + SplitLeafNodeAlongPartition(tree, treeOne, treeTwo, cutAxis, cut); + + TreeType* parent = tree->Parent(); + size_t i = 0; + while (parent->children[i] != tree) + i++; + + assert(i < parent->NumChildren()); + + // Insert two new nodes to the tree. + parent->children[i] = treeOne; + parent->children[parent->NumChildren()++] = treeTwo; + + assert(parent->NumChildren() <= parent->MaxNumChildren() + 1); + + // Propagate the split upward if necessary. + if (parent->NumChildren() == parent->MaxNumChildren() + 1) + RPlusTreeSplit::SplitNonLeafNode(parent, relevels); + + tree->SoftDelete(); +} + +template class SweepType> +template +bool RPlusTreeSplit:: +SplitNonLeafNode(TreeType* tree, std::vector& relevels) +{ + // If we are splitting the root node, we need will do things differently so + // that the constructor and other methods don't confuse the end user by giving + // an address of another node. + if (tree->Parent() == NULL) + { + // We actually want to copy this way. Pointers and everything. + TreeType* copy = new TreeType(*tree, false); + + copy->Parent() = tree; + tree->NumChildren() = 0; + tree->NullifyData(); + tree->children[(tree->NumChildren())++] = copy; + + RPlusTreeSplit::SplitNonLeafNode(copy,relevels); + return true; + } + size_t cutAxis; + typename TreeType::ElemType cut; + + // Try to find a partiotion of the node. + if ( !PartitionNode(tree, cutAxis, cut)) + return false; + + // If we could not find a suitable partition. + if (cutAxis == tree->Bound().Dim()) + { + tree->MaxNumChildren()++; + tree->children.resize(tree->MaxNumChildren() + 1); + Log::Warn << "Could not find an acceptable partition." + "The size of the node will be increased."; + return false; + } + + TreeType* treeOne = new TreeType(tree->Parent()); + TreeType* treeTwo = new TreeType(tree->Parent()); + treeOne->MinLeafSize() = 0; + treeOne->MinNumChildren() = 0; + treeTwo->MinLeafSize() = 0; + treeTwo->MinNumChildren() = 0; + + // Split the node into two new nodes. + SplitNonLeafNodeAlongPartition(tree, treeOne, treeTwo, cutAxis, cut); + + TreeType* parent = tree->Parent(); + size_t i = 0; + while (parent->children[i] != tree) + i++; + + assert(i < parent->NumChildren()); + + // Insert two new nodes to the tree. + parent->children[i] = treeOne; + parent->children[parent->NumChildren()++] = treeTwo; + + tree->SoftDelete(); + + assert(parent->NumChildren() <= parent->MaxNumChildren() + 1); + + // Propagate the split upward if necessary. + if (parent->NumChildren() == parent->MaxNumChildren() + 1) + RPlusTreeSplit::SplitNonLeafNode(parent, relevels); + + return false; +} + +template class SweepType> +template +void RPlusTreeSplit::SplitLeafNodeAlongPartition( + TreeType* tree, + TreeType* treeOne, + TreeType* treeTwo, + const size_t cutAxis, + const typename TreeType::ElemType cut) +{ + // Split the auxiliary information. + tree->AuxiliaryInfo().SplitAuxiliaryInfo(treeOne, treeTwo, cutAxis, cut); + + // Insert points into the corresponding subtree. + for (size_t i = 0; i < tree->NumPoints(); i++) + { + if (tree->Dataset().col(tree->Point(i))[cutAxis] <= cut) + { + treeOne->Point(treeOne->Count()++) = tree->Point(i); + treeOne->Bound() |= tree->Dataset().col(tree->Point(i)); + } + else + { + treeTwo->Point(treeTwo->Count()++) = tree->Point(i); + treeTwo->Bound() |= tree->Dataset().col(tree->Point(i)); + } + } + // Update the number of descandants. + treeOne->numDescendants = treeOne->Count(); + treeTwo->numDescendants = treeTwo->Count(); + + assert(treeOne->Count() <= treeOne->MaxLeafSize()); + assert(treeTwo->Count() <= treeTwo->MaxLeafSize()); + + assert(tree->Count() == treeOne->Count() + treeTwo->Count()); + assert(treeOne->Bound()[cutAxis].Hi() < treeTwo->Bound()[cutAxis].Lo()); +} + +template class SweepType> +template +void RPlusTreeSplit::SplitNonLeafNodeAlongPartition( + TreeType* tree, + TreeType* treeOne, + TreeType* treeTwo, + const size_t cutAxis, + const typename TreeType::ElemType cut) +{ + // Split the auxiliary information. + tree->AuxiliaryInfo().SplitAuxiliaryInfo(treeOne, treeTwo, cutAxis, cut); + + // Insert children into the corresponding subtree. + for (size_t i = 0; i < tree->NumChildren(); i++) + { + TreeType* child = tree->children[i]; + int policy = SplitPolicyType::GetSplitPolicy(*child, cutAxis, cut); + + if (policy == SplitPolicyType::AssignToFirstTree) + { + InsertNodeIntoTree(treeOne, child); + child->Parent() = treeOne; + } + else if (policy == SplitPolicyType::AssignToSecondTree) + { + InsertNodeIntoTree(treeTwo, child); + child->Parent() = treeTwo; + } + else + { + // The child should be split (i.e. the partition divides its bound). + TreeType* childOne = new TreeType(treeOne); + TreeType* childTwo = new TreeType(treeTwo); + treeOne->MinLeafSize() = 0; + treeOne->MinNumChildren() = 0; + treeTwo->MinLeafSize() = 0; + treeTwo->MinNumChildren() = 0; + + // Propagate the split downward. + if (child->IsLeaf()) + SplitLeafNodeAlongPartition(child, childOne, childTwo, cutAxis, cut); + else + SplitNonLeafNodeAlongPartition(child, childOne, childTwo, cutAxis, cut); + + InsertNodeIntoTree(treeOne, childOne); + InsertNodeIntoTree(treeTwo, childTwo); + + child->SoftDelete(); + } + } + + assert(treeOne->NumChildren() + treeTwo->NumChildren() != 0); + + // Add a fake subtree if one of the subtrees is empty. + if (treeOne->NumChildren() == 0) + AddFakeNodes(treeTwo, treeOne); + else if (treeTwo->NumChildren() == 0) + AddFakeNodes(treeOne, treeTwo); + + assert(treeOne->NumChildren() <= treeOne->MaxNumChildren()); + assert(treeTwo->NumChildren() <= treeTwo->MaxNumChildren()); +} + +template class SweepType> +template +void RPlusTreeSplit:: +AddFakeNodes(const TreeType* tree, TreeType* emptyTree) +{ + size_t numDescendantNodes = tree->TreeDepth() - 1; + + TreeType* node = emptyTree; + for (size_t i = 0; i < numDescendantNodes; i++) + { + TreeType* child = new TreeType(node); + node->children[node->NumChildren()++] = child; + + node = child; + } +} + +template class SweepType> +template +bool RPlusTreeSplit:: +PartitionNode(const TreeType* node, size_t& minCutAxis, + typename TreeType::ElemType& minCut) +{ + if ((node->NumChildren() <= node->MaxNumChildren() && !node->IsLeaf()) || + (node->Count() <= node->MaxLeafSize() && node->IsLeaf())) + return false; // No partition required. + + // Define the type of the sweep cost. + typedef typename + SweepType::template SweepCost::type + SweepCostType; + + SweepCostType minCost = std::numeric_limits::max(); + minCutAxis = node->Bound().Dim(); + + // Find the sweep with a minimal cost. + for (size_t k = 0; k < node->Bound().Dim(); k++) + { + typename TreeType::ElemType cut; + SweepCostType cost; + + if (node->IsLeaf()) + cost = SweepType::SweepLeafNode(k, node, cut); + else + cost = SweepType::SweepNonLeafNode(k, node, cut); + + + if (cost < minCost) + { + minCost = cost; + minCutAxis = k; + minCut = cut; + } + } + return true; +} + +template class SweepType> +template +void RPlusTreeSplit:: +InsertNodeIntoTree(TreeType* destTree, TreeType* srcNode) +{ + destTree->Bound() |= srcNode->Bound(); + destTree->numDescendants += srcNode->numDescendants; + destTree->children[destTree->NumChildren()++] = srcNode; +} + + +} // namespace tree +} // namespace mlpack + +#endif // MLPACK_CORE_TREE_RECTANGLE_TREE_HILBERT_R_TREE_SPLIT_IMPL_HPP diff --git a/src/mlpack/core/tree/rectangle_tree/r_plus_tree_split_policy.hpp b/src/mlpack/core/tree/rectangle_tree/r_plus_tree_split_policy.hpp new file mode 100644 index 0000000000..1302dd398b --- /dev/null +++ b/src/mlpack/core/tree/rectangle_tree/r_plus_tree_split_policy.hpp @@ -0,0 +1,75 @@ +/** + * @file r_plus_tree_split_policy.hpp + * @author Mikhail Lozhnikov + * + * Defintion and implementation of the RPlusTreeSplitPolicy class, a class that + * helps to determine the subtree into which we should insert an intermediate + * node. + */ +#ifndef MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_SPLIT_POLICY_HPP +#define MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_SPLIT_POLICY_HPP + +namespace mlpack { +namespace tree { + +/** + * The RPlusPlusTreeSplitPolicy helps to determine the subtree into which + * we should insert a child of an intermediate node that is being split. + * This class is designed for the R+ tree. + */ +class RPlusTreeSplitPolicy +{ + public: + //! Indicate that the child should be split. + static const int SplitRequired = 0; + //! Indicate that the child should be inserted to the first subtree. + static const int AssignToFirstTree = 1; + //! Indicate that the child should be inserted to the second subtree. + static const int AssignToSecondTree = 2; + + /** + * This method returns SplitRequired if a child of an intermediate node should + * be split, AssignToFirstTree if the child should be inserted to the first + * subtree, AssignToSecondTree if the child should be inserted to the second + * subtree. The method makes desicion according to the minimum bounding + * rectangle of the child, the axis along which the intermediate node is being + * split and the coordinate at which the node is being split. + * + * @param child A child of the node that is being split. + * @param axis The axis along which the node is being split. + * @param cut The coordinate at which the node is being split. + */ + template + static int GetSplitPolicy(const TreeType& child, + const size_t axis, + const typename TreeType::ElemType cut) + { + if (child.Bound()[axis].Hi() <= cut) + return AssignToFirstTree; + else if (child.Bound()[axis].Lo() >= cut) + return AssignToSecondTree; + + return SplitRequired; + } + + /** + * Return the minimum bounding rectangle of the node. + * This method should always return the bound that is used for the + * desicion-making in GetSplitPolicy(). + * + * @param node The node whose bound is requested. + */ + template + static const + bound::HRectBound& + Bound(const TreeType& node) + { + return node.Bound(); + } +}; + +} // namespace tree +} // namespace mlpack +#endif // MLPACK_CORE_TREE_RECTANGLE_TREE_R_PLUS_TREE_SPLIT_POLICY_HPP + + diff --git a/src/mlpack/core/tree/rectangle_tree/r_star_tree_split_impl.hpp b/src/mlpack/core/tree/rectangle_tree/r_star_tree_split_impl.hpp index 9347b87cd8..795170dbb2 100644 --- a/src/mlpack/core/tree/rectangle_tree/r_star_tree_split_impl.hpp +++ b/src/mlpack/core/tree/rectangle_tree/r_star_tree_split_impl.hpp @@ -27,6 +27,9 @@ void RStarTreeSplit::SplitLeafNode(TreeType *tree,std::vector& relevels) // Convenience typedef. typedef typename TreeType::ElemType ElemType; + if (tree->Count() <= tree->MaxLeafSize()) + return; + // If we are splitting the root node, we need will do things differently so // that the constructor and other methods don't confuse the end user by giving // an address of another node. diff --git a/src/mlpack/core/tree/rectangle_tree/r_tree_split_impl.hpp b/src/mlpack/core/tree/rectangle_tree/r_tree_split_impl.hpp index faf75f38b9..31ebda44ac 100644 --- a/src/mlpack/core/tree/rectangle_tree/r_tree_split_impl.hpp +++ b/src/mlpack/core/tree/rectangle_tree/r_tree_split_impl.hpp @@ -23,6 +23,8 @@ namespace tree { template void RTreeSplit::SplitLeafNode(TreeType *tree,std::vector& relevels) { + if (tree->Count() <= tree->MaxLeafSize()) + return; // If we are splitting the root node, we need will do things differently so // that the constructor and other methods don't confuse the end user by giving // an address of another node. diff --git a/src/mlpack/core/tree/rectangle_tree/rectangle_tree.hpp b/src/mlpack/core/tree/rectangle_tree/rectangle_tree.hpp index 9590aa59d9..bbdebdadc1 100644 --- a/src/mlpack/core/tree/rectangle_tree/rectangle_tree.hpp +++ b/src/mlpack/core/tree/rectangle_tree/rectangle_tree.hpp @@ -512,6 +512,9 @@ class RectangleTree //! Friend access is given for the default constructor. friend class boost::serialization::access; + //! Give friend access for DescentType. + friend DescentType; + //! Give friend access for SplitType. friend SplitType; diff --git a/src/mlpack/core/tree/rectangle_tree/rectangle_tree_impl.hpp b/src/mlpack/core/tree/rectangle_tree/rectangle_tree_impl.hpp index 6c4b42f4f6..81842ac40f 100644 --- a/src/mlpack/core/tree/rectangle_tree/rectangle_tree_impl.hpp +++ b/src/mlpack/core/tree/rectangle_tree/rectangle_tree_impl.hpp @@ -692,12 +692,11 @@ void RectangleTree; +/** + * The R+ tree, a variant of the R tree that avoids overlapping rectangles. + * The implementation is modified from the original paper implementation. + * This template typedef satisfies the TreeType policy API. + * + * @code + * @inproceedings{sellis1987r, + * author = {Sellis, Timos K. and Roussopoulos, Nick and Faloutsos, Christos}, + * title = {The R+-Tree: A Dynamic Index for Multi-Dimensional Objects}, + * booktitle = {Proceedings of the 13th International Conference on Very + * Large Data Bases}, + * series = {VLDB '87}, + * year = {1987}, + * isbn = {0-934613-46-X}, + * pages = {507--518}, + * numpages = {12}, + * publisher = {Morgan Kaufmann Publishers Inc.}, + * address = {San Francisco, CA, USA}, + * } + * @endcode + * + * @see @ref trees, RTree, RTree, RPlusTree + */ +template +using RPlusTree = RectangleTree, + RPlusTreeDescentHeuristic, + NoAuxiliaryInformation>; +/** + * The R++ tree, a variant of the R+ tree with maximum buonding rectangles. + * This template typedef satisfies the TreeType policy API. + * + * @code + * @inproceedings{sumak2014r, + * author = {{\v{S}}um{\'a}k, Martin and Gursk{\'y}, Peter}, + * title = {R++-Tree: An Efficient Spatial Access Method for Highly Redundant + * Point Data}, + * booktitle = {New Trends in Databases and Information Systems: 17th East + * European Conference on Advances in Databases and Information Systems}, + * year = {2014}, + * isbn = {978-3-319-01863-8}, + * pages = {37--44}, + * publisher = {Springer International Publishing}, + * } + * @endcode + * + * @see @ref trees, RTree, RTree, RPlusTree, RPlusPlusTree + */template +using RPlusPlusTree = RectangleTree, + RPlusPlusTreeDescentHeuristic, + RPlusPlusTreeAuxiliaryInformation>; } // namespace tree } // namespace mlpack diff --git a/src/mlpack/core/tree/rectangle_tree/x_tree_split_impl.hpp b/src/mlpack/core/tree/rectangle_tree/x_tree_split_impl.hpp index 8d8fc6d061..073d77182d 100644 --- a/src/mlpack/core/tree/rectangle_tree/x_tree_split_impl.hpp +++ b/src/mlpack/core/tree/rectangle_tree/x_tree_split_impl.hpp @@ -26,6 +26,9 @@ void XTreeSplit::SplitLeafNode(TreeType *tree,std::vector& relevels) // Convenience typedef. typedef typename TreeType::ElemType ElemType; + if (tree->Count() <= tree->MaxLeafSize()) + return; + // If we are splitting the root node, we need will do things differently so // that the constructor and other methods don't confuse the end user by giving // an address of another node. diff --git a/src/mlpack/methods/neighbor_search/kfn_main.cpp b/src/mlpack/methods/neighbor_search/kfn_main.cpp index 254dc52b50..6adbc566f7 100644 --- a/src/mlpack/methods/neighbor_search/kfn_main.cpp +++ b/src/mlpack/methods/neighbor_search/kfn_main.cpp @@ -62,8 +62,10 @@ PARAM_INT("k", "Number of furthest neighbors to find.", "k", 0); // The user may specify the type of tree to use, and a few pararmeters for tree // building. PARAM_STRING("tree_type", "Type of tree to use: 'kd', 'cover', 'r', 'r-star', " - "'x', 'ball', 'hilbert-r'.", "t", "kd"); -PARAM_INT("leaf_size", "Leaf size for tree building.", "l", 20); + "'x', 'ball', 'hilbert-r', 'r-plus', 'r-plus-plus'.", "t", "kd"); +PARAM_INT("leaf_size", "Leaf size for tree building (used for kd-trees, R " + "trees, R* trees, X trees, Hilbert R trees, R+ trees and R++ trees).", "l", + 20); PARAM_FLAG("random_basis", "Before tree-building, project the data onto a " "random orthogonal basis.", "R"); PARAM_INT("seed", "Random seed (if 0, std::time(NULL) is used).", "s", 0); @@ -188,9 +190,14 @@ int main(int argc, char *argv[]) tree = KFNModel::X_TREE; else if (treeType == "hilbert-r") tree = KFNModel::HILBERT_R_TREE; + else if (treeType == "r-plus") + tree = KFNModel::R_PLUS_TREE; + else if (treeType == "r-plus-plus") + tree = KFNModel::R_PLUS_PLUS_TREE; else Log::Fatal << "Unknown tree type '" << treeType << "'; valid choices are " - << "'kd', 'cover', 'r', 'r-star', 'x', 'ball' and 'hilbert-r'." << endl; + << "'kd', 'cover', 'r', 'r-star', 'x', 'ball', 'hilbert-r', " + << "'r-plus' and 'r-plus-plus'." << endl; kfn.TreeType() = tree; kfn.RandomBasis() = randomBasis; diff --git a/src/mlpack/methods/neighbor_search/knn_main.cpp b/src/mlpack/methods/neighbor_search/knn_main.cpp index aad1991155..87bdb32e79 100644 --- a/src/mlpack/methods/neighbor_search/knn_main.cpp +++ b/src/mlpack/methods/neighbor_search/knn_main.cpp @@ -63,9 +63,10 @@ PARAM_INT("k", "Number of nearest neighbors to find.", "k", 0); // The user may specify the type of tree to use, and a few parameters for tree // building. PARAM_STRING("tree_type", "Type of tree to use: 'kd', 'cover', 'r', 'r-star', " - "'x', 'ball', 'hilbert-r'.", "t", "kd"); + "'x', 'ball', 'hilbert-r', 'r-plus', 'r-plus-plus'.", "t", "kd"); PARAM_INT("leaf_size", "Leaf size for tree building (used for kd-trees, R " - "trees, and R* trees).", "l", 20); + "trees, R* trees, X trees, Hilbert R trees, R+ trees and R++ trees).", "l", + 20); PARAM_FLAG("random_basis", "Before tree-building, project the data onto a " "random orthogonal basis.", "R"); PARAM_INT("seed", "Random seed (if 0, std::time(NULL) is used).", "s", 0); @@ -174,9 +175,14 @@ int main(int argc, char *argv[]) tree = KNNModel::X_TREE; else if (treeType == "hilbert-r") tree = KNNModel::HILBERT_R_TREE; + else if (treeType == "r-plus") + tree = KNNModel::R_PLUS_TREE; + else if (treeType == "r-plus-plus") + tree = KNNModel::R_PLUS_PLUS_TREE; else Log::Fatal << "Unknown tree type '" << treeType << "'; valid choices are " - << "'kd', 'cover', 'r', 'r-star', 'x', 'ball' and 'hilbert-r'." << endl; + << "'kd', 'cover', 'r', 'r-star', 'x', 'ball', 'hilbert-r', " + << "'r-plus' and 'r-plus-plus'." << endl; knn.TreeType() = tree; knn.RandomBasis() = randomBasis; diff --git a/src/mlpack/methods/neighbor_search/ns_model.hpp b/src/mlpack/methods/neighbor_search/ns_model.hpp index 5001675714..38e474898b 100644 --- a/src/mlpack/methods/neighbor_search/ns_model.hpp +++ b/src/mlpack/methods/neighbor_search/ns_model.hpp @@ -254,7 +254,9 @@ class NSModel R_STAR_TREE, BALL_TREE, X_TREE, - HILBERT_R_TREE + HILBERT_R_TREE, + R_PLUS_TREE, + R_PLUS_PLUS_TREE }; private: @@ -280,7 +282,9 @@ class NSModel NSType*, NSType*, NSType*, - NSType*> nSearch; + NSType*, + NSType*, + NSType*> nSearch; public: /** diff --git a/src/mlpack/methods/neighbor_search/ns_model_impl.hpp b/src/mlpack/methods/neighbor_search/ns_model_impl.hpp index ae34feba75..acbed6ce2a 100644 --- a/src/mlpack/methods/neighbor_search/ns_model_impl.hpp +++ b/src/mlpack/methods/neighbor_search/ns_model_impl.hpp @@ -386,6 +386,14 @@ void NSModel::BuildModel(arma::mat&& referenceSet, nSearch = new NSType(naive, singleMode, epsilon); break; + case R_PLUS_TREE: + nSearch = new NSType(naive, singleMode, + epsilon); + break; + case R_PLUS_PLUS_TREE: + nSearch = new NSType(naive, singleMode, + epsilon); + break; } TrainVisitor tn(std::move(referenceSet), leafSize); @@ -466,6 +474,10 @@ std::string NSModel::TreeName() const return "X tree"; case HILBERT_R_TREE: return "Hilbert R tree"; + case R_PLUS_TREE: + return "R+ tree"; + case R_PLUS_PLUS_TREE: + return "R++ tree"; default: return "unknown tree"; } diff --git a/src/mlpack/methods/range_search/range_search_main.cpp b/src/mlpack/methods/range_search/range_search_main.cpp index ca3f4330de..c8ea2a5b83 100644 --- a/src/mlpack/methods/range_search/range_search_main.cpp +++ b/src/mlpack/methods/range_search/range_search_main.cpp @@ -70,8 +70,10 @@ PARAM_DOUBLE("min", "Lower bound in range.", "L", 0.0); // The user may specify the type of tree to use, and a few parameters for tree // building. PARAM_STRING("tree_type", "Type of tree to use: 'kd', 'cover', 'r', 'r-star', " - "'x', 'ball', 'hilbert-r'.", "t", "kd"); -PARAM_INT("leaf_size", "Leaf size for tree building.", "l", 20); + "'x', 'ball', 'hilbert-r', 'r-plus', 'r-plus-plus'.", "t", "kd"); +PARAM_INT("leaf_size", "Leaf size for tree building (used for kd-trees, R " + "trees, R* trees, X trees, Hilbert R trees, R+ trees and R++ trees).", "l", + 20); PARAM_FLAG("random_basis", "Before tree-building, project the data onto a " "random orthogonal basis.", "R"); PARAM_INT("seed", "Random seed (if 0, std::time(NULL) is used).", "s", 0); @@ -175,9 +177,14 @@ int main(int argc, char *argv[]) tree = RSModel::X_TREE; else if (treeType == "hilbert-r") tree = RSModel::HILBERT_R_TREE; + else if (treeType == "r-plus") + tree = RSModel::R_PLUS_TREE; + else if (treeType == "r-plus-plus") + tree = RSModel::R_PLUS_PLUS_TREE; else Log::Fatal << "Unknown tree type '" << treeType << "; valid choices are " - << "'kd', 'cover', 'r', 'r-star', 'x', 'ball' and 'hilbert-r'." << endl; + << "'kd', 'cover', 'r', 'r-star', 'x', 'ball', 'hilbert-r', " + << "'r-plus' and 'r-plus-plus'." << endl; rs.TreeType() = tree; rs.RandomBasis() = randomBasis; diff --git a/src/mlpack/methods/range_search/rs_model.cpp b/src/mlpack/methods/range_search/rs_model.cpp index 1bf565ab67..1cf3938523 100644 --- a/src/mlpack/methods/range_search/rs_model.cpp +++ b/src/mlpack/methods/range_search/rs_model.cpp @@ -23,7 +23,9 @@ RSModel::RSModel(TreeTypes treeType, bool randomBasis) : rStarTreeRS(NULL), ballTreeRS(NULL), xTreeRS(NULL), - hilbertRTreeRS(NULL) + hilbertRTreeRS(NULL), + rPlusTreeRS(NULL), + rPlusPlusTreeRS(NULL) { // Nothing to do. } @@ -128,6 +130,16 @@ void RSModel::BuildModel(arma::mat&& referenceSet, hilbertRTreeRS = new RSType(move(referenceSet), naive, singleMode); break; + + case R_PLUS_TREE: + rPlusTreeRS = new RSType(move(referenceSet), naive, + singleMode); + break; + + case R_PLUS_PLUS_TREE: + rPlusPlusTreeRS = new RSType(move(referenceSet), naive, + singleMode); + break; } if (!naive) @@ -241,6 +253,14 @@ void RSModel::Search(arma::mat&& querySet, case HILBERT_R_TREE: hilbertRTreeRS->Search(querySet, range, neighbors, distances); break; + + case R_PLUS_TREE: + rPlusTreeRS->Search(querySet, range, neighbors, distances); + break; + + case R_PLUS_PLUS_TREE: + rPlusPlusTreeRS->Search(querySet, range, neighbors, distances); + break; } } @@ -287,6 +307,14 @@ void RSModel::Search(const math::Range& range, case HILBERT_R_TREE: hilbertRTreeRS->Search(range, neighbors, distances); break; + + case R_PLUS_TREE: + rPlusTreeRS->Search(range, neighbors, distances); + break; + + case R_PLUS_PLUS_TREE: + rPlusPlusTreeRS->Search(range, neighbors, distances); + break; } } @@ -309,6 +337,10 @@ std::string RSModel::TreeName() const return "X tree"; case HILBERT_R_TREE: return "Hilbert R tree"; + case R_PLUS_TREE: + return "R+ tree"; + case R_PLUS_PLUS_TREE: + return "R++ tree"; default: return "unknown tree"; } @@ -331,6 +363,10 @@ void RSModel::CleanMemory() delete xTreeRS; if (hilbertRTreeRS) delete hilbertRTreeRS; + if (rPlusTreeRS) + delete rPlusTreeRS; + if (rPlusPlusTreeRS) + delete rPlusPlusTreeRS; kdTreeRS = NULL; coverTreeRS = NULL; @@ -339,4 +375,6 @@ void RSModel::CleanMemory() ballTreeRS = NULL; xTreeRS = NULL; hilbertRTreeRS = NULL; + rPlusTreeRS = NULL; + rPlusPlusTreeRS = NULL; } diff --git a/src/mlpack/methods/range_search/rs_model.hpp b/src/mlpack/methods/range_search/rs_model.hpp index d256c31984..7903d373c3 100644 --- a/src/mlpack/methods/range_search/rs_model.hpp +++ b/src/mlpack/methods/range_search/rs_model.hpp @@ -30,7 +30,9 @@ class RSModel R_STAR_TREE, BALL_TREE, X_TREE, - HILBERT_R_TREE + HILBERT_R_TREE, + R_PLUS_TREE, + R_PLUS_PLUS_TREE }; private: @@ -63,6 +65,10 @@ class RSModel RSType* xTreeRS; //! Hilbert R tree based range search object (NULL if not in use). RSType* hilbertRTreeRS; + //! R+ tree based range search object (NULL if not in use). + RSType* rPlusTreeRS; + //! R++ tree based range search object (NULL if not in use). + RSType* rPlusPlusTreeRS; public: /** diff --git a/src/mlpack/methods/range_search/rs_model_impl.hpp b/src/mlpack/methods/range_search/rs_model_impl.hpp index 0f308d4712..98fa7a8224 100644 --- a/src/mlpack/methods/range_search/rs_model_impl.hpp +++ b/src/mlpack/methods/range_search/rs_model_impl.hpp @@ -57,6 +57,14 @@ void RSModel::Serialize(Archive& ar, const unsigned int /* version */) case HILBERT_R_TREE: ar & CreateNVP(hilbertRTreeRS, "range_search_model"); break; + + case R_PLUS_TREE: + ar & CreateNVP(rPlusTreeRS, "range_search_model"); + break; + + case R_PLUS_PLUS_TREE: + ar & CreateNVP(rPlusPlusTreeRS, "range_search_model"); + break; } } @@ -76,6 +84,10 @@ inline const arma::mat& RSModel::Dataset() const return xTreeRS->ReferenceSet(); else if (hilbertRTreeRS) return hilbertRTreeRS->ReferenceSet(); + else if (rPlusTreeRS) + return rPlusTreeRS->ReferenceSet(); + else if (rPlusPlusTreeRS) + return rPlusPlusTreeRS->ReferenceSet(); throw std::runtime_error("no range search model initialized"); } @@ -96,6 +108,10 @@ inline bool RSModel::SingleMode() const return xTreeRS->SingleMode(); else if (hilbertRTreeRS) return hilbertRTreeRS->SingleMode(); + else if (rPlusTreeRS) + return rPlusTreeRS->SingleMode(); + else if (rPlusPlusTreeRS) + return rPlusPlusTreeRS->SingleMode(); throw std::runtime_error("no range search model initialized"); } @@ -116,6 +132,10 @@ inline bool& RSModel::SingleMode() return xTreeRS->SingleMode(); else if (hilbertRTreeRS) return hilbertRTreeRS->SingleMode(); + else if (rPlusTreeRS) + return rPlusTreeRS->SingleMode(); + else if (rPlusPlusTreeRS) + return rPlusPlusTreeRS->SingleMode(); throw std::runtime_error("no range search model initialized"); } @@ -136,6 +156,10 @@ inline bool RSModel::Naive() const return xTreeRS->Naive(); else if (hilbertRTreeRS) return hilbertRTreeRS->Naive(); + else if (rPlusTreeRS) + return rPlusTreeRS->Naive(); + else if (rPlusPlusTreeRS) + return rPlusPlusTreeRS->Naive(); throw std::runtime_error("no range search model initialized"); } @@ -156,6 +180,10 @@ inline bool& RSModel::Naive() return xTreeRS->Naive(); else if (hilbertRTreeRS) return hilbertRTreeRS->Naive(); + else if (rPlusTreeRS) + return rPlusTreeRS->Naive(); + else if (rPlusPlusTreeRS) + return rPlusPlusTreeRS->Naive(); throw std::runtime_error("no range search model initialized"); } diff --git a/src/mlpack/methods/rann/krann_main.cpp b/src/mlpack/methods/rann/krann_main.cpp index bcd57e188b..f9a6c67547 100644 --- a/src/mlpack/methods/rann/krann_main.cpp +++ b/src/mlpack/methods/rann/krann_main.cpp @@ -64,9 +64,10 @@ PARAM_INT("k", "Number of nearest neighbors to find.", "k", 0); // The user may specify the type of tree to use, and a few parameters for tree // building. PARAM_STRING("tree_type", "Type of tree to use: 'kd', 'cover', 'r', or " - "'x', 'r-star', 'hilbert-r'.", "t", "kd"); + "'x', 'r-star', 'hilbert-r', 'r-plus', 'r-plus-plus'.", "t", "kd"); PARAM_INT("leaf_size", "Leaf size for tree building (used for kd-trees, R " - "trees, and R* trees).", "l", 20); + "trees, R* trees, X trees, Hilbert R trees, R+ trees and R++ trees).", "l", + 20); PARAM_FLAG("random_basis", "Before tree-building, project the data onto a " "random orthogonal basis.", "R"); PARAM_INT("seed", "Random seed (if 0, std::time(NULL) is used).", "s", 0); @@ -174,9 +175,14 @@ int main(int argc, char *argv[]) tree = RANNModel::X_TREE; else if (treeType == "hilbert-r") tree = RANNModel::HILBERT_R_TREE; + else if (treeType == "r-plus") + tree = RANNModel::R_PLUS_TREE; + else if (treeType == "r-plus-plus") + tree = RANNModel::R_PLUS_PLUS_TREE; else Log::Fatal << "Unknown tree type '" << treeType << "'; valid choices are " - << "'kd', 'cover', 'r', 'r-star', 'x' and 'hilbert-r'." << endl; + << "'kd', 'cover', 'r', 'r-star', 'x', 'hilbert-r', " + << "'r-plus' and 'r-plus-plus'." << endl; rann.TreeType() = tree; rann.RandomBasis() = randomBasis; diff --git a/src/mlpack/methods/rann/ra_model.hpp b/src/mlpack/methods/rann/ra_model.hpp index 2c929796d9..1e755d3db4 100644 --- a/src/mlpack/methods/rann/ra_model.hpp +++ b/src/mlpack/methods/rann/ra_model.hpp @@ -41,7 +41,9 @@ class RAModel R_TREE, R_STAR_TREE, X_TREE, - HILBERT_R_TREE + HILBERT_R_TREE, + R_PLUS_TREE, + R_PLUS_PLUS_TREE }; private: @@ -76,6 +78,10 @@ class RAModel RAType* xTreeRA; //! Non-NULL if the Hilbert R tree is used. RAType* hilbertRTreeRA; + //! Non-NULL if the R+ tree is used. + RAType* rPlusTreeRA; + //! Non-NULL if the R++ tree is used. + RAType* rPlusPlusTreeRA; public: /** diff --git a/src/mlpack/methods/rann/ra_model_impl.hpp b/src/mlpack/methods/rann/ra_model_impl.hpp index edaf03866b..f096540614 100644 --- a/src/mlpack/methods/rann/ra_model_impl.hpp +++ b/src/mlpack/methods/rann/ra_model_impl.hpp @@ -23,7 +23,9 @@ RAModel::RAModel(const TreeTypes treeType, const bool randomBasis) : rTreeRA(NULL), rStarTreeRA(NULL), xTreeRA(NULL), - hilbertRTreeRA(NULL) + hilbertRTreeRA(NULL), + rPlusTreeRA(NULL), + rPlusPlusTreeRA(NULL) { // Nothing to do. } @@ -43,6 +45,10 @@ RAModel::~RAModel() delete xTreeRA; if (hilbertRTreeRA) delete hilbertRTreeRA; + if (rPlusTreeRA) + delete rPlusTreeRA; + if (rPlusPlusTreeRA) + delete rPlusPlusTreeRA; } template @@ -69,6 +75,10 @@ void RAModel::Serialize(Archive& ar, delete xTreeRA; if (hilbertRTreeRA) delete hilbertRTreeRA; + if (rPlusTreeRA) + delete rPlusTreeRA; + if (rPlusPlusTreeRA) + delete rPlusPlusTreeRA; // Set all the pointers to NULL. kdTreeRA = NULL; @@ -77,6 +87,8 @@ void RAModel::Serialize(Archive& ar, rStarTreeRA = NULL; xTreeRA = NULL; hilbertRTreeRA = NULL; + rPlusPlusTreeRA = NULL; + rPlusTreeRA = NULL; } // We only need to serialize one of the kRANN objects. @@ -100,6 +112,12 @@ void RAModel::Serialize(Archive& ar, case HILBERT_R_TREE: ar & data::CreateNVP(hilbertRTreeRA, "ra_model"); break; + case R_PLUS_TREE: + ar & data::CreateNVP(rPlusTreeRA, "ra_model"); + break; + case R_PLUS_PLUS_TREE: + ar & data::CreateNVP(rPlusPlusTreeRA, "ra_model"); + break; } } @@ -118,6 +136,10 @@ const arma::mat& RAModel::Dataset() const return xTreeRA->ReferenceSet(); else if (hilbertRTreeRA) return hilbertRTreeRA->ReferenceSet(); + else if (rPlusTreeRA) + return rPlusTreeRA->ReferenceSet(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->ReferenceSet(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -138,6 +160,10 @@ bool RAModel::Naive() const return xTreeRA->Naive(); else if (hilbertRTreeRA) return hilbertRTreeRA->Naive(); + else if (rPlusTreeRA) + return rPlusTreeRA->Naive(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->Naive(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -158,6 +184,10 @@ bool& RAModel::Naive() return xTreeRA->Naive(); else if (hilbertRTreeRA) return hilbertRTreeRA->Naive(); + else if (rPlusTreeRA) + return rPlusTreeRA->Naive(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->Naive(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -178,6 +208,10 @@ bool RAModel::SingleMode() const return xTreeRA->SingleMode(); else if (hilbertRTreeRA) return hilbertRTreeRA->SingleMode(); + else if (rPlusTreeRA) + return rPlusTreeRA->SingleMode(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->SingleMode(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -198,6 +232,10 @@ bool& RAModel::SingleMode() return xTreeRA->SingleMode(); else if (hilbertRTreeRA) return hilbertRTreeRA->SingleMode(); + else if (rPlusTreeRA) + return rPlusTreeRA->SingleMode(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->SingleMode(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -218,6 +256,10 @@ double RAModel::Tau() const return xTreeRA->Tau(); else if (hilbertRTreeRA) return hilbertRTreeRA->Tau(); + else if (rPlusTreeRA) + return rPlusTreeRA->Tau(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->Tau(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -238,6 +280,10 @@ double& RAModel::Tau() return xTreeRA->Tau(); else if (hilbertRTreeRA) return hilbertRTreeRA->Tau(); + else if (rPlusTreeRA) + return rPlusTreeRA->Tau(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->Tau(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -258,6 +304,10 @@ double RAModel::Alpha() const return xTreeRA->Alpha(); else if (hilbertRTreeRA) return hilbertRTreeRA->Alpha(); + else if (rPlusTreeRA) + return rPlusTreeRA->Alpha(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->Alpha(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -278,6 +328,10 @@ double& RAModel::Alpha() return xTreeRA->Alpha(); else if (hilbertRTreeRA) return hilbertRTreeRA->Alpha(); + else if (rPlusTreeRA) + return rPlusTreeRA->Alpha(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->Alpha(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -298,6 +352,10 @@ bool RAModel::SampleAtLeaves() const return xTreeRA->SampleAtLeaves(); else if (hilbertRTreeRA) return hilbertRTreeRA->SampleAtLeaves(); + else if (rPlusTreeRA) + return rPlusTreeRA->SampleAtLeaves(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->SampleAtLeaves(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -318,6 +376,10 @@ bool& RAModel::SampleAtLeaves() return xTreeRA->SampleAtLeaves(); else if (hilbertRTreeRA) return hilbertRTreeRA->SampleAtLeaves(); + else if (rPlusTreeRA) + return rPlusTreeRA->SampleAtLeaves(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->SampleAtLeaves(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -338,6 +400,10 @@ bool RAModel::FirstLeafExact() const return xTreeRA->FirstLeafExact(); else if (hilbertRTreeRA) return hilbertRTreeRA->FirstLeafExact(); + else if (rPlusTreeRA) + return rPlusTreeRA->FirstLeafExact(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->FirstLeafExact(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -358,6 +424,10 @@ bool& RAModel::FirstLeafExact() return xTreeRA->FirstLeafExact(); else if (hilbertRTreeRA) return hilbertRTreeRA->FirstLeafExact(); + else if (rPlusTreeRA) + return rPlusTreeRA->FirstLeafExact(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->FirstLeafExact(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -378,6 +448,10 @@ size_t RAModel::SingleSampleLimit() const return xTreeRA->SingleSampleLimit(); else if (hilbertRTreeRA) return hilbertRTreeRA->SingleSampleLimit(); + else if (rPlusTreeRA) + return rPlusTreeRA->SingleSampleLimit(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->SingleSampleLimit(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -398,6 +472,10 @@ size_t& RAModel::SingleSampleLimit() return xTreeRA->SingleSampleLimit(); else if (hilbertRTreeRA) return hilbertRTreeRA->SingleSampleLimit(); + else if (rPlusTreeRA) + return rPlusTreeRA->SingleSampleLimit(); + else if (rPlusPlusTreeRA) + return rPlusPlusTreeRA->SingleSampleLimit(); throw std::runtime_error("no rank-approximate nearest neighbor search model " "initialized"); @@ -465,6 +543,10 @@ void RAModel::BuildModel(arma::mat&& referenceSet, delete xTreeRA; if (hilbertRTreeRA) delete hilbertRTreeRA; + if (rPlusTreeRA) + delete rPlusTreeRA; + if (rPlusPlusTreeRA) + delete rPlusPlusTreeRA; if (randomBasis) referenceSet = q * referenceSet; @@ -517,6 +599,14 @@ void RAModel::BuildModel(arma::mat&& referenceSet, hilbertRTreeRA = new RAType(std::move(referenceSet), naive, singleMode); break; + case R_PLUS_TREE: + rPlusTreeRA = new RAType(std::move(referenceSet), + naive, singleMode); + break; + case R_PLUS_PLUS_TREE: + rPlusPlusTreeRA = new RAType(std::move(referenceSet), + naive, singleMode); + break; } if (!naive) @@ -598,6 +688,14 @@ void RAModel::Search(arma::mat&& querySet, // No mapping necessary. hilbertRTreeRA->Search(querySet, k, neighbors, distances); break; + case R_PLUS_TREE: + // No mapping necessary. + rPlusTreeRA->Search(querySet, k, neighbors, distances); + break; + case R_PLUS_PLUS_TREE: + // No mapping necessary. + rPlusPlusTreeRA->Search(querySet, k, neighbors, distances); + break; } } @@ -635,6 +733,12 @@ void RAModel::Search(const size_t k, case HILBERT_R_TREE: hilbertRTreeRA->Search(k, neighbors, distances); break; + case R_PLUS_TREE: + rPlusTreeRA->Search(k, neighbors, distances); + break; + case R_PLUS_PLUS_TREE: + rPlusPlusTreeRA->Search(k, neighbors, distances); + break; } } @@ -655,6 +759,10 @@ std::string RAModel::TreeName() const return "X tree"; case HILBERT_R_TREE: return "Hilbert R tree"; + case R_PLUS_TREE: + return "R+ tree"; + case R_PLUS_PLUS_TREE: + return "R++ tree"; default: return "unknown tree"; } diff --git a/src/mlpack/tests/aknn_test.cpp b/src/mlpack/tests/aknn_test.cpp index 6a7f7343f1..f38978dbfe 100644 --- a/src/mlpack/tests/aknn_test.cpp +++ b/src/mlpack/tests/aknn_test.cpp @@ -352,7 +352,7 @@ BOOST_AUTO_TEST_CASE(KNNModelMonochromaticTest) arma::mat referenceData = arma::randu(10, 200); // Build all the possible models. - KNNModel models[14]; + KNNModel models[18]; models[0] = KNNModel(KNNModel::TreeTypes::KD_TREE, true); models[1] = KNNModel(KNNModel::TreeTypes::KD_TREE, false); models[2] = KNNModel(KNNModel::TreeTypes::COVER_TREE, true); @@ -367,6 +367,10 @@ BOOST_AUTO_TEST_CASE(KNNModelMonochromaticTest) models[11] = KNNModel(KNNModel::TreeTypes::BALL_TREE, false); models[12] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, true); models[13] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, false); + models[14] = KNNModel(KNNModel::TreeTypes::R_PLUS_TREE, true); + models[15] = KNNModel(KNNModel::TreeTypes::R_PLUS_TREE, false); + models[16] = KNNModel(KNNModel::TreeTypes::R_PLUS_PLUS_TREE, true); + models[17] = KNNModel(KNNModel::TreeTypes::R_PLUS_PLUS_TREE, false); for (size_t j = 0; j < 2; ++j) { @@ -376,7 +380,7 @@ BOOST_AUTO_TEST_CASE(KNNModelMonochromaticTest) arma::mat distancesExact; exact.Search(3, neighborsExact, distancesExact); - for (size_t i = 0; i < 14; ++i) + for (size_t i = 0; i < 18; ++i) { // We only have a std::move() constructor... so copy the data. arma::mat referenceCopy(referenceData); diff --git a/src/mlpack/tests/knn_test.cpp b/src/mlpack/tests/knn_test.cpp index 398aee508b..0de22b8b95 100644 --- a/src/mlpack/tests/knn_test.cpp +++ b/src/mlpack/tests/knn_test.cpp @@ -977,7 +977,7 @@ BOOST_AUTO_TEST_CASE(KNNModelTest) arma::mat referenceData = arma::randu(10, 200); // Build all the possible models. - KNNModel models[14]; + KNNModel models[18]; models[0] = KNNModel(KNNModel::TreeTypes::KD_TREE, true); models[1] = KNNModel(KNNModel::TreeTypes::KD_TREE, false); models[2] = KNNModel(KNNModel::TreeTypes::COVER_TREE, true); @@ -992,6 +992,10 @@ BOOST_AUTO_TEST_CASE(KNNModelTest) models[11] = KNNModel(KNNModel::TreeTypes::BALL_TREE, false); models[12] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, true); models[13] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, false); + models[14] = KNNModel(KNNModel::TreeTypes::R_PLUS_TREE, true); + models[15] = KNNModel(KNNModel::TreeTypes::R_PLUS_TREE, false); + models[16] = KNNModel(KNNModel::TreeTypes::R_PLUS_PLUS_TREE, true); + models[17] = KNNModel(KNNModel::TreeTypes::R_PLUS_PLUS_TREE, false); for (size_t j = 0; j < 2; ++j) { @@ -1001,7 +1005,7 @@ BOOST_AUTO_TEST_CASE(KNNModelTest) arma::mat baselineDistances; knn.Search(queryData, 3, baselineNeighbors, baselineDistances); - for (size_t i = 0; i < 14; ++i) + for (size_t i = 0; i < 18; ++i) { // We only have std::move() constructors so make a copy of our data. arma::mat referenceCopy(referenceData); @@ -1045,7 +1049,7 @@ BOOST_AUTO_TEST_CASE(KNNModelMonochromaticTest) arma::mat referenceData = arma::randu(10, 200); // Build all the possible models. - KNNModel models[14]; + KNNModel models[18]; models[0] = KNNModel(KNNModel::TreeTypes::KD_TREE, true); models[1] = KNNModel(KNNModel::TreeTypes::KD_TREE, false); models[2] = KNNModel(KNNModel::TreeTypes::COVER_TREE, true); @@ -1060,6 +1064,10 @@ BOOST_AUTO_TEST_CASE(KNNModelMonochromaticTest) models[11] = KNNModel(KNNModel::TreeTypes::BALL_TREE, false); models[12] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, true); models[13] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, false); + models[14] = KNNModel(KNNModel::TreeTypes::R_PLUS_TREE, true); + models[15] = KNNModel(KNNModel::TreeTypes::R_PLUS_TREE, false); + models[16] = KNNModel(KNNModel::TreeTypes::R_PLUS_PLUS_TREE, true); + models[17] = KNNModel(KNNModel::TreeTypes::R_PLUS_PLUS_TREE, false); for (size_t j = 0; j < 2; ++j) { @@ -1069,7 +1077,7 @@ BOOST_AUTO_TEST_CASE(KNNModelMonochromaticTest) arma::mat baselineDistances; knn.Search(3, baselineNeighbors, baselineDistances); - for (size_t i = 0; i < 14; ++i) + for (size_t i = 0; i < 18; ++i) { // We only have a std::move() constructor... so copy the data. arma::mat referenceCopy(referenceData); diff --git a/src/mlpack/tests/krann_search_test.cpp b/src/mlpack/tests/krann_search_test.cpp index fa95c543f1..2a7f263c59 100644 --- a/src/mlpack/tests/krann_search_test.cpp +++ b/src/mlpack/tests/krann_search_test.cpp @@ -625,7 +625,7 @@ BOOST_AUTO_TEST_CASE(RAModelTest) data::Load("rann_test_q_3_100.csv", queryData, true); // Build all the possible models. - KNNModel models[12]; + KNNModel models[16]; models[0] = KNNModel(KNNModel::TreeTypes::KD_TREE, false); models[1] = KNNModel(KNNModel::TreeTypes::KD_TREE, true); models[2] = KNNModel(KNNModel::TreeTypes::COVER_TREE, false); @@ -638,13 +638,17 @@ BOOST_AUTO_TEST_CASE(RAModelTest) models[9] = KNNModel(KNNModel::TreeTypes::X_TREE, true); models[10] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, false); models[11] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, true); + models[12] = KNNModel(KNNModel::TreeTypes::R_PLUS_TREE, false); + models[13] = KNNModel(KNNModel::TreeTypes::R_PLUS_TREE, true); + models[14] = KNNModel(KNNModel::TreeTypes::R_PLUS_PLUS_TREE, false); + models[15] = KNNModel(KNNModel::TreeTypes::R_PLUS_PLUS_TREE, true); arma::Mat qrRanks; data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose. for (size_t j = 0; j < 3; ++j) { - for (size_t i = 0; i < 12; ++i) + for (size_t i = 0; i < 16; ++i) { // We only have std::move() constructors so make a copy of our data. arma::mat referenceCopy(referenceData); diff --git a/src/mlpack/tests/range_search_test.cpp b/src/mlpack/tests/range_search_test.cpp index 43a8ace58f..7f28f2e24e 100644 --- a/src/mlpack/tests/range_search_test.cpp +++ b/src/mlpack/tests/range_search_test.cpp @@ -1249,7 +1249,7 @@ BOOST_AUTO_TEST_CASE(RSModelTest) arma::mat referenceData = arma::randu(10, 200); // Build all the possible models. - RSModel models[14]; + RSModel models[18]; models[0] = RSModel(RSModel::TreeTypes::KD_TREE, true); models[1] = RSModel(RSModel::TreeTypes::KD_TREE, false); models[2] = RSModel(RSModel::TreeTypes::COVER_TREE, true); @@ -1264,6 +1264,10 @@ BOOST_AUTO_TEST_CASE(RSModelTest) models[11] = RSModel(RSModel::TreeTypes::BALL_TREE, false); models[12] = RSModel(RSModel::TreeTypes::HILBERT_R_TREE, true); models[13] = RSModel(RSModel::TreeTypes::HILBERT_R_TREE, false); + models[14] = RSModel(RSModel::TreeTypes::R_PLUS_TREE, true); + models[15] = RSModel(RSModel::TreeTypes::R_PLUS_TREE, false); + models[16] = RSModel(RSModel::TreeTypes::R_PLUS_PLUS_TREE, true); + models[17] = RSModel(RSModel::TreeTypes::R_PLUS_PLUS_TREE, false); for (size_t j = 0; j < 2; ++j) { @@ -1277,7 +1281,7 @@ BOOST_AUTO_TEST_CASE(RSModelTest) vector>> baselineSorted; SortResults(baselineNeighbors, baselineDistances, baselineSorted); - for (size_t i = 0; i < 14; ++i) + for (size_t i = 0; i < 18; ++i) { // We only have std::move() constructors, so make a copy of our data. arma::mat referenceCopy(referenceData); @@ -1321,7 +1325,7 @@ BOOST_AUTO_TEST_CASE(RSModelMonochromaticTest) arma::mat referenceData = arma::randu(10, 200); // Build all the possible models. - RSModel models[14]; + RSModel models[18]; models[0] = RSModel(RSModel::TreeTypes::KD_TREE, true); models[1] = RSModel(RSModel::TreeTypes::KD_TREE, false); models[2] = RSModel(RSModel::TreeTypes::COVER_TREE, true); @@ -1336,6 +1340,10 @@ BOOST_AUTO_TEST_CASE(RSModelMonochromaticTest) models[11] = RSModel(RSModel::TreeTypes::BALL_TREE, false); models[12] = RSModel(RSModel::TreeTypes::HILBERT_R_TREE, true); models[13] = RSModel(RSModel::TreeTypes::HILBERT_R_TREE, false); + models[14] = RSModel(RSModel::TreeTypes::R_PLUS_TREE, true); + models[15] = RSModel(RSModel::TreeTypes::R_PLUS_TREE, false); + models[16] = RSModel(RSModel::TreeTypes::R_PLUS_PLUS_TREE, true); + models[17] = RSModel(RSModel::TreeTypes::R_PLUS_PLUS_TREE, false); for (size_t j = 0; j < 2; ++j) { @@ -1348,7 +1356,7 @@ BOOST_AUTO_TEST_CASE(RSModelMonochromaticTest) vector>> baselineSorted; SortResults(baselineNeighbors, baselineDistances, baselineSorted); - for (size_t i = 0; i < 14; ++i) + for (size_t i = 0; i < 18; ++i) { // We only have std::move() cosntructors, so make a copy of our data. arma::mat referenceCopy(referenceData); diff --git a/src/mlpack/tests/rectangle_tree_test.cpp b/src/mlpack/tests/rectangle_tree_test.cpp index e6aedd918f..5ba1ec8aec 100644 --- a/src/mlpack/tests/rectangle_tree_test.cpp +++ b/src/mlpack/tests/rectangle_tree_test.cpp @@ -137,7 +137,17 @@ void CheckContainment(const TreeType& tree) for (size_t i = 0; i < tree.NumChildren(); i++) { for (size_t j = 0; j < tree.Bound().Dim(); j++) - BOOST_REQUIRE(tree.Bound()[j].Contains(tree.Child(i).Bound()[j])); + { + // All children should be covered by the parent node. + // Some children can be empty (only in case of the R++ tree) + bool success = (tree.Child(i).Bound()[j].Hi() == + std::numeric_limits::lowest() && + tree.Child(i).Bound()[j].Lo() == + std::numeric_limits::max()) || + tree.Bound()[j].Contains(tree.Child(i).Bound()[j]); + + BOOST_REQUIRE(success); + } CheckContainment(tree.Child(i)); } @@ -854,6 +864,223 @@ BOOST_AUTO_TEST_CASE(DiscreteHilbertValueTest) point4), -1); } +template +void CheckOverlap(const TreeType& tree) +{ + bool success = true; + + // Check if two nodes overlap each other. + for (size_t i = 0; i < tree.NumChildren(); i++) + { + success = true; + + for (size_t j = 0; j < tree.NumChildren(); j++) + { + if (j == i) + continue; + + success = !tree.Child(i).Bound().Contains(tree.Child(j).Bound()); + + if (!success) + break; + } + if (!success) + break; + } + BOOST_REQUIRE_EQUAL(success, true); + + for (size_t i = 0; i < tree.NumChildren(); i++) + CheckOverlap(tree.Child(i)); +} + +BOOST_AUTO_TEST_CASE(RPlusTreeOverlapTest) +{ + arma::mat dataset; + dataset.randu(8, 1000); // 1000 points in 8 dimensions. + + typedef RPlusTree,arma::mat> TreeType; + TreeType rPlusTree(dataset, 20, 6, 5, 2, 0); + + CheckOverlap(rPlusTree); + + // Ensure that all leaf nodes are at the same level. + BOOST_REQUIRE_EQUAL(GetMinLevel(rPlusTree), GetMaxLevel(rPlusTree)); + BOOST_REQUIRE_EQUAL(rPlusTree.TreeDepth(), GetMinLevel(rPlusTree)); +} + + +BOOST_AUTO_TEST_CASE(RPlusTreeTraverserTest) +{ + arma::mat dataset; + + const int numP = 1000; + + dataset.randu(8, numP); // 1000 points in 8 dimensions. + arma::Mat neighbors1; + arma::mat distances1; + arma::Mat neighbors2; + arma::mat distances2; + + typedef RPlusTree, + arma::mat > TreeType; + TreeType rPlusTree(dataset, 20, 6, 5, 2, 0); + + // Nearest neighbor search with the R+ tree. + + NeighborSearch, arma::mat, + RPlusTree > knn1(&rPlusTree, true); + + BOOST_REQUIRE_EQUAL(rPlusTree.NumDescendants(), numP); + + CheckContainment(rPlusTree); + CheckExactContainment(rPlusTree); + CheckHierarchy(rPlusTree); + CheckOverlap(rPlusTree); + CheckNumDescendants(rPlusTree); + + knn1.Search(5, neighbors1, distances1); + + // Nearest neighbor search the naive way. + KNN knn2(dataset, true, true); + + knn2.Search(5, neighbors2, distances2); + + for (size_t i = 0; i < neighbors1.size(); i++) + { + BOOST_REQUIRE_EQUAL(neighbors1[i], neighbors2[i]); + BOOST_REQUIRE_EQUAL(distances1[i], distances2[i]); + } +} + +template +void CheckRPlusPlusTreeBound(const TreeType& tree) +{ + typedef bound::HRectBound Bound; + + bool success = true; + + // Ensure that the maximum bounding rectangle contains all children. + for (size_t k = 0; k < tree.Bound().Dim(); k++) + { + BOOST_REQUIRE_LE(tree.Bound()[k].Hi(), + tree.AuxiliaryInfo().OuterBound()[k].Hi()); + BOOST_REQUIRE_LE(tree.AuxiliaryInfo().OuterBound()[k].Lo(), + tree.Bound()[k].Lo()); + } + + if (tree.IsLeaf()) + { + // Ensure that the maximum bounding rectangle contains all points. + for (size_t i = 0; i < tree.Count(); i++) + BOOST_REQUIRE_EQUAL(true, + tree.Bound().Contains(tree.Dataset().col(tree.Point(i)))); + + return; + } + + // Ensure that two children's maximum bounding rectangles do not overlap + // each other. + for (size_t i = 0; i < tree.NumChildren(); i++) + { + const Bound& bound1 = tree.Child(i).AuxiliaryInfo().OuterBound(); + success = true; + + for (size_t j = 0; j < tree.NumChildren(); j++) + { + if (j == i) + continue; + const Bound& bound2 = tree.Child(j).AuxiliaryInfo().OuterBound(); + + success = !bound1.Contains(bound2); + + if (!success) + break; + } + if (!success) + break; + } + BOOST_REQUIRE_EQUAL(success, true); + + for (size_t i = 0; i < tree.NumChildren(); i++) + CheckRPlusPlusTreeBound(tree.Child(i)); +} + +BOOST_AUTO_TEST_CASE(RPlusPlusTreeBoundTest) +{ + arma::mat dataset; + dataset.randu(8, 1000); // 1000 points in 8 dimensions. + + // Check the MinimalCoverageSweep. + typedef RPlusPlusTree,arma::mat> TreeType; + TreeType rPlusPlusTree(dataset, 20, 6, 5, 2, 0); + + CheckRPlusPlusTreeBound(rPlusPlusTree); + + BOOST_REQUIRE_EQUAL(GetMinLevel(rPlusPlusTree), GetMaxLevel(rPlusPlusTree)); + BOOST_REQUIRE_EQUAL(rPlusPlusTree.TreeDepth(), GetMinLevel(rPlusPlusTree)); + + // Check the MinimalSplitsNumberSweep. + typedef RectangleTree, arma::mat, + RPlusTreeSplit, + RPlusPlusTreeDescentHeuristic, RPlusPlusTreeAuxiliaryInformation> + RPlusPlusTreeMinimalSplits; + + RPlusPlusTreeMinimalSplits rPlusPlusTree2(dataset, 20, 6, 5, 2, 0); + + CheckRPlusPlusTreeBound(rPlusPlusTree2); + + BOOST_REQUIRE_EQUAL(GetMinLevel(rPlusPlusTree2), GetMaxLevel(rPlusPlusTree2)); + BOOST_REQUIRE_EQUAL(rPlusPlusTree2.TreeDepth(), GetMinLevel(rPlusPlusTree2)); +} + +BOOST_AUTO_TEST_CASE(RPlusPlusTreeTraverserTest) +{ + arma::mat dataset; + + const int numP = 1000; + + dataset.randu(8, numP); // 1000 points in 8 dimensions. + arma::Mat neighbors1; + arma::mat distances1; + arma::Mat neighbors2; + arma::mat distances2; + + typedef RPlusPlusTree, arma::mat > TreeType; + TreeType rPlusPlusTree(dataset, 20, 6, 5, 2, 0); + + // Nearest neighbor search with the R++ tree. + + NeighborSearch, + arma::mat, RPlusPlusTree > knn1(&rPlusPlusTree, true); + + BOOST_REQUIRE_EQUAL(rPlusPlusTree.NumDescendants(), numP); + + CheckContainment(rPlusPlusTree); + CheckExactContainment(rPlusPlusTree); + CheckHierarchy(rPlusPlusTree); + CheckRPlusPlusTreeBound(rPlusPlusTree); + CheckNumDescendants(rPlusPlusTree); + + knn1.Search(5, neighbors1, distances1); + + // Nearest neighbor search the naive way. + KNN knn2(dataset, true, true); + + knn2.Search(5, neighbors2, distances2); + + for (size_t i = 0; i < neighbors1.size(); i++) + { + BOOST_REQUIRE_EQUAL(neighbors1[i], neighbors2[i]); + BOOST_REQUIRE_EQUAL(distances1[i], distances2[i]); + } +} + + // Test the tree splitting. We set MaxLeafSize and MaxNumChildren rather low // to allow us to test by hand without adding hundreds of points. BOOST_AUTO_TEST_CASE(RTreeSplitTest) diff --git a/src/mlpack/tests/tree_test.cpp b/src/mlpack/tests/tree_test.cpp index 81a94463b2..4c68bb93d1 100644 --- a/src/mlpack/tests/tree_test.cpp +++ b/src/mlpack/tests/tree_test.cpp @@ -1272,10 +1272,6 @@ void GenerateVectorOfTree(TreeType* node, size_t depth, std::vector& v); -template -bool DoBoundsIntersect(HRectBound& a, - HRectBound& b); - /** * Exhaustive kd-tree test based on #125. * @@ -1344,7 +1340,7 @@ BOOST_AUTO_TEST_CASE(KdTreeTest) for (size_t i = depth; i < 2 * depth && i < v.size(); i++) for (size_t j = i + 1; j < 2 * depth && j < v.size(); j++) if (v[i] != NULL && v[j] != NULL) - BOOST_REQUIRE(!DoBoundsIntersect(v[i]->Bound(), v[j]->Bound())); + BOOST_REQUIRE(!v[i]->Bound().Contains(v[j]->Bound())); depth *= 2; } @@ -1430,26 +1426,6 @@ BOOST_AUTO_TEST_CASE(BallTreeTest) } } -template -bool DoBoundsIntersect(HRectBound& a, - HRectBound& b) -{ - size_t dimensionality = a.Dim(); - - Range r_a; - Range r_b; - - for (size_t i = 0; i < dimensionality; i++) - { - r_a = a[i]; - r_b = b[i]; - if (r_a < r_b || r_a > r_b) // If a does not overlap b at all. - return false; - } - - return true; -} - template void GenerateVectorOfTree(TreeType* node, size_t depth, @@ -1541,7 +1517,7 @@ BOOST_AUTO_TEST_CASE(ExhaustiveSparseKDTreeTest) for (size_t i = depth; i < 2 * depth && i < v.size(); i++) for (size_t j = i + 1; j < 2 * depth && j < v.size(); j++) if (v[i] != NULL && v[j] != NULL) - BOOST_REQUIRE(!DoBoundsIntersect(v[i]->Bound(), v[j]->Bound())); + BOOST_REQUIRE(!v[i]->Bound().Contains(v[j]->Bound())); depth *= 2; }