Files
mlpack/src/mlpack/core/tree/hrectbound_impl.hpp
T

700 lines
18 KiB
C++

/**
* @file core/tree/hrectbound_impl.hpp
*
* Implementation of hyper-rectangle bound policy class.
*
* 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.
*/
#ifndef MLPACK_CORE_TREE_HRECTBOUND_IMPL_HPP
#define MLPACK_CORE_TREE_HRECTBOUND_IMPL_HPP
#include <math.h>
// In case it has not been included yet.
#include "hrectbound.hpp"
namespace mlpack {
/**
* Empty constructor.
*/
template<typename DistanceType, typename ElemType>
inline HRectBound<DistanceType, ElemType>::HRectBound() :
dim(0),
bounds(NULL),
minWidth(0)
{ /* Nothing to do. */ }
/**
* Initializes to specified dimensionality with each dimension the empty
* set.
*/
template<typename DistanceType, typename ElemType>
inline HRectBound<DistanceType, ElemType>::HRectBound(const size_t dimension) :
dim(dimension),
bounds(new RangeType<ElemType>[dim]),
minWidth(0)
{ /* Nothing to do. */ }
/**
* Copy constructor necessary to prevent memory leaks.
*/
template<typename DistanceType, typename ElemType>
inline HRectBound<DistanceType, ElemType>::HRectBound(
const HRectBound<DistanceType, ElemType>& other) :
dim(other.Dim()),
bounds(new RangeType<ElemType>[dim]),
minWidth(other.MinWidth())
{
// Copy other bounds over.
for (size_t i = 0; i < dim; ++i)
bounds[i] = other[i];
}
/**
* Same as the copy constructor.
*/
template<typename DistanceType, typename ElemType>
inline HRectBound<
DistanceType,
ElemType>& HRectBound<DistanceType,
ElemType>::operator=(const HRectBound<DistanceType, ElemType>& other)
{
if (this == &other)
return *this;
if (dim != other.Dim())
{
// Reallocation is necessary.
if (bounds)
delete[] bounds;
dim = other.Dim();
bounds = new RangeType<ElemType>[dim];
}
// Now copy each of the bound values.
for (size_t i = 0; i < dim; ++i)
bounds[i] = other[i];
minWidth = other.MinWidth();
return *this;
}
/**
* Move constructor: take possession of another bound's information.
*/
template<typename DistanceType, typename ElemType>
inline HRectBound<DistanceType, ElemType>::HRectBound(
HRectBound<DistanceType, ElemType>&& other) :
dim(other.dim),
bounds(other.bounds),
minWidth(other.minWidth)
{
// Fix the other bound.
other.dim = 0;
other.bounds = NULL;
other.minWidth = 0.0;
}
/**
* Move assignment operator.
*/
template<typename DistanceType, typename ElemType>
inline HRectBound<DistanceType, ElemType>&
HRectBound<DistanceType, ElemType>::operator=(
HRectBound<DistanceType, ElemType>&& other)
{
if (this != &other)
{
bounds = other.bounds;
minWidth = other.minWidth;
dim = other.dim;
other.dim = 0;
other.bounds = nullptr;
other.minWidth = 0.0;
}
return *this;
}
/**
* Destructor: clean up memory.
*/
template<typename DistanceType, typename ElemType>
inline HRectBound<DistanceType, ElemType>::~HRectBound()
{
if (bounds)
delete[] bounds;
}
/**
* Resets all dimensions to the empty set.
*/
template<typename DistanceType, typename ElemType>
inline void HRectBound<DistanceType, ElemType>::Clear()
{
for (size_t i = 0; i < dim; ++i)
bounds[i] = RangeType<ElemType>();
minWidth = 0;
}
/***
* Calculates the centroid of the range, placing it into the given vector.
*
* @param centroid Vector which the centroid will be written to.
*/
template<typename DistanceType, typename ElemType>
inline void HRectBound<DistanceType, ElemType>::Center(
arma::Col<ElemType>& center) const
{
// Set size correctly if necessary.
if (!(center.n_elem == dim))
center.set_size(dim);
for (size_t i = 0; i < dim; ++i)
center(i) = bounds[i].Mid();
}
/**
* Calculate the volume of the hyperrectangle.
*
* @return Volume of the hyperrectangle.
*/
template<typename DistanceType, typename ElemType>
inline ElemType HRectBound<DistanceType, ElemType>::Volume() const
{
ElemType volume = 1.0;
for (size_t i = 0; i < dim; ++i)
{
if (bounds[i].Lo() >= bounds[i].Hi())
return 0;
volume *= (bounds[i].Hi() - bounds[i].Lo());
}
return volume;
}
/**
* Calculates minimum bound-to-point squared distance.
*/
template<typename DistanceType, typename ElemType>
template<typename VecType>
inline ElemType HRectBound<DistanceType, ElemType>::MinDistance(
const VecType& point,
typename std::enable_if_t<IsVector<VecType>::value>* /* junk */) const
{
Log::Assert(point.n_elem == dim);
ElemType sum = 0;
ElemType lower, higher;
for (size_t d = 0; d < dim; d++)
{
lower = bounds[d].Lo() - point[d];
higher = point[d] - bounds[d].Hi();
// Since only one of 'lower' or 'higher' is negative, if we add each's
// absolute value to itself and then sum those two, our result is the
// nonnegative half of the equation times two; then we raise to power Power.
if (DistanceType::Power == 1)
sum += (lower + std::fabs(lower)) + (higher + std::fabs(higher));
else if (DistanceType::Power == 2)
{
ElemType dist = (lower + std::fabs(lower)) + (higher + std::fabs(higher));
sum += dist * dist;
}
else
{
sum += std::pow((lower + std::fabs(lower)) + (higher + std::fabs(higher)),
(ElemType) DistanceType::Power);
}
}
// Now take the Power'th root (but make sure our result is squared if it needs
// to be); then cancel out the constant of 2 (which may have been squared now)
// that was introduced earlier. The compiler should optimize out the if
// statement entirely.
if (DistanceType::Power == 1)
return sum * 0.5;
else if (DistanceType::Power == 2)
{
if (DistanceType::TakeRoot)
return (ElemType) std::sqrt(sum) * 0.5;
else
return sum * 0.25;
}
else
{
if (DistanceType::TakeRoot)
return (ElemType) std::pow((double) sum,
1.0 / (double) DistanceType::Power) / 2.0;
else
return sum / std::pow(2.0, DistanceType::Power);
}
}
/**
* Calculates minimum bound-to-bound squared distance.
*/
template<typename DistanceType, typename ElemType>
ElemType HRectBound<DistanceType, ElemType>::MinDistance(const HRectBound& other)
const
{
Log::Assert(dim == other.dim);
ElemType sum = 0;
const RangeType<ElemType>* mbound = bounds;
const RangeType<ElemType>* obound = other.bounds;
ElemType lower, higher;
for (size_t d = 0; d < dim; d++)
{
lower = obound->Lo() - mbound->Hi();
higher = mbound->Lo() - obound->Hi();
// We invoke the following:
// x + fabs(x) = max(x * 2, 0)
// (x * 2)^2 / 4 = x^2
// The compiler should optimize out this if statement entirely.
if (DistanceType::Power == 1)
sum += (lower + std::fabs(lower)) + (higher + std::fabs(higher));
else if (DistanceType::Power == 2)
{
ElemType dist = (lower + std::fabs(lower)) + (higher + std::fabs(higher));
sum += dist * dist;
}
else
{
sum += std::pow((lower + std::fabs(lower)) + (higher + std::fabs(higher)),
(ElemType) DistanceType::Power);
}
// Move bound pointers.
mbound++;
obound++;
}
// The compiler should optimize out this if statement entirely.
if (DistanceType::Power == 1)
return sum * 0.5;
else if (DistanceType::Power == 2)
{
if (DistanceType::TakeRoot)
return (ElemType) std::sqrt(sum) * 0.5;
else
return sum * 0.25;
}
else
{
if (DistanceType::TakeRoot)
return (ElemType) std::pow((double) sum,
1.0 / (double) DistanceType::Power) / 2.0;
else
return sum / std::pow(2.0, DistanceType::Power);
}
}
/**
* Calculates maximum bound-to-point squared distance.
*/
template<typename DistanceType, typename ElemType>
template<typename VecType>
inline ElemType HRectBound<DistanceType, ElemType>::MaxDistance(
const VecType& point,
typename std::enable_if_t<IsVector<VecType>::value>* /* junk */) const
{
ElemType sum = 0;
Log::Assert(point.n_elem == dim);
for (size_t d = 0; d < dim; d++)
{
ElemType v = std::max(fabs(point[d] - bounds[d].Lo()),
fabs(bounds[d].Hi() - point[d]));
// The compiler should optimize out this if statement entirely.
if (DistanceType::Power == 1)
sum += v; // v is non-negative.
else if (DistanceType::Power == 2)
sum += v * v;
else
sum += std::pow(v, (ElemType) DistanceType::Power);
}
// The compiler should optimize out this if statement entirely.
if (DistanceType::TakeRoot)
{
if (DistanceType::Power == 1)
return sum;
else if (DistanceType::Power == 2)
return (ElemType) std::sqrt(sum);
else
return (ElemType) std::pow((double) sum, 1.0 /
(double) DistanceType::Power);
}
else
return sum;
}
/**
* Computes maximum distance.
*/
template<typename DistanceType, typename ElemType>
inline ElemType HRectBound<DistanceType, ElemType>::MaxDistance(
const HRectBound& other)
const
{
ElemType sum = 0;
Log::Assert(dim == other.dim);
ElemType v;
for (size_t d = 0; d < dim; d++)
{
v = std::max(fabs(other.bounds[d].Hi() - bounds[d].Lo()),
fabs(bounds[d].Hi() - other.bounds[d].Lo()));
// The compiler should optimize out this if statement entirely.
if (DistanceType::Power == 1)
sum += v; // v is non-negative.
else if (DistanceType::Power == 2)
sum += v * v;
else
sum += std::pow(v, (ElemType) DistanceType::Power);
}
// The compiler should optimize out this if statement entirely.
if (DistanceType::TakeRoot)
{
if (DistanceType::Power == 1)
return sum;
else if (DistanceType::Power == 2)
return (ElemType) std::sqrt(sum);
else
return (ElemType) std::pow((double) sum, 1.0 /
(double) DistanceType::Power);
}
else
return sum;
}
/**
* Calculates minimum and maximum bound-to-bound squared distance.
*/
template<typename DistanceType, typename ElemType>
inline RangeType<ElemType>
HRectBound<DistanceType, ElemType>::RangeDistance(
const HRectBound& other) const
{
ElemType loSum = 0;
ElemType hiSum = 0;
Log::Assert(dim == other.dim);
ElemType v1, v2, vLo, vHi;
for (size_t d = 0; d < dim; d++)
{
v1 = other.bounds[d].Lo() - bounds[d].Hi();
v2 = bounds[d].Lo() - other.bounds[d].Hi();
// One of v1 or v2 is negative.
if (v1 >= v2)
{
vHi = -v2; // Make it nonnegative.
vLo = (v1 > 0) ? v1 : 0; // Force to be 0 if negative.
}
else
{
vHi = -v1; // Make it nonnegative.
vLo = (v2 > 0) ? v2 : 0; // Force to be 0 if negative.
}
// The compiler should optimize out this if statement entirely.
if (DistanceType::Power == 1)
{
loSum += vLo; // vLo is non-negative.
hiSum += vHi; // vHi is non-negative.
}
else if (DistanceType::Power == 2)
{
loSum += vLo * vLo;
hiSum += vHi * vHi;
}
else
{
loSum += std::pow(vLo, (ElemType) DistanceType::Power);
hiSum += std::pow(vHi, (ElemType) DistanceType::Power);
}
}
if (DistanceType::TakeRoot)
{
if (DistanceType::Power == 1)
return RangeType<ElemType>(loSum, hiSum);
else if (DistanceType::Power == 2)
return RangeType<ElemType>((ElemType) std::sqrt(loSum),
(ElemType) std::sqrt(hiSum));
else
{
return RangeType<ElemType>(
(ElemType) std::pow((double) loSum, 1.0 / (double) DistanceType::Power),
(ElemType) std::pow((double) hiSum,
1.0 / (double) DistanceType::Power));
}
}
else
return RangeType<ElemType>(loSum, hiSum);
}
/**
* Calculates minimum and maximum bound-to-point squared distance.
*/
template<typename DistanceType, typename ElemType>
template<typename VecType>
inline RangeType<ElemType>
HRectBound<DistanceType, ElemType>::RangeDistance(
const VecType& point,
typename std::enable_if_t<IsVector<VecType>::value>* /* junk */) const
{
ElemType loSum = 0;
ElemType hiSum = 0;
Log::Assert(point.n_elem == dim);
ElemType v1, v2, vLo, vHi;
for (size_t d = 0; d < dim; d++)
{
v1 = bounds[d].Lo() - point[d]; // Negative if point[d] > lo.
v2 = point[d] - bounds[d].Hi(); // Negative if point[d] < hi.
// One of v1 or v2 (or both) is negative.
if (v1 >= 0) // point[d] <= bounds_[d].Lo().
{
vHi = -v2; // v2 will be larger but must be negated.
vLo = v1;
}
else // point[d] is between lo and hi, or greater than hi.
{
if (v2 >= 0)
{
vHi = -v1; // v1 will be larger, but must be negated.
vLo = v2;
}
else
{
vHi = -std::min(v1, v2); // Both are negative, but we need the larger.
vLo = 0;
}
}
// The compiler should optimize out this if statement entirely.
if (DistanceType::Power == 1)
{
loSum += vLo; // vLo is non-negative.
hiSum += vHi; // vHi is non-negative.
}
else if (DistanceType::Power == 2)
{
loSum += vLo * vLo;
hiSum += vHi * vHi;
}
else
{
loSum += std::pow(vLo, (ElemType) DistanceType::Power);
hiSum += std::pow(vHi, (ElemType) DistanceType::Power);
}
}
if (DistanceType::TakeRoot)
{
if (DistanceType::Power == 1)
return RangeType<ElemType>(loSum, hiSum);
else if (DistanceType::Power == 2)
return RangeType<ElemType>((ElemType) std::sqrt(loSum),
(ElemType) std::sqrt(hiSum));
else
{
return RangeType<ElemType>(
(ElemType) std::pow((double) loSum, 1.0 / (double) DistanceType::Power),
(ElemType) std::pow((double) hiSum,
1.0 / (double) DistanceType::Power));
}
}
else
return RangeType<ElemType>(loSum, hiSum);
}
/**
* Expands this region to include a new point.
*/
template<typename DistanceType, typename ElemType>
template<typename MatType>
inline HRectBound<DistanceType, ElemType>&
HRectBound<DistanceType, ElemType>::operator|=(const MatType& data)
{
Log::Assert(data.n_rows == dim);
arma::Col<ElemType> mins(min(data, 1));
arma::Col<ElemType> maxs(max(data, 1));
minWidth = std::numeric_limits<ElemType>::max();
for (size_t i = 0; i < dim; ++i)
{
bounds[i] |= RangeType<ElemType>(mins[i], maxs[i]);
const ElemType width = bounds[i].Width();
if (width < minWidth)
minWidth = width;
}
return *this;
}
/**
* Expands this region to encompass another bound.
*/
template<typename DistanceType, typename ElemType>
inline HRectBound<DistanceType, ElemType>&
HRectBound<DistanceType, ElemType>::operator|=(const HRectBound& other)
{
assert(other.dim == dim);
minWidth = std::numeric_limits<ElemType>::max();
for (size_t i = 0; i < dim; ++i)
{
bounds[i] |= other.bounds[i];
const ElemType width = bounds[i].Width();
if (width < minWidth)
minWidth = width;
}
return *this;
}
/**
* Determines if a point is within this bound.
*/
template<typename DistanceType, typename ElemType>
template<typename VecType>
inline bool HRectBound<DistanceType, ElemType>::Contains(
const VecType& point) const
{
for (size_t i = 0; i < point.n_elem; ++i)
{
if (!bounds[i].Contains(point(i)))
return false;
}
return true;
}
/**
* Determines if this bound partially contains a bound.
*/
template<typename DistanceType, typename ElemType>
inline bool HRectBound<DistanceType, ElemType>::Contains(
const HRectBound& bound) const
{
for (size_t i = 0; i < dim; ++i)
{
const RangeType<ElemType>& r_a = bounds[i];
const RangeType<ElemType>& r_b = bound.bounds[i];
// If a does not overlap b at all.
if (r_a.Hi() <= r_b.Lo() || r_a.Lo() >= r_b.Hi())
return false;
}
return true;
}
/**
* Returns the intersection of this bound and another.
*/
template<typename DistanceType, typename ElemType>
inline HRectBound<DistanceType, ElemType>
HRectBound<DistanceType, ElemType>::operator&(const HRectBound& bound) const
{
HRectBound<DistanceType, ElemType> result(dim);
for (size_t k = 0; k < dim; ++k)
{
result[k].Lo() = std::max(bounds[k].Lo(), bound.bounds[k].Lo());
result[k].Hi() = std::min(bounds[k].Hi(), bound.bounds[k].Hi());
}
return result;
}
/**
* Intersects this bound with another.
*/
template<typename DistanceType, typename ElemType>
inline HRectBound<DistanceType, ElemType>&
HRectBound<DistanceType, ElemType>::operator&=(const HRectBound& bound)
{
for (size_t k = 0; k < dim; ++k)
{
bounds[k].Lo() = std::max(bounds[k].Lo(), bound.bounds[k].Lo());
bounds[k].Hi() = std::min(bounds[k].Hi(), bound.bounds[k].Hi());
}
return *this;
}
/**
* Returns the volume of overlap of this bound and another.
*/
template<typename DistanceType, typename ElemType>
inline ElemType HRectBound<DistanceType, ElemType>::Overlap(
const HRectBound& bound) const
{
ElemType volume = 1.0;
for (size_t k = 0; k < dim; ++k)
{
ElemType lo = std::max(bounds[k].Lo(), bound.bounds[k].Lo());
ElemType hi = std::min(bounds[k].Hi(), bound.bounds[k].Hi());
if ( hi <= lo)
return 0;
volume *= hi - lo;
}
return volume;
}
/**
* Returns the diameter of the hyperrectangle (that is, the longest diagonal).
*/
template<typename DistanceType, typename ElemType>
inline ElemType HRectBound<DistanceType, ElemType>::Diameter() const
{
ElemType d = 0;
for (size_t i = 0; i < dim; ++i)
d += std::pow(bounds[i].Hi() - bounds[i].Lo(),
(ElemType) DistanceType::Power);
if (DistanceType::TakeRoot)
return (ElemType) std::pow((double) d, 1.0 / (double) DistanceType::Power);
else
return d;
}
//! Serialize the bound object.
template<typename DistanceType, typename ElemType>
template<typename Archive>
void HRectBound<DistanceType, ElemType>::serialize(
Archive& ar,
const uint32_t /* version */)
{
// We can't serialize a raw array directly, so wrap it.
ar(CEREAL_POINTER_ARRAY(bounds, dim));
ar(CEREAL_NVP(minWidth));
ar(CEREAL_NVP(distance));
}
} // namespace mlpack
#endif // MLPACK_CORE_TREE_HRECTBOUND_IMPL_HPP