Refactor RASearch to take queries in Search().

This commit is contained in:
ryan
2015-04-23 14:41:41 -04:00
parent 7983ad4061
commit da47b6ea24
5 changed files with 594 additions and 548 deletions
+68 -131
View File
@@ -8,13 +8,13 @@
#include <time.h>
#include <mlpack/core.hpp>
#include <mlpack/core/tree/cover_tree.hpp>
#include <string>
#include <fstream>
#include <iostream>
#include "ra_search.hpp"
#include <mlpack/methods/neighbor_search/unmap.hpp>
using namespace std;
using namespace mlpack;
@@ -68,8 +68,6 @@ PARAM_FLAG("naive", "If true, sampling will be done without using a tree.",
"N");
PARAM_FLAG("single_mode", "If true, single-tree search is used (as opposed to "
"dual-tree search.", "s");
PARAM_FLAG("cover_tree", "If true, use cover trees to perform the search.",
"c");
PARAM_FLAG("sample_at_leaves", "The flag to trigger sampling at leaves.", "L");
PARAM_FLAG("first_leaf_exact", "The flag to trigger sampling only after "
@@ -117,6 +115,15 @@ int main(int argc, char *argv[])
Log::Fatal << referenceData.n_cols << ")." << endl;
}
// Load query data, if necessary.
if (CLI::HasParam("query_file"))
{
const string queryFile = CLI::GetParam<string>("query_file");
data::Load(queryFile, queryData, true);
Log::Info << "Loaded query data from '" << queryFile << "' ("
<< queryData.n_rows << " x " << queryData.n_cols << ")." << endl;
}
// Sanity check on the value of 'tau' with respect to 'k' so that
// 'k' neighbors are not requested from the top-'rank_error' neighbors
// where 'rank_error' <= 'k'.
@@ -142,152 +149,82 @@ int main(int argc, char *argv[])
if (naive)
{
AllkRANN* allkrann;
AllkRANN allkrann(referenceData, naive, false, tau, alpha);
Log::Info << "Computing " << k << " nearest neighbors " << "with "
<< tau << "% rank approximation..." << endl;
if (CLI::GetParam<string>("query_file") != "")
{
string queryFile = CLI::GetParam<string>("query_file");
data::Load(queryFile, queryData, true);
Log::Info << "Loaded query data from '" << queryFile << "' (" <<
queryData.n_rows << " x " << queryData.n_cols << ")." << endl;
allkrann = new AllkRANN(referenceData, queryData, naive);
}
allkrann.Search(queryData, k, neighbors, distances);
else
allkrann = new AllkRANN(referenceData, naive);
Log::Info << "Computing " << k << " nearest neighbors " << "with " <<
tau << "% rank approximation..." << endl;
allkrann->Search(k, neighbors, distances, tau, alpha);
allkrann.Search(k, neighbors, distances);
Log::Info << "Neighbors computed." << endl;
delete allkrann;
}
else
{
// The results output by the AllkRANN class
// shuffled because the tree construction shuffles the point sets.
// The results output by the AllkRANN class are
// shuffled if the tree construction shuffles the point sets.
arma::Mat<size_t> neighborsOut;
arma::mat distancesOut;
if (!CLI::HasParam("cover_tree"))
// Mappings for when we build the tree.
std::vector<size_t> oldFromNewRefs;
std::vector<size_t> oldFromNewQueries;
// Build trees by hand, so we can save memory: if we pass a tree to
// NeighborSearch, it does not copy the matrix.
Log::Info << "Building reference tree..." << endl;
Timer::Start("tree_building");
typedef BinarySpaceTree<bound::HRectBound<2, false>,
RAQueryStat<NearestNeighborSort> > TreeType;
TreeType refTree(referenceData, oldFromNewRefs, leafSize);
Timer::Stop("tree_building");
// Because we may construct it differently, we need a pointer.
AllkRANN allkrann(&refTree, singleMode, tau, alpha, sampleAtLeaves,
firstLeafExact, singleSampleLimit);
if (CLI::HasParam("query_file") && !singleMode)
{
// Because we may construct it differently, we need a pointer.
AllkRANN* allkrann = NULL;
// Mappings for when we build the tree.
std::vector<size_t> oldFromNewRefs;
// Build trees by hand, so we can save memory: if we pass a tree to
// NeighborSearch, it does not copy the matrix.
Log::Info << "Building reference tree..." << endl;
Log::Info << "Building query tree..." << endl;
Timer::Start("tree_building");
BinarySpaceTree<bound::HRectBound<2, false>,
RAQueryStat<NearestNeighborSort> >
refTree(referenceData, oldFromNewRefs, leafSize);
BinarySpaceTree<bound::HRectBound<2, false>,
RAQueryStat<NearestNeighborSort> >*
queryTree = NULL; // Empty for now.
TreeType queryTree(queryData, oldFromNewQueries, leafSize);
Timer::Stop("tree_building");
std::vector<size_t> oldFromNewQueries;
if (CLI::GetParam<string>("query_file") != "")
{
string queryFile = CLI::GetParam<string>("query_file");
data::Load(queryFile, queryData, true);
if (naive && leafSize < queryData.n_cols)
leafSize = queryData.n_cols;
Log::Info << "Loaded query data from '" << queryFile << "' (" <<
queryData.n_rows << " x " << queryData.n_cols << ")." << endl;
Log::Info << "Building query tree..." << endl;
// Build trees by hand, so we can save memory: if we pass a tree to
// NeighborSearch, it does not copy the matrix.
Timer::Start("tree_building");
queryTree = new BinarySpaceTree<bound::HRectBound<2, false>,
RAQueryStat<NearestNeighborSort> >
(queryData, oldFromNewQueries, leafSize);
Timer::Stop("tree_building");
allkrann = new AllkRANN(&refTree, queryTree, referenceData, queryData,
singleMode);
Log::Info << "Tree built." << endl;
}
else
{
allkrann = new AllkRANN(&refTree, referenceData, singleMode);
Log::Info << "Trees built." << endl;
}
Log::Info << "Tree built." << endl;
Log::Info << "Computing " << k << " nearest neighbors " << "with " <<
tau << "% rank approximation..." << endl;
allkrann->Search(k, neighborsOut, distancesOut,
tau, alpha, sampleAtLeaves,
firstLeafExact, singleSampleLimit);
Log::Info << "Neighbors computed." << endl;
// We have to map back to the original indices from before the tree
// construction.
Log::Info << "Re-mapping indices..." << endl;
neighbors.set_size(neighborsOut.n_rows, neighborsOut.n_cols);
distances.set_size(distancesOut.n_rows, distancesOut.n_cols);
// Do the actual remapping.
if (CLI::GetParam<string>("query_file") != "")
{
for (size_t i = 0; i < distancesOut.n_cols; ++i)
{
// Map distances (copy a column).
distances.col(oldFromNewQueries[i]) = distancesOut.col(i);
// Map indices of neighbors.
for (size_t j = 0; j < distancesOut.n_rows; ++j)
{
neighbors(j, oldFromNewQueries[i])
= oldFromNewRefs[neighborsOut(j, i)];
}
}
}
else
{
for (size_t i = 0; i < distancesOut.n_cols; ++i)
{
// Map distances (copy a column).
distances.col(oldFromNewRefs[i]) = distancesOut.col(i);
// Map indices of neighbors.
for (size_t j = 0; j < distancesOut.n_rows; ++j)
{
neighbors(j, oldFromNewRefs[i])
= oldFromNewRefs[neighborsOut(j, i)];
}
}
}
// Clean up.
if (queryTree)
delete queryTree;
delete allkrann;
tau << "% rank approximation..." << endl;
allkrann.Search(&queryTree, k, neighborsOut, distancesOut);
}
else // Cover trees.
else if (CLI::HasParam("query_file") && singleMode)
{
Log::Fatal << "Cover tree case not implemented yet..." << endl;
Log::Info << "Computing " << k << " nearest neighbors " << "with " <<
tau << "% rank approximation..." << endl;
allkrann.Search(queryData, k, neighborsOut, distancesOut);
}
else
{
Log::Info << "Computing " << k << " nearest neighbors " << "with " <<
tau << "% rank approximation..." << endl;
allkrann.Search(k, neighborsOut, distancesOut);
}
Log::Info << "Neighbors computed." << endl;
// We have to map back to the original indices from before the tree
// construction.
Log::Info << "Re-mapping indices..." << endl;
// Map the results back to the correct places.
if ((CLI::GetParam<string>("query_file") != "") && !singleMode)
Unmap(neighborsOut, distancesOut, oldFromNewRefs, oldFromNewQueries,
neighbors, distances);
else if ((CLI::GetParam<string>("query_file") != "") && singleMode)
Unmap(neighborsOut, distancesOut, oldFromNewRefs, neighbors, distances);
else
Unmap(neighborsOut, distancesOut, oldFromNewRefs, oldFromNewRefs,
neighbors, distances);
}
// Save output.
+208 -176
View File
@@ -57,158 +57,38 @@ class RASearch
{
public:
/**
* Initialize the RASearch object, passing both a query and reference dataset.
* Optionally, perform the computation in naive mode or single-tree mode, and
* set the leaf size used for tree-building. An initialized distance metric
* can be given, for cases where the metric has internal data (i.e. the
* Initialize the RASearch object, passing both a reference dataset (this is
* the dataset that will be searched). Optionally, perform the computation in
* naive mode or single-tree mode. An initialized distance metric can be
* given, for cases where the metric has internal data (i.e. the
* distance::MahalanobisDistance class).
*
* This method will copy the matrices to internal copies, which are rearranged
* during tree-building. You can avoid this extra copy by pre-constructing
* the trees and passing them using a diferent constructor.
* the trees and passing them using a different constructor.
*
* tau, the rank-approximation parameter, specifies that we are looking for k
* neighbors with probability alpha of being in the top tau percent of nearest
* neighbors. So, as an example, if our dataset has 1000 points, and we want
* 5 nearest neighbors with 95% probability of being in the top 5% of nearest
* neighbors (or, the top 50 nearest neighbors), we set k = 5, tau = 5, and
* alpha = 0.95.
*
* The method will fail (and throw a std::invalid_argument exception) if the
* value of tau is too low: tau must be set such that the number of points in
* the corresponding percentile of the data is greater than k. Thus, if we
* choose tau = 0.1 with a dataset of 1000 points and k = 5, then we are
* attempting to choose 5 nearest neighbors out of the closest 1 point -- this
* is invalid.
*
* @param referenceSet Set of reference points.
* @param querySet Set of query points.
* @param naive If true, the rank-approximate search will be performed by
* directly sampling the whole set instead of using the stratified
* sampling on the tree.
* @param singleMode If true, single-tree search will be used (as opposed to
* dual-tree search).
* @param leafSize Leaf size for tree construction (ignored if tree is given).
* dual-tree search). This is useful when Search() will be called with
* few query points.
* @param metric An optional instance of the MetricType class.
*/
RASearch(const typename TreeType::Mat& referenceSet,
const typename TreeType::Mat& querySet,
const bool naive = false,
const bool singleMode = false,
const MetricType metric = MetricType());
/**
* Initialize the RASearch object, passing only one dataset, which is
* used as both the query and the reference dataset. Optionally, perform the
* computation in naive mode or single-tree mode, and set the leaf size used
* for tree-building. An initialized distance metric can be given, for cases
* where the metric has internal data (i.e. the distance::MahalanobisDistance
* class).
*
* If naive mode is being used and a pre-built tree is given, it may not work:
* naive mode operates by building a one-node tree (the root node holds all
* the points). If that condition is not satisfied with the pre-built tree,
* then naive mode will not work.
*
* @param referenceSet Set of reference points.
* @param naive If true, the rank-approximate search will be performed
* by directly sampling the whole set instead of using the stratified
* sampling on the tree.
* @param singleMode If true, single-tree search will be used (as opposed to
* dual-tree search).
* @param leafSize Leaf size for tree construction (ignored if tree is given).
* @param metric An optional instance of the MetricType class.
*/
RASearch(const typename TreeType::Mat& referenceSet,
const bool naive = false,
const bool singleMode = false,
const MetricType metric = MetricType());
/**
* Initialize the RASearch object with the given datasets and
* pre-constructed trees. It is assumed that the points in referenceSet and
* querySet correspond to the points in referenceTree and queryTree,
* respectively. Optionally, choose to use single-tree mode. Naive mode is
* not available as an option for this constructor; instead, to run naive
* computation, construct a tree with all of the points in one leaf (i.e.
* leafSize = number of points). Additionally, an instantiated distance
* metric can be given, for cases where the distance metric holds data.
*
* There is no copying of the data matrices in this constructor (because
* tree-building is not necessary), so this is the constructor to use when
* copies absolutely must be avoided.
*
* @note
* Because tree-building (at least with BinarySpaceTree) modifies the ordering
* of a matrix, be sure you pass the modified matrix to this object! In
* addition, mapping the points of the matrix back to their original indices
* is not done when this constructor is used.
* @endnote
*
* @param referenceTree Pre-built tree for reference points.
* @param queryTree Pre-built tree for query points.
* @param referenceSet Set of reference points corresponding to referenceTree.
* @param querySet Set of query points corresponding to queryTree.
* @param singleMode Whether single-tree computation should be used (as
* opposed to dual-tree computation).
* @param metric Instantiated distance metric.
*/
RASearch(TreeType* referenceTree,
TreeType* queryTree,
const typename TreeType::Mat& referenceSet,
const typename TreeType::Mat& querySet,
const bool singleMode = false,
const MetricType metric = MetricType());
/**
* Initialize the RASearch object with the given reference dataset and
* pre-constructed tree. It is assumed that the points in referenceSet
* correspond to the points in referenceTree. Optionally, choose to use
* single-tree mode. Naive mode is not available as an option for this
* constructor; instead, to run naive computation, construct a tree with all
* the points in one leaf (i.e. leafSize = number of points). Additionally,
* an instantiated distance metric can be given, for the case where the
* distance metric holds data.
*
* There is no copying of the data matrices in this constructor (because
* tree-building is not necessary), so this is the constructor to use when
* copies absolutely must be avoided.
*
* @note
* Because tree-building (at least with BinarySpaceTree) modifies the ordering
* of a matrix, be sure you pass the modified matrix to this object! In
* addition, mapping the points of the matrix back to their original indices
* is not done when this constructor is used.
* @endnote
*
* @param referenceTree Pre-built tree for reference points.
* @param referenceSet Set of reference points corresponding to referenceTree.
* @param singleMode Whether single-tree computation should be used (as
* opposed to dual-tree computation).
* @param metric Instantiated distance metric.
*/
RASearch(TreeType* referenceTree,
const typename TreeType::Mat& referenceSet,
const bool singleMode = false,
const MetricType metric = MetricType());
/**
* Delete the RASearch object. The tree is the only member we are
* responsible for deleting. The others will take care of themselves.
*/
~RASearch();
/**
* Compute the rank approximate nearest neighbors and store the output in the
* given matrices. The matrices will be set to the size of n columns by k
* rows, where n is the number of points in the query dataset and k is the
* number of neighbors being searched for.
*
* Note that tau, the rank-approximation parameter, specifies that we are
* looking for k neighbors with probability alpha of being in the top tau
* percent of nearest neighbors. So, as an example, if our dataset has 1000
* points, and we want 5 nearest neighbors with 95% probability of being in
* the top 5% of nearest neighbors (or, the top 50 nearest neighbors), we set
* k = 5, tau = 5, and alpha = 0.95.
*
* The method will fail (and issue a failure message) if the value of tau is
* too low: tau must be set such that the number of points in the
* corresponding percentile of the data is greater than k. Thus, if we choose
* tau = 0.1 with a dataset of 1000 points and k = 5, then we are attempting
* to choose 5 nearest neighbors out of the closest 1 point -- this is
* invalid.
*
* @param k Number of neighbors to search for.
* @param resultingNeighbors Matrix storing lists of neighbors for each query
* point.
* @param distances Matrix storing distances of neighbors for each query
* point.
* @param tau The rank-approximation in percentile of the data. The default
* value is 5%.
* @param alpha The desired success probability. The default value is 0.95.
@@ -220,70 +100,222 @@ class RASearch
* @param singleSampleLimit The limit on the largest node that can be
* approximated by sampling. This defaults to 20.
*/
void Search(const size_t k,
arma::Mat<size_t>& resultingNeighbors,
arma::mat& distances,
const double tau = 5,
const double alpha = 0.95,
const bool sampleAtLeaves = false,
const bool firstLeafExact = false,
const size_t singleSampleLimit = 20);
RASearch(const typename TreeType::Mat& referenceSet,
const bool naive = false,
const bool singleMode = false,
const double tau = 5,
const double alpha = 0.95,
const bool sampleAtLeaves = false,
const bool firstLeafExact = false,
const size_t singleSampleLimit = 20,
const MetricType metric = MetricType());
/**
* This function recursively resets the RAQueryStat of the queryTree to set
* 'bound' to WorstDistance and the 'numSamplesMade' to 0. This allows a user
* to perform multiple searches on the same pair of trees, possibly with
* different levels of approximation without requiring to build a new pair of
* trees for every new (approximate) search.
* Initialize the RASearch object with the given pre-constructed reference
* tree. It is assumed that the points in the tree's dataset correspond to
* the reference set. Optionally, choose to use single-tree mode. Naive mode
* is not available as an option for this constructor; instead, to run naive
* computation, use a different constructor. Additionally, an instantiated
* distance metric can be given, for cases where the distance metric holds
* data.
*
* There is no copying of the data matrices in this constructor (because
* tree-building is not necessary), so this is the constructor to use when
* copies absolutely must be avoided.
*
* tau, the rank-approximation parameter, specifies that we are looking for k
* neighbors with probability alpha of being in the top tau percent of nearest
* neighbors. So, as an example, if our dataset has 1000 points, and we want
* 5 nearest neighbors with 95% probability of being in the top 5% of nearest
* neighbors (or, the top 50 nearest neighbors), we set k = 5, tau = 5, and
* alpha = 0.95.
*
* The method will fail (and throw a std::invalid_argument exception) if the
* value of tau is too low: tau must be set such that the number of points in
* the corresponding percentile of the data is greater than k. Thus, if we
* choose tau = 0.1 with a dataset of 1000 points and k = 5, then we are
* attempting to choose 5 nearest neighbors out of the closest 1 point -- this
* is invalid.
*
* @note
* Tree-building may (at least with BinarySpaceTree) modify the ordering
* of a matrix, so be aware that the results you get from Search() will
* correspond to the modified matrix.
* @endnote
*
* @param referenceTree Pre-built tree for reference points.
* @param singleMode Whether single-tree computation should be used (as
* opposed to dual-tree computation).
* @param metric Instantiated distance metric.
* @param tau The rank-approximation in percentile of the data. The default
* value is 5%.
* @param alpha The desired success probability. The default value is 0.95.
* @param sampleAtLeaves Sample at leaves for faster but less accurate
* computation. This defaults to 'false'.
* @param firstLeafExact Traverse to the first leaf without approximation.
* This can ensure that the query definitely finds its (near) duplicate
* if there exists one. This defaults to 'false' for now.
* @param singleSampleLimit The limit on the largest node that can be
* approximated by sampling. This defaults to 20.
*/
void ResetQueryTree();
RASearch(TreeType* referenceTree,
const bool singleMode = false,
const double tau = 5,
const double alpha = 0.95,
const bool sampleAtLeaves = false,
const bool firstLeafExact = false,
const size_t singleSampleLimit = 20,
const MetricType metric = MetricType());
// Returns a string representation of this object.
/**
* Delete the RASearch object. The tree is the only member we are
* responsible for deleting. The others will take care of themselves.
*/
~RASearch();
/**
* Compute the rank approximate nearest neighbors of each query point in the
* query set and store the output in the given matrices. The matrices will be
* set to the size of n columns by k rows, where n is the number of points in
* the query dataset and k is the number of neighbors being searched for.
*
* If querySet is small or only contains one point, it can be faster to do
* single-tree search; single-tree search can be set with the SingleMode()
* function or in the constructor.
*
* @param querySet Set of query points (can be a single point).
* @param k Number of neighbors to search for.
* @param neighbors Matrix storing lists of neighbors for each query point.
* @param distances Matrix storing distances of neighbors for each query
* point.
*/
void Search(const typename TreeType::Mat& querySet,
const size_t k,
arma::Mat<size_t>& neighbors,
arma::mat& distances);
/**
* Compute the rank approximate nearest neighbors of each point in the
* pre-built query tree and store the output in the given matrices. The
* matrices will be set to the size of n columns by k rows, where n is the
* number of points in the query dataset and k is the number of neighbors
* being searched for.
*
* If singleMode or naive is enabled, then this method will throw a
* std::invalid_argument exception; calling this function implies a dual-tree
* algorithm.
*
* @note
* If the tree type you are using modifies the data matrix, be aware that the
* results returned from this function will be with respect to the modified
* data matrix.
* @endnote
*
* @param queryTree Tree built on query points.
* @param k Number of neighbors to search for.
* @param neighbors Matrix storing lists of neighbors for each query point.
* @param distances Matrix storing distances of neighbors for each query
* point.
*/
void Search(TreeType* queryTree,
const size_t k,
arma::Mat<size_t>& neighbors,
arma::mat& distances);
/**
* Compute the rank approximate nearest neighbors of each point in the
* reference set (that is, the query set is taken to be the reference set),
* and store the output in the given matrices. The matrices will be set to
* the size of n columns by k rows, where n is the number of points in the
* query dataset and k is the number of neighbors being searched for.
*
* @param k Number of neighbors to search for.
* @param neighbors Matrix storing lists of neighbors for each point.
* @param distances Matrix storing distances of neighbors for each query
* point.
*/
void Search(const size_t k,
arma::Mat<size_t>& neighbors,
arma::mat& distances);
/**
* This function recursively resets the RAQueryStat of the given query tree to
* set 'bound' to SortPolicy::WorstDistance and 'numSamplesMade' to 0. This
* allows a user to perform multiple searches with the same query tree,
* possibly with different levels of approximation without requiring to build
* a new pair of trees for every new (approximate) search.
*
* If Search() is called multiple times with the same query tree without
* calling ResetQueryTree(), the results may not satisfy the theoretical
* guarantees provided by the rank-approximate neighbor search algorithm.
*
* @param queryTree Tree whose statistics should be reset.
*/
void ResetQueryTree(TreeType* queryTree) const;
//! Get the rank-approximation in percentile of the data.
double Tau() const { return tau; }
//! Modify the rank-approximation in percentile of the data.
double& Tau() { return tau; }
//! Get the desired success probability.
double Alpha() const { return alpha; }
//! Modify the desired success probability.
double& Alpha() { return alpha; }
//! Get whether or not sampling is done at the leaves.
bool SampleAtLeaves() const { return sampleAtLeaves; }
//! Modify whether or not sampling is done at the leaves.
bool& SampleAtLeaves() { return sampleAtLeaves; }
//! Get whether or not we traverse to the first leaf without approximation.
bool FirstLeafExact() const { return firstLeafExact; }
//! Modify whether or not we traverse to the first leaf without approximation.
bool& FirstLeafExact() { return firstLeafExact; }
//! Get the limit on the size of a node that can be approximated.
size_t SingleSampleLimit() const { return singleSampleLimit; }
//! Modify the limit on the size of a node that can be approximation.
size_t& SingleSampleLimit() { return singleSampleLimit; }
//! Returns a string representation of this object.
std::string ToString() const;
private:
//! Copy of reference dataset (if we need it, because tree building modifies
//! it).
arma::mat referenceCopy;
//! Copy of query dataset (if we need it, because tree building modifies it).
arma::mat queryCopy;
//! Reference dataset.
const arma::mat& referenceSet;
//! Query dataset (may not be given).
const arma::mat& querySet;
//! Pointer to the root of the reference tree.
TreeType* referenceTree;
//! Pointer to the root of the query tree (might not exist).
TreeType* queryTree;
//! If true, this object created the trees and is responsible for them.
bool treeOwner;
//! Indicates if a separate query set was passed.
bool hasQuerySet;
//! Indicates if naive random sampling on the set is being used.
bool naive;
//! Indicates if single-tree search is being used (opposed to dual-tree).
bool singleMode;
//! The rank-approximation in percentile of the data (between 0 and 100).
double tau;
//! The desired success probability (between 0 and 1).
double alpha;
//! Whether or not sampling is done at the leaves. Faster, but less accurate.
bool sampleAtLeaves;
//! If true, we will traverse to the first leaf without approximation.
bool firstLeafExact;
//! The limit on the number of points in the largest node that can be
//! approximated by sampling.
size_t singleSampleLimit;
//! Instantiation of kernel.
MetricType metric;
//! Permutations of reference points during tree building.
std::vector<size_t> oldFromNewReferences;
//! Permutations of query points during tree building.
std::vector<size_t> oldFromNewQueries;
//! Total number of pruned nodes during the neighbor search.
size_t numberOfPrunes;
/**
* @param treeNode The node of the tree whose RAQueryStat is reset
* and whose children are to be explored recursively.
*/
void ResetRAQueryStat(TreeType* treeNode);
}; // class RASearch
}; // namespace neighbor
+301 -228
View File
@@ -41,53 +41,7 @@ TreeType* BuildTree(
return new TreeType(dataset);
}
}; // namespace aux
// Construct the object.
template<typename SortPolicy, typename MetricType, typename TreeType>
RASearch<SortPolicy, MetricType, TreeType>::
RASearch(const typename TreeType::Mat& referenceSetIn,
const typename TreeType::Mat& querySetIn,
const bool naive,
const bool singleMode,
const MetricType metric) :
referenceSet(tree::TreeTraits<TreeType>::RearrangesDataset ? referenceCopy :
referenceSetIn),
querySet((tree::TreeTraits<TreeType>::RearrangesDataset && !singleMode) ?
queryCopy : querySetIn),
referenceTree(NULL),
queryTree(NULL),
treeOwner(!naive),
hasQuerySet(true),
naive(naive),
singleMode(!naive && singleMode), // No single mode if naive.
metric(metric),
numberOfPrunes(0)
{
// We'll time tree building.
Timer::Start("tree_building");
if (tree::TreeTraits<TreeType>::RearrangesDataset)
{
referenceCopy = referenceSetIn;
if (!singleMode)
queryCopy = querySetIn;
}
// Construct as a naive object if we need to.
if (!naive)
{
referenceTree = aux::BuildTree<TreeType>(const_cast<typename
TreeType::Mat&>(referenceSet), oldFromNewReferences);
if (!singleMode)
queryTree = aux::BuildTree<TreeType>(const_cast<typename
TreeType::Mat&>(querySet), oldFromNewQueries);
}
// Stop the timer we started above.
Timer::Stop("tree_building");
}
} // namespace aux
// Construct the object.
template<typename SortPolicy, typename MetricType, typename TreeType>
@@ -95,30 +49,37 @@ RASearch<SortPolicy, MetricType, TreeType>::
RASearch(const typename TreeType::Mat& referenceSetIn,
const bool naive,
const bool singleMode,
const double tau,
const double alpha,
const bool sampleAtLeaves,
const bool firstLeafExact,
const size_t singleSampleLimit,
const MetricType metric) :
referenceSet(tree::TreeTraits<TreeType>::RearrangesDataset ? referenceCopy :
referenceSetIn),
querySet(tree::TreeTraits<TreeType>::RearrangesDataset && !singleMode ?
referenceCopy : referenceSetIn),
referenceSet((tree::TreeTraits<TreeType>::RearrangesDataset && !naive)
? referenceCopy : referenceSetIn),
referenceTree(NULL),
queryTree(NULL),
treeOwner(!naive),
hasQuerySet(false),
naive(naive),
singleMode(!naive && singleMode), // No single mode if naive.
metric(metric),
numberOfPrunes(0)
tau(tau),
alpha(alpha),
sampleAtLeaves(sampleAtLeaves),
firstLeafExact(firstLeafExact),
singleSampleLimit(singleSampleLimit),
metric(metric)
{
// We'll time tree building.
Timer::Start("tree_building");
if (tree::TreeTraits<TreeType>::RearrangesDataset)
referenceCopy = referenceSetIn;
// Construct as a naive object if we need to.
if (!naive)
referenceTree = aux::BuildTree<TreeType>(const_cast<typename
TreeType::Mat&>(referenceSet), oldFromNewReferences);
{
if (tree::TreeTraits<TreeType>::RearrangesDataset)
referenceCopy = referenceSetIn;
referenceTree = aux::BuildTree<TreeType>(
const_cast<typename TreeType::Mat&>(referenceSet),
oldFromNewReferences);
}
// Stop the timer we started above.
Timer::Stop("tree_building");
@@ -128,44 +89,27 @@ RASearch(const typename TreeType::Mat& referenceSetIn,
template<typename SortPolicy, typename MetricType, typename TreeType>
RASearch<SortPolicy, MetricType, TreeType>::
RASearch(TreeType* referenceTree,
TreeType* queryTree,
const typename TreeType::Mat& referenceSet,
const typename TreeType::Mat& querySet,
const bool singleMode,
const double tau,
const double alpha,
const bool sampleAtLeaves,
const bool firstLeafExact,
const size_t singleSampleLimit,
const MetricType metric) :
referenceSet(referenceSet),
querySet(querySet),
referenceSet(referenceTree->Dataset()),
referenceTree(referenceTree),
queryTree(queryTree),
treeOwner(false),
hasQuerySet(true),
naive(false),
singleMode(singleMode),
metric(metric),
numberOfPrunes(0)
tau(tau),
alpha(alpha),
sampleAtLeaves(sampleAtLeaves),
firstLeafExact(firstLeafExact),
singleSampleLimit(singleSampleLimit),
metric(metric)
// Nothing else to initialize.
{ }
// Construct the object.
template<typename SortPolicy, typename MetricType, typename TreeType>
RASearch<SortPolicy, MetricType, TreeType>::
RASearch(TreeType* referenceTree,
const typename TreeType::Mat& referenceSet,
const bool singleMode,
const MetricType metric) :
referenceSet(referenceSet),
querySet(referenceSet),
referenceTree(referenceTree),
queryTree(NULL),
treeOwner(false),
hasQuerySet(false),
naive(false),
singleMode(singleMode),
metric(metric),
numberOfPrunes(0)
// Nothing else to initialize.
{ }
/**
* The tree is the only member we may be responsible for deleting. The others
* will take care of themselves.
@@ -174,13 +118,8 @@ template<typename SortPolicy, typename MetricType, typename TreeType>
RASearch<SortPolicy, MetricType, TreeType>::
~RASearch()
{
if (treeOwner)
{
if (referenceTree)
delete referenceTree;
if (queryTree)
delete queryTree;
}
if (treeOwner && referenceTree)
delete referenceTree;
}
/**
@@ -189,30 +128,30 @@ RASearch<SortPolicy, MetricType, TreeType>::
*/
template<typename SortPolicy, typename MetricType, typename TreeType>
void RASearch<SortPolicy, MetricType, TreeType>::
Search(const size_t k,
arma::Mat<size_t>& resultingNeighbors,
arma::mat& distances,
const double tau,
const double alpha,
const bool sampleAtLeaves,
const bool firstLeafExact,
const size_t singleSampleLimit)
Search(const typename TreeType::Mat& querySet,
const size_t k,
arma::Mat<size_t>& neighbors,
arma::mat& distances)
{
Timer::Start("computing_neighbors");
// This will hold mappings for query points, if necessary.
std::vector<size_t> oldFromNewQueries;
// If we have built the trees ourselves, then we will have to map all the
// indices back to their original indices when this computation is finished.
// To avoid an extra copy, we will store the neighbors and distances in a
// separate matrix.
arma::Mat<size_t>* neighborPtr = &resultingNeighbors;
arma::Mat<size_t>* neighborPtr = &neighbors;
arma::mat* distancePtr = &distances;
// Mapping is only required if this tree type rearranges points and we are not
// in naive mode.
if (tree::TreeTraits<TreeType>::RearrangesDataset)
{
if (treeOwner && !(singleMode && hasQuerySet))
if (!singleMode && !naive)
distancePtr = new arma::mat; // Query indices need to be mapped.
if (treeOwner)
neighborPtr = new arma::Mat<size_t>; // All indices need mapping.
}
@@ -222,18 +161,25 @@ Search(const size_t k,
distancePtr->set_size(k, querySet.n_cols);
distancePtr->fill(SortPolicy::WorstDistance());
size_t numPrunes = 0;
// If we will be building a tree and it will modify the query set, make a copy
// of the dataset.
typename TreeType::Mat queryCopy;
const bool needsCopy = (!naive && !singleMode &&
tree::TreeTraits<TreeType>::RearrangesDataset);
if (needsCopy)
queryCopy = querySet;
const typename TreeType::Mat& querySetRef = (needsCopy) ? queryCopy :
querySet;
// Create the helper object for the tree traversal.
typedef RASearchRules<SortPolicy, MetricType, TreeType> RuleType;
RuleType rules(referenceSet, querySetRef, *neighborPtr, *distancePtr,
metric, tau, alpha, naive, sampleAtLeaves, firstLeafExact,
singleSampleLimit, false);
if (naive)
{
// We don't need to run the base case on every possible combination of
// points; we can achieve the rank approximation guarantee with probability
// alpha by sampling the reference set.
typedef RASearchRules<SortPolicy, MetricType, TreeType> RuleType;
RuleType rules(referenceSet, querySet, *neighborPtr, *distancePtr,
metric, tau, alpha, naive, sampleAtLeaves, firstLeafExact,
singleSampleLimit);
// Find how many samples from the reference set we need and sample uniformly
// from the reference set without replacement.
const size_t numSamples = rules.MinimumSamplesReqd(referenceSet.n_cols, k,
@@ -244,19 +190,12 @@ Search(const size_t k,
// Run the base case on each combination of query point and sampled
// reference point.
for (size_t i = 0; i < querySet.n_cols; ++i)
for (size_t i = 0; i < querySetRef.n_cols; ++i)
for (size_t j = 0; j < distinctSamples.n_elem; ++j)
rules.BaseCase(i, (size_t) distinctSamples[j]);
}
else if (singleMode)
{
// Create the helper object for the tree traversal. Initialization of
// RASearchRules already implicitly performs the naive tree traversal.
typedef RASearchRules<SortPolicy, MetricType, TreeType> RuleType;
RuleType rules(referenceSet, querySet, *neighborPtr, *distancePtr,
metric, tau, alpha, naive, sampleAtLeaves, firstLeafExact,
singleSampleLimit);
// If the reference root node is a leaf, then the sampling has already been
// done in the RASearchRules constructor. This happens when naive = true.
if (!referenceTree->IsLeaf())
@@ -268,11 +207,9 @@ Search(const size_t k,
traverser(rules);
// Now have it traverse for each point.
for (size_t i = 0; i < querySet.n_cols; ++i)
for (size_t i = 0; i < querySetRef.n_cols; ++i)
traverser.Traverse(i, *referenceTree);
numPrunes = traverser.NumPrunes();
Log::Info << "Single-tree traversal complete." << std::endl;
Log::Info << "Average number of distance calculations per query point: "
<< (rules.NumDistComputations() / querySet.n_cols) << "."
@@ -283,27 +220,20 @@ Search(const size_t k,
{
Log::Info << "Performing dual-tree traversal..." << std::endl;
typedef RASearchRules<SortPolicy, MetricType, TreeType> RuleType;
RuleType rules(referenceSet, querySet, *neighborPtr, *distancePtr,
metric, tau, alpha, sampleAtLeaves, firstLeafExact,
singleSampleLimit);
// Build the query tree.
Timer::Stop("computing_neighbors");
Timer::Start("tree_building");
TreeType* queryTree = aux::BuildTree<TreeType>(
const_cast<typename TreeType::Mat&>(querySetRef), oldFromNewQueries);
Timer::Stop("tree_building");
Timer::Start("computing_neighbors");
typename TreeType::template DualTreeTraverser<RuleType> traverser(rules);
if (queryTree)
{
Log::Info << "Query statistic pre-search: "
<< queryTree->Stat().NumSamplesMade() << std::endl;
traverser.Traverse(*queryTree, *referenceTree);
}
else
{
Log::Info << "Query statistic pre-search: "
<< referenceTree->Stat().NumSamplesMade() << std::endl;
traverser.Traverse(*referenceTree, *referenceTree);
}
Log::Info << "Query statistic pre-search: "
<< queryTree->Stat().NumSamplesMade() << std::endl;
numPrunes = traverser.NumPrunes();
traverser.Traverse(*queryTree, *referenceTree);
Log::Info << "Dual-tree traversal complete." << std::endl;
Log::Info << "Average number of distance calculations per query point: "
@@ -311,114 +241,257 @@ Search(const size_t k,
}
Timer::Stop("computing_neighbors");
Log::Info << "Pruned " << numPrunes << " nodes." << std::endl;
// Now, do we need to do mapping of indices?
if (!treeOwner || !tree::TreeTraits<TreeType>::RearrangesDataset)
// Map points back to original indices, if necessary.
if (tree::TreeTraits<TreeType>::RearrangesDataset)
{
// No mapping needed. We are done.
return;
}
else if (treeOwner && hasQuerySet && !singleMode) // Map both sets.
{
// Set size of output matrices correctly.
resultingNeighbors.set_size(k, querySet.n_cols);
distances.set_size(k, querySet.n_cols);
for (size_t i = 0; i < distances.n_cols; i++)
if (!singleMode && !naive && treeOwner)
{
// Map distances (copy a column).
distances.col(oldFromNewQueries[i]) = distancePtr->col(i);
// We must map both query and reference indices.
neighbors.set_size(k, querySet.n_cols);
distances.set_size(k, querySet.n_cols);
for (size_t i = 0; i < distances.n_cols; i++)
{
// Map distances (copy a column).
distances.col(oldFromNewQueries[i]) = distancePtr->col(i);
// Map indices of neighbors.
for (size_t j = 0; j < distances.n_rows; j++)
{
neighbors(j, oldFromNewQueries[i]) =
oldFromNewReferences[(*neighborPtr)(j, i)];
}
}
// Finished with temporary matrices.
delete neighborPtr;
delete distancePtr;
}
else if (!singleMode && !naive)
{
// We must map query indices only.
neighbors.set_size(k, querySet.n_cols);
distances.set_size(k, querySet.n_cols);
for (size_t i = 0; i < distances.n_cols; ++i)
{
// Map distances (copy a column).
const size_t queryMapping = oldFromNewQueries[i];
distances.col(queryMapping) = distancePtr->col(i);
neighbors.col(queryMapping) = neighborPtr->col(i);
}
// Finished with temporary matrices.
delete neighborPtr;
delete distancePtr;
}
else if (treeOwner)
{
// We must map reference indices only.
neighbors.set_size(k, querySet.n_cols);
// Map indices of neighbors.
for (size_t j = 0; j < distances.n_rows; j++)
{
resultingNeighbors(j, oldFromNewQueries[i]) =
oldFromNewReferences[(*neighborPtr)(j, i)];
}
for (size_t i = 0; i < neighbors.n_cols; i++)
for (size_t j = 0; j < neighbors.n_rows; j++)
neighbors(j, i) = oldFromNewReferences[(*neighborPtr)(j, i)];
// Finished with temporary matrix.
delete neighborPtr;
}
}
}
template<typename SortPolicy, typename MetricType, typename TreeType>
void RASearch<SortPolicy, MetricType, TreeType>::Search(
TreeType* queryTree,
const size_t k,
arma::Mat<size_t>& neighbors,
arma::mat& distances)
{
Timer::Start("computing_neighbors");
// Get a reference to the query set.
const typename TreeType::Mat& querySet = queryTree->Dataset();
// Make sure we are in dual-tree mode.
if (singleMode || naive)
throw std::invalid_argument("cannot call NeighborSearch::Search() with a "
"query tree when naive or singleMode are set to true");
// We won't need to map query indices, but will we need to map distances?
arma::Mat<size_t>* neighborPtr = &neighbors;
if (treeOwner && tree::TreeTraits<TreeType>::RearrangesDataset)
neighborPtr = new arma::Mat<size_t>;
neighborPtr->set_size(k, querySet.n_cols);
neighborPtr->fill(size_t() - 1);
distances.set_size(k, querySet.n_cols);
distances.fill(SortPolicy::WorstDistance());
// Create the helper object for the tree traversal.
typedef RASearchRules<SortPolicy, MetricType, TreeType> RuleType;
RuleType rules(referenceSet, queryTree->Dataset(), *neighborPtr, distances,
metric, tau, alpha, naive, sampleAtLeaves, firstLeafExact,
singleSampleLimit, false);
// Create the traverser.
typename TreeType::template DualTreeTraverser<RuleType> traverser(rules);
traverser.Traverse(*queryTree, *referenceTree);
Timer::Stop("computing_neighbors");
// Do we need to map indices?
if (treeOwner && tree::TreeTraits<TreeType>::RearrangesDataset)
{
// We must map reference indices only.
neighbors.set_size(k, querySet.n_cols);
// Map indices of neighbors.
for (size_t i = 0; i < neighbors.n_cols; i++)
for (size_t j = 0; j < neighbors.n_rows; j++)
neighbors(j, i) = oldFromNewReferences[(*neighborPtr)(j, i)];
// Finished with temporary matrix.
delete neighborPtr;
}
}
template<typename SortPolicy, typename MetricType, typename TreeType>
void RASearch<SortPolicy, MetricType, TreeType>::Search(
const size_t k,
arma::Mat<size_t>& neighbors,
arma::mat& distances)
{
Timer::Start("computing_neighbors");
arma::Mat<size_t>* neighborPtr = &neighbors;
arma::mat* distancePtr = &distances;
if (tree::TreeTraits<TreeType>::RearrangesDataset && treeOwner)
{
// We will always need to rearrange in this case.
distancePtr = new arma::mat;
neighborPtr = new arma::Mat<size_t>;
}
// Initialize results.
neighborPtr->set_size(k, referenceSet.n_cols);
neighborPtr->fill(size_t() - 1);
distancePtr->set_size(k, referenceSet.n_cols);
distancePtr->fill(SortPolicy::WorstDistance());
// Create the helper object for the tree traversal.
typedef RASearchRules<SortPolicy, MetricType, TreeType> RuleType;
RuleType rules(referenceSet, referenceSet, *neighborPtr, *distancePtr,
metric, tau, alpha, naive, sampleAtLeaves, firstLeafExact,
singleSampleLimit, true /* sets are the same */);
if (naive)
{
// Find how many samples from the reference set we need and sample uniformly
// from the reference set without replacement.
const size_t numSamples = rules.MinimumSamplesReqd(referenceSet.n_cols, k,
tau, alpha);
arma::uvec distinctSamples;
rules.ObtainDistinctSamples(numSamples, referenceSet.n_cols,
distinctSamples);
// The naive brute-force solution.
for (size_t i = 0; i < referenceSet.n_cols; ++i)
for (size_t j = 0; j < referenceSet.n_cols; ++j)
rules.BaseCase(i, j);
}
else if (singleMode)
{
// Create the traverser.
typename TreeType::template SingleTreeTraverser<RuleType> traverser(rules);
// Now have it traverse for each point.
for (size_t i = 0; i < referenceSet.n_cols; ++i)
traverser.Traverse(i, *referenceTree);
}
else
{
// Create the traverser.
typename TreeType::template DualTreeTraverser<RuleType> traverser(rules);
traverser.Traverse(*referenceTree, *referenceTree);
}
Timer::Stop("computing_neighbors");
// Do we need to map the reference indices?
if (treeOwner && tree::TreeTraits<TreeType>::RearrangesDataset)
{
neighbors.set_size(k, referenceSet.n_cols);
distances.set_size(k, referenceSet.n_cols);
for (size_t i = 0; i < distances.n_cols; ++i)
{
// Map distances (copy a column).
const size_t refMapping = oldFromNewReferences[i];
distances.col(refMapping) = distancePtr->col(i);
// Map each neighbor's index.
for (size_t j = 0; j < distances.n_rows; ++j)
neighbors(j, refMapping) = oldFromNewReferences[(*neighborPtr)(j, i)];
}
// Finished with temporary matrices.
delete neighborPtr;
delete distancePtr;
}
else if (treeOwner && !hasQuerySet)
{
// No query tree -- map both references and queries.
resultingNeighbors.set_size(k, querySet.n_cols);
distances.set_size(k, querySet.n_cols);
for (size_t i = 0; i < distances.n_cols; i++)
{
// Map distances (copy a column).
distances.col(oldFromNewReferences[i]) = distancePtr->col(i);
// Map indices of neighbors.
for (size_t j = 0; j < distances.n_rows; j++)
{
resultingNeighbors(j, oldFromNewReferences[i]) =
oldFromNewReferences[(*neighborPtr)(j, i)];
}
}
}
else if (treeOwner && hasQuerySet && singleMode) // Map only references.
{
// Set size of neighbor indices matrix correctly.
resultingNeighbors.set_size(k, querySet.n_cols);
// Map indices of neighbors.
for (size_t i = 0; i < resultingNeighbors.n_cols; i++)
{
for (size_t j = 0; j < resultingNeighbors.n_rows; j++)
{
resultingNeighbors(j, i) = oldFromNewReferences[(*neighborPtr)(j, i)];
}
}
// Finished with temporary matrix.
delete neighborPtr;
}
} // Search
template<typename SortPolicy, typename MetricType, typename TreeType>
void RASearch<SortPolicy, MetricType, TreeType>::ResetQueryTree()
{
if (!singleMode)
{
if (queryTree)
ResetRAQueryStat(queryTree);
else
ResetRAQueryStat(referenceTree);
}
}
template<typename SortPolicy, typename MetricType, typename TreeType>
void RASearch<SortPolicy, MetricType, TreeType>::ResetRAQueryStat(
TreeType* treeNode)
void RASearch<SortPolicy, MetricType, TreeType>::ResetQueryTree(
TreeType* queryNode) const
{
treeNode->Stat().Bound() = SortPolicy::WorstDistance();
treeNode->Stat().NumSamplesMade() = 0;
queryNode->Stat().Bound() = SortPolicy::WorstDistance();
queryNode->Stat().NumSamplesMade() = 0;
for (size_t i = 0; i < treeNode->NumChildren(); i++)
ResetRAQueryStat(&treeNode->Child(i));
for (size_t i = 0; i < queryNode->NumChildren(); i++)
ResetQueryTree(&queryNode->Child(i));
}
// Returns a String of the Object.
// Returns a string representation of the object.
template<typename SortPolicy, typename MetricType, typename TreeType>
std::string RASearch<SortPolicy, MetricType, TreeType>::ToString() const
{
std::ostringstream convert;
convert << "RA Search [" << this << "]" << std::endl;
convert << " Reference Set: " << referenceSet.n_rows << "x" ;
convert << referenceSet.n_cols << std::endl;
if (&referenceSet != &querySet)
convert << " QuerySet: " << querySet.n_rows << "x" << querySet.n_cols
<< std::endl;
convert << "RASearch [" << this << "]" << std::endl;
convert << " referenceSet: " << referenceSet.n_rows << "x"
<< referenceSet.n_cols << std::endl;
convert << " naive: ";
if (naive)
convert << " Naive: TRUE" << std::endl;
convert << "true" << std::endl;
else
convert << "false" << std::endl;
convert << " singleMode: ";
if (singleMode)
convert << " Single Node: TRUE" << std::endl;
convert << " Metric: " << std::endl <<
convert << "true" << std::endl;
else
convert << "false" << std::endl;
convert << " tau: " << tau << std::endl;
convert << " alpha: " << alpha << std::endl;
convert << " sampleAtLeaves: ";
if (sampleAtLeaves)
convert << "true" << std::endl;
else
convert << "false" << std::endl;
convert << " firstLeafExact: ";
if (firstLeafExact)
convert << "true" << std::endl;
else
convert << "false" << std::endl;
convert << " singleSampleLimit: " << singleSampleLimit << std::endl;
convert << " metric: " << std::endl <<
mlpack::util::Indent(metric.ToString(),2);
return convert.str();
}
+5 -3
View File
@@ -29,9 +29,8 @@ class RASearchRules
const bool naive = false,
const bool sampleAtLeaves = false,
const bool firstLeafExact = false,
const size_t singleSampleLimit = 20);
const size_t singleSampleLimit = 20,
const bool sameSet = false);
double BaseCase(const size_t queryIndex, const size_t referenceIndex);
@@ -229,6 +228,9 @@ class RASearchRules
// TO REMOVE: just for testing
size_t numDistComputations;
//! If the query and reference set are identical, this is true.
bool sameSet;
TraversalInfoType traversalInfo;
/**
@@ -25,15 +25,17 @@ RASearchRules(const arma::mat& referenceSet,
const bool naive,
const bool sampleAtLeaves,
const bool firstLeafExact,
const size_t singleSampleLimit) :
referenceSet(referenceSet),
querySet(querySet),
neighbors(neighbors),
distances(distances),
metric(metric),
sampleAtLeaves(sampleAtLeaves),
firstLeafExact(firstLeafExact),
singleSampleLimit(singleSampleLimit)
const size_t singleSampleLimit,
const bool sameSet) :
referenceSet(referenceSet),
querySet(querySet),
neighbors(neighbors),
distances(distances),
metric(metric),
sampleAtLeaves(sampleAtLeaves),
firstLeafExact(firstLeafExact),
singleSampleLimit(singleSampleLimit),
sameSet(sameSet)
{
// Validate tau to make sure that the rank approximation is greater than the
// number of neighbors requested.
@@ -268,7 +270,7 @@ double RASearchRules<SortPolicy, MetricType, TreeType>::BaseCase(
{
// If the datasets are the same, then this search is only using one dataset
// and we should not return identical points.
if ((&querySet == &referenceSet) && (queryIndex == referenceIndex))
if (sameSet && (queryIndex == referenceIndex))
return 0.0;
double distance = metric.Evaluate(querySet.unsafe_col(queryIndex),