Files
mlpack/src/mlpack/tests/tree_test.cpp
T

2256 lines
68 KiB
C++

/**
* @file tests/tree_test.cpp
*
* Tests for tree-building methods.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#include <mlpack/core.hpp>
#include <queue>
#include <stack>
#include "catch.hpp"
#include "test_catch_tools.hpp"
using namespace mlpack;
/**
* Ensure that a bound, by default, is empty and has no dimensionality.
*/
TEST_CASE("HRectBoundEmptyConstructor", "[TreeTest]")
{
HRectBound<EuclideanDistance> b;
REQUIRE((int) b.Dim() == 0);
REQUIRE(b.MinWidth() == 0.0);
}
/**
* Ensure that when we specify the dimensionality in the constructor, it is
* correct, and the bounds are all the empty set.
*/
TEST_CASE("HRectBoundDimConstructor", "[TreeTest]")
{
HRectBound<EuclideanDistance> b(2); // We'll do this with 2 and 5 dimensions.
REQUIRE(b.Dim() == 2);
REQUIRE(b[0].Width() == Approx(0.0).margin(1e-5));
REQUIRE(b[1].Width() == Approx(0.0).margin(1e-5));
b = HRectBound<EuclideanDistance>(5);
REQUIRE(b.Dim() == 5);
REQUIRE(b[0].Width() == Approx(0.0).margin(1e-5));
REQUIRE(b[1].Width() == Approx(0.0).margin(1e-5));
REQUIRE(b[2].Width() == Approx(0.0).margin(1e-5));
REQUIRE(b[3].Width() == Approx(0.0).margin(1e-5));
REQUIRE(b[4].Width() == Approx(0.0).margin(1e-5));
REQUIRE(b.MinWidth() == 0.0);
}
/**
* Test the copy constructor.
*/
TEST_CASE("HRectBoundCopyConstructor", "[TreeTest]")
{
HRectBound<EuclideanDistance> b(2);
b[0] = Range(0.0, 2.0);
b[1] = Range(2.0, 3.0);
b.MinWidth() = 0.5;
HRectBound<EuclideanDistance> c(b);
REQUIRE(c.Dim() == 2);
REQUIRE(c[0].Lo() == Approx(0.0).margin(1e-5));
REQUIRE(c[0].Hi() == Approx(2.0).epsilon(1e-7));
REQUIRE(c[1].Lo() == Approx(2.0).epsilon(1e-7));
REQUIRE(c[1].Hi() == Approx(3.0).epsilon(1e-7));
REQUIRE(c.MinWidth() == Approx(0.5).epsilon(1e-7));
}
/**
* Test the assignment operator.
*/
TEST_CASE("HRectBoundAssignmentOperator", "[TreeTest]")
{
HRectBound<EuclideanDistance> b(2);
b[0] = Range(0.0, 2.0);
b[1] = Range(2.0, 3.0);
b.MinWidth() = 0.5;
HRectBound<EuclideanDistance> c(4);
c = b;
REQUIRE(c.Dim() == 2);
REQUIRE(c[0].Lo() == Approx(0.0).margin(1e-5));
REQUIRE(c[0].Hi() == Approx(2.0).epsilon(1e-7));
REQUIRE(c[1].Lo() == Approx(2.0).epsilon(1e-7));
REQUIRE(c[1].Hi() == Approx(3.0).epsilon(1e-7));
REQUIRE(c.MinWidth() == Approx(0.5).epsilon(1e-7));
}
/**
* Test that clearing the dimensions resets the bound to empty.
*/
TEST_CASE("HRectBoundClear", "[TreeTest]")
{
HRectBound<EuclideanDistance> b(2); // We'll do this with two dimensions only.
b[0] = Range(0.0, 2.0);
b[1] = Range(2.0, 4.0);
b.MinWidth() = 1.0;
// Now we just need to make sure that we clear the range.
b.Clear();
REQUIRE(b[0].Width() == Approx(0.0).margin(1e-5));
REQUIRE(b[1].Width() == Approx(0.0).margin(1e-5));
REQUIRE(b.MinWidth() == Approx(0.0).margin(1e-5));
}
TEST_CASE("HRectBoundMoveConstructor", "[TreeTest]")
{
HRectBound<EuclideanDistance> b(2);
b[0] = Range(0.0, 2.0);
b[1] = Range(2.0, 4.0);
b.MinWidth() = 1.0;
HRectBound<EuclideanDistance> b2(std::move(b));
REQUIRE(b.Dim() == 0);
REQUIRE(b2.Dim() == 2);
REQUIRE(b.MinWidth() == 0.0);
REQUIRE(b2.MinWidth() == 1.0);
REQUIRE(b2[0].Lo() == Approx(0.0).margin(1e-5));
REQUIRE(b2[0].Hi() == Approx(2.0).epsilon(1e-7));
REQUIRE(b2[1].Lo() == Approx(2.0).epsilon(1e-7));
REQUIRE(b2[1].Hi() == Approx(4.0).epsilon(1e-7));
}
/**
* Ensure that we get the correct center for our bound.
*/
TEST_CASE("HRectBoundCenter", "[TreeTest]")
{
// Create a simple 3-dimensional bound.
HRectBound<EuclideanDistance> b(3);
b[0] = Range(0.0, 5.0);
b[1] = Range(-2.0, -1.0);
b[2] = Range(-10.0, 50.0);
arma::vec center;
b.Center(center);
REQUIRE(center.n_elem == 3);
REQUIRE(center[0] == Approx(2.5).epsilon(1e-7));
REQUIRE(center[1] == Approx(-1.5).epsilon(1e-7));
REQUIRE(center[2] == Approx(20.0).epsilon(1e-7));
}
/**
* Ensure the volume calculation is correct.
*/
TEST_CASE("HRectBoundVolume", "[TreeTest]")
{
// Create a simple 3-dimensional bound.
HRectBound<EuclideanDistance> b(3);
b[0] = Range(0.0, 5.0);
b[1] = Range(-2.0, -1.0);
b[2] = Range(-10.0, 50.0);
REQUIRE(b.Volume() == Approx(300.0).epsilon(1e-7));
}
/**
* Ensure that we calculate the correct minimum distance between a point and a
* bound.
*/
TEST_CASE("HRectBoundMinDistancePoint", "[TreeTest]")
{
// We'll do the calculation in five dimensions, and we'll use three cases for
// the point: point is outside the bound; point is on the edge of the bound;
// point is inside the bound. In the latter two cases, the distance should be
// zero.
HRectBound<EuclideanDistance> b(5);
b[0] = Range(0.0, 2.0);
b[1] = Range(1.0, 5.0);
b[2] = Range(-2.0, 2.0);
b[3] = Range(-5.0, -2.0);
b[4] = Range(1.0, 2.0);
arma::vec point = "-2.0 0.0 10.0 3.0 3.0";
// This will be the Euclidean distance.
REQUIRE(b.MinDistance(point) == Approx(sqrt(95.0)).epsilon(1e-7));
point = "2.0 5.0 2.0 -5.0 1.0";
REQUIRE(b.MinDistance(point) == Approx(0.0).margin(1e-5));
point = "1.0 2.0 0.0 -2.0 1.5";
REQUIRE(b.MinDistance(point) == Approx(0.0).margin(1e-5));
}
/**
* Ensure that we calculate the correct minimum distance between a bound and
* another bound.
*/
TEST_CASE("HRectBoundMinDistanceBound", "[TreeTest]")
{
// We'll do the calculation in five dimensions, and we can use six cases.
// The other bound is completely outside the bound; the other bound is on the
// edge of the bound; the other bound partially overlaps the bound; the other
// bound fully overlaps the bound; the other bound is entirely inside the
// bound; the other bound entirely envelops the bound.
HRectBound<EuclideanDistance> b(5);
b[0] = Range(0.0, 2.0);
b[1] = Range(1.0, 5.0);
b[2] = Range(-2.0, 2.0);
b[3] = Range(-5.0, -2.0);
b[4] = Range(1.0, 2.0);
HRectBound<EuclideanDistance> c(5);
// The other bound is completely outside the bound.
c[0] = Range(-5.0, -2.0);
c[1] = Range(6.0, 7.0);
c[2] = Range(-2.0, 2.0);
c[3] = Range(2.0, 5.0);
c[4] = Range(3.0, 4.0);
REQUIRE(b.MinDistance(c) == Approx(sqrt(22.0)).epsilon(1e-7));
REQUIRE(c.MinDistance(b) == Approx(sqrt(22.0)).epsilon(1e-7));
// The other bound is on the edge of the bound.
c[0] = Range(-2.0, 0.0);
c[1] = Range(0.0, 1.0);
c[2] = Range(-3.0, -2.0);
c[3] = Range(-10.0, -5.0);
c[4] = Range(2.0, 3.0);
REQUIRE(b.MinDistance(c) == Approx(0.0).margin(1e-5));
REQUIRE(c.MinDistance(b) == Approx(0.0).margin(1e-5));
// The other bound partially overlaps the bound.
c[0] = Range(-2.0, 1.0);
c[1] = Range(0.0, 2.0);
c[2] = Range(-2.0, 2.0);
c[3] = Range(-8.0, -4.0);
c[4] = Range(0.0, 4.0);
REQUIRE(b.MinDistance(c) == Approx(0.0).margin(1e-5));
REQUIRE(c.MinDistance(b) == Approx(0.0).margin(1e-5));
// The other bound fully overlaps the bound.
REQUIRE(b.MinDistance(b) == Approx(0.0).margin(1e-5));
REQUIRE(c.MinDistance(c) == Approx(0.0).margin(1e-5));
// The other bound is entirely inside the bound / the other bound entirely
// envelops the bound.
c[0] = Range(-1.0, 3.0);
c[1] = Range(0.0, 6.0);
c[2] = Range(-3.0, 3.0);
c[3] = Range(-7.0, 0.0);
c[4] = Range(0.0, 5.0);
REQUIRE(b.MinDistance(c) == Approx(0.0).margin(1e-5));
REQUIRE(c.MinDistance(b) == Approx(0.0).margin(1e-5));
// Now we must be sure that the minimum distance to itself is 0.
REQUIRE(b.MinDistance(b) == Approx(0.0).margin(1e-5));
REQUIRE(c.MinDistance(c) == Approx(0.0).margin(1e-5));
}
/**
* Ensure that we calculate the correct maximum distance between a bound and a
* point. This uses the same test cases as the MinDistance test.
*/
TEST_CASE("HRectBoundMaxDistancePoint", "[TreeTest]")
{
// We'll do the calculation in five dimensions, and we'll use three cases for
// the point: point is outside the bound; point is on the edge of the bound;
// point is inside the bound. In the latter two cases, the distance should be
// zero.
HRectBound<EuclideanDistance> b(5);
b[0] = Range(0.0, 2.0);
b[1] = Range(1.0, 5.0);
b[2] = Range(-2.0, 2.0);
b[3] = Range(-5.0, -2.0);
b[4] = Range(1.0, 2.0);
arma::vec point = "-2.0 0.0 10.0 3.0 3.0";
// This will be the Euclidean distance.
REQUIRE(b.MaxDistance(point) == Approx(sqrt(253.0)).epsilon(1e-7));
point = "2.0 5.0 2.0 -5.0 1.0";
REQUIRE(b.MaxDistance(point) == Approx(sqrt(46.0)).epsilon(1e-7));
point = "1.0 2.0 0.0 -2.0 1.5";
REQUIRE(b.MaxDistance(point) == Approx(sqrt(23.25)).epsilon(1e-7));
}
/**
* Ensure that we calculate the correct maximum distance between a bound and
* another bound. This uses the same test cases as the MinDistance test.
*/
TEST_CASE("HRectBoundMaxDistanceBound", "[TreeTest]")
{
// We'll do the calculation in five dimensions, and we can use six cases.
// The other bound is completely outside the bound; the other bound is on the
// edge of the bound; the other bound partially overlaps the bound; the other
// bound fully overlaps the bound; the other bound is entirely inside the
// bound; the other bound entirely envelops the bound.
HRectBound<EuclideanDistance> b(5);
b[0] = Range(0.0, 2.0);
b[1] = Range(1.0, 5.0);
b[2] = Range(-2.0, 2.0);
b[3] = Range(-5.0, -2.0);
b[4] = Range(1.0, 2.0);
HRectBound<EuclideanDistance> c(5);
// The other bound is completely outside the bound.
c[0] = Range(-5.0, -2.0);
c[1] = Range(6.0, 7.0);
c[2] = Range(-2.0, 2.0);
c[3] = Range(2.0, 5.0);
c[4] = Range(3.0, 4.0);
REQUIRE(b.MaxDistance(c) == Approx(sqrt(210.0)).epsilon(1e-7));
REQUIRE(c.MaxDistance(b) == Approx(sqrt(210.0)).epsilon(1e-7));
// The other bound is on the edge of the bound.
c[0] = Range(-2.0, 0.0);
c[1] = Range(0.0, 1.0);
c[2] = Range(-3.0, -2.0);
c[3] = Range(-10.0, -5.0);
c[4] = Range(2.0, 3.0);
REQUIRE(b.MaxDistance(c) == Approx(sqrt(134.0)).epsilon(1e-7));
REQUIRE(c.MaxDistance(b) == Approx(sqrt(134.0)).epsilon(1e-7));
// The other bound partially overlaps the bound.
c[0] = Range(-2.0, 1.0);
c[1] = Range(0.0, 2.0);
c[2] = Range(-2.0, 2.0);
c[3] = Range(-8.0, -4.0);
c[4] = Range(0.0, 4.0);
REQUIRE(b.MaxDistance(c) == Approx(sqrt(102.0)).epsilon(1e-7));
REQUIRE(c.MaxDistance(b) == Approx(sqrt(102.0)).epsilon(1e-7));
// The other bound fully overlaps the bound.
REQUIRE(b.MaxDistance(b) == Approx(sqrt(46.0)).epsilon(1e-7));
REQUIRE(c.MaxDistance(c) == Approx(sqrt(61.0)).epsilon(1e-7));
// The other bound is entirely inside the bound / the other bound entirely
// envelops the bound.
c[0] = Range(-1.0, 3.0);
c[1] = Range(0.0, 6.0);
c[2] = Range(-3.0, 3.0);
c[3] = Range(-7.0, 0.0);
c[4] = Range(0.0, 5.0);
REQUIRE(b.MaxDistance(c) == Approx(sqrt(100.0)).epsilon(1e-7));
REQUIRE(c.MaxDistance(b) == Approx(sqrt(100.0)).epsilon(1e-7));
// Identical bounds. This will be the sum of the squared widths in each
// dimension.
REQUIRE(b.MaxDistance(b) == Approx(sqrt(46.0)).epsilon(1e-7));
REQUIRE(c.MaxDistance(c) == Approx(sqrt(162.0)).epsilon(1e-7));
// One last additional case. If the bound encloses only one point, the
// maximum distance between it and itself is 0.
HRectBound<EuclideanDistance> d(2);
d[0] = Range(2.0, 2.0);
d[1] = Range(3.0, 3.0);
REQUIRE(d.MaxDistance(d) == Approx(0.0).margin(1e-5));
}
/**
* Ensure that the ranges returned by RangeDistance() are equal to the minimum
* and maximum distance. We will perform this test by creating random bounds
* and comparing the behavior to MinDistance() and MaxDistance() -- so this test
* is assuming that those passed and operate correctly.
*/
TEST_CASE("HRectBoundRangeDistanceBound", "[TreeTest]")
{
for (int i = 0; i < 50; ++i)
{
size_t dim = RandInt(20);
HRectBound<EuclideanDistance> a(dim);
HRectBound<EuclideanDistance> b(dim);
// We will set the low randomly and the width randomly for each dimension of
// each bound.
arma::vec loA(dim);
arma::vec widthA(dim);
loA.randu();
widthA.randu();
arma::vec lo_b(dim);
arma::vec width_b(dim);
lo_b.randu();
width_b.randu();
for (size_t j = 0; j < dim; ++j)
{
a[j] = Range(loA[j], loA[j] + widthA[j]);
b[j] = Range(lo_b[j], lo_b[j] + width_b[j]);
}
// Now ensure that MinDistance and MaxDistance report the same.
Range r = a.RangeDistance(b);
Range s = b.RangeDistance(a);
REQUIRE(r.Lo() == Approx(s.Lo()).epsilon(1e-7));
REQUIRE(r.Hi() == Approx(s.Hi()).epsilon(1e-7));
REQUIRE(r.Lo() == Approx(a.MinDistance(b)).epsilon(1e-7));
REQUIRE(r.Hi() == Approx(a.MaxDistance(b)).epsilon(1e-7));
REQUIRE(s.Lo() == Approx(b.MinDistance(a)).epsilon(1e-7));
REQUIRE(s.Hi() == Approx(b.MaxDistance(a)).epsilon(1e-7));
}
}
/**
* Ensure that the ranges returned by RangeDistance() are equal to the minimum
* and maximum distance. We will perform this test by creating random bounds
* and comparing the bheavior to MinDistance() and MaxDistance() -- so this test
* is assuming that those passed and operate correctly. This is for the
* bound-to-point case.
*/
TEST_CASE("HRectBoundRangeDistancePoint", "[TreeTest]")
{
for (int i = 0; i < 20; ++i)
{
size_t dim = RandInt(20);
HRectBound<EuclideanDistance> a(dim);
// We will set the low randomly and the width randomly for each dimension of
// each bound.
arma::vec loA(dim);
arma::vec widthA(dim);
loA.randu();
widthA.randu();
for (size_t j = 0; j < dim; ++j)
a[j] = Range(loA[j], loA[j] + widthA[j]);
// Now run the test on a few points.
for (int j = 0; j < 10; ++j)
{
arma::vec point(dim);
point.randu();
Range r = a.RangeDistance(point);
REQUIRE(r.Lo() == Approx(a.MinDistance(point)).epsilon(1e-7));
REQUIRE(r.Hi() == Approx(a.MaxDistance(point)).epsilon(1e-7));
}
}
}
/**
* Test that we can expand the bound to include a new point.
*/
TEST_CASE("HRectBoundOrOperatorPoint", "[TreeTest]")
{
// Because this should be independent in each dimension, we can essentially
// run five test cases at once.
HRectBound<EuclideanDistance> b(5);
b[0] = Range(1.0, 3.0);
b[1] = Range(2.0, 4.0);
b[2] = Range(-2.0, -1.0);
b[3] = Range(0.0, 0.0);
b[4] = Range(); // Empty range.
b.MinWidth() = 0.0;
arma::vec point = "2.0 4.0 2.0 -1.0 6.0";
b |= point;
REQUIRE(b[0].Lo() == Approx(1.0).epsilon(1e-7));
REQUIRE(b[0].Hi() == Approx(3.0).epsilon(1e-7));
REQUIRE(b[1].Lo() == Approx(2.0).epsilon(1e-7));
REQUIRE(b[1].Hi() == Approx(4.0).epsilon(1e-7));
REQUIRE(b[2].Lo() == Approx(-2.0).epsilon(1e-7));
REQUIRE(b[2].Hi() == Approx(2.0).epsilon(1e-7));
REQUIRE(b[3].Lo() == Approx(-1.0).epsilon(1e-7));
REQUIRE(b[3].Hi() == Approx(0.0).margin(1e-5));
REQUIRE(b[4].Lo() == Approx(6.0).epsilon(1e-7));
REQUIRE(b[4].Hi() == Approx(6.0).epsilon(1e-7));
REQUIRE(b.MinWidth() == Approx(0.0).margin(1e-5));
}
/**
* Test that we can expand the bound to include another bound.
*/
TEST_CASE("HRectBoundOrOperatorBound", "[TreeTest]")
{
// Because this should be independent in each dimension, we can run many tests
// at once.
HRectBound<EuclideanDistance> b(8);
b[0] = Range(1.0, 3.0);
b[1] = Range(2.0, 4.0);
b[2] = Range(-2.0, -1.0);
b[3] = Range(4.0, 5.0);
b[4] = Range(2.0, 4.0);
b[5] = Range(0.0, 0.0);
b[6] = Range();
b[7] = Range(1.0, 3.0);
HRectBound<EuclideanDistance> c(8);
c[0] = Range(-3.0, -1.0); // Entirely less than the other bound.
c[1] = Range(0.0, 2.0); // Touching edges.
c[2] = Range(-3.0, -1.5); // Partially overlapping.
c[3] = Range(4.0, 5.0); // Identical.
c[4] = Range(1.0, 5.0); // Entirely enclosing.
c[5] = Range(2.0, 2.0); // A single point.
c[6] = Range(1.0, 3.0);
c[7] = Range(); // Empty set.
HRectBound<EuclideanDistance> d = c;
b |= c;
d |= b;
REQUIRE(b[0].Lo() == Approx(-3.0).epsilon(1e-7));
REQUIRE(b[0].Hi() == Approx(3.0).epsilon(1e-7));
REQUIRE(d[0].Lo() == Approx(-3.0).epsilon(1e-7));
REQUIRE(d[0].Hi() == Approx(3.0).epsilon(1e-7));
REQUIRE(b[1].Lo() == Approx(0.0).epsilon(1e-7));
REQUIRE(b[1].Hi() == Approx(4.0).epsilon(1e-7));
REQUIRE(d[1].Lo() == Approx(0.0).epsilon(1e-7));
REQUIRE(d[1].Hi() == Approx(4.0).epsilon(1e-7));
REQUIRE(b[2].Lo() == Approx(-3.0).epsilon(1e-7));
REQUIRE(b[2].Hi() == Approx(-1.0).epsilon(1e-7));
REQUIRE(d[2].Lo() == Approx(-3.0).epsilon(1e-7));
REQUIRE(d[2].Hi() == Approx(-1.0).epsilon(1e-7));
REQUIRE(b[3].Lo() == Approx(4.0).epsilon(1e-7));
REQUIRE(b[3].Hi() == Approx(5.0).epsilon(1e-7));
REQUIRE(d[3].Lo() == Approx(4.0).epsilon(1e-7));
REQUIRE(d[3].Hi() == Approx(5.0).epsilon(1e-7));
REQUIRE(b[4].Lo() == Approx(1.0).epsilon(1e-7));
REQUIRE(b[4].Hi() == Approx(5.0).epsilon(1e-7));
REQUIRE(d[4].Lo() == Approx(1.0).epsilon(1e-7));
REQUIRE(d[4].Hi() == Approx(5.0).epsilon(1e-7));
REQUIRE(b[5].Lo() == Approx(0.0).margin(1e-5));
REQUIRE(b[5].Hi() == Approx(2.0).epsilon(1e-7));
REQUIRE(d[5].Lo() == Approx(0.0).margin(1e-5));
REQUIRE(d[5].Hi() == Approx(2.0).epsilon(1e-7));
REQUIRE(b[6].Lo() == Approx(1.0).epsilon(1e-7));
REQUIRE(b[6].Hi() == Approx(3.0).epsilon(1e-7));
REQUIRE(d[6].Lo() == Approx(1.0).epsilon(1e-7));
REQUIRE(d[6].Hi() == Approx(3.0).epsilon(1e-7));
REQUIRE(b[7].Lo() == Approx(1.0).epsilon(1e-7));
REQUIRE(b[7].Hi() == Approx(3.0).epsilon(1e-7));
REQUIRE(d[7].Lo() == Approx(1.0).epsilon(1e-7));
REQUIRE(d[7].Hi() == Approx(3.0).epsilon(1e-7));
REQUIRE(b.MinWidth() == Approx(1.0).epsilon(1e-7));
REQUIRE(d.MinWidth() == Approx(1.0).epsilon(1e-7));
}
/**
* Test that the Contains() function correctly figures out whether or not a
* point is in a bound.
*/
TEST_CASE("HRectBoundContains", "[TreeTest]")
{
// We can test a couple different points: completely outside the bound,
// adjacent in one dimension to the bound, adjacent in all dimensions to the
// bound, and inside the bound.
HRectBound<EuclideanDistance> b(3);
b[0] = Range(0.0, 2.0);
b[1] = Range(0.0, 2.0);
b[2] = Range(0.0, 2.0);
// Completely outside the range.
arma::vec point = "-1.0 4.0 4.0";
REQUIRE(!b.Contains(point));
// Completely outside, but one dimension is in the range.
point = "-1.0 4.0 1.0";
REQUIRE(!b.Contains(point));
// Outside, but one dimension is on the edge.
point = "-1.0 0.0 3.0";
REQUIRE(!b.Contains(point));
// Two dimensions are on the edge, but one is outside.
point = "0.0 0.0 3.0";
REQUIRE(!b.Contains(point));
// Completely on the edge (should be contained).
point = "0.0 0.0 0.0";
REQUIRE(b.Contains(point));
// Inside the range.
point = "0.3 1.0 0.4";
REQUIRE(b.Contains(point));
}
TEST_CASE("TestBallBound", "[TreeTest]")
{
BallBound<> b1;
BallBound<> b2;
// Create two balls with a center distance of 1 from each other.
// Give the first one a radius of 0.3 and the second a radius of 0.4.
b1.Center().set_size(3);
b1.Center()[0] = 1;
b1.Center()[1] = 2;
b1.Center()[2] = 3;
b1.Radius() = 0.3;
b2.Center().set_size(3);
b2.Center()[0] = 1;
b2.Center()[1] = 2;
b2.Center()[2] = 4;
b2.Radius() = 0.4;
REQUIRE(b1.MinDistance(b2) == Approx(1-0.3-0.4).epsilon(1e-7));
REQUIRE(b1.RangeDistance(b2).Hi() == Approx(1+0.3+0.4).epsilon(1e-7));
REQUIRE(b1.RangeDistance(b2).Lo() == Approx(1-0.3-0.4).epsilon(1e-7));
REQUIRE(b1.RangeDistance(b2).Hi() == Approx(1+0.3+0.4).epsilon(1e-7));
REQUIRE(b1.RangeDistance(b2).Lo() == Approx(1-0.3-0.4).epsilon(1e-7));
REQUIRE(b2.MinDistance(b1) == Approx(1-0.3-0.4).epsilon(1e-7));
REQUIRE(b2.MaxDistance(b1) == Approx(1+0.3+0.4).epsilon(1e-7));
REQUIRE(b2.RangeDistance(b1).Hi() == Approx(1+0.3+0.4).epsilon(1e-7));
REQUIRE(b2.RangeDistance(b1).Lo() == Approx(1-0.3-0.4).epsilon(1e-7));
REQUIRE(b1.Contains(b1.Center()));
REQUIRE(!b1.Contains(b2.Center()));
REQUIRE(!b2.Contains(b1.Center()));
REQUIRE(b2.Contains(b2.Center()));
arma::vec b2point(3); // A point that's within the radius but not the center.
b2point[0] = 1.1;
b2point[1] = 2.1;
b2point[2] = 4.1;
REQUIRE(b2.Contains(b2point));
REQUIRE(b1.MinDistance(b1.Center()) == Approx(0.0).margin(1e-5));
REQUIRE(b1.MinDistance(b2.Center()) == Approx(1 - 0.3).epsilon(1e-7));
REQUIRE(b2.MinDistance(b1.Center()) == Approx(1 - 0.4).epsilon(1e-7));
REQUIRE(b2.MaxDistance(b1.Center()) == Approx(1 + 0.4).epsilon(1e-7));
REQUIRE(b1.MaxDistance(b2.Center()) == Approx(1 + 0.3).epsilon(1e-7));
}
TEST_CASE("BallBoundMoveConstructor", "[TreeTest]")
{
BallBound<> b1(2.0, arma::vec("2 1 1"));
BallBound<> b2(std::move(b1));
REQUIRE(b2.Dim() == 3);
REQUIRE(b1.Dim() == 0);
REQUIRE(b2.Center()[0] == Approx(2.0).epsilon(1e-7));
REQUIRE(b2.Center()[1] == Approx(1.0).epsilon(1e-7));
REQUIRE(b2.Center()[2] == Approx(1.0).epsilon(1e-7));
REQUIRE(b2.MinWidth() == Approx(4.0).epsilon(1e-7));
REQUIRE(b1.MinWidth() == Approx(0.0).margin(1e-5));
}
/**
* Ensure that we calculate the correct minimum distance between a point and a
* bound.
*/
TEST_CASE("HRectBoundRootMinDistancePoint", "[TreeTest]")
{
// We'll do the calculation in five dimensions, and we'll use three cases for
// the point: point is outside the bound; point is on the edge of the bound;
// point is inside the bound. In the latter two cases, the distance should be
// zero.
HRectBound<EuclideanDistance> b(5);
b[0] = Range(0.0, 2.0);
b[1] = Range(1.0, 5.0);
b[2] = Range(-2.0, 2.0);
b[3] = Range(-5.0, -2.0);
b[4] = Range(1.0, 2.0);
arma::vec point = "-2.0 0.0 10.0 3.0 3.0";
// This will be the Euclidean distance.
REQUIRE(b.MinDistance(point) == Approx(sqrt(95.0)).epsilon(1e-7));
point = "2.0 5.0 2.0 -5.0 1.0";
REQUIRE(b.MinDistance(point) == Approx(0.0).margin(1e-5));
point = "1.0 2.0 0.0 -2.0 1.5";
REQUIRE(b.MinDistance(point) == Approx(0.0).margin(1e-5));
}
/**
* Ensure that we calculate the correct minimum distance between a bound and
* another bound.
*/
TEST_CASE("HRectBoundRootMinDistanceBound", "[TreeTest]")
{
// We'll do the calculation in five dimensions, and we can use six cases.
// The other bound is completely outside the bound; the other bound is on the
// edge of the bound; the other bound partially overlaps the bound; the other
// bound fully overlaps the bound; the other bound is entirely inside the
// bound; the other bound entirely envelops the bound.
HRectBound<EuclideanDistance> b(5);
b[0] = Range(0.0, 2.0);
b[1] = Range(1.0, 5.0);
b[2] = Range(-2.0, 2.0);
b[3] = Range(-5.0, -2.0);
b[4] = Range(1.0, 2.0);
HRectBound<EuclideanDistance> c(5);
// The other bound is completely outside the bound.
c[0] = Range(-5.0, -2.0);
c[1] = Range(6.0, 7.0);
c[2] = Range(-2.0, 2.0);
c[3] = Range(2.0, 5.0);
c[4] = Range(3.0, 4.0);
REQUIRE(b.MinDistance(c) == Approx(sqrt(22.0)).epsilon(1e-7));
REQUIRE(c.MinDistance(b) == Approx(sqrt(22.0)).epsilon(1e-7));
// The other bound is on the edge of the bound.
c[0] = Range(-2.0, 0.0);
c[1] = Range(0.0, 1.0);
c[2] = Range(-3.0, -2.0);
c[3] = Range(-10.0, -5.0);
c[4] = Range(2.0, 3.0);
REQUIRE(b.MinDistance(c) == Approx(0.0).margin(1e-5));
REQUIRE(c.MinDistance(b) == Approx(0.0).margin(1e-5));
// The other bound partially overlaps the bound.
c[0] = Range(-2.0, 1.0);
c[1] = Range(0.0, 2.0);
c[2] = Range(-2.0, 2.0);
c[3] = Range(-8.0, -4.0);
c[4] = Range(0.0, 4.0);
REQUIRE(b.MinDistance(c) == Approx(0.0).margin(1e-5));
REQUIRE(c.MinDistance(b) == Approx(0.0).margin(1e-5));
// The other bound fully overlaps the bound.
REQUIRE(b.MinDistance(b) == Approx(0.0).margin(1e-5));
REQUIRE(c.MinDistance(c) == Approx(0.0).margin(1e-5));
// The other bound is entirely inside the bound / the other bound entirely
// envelops the bound.
c[0] = Range(-1.0, 3.0);
c[1] = Range(0.0, 6.0);
c[2] = Range(-3.0, 3.0);
c[3] = Range(-7.0, 0.0);
c[4] = Range(0.0, 5.0);
REQUIRE(b.MinDistance(c) == Approx(0.0).margin(1e-5));
REQUIRE(c.MinDistance(b) == Approx(0.0).margin(1e-5));
// Now we must be sure that the minimum distance to itself is 0.
REQUIRE(b.MinDistance(b) == Approx(0.0).margin(1e-5));
REQUIRE(c.MinDistance(c) == Approx(0.0).margin(1e-5));
}
/**
* Ensure that we calculate the correct maximum distance between a bound and a
* point. This uses the same test cases as the MinDistance test.
*/
TEST_CASE("HRectBoundRootMaxDistancePoint", "[TreeTest]")
{
// We'll do the calculation in five dimensions, and we'll use three cases for
// the point: point is outside the bound; point is on the edge of the bound;
// point is inside the bound. In the latter two cases, the distance should be
// zero.
HRectBound<EuclideanDistance> b(5);
b[0] = Range(0.0, 2.0);
b[1] = Range(1.0, 5.0);
b[2] = Range(-2.0, 2.0);
b[3] = Range(-5.0, -2.0);
b[4] = Range(1.0, 2.0);
arma::vec point = "-2.0 0.0 10.0 3.0 3.0";
// This will be the Euclidean distance.
REQUIRE(b.MaxDistance(point) == Approx(sqrt(253.0)).epsilon(1e-7));
point = "2.0 5.0 2.0 -5.0 1.0";
REQUIRE(b.MaxDistance(point) == Approx(sqrt(46.0)).epsilon(1e-7));
point = "1.0 2.0 0.0 -2.0 1.5";
REQUIRE(b.MaxDistance(point) == Approx(sqrt(23.25)).epsilon(1e-7));
}
/**
* Ensure that we calculate the correct maximum distance between a bound and
* another bound. This uses the same test cases as the MinDistance test.
*/
TEST_CASE("HRectBoundRootMaxDistanceBound", "[TreeTest]")
{
// We'll do the calculation in five dimensions, and we can use six cases.
// The other bound is completely outside the bound; the other bound is on the
// edge of the bound; the other bound partially overlaps the bound; the other
// bound fully overlaps the bound; the other bound is entirely inside the
// bound; the other bound entirely envelops the bound.
HRectBound<EuclideanDistance> b(5);
b[0] = Range(0.0, 2.0);
b[1] = Range(1.0, 5.0);
b[2] = Range(-2.0, 2.0);
b[3] = Range(-5.0, -2.0);
b[4] = Range(1.0, 2.0);
HRectBound<EuclideanDistance> c(5);
// The other bound is completely outside the bound.
c[0] = Range(-5.0, -2.0);
c[1] = Range(6.0, 7.0);
c[2] = Range(-2.0, 2.0);
c[3] = Range(2.0, 5.0);
c[4] = Range(3.0, 4.0);
REQUIRE(b.MaxDistance(c) == Approx(sqrt(210.0)).epsilon(1e-7));
REQUIRE(c.MaxDistance(b) == Approx(sqrt(210.0)).epsilon(1e-7));
// The other bound is on the edge of the bound.
c[0] = Range(-2.0, 0.0);
c[1] = Range(0.0, 1.0);
c[2] = Range(-3.0, -2.0);
c[3] = Range(-10.0, -5.0);
c[4] = Range(2.0, 3.0);
REQUIRE(b.MaxDistance(c) == Approx(sqrt(134.0)).epsilon(1e-7));
REQUIRE(c.MaxDistance(b) == Approx(sqrt(134.0)).epsilon(1e-7));
// The other bound partially overlaps the bound.
c[0] = Range(-2.0, 1.0);
c[1] = Range(0.0, 2.0);
c[2] = Range(-2.0, 2.0);
c[3] = Range(-8.0, -4.0);
c[4] = Range(0.0, 4.0);
REQUIRE(b.MaxDistance(c) == Approx(sqrt(102.0)).epsilon(1e-7));
REQUIRE(c.MaxDistance(b) == Approx(sqrt(102.0)).epsilon(1e-7));
// The other bound fully overlaps the bound.
REQUIRE(b.MaxDistance(b) == Approx(sqrt(46.0)).epsilon(1e-7));
REQUIRE(c.MaxDistance(c) == Approx(sqrt(61.0)).epsilon(1e-7));
// The other bound is entirely inside the bound / the other bound entirely
// envelops the bound.
c[0] = Range(-1.0, 3.0);
c[1] = Range(0.0, 6.0);
c[2] = Range(-3.0, 3.0);
c[3] = Range(-7.0, 0.0);
c[4] = Range(0.0, 5.0);
REQUIRE(b.MaxDistance(c) == Approx(sqrt(100.0)).epsilon(1e-7));
REQUIRE(c.MaxDistance(b) == Approx(sqrt(100.0)).epsilon(1e-7));
// Identical bounds. This will be the sum of the squared widths in each
// dimension.
REQUIRE(b.MaxDistance(b) == Approx(sqrt(46.0)).epsilon(1e-7));
REQUIRE(c.MaxDistance(c) == Approx(sqrt(162.0)).epsilon(1e-7));
// One last additional case. If the bound encloses only one point, the
// maximum distance between it and itself is 0.
HRectBound<EuclideanDistance> d(2);
d[0] = Range(2.0, 2.0);
d[1] = Range(3.0, 3.0);
REQUIRE(d.MaxDistance(d) == Approx(0.0).margin(1e-5));
}
/**
* Ensure that the ranges returned by RangeDistance() are equal to the minimum
* and maximum distance. We will perform this test by creating random bounds
* and comparing the behavior to MinDistance() and MaxDistance() -- so this test
* is assuming that those passed and operate correctly.
*/
TEST_CASE("HRectBoundRootRangeDistanceBound", "[TreeTest]")
{
for (int i = 0; i < 50; ++i)
{
size_t dim = RandInt(20);
HRectBound<EuclideanDistance> a(dim);
HRectBound<EuclideanDistance> b(dim);
// We will set the low randomly and the width randomly for each dimension of
// each bound.
arma::vec loA(dim);
arma::vec widthA(dim);
loA.randu();
widthA.randu();
arma::vec lo_b(dim);
arma::vec width_b(dim);
lo_b.randu();
width_b.randu();
for (size_t j = 0; j < dim; ++j)
{
a[j] = Range(loA[j], loA[j] + widthA[j]);
b[j] = Range(lo_b[j], lo_b[j] + width_b[j]);
}
// Now ensure that MinDistance and MaxDistance report the same.
Range r = a.RangeDistance(b);
Range s = b.RangeDistance(a);
REQUIRE(r.Lo() == Approx(s.Lo()).epsilon(1e-7));
REQUIRE(r.Hi() == Approx(s.Hi()).epsilon(1e-7));
REQUIRE(r.Lo() == Approx(a.MinDistance(b)).epsilon(1e-7));
REQUIRE(r.Hi() == Approx(a.MaxDistance(b)).epsilon(1e-7));
REQUIRE(s.Lo() == Approx(b.MinDistance(a)).epsilon(1e-7));
REQUIRE(s.Hi() == Approx(b.MaxDistance(a)).epsilon(1e-7));
}
}
/**
* Ensure that the ranges returned by RangeDistance() are equal to the minimum
* and maximum distance. We will perform this test by creating random bounds
* and comparing the bheavior to MinDistance() and MaxDistance() -- so this test
* is assuming that those passed and operate correctly. This is for the
* bound-to-point case.
*/
TEST_CASE("HRectBoundRootRangeDistancePoint", "[TreeTest]")
{
for (int i = 0; i < 20; ++i)
{
size_t dim = RandInt(20);
HRectBound<EuclideanDistance> a(dim);
// We will set the low randomly and the width randomly for each dimension of
// each bound.
arma::vec loA(dim);
arma::vec widthA(dim);
loA.randu();
widthA.randu();
for (size_t j = 0; j < dim; ++j)
a[j] = Range(loA[j], loA[j] + widthA[j]);
// Now run the test on a few points.
for (int j = 0; j < 10; ++j)
{
arma::vec point(dim);
point.randu();
Range r = a.RangeDistance(point);
REQUIRE(r.Lo() == Approx(a.MinDistance(point)).epsilon(1e-7));
REQUIRE(r.Hi() == Approx(a.MaxDistance(point)).epsilon(1e-7));
}
}
}
/**
* Ensure that HRectBound::Diameter() works properly.
*/
TEST_CASE("HRectBoundDiameter", "[TreeTest]")
{
HRectBound<LMetric<3, true>> b(4);
b[0] = Range(0.0, 1.0);
b[1] = Range(-1.0, 0.0);
b[2] = Range(2.0, 3.0);
b[3] = Range(7.0, 7.0);
REQUIRE(b.Diameter()== Approx(std::pow(3.0, 1.0 / 3.0)).epsilon(1e-7));
HRectBound<LMetric<2, false>> c(4);
c[0] = Range(0.0, 1.0);
c[1] = Range(-1.0, 0.0);
c[2] = Range(2.0, 3.0);
c[3] = Range(0.0, 0.0);
REQUIRE(c.Diameter() == Approx(3.0).epsilon(1e-7));
HRectBound<LMetric<5, true>> d(2);
d[0] = Range(2.2, 2.2);
d[1] = Range(1.0, 1.0);
REQUIRE(d.Diameter() == Approx(0.0).margin(1e-5));
}
/**
* It seems as though Bill has stumbled across a bug where
* BinarySpaceTree<>::count() returns something different than
* BinarySpaceTree<>::count_. So, let's build a simple tree and make sure they
* are the same.
*/
TEST_CASE("TreeCountMismatch", "[TreeTest]")
{
arma::mat dataset = "2.0 5.0 9.0 4.0 8.0 7.0;"
"3.0 4.0 6.0 7.0 1.0 2.0 ";
// Leaf size of 1.
KDTree<EuclideanDistance, EmptyStatistic, arma::mat> rootNode(dataset, 1);
REQUIRE(rootNode.Count() == 6);
REQUIRE(rootNode.Left()->Count() == 3);
REQUIRE(rootNode.Left()->Left()->Count() == 2);
REQUIRE(rootNode.Left()->Left()->Left()->Count() == 1);
REQUIRE(rootNode.Left()->Left()->Right()->Count() == 1);
REQUIRE(rootNode.Left()->Right()->Count() == 1);
REQUIRE(rootNode.Right()->Count() == 3);
REQUIRE(rootNode.Right()->Left()->Count() == 2);
REQUIRE(rootNode.Right()->Left()->Left()->Count() == 1);
REQUIRE(rootNode.Right()->Left()->Right()->Count() == 1);
REQUIRE(rootNode.Right()->Right()->Count() == 1);
}
TEST_CASE("CheckParents", "[TreeTest]")
{
arma::mat dataset = "2.0 5.0 9.0 4.0 8.0 7.0;"
"3.0 4.0 6.0 7.0 1.0 2.0 ";
// Leaf size of 1.
KDTree<EuclideanDistance, EmptyStatistic, arma::mat> rootNode(dataset, 1);
REQUIRE(rootNode.Parent() ==
(KDTree<EuclideanDistance, EmptyStatistic, arma::mat>*) NULL);
REQUIRE(&rootNode == rootNode.Left()->Parent());
REQUIRE(&rootNode == rootNode.Right()->Parent());
REQUIRE(rootNode.Left() == rootNode.Left()->Left()->Parent());
REQUIRE(rootNode.Left() == rootNode.Left()->Right()->Parent());
REQUIRE(rootNode.Left()->Left() ==
rootNode.Left()->Left()->Left()->Parent());
REQUIRE(rootNode.Left()->Left() ==
rootNode.Left()->Left()->Right()->Parent());
REQUIRE(rootNode.Right() == rootNode.Right()->Left()->Parent());
REQUIRE(rootNode.Right() == rootNode.Right()->Right()->Parent());
REQUIRE(rootNode.Right()->Left() ==
rootNode.Right()->Left()->Left()->Parent());
REQUIRE(rootNode.Right()->Left() ==
rootNode.Right()->Left()->Right()->Parent());
}
TEST_CASE("CheckDataset", "[TreeTest]")
{
arma::mat dataset = "2.0 5.0 9.0 4.0 8.0 7.0;"
"3.0 4.0 6.0 7.0 1.0 2.0 ";
// Leaf size of 1.
KDTree<EuclideanDistance, EmptyStatistic, arma::mat> rootNode(dataset, 1);
arma::mat* rootDataset = &rootNode.Dataset();
REQUIRE(&rootNode.Left()->Dataset() == rootDataset);
REQUIRE(&rootNode.Right()->Dataset() == rootDataset);
REQUIRE(&rootNode.Left()->Left()->Dataset() == rootDataset);
REQUIRE(&rootNode.Left()->Right()->Dataset() == rootDataset);
REQUIRE(&rootNode.Right()->Left()->Dataset() == rootDataset);
REQUIRE(&rootNode.Right()->Right()->Dataset() == rootDataset);
REQUIRE(&rootNode.Left()->Left()->Left()->Dataset() ==
rootDataset);
REQUIRE(&rootNode.Left()->Left()->Right()->Dataset() ==
rootDataset);
REQUIRE(&rootNode.Right()->Left()->Left()->Dataset() ==
rootDataset);
REQUIRE(&rootNode.Right()->Left()->Right()->Dataset() ==
rootDataset);
}
// Ensure FurthestDescendantDistance() works.
TEST_CASE("FurthestDescendantDistanceTest", "[TreeTest]")
{
arma::mat dataset = "1; 3"; // One point.
KDTree<EuclideanDistance, EmptyStatistic, arma::mat> rootNode(dataset, 1);
REQUIRE(rootNode.FurthestDescendantDistance() == Approx(0.0).margin(1e-5));
dataset = "1 -1; 1 -1"; // Square of size [2, 2].
// Both points are contained in the one node.
KDTree<EuclideanDistance, EmptyStatistic, arma::mat> twoPoint(dataset);
REQUIRE(twoPoint.FurthestDescendantDistance() ==
Approx(sqrt(2.0)).epsilon(1e-7));
}
// Ensure that FurthestPointDistance() works.
TEST_CASE("FurthestPointDistanceTest", "[TreeTest]")
{
arma::mat dataset;
dataset.randu(5, 100);
typedef KDTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;
TreeType tree(dataset);
// Now, check each node.
std::queue<TreeType*> nodeQueue;
nodeQueue.push(&tree);
while (!nodeQueue.empty())
{
TreeType* node = nodeQueue.front();
nodeQueue.pop();
if (node->NumChildren() != 0)
REQUIRE(node->FurthestPointDistance() == 0.0);
else
{
// Get center.
arma::vec center;
node->Center(center);
double maxDist = 0.0;
for (size_t i = 0; i < node->NumPoints(); ++i)
{
const double dist = EuclideanDistance::Evaluate(center,
dataset.col(node->Point(i)));
if (dist > maxDist)
maxDist = dist;
}
// We don't require an exact value because FurthestPointDistance() can
// just bound the value instead of returning the exact value.
REQUIRE(maxDist <= node->FurthestPointDistance());
if (node->Left())
nodeQueue.push(node->Left());
if (node->Right())
nodeQueue.push(node->Right());
}
}
}
TEST_CASE("ParentDistanceTest", "[TreeTest]")
{
arma::mat dataset;
dataset.randu(5, 500);
typedef KDTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;
TreeType tree(dataset);
// The root's parent distance should be 0 (although maybe it doesn't actually
// matter; I just want to be sure it's not an uninitialized value, which this
// test *sort* of checks).
REQUIRE(tree.ParentDistance() == 0.0);
// Do a depth-first traversal and make sure the parent distance is the same as
// we calculate.
std::stack<TreeType*> nodeStack;
nodeStack.push(&tree);
while (!nodeStack.empty())
{
TreeType* node = nodeStack.top();
nodeStack.pop();
// If it's a leaf, nothing to check.
if (node->NumChildren() == 0)
continue;
arma::vec center, leftCenter, rightCenter;
node->Center(center);
node->Left()->Center(leftCenter);
node->Right()->Center(rightCenter);
const double leftDistance = LMetric<2>::Evaluate(center, leftCenter);
const double rightDistance = LMetric<2>::Evaluate(center, rightCenter);
REQUIRE(leftDistance ==
Approx(node->Left()->ParentDistance()).epsilon(1e-7));
REQUIRE(rightDistance ==
Approx(node->Right()->ParentDistance()).epsilon(1e-7));
nodeStack.push(node->Left());
nodeStack.push(node->Right());
}
}
TEST_CASE("ParentDistanceTestWithMapping", "[TreeTest]")
{
arma::mat dataset;
dataset.randu(5, 500);
std::vector<size_t> oldFromNew;
typedef KDTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;
TreeType tree(dataset, oldFromNew);
// The root's parent distance should be 0 (although maybe it doesn't actually
// matter; I just want to be sure it's not an uninitialized value, which this
// test *sort* of checks).
REQUIRE(tree.ParentDistance() == 0.0);
// Do a depth-first traversal and make sure the parent distance is the same as
// we calculate.
std::stack<TreeType*> nodeStack;
nodeStack.push(&tree);
while (!nodeStack.empty())
{
TreeType* node = nodeStack.top();
nodeStack.pop();
// If it's a leaf, nothing to check.
if (node->NumChildren() == 0)
continue;
arma::vec center, leftCenter, rightCenter;
node->Center(center);
node->Left()->Center(leftCenter);
node->Right()->Center(rightCenter);
const double leftDistance = LMetric<2>::Evaluate(center, leftCenter);
const double rightDistance = LMetric<2>::Evaluate(center, rightCenter);
REQUIRE(leftDistance ==
Approx(node->Left()->ParentDistance()).epsilon(1e-7));
REQUIRE(rightDistance ==
Approx(node->Right()->ParentDistance()).epsilon(1e-7));
nodeStack.push(node->Left());
nodeStack.push(node->Right());
}
}
// Forward declaration of methods we need for the next test.
template<typename TreeType>
bool CheckPointBounds(TreeType& node);
template<typename TreeType>
void GenerateVectorOfTree(TreeType* node,
size_t depth,
std::vector<TreeType*>& v);
/**
* Exhaustive kd-tree test based on #125.
*
* - Generate a random dataset of a random size.
* - Build a tree on that dataset.
* - Ensure all the permutation indices map back to the correct points.
* - Verify that each point is contained inside all of the bounds of its parent
* nodes.
* - Verify that each bound at a particular level of the tree does not overlap
* with any other bounds at that level.
*
* Then, we do that whole process a handful of times.
*/
TEST_CASE("KdTreeTest", "[TreeTest]")
{
typedef KDTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;
size_t maxRuns = 10; // Ten total tests.
size_t pointIncrements = 1000; // Range is from 2000 points to 11000.
// We use the default leaf size of 20.
for (size_t run = 0; run < maxRuns; run++)
{
size_t dimensions = run + 2;
size_t maxPoints = (run + 1) * pointIncrements;
size_t size = maxPoints;
arma::mat dataset = arma::mat(dimensions, size);
// Mappings for post-sort verification of data.
std::vector<size_t> newToOld;
std::vector<size_t> oldToNew;
// Generate data.
dataset.randu();
// Build the tree itself.
TreeType root(dataset, newToOld, oldToNew);
const arma::mat& treeset = root.Dataset();
// Ensure the size of the tree is correct.
REQUIRE(root.Count() == size);
// Check the forward and backward mappings for correctness.
for (size_t i = 0; i < size; ++i)
{
for (size_t j = 0; j < dimensions; ++j)
{
REQUIRE(treeset(j, i) == dataset(j, newToOld[i]));
REQUIRE(treeset(j, oldToNew[i]) == dataset(j, i));
}
}
// Now check that each point is contained inside of all bounds above it.
CheckPointBounds(root);
// Now check that no peers overlap.
std::vector<TreeType*> v;
GenerateVectorOfTree(&root, 1, v);
// Start with the first pair.
size_t depth = 2;
// Compare each peer against every other peer.
while (depth < v.size())
{
for (size_t i = depth; i < 2 * depth && i < v.size(); ++i)
for (size_t j = i + 1; j < 2 * depth && j < v.size(); ++j)
if (v[i] != NULL && v[j] != NULL)
REQUIRE(!v[i]->Bound().Contains(v[j]->Bound()));
depth *= 2;
}
}
arma::mat dataset(25, 1000);
for (size_t col = 0; col < dataset.n_cols; ++col)
for (size_t row = 0; row < dataset.n_rows; ++row)
dataset(row, col) = row + col;
TreeType root(dataset);
}
TEST_CASE("MaxRPTreeTest", "[TreeTest]")
{
typedef MaxRPTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;
size_t maxRuns = 10; // Ten total tests.
size_t pointIncrements = 1000; // Range is from 2000 points to 11000.
// We use the default leaf size of 20.
for (size_t run = 0; run < maxRuns; run++)
{
size_t dimensions = run + 2;
size_t maxPoints = (run + 1) * pointIncrements;
size_t size = maxPoints;
arma::mat dataset = arma::mat(dimensions, size);
// Mappings for post-sort verification of data.
std::vector<size_t> newToOld;
std::vector<size_t> oldToNew;
// Generate data.
dataset.randu();
// Build the tree itself.
TreeType root(dataset, newToOld, oldToNew);
const arma::mat& treeset = root.Dataset();
// Ensure the size of the tree is correct.
REQUIRE(root.Count() == size);
// Check the forward and backward mappings for correctness.
for (size_t i = 0; i < size; ++i)
{
for (size_t j = 0; j < dimensions; ++j)
{
REQUIRE(treeset(j, i) == dataset(j, newToOld[i]));
REQUIRE(treeset(j, oldToNew[i]) == dataset(j, i));
}
}
}
}
template<typename TreeType>
bool CheckHyperplaneSplit(const TreeType& tree)
{
typedef typename TreeType::ElemType ElemType;
const typename TreeType::Mat& dataset = tree.Dataset();
arma::Mat<typename TreeType::ElemType> mat(dataset.n_rows + 1,
tree.Left()->NumDescendants() + tree.Right()->NumDescendants());
// We will try to find a hyperplane that splits the node.
// The hyperplane may be represented as
// a_1 * x_1 + ... + a_n * x_n + a_{n + 1} = 0.
// We have to solve the system of inequalities (mat^t) * x <= 0,
// where x[0], ... , x[dataset.n_rows-1] are the components of the normal
// to the hyperplane and x[dataset.n_rows] is the position of the hyperplane
// i.e. x = (a_1, ... , a_{n + 1}).
// Each column of the matrix consists of a point and 1.
// In such a way, the inner product of a column and x is equal to the value
// of the hyperplane expression.
// The hyperplane splits the node if the expression takes on opposite
// values on node's children.
for (size_t i = 0; i < tree.Left()->NumDescendants(); ++i)
{
for (size_t k = 0; k < dataset.n_rows; ++k)
mat(k, i) = - dataset(k, tree.Left()->Descendant(i));
mat(dataset.n_rows, i) = -1;
}
for (size_t i = 0; i < tree.Right()->NumDescendants(); ++i)
{
for (size_t k = 0; k < dataset.n_rows; ++k)
mat(k, i + tree.Left()->NumDescendants()) =
dataset(k, tree.Right()->Descendant(i));
mat(dataset.n_rows, i + tree.Left()->NumDescendants()) = 1;
}
arma::Col<ElemType> x(dataset.n_rows + 1);
x.zeros();
// Define an initial value.
x[0] = 1.0;
x[1] = -arma::mean(
dataset.cols(tree.Begin(), tree.Begin() + tree.Count() - 1).row(0));
const size_t numIters = 1000000;
const ElemType delta = 1e-4;
// We will solve the system using a simple gradient method.
bool success = false;
for (size_t it = 0; it < numIters; it++)
{
success = true;
for (size_t k = 0; k < tree.Count(); ++k)
{
ElemType result = dot(mat.col(k), x);
if (result > 0)
{
x -= mat.col(k) * delta;
success = false;
}
}
// The norm of the direction shouldn't be equal to zero.
if (arma::norm(x.rows(0, dataset.n_rows-1)) < 1e-8)
{
x[RandInt(0, dataset.n_rows)] = 1.0;
success = false;
}
if (success)
break;
}
return success;
}
template<typename TreeType>
void CheckMaxRPTreeSplit(const TreeType& tree)
{
if (tree.IsLeaf())
return;
REQUIRE(CheckHyperplaneSplit(tree) == true);
CheckMaxRPTreeSplit(*tree.Left());
CheckMaxRPTreeSplit(*tree.Right());
}
TEST_CASE("MaxRPTreeSplitTest", "[TreeTest]")
{
typedef MaxRPTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;
arma::mat dataset;
dataset.randu(8, 1000);
TreeType root(dataset);
CheckMaxRPTreeSplit(root);
}
TEST_CASE("RPTreeTest", "[TreeTest]")
{
typedef RPTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;
size_t maxRuns = 10; // Ten total tests.
size_t pointIncrements = 1000; // Range is from 2000 points to 11000.
// We use the default leaf size of 20.
for (size_t run = 0; run < maxRuns; run++)
{
size_t dimensions = run + 2;
size_t maxPoints = (run + 1) * pointIncrements;
size_t size = maxPoints;
arma::mat dataset = arma::mat(dimensions, size);
// Mappings for post-sort verification of data.
std::vector<size_t> newToOld;
std::vector<size_t> oldToNew;
// Generate data.
dataset.randu();
// Build the tree itself.
TreeType root(dataset, newToOld, oldToNew);
const arma::mat& treeset = root.Dataset();
// Ensure the size of the tree is correct.
REQUIRE(root.Count() == size);
// Check the forward and backward mappings for correctness.
for (size_t i = 0; i < size; ++i)
{
for (size_t j = 0; j < dimensions; ++j)
{
REQUIRE(treeset(j, i) == dataset(j, newToOld[i]));
REQUIRE(treeset(j, oldToNew[i]) == dataset(j, i));
}
}
}
}
template<typename TreeType, typename DistanceType>
void CheckRPTreeSplit(const TreeType& tree)
{
typedef typename TreeType::ElemType ElemType;
if (tree.IsLeaf())
return;
if (!CheckHyperplaneSplit(tree))
{
// Check if that was mean split.
arma::Col<ElemType> center;
tree.Left()->Bound().Center(center);
ElemType maxDist = 0;
for (size_t k =0; k < tree.Left()->NumDescendants(); ++k)
{
ElemType dist = DistanceType::Evaluate(center,
tree.Dataset().col(tree.Left()->Descendant(k)));
if (dist > maxDist)
maxDist = dist;
}
for (size_t k =0; k < tree.Right()->NumDescendants(); ++k)
{
ElemType dist = DistanceType::Evaluate(center,
tree.Dataset().col(tree.Right()->Descendant(k)));
REQUIRE(maxDist <= dist *
(1.0 + 10.0 * std::numeric_limits<ElemType>::epsilon()));
}
}
CheckRPTreeSplit<TreeType, DistanceType>(*tree.Left());
CheckRPTreeSplit<TreeType, DistanceType>(*tree.Right());
}
TEST_CASE("RPTreeSplitTest", "[TreeTest]")
{
typedef RPTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;
arma::mat dataset;
dataset.randu(8, 1000);
TreeType root(dataset);
CheckRPTreeSplit<TreeType, EuclideanDistance>(root);
}
// Recursively checks that each node contains all points that it claims to have.
template<typename TreeType>
bool CheckPointBounds(TreeType& node)
{
// Check that each point which this tree claims is actually inside the tree.
for (size_t index = 0; index < node.NumDescendants(); index++)
if (!node.Bound().Contains(node.Dataset().col(node.Descendant(index))))
return false;
bool result = true;
for (size_t child = 0; child < node.NumChildren(); ++child)
result &= CheckPointBounds(node.Child(child));
return result;
}
/**
* Exhaustive ball tree test based on #125.
*
* - Generate a random dataset of a random size.
* - Build a tree on that dataset.
* - Ensure all the permutation indices map back to the correct points.
* - Verify that each point is contained inside all of the bounds of its parent
* nodes.
*
* Then, we do that whole process a handful of times.
*/
TEST_CASE("BallTreeTest", "[TreeTest]")
{
typedef BallTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;
size_t maxRuns = 10; // Ten total tests.
size_t pointIncrements = 1000; // Range is from 2000 points to 11000.
// We use the default leaf size of 20.
for (size_t run = 0; run < maxRuns; run++)
{
size_t dimensions = run + 2;
size_t maxPoints = (run + 1) * pointIncrements;
size_t size = maxPoints;
arma::mat dataset = arma::mat(dimensions, size);
arma::mat datacopy; // Used to test mappings.
// Mappings for post-sort verification of data.
std::vector<size_t> newToOld;
std::vector<size_t> oldToNew;
// Generate data.
dataset.randu();
// Build the tree itself.
TreeType root(dataset, newToOld, oldToNew);
const arma::mat& treeset = root.Dataset();
// Ensure the size of the tree is correct.
REQUIRE(root.NumDescendants() == size);
// Check the forward and backward mappings for correctness.
for (size_t i = 0; i < size; ++i)
{
for (size_t j = 0; j < dimensions; ++j)
{
REQUIRE(treeset(j, i) == dataset(j, newToOld[i]));
REQUIRE(treeset(j, oldToNew[i]) == dataset(j, i));
}
}
// Now check that each point is contained inside of all bounds above it.
CheckPointBounds(root);
}
}
template<typename TreeType>
void GenerateVectorOfTree(TreeType* node,
size_t depth,
std::vector<TreeType*>& v)
{
if (node == NULL)
return;
if (depth >= v.size())
v.resize(2 * depth + 1, NULL); // Resize to right size; fill with NULL.
v[depth] = node;
// Recurse to the left and right children.
GenerateVectorOfTree(node->Left(), depth * 2, v);
GenerateVectorOfTree(node->Right(), depth * 2 + 1, v);
return;
}
/**
* Exhaustive sparse kd-tree test based on #125.
*
* - Generate a random dataset of a random size.
* - Build a tree on that dataset.
* - Ensure all the permutation indices map back to the correct points.
* - Verify that each point is contained inside all of the bounds of its parent
* nodes.
* - Verify that each bound at a particular level of the tree does not overlap
* with any other bounds at that level.
*
* Then, we do that whole process a handful of times.
*/
TEST_CASE("ExhaustiveSparseKDTreeTest", "[TreeTest]")
{
typedef KDTree<EuclideanDistance, EmptyStatistic, arma::SpMat<double>>
TreeType;
size_t maxRuns = 2; // Two total tests.
size_t pointIncrements = 200; // Range is from 200 points to 400.
// We use the default leaf size of 20.
for (size_t run = 0; run < maxRuns; run++)
{
size_t dimensions = run + 2;
size_t maxPoints = (run + 1) * pointIncrements;
size_t size = maxPoints;
arma::SpMat<double> dataset = arma::SpMat<double>(dimensions, size);
arma::SpMat<double> datacopy; // Used to test mappings.
// Mappings for post-sort verification of data.
std::vector<size_t> newToOld;
std::vector<size_t> oldToNew;
// Generate data.
dataset.sprandu(dimensions, size, 0.1);
datacopy = dataset; // Save a copy.
// Build the tree itself.
TreeType root(dataset, newToOld, oldToNew);
const arma::sp_mat& treeset = root.Dataset();
// Ensure the size of the tree is correct.
REQUIRE(root.Count() == size);
// Check the forward and backward mappings for correctness.
for (size_t i = 0; i < size; ++i)
{
for (size_t j = 0; j < dimensions; ++j)
{
REQUIRE(treeset(j, i) == dataset(j, newToOld[i]));
REQUIRE(treeset(j, oldToNew[i]) == dataset(j, i));
}
}
// Now check that each point is contained inside of all bounds above it.
CheckPointBounds(root);
// Now check that no peers overlap.
std::vector<TreeType*> v;
GenerateVectorOfTree(&root, 1, v);
// Start with the first pair.
size_t depth = 2;
// Compare each peer against every other peer.
while (depth < v.size())
{
for (size_t i = depth; i < 2 * depth && i < v.size(); ++i)
for (size_t j = i + 1; j < 2 * depth && j < v.size(); ++j)
if (v[i] != NULL && v[j] != NULL)
REQUIRE(!v[i]->Bound().Contains(v[j]->Bound()));
depth *= 2;
}
}
arma::SpMat<double> dataset(25, 1000);
for (size_t col = 0; col < dataset.n_cols; ++col)
for (size_t row = 0; row < dataset.n_rows; ++row)
dataset(row, col) = row + col;
TreeType root(dataset);
}
TEST_CASE("BinarySpaceTreeMoveConstructorTest", "[TreeTest]")
{
arma::mat dataset(5, 1000);
dataset.randu();
BinarySpaceTree<EuclideanDistance> tree(dataset);
BinarySpaceTree<EuclideanDistance> tree2(std::move(tree));
REQUIRE(tree.NumChildren() == 0);
REQUIRE(tree2.NumChildren() == 2);
}
template<typename TreeType>
void RecurseTreeCountLeaves(const TreeType& node, arma::vec& counts)
{
for (size_t i = 0; i < node.NumChildren(); ++i)
{
if (node.Child(i).NumChildren() == 0)
counts[node.Child(i).Point()]++;
else
RecurseTreeCountLeaves<TreeType>(node.Child(i), counts);
}
}
template<typename TreeType>
void CheckSelfChild(const TreeType& node)
{
if (node.NumChildren() == 0)
return; // No self-child applicable here.
bool found = false;
for (size_t i = 0; i < node.NumChildren(); ++i)
{
if (node.Child(i).Point() == node.Point())
found = true;
// Recursively check the children.
CheckSelfChild(node.Child(i));
}
// Ensure this has its own self-child.
REQUIRE(found == true);
}
template<typename TreeType, typename DistanceType>
void CheckCovering(const TreeType& node)
{
// Return if a leaf. No checking necessary.
if (node.NumChildren() == 0)
return;
const typename TreeType::Mat& dataset = node.Dataset();
const size_t nodePoint = node.Point();
// To ensure that this node satisfies the covering principle, we must ensure
// that the distance to each child is less than pow(base, scale).
double maxDistance = pow(node.Base(), node.Scale());
for (size_t i = 0; i < node.NumChildren(); ++i)
{
const size_t childPoint = node.Child(i).Point();
double distance = DistanceType::Evaluate(dataset.col(nodePoint),
dataset.col(childPoint));
REQUIRE(distance <= maxDistance);
// Check the child.
CheckCovering<TreeType, DistanceType>(node.Child(i));
}
}
/**
* Create a simple cover tree and then make sure it is valid.
*/
TEST_CASE("SimpleCoverTreeConstructionTest", "[TreeTest]")
{
// 20-point dataset.
arma::mat data = trans(arma::mat("0.0 0.0;"
"1.0 0.0;"
"0.5 0.5;"
"2.0 2.0;"
"-1.0 2.0;"
"3.0 0.0;"
"1.5 5.5;"
"-2.0 -2.0;"
"-1.5 1.5;"
"0.0 4.0;"
"2.0 1.0;"
"2.0 1.2;"
"-3.0 -2.5;"
"-5.0 -5.0;"
"3.5 1.5;"
"2.0 2.5;"
"-1.0 -1.0;"
"-3.5 1.5;"
"3.5 -1.5;"
"2.0 1.0;"));
// The root point will be the first point, (0, 0).
typedef StandardCoverTree<EuclideanDistance, EmptyStatistic, arma::mat>
TreeType;
TreeType tree(data); // Expansion constant of 2.0.
// The furthest point from the root will be (-5, -5), with a distance of
// of sqrt(50). This means the scale of the root node should be 3 (because
// 2^3 = 8).
REQUIRE(tree.Scale() == 3);
// Now loop through the tree and ensure that each leaf is only created once.
arma::vec counts;
counts.zeros(20);
RecurseTreeCountLeaves(tree, counts);
// Each point should only have one leaf node representing it.
for (size_t i = 0; i < 20; ++i)
REQUIRE(counts[i] == 1);
// Each non-leaf should have a self-child.
CheckSelfChild<TreeType>(tree);
// Each node must satisfy the covering principle (its children must be less
// than or equal to a certain distance apart).
CheckCovering<TreeType, LMetric<2, true>>(tree);
// There's no need to check the separation invariant because that is relaxed
// in our implementation.
}
/**
* Create a large cover tree and make sure it's accurate.
*/
TEST_CASE("CoverTreeConstructionTest", "[TreeTest]")
{
arma::mat dataset;
// 50-dimensional, 1000 point.
dataset.randu(50, 1000);
typedef StandardCoverTree<EuclideanDistance, EmptyStatistic, arma::mat>
TreeType;
TreeType tree(dataset);
// Ensure each leaf is only created once.
arma::vec counts;
counts.zeros(1000);
RecurseTreeCountLeaves(tree, counts);
for (size_t i = 0; i < 1000; ++i)
REQUIRE(counts[i] == 1);
// Each non-leaf should have a self-child.
CheckSelfChild<TreeType>(tree);
// Each node must satisfy the covering principle (its children must be less
// than or equal to a certain distance apart).
CheckCovering<TreeType, LMetric<2, true> >(tree);
// There's no need to check the separation because that is relaxed in our
// implementation.
}
/**
* Create a cover tree on sparse data and make sure it's accurate.
*/
TEST_CASE("SparseCoverTreeConstructionTest", "[TreeTest]")
{
arma::sp_mat dataset;
// 50-dimensional, 1000 point.
dataset.sprandu(50, 1000, 0.3);
typedef StandardCoverTree<EuclideanDistance, EmptyStatistic, arma::sp_mat>
TreeType;
TreeType tree(dataset);
// Ensure each leaf is only created once.
arma::vec counts;
counts.zeros(1000);
RecurseTreeCountLeaves(tree, counts);
for (size_t i = 0; i < 1000; ++i)
REQUIRE(counts[i] == 1);
// Each non-leaf should have a self-child.
CheckSelfChild<TreeType>(tree);
// Each node must satisfy the covering principle (its children must be less
// than or equal to a certain distance apart).
CheckCovering<TreeType, LMetric<2, true> >(tree);
// There's no need to check the separation invariant because that is relaxed
// in our implementation.
}
/**
* Test the manual constructor.
*/
TEST_CASE("CoverTreeManualConstructorTest", "[TreeTest]")
{
arma::mat dataset;
dataset.zeros(10, 10);
typedef StandardCoverTree<EuclideanDistance, EmptyStatistic, arma::mat>
TreeType;
TreeType node(dataset, 1.3, 3, 2, NULL, 1.5, 2.75);
REQUIRE(&node.Dataset() == &dataset);
REQUIRE(node.Base() == 1.3);
REQUIRE(node.Point() == 3);
REQUIRE(node.Scale() == 2);
REQUIRE(node.Parent() == (CoverTree<>*) NULL);
REQUIRE(node.ParentDistance() == 1.5);
REQUIRE(node.FurthestDescendantDistance() == 2.75);
}
/**
* Make sure cover trees work in different metric spaces.
*/
TEST_CASE("CoverTreeAlternateMetricTest", "[TreeTest]")
{
arma::mat dataset;
// 5-dimensional, 300-point dataset.
dataset.randu(5, 300);
typedef StandardCoverTree<ManhattanDistance, EmptyStatistic, arma::mat>
TreeType;
TreeType tree(dataset);
// Ensure each leaf is only created once.
arma::vec counts;
counts.zeros(300);
RecurseTreeCountLeaves<TreeType>(tree, counts);
for (size_t i = 0; i < 300; ++i)
REQUIRE(counts[i] == 1);
// Each non-leaf should have a self-child.
CheckSelfChild<TreeType>(tree);
// Each node must satisfy the covering principle (its children must be less
// than or equal to a certain distance apart).
CheckCovering<TreeType, ManhattanDistance>(tree);
// There's no need to check the separation invariant because that is relaxed
// in our implementation.
}
/**
* Make sure copy constructor works for the cover tree.
*/
TEST_CASE("CoverTreeCopyConstructor", "[TreeTest]")
{
arma::mat dataset;
dataset.randu(10, 10); // dataset is irrelevant.
typedef StandardCoverTree<EuclideanDistance, EmptyStatistic, arma::mat>
TreeType;
TreeType c(dataset, 1.3, 0, 5, NULL, 1.45, 5.2); // Random parameters.
c.Children().push_back(new TreeType(dataset, 1.3, 1, 4, &c, 1.3, 2.45));
c.Children().push_back(new TreeType(dataset, 1.5, 2, 3, &c, 1.2, 5.67));
TreeType d = c;
// Check that everything is the same.
// As the tree being copied doesn't own the dataset, they must share the same
// pointer.
REQUIRE(c.Dataset().memptr() == d.Dataset().memptr());
REQUIRE(c.Base() == Approx(d.Base()).epsilon(1e-52));
REQUIRE(c.Point() == d.Point());
REQUIRE(c.Scale() == d.Scale());
REQUIRE(c.Parent() == d.Parent());
REQUIRE(c.ParentDistance() == d.ParentDistance());
REQUIRE(c.FurthestDescendantDistance() ==
d.FurthestDescendantDistance());
REQUIRE(c.NumChildren() == d.NumChildren());
REQUIRE(&c.Child(0) != &d.Child(0));
REQUIRE(&c.Child(1) != &d.Child(1));
REQUIRE(c.Child(0).Parent() == &c);
REQUIRE(c.Child(1).Parent() == &c);
REQUIRE(d.Child(0).Parent() == &d);
REQUIRE(d.Child(1).Parent() == &d);
// Check that the children are okay.
REQUIRE(c.Child(0).Dataset().memptr() == c.Dataset().memptr());
REQUIRE(c.Child(0).Base() == Approx(d.Child(0).Base()).epsilon(1e-52));
REQUIRE(c.Child(0).Point() == d.Child(0).Point());
REQUIRE(c.Child(0).Scale() == d.Child(0).Scale());
REQUIRE(c.Child(0).ParentDistance() == d.Child(0).ParentDistance());
REQUIRE(c.Child(0).FurthestDescendantDistance() ==
d.Child(0).FurthestDescendantDistance());
REQUIRE(c.Child(0).NumChildren() == d.Child(0).NumChildren());
REQUIRE(c.Child(1).Dataset().memptr() == c.Dataset().memptr());
REQUIRE(c.Child(1).Base() == Approx(d.Child(1).Base()).epsilon(1e-52));
REQUIRE(c.Child(1).Point() == d.Child(1).Point());
REQUIRE(c.Child(1).Scale() == d.Child(1).Scale());
REQUIRE(c.Child(1).ParentDistance() == d.Child(1).ParentDistance());
REQUIRE(c.Child(1).FurthestDescendantDistance() ==
d.Child(1).FurthestDescendantDistance());
REQUIRE(c.Child(1).NumChildren() == d.Child(1).NumChildren());
// Check copy constructor when the tree being copied owns the dataset.
TreeType e(std::move(dataset), 1.3);
TreeType f = e;
// As the tree being copied owns the dataset, they must have different
// instances.
REQUIRE(e.Dataset().memptr() != f.Dataset().memptr());
}
TEST_CASE("CoverTreeMoveDatasetTest", "[TreeTest]")
{
arma::mat dataset = arma::randu<arma::mat>(3, 1000);
typedef StandardCoverTree<EuclideanDistance, EmptyStatistic, arma::mat>
TreeType;
TreeType t(std::move(dataset));
REQUIRE(dataset.n_elem == 0);
REQUIRE(t.Dataset().n_rows == 3);
REQUIRE(t.Dataset().n_cols == 1000);
EuclideanDistance ed; // Test the other constructor.
dataset = arma::randu<arma::mat>(3, 1000);
TreeType t2(std::move(dataset), ed);
REQUIRE(dataset.n_elem == 0);
REQUIRE(t2.Dataset().n_rows == 3);
REQUIRE(t2.Dataset().n_cols == 1000);
}
/**
* Make sure copy constructor works right for the binary space tree.
*/
TEST_CASE("BinarySpaceTreeCopyConstructor", "[TreeTest]")
{
arma::mat data("1");
typedef KDTree<EuclideanDistance, EmptyStatistic, arma::mat> TreeType;
TreeType b(data);
b.Begin() = 10;
b.Count() = 50;
b.Left() = new TreeType(data);
b.Left()->Begin() = 10;
b.Left()->Count() = 30;
b.Left()->Parent() = &b;
b.Right() = new TreeType(data);
b.Right()->Begin() = 40;
b.Right()->Count() = 20;
b.Right()->Parent() = &b;
// Copy the tree.
TreeType c(b);
// Ensure everything copied correctly.
REQUIRE(b.Begin() == c.Begin());
REQUIRE(b.Count() == c.Count());
REQUIRE(b.Left() != c.Left());
REQUIRE(b.Right() != c.Right());
// Check the children.
REQUIRE(b.Left()->Begin() == c.Left()->Begin());
REQUIRE(b.Left()->Count() == c.Left()->Count());
REQUIRE(b.Left()->Left() == (TreeType*) NULL);
REQUIRE(b.Left()->Left() == c.Left()->Left());
REQUIRE(b.Left()->Right() == (TreeType*) NULL);
REQUIRE(b.Left()->Right() == c.Left()->Right());
REQUIRE(b.Right()->Begin() == c.Right()->Begin());
REQUIRE(b.Right()->Count() == c.Right()->Count());
REQUIRE(b.Right()->Left() == (TreeType*) NULL);
REQUIRE(b.Right()->Left() == c.Right()->Left());
REQUIRE(b.Right()->Right() == (TreeType*) NULL);
REQUIRE(b.Right()->Right() == c.Right()->Right());
// Clean memory (we built the tree by hand, so this is what we have to do
// since the destructor won't free the children's datasets).
delete &b.Left()->Dataset();
delete &b.Right()->Dataset();
}
//! Count the number of leaves under this node.
template<typename TreeType>
size_t NumLeaves(TreeType* node)
{
if (node->NumChildren() == 0)
return 1;
size_t count = 0;
for (size_t i = 0; i < node->NumChildren(); ++i)
count += NumLeaves(&node->Child(i));
return count;
}
//! Returns true if the index is contained somewhere under this node.
template<typename TreeType>
bool FindIndex(TreeType* node, const size_t index)
{
for (size_t i = 0; i < node->NumPoints(); ++i)
if (node->Point(i) == index)
return true;
for (size_t i = 0; i < node->NumChildren(); ++i)
if (FindIndex(&node->Child(i), index))
return true;
return false;
}
//! Check that the points in the given node are accessible through the
//! Descendant() function of the root node.
template<typename TreeType>
bool CheckAccessibility(TreeType* childNode, TreeType* rootNode)
{
for (size_t i = 0; i < childNode->NumPoints(); ++i)
{
bool found = false;
for (size_t j = 0; j < rootNode->NumDescendants(); ++j)
{
if (childNode->Point(i) == rootNode->Descendant(j))
{
found = true;
break;
}
}
if (!found)
{
Log::Debug << "Did not find descendant " << childNode->Point(i) << ".\n";
return false;
}
}
// Now check the children.
for (size_t i = 0; i < childNode->NumChildren(); ++i)
if (!CheckAccessibility(&childNode->Child(i), rootNode))
return false;
return true;
}
//! Check that Descendant() and NumDescendants() is right for this node.
template<typename TreeType>
void CheckDescendants(TreeType* node)
{
// In a cover tree, the number of leaves should be the number of descendant
// points.
const size_t numLeaves = NumLeaves(node);
REQUIRE(numLeaves == node->NumDescendants());
// Now check that each descendant is somewhere in the tree.
for (size_t i = 0; i < node->NumDescendants(); ++i)
{
Log::Debug << "Check for descendant " << node->Descendant(i) << " (i " <<
i << ").\n";
REQUIRE(FindIndex(node, node->Descendant(i)) == true);
}
// Now check that every actual descendant is accessible through the
// Descendant() function.
REQUIRE(CheckAccessibility(node, node) == true);
// Now check that there are no duplicates in the list of descendants.
std::vector<size_t> descendants;
descendants.resize(node->NumDescendants());
for (size_t i = 0; i < node->NumDescendants(); ++i)
descendants[i] = node->Descendant(i);
// Sort the list.
std::sort(descendants.begin(), descendants.end());
// Check that there are no duplicates (this is easy because it's sorted).
for (size_t i = 1; i < descendants.size(); ++i)
REQUIRE(descendants[i] != descendants[i - 1]);
// Now perform these same checks for the children.
for (size_t i = 0; i < node->NumChildren(); ++i)
CheckDescendants(&node->Child(i));
}
/**
* Make sure Descendant() and NumDescendants() works properly for the cover
* tree.
*/
TEST_CASE("CoverTreeDescendantTest", "[TreeTest]")
{
arma::mat dataset;
dataset.randu(3, 100);
StandardCoverTree<EuclideanDistance, EmptyStatistic, arma::mat> tree(dataset);
// Now check that the NumDescendants() count and each Descendant() is right
// using the recursive function above.
CheckDescendants(&tree);
}