diff --git a/.github/actions/binding_setup/action.yml b/.github/actions/binding_setup/action.yml
index 9bbdddcf5a..16f3c05ff6 100644
--- a/.github/actions/binding_setup/action.yml
+++ b/.github/actions/binding_setup/action.yml
@@ -26,7 +26,6 @@ runs:
if: inputs.lang == 'Python' && runner.os == 'macOS'
shell: bash
run: |
- /opt/homebrew/bin/python3 -m pip install --break-system-packages --upgrade pip
/opt/homebrew/bin/python3 -m pip install --break-system-packages setuptools cython pandas zipp configparser wheel pytest
echo "CMAKE_BINDING_ARGS=-DPYTHON_EXECUTABLE=/opt/homebrew/bin/python3" >> $GITHUB_ENV
diff --git a/doc/sidebar.html b/doc/sidebar.html
index 85bb8edffa..c13dea7e9d 100644
--- a/doc/sidebar.html
+++ b/doc/sidebar.html
@@ -190,6 +190,31 @@ when the sidebar is built for each page.
RectangleTree
+
+
+ SPTree
+
+
+
+
+ MeanSPTree
+
+
+
+
+ NonOrtSPTree
+
+
+
+
+ NonOrtMeanSPTree
+
+
+
+
+ SpillTree
+
+
diff --git a/doc/user/core/trees.md b/doc/user/core/trees.md
index dc5443cca1..d259a8a728 100644
--- a/doc/user/core/trees.md
+++ b/doc/user/core/trees.md
@@ -16,10 +16,13 @@ different trees. The following tree types are available in mlpack:
* [`MeanSplitBallTree`](trees/mean_split_ball_tree.md)
* [`RPTree`](trees/rp_tree.md)
* [`MaxRPTree`](trees/max_rp_tree.md)
- * [`BinarySpaceTree`](trees/binary_space_tree.md)
* [`UBTree`](trees/ub_tree.md)
+ * [`BinarySpaceTree`](trees/binary_space_tree.md)
+
* [`CoverTree`](trees/cover_tree.md)
+
* [`Octree`](trees/octree.md)
+
* [`RTree`](trees/r_tree.md)
* [`RStarTree`](trees/r_star_tree.md)
* [`XTree`](trees/x_tree.md)
@@ -28,8 +31,11 @@ different trees. The following tree types are available in mlpack:
* [`HilbertRTree`](trees/hilbert_r_tree.md)
* [`RectangleTree`](trees/rectangle_tree.md)
-*Note:* this documentation is a work in progress. Not all trees are documented
-yet.
+ * [`SPTree`](trees/sp_tree.md)
+ * [`MeanSPTree`](trees/mean_sp_tree.md)
+ * [`NonOrtSPTree`](trees/non_ort_sp_tree.md)
+ * [`NonOrtMeanSPTree`](trees/non_ort_mean_sp_tree.md)
+ * [`SpillTree`](trees/spill_tree.md)
---
diff --git a/doc/user/core/trees/ball_tree.md b/doc/user/core/trees/ball_tree.md
index fc5150f380..2519391c92 100644
--- a/doc/user/core/trees/ball_tree.md
+++ b/doc/user/core/trees/ball_tree.md
@@ -142,13 +142,11 @@ different.
loose bounding balls. It is better to simply build a new `BallTree` on the
modified dataset. For trees that support individual insertion and deletions,
see the [`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
- [`RTree`](r_tree.md), `RStarTree`, etc.).
+ [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
- See also the
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
-
-
---
### Constructor parameters:
@@ -433,7 +431,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
//
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
// `mlpack::BallTree tree(...)` will work fine in C++20 or newer).
-mlpack::BallTree<> tree(std::move(dataset));
+mlpack::BallTree<> tree(std::move(dataset), 10);
// Print the bounding ball of the root node.
std::cout << "Bounding ball of root node:" << std::endl;
diff --git a/doc/user/core/trees/binary_space_tree.md b/doc/user/core/trees/binary_space_tree.md
index f26d7b0df0..1e634de6b6 100644
--- a/doc/user/core/trees/binary_space_tree.md
+++ b/doc/user/core/trees/binary_space_tree.md
@@ -168,13 +168,12 @@ different.
with very loose bounding boxes. It is better to simply build a new
`BinarySpaceTree` on the modified dataset. For trees that support individual
insertion and deletions, see the [`RectangleTree`](rectangle_tree.md) class
- and all its variants (e.g. [`RTree`](r_tree.md), `RStarTree`, etc.).
+ and all its variants (e.g. [`RTree`](r_tree.md),
+ [`RStarTree`](r_star_tree.md), etc.).
- See also the
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
-
-
---
### Constructor parameters:
@@ -2240,7 +2239,7 @@ mlpack::BinarySpaceTree tree(std::move(dataset));
+ mlpack::MidpointSplit> tree(std::move(dataset), 10);
// Print the bounding box of the root node.
std::cout << "Bounding box of root node:" << std::endl;
diff --git a/doc/user/core/trees/cover_tree.md b/doc/user/core/trees/cover_tree.md
index 44311f87ab..8238e7e17c 100644
--- a/doc/user/core/trees/cover_tree.md
+++ b/doc/user/core/trees/cover_tree.md
@@ -101,14 +101,12 @@ dataset.
is not supported, because this generally results in a cover tree with very
loose bounding balls. It is better to simply build a new `CoverTree` on the
modified dataset. For trees that support individual insertion and deletions,
- see the `RectangleTree` class and all its variants (e.g. `RTree`,
- `RStarTree`, etc.).
+ see the [`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
+ [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
- See also the
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
-
-
---
### Constructor parameters:
diff --git a/doc/user/core/trees/hilbert_r_tree.md b/doc/user/core/trees/hilbert_r_tree.md
index 8e507b964c..45b7683515 100644
--- a/doc/user/core/trees/hilbert_r_tree.md
+++ b/doc/user/core/trees/hilbert_r_tree.md
@@ -153,7 +153,7 @@ The dataset is not permuted during the construction process.
| **name** | **type** | **description** | **default** |
|----------|----------|-----------------|-------------|
-| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
+| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
diff --git a/doc/user/core/trees/kdtree.md b/doc/user/core/trees/kdtree.md
index 44a547a6ec..e3659f77d3 100644
--- a/doc/user/core/trees/kdtree.md
+++ b/doc/user/core/trees/kdtree.md
@@ -126,13 +126,11 @@ different.
bounding boxes. It is better to simply build a new `KDTree` on the modified
dataset. For trees that support individual insertion and deletions, see the
[`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
- [`RTree`](r_tree.md), `RStarTree`, etc.).
+ [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
- See also the
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
-
-
---
### Constructor parameters:
@@ -421,7 +419,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
//
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
// `mlpack::KDTree tree(...)` will work fine in C++20 or newer).
-mlpack::KDTree<> tree(std::move(dataset));
+mlpack::KDTree<> tree(std::move(dataset), 10);
// Print the bounding box of the root node.
std::cout << "Bounding box of root node:" << std::endl;
diff --git a/doc/user/core/trees/max_rp_tree.md b/doc/user/core/trees/max_rp_tree.md
index 3fb8d87364..20ed12de12 100644
--- a/doc/user/core/trees/max_rp_tree.md
+++ b/doc/user/core/trees/max_rp_tree.md
@@ -9,10 +9,9 @@ nearest neighbor search) in low dimensions---typically less than 100.
An `MaxRPTree` (or the similar [`RPTree`](rp_tree.md)) may be preferred over
a [`KDTree`](kdtree.md) or other tree structures as it is theoretically known to
-adapt to the intrinsic dimension of the data. This is similar to the cover
-tree, but the implementation is far simpler and as a result, more efficient.
-
-
+adapt to the intrinsic dimension of the data. This is similar to the
+[cover tree](cover_tree.md), but the implementation is far simpler and as a
+result, more efficient.
mlpack's `MaxRPTree` implementation supports three template parameters for
configurable behavior, and implements all the functionality required by the
@@ -137,13 +136,12 @@ different.
with very loose bounding boxes. It is better to simply build a new
`MaxRPTree` on the modified dataset. For trees that support individual
insertion and deletions, see the [`RectangleTree`](rectangle_tree.md) class
- and all its variants (e.g. [`RTree`](r_tree.md), `RStarTree`, etc.).
+ and all its variants (e.g. [`RTree`](r_tree.md),
+ [`RStarTree`](r_star_tree.md), etc.).
- See also the
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
-
-
---
### Constructor parameters:
@@ -432,7 +430,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
//
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
// `mlpack::MaxRPTree tree(...)` will work fine in C++20 or newer).
-mlpack::MaxRPTree<> tree(std::move(dataset));
+mlpack::MaxRPTree<> tree(std::move(dataset), 10);
// Print the bounding box of the root node.
std::cout << "Bounding box of root node:" << std::endl;
diff --git a/doc/user/core/trees/mean_sp_tree.md b/doc/user/core/trees/mean_sp_tree.md
new file mode 100644
index 0000000000..39b6abdaba
--- /dev/null
+++ b/doc/user/core/trees/mean_sp_tree.md
@@ -0,0 +1,754 @@
+# `MeanSPTree`
+
+The `MeanSPTree` class implements the mean-split hybrid spill tree, a binary
+space partitioning tree that allows overlapping volumes between nodes. This
+type of tree can be more effective than trees like the [`KDTree`](kdtree.md) for
+approximate nearest neighbor search and related tasks. `MeanSPTree` is the same
+tree as [`SPTree`](sp_tree.md), except nodes are split using the mean value of
+data points projected onto the splitting hyperplane's tangent vector
+([`SPTree`](sp_tree.md) instead uses the midpoint).
+
+`MeanSPTree` supports three template parameters for configurable behavior, and
+implements all the functionality required by the [TreeType
+API](../../../developer/trees.md#the-treetype-api), plus some additional
+functionality specific to spill trees. `MeanSPTree` is built on the more
+generic [`SpillTree`](spill_tree.md) class, so if fully custom behavior is
+desired, that
+
+ * [Template parameters](#template-parameters)
+ * [Constructors](#constructors)
+ * [Basic tree properties](#basic-tree-properties)
+ * [Bounding distances with the tree](#bounding-distances-with-the-tree)
+ * [Tree traversals](#tree-traversals)
+ * [Example usage](#example-usage)
+
+## See also
+
+
+
+ * [`SpillTree`](spill_tree.md)
+ * [`SPTree`](sp_tree.md)
+ * [`NonOrtSPTree`](non_ort_sp_tree.md)
+ * [`NonOrtMeanSPTree`](non_ort_mean_sp_tree.md)
+ * [`BinarySpaceTree`](binary_space_tree.md)
+ * [An Investigation of Practical Approximate Nearest Neighbor Algorithms (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf)
+ * [Tree-Independent Dual-Tree Algorithms (pdf)](https://www.ratml.org/pub/pdf/2013tree.pdf)
+
+## Template parameters
+
+In accordance with the
+[TreeType API](../../../developer/trees.md#template-parameters-required-by-the-treetype-policy)
+(see also
+[this more detailed section](../../../developer/trees.md#template-parameters)),
+the `MeanSPTree` class takes three template parameters:
+
+```
+MeanSPTree
+```
+
+ * `DistanceType`: the [distance metric](../distances.md) to use for distance
+ computations. Because the `MeanSPTree` internally uses
+ [`HRectBound`](binary_space_tree.md#hrectbound), this is required to be
+ [`EuclideanDistance`](../distances.md#lmetric). See
+ [`NonOrtMeanSPTree`](non_ort_mean_sp_tree.md) for a version of the mean-split
+ spill tree where arbitrary distance metrics are allowed.
+
+ * `StatisticType`: this holds auxiliary information in each tree node. By
+ default, [`EmptyStatistic`](binary_space_tree.md#emptystatistic) is used,
+ which holds no information.
+ - See the [`StatisticType`](binary_space_tree.md#statistictype) section in
+ the `BinarySpaceTree` documentation for more details.
+
+ * `MatType`: the type of matrix used to represent points. Must be a type
+ matching the [Armadillo API](../../matrices.md). By default, `arma::mat` is
+ used, but other types such as `arma::fmat` or similar will work just fine.
+
+The `MeanSPTree` class itself is a convenience typedef of the generic
+[`SpillTree`](spill_tree.md) class, using the
+[`AxisOrthogonalHyperplane`](spill_tree.md#axisorthogonalhyperplane) class as
+the splitting hyperplane type, and the
+[`MeanSpaceSplit`](spill_tree.md#meanspacesplit) class as the splitting
+strategy.
+
+If no template parameters are explicitly specified, then defaults are used:
+
+```
+MeanSPTree<> = MeanSPTree
+```
+
+## Constructors
+
+`MeanSPTree`s are constructed by iteratively finding splitting hyperplanes, and
+points within a margin of the hyperplane are assigned to *both* child nodes.
+Unlike the constructors of
+[`BinarySpaceTree`](binary_space_tree.md#constructors), the dataset is not
+permuted during construction.
+
+---
+
+ * `node = MeanSPTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
+ - Construct a `MeanSPTree` on the given `data`, using the specified
+ hyperparameters to control tree construction behavior.
+ - By default, a reference to `data` is stored. If `data` goes out of scope
+ after tree construction, memory errors will occur! To avoid this, either
+ pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
+ when doing this, `data` will be set to an empty matrix.
+
+---
+
+ * `node = MeanSPTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
+ - Construct a `MeanSPTree` on the given `data`, using custom template
+ parameters, and using the specified hyperparameters to control tree
+ construction behavior.
+ - By default, a reference to `data` is stored. If `data` goes out of scope
+ after tree construction, memory errors will occur! To avoid this, either
+ pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
+ when doing this, `data` will be set to an empty matrix.
+
+---
+
+ * `node = MeanSPTree()`
+ - Construct an empty `MeanSPTree` with no children, no points, and default
+ template parameters.
+
+---
+
+***Notes:***
+
+ - The name `node` is used here for `MeanSPTree` objects instead of `tree`,
+ because each `MeanSPTree` object is a single node in the tree. The
+ constructor returns the node that is the root of the tree.
+
+ - Inserting individual points or removing individual points from a `MeanSPTree`
+ is not supported, because this generally results in a tree with very
+ suboptimal hyperplane splits. It is better to simply build a new
+ `MeanSPTree` on the modified dataset. For trees that support individual
+ insertion and deletions, see the [`RectangleTree`](rectangle_tree.md) class
+ and all its variants (e.g. [`RTree`](r_tree.md),
+ [`RStarTree`](r_star_tree.md), etc.).
+
+ - See also the
+ [developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
+
+---
+
+### Constructor parameters:
+
+| **name** | **type** | **description** | **default** |
+|----------|----------|-----------------|-------------|
+| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
+| `tau` | `double` | Width of spill margin: points within `tau` of the splitting hyperplane of a node will be contained in both left and right children. | `0.0` |
+| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
+| `rho` | `double` | Balance threshold. When splitting, if either overlapping node would contain a fraction of more than `rho` of the points, a non-overlapping split is performed. Must be in the range `[0.0, 1.0)`. | `0.7` |
+
+***Caveats***:
+
+ * `tau` must be manually tuned for the properties of each dataset; the default,
+ `0.0`, will never allow overlap between nodes (and thus the created tree will
+ essentially be a non-overlapping [`BinarySpaceTree`](binary_space_tree.md)).
+
+ * If `tau` is set too large, nodes will overlap too much and search quality
+ will be degraded.
+
+ * `rho` implicitly controls the depth of the tree by forcing very overlapping
+ children to be non-overlapping. As `rho` gets closer to `1`, more overlap is
+ allowed, which in turn makes the tree deeper. If `rho` is set to `0.5` or
+ less, then all splits will be non-overlapping (and the tree will essentially
+ be a [`BinarySpaceTree`](binary_space_tree.md)).
+
+## Basic tree properties
+
+Once a `MeanSPTree` object is constructed, various properties of the tree can be
+accessed or inspected. Many of these functions are required by the
+[TreeType API](../../../developer/trees.md#the-treetype-api).
+
+### Navigating the tree
+
+ * `node.NumChildren()` returns the number of children in `node`. This is
+ either `2` if `node` has children, or `0` if `node` is a leaf.
+
+ * `node.IsLeaf()` returns a `bool` indicating whether or not `node` is a leaf.
+
+ * `node.Child(i)` returns an `MeanSPTree&` that is the `i`th child.
+ - `i` must be `0` or `1`.
+ - This function should only be called if `node.NumChildren()` is not `0`
+ (e.g. if `node` is not a leaf). Note that this returns a valid
+ `MeanSPTree&` that can itself be used just like the root node of the
+ tree!
+ - `node.Left()` and `node.Right()` are convenience functions specific to
+ `MeanSPTree` that will return `MeanSPTree*` (pointers) to the left and
+ right children, respectively, or `NULL` if `node` has no children.
+
+ * `node.Parent()` will return a `MeanSPTree*` that points to the parent of
+ `node`, or `NULL` if `node` is the root of the `MeanSPTree`.
+
+---
+
+### Accessing members of a tree
+
+ * `node.Overlap()` will return a `bool` that is `true` if `node`'s children are
+ overlapping, and `false` otherwise.
+
+ * `node.Hyperplane()` will return an
+ [`AxisOrthogonalHyperplane`](spill_tree.md#axisorthogonalhyperplane) object
+ that represents the axis-aligned splitting hyperplane of `node`.
+ - All points in `node.Left()` are to the left of `node.Hyperplane()` if
+ `node.Overlap()` is `false`; otherwise, all points in `node.Left()` are to
+ the left of `node.Hyperplane() + tau`.
+ - All points in `node.Right()` are to the right of `node.Hyperplane()` if
+ `node.Overlap()` is `false`; otherwise, all points in `node.Right()` are to
+ the right of `node.Hyperplane() - tau`.
+
+ * `node.Bound()` will return a
+ [`const HRectBound&`](binary_space_tree.md#hrectbound) representing the
+ bounding box associated with `node`.
+ - If a [custom `DistanceType` and/or `MatType`](#template-parameters) are
+ specified, then a `const HRectBound&` is returned.
+ * `ElemType` is the element type of the specified `MatType` (e.g. `double`
+ for `arma::mat`, `float` for `arma::fmat`, etc.).
+
+ * `node.Stat()` will return a `StatisticType&` holding the statistics of the
+ node that were computed during tree construction.
+
+ * `node.Distance()` will return a `EuclideanDistance&`. Because
+ `EuclideanDistance` has no instantiated members, this is unlikely to be
+ useful, but is required to satisfy the
+ [`TreeType` API](../../../developer/trees.md#the-treetype-api).
+
+See also the
+[developer documentation](../../../developer/trees.md#basic-tree-functionality)
+for basic tree functionality in mlpack.
+
+---
+
+### Accessing data held in a tree
+
+ * `node.Dataset()` will return a `const MatType&` that is the dataset the
+ tree was built on.
+
+ * `node.NumPoints()` returns a `size_t` indicating the number of points held
+ directly in `node`.
+ - If `node` is not a leaf, this will return `0`, as `MeanSPTree` only holds
+ points directly in its leaves.
+ - If `node` is a leaf, then the number of points will be less than or equal
+ to the `maxLeafSize` that was specified when the tree was constructed.
+
+ * `node.Point(i)` returns a `size_t` indicating the index of the `i`'th point
+ in `node.Dataset()`.
+ - `i` must be in the range `[0, node.NumPoints() - 1]` (inclusive).
+ - `node` must be a leaf (as non-leaves do not hold any points).
+ - The `i`'th point in `node` can then be accessed as
+ `node.Dataset().col(node.Point(i))`.
+ - Accessing the actual `i`'th point itself can be done with, e.g.,
+ `node.Dataset().col(node.Point(i))`.
+
+ * `node.NumDescendants()` returns a `size_t` indicating the number of points
+ held in all descendant leaves of `node`.
+ - If `node` is the root of the tree, then `node.NumDescendants()` will be
+ equal to `node.Dataset().n_cols`.
+
+ * `node.Descendant(i)` returns a `size_t` indicating the index of the `i`'th
+ descendant point in `node.Dataset()`.
+ - `i` must be in the range `[0, node.NumDescendants() - 1]` (inclusive).
+ - `node` does not need to be a leaf.
+ - The `i`'th descendant point in `node` can then be accessed as
+ `node.Dataset().col(node.Descendant(i))`.
+ - Accessing the actual `i`'th descendant itself can be done with, e.g.,
+ `node.Dataset().col(node.Descendant(i))`.
+
+---
+
+### Accessing computed bound quantities of a tree
+
+The following quantities are cached for each node in a `MeanSPTree`, and so
+accessing them does not require any computation. In the documentation below,
+`ElemType` is the element type of the given `MatType`; e.g., if `MatType` is
+`arma::mat`, then `ElemType` is `double`.
+
+ * `node.FurthestPointDistance()` returns an `ElemType` representing the
+ distance between the center of the bound of `node` and the furthest point
+ held by `node`.
+ - If `node` is not a leaf, this returns 0 (because `node` does not hold any
+ points).
+
+ * `node.FurthestDescendantDistance()` returns an `ElemType` representing the
+ distance between the center of the bound of `node` and the furthest
+ descendant point held by `node`.
+
+ * `node.MinimumBoundDistance()` returns an `ElemType` representing the minimum
+ possible distance from the center of the node to any edge of its bound.
+
+ * `node.ParentDistance()` returns an `ElemType` representing the distance
+ between the center of the bound of `node` and the center of the bound of its
+ parent.
+ - If `node` is the root of the tree, `0` is returned.
+
+***Note:*** for more details on each bound quantity, see the [developer
+documentation](../../../developer/trees.md#complex-tree-functionality-and-bounds)
+on bound quantities for trees.
+
+---
+
+### Other functionality
+
+ * `node.Center(center)` computes the center of the bound of `node` and stores
+ it in `center`.
+ - `center` should be of type `arma::Col&`, where `ElemType` is the
+ element type of the specified `MatType`.
+ - `center` will be set to have size equivalent to the dimensionality of the
+ dataset held by `node`.
+ - This is equivalent to calling `node.Bound().Center(center)`.
+
+ * A `MeanSPTree` can be serialized with
+ [`data::Save()` and `data::Load()`](../../load_save.md#mlpack-objects).
+
+## Bounding distances with the tree
+
+The primary use of trees in mlpack is bounding distances to points or other tree
+nodes. The following functions can be used for these tasks.
+
+ * `node.GetNearestChild(point)`
+ * `node.GetFurthestChild(point)`
+ - Return a `size_t` indicating the index of the child (`0` for left, `1` for
+ right) that is closest to (or furthest from) `point`, with respect
+ to the `MinDistance()` (or `MaxDistance()`) function.
+ - If there is a tie, `0` (the left child) is returned.
+ - If `node` is a leaf, `0` is returned.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.GetNearestChild(other)`
+ * `node.GetFurthestChild(other)`
+ - Return a `size_t` indicating the index of the child (`0` for left, `1` for
+ right) that is closest to (or furthest from) the `MeanSPTree` node `other`,
+ with respect to the `MinDistance()` (or `MaxDistance()`) function.
+ - If there is a tie, `0` (the left child) is returned.
+ - If `node` is a leaf, `0` is returned.
+
+---
+
+ * `node.MinDistance(point)`
+ * `node.MinDistance(other)`
+ - Return a `double` indicating the minimum possible distance between `node`
+ and `point`, or the `MeanSPTree` node `other`.
+ - This is equivalent to the minimum possible distance between any point
+ contained in the bounding hyperrectangle of `node` and `point`, or between
+ any point contained in the bounding hyperrectangle of `node` and any point
+ contained in the bounding hyperrectangle of `other`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.MaxDistance(point)`
+ * `node.MaxDistance(other)`
+ - Return a `double` indicating the maximum possible distance between `node`
+ and `point`, or the `MeanSPTree` node `other`.
+ - This is equivalent to the maximum possible distance between any point
+ contained in the bounding hyperrectangle of `node` and `point`, or between
+ any point contained in the bounding hyperrectangle of `node` and any point
+ contained in the bounding hyperrectangle of `other`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.RangeDistance(point)`
+ * `node.RangeDistance(other)`
+ - Return a [`RangeType`](../math.md#range) whose lower bound is
+ `node.MinDistance(point)` or `node.MinDistance(other)`, and whose upper
+ bound is `node.MaxDistance(point)` or `node.MaxDistance(other)`.
+ - `ElemType` is the element type of `MatType`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+## Tree traversals
+
+Like every mlpack tree, the `MeanSPTree` class provides a [single-tree and
+dual-tree traversal](../../../developer/trees.md#traversals) that can be paired
+with a [`RuleType` class](../../../developer/trees.md#rules) to implement a
+single-tree or dual-tree algorithm.
+
+ * `MeanSPTree::SingleTreeTraverser`
+ - Implements a depth-first single-tree traverser.
+
+ * `MeanSPTree::DualTreeTraverser`
+ - Implements a dual-depth-first dual-tree traverser.
+
+However, spill trees are primarily useful because the overlapping nodes allow
+*defeatist* search to be effective. Defeatist search is non-backtracking: the
+tree is traversed to one leaf only. For example, finding the approximate
+nearest neighbor of a point `p` with defeatist search is done by recursing in
+the tree, choosing the child with smallest minimum distance to `p`, and when a
+leaf is encountered, choosing the closest point in the leaf to `p` as the
+nearest neighbor. This is the strategy used in the
+[original spill tree paper (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf).
+
+Defeatist traversers, matching the API for a regular
+[traversal](../../../developer/trees.md#traversals) are made available as the
+following two classes:
+
+ * `MeanSPTree::DefeatistSingleTreeTraverser`
+ - Implements a depth-first single-tree defeatist traverser with no
+ backtracking. Traversal will terminate after the first leaf is visited.
+
+ * `MeanSPTree::DefeatistDualTreeTraverser`
+ - Implements a dual-depth-first dual-tree defeatist traversal with no
+ backtracking. For each query leaf node, traversal will terminate after the
+ first reference leaf node is visited.
+
+Any [`RuleType`](../../../developer/trees.md#rules) that is being used with a
+defeatist traversal, in addition to the functions required by the `RuleType`
+API, must implement the following functions:
+
+```
+// This is only required for single-tree defeatist traversals.
+// It should return the index of the branch that should be chosen for the given
+// query point and reference node.
+template
+size_t GetBestChild(const VecType& queryPoint, TreeType& referenceNode);
+
+// This is only required for dual-tree defeatist traversals.
+// It should return the index of the best child of the reference node that
+// should be chosen for the given query node.
+template
+size_t GetBestChild(TreeType& queryNode, TreeType& referenceNode);
+
+// Return the minimum number of base cases (point-to-point computations) that
+// are required during the traversal.
+size_t MinimumBaseCases();
+```
+
+## Example usage
+
+Build a `MeanSPTree` on the `cloud` dataset and print basic statistics about the
+tree.
+
+```c++
+// See https://datasets.mlpack.org/cloud.csv.
+arma::mat dataset;
+mlpack::data::Load("cloud.csv", dataset, true);
+
+// Build the mean-split spill tree with a tau (margin) of 0.2 and a leaf size of
+// 10. (This means that nodes are split until they contain 10 or fewer points.)
+//
+// The std::move() means that `dataset` will be empty after this call, and no
+// data will be copied during tree building.
+//
+// When C++20 is enabled, then the <> is not necessary and the following line
+// will work:
+// mlpack::MeanSPTree tree(std::move(dataset), 0.2, 10);
+mlpack::MeanSPTree<> tree(std::move(dataset), 0.2, 10);
+
+// Print the bounding box of the root node.
+std::cout << "Bounding box of root node:" << std::endl;
+for (size_t i = 0; i < tree.Bound().Dim(); ++i)
+{
+ std::cout << " - Dimension " << i << ": [" << tree.Bound()[i].Lo() << ", "
+ << tree.Bound()[i].Hi() << "]." << std::endl;
+}
+std::cout << std::endl;
+
+// Print the number of descendant points of the root, and of each of its
+// children.
+std::cout << "Descendant points of root: "
+ << tree.NumDescendants() << "." << std::endl;
+std::cout << "Descendant points of left child: "
+ << tree.Left()->NumDescendants() << "." << std::endl;
+std::cout << "Descendant points of right child: "
+ << tree.Right()->NumDescendants() << "." << std::endl;
+std::cout << std::endl;
+
+// Compute the center of the MeanSPTree.
+arma::vec center;
+tree.Center(center);
+std::cout << "Center of tree: " << center.t();
+```
+
+---
+
+Build two `MeanSPTree`s on subsets of the corel dataset and compute minimum and
+maximum distances between different nodes in the tree.
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::mat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset, true);
+
+// Build trees on the first half and the second half of points. Use a tau
+// (overlap) parameter of 0.3, which is tuned to this dataset, and a rho value
+// of 0.6 to prevent the trees getting too deep.
+mlpack::MeanSPTree<> tree1(dataset.cols(0, dataset.n_cols / 2), 0.3, 20, 0.6);
+mlpack::MeanSPTree<> tree2(dataset.cols(dataset.n_cols / 2 + 1,
+ dataset.n_cols - 1), 0.3, 20, 0.6);
+
+// Compute the maximum distance between the trees.
+std::cout << "Maximum distance between tree root nodes: "
+ << tree1.MaxDistance(tree2) << "." << std::endl;
+
+// Get the leftmost grandchild of the first tree's root---if it exists.
+if (!tree1.IsLeaf() && !tree1.Child(0).IsLeaf())
+{
+ mlpack::MeanSPTree<>& node1 = tree1.Child(0).Child(0);
+
+ // Get the rightmost grandchild of the second tree's root---if it exists.
+ if (!tree2.IsLeaf() && !tree2.Child(1).IsLeaf())
+ {
+ mlpack::MeanSPTree<>& node2 = tree2.Child(1).Child(1);
+
+ // Print the minimum and maximum distance between the nodes.
+ mlpack::Range dists = node1.RangeDistance(node2);
+ std::cout << "Possible distances between two grandchild nodes: ["
+ << dists.Lo() << ", " << dists.Hi() << "]." << std::endl;
+
+ // Print the minimum distance between the first node and the first
+ // descendant point of the second node.
+ const size_t descendantIndex = node2.Descendant(0);
+ const double descendantMinDist =
+ node1.MinDistance(node2.Dataset().col(descendantIndex));
+ std::cout << "Minimum distance between grandchild node and descendant "
+ << "point: " << descendantMinDist << "." << std::endl;
+
+ // Which child of node2 is closer to node1?
+ const size_t closerIndex = node2.GetNearestChild(node1);
+ if (closerIndex == 0)
+ std::cout << "The left child of node2 is closer to node1." << std::endl;
+ else if (closerIndex == 1)
+ std::cout << "The right child of node2 is closer to node1." << std::endl;
+ else // closerIndex == 2 in this case.
+ std::cout << "Both children of node2 are equally close to node1."
+ << std::endl;
+
+ // And which child of node1 is further from node2?
+ const size_t furtherIndex = node1.GetFurthestChild(node2);
+ if (furtherIndex == 0)
+ std::cout << "The left child of node1 is further from node2."
+ << std::endl;
+ else if (furtherIndex == 1)
+ std::cout << "The right child of node1 is further from node2."
+ << std::endl;
+ else // furtherIndex == 2 in this case.
+ std::cout << "Both children of node1 are equally far from node2."
+ << std::endl;
+ }
+}
+```
+
+---
+
+Build a `MeanSPTree` on 32-bit floating point data and save it to disk.
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::fmat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset);
+
+// Build the MeanSPTree using 32-bit floating point data as the matrix type.
+// We will still use the default EmptyStatistic and EuclideanDistance
+// parameters.
+mlpack::MeanSPTree tree(std::move(dataset), 0.1, 20, 0.95);
+
+// Save the tree to disk with the name 'tree'.
+mlpack::data::Save("tree.bin", "tree", tree);
+
+std::cout << "Saved tree with " << tree.Dataset().n_cols << " points to "
+ << "'tree.bin'." << std::endl;
+```
+
+---
+
+Load a 32-bit floating point `MeanSPTree` from disk, then traverse it manually
+and find the number of nodes whose children overlap.
+
+```c++
+// This assumes the tree has already been saved to 'tree.bin' (as in the example
+// above).
+
+// This convenient typedef saves us a long type name!
+using TreeType = mlpack::MeanSPTree;
+
+TreeType tree;
+mlpack::data::Load("tree.bin", "tree", tree);
+std::cout << "Tree loaded with " << tree.NumDescendants() << " points."
+ << std::endl;
+
+// Recurse in a depth-first manner. Count both the total number of non-leaves,
+// and the number of non-leaves that have overlapping children.
+size_t overlapCount = 0;
+size_t totalInternalNodeCount = 0;
+std::stack stack;
+stack.push(&tree);
+while (!stack.empty())
+{
+ TreeType* node = stack.top();
+ stack.pop();
+
+ if (node->IsLeaf())
+ continue;
+
+ if (node->Overlap())
+ ++overlapCount;
+ ++totalInternalNodeCount;
+
+ stack.push(node->Left());
+ stack.push(node->Right());
+}
+
+// Note that it would be possible to use TreeType::SingleTreeTraverser to
+// perform the recursion above, but that is more well-suited for more complex
+// tasks that require pruning and other non-trivial behavior; so using a simple
+// stack is the better option here.
+
+// Print the results.
+std::cout << overlapCount << " out of " << totalInternalNodeCount
+ << " internal nodes have overlapping children." << std::endl;
+```
+
+---
+
+Use a defeatist traversal to find the approximate nearest neighbor of the third
+and fourth points in the `corel-histogram` dataset. (Note: this can also be
+done more easily with the `KNN` class! This example is a demonstration of how
+to use the defeatist traverser.)
+
+
+
+For this example, we must first define a
+[`RuleType` class](../../../developer/trees.md#rules).
+
+```c++
+// For simplicity, this only implements those methods required by single-tree
+// traversals, and cannot be used with a dual-tree traversal.
+//
+// `.Reset()` must be called before any additional single-tree traversals after
+// the first is run.
+class SpillNearestNeighborRule
+{
+ public:
+ // Store the dataset internally.
+ SpillNearestNeighborRule(const arma::mat& dataset) :
+ dataset(dataset),
+ nearestNeighbor(size_t(-1)),
+ nearestDistance(DBL_MAX) { }
+
+ // Compute the base case (point-to-point comparison).
+ double BaseCase(const size_t queryIndex, const size_t referenceIndex)
+ {
+ // Skip the base case if the points are the same.
+ if (queryIndex == referenceIndex)
+ return 0.0;
+
+ const double dist = mlpack::EuclideanDistance::Evaluate(
+ dataset.col(queryIndex), dataset.col(referenceIndex));
+
+ if (dist < nearestDistance)
+ {
+ nearestNeighbor = referenceIndex;
+ nearestDistance = dist;
+ }
+
+ return dist;
+ }
+
+ // Score the given node in the tree; if it is sufficiently far away that it
+ // cannot contain a better nearest neighbor candidate, we can prune it.
+ template
+ double Score(const size_t queryIndex, const TreeType& referenceNode) const
+ {
+ const double minDist = referenceNode.MinDistance(dataset.col(queryIndex));
+ if (minDist > nearestDistance)
+ return DBL_MAX; // Prune: this cannot contain a better candidate!
+
+ return minDist;
+ }
+
+ // Rescore the given node/point combination. Note that this will not be used
+ // by the defeatist traversal as it never backtracks, but we include it for
+ // completeness because the RuleType API requires it.
+ template
+ double Rescore(const size_t, const TreeType&, const double oldScore) const
+ {
+ if (oldScore > nearestDistance)
+ return DBL_MAX; // Prune: the node is too far away.
+ return oldScore;
+ }
+
+ // This is required by defeatist traversals to select the best reference
+ // child to recurse into for overlapping nodes.
+ template
+ size_t GetBestChild(const size_t queryIndex, TreeType& referenceNode)
+ const
+ {
+ return referenceNode.GetNearestChild(dataset.col(queryIndex));
+ }
+
+ // We must perform at least two base cases in order to have a result. Note
+ // that this is two, and not one, because we skip base cases where the query
+ // and reference points are the same. That can only happen a maximum of once,
+ // so to ensure that we compare a query point to a different reference point
+ // at least once, we must return 2 here.
+ size_t MinimumBaseCases() const { return 2; }
+
+ // Get the results (to be called after the traversal).
+ size_t NearestNeighbor() const { return nearestNeighbor; }
+ double NearestDistance() const { return nearestDistance; }
+
+ // Reset the internal statistics for an additional traversal.
+ void Reset()
+ {
+ nearestNeighbor = size_t(-1);
+ nearestDistance = DBL_MAX;
+ }
+
+ private:
+ const arma::mat& dataset;
+
+ size_t nearestNeighbor;
+ double nearestDistance;
+};
+```
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::mat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset, true);
+
+// Build two trees, one with a lot of overlap, and one with no overlap
+// (e.g. tau = 0).
+mlpack::MeanSPTree<> tree1(dataset, 0.5, 10), tree2(dataset, 0.0, 10);
+
+// Construct the rule types, and then the traversals.
+SpillNearestNeighborRule r1(dataset), r2(dataset);
+
+mlpack::MeanSPTree<>::DefeatistSingleTreeTraverser
+ t1(r1), t2(r2);
+
+// Search for the approximate nearest neighbor of point 3 using both trees.
+t1.Traverse(3, tree1);
+t2.Traverse(3, tree2);
+
+std::cout << "Approximate nearest neighbor of point 3:" << std::endl;
+std::cout << " - Mean-split spill tree with overlap 0.5 found: point "
+ << r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
+ << "." << std::endl;
+
+std::cout << " - Mean-split spill tree with no overlap found: point "
+ << r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
+ << "." << std::endl;
+
+// Now search for point 6.
+r1.Reset();
+r2.Reset();
+
+t1.Traverse(6, tree1);
+t2.Traverse(6, tree2);
+
+std::cout << "Approximate nearest neighbor of point 6:" << std::endl;
+std::cout << " - Mean-split spill tree with overlap 0.5 found: point "
+ << r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
+ << "." << std::endl;
+
+std::cout << " - Mean-split spill tree with no overlap found: point "
+ << r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
+ << "." << std::endl;
+```
diff --git a/doc/user/core/trees/mean_split_ball_tree.md b/doc/user/core/trees/mean_split_ball_tree.md
index 88a5d91a82..50b63b09cf 100644
--- a/doc/user/core/trees/mean_split_ball_tree.md
+++ b/doc/user/core/trees/mean_split_ball_tree.md
@@ -140,13 +140,11 @@ may be different.
`MeanSplitBallTree` on the modified dataset. For trees that support
individual insertion and deletions, see the
[`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
- [`RTree`](r_tree.md), `RStarTree`, etc.).
+ [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
- See also the
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
-
-
---
### Constructor parameters:
@@ -432,7 +430,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
//
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
// `mlpack::MeanSplitBallTree tree(...)` will work fine in C++20 or newer).
-mlpack::MeanSplitBallTree<> tree(std::move(dataset));
+mlpack::MeanSplitBallTree<> tree(std::move(dataset), 10);
// Print the bounding box of the root node.
std::cout << "Bounding ball of root node:" << std::endl;
diff --git a/doc/user/core/trees/mean_split_kdtree.md b/doc/user/core/trees/mean_split_kdtree.md
index 0710ea1a8f..a6fdde5d55 100644
--- a/doc/user/core/trees/mean_split_kdtree.md
+++ b/doc/user/core/trees/mean_split_kdtree.md
@@ -137,13 +137,11 @@ different.
build a new `MeanSplitKDTree` on the modified dataset. For trees that
support individual insertion and deletions, see the
[`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
- [`RTree`](r_tree.md), `RStarTree`, etc.).
+ [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
- See also the
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
-
-
---
### Constructor parameters:
@@ -434,7 +432,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
//
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
// `mlpack::MeanSplitKDTree tree(...)` will work fine in C++20 or newer).
-mlpack::MeanSplitKDTree<> tree(std::move(dataset));
+mlpack::MeanSplitKDTree<> tree(std::move(dataset), 10);
// Print the bounding box of the root node.
std::cout << "Bounding box of root node:" << std::endl;
diff --git a/doc/user/core/trees/non_ort_mean_sp_tree.md b/doc/user/core/trees/non_ort_mean_sp_tree.md
new file mode 100644
index 0000000000..c67ec54362
--- /dev/null
+++ b/doc/user/core/trees/non_ort_mean_sp_tree.md
@@ -0,0 +1,746 @@
+# `NonOrtMeanSPTree`
+
+The `NonOrtMeanSPTree` class implements the hybrid spill tree with
+non-axis-orthogonal splitting hyperplanes and mean-split behavior; this is a
+binary space partitioning tree that allows overlapping volumes between nodes.
+This type of tree can be more effective than trees like the
+[`KDTree`](kdtree.md) for approximate nearest neighbor search and related tasks.
+
+`NonOrtMeanSPTree` supports three template parameters for configurable behavior,
+and implements all the functionality required by the
+[TreeType API](../../../developer/trees.md#the-treetype-api), plus some
+additional functionality specific to spill trees. `NonOrtMeanSPTree` is built
+on the more generic [`SpillTree`](spill_tree.md) class, so if fully custom
+behavior is desired, that
+
+ * [Template parameters](#template-parameters)
+ * [Constructors](#constructors)
+ * [Basic tree properties](#basic-tree-properties)
+ * [Bounding distances with the tree](#bounding-distances-with-the-tree)
+ * [Tree traversals](#tree-traversals)
+ * [Example usage](#example-usage)
+
+## See also
+
+
+
+ * [`SpillTree`](spill_tree.md)
+ * [`SPTree`](sp_tree.md)
+ * [`MeanSPTree`](mean_sp_tree.md)
+ * [`NonOrtSPTree`](non_ort_sp_tree.md)
+ * [`BinarySpaceTree`](binary_space_tree.md)
+ * [An Investigation of Practical Approximate Nearest Neighbor Algorithms (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf)
+ * [Tree-Independent Dual-Tree Algorithms (pdf)](https://www.ratml.org/pub/pdf/2013tree.pdf)
+
+## Template parameters
+
+In accordance with the
+[TreeType API](../../../developer/trees.md#template-parameters-required-by-the-treetype-policy)
+(see also
+[this more detailed section](../../../developer/trees.md#template-parameters)),
+the `NonOrtMeanSPTree` class takes three template parameters:
+
+```
+NonOrtMeanSPTree
+```
+
+ * `DistanceType`: the [distance metric](../distances.md) to use for distance
+ computations.
+
+ * `StatisticType`: this holds auxiliary information in each tree node. By
+ default, [`EmptyStatistic`](binary_space_tree.md#emptystatistic) is used,
+ which holds no information.
+ - See the [`StatisticType`](binary_space_tree.md#statistictype) section in
+ the `BinarySpaceTree` documentation for more details.
+
+ * `MatType`: the type of matrix used to represent points. Must be a type
+ matching the [Armadillo API](../../matrices.md). By default, `arma::mat` is
+ used, but other types such as `arma::fmat` or similar will work just fine.
+
+The `NonOrtMeanSPTree` class itself is a convenience typedef of the generic
+[`SpillTree`](spill_tree.md) class, using the
+[`Hyperplane`](spill_tree.md#hyperplane) class as the splitting hyperplane type,
+and the [`MeanSpaceSplit`](spill_tree.md#meanspacesplit) class as the splitting
+strategy.
+
+If no template parameters are explicitly specified, then defaults are used:
+
+```
+NonOrtMeanSPTree<> = NonOrtMeanSPTree
+```
+
+## Constructors
+
+`NonOrtMeanSPTree`s are constructed by iteratively finding splitting
+hyperplanes, and points within a margin of the hyperplane are assigned to *both*
+child nodes. Unlike the constructors of
+[`BinarySpaceTree`](binary_space_tree.md#constructors), the dataset is not
+permuted during construction.
+
+---
+
+ * `node = NonOrtMeanSPTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
+ - Construct a `NonOrtMeanSPTree` on the given `data`, using the specified
+ hyperparameters to control tree construction behavior.
+ - By default, a reference to `data` is stored. If `data` goes out of scope
+ after tree construction, memory errors will occur! To avoid this, either
+ pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
+ when doing this, `data` will be set to an empty matrix.
+
+---
+
+ * `node = NonOrtMeanSPTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
+ - Construct a `NonOrtMeanSPTree` on the given `data`, using custom template
+ parameters, and using the specified hyperparameters to control tree
+ construction behavior.
+ - By default, a reference to `data` is stored. If `data` goes out of scope
+ after tree construction, memory errors will occur! To avoid this, either
+ pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
+ when doing this, `data` will be set to an empty matrix.
+
+---
+
+ * `node = NonOrtMeanSPTree()`
+ - Construct an empty `NonOrtMeanSPTree` with no children, no points, and
+ default template parameters.
+
+---
+
+***Notes:***
+
+ - The name `node` is used here for `NonOrtMeanSPTree` objects instead of
+ `tree`, because each `NonOrtMeanSPTree` object is a single node in the tree.
+ The constructor returns the node that is the root of the tree.
+
+ - Inserting individual points or removing individual points from a
+ `NonOrtMeanSPTree` is not supported, because this generally results in a tree
+ with very suboptimal hyperplane splits. It is better to simply build a new
+ `NonOrtMeanSPTree` on the modified dataset. For trees that support
+ individual insertion and deletions, see the
+ [`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
+ [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
+
+ - See also the
+ [developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
+
+---
+
+### Constructor parameters:
+
+| **name** | **type** | **description** | **default** |
+|----------|----------|-----------------|-------------|
+| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
+| `tau` | `double` | Width of spill margin: points within `tau` of the splitting hyperplane of a node will be contained in both left and right children. | `0.0` |
+| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
+| `rho` | `double` | Balance threshold. When splitting, if either overlapping node would contain a fraction of more than `rho` of the points, a non-overlapping split is performed. Must be in the range `[0.0, 1.0)`. | `0.7` |
+
+***Caveats***:
+
+ * `tau` must be manually tuned for the properties of each dataset; the default,
+ `0.0`, will never allow overlap between nodes (and thus the created tree will
+ essentially be a non-overlapping [`BinarySpaceTree`](binary_space_tree.md)).
+
+ * If `tau` is set too large, nodes will overlap too much and search quality
+ will be degraded.
+
+ * `rho` implicitly controls the depth of the tree by forcing very overlapping
+ children to be non-overlapping. As `rho` gets closer to `1`, more overlap is
+ allowed, which in turn makes the tree deeper. If `rho` is set to `0.5` or
+ less, then all splits will be non-overlapping (and the tree will essentially
+ be a [`BinarySpaceTree`](binary_space_tree.md)).
+
+## Basic tree properties
+
+Once an `NonOrtMeanSPTree` object is constructed, various properties of the tree
+can be accessed or inspected. Many of these functions are required by the
+[TreeType API](../../../developer/trees.md#the-treetype-api).
+
+### Navigating the tree
+
+ * `node.NumChildren()` returns the number of children in `node`. This is
+ either `2` if `node` has children, or `0` if `node` is a leaf.
+
+ * `node.IsLeaf()` returns a `bool` indicating whether or not `node` is a leaf.
+
+ * `node.Child(i)` returns a `NonOrtMeanSPTree&` that is the `i`th child.
+ - `i` must be `0` or `1`.
+ - This function should only be called if `node.NumChildren()` is not `0`
+ (e.g. if `node` is not a leaf). Note that this returns a valid
+ `NonOrtMeanSPTree&` that can itself be used just like the root node of the
+ tree!
+ - `node.Left()` and `node.Right()` are convenience functions specific to
+ `NonOrtMeanSPTree` that will return `NonOrtMeanSPTree*` (pointers) to the
+ left and right children, respectively, or `NULL` if `node` has no children.
+
+ * `node.Parent()` will return an `NonOrtMeanSPTree*` that points to the parent
+ of `node`, or `NULL` if `node` is the root of the `NonOrtMeanSPTree`.
+
+---
+
+### Accessing members of a tree
+
+ * `node.Overlap()` will return a `bool` that is `true` if `node`'s children are
+ overlapping, and `false` otherwise.
+
+ * `node.Hyperplane()` will return an [`Hyperplane`](spill_tree.md#hyperplane)
+ object that represents the splitting hyperplane of `node`.
+ - All points in `node.Left()` are to the left of `node.Hyperplane()` if
+ `node.Overlap()` is `false`; otherwise, all points in `node.Left()` are to
+ the left of `node.Hyperplane() + tau`.
+ - All points in `node.Right()` are to the right of `node.Hyperplane()` if
+ `node.Overlap()` is `false`; otherwise, all points in `node.Right()` are to
+ the right of `node.Hyperplane() - tau`.
+
+ * `node.Bound()` will return a
+ [`const BallBound&`](binary_space_tree.md#ballbound) representing the
+ bounding box associated with `node`.
+ - If a [custom `DistanceType` and/or `MatType`](#template-parameters) are
+ specified, then a `const BallBound&` is returned.
+ * `ElemType` is the element type of the specified `MatType` (e.g. `double`
+ for `arma::mat`, `float` for `arma::fmat`, etc.).
+
+ * `node.Stat()` will return a `StatisticType&` holding the statistics of the
+ node that were computed during tree construction.
+
+ * `node.Distance()` will return a `DistanceType&` that can be used to make
+ distance computations.
+
+See also the
+[developer documentation](../../../developer/trees.md#basic-tree-functionality)
+for basic tree functionality in mlpack.
+
+---
+
+### Accessing data held in a tree
+
+ * `node.Dataset()` will return a `const MatType&` that is the dataset the
+ tree was built on.
+
+ * `node.NumPoints()` returns a `size_t` indicating the number of points held
+ directly in `node`.
+ - If `node` is not a leaf, this will return `0`, as `NonOrtMeanSPTree` only
+ holds points directly in its leaves.
+ - If `node` is a leaf, then the number of points will be less than or equal
+ to the `maxLeafSize` that was specified when the tree was constructed.
+
+ * `node.Point(i)` returns a `size_t` indicating the index of the `i`'th point
+ in `node.Dataset()`.
+ - `i` must be in the range `[0, node.NumPoints() - 1]` (inclusive).
+ - `node` must be a leaf (as non-leaves do not hold any points).
+ - The `i`'th point in `node` can then be accessed as
+ `node.Dataset().col(node.Point(i))`.
+ - Accessing the actual `i`'th point itself can be done with, e.g.,
+ `node.Dataset().col(node.Point(i))`.
+
+ * `node.NumDescendants()` returns a `size_t` indicating the number of points
+ held in all descendant leaves of `node`.
+ - If `node` is the root of the tree, then `node.NumDescendants()` will be
+ equal to `node.Dataset().n_cols`.
+
+ * `node.Descendant(i)` returns a `size_t` indicating the index of the `i`'th
+ descendant point in `node.Dataset()`.
+ - `i` must be in the range `[0, node.NumDescendants() - 1]` (inclusive).
+ - `node` does not need to be a leaf.
+ - The `i`'th descendant point in `node` can then be accessed as
+ `node.Dataset().col(node.Descendant(i))`.
+ - Accessing the actual `i`'th descendant itself can be done with, e.g.,
+ `node.Dataset().col(node.Descendant(i))`.
+
+---
+
+### Accessing computed bound quantities of a tree
+
+The following quantities are cached for each node in a `NonOrtMeanSPTree`, and
+so accessing them does not require any computation. In the documentation below,
+`ElemType` is the element type of the given `MatType`; e.g., if `MatType` is
+`arma::mat`, then `ElemType` is `double`.
+
+ * `node.FurthestPointDistance()` returns an `ElemType` representing the
+ distance between the center of the bound of `node` and the furthest point
+ held by `node`.
+ - If `node` is not a leaf, this returns 0 (because `node` does not hold any
+ points).
+
+ * `node.FurthestDescendantDistance()` returns an `ElemType` representing the
+ distance between the center of the bound of `node` and the furthest
+ descendant point held by `node`.
+
+ * `node.MinimumBoundDistance()` returns an `ElemType` representing the minimum
+ possible distance from the center of the node to any edge of its bound.
+
+ * `node.ParentDistance()` returns an `ElemType` representing the distance
+ between the center of the bound of `node` and the center of the bound of its
+ parent.
+ - If `node` is the root of the tree, `0` is returned.
+
+***Note:*** for more details on each bound quantity, see the [developer
+documentation](../../../developer/trees.md#complex-tree-functionality-and-bounds)
+on bound quantities for trees.
+
+---
+
+### Other functionality
+
+ * `node.Center(center)` computes the center of the bound of `node` and stores
+ it in `center`.
+ - `center` should be of type `arma::Col&`, where `ElemType` is the
+ element type of the specified `MatType`.
+ - `center` will be set to have size equivalent to the dimensionality of the
+ dataset held by `node`.
+ - This is equivalent to calling `node.Bound().Center(center)`.
+
+ * A `NonOrtMeanSPTree` can be serialized with
+ [`data::Save()` and `data::Load()`](../../load_save.md#mlpack-objects).
+
+## Bounding distances with the tree
+
+The primary use of trees in mlpack is bounding distances to points or other tree
+nodes. The following functions can be used for these tasks.
+
+ * `node.GetNearestChild(point)`
+ * `node.GetFurthestChild(point)`
+ - Return a `size_t` indicating the index of the child (`0` for left, `1` for
+ right) that is closest to (or furthest from) `point`, with respect
+ to the `MinDistance()` (or `MaxDistance()`) function.
+ - If there is a tie, `0` (the left child) is returned.
+ - If `node` is a leaf, `0` is returned.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.GetNearestChild(other)`
+ * `node.GetFurthestChild(other)`
+ - Return a `size_t` indicating the index of the child (`0` for left, `1` for
+ right) that is closest to (or furthest from) the `NonOrtMeanSPTree` node
+ `other`, with respect to the `MinDistance()` (or `MaxDistance()`) function.
+ - If there is a tie, `0` (the left child) is returned.
+ - If `node` is a leaf, `0` is returned.
+
+---
+
+ * `node.MinDistance(point)`
+ * `node.MinDistance(other)`
+ - Return a `double` indicating the minimum possible distance between `node`
+ and `point`, or the `NonOrtMeanSPTree` node `other`.
+ - This is equivalent to the minimum possible distance between any point
+ contained in the bounding hyperrectangle of `node` and `point`, or between
+ any point contained in the bounding hyperrectangle of `node` and any point
+ contained in the bounding hyperrectangle of `other`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.MaxDistance(point)`
+ * `node.MaxDistance(other)`
+ - Return a `double` indicating the maximum possible distance between `node`
+ and `point`, or the `NonOrtMeanSPTree` node `other`.
+ - This is equivalent to the maximum possible distance between any point
+ contained in the bounding hyperrectangle of `node` and `point`, or between
+ any point contained in the bounding hyperrectangle of `node` and any point
+ contained in the bounding hyperrectangle of `other`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.RangeDistance(point)`
+ * `node.RangeDistance(other)`
+ - Return a [`RangeType`](../math.md#range) whose lower bound is
+ `node.MinDistance(point)` or `node.MinDistance(other)`, and whose upper
+ bound is `node.MaxDistance(point)` or `node.MaxDistance(other)`.
+ - `ElemType` is the element type of `MatType`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+## Tree traversals
+
+Like every mlpack tree, the `NonOrtMeanSPTree` class provides a [single-tree and
+dual-tree traversal](../../../developer/trees.md#traversals) that can be paired
+with a [`RuleType` class](../../../developer/trees.md#rules) to implement a
+single-tree or dual-tree algorithm.
+
+ * `NonOrtMeanSPTree::SingleTreeTraverser`
+ - Implements a depth-first single-tree traverser.
+
+ * `NonOrtMeanSPTree::DualTreeTraverser`
+ - Implements a dual-depth-first dual-tree traverser.
+
+However, spill trees are primarily useful because the overlapping nodes allow
+*defeatist* search to be effective. Defeatist search is non-backtracking: the
+tree is traversed to one leaf only. For example, finding the approximate
+nearest neighbor of a point `p` with defeatist search is done by recursing in
+the tree, choosing the child with smallest minimum distance to `p`, and when a
+leaf is encountered, choosing the closest point in the leaf to `p` as the
+nearest neighbor. This is the strategy used in the
+[original spill tree paper (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf).
+
+Defeatist traversers, matching the API for a regular
+[traversal](../../../developer/trees.md#traversals) are made available as the
+following two classes:
+
+ * `NonOrtMeanSPTree::DefeatistSingleTreeTraverser`
+ - Implements a depth-first single-tree defeatist traverser with no
+ backtracking. Traversal will terminate after the first leaf is visited.
+
+ * `NonOrtMeanSPTree::DefeatistDualTreeTraverser`
+ - Implements a dual-depth-first dual-tree defeatist traversal with no
+ backtracking. For each query leaf node, traversal will terminate after the
+ first reference leaf node is visited.
+
+Any [`RuleType`](../../../developer/trees.md#rules) that is being used with a
+defeatist traversal, in addition to the functions required by the `RuleType`
+API, must implement the following functions:
+
+```
+// This is only required for single-tree defeatist traversals.
+// It should return the index of the branch that should be chosen for the given
+// query point and reference node.
+template
+size_t GetBestChild(const VecType& queryPoint, TreeType& referenceNode);
+
+// This is only required for dual-tree defeatist traversals.
+// It should return the index of the best child of the reference node that
+// should be chosen for the given query node.
+template
+size_t GetBestChild(TreeType& queryNode, TreeType& referenceNode);
+
+// Return the minimum number of base cases (point-to-point computations) that
+// are required during the traversal.
+size_t MinimumBaseCases();
+```
+
+## Example usage
+
+Build an `NonOrtMeanSPTree` on the `cloud` dataset and print basic statistics
+about the tree.
+
+```c++
+// See https://datasets.mlpack.org/cloud.csv.
+arma::mat dataset;
+mlpack::data::Load("cloud.csv", dataset, true);
+
+// Build the spill tree with a tau (margin) of 0.2 and a leaf size of 10.
+// (This means that nodes are split until they contain 10 or fewer points.)
+//
+// The std::move() means that `dataset` will be empty after this call, and no
+// data will be copied during tree building.
+//
+// When C++20 is enabled, then the <> is not necessary and the following line
+// will work:
+// mlpack::NonOrtMeanSPTree tree(std::move(dataset), 0.2, 10);
+mlpack::NonOrtMeanSPTree<> tree(std::move(dataset), 0.2, 10);
+
+// Print the bounding ball of the root node.
+std::cout << "Bounding ball of root node:" << std::endl;
+std::cout << " Center: " << tree.Bound().Center().t();
+std::cout << " Radius: " << tree.Bound().Radius() << "." << std::endl;
+std::cout << std::endl;
+
+// Print the number of descendant points of the root, and of each of its
+// children.
+std::cout << "Descendant points of root: "
+ << tree.NumDescendants() << "." << std::endl;
+std::cout << "Descendant points of left child: "
+ << tree.Left()->NumDescendants() << "." << std::endl;
+std::cout << "Descendant points of right child: "
+ << tree.Right()->NumDescendants() << "." << std::endl;
+std::cout << std::endl;
+
+// Compute the center of the NonOrtMeanSPTree. THis is the same as the center
+// of the bounding ball of the root.
+arma::vec center;
+tree.Center(center);
+std::cout << "Center of tree: " << center.t();
+```
+
+---
+
+Build two `NonOrtMeanSPTree`s on subsets of the corel dataset and compute
+minimum and maximum distances between different nodes in the tree.
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::mat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset, true);
+
+// Build trees on the first half and the second half of points. Use a tau
+// (overlap) parameter of 0.3, which is tuned to this dataset, and a rho value
+// of 0.6 to prevent the trees getting too deep.
+mlpack::NonOrtMeanSPTree<> tree1(dataset.cols(0, dataset.n_cols / 2),
+ 0.3, 20, 0.6);
+mlpack::NonOrtMeanSPTree<> tree2(dataset.cols(dataset.n_cols / 2 + 1,
+ dataset.n_cols - 1),
+ 0.3, 20, 0.6);
+
+// Compute the maximum distance between the trees.
+std::cout << "Maximum distance between tree root nodes: "
+ << tree1.MaxDistance(tree2) << "." << std::endl;
+
+// Get the leftmost grandchild of the first tree's root---if it exists.
+if (!tree1.IsLeaf() && !tree1.Child(0).IsLeaf())
+{
+ mlpack::NonOrtMeanSPTree<>& node1 = tree1.Child(0).Child(0);
+
+ // Get the rightmost grandchild of the second tree's root---if it exists.
+ if (!tree2.IsLeaf() && !tree2.Child(1).IsLeaf())
+ {
+ mlpack::NonOrtMeanSPTree<>& node2 = tree2.Child(1).Child(1);
+
+ // Print the minimum and maximum distance between the nodes.
+ mlpack::Range dists = node1.RangeDistance(node2);
+ std::cout << "Possible distances between two grandchild nodes: ["
+ << dists.Lo() << ", " << dists.Hi() << "]." << std::endl;
+
+ // Print the minimum distance between the first node and the first
+ // descendant point of the second node.
+ const size_t descendantIndex = node2.Descendant(0);
+ const double descendantMinDist =
+ node1.MinDistance(node2.Dataset().col(descendantIndex));
+ std::cout << "Minimum distance between grandchild node and descendant "
+ << "point: " << descendantMinDist << "." << std::endl;
+
+ // Which child of node2 is closer to node1?
+ const size_t closerIndex = node2.GetNearestChild(node1);
+ if (closerIndex == 0)
+ std::cout << "The left child of node2 is closer to node1." << std::endl;
+ else if (closerIndex == 1)
+ std::cout << "The right child of node2 is closer to node1." << std::endl;
+ else // closerIndex == 2 in this case.
+ std::cout << "Both children of node2 are equally close to node1."
+ << std::endl;
+
+ // And which child of node1 is further from node2?
+ const size_t furtherIndex = node1.GetFurthestChild(node2);
+ if (furtherIndex == 0)
+ std::cout << "The left child of node1 is further from node2."
+ << std::endl;
+ else if (furtherIndex == 1)
+ std::cout << "The right child of node1 is further from node2."
+ << std::endl;
+ else // furtherIndex == 2 in this case.
+ std::cout << "Both children of node1 are equally far from node2."
+ << std::endl;
+ }
+}
+```
+
+---
+
+Build a `NonOrtMeanSPTree` on 32-bit floating point data and save it to disk.
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::fmat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset);
+
+// Build the NonOrtMeanSPTree using 32-bit floating point data as the matrix
+// type. We will still use the default EmptyStatistic and EuclideanDistance
+// parameters.
+mlpack::NonOrtSPTree tree(std::move(dataset), 0.1, 20, 0.6);
+
+// Save the tree to disk with the name 'tree'.
+mlpack::data::Save("tree.bin", "tree", tree);
+
+std::cout << "Saved tree with " << tree.Dataset().n_cols << " points to "
+ << "'tree.bin'." << std::endl;
+```
+
+---
+
+Load a 32-bit floating point `NonOrtMeanSPTree` from disk, then traverse it
+manually and find the number of nodes whose children overlap.
+
+```c++
+// This assumes the tree has already been saved to 'tree.bin' (as in the example
+// above).
+
+// This convenient typedef saves us a long type name!
+using TreeType = mlpack::NonOrtMeanSPTree;
+
+TreeType tree;
+mlpack::data::Load("tree.bin", "tree", tree);
+std::cout << "Tree loaded with " << tree.NumDescendants() << " points."
+ << std::endl;
+
+// Recurse in a depth-first manner. Count both the total number of non-leaves,
+// and the number of non-leaves that have overlapping children.
+size_t overlapCount = 0;
+size_t totalInternalNodeCount = 0;
+std::stack stack;
+stack.push(&tree);
+while (!stack.empty())
+{
+ TreeType* node = stack.top();
+ stack.pop();
+
+ if (node->IsLeaf())
+ continue;
+
+ if (node->Overlap())
+ ++overlapCount;
+ ++totalInternalNodeCount;
+
+ stack.push(node->Left());
+ stack.push(node->Right());
+}
+
+// Note that it would be possible to use TreeType::SingleTreeTraverser to
+// perform the recursion above, but that is more well-suited for more complex
+// tasks that require pruning and other non-trivial behavior; so using a simple
+// stack is the better option here.
+
+// Print the results.
+std::cout << overlapCount << " out of " << totalInternalNodeCount
+ << " internal nodes have overlapping children." << std::endl;
+```
+
+---
+
+Use a defeatist traversal to find the approximate nearest neighbor of the third
+and fourth points in the `corel-histogram` dataset. (Note: this can also be
+done more easily with the `KNN` class! This example is a demonstration of how
+to use the defeatist traverser.)
+
+
+
+For this example, we must first define a
+[`RuleType` class](../../../developer/trees.md#rules).
+
+```c++
+// For simplicity, this only implements those methods required by single-tree
+// traversals, and cannot be used with a dual-tree traversal.
+//
+// `.Reset()` must be called before any additional single-tree traversals after
+// the first is run.
+class SpillNearestNeighborRule
+{
+ public:
+ // Store the dataset internally.
+ SpillNearestNeighborRule(const arma::mat& dataset) :
+ dataset(dataset),
+ nearestNeighbor(size_t(-1)),
+ nearestDistance(DBL_MAX) { }
+
+ // Compute the base case (point-to-point comparison).
+ double BaseCase(const size_t queryIndex, const size_t referenceIndex)
+ {
+ // Skip the base case if the points are the same.
+ if (queryIndex == referenceIndex)
+ return 0.0;
+
+ const double dist = mlpack::EuclideanDistance::Evaluate(
+ dataset.col(queryIndex), dataset.col(referenceIndex));
+
+ if (dist < nearestDistance)
+ {
+ nearestNeighbor = referenceIndex;
+ nearestDistance = dist;
+ }
+
+ return dist;
+ }
+
+ // Score the given node in the tree; if it is sufficiently far away that it
+ // cannot contain a better nearest neighbor candidate, we can prune it.
+ template
+ double Score(const size_t queryIndex, const TreeType& referenceNode) const
+ {
+ const double minDist = referenceNode.MinDistance(dataset.col(queryIndex));
+ if (minDist > nearestDistance)
+ return DBL_MAX; // Prune: this cannot contain a better candidate!
+
+ return minDist;
+ }
+
+ // Rescore the given node/point combination. Note that this will not be used
+ // by the defeatist traversal as it never backtracks, but we include it for
+ // completeness because the RuleType API requires it.
+ template
+ double Rescore(const size_t, const TreeType&, const double oldScore) const
+ {
+ if (oldScore > nearestDistance)
+ return DBL_MAX; // Prune: the node is too far away.
+ return oldScore;
+ }
+
+ // This is required by defeatist traversals to select the best reference
+ // child to recurse into for overlapping nodes.
+ template
+ size_t GetBestChild(const size_t queryIndex, TreeType& referenceNode)
+ const
+ {
+ return referenceNode.GetNearestChild(dataset.col(queryIndex));
+ }
+
+ // We must perform at least two base cases in order to have a result. Note
+ // that this is two, and not one, because we skip base cases where the query
+ // and reference points are the same. That can only happen a maximum of once,
+ // so to ensure that we compare a query point to a different reference point
+ // at least once, we must return 2 here.
+ size_t MinimumBaseCases() const { return 2; }
+
+ // Get the results (to be called after the traversal).
+ size_t NearestNeighbor() const { return nearestNeighbor; }
+ double NearestDistance() const { return nearestDistance; }
+
+ // Reset the internal statistics for an additional traversal.
+ void Reset()
+ {
+ nearestNeighbor = size_t(-1);
+ nearestDistance = DBL_MAX;
+ }
+
+ private:
+ const arma::mat& dataset;
+
+ size_t nearestNeighbor;
+ double nearestDistance;
+};
+```
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::mat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset, true);
+
+// Build two trees, one with a lot of overlap, and one with no overlap
+// (e.g. tau = 0).
+mlpack::NonOrtMeanSPTree<> tree1(dataset, 0.5, 10), tree2(dataset, 0.0, 10);
+
+// Construct the rule types, and then the traversals.
+SpillNearestNeighborRule r1(dataset), r2(dataset);
+
+mlpack::NonOrtMeanSPTree<>::DefeatistSingleTreeTraverser<
+ SpillNearestNeighborRule> t1(r1), t2(r2);
+
+// Search for the approximate nearest neighbor of point 3 using both trees.
+t1.Traverse(3, tree1);
+t2.Traverse(3, tree2);
+
+std::cout << "Approximate nearest neighbor of point 3:" << std::endl;
+std::cout << " - Non-axis-aligned mean-split spill tree with overlap 0.5 "
+ << "found: point " << r1.NearestNeighbor() << ", distance "
+ << r1.NearestDistance() << "." << std::endl;
+
+std::cout << " - Non-axis-aligned mean-split spill tree with no overlap "
+ << "found: point " << r2.NearestNeighbor() << ", distance "
+ << r2.NearestDistance() << "." << std::endl;
+
+// Now search for point 6.
+r1.Reset();
+r2.Reset();
+
+t1.Traverse(6, tree1);
+t2.Traverse(6, tree2);
+
+std::cout << "Approximate nearest neighbor of point 6:" << std::endl;
+std::cout << " - Non-axis-aligned mean-split spill tree with overlap 0.5 "
+ << "found: point " << r1.NearestNeighbor() << ", distance "
+ << r1.NearestDistance() << "." << std::endl;
+
+std::cout << " - Non-axis-aligned mean-split spill tree with no overlap "
+ << "found: point " << r2.NearestNeighbor() << ", distance "
+ << r2.NearestDistance() << "." << std::endl;
+```
diff --git a/doc/user/core/trees/non_ort_sp_tree.md b/doc/user/core/trees/non_ort_sp_tree.md
new file mode 100644
index 0000000000..f0dfc62334
--- /dev/null
+++ b/doc/user/core/trees/non_ort_sp_tree.md
@@ -0,0 +1,742 @@
+# `NonOrtSPTree`
+
+The `NonOrtSPTree` class implements the hybrid spill tree with
+non-axis-orthogonal splitting hyperplanes; this is a binary space partitioning
+tree that allows overlapping volumes between nodes. This type of tree can be
+more effective than trees like the [`KDTree`](kdtree.md) for approximate nearest
+neighbor search and related tasks.
+
+`NonOrtSPTree` supports three template parameters for configurable behavior, and
+implements all the functionality required by the [TreeType
+API](../../../developer/trees.md#the-treetype-api), plus some additional
+functionality specific to spill trees. `NonOrtSPTree` is built on the more
+generic [`SpillTree`](spill_tree.md) class, so if fully custom behavior is
+desired, that
+
+ * [Template parameters](#template-parameters)
+ * [Constructors](#constructors)
+ * [Basic tree properties](#basic-tree-properties)
+ * [Bounding distances with the tree](#bounding-distances-with-the-tree)
+ * [Tree traversals](#tree-traversals)
+ * [Example usage](#example-usage)
+
+## See also
+
+
+
+ * [`SpillTree`](spill_tree.md)
+ * [`SPTree`](sp_tree.md)
+ * [`MeanSPTree`](mean_sp_tree.md)
+ * [`NonOrtMeanSPTree`](non_ort_mean_sp_tree.md)
+ * [`BinarySpaceTree`](binary_space_tree.md)
+ * [An Investigation of Practical Approximate Nearest Neighbor Algorithms (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf)
+ * [Tree-Independent Dual-Tree Algorithms (pdf)](https://www.ratml.org/pub/pdf/2013tree.pdf)
+
+## Template parameters
+
+In accordance with the
+[TreeType API](../../../developer/trees.md#template-parameters-required-by-the-treetype-policy)
+(see also
+[this more detailed section](../../../developer/trees.md#template-parameters)),
+the `NonOrtSPTree` class takes three template parameters:
+
+```
+NonOrtSPTree
+```
+
+ * `DistanceType`: the [distance metric](../distances.md) to use for distance
+ computations.
+
+ * `StatisticType`: this holds auxiliary information in each tree node. By
+ default, [`EmptyStatistic`](binary_space_tree.md#emptystatistic) is used,
+ which holds no information.
+ - See the [`StatisticType`](binary_space_tree.md#statistictype) section in
+ the `BinarySpaceTree` documentation for more details.
+
+ * `MatType`: the type of matrix used to represent points. Must be a type
+ matching the [Armadillo API](../../matrices.md). By default, `arma::mat` is
+ used, but other types such as `arma::fmat` or similar will work just fine.
+
+The `NonOrtSPTree` class itself is a convenience typedef of the generic
+[`SpillTree`](spill_tree.md) class, using the
+[`Hyperplane`](spill_tree.md#hyperplane) class as the splitting hyperplane type,
+and the [`MidpointSpaceSplit`](spill_tree.md#midpointspacesplit) class as the
+splitting strategy.
+
+If no template parameters are explicitly specified, then defaults are used:
+
+```
+NonOrtSPTree<> = NonOrtSPTree
+```
+
+## Constructors
+
+`NonOrtSPTree`s are constructed by iteratively finding splitting hyperplanes,
+and points within a margin of the hyperplane are assigned to *both* child nodes.
+Unlike the constructors of
+[`BinarySpaceTree`](binary_space_tree.md#constructors), the dataset is not
+permuted during construction.
+
+---
+
+ * `node = NonOrtSPTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
+ - Construct a `NonOrtSPTree` on the given `data`, using the specified
+ hyperparameters to control tree construction behavior.
+ - By default, a reference to `data` is stored. If `data` goes out of scope
+ after tree construction, memory errors will occur! To avoid this, either
+ pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
+ when doing this, `data` will be set to an empty matrix.
+
+---
+
+ * `node = NonOrtSPTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
+ - Construct a `NonOrtSPTree` on the given `data`, using custom template
+ parameters, and using the specified hyperparameters to control tree
+ construction behavior.
+ - By default, a reference to `data` is stored. If `data` goes out of scope
+ after tree construction, memory errors will occur! To avoid this, either
+ pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
+ when doing this, `data` will be set to an empty matrix.
+
+---
+
+ * `node = NonOrtSPTree()`
+ - Construct an empty `NonOrtSPTree` with no children, no points, and default
+ template parameters.
+
+---
+
+***Notes:***
+
+ - The name `node` is used here for `NonOrtSPTree` objects instead of `tree`,
+ because each `NonOrtSPTree` object is a single node in the tree. The
+ constructor returns the node that is the root of the tree.
+
+ - Inserting individual points or removing individual points from a
+ `NonOrtSPTree` is not supported, because this generally results in a tree
+ with very suboptimal hyperplane splits. It is better to simply build a new
+ `NonOrtSPTree` on the modified dataset. For trees that support individual
+ insertion and deletions, see the [`RectangleTree`](rectangle_tree.md) class
+ and all its variants (e.g. [`RTree`](r_tree.md),
+ [`RStarTree`](r_star_tree.md), etc.).
+
+ - See also the
+ [developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
+
+---
+
+### Constructor parameters:
+
+| **name** | **type** | **description** | **default** |
+|----------|----------|-----------------|-------------|
+| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
+| `tau` | `double` | Width of spill margin: points within `tau` of the splitting hyperplane of a node will be contained in both left and right children. | `0.0` |
+| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
+| `rho` | `double` | Balance threshold. When splitting, if either overlapping node would contain a fraction of more than `rho` of the points, a non-overlapping split is performed. Must be in the range `[0.0, 1.0)`. | `0.7` |
+
+***Caveats***:
+
+ * `tau` must be manually tuned for the properties of each dataset; the default,
+ `0.0`, will never allow overlap between nodes (and thus the created tree will
+ essentially be a non-overlapping [`BinarySpaceTree`](binary_space_tree.md)).
+
+ * If `tau` is set too large, nodes will overlap too much and search quality
+ will be degraded.
+
+ * `rho` implicitly controls the depth of the tree by forcing very overlapping
+ children to be non-overlapping. As `rho` gets closer to `1`, more overlap is
+ allowed, which in turn makes the tree deeper. If `rho` is set to `0.5` or
+ less, then all splits will be non-overlapping (and the tree will essentially
+ be a [`BinarySpaceTree`](binary_space_tree.md)).
+
+## Basic tree properties
+
+Once an `NonOrtSPTree` object is constructed, various properties of the tree can
+be accessed or inspected. Many of these functions are required by the
+[TreeType API](../../../developer/trees.md#the-treetype-api).
+
+### Navigating the tree
+
+ * `node.NumChildren()` returns the number of children in `node`. This is
+ either `2` if `node` has children, or `0` if `node` is a leaf.
+
+ * `node.IsLeaf()` returns a `bool` indicating whether or not `node` is a leaf.
+
+ * `node.Child(i)` returns a `NonOrtSPTree&` that is the `i`th child.
+ - `i` must be `0` or `1`.
+ - This function should only be called if `node.NumChildren()` is not `0`
+ (e.g. if `node` is not a leaf). Note that this returns a valid
+ `NonOrtSPTree&` that can itself be used just like the root node of the
+ tree!
+ - `node.Left()` and `node.Right()` are convenience functions specific to
+ `NonOrtSPTree` that will return `NonOrtSPTree*` (pointers) to the left and
+ right children, respectively, or `NULL` if `node` has no children.
+
+ * `node.Parent()` will return an `NonOrtSPTree*` that points to the parent of
+ `node`, or `NULL` if `node` is the root of the `NonOrtSPTree`.
+
+---
+
+### Accessing members of a tree
+
+ * `node.Overlap()` will return a `bool` that is `true` if `node`'s children are
+ overlapping, and `false` otherwise.
+
+ * `node.Hyperplane()` will return an [`Hyperplane`](spill_tree.md#hyperplane)
+ object that represents the splitting hyperplane of `node`.
+ - All points in `node.Left()` are to the left of `node.Hyperplane()` if
+ `node.Overlap()` is `false`; otherwise, all points in `node.Left()` are to
+ the left of `node.Hyperplane() + tau`.
+ - All points in `node.Right()` are to the right of `node.Hyperplane()` if
+ `node.Overlap()` is `false`; otherwise, all points in `node.Right()` are to
+ the right of `node.Hyperplane() - tau`.
+
+ * `node.Bound()` will return a
+ [`const BallBound&`](binary_space_tree.md#ballbound) representing the
+ bounding box associated with `node`.
+ - If a [custom `DistanceType` and/or `MatType`](#template-parameters) are
+ specified, then a `const BallBound&` is returned.
+ * `ElemType` is the element type of the specified `MatType` (e.g. `double`
+ for `arma::mat`, `float` for `arma::fmat`, etc.).
+
+ * `node.Stat()` will return a `StatisticType&` holding the statistics of the
+ node that were computed during tree construction.
+
+ * `node.Distance()` will return a `DistanceType&` that can be used to make
+ distance computations.
+
+See also the
+[developer documentation](../../../developer/trees.md#basic-tree-functionality)
+for basic tree functionality in mlpack.
+
+---
+
+### Accessing data held in a tree
+
+ * `node.Dataset()` will return a `const MatType&` that is the dataset the
+ tree was built on.
+
+ * `node.NumPoints()` returns a `size_t` indicating the number of points held
+ directly in `node`.
+ - If `node` is not a leaf, this will return `0`, as `NonOrtSPTree` only holds
+ points directly in its leaves.
+ - If `node` is a leaf, then the number of points will be less than or equal
+ to the `maxLeafSize` that was specified when the tree was constructed.
+
+ * `node.Point(i)` returns a `size_t` indicating the index of the `i`'th point
+ in `node.Dataset()`.
+ - `i` must be in the range `[0, node.NumPoints() - 1]` (inclusive).
+ - `node` must be a leaf (as non-leaves do not hold any points).
+ - The `i`'th point in `node` can then be accessed as
+ `node.Dataset().col(node.Point(i))`.
+ - Accessing the actual `i`'th point itself can be done with, e.g.,
+ `node.Dataset().col(node.Point(i))`.
+
+ * `node.NumDescendants()` returns a `size_t` indicating the number of points
+ held in all descendant leaves of `node`.
+ - If `node` is the root of the tree, then `node.NumDescendants()` will be
+ equal to `node.Dataset().n_cols`.
+
+ * `node.Descendant(i)` returns a `size_t` indicating the index of the `i`'th
+ descendant point in `node.Dataset()`.
+ - `i` must be in the range `[0, node.NumDescendants() - 1]` (inclusive).
+ - `node` does not need to be a leaf.
+ - The `i`'th descendant point in `node` can then be accessed as
+ `node.Dataset().col(node.Descendant(i))`.
+ - Accessing the actual `i`'th descendant itself can be done with, e.g.,
+ `node.Dataset().col(node.Descendant(i))`.
+
+---
+
+### Accessing computed bound quantities of a tree
+
+The following quantities are cached for each node in a `NonOrtSPTree`, and so
+accessing them does not require any computation. In the documentation below,
+`ElemType` is the element type of the given `MatType`; e.g., if `MatType` is
+`arma::mat`, then `ElemType` is `double`.
+
+ * `node.FurthestPointDistance()` returns an `ElemType` representing the
+ distance between the center of the bound of `node` and the furthest point
+ held by `node`.
+ - If `node` is not a leaf, this returns 0 (because `node` does not hold any
+ points).
+
+ * `node.FurthestDescendantDistance()` returns an `ElemType` representing the
+ distance between the center of the bound of `node` and the furthest
+ descendant point held by `node`.
+
+ * `node.MinimumBoundDistance()` returns an `ElemType` representing the minimum
+ possible distance from the center of the node to any edge of its bound.
+
+ * `node.ParentDistance()` returns an `ElemType` representing the distance
+ between the center of the bound of `node` and the center of the bound of its
+ parent.
+ - If `node` is the root of the tree, `0` is returned.
+
+***Note:*** for more details on each bound quantity, see the [developer
+documentation](../../../developer/trees.md#complex-tree-functionality-and-bounds)
+on bound quantities for trees.
+
+---
+
+### Other functionality
+
+ * `node.Center(center)` computes the center of the bound of `node` and stores
+ it in `center`.
+ - `center` should be of type `arma::Col&`, where `ElemType` is the
+ element type of the specified `MatType`.
+ - `center` will be set to have size equivalent to the dimensionality of the
+ dataset held by `node`.
+ - This is equivalent to calling `node.Bound().Center(center)`.
+
+ * A `NonOrtSPTree` can be serialized with
+ [`data::Save()` and `data::Load()`](../../load_save.md#mlpack-objects).
+
+## Bounding distances with the tree
+
+The primary use of trees in mlpack is bounding distances to points or other tree
+nodes. The following functions can be used for these tasks.
+
+ * `node.GetNearestChild(point)`
+ * `node.GetFurthestChild(point)`
+ - Return a `size_t` indicating the index of the child (`0` for left, `1` for
+ right) that is closest to (or furthest from) `point`, with respect
+ to the `MinDistance()` (or `MaxDistance()`) function.
+ - If there is a tie, `0` (the left child) is returned.
+ - If `node` is a leaf, `0` is returned.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.GetNearestChild(other)`
+ * `node.GetFurthestChild(other)`
+ - Return a `size_t` indicating the index of the child (`0` for left, `1` for
+ right) that is closest to (or furthest from) the `NonOrtSPTree` node
+ `other`, with respect to the `MinDistance()` (or `MaxDistance()`) function.
+ - If there is a tie, `0` (the left child) is returned.
+ - If `node` is a leaf, `0` is returned.
+
+---
+
+ * `node.MinDistance(point)`
+ * `node.MinDistance(other)`
+ - Return a `double` indicating the minimum possible distance between `node`
+ and `point`, or the `NonOrtSPTree` node `other`.
+ - This is equivalent to the minimum possible distance between any point
+ contained in the bounding hyperrectangle of `node` and `point`, or between
+ any point contained in the bounding hyperrectangle of `node` and any point
+ contained in the bounding hyperrectangle of `other`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.MaxDistance(point)`
+ * `node.MaxDistance(other)`
+ - Return a `double` indicating the maximum possible distance between `node`
+ and `point`, or the `NonOrtSPTree` node `other`.
+ - This is equivalent to the maximum possible distance between any point
+ contained in the bounding hyperrectangle of `node` and `point`, or between
+ any point contained in the bounding hyperrectangle of `node` and any point
+ contained in the bounding hyperrectangle of `other`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.RangeDistance(point)`
+ * `node.RangeDistance(other)`
+ - Return a [`RangeType`](../math.md#range) whose lower bound is
+ `node.MinDistance(point)` or `node.MinDistance(other)`, and whose upper
+ bound is `node.MaxDistance(point)` or `node.MaxDistance(other)`.
+ - `ElemType` is the element type of `MatType`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+## Tree traversals
+
+Like every mlpack tree, the `NonOrtSPTree` class provides a [single-tree and
+dual-tree traversal](../../../developer/trees.md#traversals) that can be paired
+with a [`RuleType` class](../../../developer/trees.md#rules) to implement a
+single-tree or dual-tree algorithm.
+
+ * `NonOrtSPTree::SingleTreeTraverser`
+ - Implements a depth-first single-tree traverser.
+
+ * `NonOrtSPTree::DualTreeTraverser`
+ - Implements a dual-depth-first dual-tree traverser.
+
+However, spill trees are primarily useful because the overlapping nodes allow
+*defeatist* search to be effective. Defeatist search is non-backtracking: the
+tree is traversed to one leaf only. For example, finding the approximate
+nearest neighbor of a point `p` with defeatist search is done by recursing in
+the tree, choosing the child with smallest minimum distance to `p`, and when a
+leaf is encountered, choosing the closest point in the leaf to `p` as the
+nearest neighbor. This is the strategy used in the
+[original spill tree paper (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf).
+
+Defeatist traversers, matching the API for a regular
+[traversal](../../../developer/trees.md#traversals) are made available as the
+following two classes:
+
+ * `NonOrtSPTree::DefeatistSingleTreeTraverser`
+ - Implements a depth-first single-tree defeatist traverser with no
+ backtracking. Traversal will terminate after the first leaf is visited.
+
+ * `NonOrtSPTree::DefeatistDualTreeTraverser`
+ - Implements a dual-depth-first dual-tree defeatist traversal with no
+ backtracking. For each query leaf node, traversal will terminate after the
+ first reference leaf node is visited.
+
+Any [`RuleType`](../../../developer/trees.md#rules) that is being used with a
+defeatist traversal, in addition to the functions required by the `RuleType`
+API, must implement the following functions:
+
+```
+// This is only required for single-tree defeatist traversals.
+// It should return the index of the branch that should be chosen for the given
+// query point and reference node.
+template
+size_t GetBestChild(const VecType& queryPoint, TreeType& referenceNode);
+
+// This is only required for dual-tree defeatist traversals.
+// It should return the index of the best child of the reference node that
+// should be chosen for the given query node.
+template
+size_t GetBestChild(TreeType& queryNode, TreeType& referenceNode);
+
+// Return the minimum number of base cases (point-to-point computations) that
+// are required during the traversal.
+size_t MinimumBaseCases();
+```
+
+## Example usage
+
+Build an `NonOrtSPTree` on the `cloud` dataset and print basic statistics about
+the tree.
+
+```c++
+// See https://datasets.mlpack.org/cloud.csv.
+arma::mat dataset;
+mlpack::data::Load("cloud.csv", dataset, true);
+
+// Build the spill tree with a tau (margin) of 0.2 and a leaf size of 10.
+// (This means that nodes are split until they contain 10 or fewer points.)
+//
+// The std::move() means that `dataset` will be empty after this call, and no
+// data will be copied during tree building.
+//
+// When C++20 is enabled, then the <> is not necessary and the following line
+// will work:
+// mlpack::NonOrtSPTree tree(std::move(dataset), 0.2, 10);
+mlpack::NonOrtSPTree<> tree(std::move(dataset), 0.2, 10);
+
+// Print the bounding ball of the root node.
+std::cout << "Bounding ball of root node:" << std::endl;
+std::cout << " Center: " << tree.Bound().Center().t();
+std::cout << " Radius: " << tree.Bound().Radius() << "." << std::endl;
+std::cout << std::endl;
+
+// Print the number of descendant points of the root, and of each of its
+// children.
+std::cout << "Descendant points of root: "
+ << tree.NumDescendants() << "." << std::endl;
+std::cout << "Descendant points of left child: "
+ << tree.Left()->NumDescendants() << "." << std::endl;
+std::cout << "Descendant points of right child: "
+ << tree.Right()->NumDescendants() << "." << std::endl;
+std::cout << std::endl;
+
+// Compute the center of the NonOrtSPTree. THis is the same as the center of
+// the bounding ball of the root.
+arma::vec center;
+tree.Center(center);
+std::cout << "Center of tree: " << center.t();
+```
+
+---
+
+Build two `NonOrtSPTree`s on subsets of the corel dataset and compute minimum
+and maximum distances between different nodes in the tree.
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::mat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset, true);
+
+// Build trees on the first half and the second half of points. Use a tau
+// (overlap) parameter of 0.3, which is tuned to this dataset, and a rho value
+// of 0.6 to prevent the trees getting too deep.
+mlpack::NonOrtSPTree<> tree1(dataset.cols(0, dataset.n_cols / 2), 0.3, 20, 0.6);
+mlpack::NonOrtSPTree<> tree2(dataset.cols(dataset.n_cols / 2 + 1,
+ dataset.n_cols - 1), 0.3, 20, 0.6);
+
+// Compute the maximum distance between the trees.
+std::cout << "Maximum distance between tree root nodes: "
+ << tree1.MaxDistance(tree2) << "." << std::endl;
+
+// Get the leftmost grandchild of the first tree's root---if it exists.
+if (!tree1.IsLeaf() && !tree1.Child(0).IsLeaf())
+{
+ mlpack::NonOrtSPTree<>& node1 = tree1.Child(0).Child(0);
+
+ // Get the rightmost grandchild of the second tree's root---if it exists.
+ if (!tree2.IsLeaf() && !tree2.Child(1).IsLeaf())
+ {
+ mlpack::NonOrtSPTree<>& node2 = tree2.Child(1).Child(1);
+
+ // Print the minimum and maximum distance between the nodes.
+ mlpack::Range dists = node1.RangeDistance(node2);
+ std::cout << "Possible distances between two grandchild nodes: ["
+ << dists.Lo() << ", " << dists.Hi() << "]." << std::endl;
+
+ // Print the minimum distance between the first node and the first
+ // descendant point of the second node.
+ const size_t descendantIndex = node2.Descendant(0);
+ const double descendantMinDist =
+ node1.MinDistance(node2.Dataset().col(descendantIndex));
+ std::cout << "Minimum distance between grandchild node and descendant "
+ << "point: " << descendantMinDist << "." << std::endl;
+
+ // Which child of node2 is closer to node1?
+ const size_t closerIndex = node2.GetNearestChild(node1);
+ if (closerIndex == 0)
+ std::cout << "The left child of node2 is closer to node1." << std::endl;
+ else if (closerIndex == 1)
+ std::cout << "The right child of node2 is closer to node1." << std::endl;
+ else // closerIndex == 2 in this case.
+ std::cout << "Both children of node2 are equally close to node1."
+ << std::endl;
+
+ // And which child of node1 is further from node2?
+ const size_t furtherIndex = node1.GetFurthestChild(node2);
+ if (furtherIndex == 0)
+ std::cout << "The left child of node1 is further from node2."
+ << std::endl;
+ else if (furtherIndex == 1)
+ std::cout << "The right child of node1 is further from node2."
+ << std::endl;
+ else // furtherIndex == 2 in this case.
+ std::cout << "Both children of node1 are equally far from node2."
+ << std::endl;
+ }
+}
+```
+
+---
+
+Build a `NonOrtSPTree` on 32-bit floating point data and save it to disk.
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::fmat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset);
+
+// Build the NonOrtSPTree using 32-bit floating point data as the matrix type.
+// We will still use the default EmptyStatistic and EuclideanDistance
+// parameters.
+mlpack::NonOrtSPTree tree(std::move(dataset), 0.1, 20, 0.6);
+
+// Save the tree to disk with the name 'tree'.
+mlpack::data::Save("tree.bin", "tree", tree);
+
+std::cout << "Saved tree with " << tree.Dataset().n_cols << " points to "
+ << "'tree.bin'." << std::endl;
+```
+
+---
+
+Load a 32-bit floating point `NonOrtSPTree` from disk, then traverse it manually
+and find the number of nodes whose children overlap.
+
+```c++
+// This assumes the tree has already been saved to 'tree.bin' (as in the example
+// above).
+
+// This convenient typedef saves us a long type name!
+using TreeType = mlpack::NonOrtSPTree;
+
+TreeType tree;
+mlpack::data::Load("tree.bin", "tree", tree);
+std::cout << "Tree loaded with " << tree.NumDescendants() << " points."
+ << std::endl;
+
+// Recurse in a depth-first manner. Count both the total number of non-leaves,
+// and the number of non-leaves that have overlapping children.
+size_t overlapCount = 0;
+size_t totalInternalNodeCount = 0;
+std::stack stack;
+stack.push(&tree);
+while (!stack.empty())
+{
+ TreeType* node = stack.top();
+ stack.pop();
+
+ if (node->IsLeaf())
+ continue;
+
+ if (node->Overlap())
+ ++overlapCount;
+ ++totalInternalNodeCount;
+
+ stack.push(node->Left());
+ stack.push(node->Right());
+}
+
+// Note that it would be possible to use TreeType::SingleTreeTraverser to
+// perform the recursion above, but that is more well-suited for more complex
+// tasks that require pruning and other non-trivial behavior; so using a simple
+// stack is the better option here.
+
+// Print the results.
+std::cout << overlapCount << " out of " << totalInternalNodeCount
+ << " internal nodes have overlapping children." << std::endl;
+```
+
+---
+
+Use a defeatist traversal to find the approximate nearest neighbor of the third
+and fourth points in the `corel-histogram` dataset. (Note: this can also be
+done more easily with the `KNN` class! This example is a demonstration of how
+to use the defeatist traverser.)
+
+
+
+For this example, we must first define a
+[`RuleType` class](../../../developer/trees.md#rules).
+
+```c++
+// For simplicity, this only implements those methods required by single-tree
+// traversals, and cannot be used with a dual-tree traversal.
+//
+// `.Reset()` must be called before any additional single-tree traversals after
+// the first is run.
+class SpillNearestNeighborRule
+{
+ public:
+ // Store the dataset internally.
+ SpillNearestNeighborRule(const arma::mat& dataset) :
+ dataset(dataset),
+ nearestNeighbor(size_t(-1)),
+ nearestDistance(DBL_MAX) { }
+
+ // Compute the base case (point-to-point comparison).
+ double BaseCase(const size_t queryIndex, const size_t referenceIndex)
+ {
+ // Skip the base case if the points are the same.
+ if (queryIndex == referenceIndex)
+ return 0.0;
+
+ const double dist = mlpack::EuclideanDistance::Evaluate(
+ dataset.col(queryIndex), dataset.col(referenceIndex));
+
+ if (dist < nearestDistance)
+ {
+ nearestNeighbor = referenceIndex;
+ nearestDistance = dist;
+ }
+
+ return dist;
+ }
+
+ // Score the given node in the tree; if it is sufficiently far away that it
+ // cannot contain a better nearest neighbor candidate, we can prune it.
+ template
+ double Score(const size_t queryIndex, const TreeType& referenceNode) const
+ {
+ const double minDist = referenceNode.MinDistance(dataset.col(queryIndex));
+ if (minDist > nearestDistance)
+ return DBL_MAX; // Prune: this cannot contain a better candidate!
+
+ return minDist;
+ }
+
+ // Rescore the given node/point combination. Note that this will not be used
+ // by the defeatist traversal as it never backtracks, but we include it for
+ // completeness because the RuleType API requires it.
+ template
+ double Rescore(const size_t, const TreeType&, const double oldScore) const
+ {
+ if (oldScore > nearestDistance)
+ return DBL_MAX; // Prune: the node is too far away.
+ return oldScore;
+ }
+
+ // This is required by defeatist traversals to select the best reference
+ // child to recurse into for overlapping nodes.
+ template
+ size_t GetBestChild(const size_t queryIndex, TreeType& referenceNode)
+ const
+ {
+ return referenceNode.GetNearestChild(dataset.col(queryIndex));
+ }
+
+ // We must perform at least two base cases in order to have a result. Note
+ // that this is two, and not one, because we skip base cases where the query
+ // and reference points are the same. That can only happen a maximum of once,
+ // so to ensure that we compare a query point to a different reference point
+ // at least once, we must return 2 here.
+ size_t MinimumBaseCases() const { return 2; }
+
+ // Get the results (to be called after the traversal).
+ size_t NearestNeighbor() const { return nearestNeighbor; }
+ double NearestDistance() const { return nearestDistance; }
+
+ // Reset the internal statistics for an additional traversal.
+ void Reset()
+ {
+ nearestNeighbor = size_t(-1);
+ nearestDistance = DBL_MAX;
+ }
+
+ private:
+ const arma::mat& dataset;
+
+ size_t nearestNeighbor;
+ double nearestDistance;
+};
+```
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::mat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset, true);
+
+// Build two trees, one with a lot of overlap, and one with no overlap
+// (e.g. tau = 0).
+mlpack::NonOrtSPTree<> tree1(dataset, 0.5, 10), tree2(dataset, 0.0, 10);
+
+// Construct the rule types, and then the traversals.
+SpillNearestNeighborRule r1(dataset), r2(dataset);
+
+mlpack::NonOrtSPTree<>::DefeatistSingleTreeTraverser
+ t1(r1), t2(r2);
+
+// Search for the approximate nearest neighbor of point 3 using both trees.
+t1.Traverse(3, tree1);
+t2.Traverse(3, tree2);
+
+std::cout << "Approximate nearest neighbor of point 3:" << std::endl;
+std::cout << " - Non-axis-aligned spill tree with overlap 0.5 found: point "
+ << r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
+ << "." << std::endl;
+
+std::cout << " - Non-axis-aligned spill tree with no overlap found: point "
+ << r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
+ << "." << std::endl;
+
+// Now search for point 6.
+r1.Reset();
+r2.Reset();
+
+t1.Traverse(6, tree1);
+t2.Traverse(6, tree2);
+
+std::cout << "Approximate nearest neighbor of point 6:" << std::endl;
+std::cout << " - Non-axis-aligned spill tree with overlap 0.5 found: point "
+ << r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
+ << "." << std::endl;
+
+std::cout << " - Non-axis-aligned spill tree with no overlap found: point "
+ << r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
+ << "." << std::endl;
+```
diff --git a/doc/user/core/trees/octree.md b/doc/user/core/trees/octree.md
index 1856e907ae..1ac4bf4c83 100644
--- a/doc/user/core/trees/octree.md
+++ b/doc/user/core/trees/octree.md
@@ -131,13 +131,12 @@ different.
not supported, because this generally results in a octree with very loose
bounding boxes. It is better to simply build a new `Octree` on the modified
dataset. For trees that support individual insertion and deletions, see the
- `RectangleTree` class and all its variants (e.g. `RTree`, `RStarTree`, etc.).
+ [`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
+ [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
- See also the
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
-
-
---
### Constructor parameters:
diff --git a/doc/user/core/trees/r_plus_plus_tree.md b/doc/user/core/trees/r_plus_plus_tree.md
index 7760cccc74..a6dc3bd4c7 100644
--- a/doc/user/core/trees/r_plus_plus_tree.md
+++ b/doc/user/core/trees/r_plus_plus_tree.md
@@ -152,7 +152,7 @@ The dataset is not permuted during the construction process.
| **name** | **type** | **description** | **default** |
|----------|----------|-----------------|-------------|
-| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
+| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
diff --git a/doc/user/core/trees/r_plus_tree.md b/doc/user/core/trees/r_plus_tree.md
index 4cc86fd58c..3f465902b6 100644
--- a/doc/user/core/trees/r_plus_tree.md
+++ b/doc/user/core/trees/r_plus_tree.md
@@ -148,7 +148,7 @@ The dataset is not permuted during the construction process.
| **name** | **type** | **description** | **default** |
|----------|----------|-----------------|-------------|
-| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
+| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
diff --git a/doc/user/core/trees/r_star_tree.md b/doc/user/core/trees/r_star_tree.md
index e84bd20a9f..bf6002d259 100644
--- a/doc/user/core/trees/r_star_tree.md
+++ b/doc/user/core/trees/r_star_tree.md
@@ -151,7 +151,7 @@ The dataset is not permuted during the construction process.
| **name** | **type** | **description** | **default** |
|----------|----------|-----------------|-------------|
-| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
+| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
diff --git a/doc/user/core/trees/r_tree.md b/doc/user/core/trees/r_tree.md
index 0ff376b222..afad716f70 100644
--- a/doc/user/core/trees/r_tree.md
+++ b/doc/user/core/trees/r_tree.md
@@ -145,7 +145,7 @@ The dataset is not permuted during the construction process.
| **name** | **type** | **description** | **default** |
|----------|----------|-----------------|-------------|
-| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
+| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
diff --git a/doc/user/core/trees/rectangle_tree.md b/doc/user/core/trees/rectangle_tree.md
index 882290dda3..df08fbe5d8 100644
--- a/doc/user/core/trees/rectangle_tree.md
+++ b/doc/user/core/trees/rectangle_tree.md
@@ -183,15 +183,13 @@ The dataset is not permuted during the construction process.
- See also the
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
-
-
---
### Constructor parameters:
| **name** | **type** | **description** | **default** |
|----------|----------|-----------------|-------------|
-| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
+| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
diff --git a/doc/user/core/trees/rp_tree.md b/doc/user/core/trees/rp_tree.md
index 557e14060b..40ac4c72ff 100644
--- a/doc/user/core/trees/rp_tree.md
+++ b/doc/user/core/trees/rp_tree.md
@@ -9,10 +9,9 @@ nearest neighbor search) in low dimensions---typically less than 100.
An `RPTree` (or the similar [`MaxRPTree`](max_rp_tree.md)) may be preferred over
a [`KDTree`](kdtree.md) or other tree structures as it is theoretically known to
-adapt to the intrinsic dimension of the data. This is similar to the cover
-tree, but the implementation is far simpler and as a result, more efficient.
-
-
+adapt to the intrinsic dimension of the data. This is similar to the
+[cover tree](cover_tree.md), but the implementation is far simpler and as a
+result, more efficient.
mlpack's `RPTree` implementation supports three template parameters for
configurable behavior, and implements all the functionality required by the
@@ -137,13 +136,11 @@ different.
with very loose bounding boxes. It is better to simply build a new `RPTree`
on the modified dataset. For trees that support individual insertion and
deletions, see the [`RectangleTree`](rectangle_tree.md) class and all its
- variants (e.g. [`RTree`](r_tree.md), `RStarTree`, etc.).
+ variants (e.g. [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
- See also the
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
-
-
---
### Constructor parameters:
@@ -432,7 +429,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
//
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
// `mlpack::RPTree tree(...)` will work fine in C++20 or newer).
-mlpack::RPTree<> tree(std::move(dataset));
+mlpack::RPTree<> tree(std::move(dataset), 10);
// Print the bounding box of the root node.
std::cout << "Bounding box of root node:" << std::endl;
diff --git a/doc/user/core/trees/sp_tree.md b/doc/user/core/trees/sp_tree.md
new file mode 100644
index 0000000000..d02cf44a84
--- /dev/null
+++ b/doc/user/core/trees/sp_tree.md
@@ -0,0 +1,749 @@
+# `SPTree`
+
+The `SPTree` class implements the standard hybrid spill tree, a binary space
+partitioning tree that allows overlapping volumes between nodes. This type of
+tree can be more effective than trees like the [`KDTree`](kdtree.md) for
+approximate nearest neighbor search and related tasks.
+
+`SPTree` supports three template parameters for configurable behavior, and
+implements all the functionality required by the [TreeType
+API](../../../developer/trees.md#the-treetype-api), plus some additional
+functionality specific to spill trees. `SPTree` is built on the more generic
+[`SpillTree`](spill_tree.md) class, so if fully custom behavior is desired, that
+
+ * [Template parameters](#template-parameters)
+ * [Constructors](#constructors)
+ * [Basic tree properties](#basic-tree-properties)
+ * [Bounding distances with the tree](#bounding-distances-with-the-tree)
+ * [Tree traversals](#tree-traversals)
+ * [Example usage](#example-usage)
+
+## See also
+
+
+
+ * [`SpillTree`](spill_tree.md)
+ * [`MeanSPTree`](mean_sp_tree.md)
+ * [`NonOrtSPTree`](non_ort_sp_tree.md)
+ * [`NonOrtMeanSPTree`](non_ort_mean_sp_tree.md)
+ * [`BinarySpaceTree`](binary_space_tree.md)
+ * [An Investigation of Practical Approximate Nearest Neighbor Algorithms (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf)
+ * [Tree-Independent Dual-Tree Algorithms (pdf)](https://www.ratml.org/pub/pdf/2013tree.pdf)
+
+## Template parameters
+
+In accordance with the
+[TreeType API](../../../developer/trees.md#template-parameters-required-by-the-treetype-policy)
+(see also
+[this more detailed section](../../../developer/trees.md#template-parameters)),
+the `SPTree` class takes three template parameters:
+
+```
+SPTree
+```
+
+ * `DistanceType`: the [distance metric](../distances.md) to use for distance
+ computations. Because the `SPTree` internally uses
+ [`HRectBound`](binary_space_tree.md#hrectbound), this is required to be
+ [`EuclideanDistance`](../distances.md#lmetric). See
+ [`NonOrtSPTree`](non_ort_sp_tree.md) for a version of the spill tree where
+ arbitrary distance metrics are allowed.
+
+ * `StatisticType`: this holds auxiliary information in each tree node. By
+ default, [`EmptyStatistic`](binary_space_tree.md#emptystatistic) is used,
+ which holds no information.
+ - See the [`StatisticType`](binary_space_tree.md#statistictype) section in
+ the `BinarySpaceTree` documentation for more details.
+
+ * `MatType`: the type of matrix used to represent points. Must be a type
+ matching the [Armadillo API](../../matrices.md). By default, `arma::mat` is
+ used, but other types such as `arma::fmat` or similar will work just fine.
+
+The `SPTree` class itself is a convenience typedef of the generic
+[`SpillTree`](spill_tree.md) class, using the
+[`AxisOrthogonalHyperplane`](spill_tree.md#axisorthogonalhyperplane) class as
+the splitting hyperplane type, and the
+[`MidpointSpaceSplit`](spill_tree.md#midpointspacesplit) class as the splitting
+strategy.
+
+If no template parameters are explicitly specified, then defaults are used:
+
+```
+SPTree<> = SPTree
+```
+
+## Constructors
+
+`SPTree`s are constructed by iteratively finding splitting hyperplanes, and
+points within a margin of the hyperplane are assigned to *both* child nodes.
+Unlike the constructors of
+[`BinarySpaceTree`](binary_space_tree.md#constructors), the dataset is not
+permuted during construction.
+
+---
+
+ * `node = SPTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
+ - Construct an `SPTree` on the given `data`, using the specified
+ hyperparameters to control tree construction behavior.
+ - By default, a reference to `data` is stored. If `data` goes out of scope
+ after tree construction, memory errors will occur! To avoid this, either
+ pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
+ when doing this, `data` will be set to an empty matrix.
+
+---
+
+ * `node = SPTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
+ - Construct an `SPTree` on the given `data`, using custom template
+ parameters, and using the specified hyperparameters to control tree
+ construction behavior.
+ - By default, a reference to `data` is stored. If `data` goes out of scope
+ after tree construction, memory errors will occur! To avoid this, either
+ pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
+ when doing this, `data` will be set to an empty matrix.
+
+---
+
+ * `node = SPTree()`
+ - Construct an empty `SPTree` with no children, no points, and default
+ template parameters.
+
+---
+
+***Notes:***
+
+ - The name `node` is used here for `SPTree` objects instead of `tree`, because
+ each `SPTree` object is a single node in the tree. The constructor returns
+ the node that is the root of the tree.
+
+ - Inserting individual points or removing individual points from an `SPTree` is
+ not supported, because this generally results in a tree with very suboptimal
+ hyperplane splits. It is better to simply build a new `SPTree` on the
+ modified dataset. For trees that support individual insertion and deletions,
+ see the [`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
+ [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
+
+ - See also the
+ [developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
+
+---
+
+### Constructor parameters:
+
+| **name** | **type** | **description** | **default** |
+|----------|----------|-----------------|-------------|
+| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
+| `tau` | `double` | Width of spill margin: points within `tau` of the splitting hyperplane of a node will be contained in both left and right children. | `0.0` |
+| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
+| `rho` | `double` | Balance threshold. When splitting, if either overlapping node would contain a fraction of more than `rho` of the points, a non-overlapping split is performed. Must be in the range `[0.0, 1.0)`. | `0.7` |
+
+***Caveats***:
+
+ * `tau` must be manually tuned for the properties of each dataset; the default,
+ `0.0`, will never allow overlap between nodes (and thus the created tree will
+ essentially be a non-overlapping [`BinarySpaceTree`](binary_space_tree.md)).
+
+ * If `tau` is set too large, nodes will overlap too much and search quality
+ will be degraded.
+
+ * `rho` implicitly controls the depth of the tree by forcing very overlapping
+ children to be non-overlapping. As `rho` gets closer to `1`, more overlap is
+ allowed, which in turn makes the tree deeper. If `rho` is set to `0.5` or
+ less, then all splits will be non-overlapping (and the tree will essentially
+ be a [`BinarySpaceTree`](binary_space_tree.md)).
+
+## Basic tree properties
+
+Once an `SPTree` object is constructed, various properties of the tree can be
+accessed or inspected. Many of these functions are required by the
+[TreeType API](../../../developer/trees.md#the-treetype-api).
+
+### Navigating the tree
+
+ * `node.NumChildren()` returns the number of children in `node`. This is
+ either `2` if `node` has children, or `0` if `node` is a leaf.
+
+ * `node.IsLeaf()` returns a `bool` indicating whether or not `node` is a leaf.
+
+ * `node.Child(i)` returns an `SPTree&` that is the `i`th child.
+ - `i` must be `0` or `1`.
+ - This function should only be called if `node.NumChildren()` is not `0`
+ (e.g. if `node` is not a leaf). Note that this returns a valid
+ `SPTree&` that can itself be used just like the root node of the
+ tree!
+ - `node.Left()` and `node.Right()` are convenience functions specific to
+ `SPTree` that will return `SPTree*` (pointers) to the left and right
+ children, respectively, or `NULL` if `node` has no children.
+
+ * `node.Parent()` will return an `SPTree*` that points to the parent of
+ `node`, or `NULL` if `node` is the root of the `SPTree`.
+
+---
+
+### Accessing members of a tree
+
+ * `node.Overlap()` will return a `bool` that is `true` if `node`'s children are
+ overlapping, and `false` otherwise.
+
+ * `node.Hyperplane()` will return an
+ [`AxisOrthogonalHyperplane`](spill_tree.md#axisorthogonalhyperplane) object
+ that represents the axis-aligned splitting hyperplane of `node`.
+ - All points in `node.Left()` are to the left of `node.Hyperplane()` if
+ `node.Overlap()` is `false`; otherwise, all points in `node.Left()` are to
+ the left of `node.Hyperplane() + tau`.
+ - All points in `node.Right()` are to the right of `node.Hyperplane()` if
+ `node.Overlap()` is `false`; otherwise, all points in `node.Right()` are to
+ the right of `node.Hyperplane() - tau`.
+
+ * `node.Bound()` will return a
+ [`const HRectBound&`](binary_space_tree.md#hrectbound) representing the
+ bounding box associated with `node`.
+ - If a [custom `DistanceType` and/or `MatType`](#template-parameters) are
+ specified, then a `const HRectBound&` is returned.
+ * `ElemType` is the element type of the specified `MatType` (e.g. `double`
+ for `arma::mat`, `float` for `arma::fmat`, etc.).
+
+ * `node.Stat()` will return a `StatisticType&` holding the statistics of the
+ node that were computed during tree construction.
+
+ * `node.Distance()` will return a `EuclideanDistance&`. Because
+ `EuclideanDistance` has no instantiated members, this is unlikely to be
+ useful, but is required to satisfy the
+ [`TreeType` API](../../../developer/trees.md#the-treetype-api).
+
+See also the
+[developer documentation](../../../developer/trees.md#basic-tree-functionality)
+for basic tree functionality in mlpack.
+
+---
+
+### Accessing data held in a tree
+
+ * `node.Dataset()` will return a `const MatType&` that is the dataset the
+ tree was built on.
+
+ * `node.NumPoints()` returns a `size_t` indicating the number of points held
+ directly in `node`.
+ - If `node` is not a leaf, this will return `0`, as `SPTree` only holds
+ points directly in its leaves.
+ - If `node` is a leaf, then the number of points will be less than or equal
+ to the `maxLeafSize` that was specified when the tree was constructed.
+
+ * `node.Point(i)` returns a `size_t` indicating the index of the `i`'th point
+ in `node.Dataset()`.
+ - `i` must be in the range `[0, node.NumPoints() - 1]` (inclusive).
+ - `node` must be a leaf (as non-leaves do not hold any points).
+ - The `i`'th point in `node` can then be accessed as
+ `node.Dataset().col(node.Point(i))`.
+ - Accessing the actual `i`'th point itself can be done with, e.g.,
+ `node.Dataset().col(node.Point(i))`.
+
+ * `node.NumDescendants()` returns a `size_t` indicating the number of points
+ held in all descendant leaves of `node`.
+ - If `node` is the root of the tree, then `node.NumDescendants()` will be
+ equal to `node.Dataset().n_cols`.
+
+ * `node.Descendant(i)` returns a `size_t` indicating the index of the `i`'th
+ descendant point in `node.Dataset()`.
+ - `i` must be in the range `[0, node.NumDescendants() - 1]` (inclusive).
+ - `node` does not need to be a leaf.
+ - The `i`'th descendant point in `node` can then be accessed as
+ `node.Dataset().col(node.Descendant(i))`.
+ - Accessing the actual `i`'th descendant itself can be done with, e.g.,
+ `node.Dataset().col(node.Descendant(i))`.
+
+---
+
+### Accessing computed bound quantities of a tree
+
+The following quantities are cached for each node in an `SPTree`, and so
+accessing them does not require any computation. In the documentation below,
+`ElemType` is the element type of the given `MatType`; e.g., if `MatType` is
+`arma::mat`, then `ElemType` is `double`.
+
+ * `node.FurthestPointDistance()` returns an `ElemType` representing the
+ distance between the center of the bound of `node` and the furthest point
+ held by `node`.
+ - If `node` is not a leaf, this returns 0 (because `node` does not hold any
+ points).
+
+ * `node.FurthestDescendantDistance()` returns an `ElemType` representing the
+ distance between the center of the bound of `node` and the furthest
+ descendant point held by `node`.
+
+ * `node.MinimumBoundDistance()` returns an `ElemType` representing the minimum
+ possible distance from the center of the node to any edge of its bound.
+
+ * `node.ParentDistance()` returns an `ElemType` representing the distance
+ between the center of the bound of `node` and the center of the bound of its
+ parent.
+ - If `node` is the root of the tree, `0` is returned.
+
+***Note:*** for more details on each bound quantity, see the [developer
+documentation](../../../developer/trees.md#complex-tree-functionality-and-bounds)
+on bound quantities for trees.
+
+---
+
+### Other functionality
+
+ * `node.Center(center)` computes the center of the bound of `node` and stores
+ it in `center`.
+ - `center` should be of type `arma::Col&`, where `ElemType` is the
+ element type of the specified `MatType`.
+ - `center` will be set to have size equivalent to the dimensionality of the
+ dataset held by `node`.
+ - This is equivalent to calling `node.Bound().Center(center)`.
+
+ * An `SPTree` can be serialized with
+ [`data::Save()` and `data::Load()`](../../load_save.md#mlpack-objects).
+
+## Bounding distances with the tree
+
+The primary use of trees in mlpack is bounding distances to points or other tree
+nodes. The following functions can be used for these tasks.
+
+ * `node.GetNearestChild(point)`
+ * `node.GetFurthestChild(point)`
+ - Return a `size_t` indicating the index of the child (`0` for left, `1` for
+ right) that is closest to (or furthest from) `point`, with respect
+ to the `MinDistance()` (or `MaxDistance()`) function.
+ - If there is a tie, `0` (the left child) is returned.
+ - If `node` is a leaf, `0` is returned.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.GetNearestChild(other)`
+ * `node.GetFurthestChild(other)`
+ - Return a `size_t` indicating the index of the child (`0` for left, `1` for
+ right) that is closest to (or furthest from) the `SPTree` node `other`,
+ with respect to the `MinDistance()` (or `MaxDistance()`) function.
+ - If there is a tie, `0` (the left child) is returned.
+ - If `node` is a leaf, `0` is returned.
+
+---
+
+ * `node.MinDistance(point)`
+ * `node.MinDistance(other)`
+ - Return a `double` indicating the minimum possible distance between `node`
+ and `point`, or the `SPTree` node `other`.
+ - This is equivalent to the minimum possible distance between any point
+ contained in the bounding hyperrectangle of `node` and `point`, or between
+ any point contained in the bounding hyperrectangle of `node` and any point
+ contained in the bounding hyperrectangle of `other`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.MaxDistance(point)`
+ * `node.MaxDistance(other)`
+ - Return a `double` indicating the maximum possible distance between `node`
+ and `point`, or the `SPTree` node `other`.
+ - This is equivalent to the maximum possible distance between any point
+ contained in the bounding hyperrectangle of `node` and `point`, or between
+ any point contained in the bounding hyperrectangle of `node` and any point
+ contained in the bounding hyperrectangle of `other`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.RangeDistance(point)`
+ * `node.RangeDistance(other)`
+ - Return a [`RangeType`](../math.md#range) whose lower bound is
+ `node.MinDistance(point)` or `node.MinDistance(other)`, and whose upper
+ bound is `node.MaxDistance(point)` or `node.MaxDistance(other)`.
+ - `ElemType` is the element type of `MatType`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+## Tree traversals
+
+Like every mlpack tree, the `SPTree` class provides a [single-tree and
+dual-tree traversal](../../../developer/trees.md#traversals) that can be paired
+with a [`RuleType` class](../../../developer/trees.md#rules) to implement a
+single-tree or dual-tree algorithm.
+
+ * `SPTree::SingleTreeTraverser`
+ - Implements a depth-first single-tree traverser.
+
+ * `SPTree::DualTreeTraverser`
+ - Implements a dual-depth-first dual-tree traverser.
+
+However, spill trees are primarily useful because the overlapping nodes allow
+*defeatist* search to be effective. Defeatist search is non-backtracking: the
+tree is traversed to one leaf only. For example, finding the approximate
+nearest neighbor of a point `p` with defeatist search is done by recursing in
+the tree, choosing the child with smallest minimum distance to `p`, and when a
+leaf is encountered, choosing the closest point in the leaf to `p` as the
+nearest neighbor. This is the strategy used in the
+[original spill tree paper (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf).
+
+Defeatist traversers, matching the API for a regular
+[traversal](../../../developer/trees.md#traversals) are made available as the
+following two classes:
+
+ * `SPTree::DefeatistSingleTreeTraverser`
+ - Implements a depth-first single-tree defeatist traverser with no
+ backtracking. Traversal will terminate after the first leaf is visited.
+
+ * `SPTree::DefeatistDualTreeTraverser`
+ - Implements a dual-depth-first dual-tree defeatist traversal with no
+ backtracking. For each query leaf node, traversal will terminate after the
+ first reference leaf node is visited.
+
+Any [`RuleType`](../../../developer/trees.md#rules) that is being used with a
+defeatist traversal, in addition to the functions required by the `RuleType`
+API, must implement the following functions:
+
+```
+// This is only required for single-tree defeatist traversals.
+// It should return the index of the branch that should be chosen for the given
+// query point and reference node.
+template
+size_t GetBestChild(const VecType& queryPoint, TreeType& referenceNode);
+
+// This is only required for dual-tree defeatist traversals.
+// It should return the index of the best child of the reference node that
+// should be chosen for the given query node.
+template
+size_t GetBestChild(TreeType& queryNode, TreeType& referenceNode);
+
+// Return the minimum number of base cases (point-to-point computations) that
+// are required during the traversal.
+size_t MinimumBaseCases();
+```
+
+## Example usage
+
+Build an `SPTree` on the `cloud` dataset and print basic statistics about the
+tree.
+
+```c++
+// See https://datasets.mlpack.org/cloud.csv.
+arma::mat dataset;
+mlpack::data::Load("cloud.csv", dataset, true);
+
+// Build the spill tree with a tau (margin) of 0.2 and a leaf size of 10.
+// (This means that nodes are split until they contain 10 or fewer points.)
+//
+// The std::move() means that `dataset` will be empty after this call, and no
+// data will be copied during tree building.
+//
+// When C++20 is enabled, then the <> is not necessary and the following line
+// will work:
+// mlpack::SPTree tree(std::move(dataset), 0.2, 10);
+mlpack::SPTree<> tree(std::move(dataset), 0.2, 10);
+
+// Print the bounding box of the root node.
+std::cout << "Bounding box of root node:" << std::endl;
+for (size_t i = 0; i < tree.Bound().Dim(); ++i)
+{
+ std::cout << " - Dimension " << i << ": [" << tree.Bound()[i].Lo() << ", "
+ << tree.Bound()[i].Hi() << "]." << std::endl;
+}
+std::cout << std::endl;
+
+// Print the number of descendant points of the root, and of each of its
+// children.
+std::cout << "Descendant points of root: "
+ << tree.NumDescendants() << "." << std::endl;
+std::cout << "Descendant points of left child: "
+ << tree.Left()->NumDescendants() << "." << std::endl;
+std::cout << "Descendant points of right child: "
+ << tree.Right()->NumDescendants() << "." << std::endl;
+std::cout << std::endl;
+
+// Compute the center of the SPTree.
+arma::vec center;
+tree.Center(center);
+std::cout << "Center of tree: " << center.t();
+```
+
+---
+
+Build two `SPTree`s on subsets of the corel dataset and compute minimum and
+maximum distances between different nodes in the tree.
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::mat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset, true);
+
+// Build trees on the first half and the second half of points. Use a tau
+// (overlap) parameter of 0.3, which is tuned to this dataset, and a rho value
+// of 0.6 to prevent the trees getting too deep.
+mlpack::SPTree<> tree1(dataset.cols(0, dataset.n_cols / 2), 0.3, 20, 0.6);
+mlpack::SPTree<> tree2(dataset.cols(dataset.n_cols / 2 + 1, dataset.n_cols - 1),
+ 0.3, 20, 0.6);
+
+// Compute the maximum distance between the trees.
+std::cout << "Maximum distance between tree root nodes: "
+ << tree1.MaxDistance(tree2) << "." << std::endl;
+
+// Get the leftmost grandchild of the first tree's root---if it exists.
+if (!tree1.IsLeaf() && !tree1.Child(0).IsLeaf())
+{
+ mlpack::SPTree<>& node1 = tree1.Child(0).Child(0);
+
+ // Get the rightmost grandchild of the second tree's root---if it exists.
+ if (!tree2.IsLeaf() && !tree2.Child(1).IsLeaf())
+ {
+ mlpack::SPTree<>& node2 = tree2.Child(1).Child(1);
+
+ // Print the minimum and maximum distance between the nodes.
+ mlpack::Range dists = node1.RangeDistance(node2);
+ std::cout << "Possible distances between two grandchild nodes: ["
+ << dists.Lo() << ", " << dists.Hi() << "]." << std::endl;
+
+ // Print the minimum distance between the first node and the first
+ // descendant point of the second node.
+ const size_t descendantIndex = node2.Descendant(0);
+ const double descendantMinDist =
+ node1.MinDistance(node2.Dataset().col(descendantIndex));
+ std::cout << "Minimum distance between grandchild node and descendant "
+ << "point: " << descendantMinDist << "." << std::endl;
+
+ // Which child of node2 is closer to node1?
+ const size_t closerIndex = node2.GetNearestChild(node1);
+ if (closerIndex == 0)
+ std::cout << "The left child of node2 is closer to node1." << std::endl;
+ else if (closerIndex == 1)
+ std::cout << "The right child of node2 is closer to node1." << std::endl;
+ else // closerIndex == 2 in this case.
+ std::cout << "Both children of node2 are equally close to node1."
+ << std::endl;
+
+ // And which child of node1 is further from node2?
+ const size_t furtherIndex = node1.GetFurthestChild(node2);
+ if (furtherIndex == 0)
+ std::cout << "The left child of node1 is further from node2."
+ << std::endl;
+ else if (furtherIndex == 1)
+ std::cout << "The right child of node1 is further from node2."
+ << std::endl;
+ else // furtherIndex == 2 in this case.
+ std::cout << "Both children of node1 are equally far from node2."
+ << std::endl;
+ }
+}
+```
+
+---
+
+Build an `SPTree` on 32-bit floating point data and save it to disk.
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::fmat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset);
+
+// Build the SPTree using 32-bit floating point data as the matrix type.
+// We will still use the default EmptyStatistic and EuclideanDistance
+// parameters.
+mlpack::SPTree tree(std::move(dataset), 0.1, 20, 0.95);
+
+// Save the tree to disk with the name 'tree'.
+mlpack::data::Save("tree.bin", "tree", tree);
+
+std::cout << "Saved tree with " << tree.Dataset().n_cols << " points to "
+ << "'tree.bin'." << std::endl;
+```
+
+---
+
+Load a 32-bit floating point `SPTree` from disk, then traverse it manually and
+find the number of nodes whose children overlap.
+
+```c++
+// This assumes the tree has already been saved to 'tree.bin' (as in the example
+// above).
+
+// This convenient typedef saves us a long type name!
+using TreeType = mlpack::SPTree;
+
+TreeType tree;
+mlpack::data::Load("tree.bin", "tree", tree);
+std::cout << "Tree loaded with " << tree.NumDescendants() << " points."
+ << std::endl;
+
+// Recurse in a depth-first manner. Count both the total number of non-leaves,
+// and the number of non-leaves that have overlapping children.
+size_t overlapCount = 0;
+size_t totalInternalNodeCount = 0;
+std::stack stack;
+stack.push(&tree);
+while (!stack.empty())
+{
+ TreeType* node = stack.top();
+ stack.pop();
+
+ if (node->IsLeaf())
+ continue;
+
+ if (node->Overlap())
+ ++overlapCount;
+ ++totalInternalNodeCount;
+
+ stack.push(node->Left());
+ stack.push(node->Right());
+}
+
+// Note that it would be possible to use TreeType::SingleTreeTraverser to
+// perform the recursion above, but that is more well-suited for more complex
+// tasks that require pruning and other non-trivial behavior; so using a simple
+// stack is the better option here.
+
+// Print the results.
+std::cout << overlapCount << " out of " << totalInternalNodeCount
+ << " internal nodes have overlapping children." << std::endl;
+```
+
+---
+
+Use a defeatist traversal to find the approximate nearest neighbor of the third
+and fourth points in the `corel-histogram` dataset. (Note: this can also be
+done more easily with the `KNN` class! This example is a demonstration of how
+to use the defeatist traverser.)
+
+
+
+For this example, we must first define a
+[`RuleType` class](../../../developer/trees.md#rules).
+
+```c++
+// For simplicity, this only implements those methods required by single-tree
+// traversals, and cannot be used with a dual-tree traversal.
+//
+// `.Reset()` must be called before any additional single-tree traversals after
+// the first is run.
+class SpillNearestNeighborRule
+{
+ public:
+ // Store the dataset internally.
+ SpillNearestNeighborRule(const arma::mat& dataset) :
+ dataset(dataset),
+ nearestNeighbor(size_t(-1)),
+ nearestDistance(DBL_MAX) { }
+
+ // Compute the base case (point-to-point comparison).
+ double BaseCase(const size_t queryIndex, const size_t referenceIndex)
+ {
+ // Skip the base case if the points are the same.
+ if (queryIndex == referenceIndex)
+ return 0.0;
+
+ const double dist = mlpack::EuclideanDistance::Evaluate(
+ dataset.col(queryIndex), dataset.col(referenceIndex));
+
+ if (dist < nearestDistance)
+ {
+ nearestNeighbor = referenceIndex;
+ nearestDistance = dist;
+ }
+
+ return dist;
+ }
+
+ // Score the given node in the tree; if it is sufficiently far away that it
+ // cannot contain a better nearest neighbor candidate, we can prune it.
+ template
+ double Score(const size_t queryIndex, const TreeType& referenceNode) const
+ {
+ const double minDist = referenceNode.MinDistance(dataset.col(queryIndex));
+ if (minDist > nearestDistance)
+ return DBL_MAX; // Prune: this cannot contain a better candidate!
+
+ return minDist;
+ }
+
+ // Rescore the given node/point combination. Note that this will not be used
+ // by the defeatist traversal as it never backtracks, but we include it for
+ // completeness because the RuleType API requires it.
+ template
+ double Rescore(const size_t, const TreeType&, const double oldScore) const
+ {
+ if (oldScore > nearestDistance)
+ return DBL_MAX; // Prune: the node is too far away.
+ return oldScore;
+ }
+
+ // This is required by defeatist traversals to select the best reference
+ // child to recurse into for overlapping nodes.
+ template
+ size_t GetBestChild(const size_t queryIndex, TreeType& referenceNode)
+ const
+ {
+ return referenceNode.GetNearestChild(dataset.col(queryIndex));
+ }
+
+ // We must perform at least two base cases in order to have a result. Note
+ // that this is two, and not one, because we skip base cases where the query
+ // and reference points are the same. That can only happen a maximum of once,
+ // so to ensure that we compare a query point to a different reference point
+ // at least once, we must return 2 here.
+ size_t MinimumBaseCases() const { return 2; }
+
+ // Get the results (to be called after the traversal).
+ size_t NearestNeighbor() const { return nearestNeighbor; }
+ double NearestDistance() const { return nearestDistance; }
+
+ // Reset the internal statistics for an additional traversal.
+ void Reset()
+ {
+ nearestNeighbor = size_t(-1);
+ nearestDistance = DBL_MAX;
+ }
+
+ private:
+ const arma::mat& dataset;
+
+ size_t nearestNeighbor;
+ double nearestDistance;
+};
+```
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::mat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset, true);
+
+// Build two trees, one with a lot of overlap, and one with no overlap
+// (e.g. tau = 0).
+mlpack::SPTree<> tree1(dataset, 0.5, 10), tree2(dataset, 0.0, 10);
+
+// Construct the rule types, and then the traversals.
+SpillNearestNeighborRule r1(dataset), r2(dataset);
+
+mlpack::SPTree<>::DefeatistSingleTreeTraverser
+ t1(r1), t2(r2);
+
+// Search for the approximate nearest neighbor of point 3 using both trees.
+t1.Traverse(3, tree1);
+t2.Traverse(3, tree2);
+
+std::cout << "Approximate nearest neighbor of point 3:" << std::endl;
+std::cout << " - Spill tree with overlap 0.5 found: point "
+ << r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
+ << "." << std::endl;
+
+std::cout << " - Spill tree with no overlap found: point "
+ << r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
+ << "." << std::endl;
+
+// Now search for point 6.
+r1.Reset();
+r2.Reset();
+
+t1.Traverse(6, tree1);
+t2.Traverse(6, tree2);
+
+std::cout << "Approximate nearest neighbor of point 6:" << std::endl;
+std::cout << " - Spill tree with overlap 0.5 found: point "
+ << r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
+ << "." << std::endl;
+
+std::cout << " - Spill tree with no overlap found: point "
+ << r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
+ << "." << std::endl;
+```
diff --git a/doc/user/core/trees/spill_tree.md b/doc/user/core/trees/spill_tree.md
new file mode 100644
index 0000000000..571edc5b7d
--- /dev/null
+++ b/doc/user/core/trees/spill_tree.md
@@ -0,0 +1,1086 @@
+# `SpillTree`
+
+The `SpillTree` class represents a generic multidimensional binary space
+partitioning tree that allows overlapping volumes between nodes, also known as a
+'hybrid spill tree'. It is heavily templatized to control splitting behavior
+and other behaviors, and is the actual class underlying trees such as the
+[`SPTree`](sp_tree.md). In general, the `SpillTree` class is not meant to be
+used directly, and instead one of the handful of variants should be used
+instead:
+
+ * [`SPTree`](sp_tree.md)
+ * [`MeanSPTree`](mean_sp_tree.md)
+ * [`NonOrtSPTree`](non_ort_sp_tree.md)
+ * [`NonOrtMeanSPTree`](non_ort_mean_sp_tree.md)
+
+The `SpillTree` is similar to the [`BinarySpaceTree`](binary_space_tree.md),
+except that the two children of a node are allowed to overlap, and thus a single
+point can be contained in multiple branches of the tree. This can be useful to,
+e.g., improve nearest neighbor performance when using [defeatist traversals
+without backtracking](#tree-traversals).
+
+---
+
+For users who want to use `SpillTree` directly or with custom behavior,
+the full class is still detailed in the subsections below. `SpillTree` supports
+the [TreeType API](../../../developer/trees.md#the-treetype-api) and can be used
+with mlpack's tree-based algorithms, although using custom behavior may require
+a template typedef.
+
+ * [Template parameters](#template-parameters)
+ * [Constructors](#constructors)
+ * [Basic tree properties](#basic-tree-properties)
+ * [Bounding distances with the tree](#bounding-distances-with-the-tree)
+ * [`HyperplaneType`](#hyperplanetype) template parameter
+ * [`SplitType`](#splittype) template parameter
+ * [Tree traversals](#tree-traversals)
+ * [Example usage](#example-usage)
+
+## See also
+
+
+
+ * [`SPTree`](sp_tree.md)
+ * [`MeanSPTree`](mean_sp_tree.md)
+ * [`NonOrtSPTree`](non_ort_sp_tree.md)
+ * [`NonOrtMeanSPTree`](non_ort_mean_sp_tree.md)
+ * [`BinarySpaceTree`](binary_space_tree.md)
+ * [An Investigation of Practical Approximate Nearest Neighbor Algorithms (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf)
+ * [Tree-Independent Dual-Tree Algorithms (pdf)](https://www.ratml.org/pub/pdf/2013tree.pdf)
+
+## Template parameters
+
+The `SpillTree` class takes five template parameters. The first three of
+these are required by the
+[TreeType API](../../../developer/trees.md#template-parameters-required-by-the-treetype-policy)
+(see also
+[this more detailed section](../../../developer/trees.md#template-parameters)). The
+full signature of the class is:
+
+```
+template class HyperplaneType,
+ template class SplitType>
+class SpillTree;
+```
+
+ * `DistanceType`: the [distance metric](../distances.md) to use for distance
+ computations. By default, this is
+ [`EuclideanDistance`](../distances.md#lmetric).
+
+ * `StatisticType`: this holds auxiliary information in each tree node. By
+ default, [`EmptyStatistic`](binary_space_tree.md#emptystatistic) is used,
+ which holds no information.
+ - See the [`StatisticType`](binary_space_tree.md#statistictype) section in
+ the `BinarySpaceTree` documentation for more details.
+
+ * `MatType`: the type of matrix used to represent points. Must be a type
+ matching the [Armadillo API](../../matrices.md). By default, `arma::mat` is
+ used, but other types such as `arma::fmat` or similar will work just fine.
+
+ * `HyperplaneType`: the class defining the type of the hyperplane that will
+ split each node. By default,
+ [`AxisOrthogonalHyperplane`](#axisorthogonalhyperplane) is used.
+ - See the [`HyperplaneType`](#hyperplanetype) section for more details.
+
+ * `SplitType`: the class defining how an individual `SpillTree` node
+ should be split. By default, [`MidpointSpaceSplit`](#midpointspacesplit) is
+ used.
+ - See the [`SplitType`](#splittype) section for more details.
+
+Note that the TreeType API requires trees to have only three template
+parameters. In order to use a `SpillTree` with its five template parameters
+with an mlpack algorithm that needs a TreeType, it is easiest to define a
+template typedef:
+
+```
+template
+using CustomTree = SpillTree
+```
+
+Here, `CustomHyperplaneType` and `CustomSplitType` are the desired hyperplane
+type and split strategy. This is the way that all `SpillTree` variants (such as
+[`SPTree`](sp_tree.md)) are defined.
+
+## Constructors
+
+`SpillTree`s are constructed by iteratively finding splitting hyperplanes, and
+points within a margin of the hyperplane are assigned to *both* child nodes.
+Unlike the constructors of
+[`BinarySpaceTree`](binary_space_tree.md#constructors), the dataset is not
+permuted during construction.
+
+---
+
+ * `node = SpillTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
+ - Construct a `SpillTree` on the given `data`, using the specified
+ hyperparameters to control tree construction behavior.
+ - Default template parameters are used, meaning that this tree will be a
+ [`SPTree`](sp_tree.md).
+ - By default, a reference to `data` is stored. If `data` goes out of scope
+ after tree construction, memory errors will occur! To avoid this, either
+ pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
+ when doing this, `data` will be set to an empty matrix.
+
+---
+
+ * `node = SpillTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
+ - Construct a `SpillTree` on the given `data`, using custom template
+ parameters, and using the specified hyperparameters to control tree
+ construction behavior.
+ - By default, a reference to `data` is stored. If `data` goes out of scope
+ after tree construction, memory errors will occur! To avoid this, either
+ pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
+ when doing this, `data` will be set to an empty matrix.
+
+---
+
+ * `node = SpillTree()`
+ - Construct an empty `SpillTree` with no children, no points, and
+ default template parameters.
+
+---
+
+***Notes:***
+
+ - The name `node` is used here for `SpillTree` objects instead of `tree`,
+ because each `SpillTree` object is a single node in the tree. The
+ constructor returns the node that is the root of the tree.
+
+ - Inserting individual points or removing individual points from a
+ `SpillTree` is not supported, because this generally results in a tree
+ with very suboptimal hyperplane splits. It is better to simply build a new
+ `SpillTree` on the modified dataset. For trees that support individual
+ insertion and deletions, see the [`RectangleTree`](rectangle_tree.md) class
+ and all its variants (e.g. [`RTree`](r_tree.md),
+ [`RStarTree`](r_star_tree.md), etc.).
+
+ - See also the
+ [developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
+
+---
+
+### Constructor parameters:
+
+| **name** | **type** | **description** | **default** |
+|----------|----------|-----------------|-------------|
+| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
+| `tau` | `double` | Width of spill margin: points within `tau` of the splitting hyperplane of a node will be contained in both left and right children. | `0.0` |
+| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
+| `rho` | `double` | Balance threshold. When splitting, if either overlapping node would contain a fraction of more than `rho` of the points, a non-overlapping split is performed. Must be in the range `[0.0, 1.0)`. | `0.7` |
+
+***Caveats***:
+
+ * `tau` must be manually tuned for the properties of each dataset; the default,
+ `0.0`, will never allow overlap between nodes (and thus the created tree will
+ essentially be a non-overlapping [`BinarySpaceTree`](binary_space_tree.md)).
+
+ * If `tau` is set too large, nodes will overlap too much and search quality
+ will be degraded.
+
+ * `rho` implicitly controls the depth of the tree by forcing very overlapping
+ children to be non-overlapping. As `rho` gets closer to `1`, more overlap is
+ allowed, which in turn makes the tree deeper. If `rho` is set to `0.5` or
+ less, then all splits will be non-overlapping (and the tree will essentially
+ be a [`BinarySpaceTree`](binary_space_tree.md)).
+
+## Basic tree properties
+
+Once a `SpillTree` object is constructed, various properties of the tree
+can be accessed or inspected. Many of these functions are required by the
+[TreeType API](../../../developer/trees.md#the-treetype-api).
+
+### Navigating the tree
+
+ * `node.NumChildren()` returns the number of children in `node`. This is
+ either `2` if `node` has children, or `0` if `node` is a leaf.
+
+ * `node.IsLeaf()` returns a `bool` indicating whether or not `node` is a leaf.
+
+ * `node.Child(i)` returns a `SpillTree&` that is the `i`th child.
+ - `i` must be `0` or `1`.
+ - This function should only be called if `node.NumChildren()` is not `0`
+ (e.g. if `node` is not a leaf). Note that this returns a valid
+ `SpillTree&` that can itself be used just like the root node of the
+ tree!
+ - `node.Left()` and `node.Right()` are convenience functions specific to
+ `SpillTree` that will return `SpillTree*` (pointers) to the left and right
+ children, respectively, or `NULL` if `node` has no children.
+
+ * `node.Parent()` will return a `SpillTree*` that points to the parent of
+ `node`, or `NULL` if `node` is the root of the `SpillTree`.
+
+---
+
+### Accessing members of a tree
+
+ * `node.Overlap()` will return a `bool` that is `true` if `node`'s children are
+ overlapping, and `false` otherwise.
+
+ * `node.Hyperplane()` will return a `HyperplaneType&` object that represents
+ the splitting hyperplane of `node`.
+ - All points in `node.Left()` are to the left of `node.Hyperplane()` if
+ `node.Overlap()` is `false`; otherwise, all points in `node.Left()` are to
+ the left of `node.Hyperplane() + tau`.
+ - All points in `node.Right()` are to the right of `node.Hyperplane()` if
+ `node.Overlap()` is `false`; otherwise, all points in `node.Right()` are to
+ the right of `node.Hyperplane() - tau`.
+
+ * `node.Bound()` will return a
+ [`const HRectBound&`](binary_space_tree.md#hrectbound) representing the
+ bounding box associated with `node`.
+ - If a [custom `HyperplaneType`](#hyperplanetype) is specified, then the
+ `BoundType` associated with that hyperplane type is returned instead.
+ - If a [custom `DistanceType` and/or `MatType`](#template-parameters) are
+ specified, then a `const HRectBound&` is returned
+ (or a `BoundType` with that `DistanceType`, if a custom `HyperplaneType`
+ was also specified).
+ * `ElemType` is the element type of the specified `MatType` (e.g. `double`
+ for `arma::mat`, `float` for `arma::fmat`, etc.).
+
+ * `node.Stat()` will return a `StatisticType&` holding the statistics of the
+ node that were computed during tree construction.
+
+ * `node.Distance()` will return a `DistanceType&`.
+
+See also the
+[developer documentation](../../../developer/trees.md#basic-tree-functionality)
+for basic tree functionality in mlpack.
+
+---
+
+### Accessing data held in a tree
+
+ * `node.Dataset()` will return a `const MatType&` that is the dataset the
+ tree was built on.
+
+ * `node.NumPoints()` returns a `size_t` indicating the number of points held
+ directly in `node`.
+ - If `node` is not a leaf, this will return `0`, as `SpillTree` only holds
+ points directly in its leaves.
+ - If `node` is a leaf, then the number of points will be less than or equal
+ to the `maxLeafSize` that was specified when the tree was constructed.
+
+ * `node.Point(i)` returns a `size_t` indicating the index of the `i`'th point
+ in `node.Dataset()`.
+ - `i` must be in the range `[0, node.NumPoints() - 1]` (inclusive).
+ - `node` must be a leaf (as non-leaves do not hold any points).
+ - The `i`'th point in `node` can then be accessed as
+ `node.Dataset().col(node.Point(i))`.
+ - Accessing the actual `i`'th point itself can be done with, e.g.,
+ `node.Dataset().col(node.Point(i))`.
+
+ * `node.NumDescendants()` returns a `size_t` indicating the number of points
+ held in all descendant leaves of `node`.
+ - If `node` is the root of the tree, then `node.NumDescendants()` will be
+ equal to `node.Dataset().n_cols`.
+
+ * `node.Descendant(i)` returns a `size_t` indicating the index of the `i`'th
+ descendant point in `node.Dataset()`.
+ - `i` must be in the range `[0, node.NumDescendants() - 1]` (inclusive).
+ - `node` does not need to be a leaf.
+ - The `i`'th descendant point in `node` can then be accessed as
+ `node.Dataset().col(node.Descendant(i))`.
+ - Accessing the actual `i`'th descendant itself can be done with, e.g.,
+ `node.Dataset().col(node.Descendant(i))`.
+
+---
+
+### Accessing computed bound quantities of a tree
+
+The following quantities are cached for each node in a `SpillTree`, and so
+accessing them does not require any computation. In the documentation below,
+`ElemType` is the element type of the given `MatType`; e.g., if `MatType` is
+`arma::mat`, then `ElemType` is `double`.
+
+ * `node.FurthestPointDistance()` returns an `ElemType` representing the
+ distance between the center of the bound of `node` and the furthest point
+ held by `node`.
+ - If `node` is not a leaf, this returns 0 (because `node` does not hold any
+ points).
+
+ * `node.FurthestDescendantDistance()` returns an `ElemType` representing the
+ distance between the center of the bound of `node` and the furthest
+ descendant point held by `node`.
+
+ * `node.MinimumBoundDistance()` returns an `ElemType` representing the minimum
+ possible distance from the center of the node to any edge of its bound.
+
+ * `node.ParentDistance()` returns an `ElemType` representing the distance
+ between the center of the bound of `node` and the center of the bound of its
+ parent.
+ - If `node` is the root of the tree, `0` is returned.
+
+***Note:*** for more details on each bound quantity, see the [developer
+documentation](../../../developer/trees.md#complex-tree-functionality-and-bounds)
+on bound quantities for trees.
+
+---
+
+### Other functionality
+
+ * `node.Center(center)` computes the center of the bound of `node` and stores
+ it in `center`.
+ - `center` should be of type `arma::Col&`, where `ElemType` is the
+ element type of the specified `MatType`.
+ - `center` will be set to have size equivalent to the dimensionality of the
+ dataset held by `node`.
+ - This is equivalent to calling `node.Bound().Center(center)`.
+
+ * A `SpillTree` can be serialized with
+ [`data::Save()` and `data::Load()`](../../load_save.md#mlpack-objects).
+
+## Bounding distances with the tree
+
+The primary use of trees in mlpack is bounding distances to points or other tree
+nodes. The following functions can be used for these tasks.
+
+ * `node.GetNearestChild(point)`
+ * `node.GetFurthestChild(point)`
+ - Return a `size_t` indicating the index of the child (`0` for left, `1` for
+ right) that is closest to (or furthest from) `point`, with respect
+ to the `MinDistance()` (or `MaxDistance()`) function.
+ - If there is a tie, `0` (the left child) is returned.
+ - If `node` is a leaf, `0` is returned.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.GetNearestChild(other)`
+ * `node.GetFurthestChild(other)`
+ - Return a `size_t` indicating the index of the child (`0` for left, `1` for
+ right) that is closest to (or furthest from) the `SpillTree` node
+ `other`, with respect to the `MinDistance()` (or `MaxDistance()`) function.
+ - If there is a tie, `0` (the left child) is returned.
+ - If `node` is a leaf, `0` is returned.
+
+---
+
+ * `node.MinDistance(point)`
+ * `node.MinDistance(other)`
+ - Return a `double` indicating the minimum possible distance between `node`
+ and `point`, or the `SpillTree` node `other`.
+ - This is equivalent to the minimum possible distance between any point
+ contained in the bounding hyperrectangle of `node` and `point`, or between
+ any point contained in the bounding hyperrectangle of `node` and any point
+ contained in the bounding hyperrectangle of `other`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.MaxDistance(point)`
+ * `node.MaxDistance(other)`
+ - Return a `double` indicating the maximum possible distance between `node`
+ and `point`, or the `SpillTree` node `other`.
+ - This is equivalent to the maximum possible distance between any point
+ contained in the bounding hyperrectangle of `node` and `point`, or between
+ any point contained in the bounding hyperrectangle of `node` and any point
+ contained in the bounding hyperrectangle of `other`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+ * `node.RangeDistance(point)`
+ * `node.RangeDistance(other)`
+ - Return a [`RangeType`](../math.md#range) whose lower bound is
+ `node.MinDistance(point)` or `node.MinDistance(other)`, and whose upper
+ bound is `node.MaxDistance(point)` or `node.MaxDistance(other)`.
+ - `ElemType` is the element type of `MatType`.
+ - `point` should be a column vector type of the same type as `MatType`.
+ (e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
+
+## Tree traversals
+
+Like every mlpack tree, the `SpillTree` class provides a [single-tree and
+dual-tree traversal](../../../developer/trees.md#traversals) that can be paired
+with a [`RuleType` class](../../../developer/trees.md#rules) to implement a
+single-tree or dual-tree algorithm.
+
+ * `SpillTree::SingleTreeTraverser`
+ - Implements a depth-first single-tree traverser.
+
+ * `SpillTree::DualTreeTraverser`
+ - Implements a dual-depth-first dual-tree traverser.
+
+However, the spill tree is primarily useful because the overlapping nodes allow
+*defeatist* search to be effective. Defeatist search is non-backtracking: the
+tree is traversed to one leaf only. For example, finding the approximate
+nearest neighbor of a point `p` with defeatist search is done by recursing in
+the tree, choosing the child with smallest minimum distance to `p`, and when a
+leaf is encountered, choosing the closest point in the leaf to `p` as the
+nearest neighbor. This is the strategy used in the
+[original spill tree paper (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf).
+
+Defeatist traversers, matching the API for a regular
+[traversal](../../../developer/trees.md#traversals) are made available as the
+following two classes:
+
+ * `SpillTree::DefeatistSingleTreeTraverser`
+ - Implements a depth-first single-tree defeatist traverser with no
+ backtracking. Traversal will terminate after the first leaf is visited.
+
+ * `SpillTree::DefeatistDualTreeTraverser`
+ - Implements a dual-depth-first dual-tree defeatist traversal with no
+ backtracking. For each query leaf node, traversal will terminate after the
+ first reference leaf node is visited.
+
+Any [`RuleType`](../../../developer/trees.md#rules) that is being used with a
+defeatist traversal, in addition to the functions required by the `RuleType`
+API, must implement the following functions:
+
+```
+// This is only required for single-tree defeatist traversals.
+// It should return the index of the branch that should be chosen for the given
+// query point and reference node.
+template
+size_t GetBestChild(const VecType& queryPoint, TreeType& referenceNode);
+
+// This is only required for dual-tree defeatist traversals.
+// It should return the index of the best child of the reference node that
+// should be chosen for the given query node.
+template
+size_t GetBestChild(TreeType& queryNode, TreeType& referenceNode);
+
+// Return the minimum number of base cases (point-to-point computations) that
+// are required during the traversal.
+size_t MinimumBaseCases();
+```
+
+## `HyperplaneType`
+
+Each node in a `SpillTree` corresponds to some region in space that contains all
+of the descendant points in the node. Similar to the [`KDTree`](kdtree.md), this
+region is a hyperrectangle; however, instead of representing that hyperrectangle
+explicitly like the `KDTree` with the
+[`HRectBound`](binary_space_tree.md#hrectbound) class, the `SpillTree`
+represents the region *implicitly*, with each node storing only the hyperplane
+and margin required to determine whether a point belongs to the left node, the
+right node, or both.
+
+The type of hyperplane (e.g. axis-aligned or arbitrary) can be controlled by the
+`HyperplaneType` template parameter. mlpack supplies two drop-in classes that
+can be used for `HyperplaneType`, and it is also possible to write a custom
+`HyperplaneType`:
+
+ * [`AxisOrthogonalHyperplane`](#axisorthogonalhyperplane): uses hyperplanes
+ that are axis-orthogonal (or axis-aligned).
+ * [`Hyperplane`](#hyperplane): uses arbitrary hyperplanes specified by any
+ vector.
+ * [Custom `HyperplaneType`s](#custom-hyperplanetypes): implement a fully custom
+ `HyperplaneType` class
+
+### `AxisOrthogonalHyperplane`
+
+The `AxisOrthogonalHyperplane` class is used to provide an axis-orthogonal split
+for a `SpillTree`. That is, whether or not a point is on the left or right side
+of the split is a very efficient computation using only a single dimension of
+the data.
+
+ * The `AxisOrthogonalHyperplane` class defines the following two typedefs:
+ - `AxisOrthogonalHyperplane::BoundType`, which is the type of the bound used
+ by the spill tree with this hyperplane, is
+ [`HRectBound`](binary_space_tree.md#hrectbound), or
+ `HRectBound` if custom
+ [`DistanceType` and/or `MatType`](#template-parameters) are specified.
+
+ - `AxisOrthogonalHyperplane::ProjVectorType` is `AxisParallelProjVector`, a
+ class that simply holds the index of the dimension of the projection
+ vector.
+ * For more details, see
+ [the source code](/src/mlpack/core/tree/space_split/projection_vector.hpp).
+
+ * An `AxisOrthogonalHyperplane` object `h` (e.g. returned with
+ `node.Hyperplane()`) has the following members:
+
+ - `h.Project(point)` returns a `double` that is the orthogonal projection of
+ `point` onto the tangent vector of the hyperplane `h`.
+
+ - `h.Left(point)` returns `true` if `point` is to the left of `h`.
+
+ - `h.Right(point)` returns `true` if `point` is to the right of `h`.
+
+ - `h.Left(bound)` returns `true` if `bound` (an
+ `AxisOrthogonalHyperplane::BoundType`; see the bullet point above) is to
+ the left of `h`.
+
+ - `h.Right(bound)` returns `true` if `bound` (an
+ `AxisOrthogonalHyperplane::BoundType`; see the bullet point above) is to
+ the right of `h`.
+
+ * An `AxisOrthogonalHyperplane` object can be serialized with
+ [`data::Save()` and `data::Load()`](../../load_save.md#mlpack-objects).
+
+For more details, see the
+[the source code](/src/mlpack/core/tree/space_split/hyperplane.hpp).
+
+### `Hyperplane`
+
+The `Hyperplane` class is used to provide an arbitrary hyperplane split for a
+`SpillTree`. The computation of whether or not a point is on the left or right
+side of the split is less efficient than
+[`AxisOrthogonalHyperplane`](#axisorthogonalhyperplane), but `Hyperplane` is
+able to represent any possible hyperplane.
+
+ * The `Hyperplane` class defines the two following typedefs:
+ - `Hyperplane::BoundType`, which is the type of the bound used by the spill
+ tree with this hyperplane, is
+ [`BallBound`](binary_space_tree.md#ballbound), or `BallBound`
+ if a custom [`DistanceType`](#template-parameters) is specified.
+
+ - `Hyperplane::ProjVectorType` is `ProjVector<>`, an arbitrary projection
+ vector class that wraps an `arma::vec`.
+ * If a custom `MatType` is specified, then `Hyperplane::ProjVectorType` is
+ `ProjVector`, which wraps a vector of the same type as
+ `MatType`.
+ * For more details, see
+ [the source code](/src/mlpack/core/tree/space_split/projection_vector.hpp).
+
+ * A `Hyperplane` object `h` (e.g. returned with `node.Hyperplane()`) has the
+ following members:
+
+ - `h.Project(point)` returns a `double` that is the orthogonal projection of
+ `point` onto the tangent vector of the hyperplane `h`.
+
+ - `h.Left(point)` returns `true` if `point` is to the left of `h`.
+
+ - `h.Right(point)` returns `true` if `point` is to the right of `h`.
+
+ - `h.Left(bound)` returns `true` if `bound` (an
+ `AxisOrthogonalHyperplane::BoundType`; see the bullet point above) is to
+ the left of `h`.
+
+ - `h.Right(bound)` returns `true` if `bound` (a `Hyperplane::BoundType`; see
+ the bullet point above) is to the right of `h`.
+
+ * A `Hyperplane` object can be serialized with
+ [`data::Save()` and `data::Load()`](../../load_save.md#mlpack-objects).
+
+For more details, see the
+[the source code](/src/mlpack/core/tree/space_split/hyperplane.hpp).
+
+### Custom `HyperplaneType`s
+
+Custom hyperplane types for a spill tree can be implemented via the
+`HyperplaneType` template parameter. By default, the
+[`AxisOrthogonalHyperplane`](#axisorthogonalhyperplane) hyperplane type is used,
+but it is also possible to implement and use a custom `HyperplaneType`. Any
+custom `HyperplaneType` class must implement the following signature:
+
+```c++
+// NOTE: the custom HyperplaneType class must take two template parameters.
+template
+class HyperplaneType
+{
+ public:
+ // The hyperplane type must specify these two public typedefs, which are used
+ // by the spill tree and the splitting strategy.
+ //
+ // Substitute HRectBound and ProjVector with your choices.
+ using BoundType = mlpack::HRectBound;
+ using ProjVectorType = mlpack::ProjVector;
+
+ // Empty constructor, which will construct an empty or default hyperplane.
+ HyperplaneType();
+
+ // Construct the HyperplaneType with the given projection vector and split
+ // value along that projection.
+ HyperplaneType(const ProjVectorType& projVector, double splitVal);
+
+ // Compute the projection of the given point (an `arma::vec` or similar type
+ // matching the Armadillo API and element type of `MatType`) onto the vector
+ // tangent to the hyperplane.
+ template
+ double Project(const VecType& point) const;
+
+ // Return true if the point (an `arma::vec` or similar type matching the
+ // Armadillo API and element type of `MatType`) falls to the left of the
+ // hyperplane.
+ template
+ double Left(const VecType& point) const;
+
+ // Return true if the point (an `arma::vec` or similar type matching the
+ // Armadillo API and element type of `MatType`) falls to the right of the
+ // hyperplane.
+ template
+ double Right(const VecType& point) const;
+
+ // Return true if the given bound is fully to the left of the hyperplane.
+ bool Left(const BoundType& bound) const;
+
+ // Return true if the given bound is fully to the right of the hyperplane.
+ bool Right(const BoundType& bound) const;
+
+ // Serialize the hyperplane using cereal.
+ template
+ void serialize(Archive& ar, const uint32_t version);
+};
+```
+
+## `SplitType`
+
+The `SplitType` template parameter controls the algorithm used to split each
+node of a `SpillTree` while building. The splitting strategy used can be
+entirely arbitrary---the `SplitType` only needs to compute a
+[`HyperplaneType`](#hyperplanetype) to split a set of points.
+
+mlpack provides two drop-in choices for `SplitType`, and it is also possible
+to write a fully custom split:
+
+ * [`MidpointSpaceSplit`](#midpointspacesplit): split a set of points using a
+ hyperplane built on the midpoint (median) of points in a dataset.
+ * [`MeanSpaceSplit`](#meanspacesplit): split a set of points using a hyperplane
+ built on the mean (average) of points in a dataset.
+ * [Custom `SplitType`s](#custom-splittypes): implement a fully custom
+ `SplitType` class
+
+### `MidpointSpaceSplit`
+
+The `MidpointSpaceSplit` class is a splitting strategy that can be used by
+`SpillTree`. It is the default strategy for splitting [`SPTree`s](sp_tree.md)
+and [`NonOrtSPTree`s](non_ort_sp_tree.md).
+
+The splitting strategy for the `MidpointSpaceSplit` class is, given a set of
+points:
+
+ * If [`AxisOrthogonalHyperplane`](#axisorthogonalhyperplane) is being used,
+ then select the dimension with the maximum width, and use the midpoint of the
+ points' values in that dimension.
+
+ * If [`Hyperplane`](#hyperplane) is being used, then estimate the furthest two
+ points in the dataset by random sampling, and use the vector connecting those
+ points as the tangent vector to the hyperplane. The midpoint of the points
+ projected onto this hyperplane is used as the split value.
+
+Note that `MidpointSpaceSplit` can only be used with a `HyperplaneType` with
+`HyperplaneType::ProjVectorType` as either `AxisAlignedProjVector` or
+`ProjVector`.
+
+For implementation details, see
+[the source code](/src/mlpack/core/tree/space_split/midpoint_space_split_impl.hpp).
+
+### `MeanSpaceSplit`
+
+The `MeanSpaceSplit` class is a splitting strategy that can be used by
+`SpillTree`. It is the splitting strategy used by the
+[`MeanSPTree`](mean_sp_tree.md) and the
+[`NonOrtMeanSPTree`](non_ort_mean_sp_tree.md) classes.
+
+The splitting strategy for the `MeanSpaceSplit` class is, given a set of
+points:
+
+ * If [`AxisOrthogonalHyperplane`](#axisorthogonalhyperplane) is being used,
+ then select the dimension with the maximum width, and use the mean of the
+ points' values in that dimension.
+
+ * If [`Hyperplane`](#hyperplane) is being used, then estimate the furthest two
+ points in the dataset by random sampling, and use the vector connecting those
+ points as the tangent vector to the hyperplane. The mean of the points
+ projected onto this hyperplane is used as the split value.
+
+Note that `MeanSpaceSplit` can only be used with a `HyperplaneType` with
+`HyperplaneType::ProjVectorType` as either `AxisAlignedProjVector` or
+`ProjVector`.
+
+For implementation details, see
+[the source code](/src/mlpack/core/tree/space_split/mean_space_split_impl.hpp).
+
+### Custom `SplitType`s
+
+Custom split strategies for a spill tree can be implemented via the
+`SplitType` template parameter. By default, the
+[`MidpointSpaceSplit`](#midpointspacesplit) splitting strategy is used, but it
+is also possible to implement and use a custom `SplitType`. Any custom
+`SplitType` class must implement the following signature:
+
+```c++
+// NOTE: the custom SplitType class must take two template parameters.
+template
+class SplitType
+{
+ public:
+ // The SplitType class is only required to provide one static function.
+
+ // Create a splitting hyperplane and store it in the given `HyperplaneType`,
+ // using the given data and bounding box `bound`. `data` will be an Armadillo
+ // matrix that is the entire dataset, and `points` are the indices of points
+ // in `data` that should be split.
+ template
+ static bool SplitSpace(
+ const typename HyperplaneType::BoundType& bound,
+ const MatType& data,
+ const arma::Col& points,
+ HyperplaneType& hyp);
+};
+```
+
+## Example usage
+
+The `SpillTree` class is only really necessary when a custom hyperplane type or
+custom splitting strategy is intended to be used. For simpler use cases, one of
+the typedefs of `SpillTree` (such as [`SPTree`](sp_tree.md)) will suffice.
+
+For this reason, all of the examples below explicitly specify all five template
+parameters of `SPTree`.
+[Writing a custom hyperplane type](#custom-hyperplanetypes) and
+[writing a custom splitting strategy](#custom-splittypes) are discussed
+in the previous sections. Each of the parameters in the examples below can be
+trivially changed for different behavior.
+
+---
+
+Build a `SpillTree` on the `cloud` dataset and print basic statistics about the
+tree.
+
+```c++
+// See https://datasets.mlpack.org/cloud.csv.
+arma::mat dataset;
+mlpack::data::Load("cloud.csv", dataset, true);
+
+// Build the spill tree with a tau (margin) of 0.2 and a leaf size of 10.
+// (This means that nodes are split until they contain 10 or fewer points.)
+//
+// The std::move() means that `dataset` will be empty after this call, and no
+// data will be copied during tree building.
+mlpack::SpillTree tree(std::move(dataset), 0.2, 10);
+
+// Print the bounding box of the root node.
+std::cout << "Bounding box of root node:" << std::endl;
+for (size_t i = 0; i < tree.Bound().Dim(); ++i)
+{
+ std::cout << " - Dimension " << i << ": [" << tree.Bound()[i].Lo() << ", "
+ << tree.Bound()[i].Hi() << "]." << std::endl;
+}
+std::cout << std::endl;
+
+// Print the number of descendant points of the root, and of each of its
+// children.
+std::cout << "Descendant points of root: "
+ << tree.NumDescendants() << "." << std::endl;
+std::cout << "Descendant points of left child: "
+ << tree.Left()->NumDescendants() << "." << std::endl;
+std::cout << "Descendant points of right child: "
+ << tree.Right()->NumDescendants() << "." << std::endl;
+std::cout << std::endl;
+
+// Compute the center of the SpillTree.
+arma::vec center;
+tree.Center(center);
+std::cout << "Center of tree: " << center.t();
+```
+
+---
+
+Build two `SpillTree`s on subsets of the corel dataset and compute minimum
+and maximum distances between different nodes in the tree.
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::mat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset, true);
+
+// Convenience typedef for the tree type.
+using TreeType = mlpack::SpillTree;
+
+// Build trees on the first half and the second half of points. Use a tau
+// (overlap) parameter of 0.3, which is tuned to this dataset, and a rho value
+// of 0.6 to prevent the trees getting too deep.
+TreeType tree1(dataset.cols(0, dataset.n_cols / 2), 0.3, 20, 0.6);
+TreeType tree2(dataset.cols(dataset.n_cols / 2 + 1, dataset.n_cols - 1),
+ 0.3, 20, 0.6);
+
+// Compute the maximum distance between the trees.
+std::cout << "Maximum distance between tree root nodes: "
+ << tree1.MaxDistance(tree2) << "." << std::endl;
+
+// Get the leftmost grandchild of the first tree's root---if it exists.
+if (!tree1.IsLeaf() && !tree1.Child(0).IsLeaf())
+{
+ TreeType& node1 = tree1.Child(0).Child(0);
+
+ // Get the rightmost grandchild of the second tree's root---if it exists.
+ if (!tree2.IsLeaf() && !tree2.Child(1).IsLeaf())
+ {
+ TreeType& node2 = tree2.Child(1).Child(1);
+
+ // Print the minimum and maximum distance between the nodes.
+ mlpack::Range dists = node1.RangeDistance(node2);
+ std::cout << "Possible distances between two grandchild nodes: ["
+ << dists.Lo() << ", " << dists.Hi() << "]." << std::endl;
+
+ // Print the minimum distance between the first node and the first
+ // descendant point of the second node.
+ const size_t descendantIndex = node2.Descendant(0);
+ const double descendantMinDist =
+ node1.MinDistance(node2.Dataset().col(descendantIndex));
+ std::cout << "Minimum distance between grandchild node and descendant "
+ << "point: " << descendantMinDist << "." << std::endl;
+
+ // Which child of node2 is closer to node1?
+ const size_t closerIndex = node2.GetNearestChild(node1);
+ if (closerIndex == 0)
+ std::cout << "The left child of node2 is closer to node1." << std::endl;
+ else if (closerIndex == 1)
+ std::cout << "The right child of node2 is closer to node1." << std::endl;
+ else // closerIndex == 2 in this case.
+ std::cout << "Both children of node2 are equally close to node1."
+ << std::endl;
+
+ // And which child of node1 is further from node2?
+ const size_t furtherIndex = node1.GetFurthestChild(node2);
+ if (furtherIndex == 0)
+ std::cout << "The left child of node1 is further from node2."
+ << std::endl;
+ else if (furtherIndex == 1)
+ std::cout << "The right child of node1 is further from node2."
+ << std::endl;
+ else // furtherIndex == 2 in this case.
+ std::cout << "Both children of node1 are equally far from node2."
+ << std::endl;
+ }
+}
+```
+
+---
+
+Build a `SpillTree` on 32-bit floating point data and save it to disk.
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::fmat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset);
+
+// Build the SpillTree using 32-bit floating point data as the matrix type.
+// We will still use the default EmptyStatistic and EuclideanDistance
+// parameters.
+mlpack::SpillTree tree(
+ std::move(dataset), 0.1, 20, 0.95);
+
+// Save the tree to disk with the name 'tree'.
+mlpack::data::Save("tree.bin", "tree", tree);
+
+std::cout << "Saved tree with " << tree.Dataset().n_cols << " points to "
+ << "'tree.bin'." << std::endl;
+```
+
+---
+
+Load a 32-bit floating point `SpillTree` from disk, then traverse it
+manually and find the number of nodes whose children overlap.
+
+```c++
+// This assumes the tree has already been saved to 'tree.bin' (as in the example
+// above).
+
+// This convenient typedef saves us a long type name!
+using TreeType = mlpack::SpillTree;
+
+TreeType tree;
+mlpack::data::Load("tree.bin", "tree", tree);
+std::cout << "Tree loaded with " << tree.NumDescendants() << " points."
+ << std::endl;
+
+// Recurse in a depth-first manner. Count both the total number of non-leaves,
+// and the number of non-leaves that have overlapping children.
+size_t overlapCount = 0;
+size_t totalInternalNodeCount = 0;
+std::stack stack;
+stack.push(&tree);
+while (!stack.empty())
+{
+ TreeType* node = stack.top();
+ stack.pop();
+
+ if (node->IsLeaf())
+ continue;
+
+ if (node->Overlap())
+ ++overlapCount;
+ ++totalInternalNodeCount;
+
+ stack.push(node->Left());
+ stack.push(node->Right());
+}
+
+// Note that it would be possible to use TreeType::SingleTreeTraverser to
+// perform the recursion above, but that is more well-suited for more complex
+// tasks that require pruning and other non-trivial behavior; so using a simple
+// stack is the better option here.
+
+// Print the results.
+std::cout << overlapCount << " out of " << totalInternalNodeCount
+ << " internal nodes have overlapping children." << std::endl;
+```
+
+---
+
+Use a defeatist traversal to find the approximate nearest neighbor of the third
+and fourth points in the `corel-histogram` dataset. (Note: this can also be
+done more easily with the `KNN` class! This example is a demonstration of how
+to use the defeatist traverser.)
+
+
+
+For this example, we must first define a
+[`RuleType` class](../../../developer/trees.md#rules).
+
+```c++
+// For simplicity, this only implements those methods required by single-tree
+// traversals, and cannot be used with a dual-tree traversal.
+//
+// `.Reset()` must be called before any additional single-tree traversals after
+// the first is run.
+class SpillNearestNeighborRule
+{
+ public:
+ // Store the dataset internally.
+ SpillNearestNeighborRule(const arma::mat& dataset) :
+ dataset(dataset),
+ nearestNeighbor(size_t(-1)),
+ nearestDistance(DBL_MAX) { }
+
+ // Compute the base case (point-to-point comparison).
+ double BaseCase(const size_t queryIndex, const size_t referenceIndex)
+ {
+ // Skip the base case if the points are the same.
+ if (queryIndex == referenceIndex)
+ return 0.0;
+
+ const double dist = mlpack::EuclideanDistance::Evaluate(
+ dataset.col(queryIndex), dataset.col(referenceIndex));
+
+ if (dist < nearestDistance)
+ {
+ nearestNeighbor = referenceIndex;
+ nearestDistance = dist;
+ }
+
+ return dist;
+ }
+
+ // Score the given node in the tree; if it is sufficiently far away that it
+ // cannot contain a better nearest neighbor candidate, we can prune it.
+ template
+ double Score(const size_t queryIndex, const TreeType& referenceNode) const
+ {
+ const double minDist = referenceNode.MinDistance(dataset.col(queryIndex));
+ if (minDist > nearestDistance)
+ return DBL_MAX; // Prune: this cannot contain a better candidate!
+
+ return minDist;
+ }
+
+ // Rescore the given node/point combination. Note that this will not be used
+ // by the defeatist traversal as it never backtracks, but we include it for
+ // completeness because the RuleType API requires it.
+ template
+ double Rescore(const size_t, const TreeType&, const double oldScore) const
+ {
+ if (oldScore > nearestDistance)
+ return DBL_MAX; // Prune: the node is too far away.
+ return oldScore;
+ }
+
+ // This is required by defeatist traversals to select the best reference
+ // child to recurse into for overlapping nodes.
+ template
+ size_t GetBestChild(const size_t queryIndex, TreeType& referenceNode)
+ const
+ {
+ return referenceNode.GetNearestChild(dataset.col(queryIndex));
+ }
+
+ // We must perform at least two base cases in order to have a result. Note
+ // that this is two, and not one, because we skip base cases where the query
+ // and reference points are the same. That can only happen a maximum of once,
+ // so to ensure that we compare a query point to a different reference point
+ // at least once, we must return 2 here.
+ size_t MinimumBaseCases() const { return 2; }
+
+ // Get the results (to be called after the traversal).
+ size_t NearestNeighbor() const { return nearestNeighbor; }
+ double NearestDistance() const { return nearestDistance; }
+
+ // Reset the internal statistics for an additional traversal.
+ void Reset()
+ {
+ nearestNeighbor = size_t(-1);
+ nearestDistance = DBL_MAX;
+ }
+
+ private:
+ const arma::mat& dataset;
+
+ size_t nearestNeighbor;
+ double nearestDistance;
+};
+```
+
+```c++
+// See https://datasets.mlpack.org/corel-histogram.csv.
+arma::mat dataset;
+mlpack::data::Load("corel-histogram.csv", dataset, true);
+
+typedef mlpack::SpillTree TreeType;
+
+// Build two trees, one with a lot of overlap, and one with no overlap
+// (e.g. tau = 0).
+TreeType tree1(dataset, 0.5, 10), tree2(dataset, 0.0, 10);
+
+// Construct the rule types, and then the traversals.
+SpillNearestNeighborRule r1(dataset), r2(dataset);
+
+TreeType::DefeatistSingleTreeTraverser t1(r1);
+TreeType::DefeatistSingleTreeTraverser t2(r2);
+
+// Search for the approximate nearest neighbor of point 3 using both trees.
+t1.Traverse(3, tree1);
+t2.Traverse(3, tree2);
+
+std::cout << "Approximate nearest neighbor of point 3:" << std::endl;
+std::cout << " - Spill tree with overlap 0.5 found: point "
+ << r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
+ << "." << std::endl;
+
+std::cout << " - Spill tree with no overlap found: point "
+ << r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
+ << "." << std::endl;
+
+// Now search for point 6.
+r1.Reset();
+r2.Reset();
+
+t1.Traverse(6, tree1);
+t2.Traverse(6, tree2);
+
+std::cout << "Approximate nearest neighbor of point 6:" << std::endl;
+std::cout << " - Spill tree with overlap 0.5 found: point "
+ << r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
+ << "." << std::endl;
+
+std::cout << " - Spill tree with no overlap found: point "
+ << r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
+ << "." << std::endl;
+```
diff --git a/doc/user/core/trees/ub_tree.md b/doc/user/core/trees/ub_tree.md
index 025fa5451a..42b2c021cf 100644
--- a/doc/user/core/trees/ub_tree.md
+++ b/doc/user/core/trees/ub_tree.md
@@ -133,13 +133,11 @@ different.
bounding boxes. It is better to simply build a new `UBTree` on the modified
dataset. For trees that support individual insertion and deletions, see the
[`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
- [`RTree`](r_tree.md), `RStarTree`, etc.).
+ [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
- See also the
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
-
-
---
### Constructor parameters:
@@ -427,7 +425,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
//
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
// `mlpack::UBTree tree(...)` will work fine in C++20 or newer).
-mlpack::UBTree<> tree(std::move(dataset));
+mlpack::UBTree<> tree(std::move(dataset), 10);
// Print the bounding box of the root node.
std::cout << "Outer bounding box of root node:" << std::endl;
diff --git a/doc/user/core/trees/vptree.md b/doc/user/core/trees/vptree.md
index 04b4675170..9a5c7361ab 100644
--- a/doc/user/core/trees/vptree.md
+++ b/doc/user/core/trees/vptree.md
@@ -128,13 +128,11 @@ different.
very loose bounding balls. It is better to simply build a new `VPTree` on
the modified dataset. For trees that support individual insertion and
deletions, see the [`RectangleTree`](rectangle_tree.md) class and all its
- variants (e.g. [`RTree`](r_tree.md), `RStarTree`, etc.).
+ variants (e.g. [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
- See also the
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
-
-
---
### Constructor parameters:
@@ -417,7 +415,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
//
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
// `mlpack::VPTree tree(...)` will work fine in C++20 or newer).
-mlpack::VPTree<> tree(std::move(dataset));
+mlpack::VPTree<> tree(std::move(dataset), 10);
// Print the bounding ball of the root node. (There will be no hollow ball.)
std::cout << "Bounding ball of root node:" << std::endl;
diff --git a/doc/user/core/trees/x_tree.md b/doc/user/core/trees/x_tree.md
index ad63df46f3..955317ec63 100644
--- a/doc/user/core/trees/x_tree.md
+++ b/doc/user/core/trees/x_tree.md
@@ -148,7 +148,7 @@ The dataset is not permuted during the construction process.
| **name** | **type** | **description** | **default** |
|----------|----------|-----------------|-------------|
-| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
+| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
diff --git a/src/mlpack/core/tree/space_split/hyperplane.hpp b/src/mlpack/core/tree/space_split/hyperplane.hpp
index 3e80686e58..c8e682d5ff 100644
--- a/src/mlpack/core/tree/space_split/hyperplane.hpp
+++ b/src/mlpack/core/tree/space_split/hyperplane.hpp
@@ -25,21 +25,23 @@ namespace mlpack {
* @tparam ProjVectorT Type of projection vector (AxisParallelProjVector,
* ProjVector).
*/
-template
+template
class HyperplaneBase
{
public:
- //! Useful typedef for the bound type.
+ // Useful typedef for the bound type.
using BoundType = BoundT;
- //! Useful typedef for the projection vector type.
+ // Useful typedef for the projection vector type.
using ProjVectorType = ProjVectorT;
+ // Useful typedef for the element type held by data matrices.
+ using ElemType = typename MatType::elem_type;
private:
- //! Projection vector.
+ // Projection vector.
ProjVectorType projVect;
- //! Projection value that determines the decision boundary.
- double splitVal;
+ // Projection value that determines the decision boundary.
+ ElemType splitVal;
public:
/**
@@ -55,7 +57,7 @@ class HyperplaneBase
* @param projVect Projection vector.
* @param splitVal Split value.
*/
- HyperplaneBase(const ProjVectorType& projVect, double splitVal) :
+ HyperplaneBase(const ProjVectorType& projVect, ElemType splitVal) :
projVect(projVect),
splitVal(splitVal)
{};
@@ -67,8 +69,9 @@ class HyperplaneBase
* @param point Point to be projected.
*/
template
- double Project(const VecType& point,
- typename std::enable_if_t::value>* = 0) const
+ ElemType Project(const VecType& point,
+ typename std::enable_if_t::value>* = 0)
+ const
{
if (splitVal == DBL_MAX)
return 0;
@@ -139,15 +142,18 @@ class HyperplaneBase
/**
* AxisOrthogonalHyperplane represents a hyperplane orthogonal to an axis.
*/
-template
-using AxisOrthogonalHyperplane = HyperplaneBase,
- AxisParallelProjVector>;
+template
+using AxisOrthogonalHyperplane = HyperplaneBase<
+ HRectBound,
+ AxisParallelProjVector, MatType>;
/**
* Hyperplane represents a general hyperplane (not necessarily axis-orthogonal).
*/
-template
-using Hyperplane = HyperplaneBase, ProjVector>;
+template
+using Hyperplane = HyperplaneBase<
+ BallBound, ProjVector,
+ MatType>;
} // namespace mlpack
diff --git a/src/mlpack/core/tree/space_split/mean_space_split_impl.hpp b/src/mlpack/core/tree/space_split/mean_space_split_impl.hpp
index 99cd91bd88..f48286872c 100644
--- a/src/mlpack/core/tree/space_split/mean_space_split_impl.hpp
+++ b/src/mlpack/core/tree/space_split/mean_space_split_impl.hpp
@@ -27,13 +27,13 @@ bool MeanSpaceSplit::SplitSpace(
HyperplaneType& hyp)
{
typename HyperplaneType::ProjVectorType projVector;
- double midValue;
+ typename MatType::elem_type midValue;
if (!SpaceSplit::GetProjVector(bound, data, points,
projVector, midValue))
return false;
- double splitVal = 0.0;
+ typename MatType::elem_type splitVal = 0.0;
for (size_t i = 0; i < points.n_elem; ++i)
splitVal += projVector.Project(data.col(points[i]));
splitVal /= points.n_elem;
diff --git a/src/mlpack/core/tree/space_split/midpoint_space_split_impl.hpp b/src/mlpack/core/tree/space_split/midpoint_space_split_impl.hpp
index 9fe8aab3d9..74038c8217 100644
--- a/src/mlpack/core/tree/space_split/midpoint_space_split_impl.hpp
+++ b/src/mlpack/core/tree/space_split/midpoint_space_split_impl.hpp
@@ -27,7 +27,7 @@ bool MidpointSpaceSplit::SplitSpace(
HyperplaneType& hyp)
{
typename HyperplaneType::ProjVectorType projVector;
- double midValue;
+ typename MatType::elem_type midValue;
if (!SpaceSplit::GetProjVector(bound, data, points,
projVector, midValue))
diff --git a/src/mlpack/core/tree/space_split/projection_vector.hpp b/src/mlpack/core/tree/space_split/projection_vector.hpp
index 8dfc0de532..26fccc4944 100644
--- a/src/mlpack/core/tree/space_split/projection_vector.hpp
+++ b/src/mlpack/core/tree/space_split/projection_vector.hpp
@@ -88,17 +88,18 @@ class AxisParallelProjVector
* ProjVector defines a general projection vector (not necessarily
* axis-parallel).
*/
+template
class ProjVector
{
- //! Projection vector.
- arma::vec projVect;
+ using ProjVecType = typename GetColType::type;
+
+ ProjVecType projVect;
public:
/**
* Empty Constructor.
*/
- ProjVector() :
- projVect()
+ ProjVector() : projVect()
{};
/**
@@ -106,7 +107,7 @@ class ProjVector
*
* @param vect Vector to be considered.
*/
- ProjVector(const arma::vec& vect) :
+ ProjVector(const ProjVecType& vect) :
projVect(normalise(vect))
{};
diff --git a/src/mlpack/core/tree/space_split/space_split.hpp b/src/mlpack/core/tree/space_split/space_split.hpp
index c18c482007..99a75278a7 100644
--- a/src/mlpack/core/tree/space_split/space_split.hpp
+++ b/src/mlpack/core/tree/space_split/space_split.hpp
@@ -35,11 +35,11 @@ class SpaceSplit
* @return Flag to determine if it is possible.
*/
static bool GetProjVector(
- const HRectBound& bound,
+ const HRectBound& bound,
const MatType& data,
const arma::Col& points,
AxisParallelProjVector& projVector,
- double& midValue);
+ typename MatType::elem_type& midValue);
/**
* Create a projection vector based on the given set of point. We efficiently
@@ -58,8 +58,8 @@ class SpaceSplit
const BoundType& bound,
const MatType& data,
const arma::Col& points,
- ProjVector& projVector,
- double& midValue);
+ ProjVector& projVector,
+ typename MatType::elem_type& midValue);
};
} // namespace mlpack
diff --git a/src/mlpack/core/tree/space_split/space_split_impl.hpp b/src/mlpack/core/tree/space_split/space_split_impl.hpp
index 80793f6498..0978c506bb 100644
--- a/src/mlpack/core/tree/space_split/space_split_impl.hpp
+++ b/src/mlpack/core/tree/space_split/space_split_impl.hpp
@@ -14,24 +14,27 @@
#define MLPACK_CORE_TREE_SPILL_TREE_SPACE_SPLIT_IMPL_HPP
#include "space_split.hpp"
+#include
namespace mlpack {
template
bool SpaceSplit::GetProjVector(
- const HRectBound& bound,
+ const HRectBound& bound,
const MatType& data,
const arma::Col& /* points */,
AxisParallelProjVector& projVector,
- double& midValue)
+ typename MatType::elem_type& midValue)
{
+ using ElemType = typename MatType::elem_type;
+
// Get the dimension that has the maximum width.
size_t splitDim = data.n_rows; // Indicate invalid.
- double maxWidth = -1;
+ ElemType maxWidth = -1;
for (size_t d = 0; d < data.n_rows; d++)
{
- const double width = bound[d].Width();
+ const ElemType width = bound[d].Width();
if (width > maxWidth)
{
@@ -56,19 +59,22 @@ bool SpaceSplit::GetProjVector(
const BoundType& /* bound */,
const MatType& data,
const arma::Col& points,
- ProjVector& projVector,
- double& midValue)
+ ProjVector& projVector,
+ typename MatType::elem_type& midValue)
{
+ using ElemType = typename MatType::elem_type;
+ using VecType = typename GetColType::type;
+
DistanceType distance;
// Efficiently estimate the farthest pair of points in the given set.
- size_t fst = points[rand() % points.n_elem];
+ size_t fst = points[RandInt(points.n_elem)];
size_t snd = points[0];
- double max = distance.Evaluate(data.col(fst), data.col(snd));
+ ElemType max = distance.Evaluate(data.col(fst), data.col(snd));
for (size_t i = 1; i < points.n_elem; ++i)
{
- double dist = distance.Evaluate(data.col(fst), data.col(points[i]));
+ ElemType dist = distance.Evaluate(data.col(fst), data.col(points[i]));
if (dist > max)
{
max = dist;
@@ -80,7 +86,7 @@ bool SpaceSplit::GetProjVector(
for (size_t i = 0; i < points.n_elem; ++i)
{
- double dist = distance.Evaluate(data.col(fst), data.col(points[i]));
+ ElemType dist = distance.Evaluate(data.col(fst), data.col(points[i]));
if (dist > max)
{
max = dist;
@@ -92,9 +98,9 @@ bool SpaceSplit::GetProjVector(
return false;
// Calculate the normalized projection vector.
- projVector = ProjVector(data.col(snd) - data.col(fst));
+ projVector = ProjVector(data.col(snd) - data.col(fst));
- arma::vec midPoint = (data.col(snd) + data.col(fst)) / 2;
+ VecType midPoint = (data.col(snd) + data.col(fst)) / 2;
midValue = projVector.Project(midPoint);
diff --git a/src/mlpack/core/tree/spill_tree/is_spill_tree.hpp b/src/mlpack/core/tree/spill_tree/is_spill_tree.hpp
index 202769968e..76752fef86 100644
--- a/src/mlpack/core/tree/spill_tree/is_spill_tree.hpp
+++ b/src/mlpack/core/tree/spill_tree/is_spill_tree.hpp
@@ -26,7 +26,7 @@ struct IsSpillTree
template
+ template
class HyperplaneType,
template
class SplitType>
diff --git a/src/mlpack/core/tree/spill_tree/spill_dual_tree_traverser.hpp b/src/mlpack/core/tree/spill_tree/spill_dual_tree_traverser.hpp
index 3a4257b8dd..381a1cf89d 100644
--- a/src/mlpack/core/tree/spill_tree/spill_dual_tree_traverser.hpp
+++ b/src/mlpack/core/tree/spill_tree/spill_dual_tree_traverser.hpp
@@ -27,7 +27,8 @@ namespace mlpack {
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
template
diff --git a/src/mlpack/core/tree/spill_tree/spill_dual_tree_traverser_impl.hpp b/src/mlpack/core/tree/spill_tree/spill_dual_tree_traverser_impl.hpp
index 03ae5ea267..2c951e50af 100644
--- a/src/mlpack/core/tree/spill_tree/spill_dual_tree_traverser_impl.hpp
+++ b/src/mlpack/core/tree/spill_tree/spill_dual_tree_traverser_impl.hpp
@@ -23,7 +23,8 @@ namespace mlpack {
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
template
@@ -40,7 +41,8 @@ SpillDualTreeTraverser::SpillDualTreeTraverser(
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
template
diff --git a/src/mlpack/core/tree/spill_tree/spill_single_tree_traverser.hpp b/src/mlpack/core/tree/spill_tree/spill_single_tree_traverser.hpp
index 78969f3d4c..f349dc0a45 100644
--- a/src/mlpack/core/tree/spill_tree/spill_single_tree_traverser.hpp
+++ b/src/mlpack/core/tree/spill_tree/spill_single_tree_traverser.hpp
@@ -26,7 +26,8 @@ namespace mlpack {
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
template
diff --git a/src/mlpack/core/tree/spill_tree/spill_single_tree_traverser_impl.hpp b/src/mlpack/core/tree/spill_tree/spill_single_tree_traverser_impl.hpp
index f14b52ce3d..eac0f63dbe 100644
--- a/src/mlpack/core/tree/spill_tree/spill_single_tree_traverser_impl.hpp
+++ b/src/mlpack/core/tree/spill_tree/spill_single_tree_traverser_impl.hpp
@@ -23,7 +23,8 @@ namespace mlpack {
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
template
@@ -37,7 +38,8 @@ SpillSingleTreeTraverser::SpillSingleTreeTraverser(
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
template
diff --git a/src/mlpack/core/tree/spill_tree/spill_tree.hpp b/src/mlpack/core/tree/spill_tree/spill_tree.hpp
index 052a75c8f3..da71bc5a53 100644
--- a/src/mlpack/core/tree/spill_tree/spill_tree.hpp
+++ b/src/mlpack/core/tree/spill_tree/spill_tree.hpp
@@ -65,7 +65,7 @@ namespace mlpack {
template
+ template
class HyperplaneType = AxisOrthogonalHyperplane,
template
class SplitType = MidpointSpaceSplit>
@@ -77,7 +77,7 @@ class SpillTree
//! The type of element held in MatType.
using ElemType = typename MatType::elem_type;
//! The bound type.
- using BoundType = typename HyperplaneType::BoundType;
+ using BoundType = typename HyperplaneType::BoundType;
private:
//! The left child node.
@@ -95,7 +95,7 @@ class SpillTree
//! Flag to distinguish overlapping nodes from non-overlapping nodes.
bool overlappingNode;
//! Splitting hyperplane represented by this node.
- HyperplaneType hyperplane;
+ HyperplaneType hyperplane;
//! The bound object for this node.
BoundType bound;
//! Any extra data contained in the node.
@@ -144,6 +144,13 @@ class SpillTree
template
using DefeatistDualTreeTraverser = SpillDualTreeTraverser;
+ /**
+ * A default constructor. This returns an empty tree, which is not useful.
+ * In general this is only used for serialization or right before copying from
+ * a different object.
+ */
+ SpillTree();
+
/**
* Construct this as the root node of a hybrid spill tree using the given
* dataset. The dataset will not be modified during the building procedure
@@ -274,7 +281,8 @@ class SpillTree
bool Overlap() const { return overlappingNode; }
//! Get the Hyperplane instance.
- const HyperplaneType& Hyperplane() const { return hyperplane; }
+ const HyperplaneType& Hyperplane() const
+ { return hyperplane; }
//! Get the distance metric that the tree uses.
[[deprecated("Will be removed in mlpack 5.0.0; use Distance()")]]
@@ -438,7 +446,7 @@ class SpillTree
static bool HasSelfChildren() { return false; }
//! Store the center of the bounding region in the given vector.
- void Center(arma::vec& center) { bound.Center(center); }
+ void Center(arma::Col& center) { bound.Center(center); }
private:
/**
@@ -469,17 +477,6 @@ class SpillTree
const arma::Col& points,
arma::Col& leftPoints,
arma::Col& rightPoints);
- protected:
- /**
- * A default constructor. This is meant to only be used with
- * cereal, which is allowed with the friend declaration below.
- * This does not return a valid tree! The method must be protected, so that
- * the serialization shim can work with the default constructor.
- */
- SpillTree();
-
- //! Friend access is given for the default constructor.
- friend class cereal::access;
public:
/**
diff --git a/src/mlpack/core/tree/spill_tree/spill_tree_impl.hpp b/src/mlpack/core/tree/spill_tree/spill_tree_impl.hpp
index 44d2c9b357..ed45d45ae8 100644
--- a/src/mlpack/core/tree/spill_tree/spill_tree_impl.hpp
+++ b/src/mlpack/core/tree/spill_tree/spill_tree_impl.hpp
@@ -18,10 +18,36 @@
namespace mlpack {
+// Default constructor (private), for cereal.
template class HyperplaneType,
+ template
+ class HyperplaneType,
+ template
+ class SplitType>
+SpillTree::
+ SpillTree() :
+ left(NULL),
+ right(NULL),
+ parent(NULL),
+ count(0),
+ pointsIndex(NULL),
+ overlappingNode(false),
+ stat(*this),
+ parentDistance(0),
+ furthestDescendantDistance(0),
+ dataset(NULL),
+ localDataset(false)
+{
+ // Nothing to do.
+}
+
+template
+ class HyperplaneType,
template
class SplitType>
SpillTree::
@@ -58,7 +84,8 @@ SpillTree(
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
SpillTree::
@@ -95,7 +122,8 @@ SpillTree(
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
SpillTree::
@@ -130,7 +158,8 @@ SpillTree(
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
SpillTree::
@@ -197,7 +226,8 @@ SpillTree(const SpillTree& other) :
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
SpillTree&
@@ -279,7 +309,8 @@ operator=(const SpillTree& other)
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
SpillTree::
@@ -324,7 +355,8 @@ SpillTree(SpillTree&& other) :
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
SpillTree&
@@ -384,7 +416,8 @@ operator=(SpillTree&& other)
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
template
@@ -407,7 +440,8 @@ SpillTree(
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
SpillTree::
@@ -425,7 +459,8 @@ SpillTree::
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
inline bool SpillTree class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
inline size_t SpillTree class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
template
@@ -489,7 +526,8 @@ size_t SpillTree class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
template
@@ -515,7 +553,8 @@ size_t SpillTree class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
size_t SpillTree class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
size_t SpillTree class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
inline typename SpillTree::
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
inline typename SpillTree::
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
inline typename SpillTree::
template class HyperplaneType,
+ template
+ class HyperplaneType,
template
class SplitType>
inline
@@ -642,7 +686,8 @@ SpillTree::
template class HyperplaneType,
+ template