From 4ccc2cf6bf87395ffd7f64b39ed3da8a71e89c98 Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Wed, 2 Oct 2024 10:32:55 -0400 Subject: [PATCH 01/14] First steps towards documenting VPTree. --- doc/sidebar.html | 5 + doc/user/core/trees/binary_space_tree.md | 345 ++++++++++++++++++++- src/mlpack/core/tree/hollow_ball_bound.hpp | 10 - 3 files changed, 348 insertions(+), 12 deletions(-) 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. + +
    +hollow ball 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. */ From d8b7c6654f22f775d52f882506508b5554faba14 Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Wed, 2 Oct 2024 12:49:16 -0400 Subject: [PATCH 02/14] Add documentation for VantagePointSplit. --- doc/user/core/trees/binary_space_tree.md | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/doc/user/core/trees/binary_space_tree.md b/doc/user/core/trees/binary_space_tree.md index 1dcb401d2d..561ba1df26 100644 --- a/doc/user/core/trees/binary_space_tree.md +++ b/doc/user/core/trees/binary_space_tree.md @@ -1295,6 +1295,8 @@ to write a fully custom split: with maximum width * [`MeanSplit`](#meansplit): splits on the mean value of the points in the dimension with maximum width + * [`VantagePointSplit`](#vantagepointsplit): split by selecting a 'vantage + point' and then split points into 'near' and 'far' sets * [Custom `SplitType`s](#custom-splittypes): implement a fully custom `SplitType` class @@ -1343,6 +1345,50 @@ task*. For implementation details, see [the source code](/src/mlpack/core/tree/binary_space_tree/mean_split_impl.hpp). +### `VantagePointSplit` + +The `VantagePointSplit` class is a splitting strategy that can be used by +[`BinarySpaceTree`](#binaryspacetree). It is the default strategy for splitting +[`VPTree`s](vptree.md), and is detailed in [the paper](...). +Due to the nature of the split, ***`VantagePointSplit` should always be used +with the [`HollowBallBound`](#hollowballbound)***. + +The splitting strategy for the `VantagePointSplit` class is, given a set of +points: + + * Select a vantage point from a sample of 100 random candidate points (or use + the full set if there are fewer than 100 points): + - Compute the distances between each candidate point and 100 additional + random samples (or the full set if there are fewer than 100 points). + - Select the vantage point as the candidate with maximum average distance to + the additional random samples. + * Compute a boundary distance `mu` that is the median distance between the + vantage point and its random samples. + * Points with distance less than `mu` from the vantage point will go to the + left child. + * Points with distance greater than `mu` from the vantage point will go to the + right child. + +The `VantagePointSplit` class has three template parameters: + +``` +VantagePointSplit +``` + +If a custom number of samples `S` is desired, the easiest way to specify is via +a template typedef: + +``` +template +using MyVantagePointSplit = VantagePointSplit; +``` + +Then, `MyVantagePointSplit` can be used directly with `BinarySpaceTree` as a +`SplitType`. + +For implementation details, see +[the source code](/src/mlpack/core/tree/binary_space_tree/vantage_point_split_impl.hpp). + ### Custom `SplitType`s Custom split strategies for a binary space tree can be implemented via the From 548950080613be526cefc7740ab6939afd347654 Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Wed, 2 Oct 2024 14:50:09 -0400 Subject: [PATCH 03/14] Remove useless assert (it is always true). --- .../core/tree/binary_space_tree/vantage_point_split_impl.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mlpack/core/tree/binary_space_tree/vantage_point_split_impl.hpp b/src/mlpack/core/tree/binary_space_tree/vantage_point_split_impl.hpp index d19dcb1f18..b815b849b4 100644 --- a/src/mlpack/core/tree/binary_space_tree/vantage_point_split_impl.hpp +++ b/src/mlpack/core/tree/binary_space_tree/vantage_point_split_impl.hpp @@ -89,7 +89,6 @@ SelectVantagePoint(const DistanceType& distance, const MatType& data, mu = arma::median(distances); } } - assert(bestSpread > 0); } } // namespace mlpack From b64d741147ee90883d09039c0591b1ffe0e41318 Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Wed, 2 Oct 2024 17:29:21 -0400 Subject: [PATCH 04/14] Add documentation for VPTree. --- doc/img/hollowballbound.png | Bin 0 -> 72856 bytes doc/user/core/trees/vptree.md | 634 ++++++++++++++++++++++++++++++++++ 2 files changed, 634 insertions(+) create mode 100644 doc/img/hollowballbound.png create mode 100644 doc/user/core/trees/vptree.md diff --git a/doc/img/hollowballbound.png b/doc/img/hollowballbound.png new file mode 100644 index 0000000000000000000000000000000000000000..3660328b3ab9fdb4ae8ccbd959b03d1c50f64ee1 GIT binary patch literal 72856 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
    vnkbJ%SoeG@)4FsI57a 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 + +The `VPTree` class represents a `k`-dimensional vantage point tree, +and is a well-known data structure for efficient distance operations (such as +nearest neighbor search) in low dimensions---typically less than 100. The +vantage point tree is also known as the 'metric tree'. + +A vantage point tree is a binary tree where each node selects a 'vantage +point', and child nodes partition points into those that are nearer to the +vantage point and those that are further from it. `VPTree` supports arbitrary +[distance metrics](../distances.md) that are not +[`LMetric`](../distances.md#lmetric), making it more flexible than `KDTree`. + +mlpack's `VPTree` implementation 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 vantage point trees. + + * [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 + + + + * [Vantage point tree on Wikipedia](https://en.wikipedia.org/wiki/Vantage-point_tree) + * [`BinarySpaceTree`](binary_space_tree.md) + * [Binary space partitioning on Wikipedia](https://dl.acm.org/doi/pdf/10.1145/361002.361007) + * [Data structures and algorithms for nearest neighbor search in general metric spaces (pdf)](https://scholar.archive.org/work/og2s6vjmcngfbkmesu3dbsazpq/access/wayback/https://dl.acm.org/doi/pdf/10.5555/313559.313789) + * [Satisfying General Proximity/Similarity Queries with Metric Trees (pdf)](https://www.mlpack.org/papers/uhlmann91.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 `VPTree` class takes three template parameters: + +``` +VPTree +``` + + * `DistanceType`: the [distance metric](../distances.md) to use for distance + computations. By default, this is + [`EuclideanDistance`](../distances.md#lmetric). + * [`StatisticType`](binary_space_tree.md#statistictype): this holds auxiliary + information in each tree node. By default, + [`EmptyStatistic`](binary_space_tree.md#emptystatistic) is used, which holds + no information. + * `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 `VPTree` class itself is a convenience typedef of the generic +[`BinarySpaceTree`](binary_space_tree.md) class, using the +[`HollowBallBound`](binary_space_tree.md#hollowballbound) class as the bounding +structure, and using the +[`VantagePointSplit`](binary_space_tree.md#vantagepointsplit) splitting strategy +for construction, which splits points into those that are nearer and further +from a 'vantage point'. + +## Constructors + +`VPTree`s are efficiently constructed by permuting points in a dataset in a +quicksort-like algorithm. However, this means that the ordering of points in +the tree's dataset (accessed with `node.Dataset()`) after construction may be +different. + +--- + + * `node = VPTree(data, maxLeafSize=20)` + * `node = VPTree(data, oldFromNew, maxLeafSize=20)` + * `node = VPTree(data, oldFromNew, newFromOld, maxLeafSize=20)` + - Construct a `VPTree` on the given `data`, using `maxLeafSize` as the + maximum number of points held in a leaf. + - By default, `data` is copied. Avoid a copy by using `std::move()` (e.g. + `std::move(data)`); when doing this, `data` will be set to an empty matrix. + - Optionally, construct mappings from old points to new points. `oldFromNew` + and `newFromOld` will have length `data.n_cols`, and: + * `oldFromNew[i]` indicates that point `i` in the tree's dataset was + originally point `oldFromNew[i]` in `data`; that is, + `node.Dataset().col(i)` is the point `data.col(oldFromNew[i])`. + * `newFromOld[i]` indicates that point `i` in `data` is now point + `newFromOld[i]` in the tree's dataset; that is, + `node.Dataset().col(newFromOld[i])` is the point `data.col(i)`. + +--- + + * `node = VPTree(data, maxLeafSize=20)` + * `node = VPTree(data, oldFromNew, maxLeafSize=20)` + * `node = VPTree(data, oldFromNew, newFromOld, maxLeafSize=20)` + - Construct a `VPTree` on the given `data`, using custom template parameters + to control the behavior of the tree, using `maxLeafSize` as the maximum + number of points held in a leaf. + - By default, `data` is copied. Avoid a copy by using `std::move()` (e.g. + `std::move(data)`); when doing this, `data` will be set to an empty matrix. + - Optionally, construct mappings from old points to new points. `oldFromNew` + and `newFromOld` will have length `data.n_cols`, and: + * `oldFromNew[i]` indicates that point `i` in the tree's dataset was + originally point `oldFromNew[i]` in `data`; that is, + `node.Dataset().col(i)` is the point `data.col(oldFromNew[i])`. + * `newFromOld[i]` indicates that point `i` in `data` is now point + `newFromOld[i]` in the tree's dataset; that is, + `node.Dataset().col(newFromOld[i])` is the point `data.col(i)`. + +--- + + * `node = VPTree()` + - Construct an empty vantage point tree with no children and no points. + +--- + +***Notes:*** + + - The name `node` is used here for `VPTree` objects instead of `tree`, because + each `VPTree` 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 `VPTree` is + not supported, because this generally results in a vantage point tree with + 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` class and all its variants (e.g. `RTree`, + `RStarTree`, etc.). + + - See also the + [developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors). + + + +--- + +### Constructor parameters: + +| **name** | **type** | **description** | **default** | +|----------|----------|-----------------|-------------| +| `data` | [`arma::mat`](../../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)_ | +| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` | +| `oldFromNew` | `std::vector` | Mappings from points in `node.Dataset()` to points in `data`. | _(N/A)_ | +| `newFromOld` | `std::vector` | Mappings from points in `data` to points in `node.Dataset()`. | _(N/A)_ | + +## Basic tree properties + +Once a `VPTree` 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 `VPTree&` 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 `VPTree&` + that can itself be used just like the root node of the tree! + - `node.Left()` and `node.Right()` are convenience functions specific to + `VPTree` that will return `VPTree*` (pointers) to the left and right + children, respectively, or `NULL` if `node` has no children. + + * `node.Parent()` will return a `VPTree*` that points to the parent of `node`, + or `NULL` if `node` is the root of the `VPTree`. + +--- + +### Accessing members of a tree + + * `node.Bound()` will return an + [`HollowBallBound&`](binary_space_tree.md#hollowballbound) object that + represents the hollow bounding ball of `node`. This structure encloses all + the descendant points of `node`. + + * `node.Stat()` will return an `EmptyStatistic&` (or a `StatisticType&` if a + [custom `StatisticType`](#template-parameters) was specified as a template + parameter) holding the statistics of the node that were computed during tree + construction. + + * `node.Distance()` will return a + [`EuclideanDistance&`](../distances.md#lmetric) (or a `DistanceType&` if a + [custom `DistanceType`](#template-parameters) was specified as a template + parameter). + +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 arma::mat&` that is the dataset the + tree was built on. Note that this is a permuted version of the `data` matrix + passed to the constructor. + - If a [custom `MatType`](#template-parameters) is being used, the return + type will be `const MatType&` instead of `const arma::mat&`. + + * `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 `VPTree` 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))`. + - In a `VPTree`, because of the permutation of points done [during + construction](#constructors), point indices are contiguous: + `node.Point(i + j)` is the same as `node.Point(i) + j` for valid `i` and + `j`. + - 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))`. + - In a `VPTree`, because of the permutation of points done [during + construction](#constructors), point indices are contiguous: + `node.Descendant(i + j)` is the same as `node.Descendant(i) + j` for valid + `i` and `j`. + - Accessing the actual `i`'th descendant itself can be done with, e.g., + `node.Dataset().col(node.Descendant(i))`. + + * `node.Begin()` returns a `size_t` indicating the index of the first + descendant point of `node`. + - This is equivalent to `node.Descendant(0)`. + + * `node.Count()` returns a `size_t` indicating the number of descendant points of `node`. + - This is equivalent to `node.NumDescendants()`. + +--- + +### Accessing computed bound quantities of a tree + +The following quantities are cached for each node in a `VPTree`, and so +accessing them does not require any computation. + + * `node.FurthestPointDistance()` returns a `double` representing the distance + between the center of the hollow bounding ball 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 a `double` representing the + distance between the center of the hollow bounding ball of `node` and the + furthest descendant point held by `node`. + + * `node.MinimumBoundDistance()` returns a `double` representing minimum + possible distance from the center of the node to any edge of the + hollow ball bound. + - This quantity is equivalent to `node.Bound().OuterRadius()`. + + * `node.ParentDistance()` returns a `double` representing the distance between + the center of the hollow bounding ball of `node` and the center of the + hollow bounding ball of its parent. + - If `node` is the root of the tree, `0` is returned. + +***Notes:*** + + - If a [custom `MatType`](#template-parameters) was specified when constructing + the `VPTree`, then the return type of each method is the element type of the + given `MatType` instead of `double`. (e.g., if `MatType` is `arma::fmat`, + then the return type is `float`.) + + - 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 hollow bounding ball of + `node` and stores it in `center`. + - `center` should be of type `arma::vec&`. (If a [custom + `MatType`](#template-parameters) was specified when constructing the + `VPTree`, the type is instead the column vector type for the given + `MatType`; e.g., `arma::fvec&` when `MatType` is `arma::fmat`.) + - `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 `VPTree` 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 of type `arma::vec`. (If a [custom + `MatType`](#template-parameters) was specified when constructing the + `VPTree`, the type is instead the column vector type for the given + `MatType`; e.g., `arma::fvec` when `MatType` is `arma::fmat`.) + + * `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 `VPTree` node `other`, + with respect to the `MinDistance()` (or `MaxDistance()`) function. + - If there is a tie, `2` (an invalid index) is returned. ***Note that this + behavior differs from the version above that takes a point.*** + - 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 `VPTree` node `other`. + - This is equivalent to the minimum possible distance between any point + contained in the hollow bounding ball of `node` and `point`, or between + any point contained in the hollow bounding ball of `node` and any point + contained in the hollow bounding ball of `other`. + - `point` should be of type `arma::vec`. (If a [custom + `MatType`](#template-parameters) was specified when constructing the + `VPTree`, the type is instead the column vector type for the given + `MatType`, and the return type is the element type of `MatType`; e.g., + `point` should be `arma::fvec` when `MatType` is `arma::fmat`, and the + returned distance is `float`). + + * `node.MaxDistance(point)` + * `node.MaxDistance(other)` + - Return a `double` indicating the maximum possible distance between `node` + and `point`, or the `VPTree` node `other`. + - This is equivalent to the maximum possible distance between any point + contained in the hollow bounding ball of `node` and `point`, or between + any point contained in the hollow bounding ball of `node` and any point + contained in the hollow bounding ball of `other`. + - `point` should be of type `arma::vec`. (If a [custom + `MatType`](#template-parameters) was specified when constructing the + `VPTree`, the type is instead the column vector type for the given + `MatType`, and the return type is the element type of `MatType`; e.g., + `point` should be `arma::fvec` when `MatType` is `arma::fmat`, and the + returned distance is `float`). + + * `node.RangeDistance(point)` + * `node.RangeDistance(other)` + - Return a [`Range`](../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)`. + - `point` should be of type `arma::vec`. (If a + [custom `MatType`](#template-parameters) was specified when constructing + the `VPTree`, the type is instead the column vector type for the given + `MatType`, and the return type is a `RangeType` with element type the same + as `MatType`; e.g., `point` should be `arma::fvec` when `MatType` is + `arma::fmat`, and the returned type is + [`RangeType`](../math.md#range)). + +### Tree traversals + +Like every mlpack tree, the `VPTree` 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. + + * `VPTree::SingleTreeTraverser` + - Implements a depth-first single-tree traverser. + + * `VPTree::DualTreeTraverser` + - Implements a dual-depth-first dual-tree traverser. + +In addition to those two classes, which are required by the +[`TreeType` policy](../../../developer/trees.md), an additional traverser is +available: + + * `VPTree::BreadthFirstDualTreeTraverser` + - Implements a dual-breadth-first dual-tree traverser. + - ***Note:*** this traverser is not useful for all tasks; because the + `VPTree` only holds points in the leaves, this means that no base cases + (e.g. comparisons between points) will be called until *all* pairs of + intermediate nodes have been scored! + +## Example usage + +Build a `VPTree` 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 vp-tree with 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. +// +// 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)); + +// Print the bounding ball of the root node. (There will be no hollow ball.) +std::cout << "Bounding ball of root node:" << std::endl; +std::cout << " - Center: " << tree.Bound().Center().t(); +std::cout << " - Outer radius: " << tree.Bound().OuterRadius() << "." + << std::endl; +std::cout << " - Hollow center: " << tree.Bound().HollowCenter().t(); +std::cout << " - Inner radius: " << tree.Bound().InnerRadius() << "." + << std::endl; +std::cout << std::endl; + +// Print the bounding ball of the right child. (This will have a hollow ball.) +std::cout << "Bounding ball of right child: " << std::endl; +std::cout << " - Center: " << tree.Right()->Bound().Center().t(); +std::cout << " - Outer radius: " << tree.Right()->Bound().OuterRadius() << "." + << std::endl; +std::cout << " - Hollow center: " << tree.Right()->Bound().HollowCenter().t(); +std::cout << " - Inner radius: " << tree.Right()->Bound().InnerRadius() << "." + << std::endl; +std::cout << " - Distance between centers: " << +mlpack::EuclideanDistance::Evaluate(tree.Right()->Bound().Center(), +tree.Right()->Bound().HollowCenter()) << "." << 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; + +// Print the center of the vp-tree. +arma::vec center; +tree.Center(center); +std::cout << "Center of vp-tree: " << center.t(); +``` + +--- + +Build two `VPTree`s on subsets of the corel dataset and compute various +bounding quantities. + +```c++ +// See https://datasets.mlpack.org/corel-histogram.csv. +arma::mat dataset; +mlpack::data::Load("corel-histogram.csv", dataset, true); + +// Build vp-trees on the first half and the second half of points. +mlpack::VPTree<> tree1(dataset.cols(0, dataset.n_cols / 2)); +mlpack::VPTree<> tree2(dataset.cols(dataset.n_cols / 2 + 1, + dataset.n_cols - 1)); + +// 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::VPTree<>& 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::VPTree<>& 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 `VPTree` 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 VPTree using 32-bit floating point data as the matrix type. +// We will still use the default EmptyStatistic and EuclideanDistance +// parameters. A leaf size of 100 is used here. +mlpack::VPTree tree(std::move(dataset), 100); + +// Save the VPTree 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 `VPTree` from disk, then traverse it manually and +find the number of leaf nodes with less than 10 children. + +```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! +typedef mlpack::VPTree TreeType; + +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 leaves, and +// the number of leaves with less than 10 points. +size_t leafCount = 0; +size_t totalLeafCount = 0; +std::stack stack; +stack.push(&tree); +while (!stack.empty()) +{ + TreeType* node = stack.top(); + stack.pop(); + + if (node->NumPoints() < 10) + ++leafCount; + ++totalLeafCount; + + if (!node->IsLeaf()) + { + 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 << leafCount << " out of " << totalLeafCount << " leaves have less " + << "than 10 points." << std::endl; +``` + +--- + +Build a `VPTree` and map between original points and new points. + +```c++ +// See https://datasets.mlpack.org/cloud.csv. +arma::mat dataset; +mlpack::data::Load("cloud.csv", dataset, true); + +// Build the tree. +std::vector oldFromNew, newFromOld; +mlpack::VPTree<> tree(dataset, oldFromNew, newFromOld); + +// oldFromNew and newFromOld will be set to the same size as the dataset. +std::cout << "Number of points in dataset: " << dataset.n_cols << "." + << std::endl; +std::cout << "Size of oldFromNew: " << oldFromNew.size() << "." << std::endl; +std::cout << "Size of newFromOld: " << newFromOld.size() << "." << std::endl; +std::cout << std::endl; + +// See where point 42 in the tree's dataset came from. +std::cout << "Point 42 in the permuted tree's dataset:" << std::endl; +std::cout << " " << tree.Dataset().col(42).t(); +std::cout << "Was originally point " << oldFromNew[42] << ":" << std::endl; +std::cout << " " << dataset.col(oldFromNew[42]).t(); +std::cout << std::endl; + +// See where point 7 in the original dataset was mapped. +std::cout << "Point 7 in original dataset:" << std::endl; +std::cout << " " << dataset.col(7).t(); +std::cout << "Mapped to point " << newFromOld[7] << ":" << std::endl; +std::cout << " " << tree.Dataset().col(newFromOld[7]).t(); +``` From 5665e3f3944bbd47d0ef30bd13e1883668cd79a9 Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Wed, 2 Oct 2024 17:33:47 -0400 Subject: [PATCH 05/14] Minor fixes to HollowBallBound documentation. --- doc/user/core/trees/binary_space_tree.md | 51 +++++++++++++++++------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/doc/user/core/trees/binary_space_tree.md b/doc/user/core/trees/binary_space_tree.md index 561ba1df26..ef66cecd1e 100644 --- a/doc/user/core/trees/binary_space_tree.md +++ b/doc/user/core/trees/binary_space_tree.md @@ -413,7 +413,7 @@ class uses a hyperrectangle bound. An example `HRectBound` is shown below; the bound is the smallest rectangle that encloses all of the points.
    -hyperrectangle bound enclosing points +hyperrectangle bound enclosing points
    mlpack supplies several drop-in `BoundType` classes, and it is also possible to @@ -782,11 +782,13 @@ secondary center point and inner radius. An example `HollowBallBound` is shown below in two dimensions; shaded area represents area held within the bound.
    -hollow ball bound +hollow ball bound
    `HollowBallBound` is used directly by the [`VPTree`](vptree.md) class. +--- + #### Constructors `HollowBallBound` allows configurable behavior via its two template parameters: @@ -841,6 +843,8 @@ before using it! - 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 @@ -853,6 +857,7 @@ be accessed and modified. * `b.HollowCenter()` returns an `arma::vec&` containing the center of the inner ball. Its elements can be directly modified. + - It is possible that `b.HollowCenter()` is outside of the outer ball! * `b.OuterRadius()` will return a `double` that is the radius of the outer ball. @@ -861,6 +866,9 @@ be accessed and modified. * `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`. + - It is possible that `b.InnerRadius() > b.OuterRadius()`, and this implies + that the hollow center is outside the outer ball (otherwise the bound is + empty). * `b[dim]` will return a [`Range`](../math.md#range) object representing the extents of the bound in dimension `dim`. @@ -913,9 +921,12 @@ be accessed and modified. * `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. +The `HollowBallBound` uses the logical `|=` to grow the bound to include points +or other bounds. * `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 @@ -931,6 +942,14 @@ The `HollowBallBound` uses the logical `|=` to grow the bound to include points. - If the bound is not empty, then `data` is expected to have dimensionality that matches `b.Dim()`. + * `b |= bound` expands `b` to include all of the volume included in `bound`. + The center points will not be modified. + - The outer ball's radius will be expanded to include the outer balls of both + `b` and `bound`. + - The inner (hollow) ball's radius will be shrunk to be the intersection of + the inner balls of `b` and `bound`. (This may result in `b.InnerRadius()` + being 0.) + ***Notes:*** - The growth operation does not grow the inner (hollow) ball. Properties @@ -940,6 +959,8 @@ The `HollowBallBound` uses the logical `|=` to grow the bound to include points. - 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 @@ -993,6 +1014,8 @@ constructor, then all distances will be computed with respect to the specified [`RangeType`](../math.md#range) (except for `Contains()`, which will still return a `bool`). +--- + #### Example usage ```c++ @@ -1004,7 +1027,7 @@ 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 << " - Hollow center: " << b.HollowCenter().t(); std::cout << " - Inner radius: " << b.InnerRadius() << "." << std::endl; for (size_t i = 0; i < 3; ++i) { @@ -1036,8 +1059,8 @@ 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)); +// We initialize this ball bound to a "slice" with radii [3.6, 3.7]. +mlpack::HollowBallBound b3(3.6, 3.7, arma::vec(3)); b3 |= dataset; std::cout << "Hollow ball bound on points with pre-initialization and " << "`operator|=()`:" << std::endl; @@ -1057,16 +1080,16 @@ 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; +const double d1 = b.MinDistance(arma::vec("0.9 0.9 0.9")); +std::cout << "Minimum distance between hollow unit ball bound and [0.9, 0.9, " + << "0.9]: " << 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; +const double d2 = b.MinDistance(arma::vec("0.0 0.0 0.0")); +std::cout << "Minimum distance between hollow unit ball bound and [0.0, 0.0, " + << "0.0]: " << d2 << "." << std::endl; std::cout << std::endl; // Use Contains(). In this case, the 'else' will be taken. @@ -1083,9 +1106,9 @@ 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")); +const double d3 = 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; + << "0.1]: " << d3 << "." << std::endl; // Compute the minimum and maximum distances between the hollow unit ball bound // and the bound built on data points. From 959333cebda6cb10e3625271c4aa4b40cf30f1f2 Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Wed, 2 Oct 2024 17:34:07 -0400 Subject: [PATCH 06/14] Add default template parameters for VPTree typedef. --- src/mlpack/core/tree/binary_space_tree/typedef.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mlpack/core/tree/binary_space_tree/typedef.hpp b/src/mlpack/core/tree/binary_space_tree/typedef.hpp index 021ecbdd7d..c14d1da02e 100644 --- a/src/mlpack/core/tree/binary_space_tree/typedef.hpp +++ b/src/mlpack/core/tree/binary_space_tree/typedef.hpp @@ -194,7 +194,9 @@ template using VPTreeSplit = VantagePointSplit; -template +template using VPTree = BinarySpaceTree Date: Wed, 2 Oct 2024 17:34:21 -0400 Subject: [PATCH 07/14] Restore operator|=() overload because it actually was implemented. --- src/mlpack/core/tree/hollow_ball_bound.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/mlpack/core/tree/hollow_ball_bound.hpp b/src/mlpack/core/tree/hollow_ball_bound.hpp index a41c10798f..2cad206b82 100644 --- a/src/mlpack/core/tree/hollow_ball_bound.hpp +++ b/src/mlpack/core/tree/hollow_ball_bound.hpp @@ -212,6 +212,16 @@ 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. */ From 100d50089aec4e55fb711b1750ff06f48e31eda7 Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Wed, 2 Oct 2024 17:34:57 -0400 Subject: [PATCH 08/14] Fix bugs in HollowBallBound implementation. --- src/mlpack/core/tree/hollow_ball_bound_impl.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/mlpack/core/tree/hollow_ball_bound_impl.hpp b/src/mlpack/core/tree/hollow_ball_bound_impl.hpp index 4d65a0aeec..41d3122234 100644 --- a/src/mlpack/core/tree/hollow_ball_bound_impl.hpp +++ b/src/mlpack/core/tree/hollow_ball_bound_impl.hpp @@ -316,8 +316,8 @@ RangeType HollowBallBound::RangeDistance( typename std::enable_if_t::value>* /* junk */) const { if (radii.Hi() < 0) - return Range(std::numeric_limits::max(), - std::numeric_limits::max()); + return RangeType(std::numeric_limits::max(), + std::numeric_limits::max()); else { RangeType range; @@ -461,7 +461,7 @@ void HollowBallBound::serialize( ar(CEREAL_NVP(radii)); ar(CEREAL_NVP(center)); ar(CEREAL_NVP(hollowCenter)); - ar(CEREAL_POINTER(distance)); + if (cereal::is_loading()) { // If we're loading, delete the local distance since we'll have a new one. @@ -470,6 +470,8 @@ void HollowBallBound::serialize( ownsDistance = true; } + + ar(CEREAL_POINTER(distance)); } } // namespace mlpack From 0eb90aeb4db7a4d346a625b92ca1cd716343cc9c Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Wed, 2 Oct 2024 17:41:20 -0400 Subject: [PATCH 09/14] Add HISTORY.md entry for fixes. --- HISTORY.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 81689fcada..bc7d882b5a 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,8 +4,9 @@ _????-??-??_ - * Fix compilation with clang 19 (#3799) + * Fix compilation with clang 19 (#3799). + * Fix serialization and `MinDistance()` bugs with `HollowBallBound` (#3808). ## mlpack 4.5.0 From 395c63383cd08aeb21c96047beaff7937bb8976c Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Wed, 16 Oct 2024 12:33:00 -0400 Subject: [PATCH 10/14] Fix some minor links. --- doc/user/core/trees/binary_space_tree.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/user/core/trees/binary_space_tree.md b/doc/user/core/trees/binary_space_tree.md index 7eab97e542..05313b16e9 100644 --- a/doc/user/core/trees/binary_space_tree.md +++ b/doc/user/core/trees/binary_space_tree.md @@ -842,8 +842,8 @@ Different constructor forms can be used to specify different template parameters any points at all). ***Note***: these constructors provide an empty bound; be sure to -[grow](#growing-and-shrinking-the-bound) the bound or -[directly modify the bound](#accessing-and-modifying-properties-of-the-bound) +[grow](#growing-the-bound) the bound or +[directly modify the bound](#accessing-and-modifying-properties-of-the-bound-1) before using it! --- @@ -1122,7 +1122,7 @@ Different constructor forms can be used to specify different template parameters - `ElemType` should generally be `double` or `float`. ***Note***: these constructors provide an empty bound; be sure to -[grow](#growing-and-shrinking-the-bound-2) the bound or +[grow](#growing-the-bound-1) the bound or [directly modify the bound](#accessing-and-modifying-properties-of-the-bound-2) before using it! @@ -1205,7 +1205,7 @@ be accessed and modified. `DistanceType` if a custom `DistanceType` has been specified in the constructor. - * `b.Center(center)` will compute the center of the `HRectBound` (e.g. the + * `b.Center(center)` will compute the center of the `HollowBallBound` (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`. From 05281d6e17f249906cfff1f41be47db877b4e7fc Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Wed, 16 Oct 2024 13:26:48 -0400 Subject: [PATCH 11/14] Fix paper link. --- doc/user/core/trees/binary_space_tree.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/user/core/trees/binary_space_tree.md b/doc/user/core/trees/binary_space_tree.md index 05313b16e9..0d51f7cdf1 100644 --- a/doc/user/core/trees/binary_space_tree.md +++ b/doc/user/core/trees/binary_space_tree.md @@ -1679,7 +1679,8 @@ For implementation details, see The `VantagePointSplit` class is a splitting strategy that can be used by [`BinarySpaceTree`](#binaryspacetree). It is the default strategy for splitting -[`VPTree`s](vptree.md), and is detailed in [the paper](...). +[`VPTree`s](vptree.md), and is detailed in +[the paper](https://www.mlpack.org/paper/uhlmann91.pdf). Due to the nature of the split, ***`VantagePointSplit` should always be used with the [`HollowBallBound`](#hollowballbound)***. From 61844246907242ebb3e77007dc556c9ea80d9cfe Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Mon, 21 Oct 2024 21:29:14 -0400 Subject: [PATCH 12/14] Fix link. --- doc/user/core/trees/binary_space_tree.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/core/trees/binary_space_tree.md b/doc/user/core/trees/binary_space_tree.md index 0d51f7cdf1..c6ce2d08ea 100644 --- a/doc/user/core/trees/binary_space_tree.md +++ b/doc/user/core/trees/binary_space_tree.md @@ -1680,7 +1680,7 @@ For implementation details, see The `VantagePointSplit` class is a splitting strategy that can be used by [`BinarySpaceTree`](#binaryspacetree). It is the default strategy for splitting [`VPTree`s](vptree.md), and is detailed in -[the paper](https://www.mlpack.org/paper/uhlmann91.pdf). +[the paper](https://www.mlpack.org/papers/uhlmann91.pdf). Due to the nature of the split, ***`VantagePointSplit` should always be used with the [`HollowBallBound`](#hollowballbound)***. From 8f5bf8da77f5e0232d8c5198096f8295f7ba6c98 Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Tue, 22 Oct 2024 09:18:01 -0400 Subject: [PATCH 13/14] Just use the direct ACM paper link instead of archive.org. --- doc/user/core/trees/vptree.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/core/trees/vptree.md b/doc/user/core/trees/vptree.md index f4f6c5e400..b0b916651d 100644 --- a/doc/user/core/trees/vptree.md +++ b/doc/user/core/trees/vptree.md @@ -32,7 +32,7 @@ additional functionality specific to vantage point trees. * [Vantage point tree on Wikipedia](https://en.wikipedia.org/wiki/Vantage-point_tree) * [`BinarySpaceTree`](binary_space_tree.md) * [Binary space partitioning on Wikipedia](https://dl.acm.org/doi/pdf/10.1145/361002.361007) - * [Data structures and algorithms for nearest neighbor search in general metric spaces (pdf)](https://scholar.archive.org/work/og2s6vjmcngfbkmesu3dbsazpq/access/wayback/https://dl.acm.org/doi/pdf/10.5555/313559.313789) + * [Data structures and algorithms for nearest neighbor search in general metric spaces (pdf)](https://dl.acm.org/doi/pdf/10.5555/313559.313789) * [Satisfying General Proximity/Similarity Queries with Metric Trees (pdf)](https://www.mlpack.org/papers/uhlmann91.pdf) * [Tree-Independent Dual-Tree Algorithms (pdf)](https://www.ratml.org/pub/pdf/2013tree.pdf) From 48333f91107e18eb418e877cb38b1f73cf229975 Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Mon, 21 Oct 2024 21:31:26 -0400 Subject: [PATCH 14/14] Fix link to HMM regression PDF. --- doc/user/core/distributions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/core/distributions.md b/doc/user/core/distributions.md index 5fcedd558c..a01057b1ab 100644 --- a/doc/user/core/distributions.md +++ b/doc/user/core/distributions.md @@ -849,7 +849,7 @@ regression model's prediction on `x`. This class is meant to be used with mlpack's [HMM](/src/mlpack/methods/hmm/hmm.hpp) class for the task of -[HMM regression (pdf)](https://conservancy.umn.edu/bitstream/handle/11299/2532/1195.pdf). +[HMM regression (pdf)](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=93a56eb64e77ac83404fddfd0036e95a742fcee6). ### Constructors