First steps towards documenting VPTree.

This commit is contained in:
Ryan Curtin
2024-10-02 10:32:55 -04:00
parent 18c309b80c
commit 4ccc2cf6bf
3 changed files with 348 additions and 12 deletions
+5
View File
@@ -86,6 +86,11 @@ when the sidebar is built for each page.
<code>MeanSplitKDTree</code>
</a>
</li>
<li>
<a href="LINKROOTuser/core/trees/vptree.html">
<code>VPTree</code>
</a>
</li>
<li>
<a href="LINKROOTuser/core/trees/binary_space_tree.html">
<code>BinarySpaceTree</code>
+343 -2
View File
@@ -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<mlpack::ChebyshevDistance, float> 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<float> 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.
<center>
<img src="../../../img/hollowballbound.png" width="50%" alt="hollow ball bound">
</center>
`HollowBallBound` is used directly by the [`VPTree`](vptree.md) class.
#### Constructors
`HollowBallBound` allows configurable behavior via its two template parameters:
```
HollowBallBound<DistanceType, ElemType>
```
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<DistanceType, ElemType>(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`.
<!-- TODO: update links here after merge of BallBound PR -->
***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<DistanceType, ElemType>(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<ElemType>`).
- 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<ElemType>`;
* `b.OuterRadius()`, `b.InnerRadius()`, `b.MinWidth()`, and `b.Diameter()` will
return `ElemType`;
* `b.Center()` and `b.HollowCenter()` will return `arma::Col<ElemType>&`; and
* `b.Center(center)` expects `center` to be of type `arma::Col<ElemType>`.
#### 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<ElemType>`).
#### 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<ElemType>`](../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<mlpack::ManhattanDistance> 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<mlpack::ChebyshevDistance, float> cb;
cb |= floatData;
// Note the use of arma::fvec to represent a point, since ElemType is float.
const mlpack::RangeType<float> 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
@@ -212,16 +212,6 @@ class HollowBallBound
template<typename MatType>
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.
*/