diff --git a/doc/sidebar.html b/doc/sidebar.html
index e49272ed4f..d02d5a3047 100644
--- a/doc/sidebar.html
+++ b/doc/sidebar.html
@@ -86,6 +86,11 @@ when the sidebar is built for each page.
MeanSplitKDTree
+
+
+ VPTree
+
+
BinarySpaceTree
diff --git a/doc/user/core/trees/binary_space_tree.md b/doc/user/core/trees/binary_space_tree.md
index de906335aa..1dcb401d2d 100644
--- a/doc/user/core/trees/binary_space_tree.md
+++ b/doc/user/core/trees/binary_space_tree.md
@@ -421,6 +421,8 @@ write a custom `BoundType` for use with `BinarySpaceTree`:
* [`HRectBound`](#hrectbound): hyperrectangle bound, encloses the descendant
points in the smallest possible hyperrectangle
+ * [`HollowBallBound`](#hollowballbound): hollow ball bound, equivalent to a
+ ball bound with a ball subtracted from it.
* [Custom `BoundType`s](#custom-boundtypes): implement a fully custom
`BoundType`
@@ -567,7 +569,7 @@ operations with data points or other bounds.
Once an `HRectBound` has been successfully created and set to the desired
bounding hyperrectangle, there are a number of functions that can bound the
-distance between a `HRectBound` and other objects.
+distance between an `HRectBound` and other objects.
* `b.Contains(point)`
* `b.Contains(bound)`
@@ -764,13 +766,352 @@ std::cout << "Distance between Manhattan distance HRectBound and "
// point.
arma::fmat floatData(3, 25, arma::fill::randu);
mlpack::HRectBound cb;
-cb |= floatData; // This will set the bound to [2.0, 3.0] in every dimension.
+cb |= floatData;
// Note the use of arma::fvec to represent a point, since ElemType is float.
const mlpack::RangeType r3 = cb.RangeDistance(arma::fvec("1.5 1.5 4.0"));
std::cout << "Distance between Chebyshev distance HRectBound and "
<< "[1.5, 1.5, 4.0]: [" << r3.Lo() << ", " << r3.Hi() << "]." << std::endl;
```
+### `HollowBallBound`
+
+The `HollowBallBound` class represents a bounding shape that is an
+arbitrary-dimensional ball bound with another smaller ball subtracted from its
+inside. A `HollowBallBound` consists of a center point, an outer radius, and a
+secondary center point and inner radius. An example `HollowBallBound` is shown
+below in two dimensions; shaded area represents area held within the bound.
+
+
+
+
+
+`HollowBallBound` is used directly by the [`VPTree`](vptree.md) class.
+
+#### Constructors
+
+`HollowBallBound` allows configurable behavior via its two template parameters:
+
+```
+HollowBallBound
+```
+
+Different constructor forms can be used to specify different template parameters
+(and thus different bound behavior).
+
+ * `b = HollowBallBound(dimensionality)`
+ - Construct a `HollowBallBound` with the given `dimensionality`.
+ - The bound will be empty with invalid centers and radii (e.g., `b` will not
+ contain any points at all).
+ - The bound will use the [Euclidean distance](../distances.md#lmetric) for
+ distance computation, and will expect data to have elements with type
+ `double`.
+
+ * `b = HollowBallBound(dimensionality)`
+ - Construct a `HollowBallBound` with the given `dimensionality` that will use
+ the given `DistanceType` class to compute distances, and expect data to
+ have elements with type `ElemType`.
+ - `ElemType` should generally be `double` or `float`.
+
+
+
+***Note***: these constructors provide an empty bound; be sure to
+[grow](#growing-and-shrinking-the-bound-1) the bound or
+[directly modify the bound](#accessing-and-modifying-properties-of-the-bound-1)
+before using it!
+
+---
+
+ * `b = HollowBallBound(innerRadius, outerRadius, center)`
+ - Construct a `HollowBallBound` with the given `innerRadius` for the inner
+ ball, `outerRadius` for the outer ball, and `center`.
+ - Both the inner and outer ball are centered at `center`.
+ - `innerRadius` and `outerRadius` should have type `double`.
+ - `center` should have type `arma::vec`.
+ - The bound will use the [Euclidean distance](../distances.md#lmetric) for
+ distance computation, and will expect data to have elements with type
+ `double`.
+
+ * `b = HollowBallBound(innerRadius, outerRadius, center)`
+ - Construct a `HollowBallBound` with the given `innerRadius` for the inner
+ ball, `outerRadius` for the outer ball, and `center`.
+ - Both the inner and outer ball are centered at `center`.
+ - `innerRadius` and `outerRadius` should have type `ElemType`.
+ - `center` should be a vector with element type `ElemType` (e.g.
+ `arma::Col`).
+ - The bound will use the given `DistanceType` class to compute distances, and
+ expect data to have elements with type `ElemType`.
+
+#### Accessing and modifying properties of the bound
+
+The individual bounds associated with each dimension of a `HollowBallBound` can
+be accessed and modified.
+
+ * `b.Dim()` will return a `size_t` indicating the dimensionality of the bound.
+
+ * `b.Center()` returns an `arma::vec&` containing the center of the outer ball.
+ Its elements can be directly modified.
+
+ * `b.HollowCenter()` returns an `arma::vec&` containing the center of the inner
+ ball. Its elements can be directly modified.
+
+ * `b.OuterRadius()` will return a `double` that is the radius of the outer
+ ball.
+ - `b.OuterRadius() = r` will set the radius of the outer ball to `r`.
+
+ * `b.InnerRadius()` will return a `double` that is the radius of the inner
+ ball.
+ - `b.InnerRadius() = r` will set the radius of the inner ball to `r`.
+
+ * `b[dim]` will return a [`Range`](../math.md#range) object representing the
+ extents of the bound in dimension `dim`.
+ - The range is defined as
+ `[b.Center()[dim] - b.OuterRadius(), b.Center()[dim] + b.OuterRadius()]`.
+ - ***Note:*** this returns the maximum extents of the bound and does not
+ consider the inner (hollow) ball.
+
+ * `b.Diameter()` returns the diameter of the ball. This is always equal to
+ `2 * b.OuterRadius()`.
+
+ * `b.MinWidth()` returns the minimum width of the bound in any dimension as a
+ `double`. This is always equal to `b.Diameter()`.
+
+ * `b.Distance()` returns either a
+ [`EuclideanDistance`](../distances.md#lmetric) distance metric object, or a
+ `DistanceType` if a custom `DistanceType` has been specified in the
+ constructor.
+
+ * `b.Center(center)` will store the center of the `HollowBallBound` in the
+ vector `center`. `center` should be of type `arma::vec`.
+
+ * `b.MinWidth()` returns the minimum width of the bound in any dimension as a
+ `double`. This value is cached and no computation is performed when calling
+ `b.MinWidth()`. If the bound is empty, `0` is returned.
+
+ * `b.Distance()` returns either a
+ [`EuclideanDistance`](../distances.md#lmetric) distance metric object, or a
+ `DistanceType` if a custom `DistanceType` has been specified in the
+ constructor.
+
+ * `b.Center(center)` will compute the center of the `HRectBound` (e.g. the
+ vector with elements equal to the midpoint of `b` in each dimension) and
+ store it in the vector `center`. `center` should be of type `arma::vec`.
+
+ * `b.Volume()` computes the volume of the hyperrectangle specified by `b`. The
+ volume is returned as a `double`.
+
+ * `b.Diameter()` computes the longest diagonal of the hyperrectangle specified
+ by `b`.
+
+ * A `HollowBallBound` can be serialized with
+ [`data::Save()` and `data::Load()`](../../load_save.md#mlpack-objects).
+
+***Note:*** if a custom `ElemType` was specified in the constructor, then:
+
+ * `b[dim]` will return a `RangeType`;
+ * `b.OuterRadius()`, `b.InnerRadius()`, `b.MinWidth()`, and `b.Diameter()` will
+ return `ElemType`;
+ * `b.Center()` and `b.HollowCenter()` will return `arma::Col&`; and
+ * `b.Center(center)` expects `center` to be of type `arma::Col`.
+
+#### Growing the bound
+
+The `HollowBallBound` uses the logical `|=` to grow the bound to include points.
+
+ * `b |= data` expands `b` so the outer ball includes all of the data points in
+ `data`, shrinking the inner ball as necessary. `data` should be a
+ [column-major `arma::mat`](../../matrices.md#representing-data-in-mlpack).
+ The expansion operation is minimal, so `b` is not expanded any more than
+ necessary.
+ - The bound is grown using [Jack Ritter's bounding sphere
+ algorithm](https://en.wikipedia.org/wiki/Bounding_sphere#Ritter's_bounding_sphere),
+ which may move the center of the bound as it iteratively adds points to the
+ bound. (The hollow center is not moved.)
+ - If the bound is empty, the centers are initialized to the first point of
+ `data`.
+ - If the bound is not empty, then `data` is expected to have dimensionality
+ that matches `b.Dim()`.
+
+***Notes:***
+
+ - The growth operation does not grow the inner (hollow) ball. Properties
+ related to the inner ball should be set manually with `b.HollowCenter()` and
+ `b.InnerRadius()`.
+
+ - If a custom `ElemType` was specified, then any `data` argument should be a
+ matrix with that `ElemType` (e.g. `arma::Mat`).
+
+#### Bounding distances to other objects
+
+Once a `HollowBallBound` has been successfully created and set to the desired
+bounding balls, there are a number of functions that can bound the
+distance between a `HollowBallBound` and other objects.
+
+ * `b.Contains(point)`
+ * `b.Contains(bound)`
+ - Return a `bool` indicating whether or not `b` contains the given `point`
+ (an `arma::vec`) or another `bound` (an `HRectBound`).
+ - When passing another `bound`, `true` will be returned if `bound` even
+ partially overlaps with `b`.
+
+ * `b.MinDistance(point)`
+ * `b.MinDistance(bound)`
+ - Return a `double` whose value is the minimum possible distance between `b`
+ and either a `point` (an `arma::vec`) or another `bound` (a
+ `HollowBallBound`).
+ - The minimum distance between `b` and another point or bound is the length
+ of the shortest possible line that can connect the other point or bound to
+ `b`.
+ - If `point` or `bound` are contained in `b`, then the returned distance is
+ 0.
+
+ * `b.MaxDistance(point)`
+ * `b.MaxDistance(bound)`
+ - Return a `double` whose value is the maximum possible distance between `b`
+ and either a `point` (an `arma::vec`) or another `bound` (a
+ `HollowBallBound`).
+ - The maximum distance between `b` and a given `point` is the furthest
+ possible distance between `point` and any possible point falling within the
+ bounding hyperrectangle of `b`.
+ - The maximum distance between `b` and another `bound` is the furthest
+ possible distance between any possible point falling within the bounding
+ hyperrectangle of `b`, and any possible point falling within the bounding
+ hyperrectangle of `bound`.
+ - Note that this definition means that even if `b.Contains(point)` or
+ `b.Contains(bound)` is `true`, the maximum distance may be greater than
+ `0`.
+
+ * `b.RangeDistance(point)`
+ * `b.RangeDistance(bound)`
+ - Compute the minimum and maximum distance between `b` and `point` or
+ `bound`, returning the result as a [`Range`](../math.md#range) object.
+ - This is more efficient than calling `b.MinDistance()` and
+ `b.MaxDistance()`.
+
+***Note:*** if a custom `DistanceType` and `ElemType` were specified in the
+constructor, then all distances will be computed with respect to the specified
+`DistanceType` and all return values will either be `ElemType` or
+[`RangeType`](../math.md#range) (except for `Contains()`, which will
+still return a `bool`).
+
+#### Example usage
+
+```c++
+// Create a hollow ball bound in 3 dimensions whose outer ball is the unit ball
+// and whose inner ball is the ball with radius 0.5 centered at the origin.
+// The bounding range for all three dimensions is [0.0, 1.0].
+mlpack::HollowBallBound b(0.5, 1.0, arma::vec(3));
+
+std::cout << "Hollow unit ball bound created manually:" << std::endl;
+std::cout << " - Center: " << b.Center().t();
+std::cout << " - Outer radius: " << b.OuterRadius() << "." << std::endl;
+std::cout << " - Hollow center: " << b.HollowCenter.();
+std::cout << " - Inner radius: " << b.InnerRadius() << "." << std::endl;
+for (size_t i = 0; i < 3; ++i)
+{
+ std::cout << " - Dimension " << i << " extents: [" << b[i].Lo() << ", "
+ << b[i].Hi() << "]." << std::endl;
+}
+std::cout << std::endl;
+
+// Create a small dataset of 5 points.
+arma::mat dataset(3, 5);
+dataset.col(0) = arma::vec("2.0 2.0 2.0");
+dataset.col(1) = arma::vec("2.5 2.5 2.5");
+dataset.col(2) = arma::vec("3.0 2.0 3.0");
+dataset.col(3) = arma::vec("2.0 3.0 2.0");
+dataset.col(4) = arma::vec("3.0 3.0 3.0");
+
+// If we simply build a HollowBallBound to enclose those points, the hollow part
+// of the ball is unmodified and remains empty.
+mlpack::HollowBallBound b2(3);
+b2 |= dataset;
+std::cout << "Hollow ball bound on points with only `operator|=()`:"
+ << std::endl;
+std::cout << " - Center: " << b2.Center().t();
+std::cout << " - Outer radius: " << b2.OuterRadius() << "." << std::endl;
+std::cout << " - Hollow center: " << b2.HollowCenter().t();
+std::cout << " - Inner radius: " << b2.InnerRadius() << "." << std::endl;
+std::cout << std::endl;
+
+// On the other hand, if we initialize a HollowBallBound to a non-empty bound,
+// then `operator|=()` will shrink the hollow ball as necessary.
+//
+// We initialize this ball bound to a "slice" with radii [2.6, 2.7].
+mlpack::HollowBallBound b3(2.6, 2.7, arma::vec(3));
+b3 |= dataset;
+std::cout << "Hollow ball bound on points with pre-initialization and "
+ << "`operator|=()`:" << std::endl;
+std::cout << " - Center: " << b3.Center().t();
+std::cout << " - Outer radius: " << b3.OuterRadius() << "." << std::endl;
+std::cout << " - Hollow center: " << b3.HollowCenter().t();
+std::cout << " - Inner radius: " << b3.InnerRadius() << "." << std::endl;
+std::cout << std::endl;
+
+// Manually create a hollow ball bound whose hollow center is different than the
+// outer ball's center.
+mlpack::HollowBallBound b4(3);
+b4.OuterRadius() = 3.0;
+b4.InnerRadius() = 1.5;
+b4.Center() = arma::vec(3);
+b4.HollowCenter() = arma::vec("1.0 1.0 1.0");
+
+// Compute the minimum distance between a point inside the hollow unit ball's
+// outer ball.
+const double d1 = b.MinDistance(arma::vec("0.75 0.75 0.75"));
+std::cout << "Minimum distance between hollow unit ball bound and [0.75, 0.75, "
+ << "0.75]: " << d1 << "." << std::endl;
+
+// Compute the minimum distance between a point inside the hollow unit ball's
+// inner ball (so the point is not contained in the bound---it is within the
+// hollow section).
+const double d2 = b.MinDistance(arma::vec("0.25 0.25 0.25"));
+std::cout << "Minimum distance between hollow unit ball bound and [0.25, 0.25, "
+ << "0.25]: " << d2 << "." << std::endl;
+std::cout << std::endl;
+
+// Use Contains(). In this case, the 'else' will be taken.
+if (b.Contains(arma::vec("1.5 1.5 1.5")))
+{
+ std::cout << "Hollow unit ball bound contains [1.5, 1.5, 1.5]." << std::endl;
+}
+else
+{
+ std::cout << "Hollow unit ball bound does not contain [1.5, 1.5, 1.5]."
+ << std::endl;
+}
+std::cout << std::endl;
+
+// Compute the maximum distance between a point inside the unit ball and the
+// unit hollow ball bound.
+const double d2 = b4.MaxDistance(arma::vec("0.1 0.1 0.1"));
+std::cout << "Maximum distance between hollow unit ball bound and [0.1, 0.1, "
+ << "0.1]: " << d2 << "." << std::endl;
+
+// Compute the minimum and maximum distances between the hollow unit ball bound
+// and the bound built on data points.
+const mlpack::Range r = b.RangeDistance(b3);
+std::cout << "Distances between hollow unit ball bound and second hollow "
+ << "dataset bound: [" << r.Lo() << ", " << r.Hi() << "]." << std::endl;
+
+// Create a bound using the Manhattan (L1) distance and compute the minimum and
+// maximum distance to a point.
+mlpack::HollowBallBound mb(2.0, 5.0, arma::vec(3));
+const mlpack::Range r2 = mb.RangeDistance(arma::vec("1.5 1.5 4.0"));
+std::cout << "Distance between Manhattan distance HollowBallBound and "
+ << "[1.5, 1.5, 4.0]: [" << r2.Lo() << ", " << r2.Hi() << "]." << std::endl;
+
+// Create a bound using the Chebyshev (L-inf) distance, using random 32-bit
+// floating point elements, and compute the minimum and maximum distance to a
+// point.
+arma::fmat floatData(3, 25, arma::fill::randu);
+mlpack::HollowBallBound cb;
+cb |= floatData;
+// Note the use of arma::fvec to represent a point, since ElemType is float.
+const mlpack::RangeType r3 = cb.RangeDistance(arma::fvec("1.5 1.5 4.0"));
+std::cout << "Distance between Chebyshev distance HollowBallBound and "
+ << "[1.5, 1.5, 4.0]: [" << r3.Lo() << ", " << r3.Hi() << "]." << std::endl;
+```
+
### Custom `BoundType`s
The `BinarySpaceTree` class allows an arbitrary `BoundType` template parameter
diff --git a/src/mlpack/core/tree/hollow_ball_bound.hpp b/src/mlpack/core/tree/hollow_ball_bound.hpp
index 2cad206b82..a41c10798f 100644
--- a/src/mlpack/core/tree/hollow_ball_bound.hpp
+++ b/src/mlpack/core/tree/hollow_ball_bound.hpp
@@ -212,16 +212,6 @@ class HollowBallBound
template
const HollowBallBound& operator|=(const MatType& data);
- /**
- * Expand the bound to include the given bound. The centroid will not be
- * moved.
- *
- * @tparam MatType Type of matrix; could be arma::mat, arma::spmat, or a
- * vector.
- * @tparam data Data points to add.
- */
- const HollowBallBound& operator|=(const HollowBallBound& other);
-
/**
* Returns the diameter of the ballbound.
*/