/**
* GeDBIT.index.VPKNNCursor
*
* Copyright Information:
*
* Change Log:
* 2006.07.09: created by Weijia Xu
*/
package GeDBIT.index;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.List;
import java.util.logging.Logger;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import GeDBIT.type.DoubleIndexObjectPair;
import GeDBIT.util.ObjectIOManager;
import GeDBIT.util.Debug;
import GeDBIT.type.IndexObject;
import GeDBIT.dist.Metric;
/**
* Implements an {@link Cursor}, for KNN search in vp tree
*
* @author Weijia Xu
* @version 2006.07.09
*/
public class VPKNNCursor extends Cursor {
IndexObject q;
double r;
int maxListLength;
private Logger logger;
LinkedList<KNNQueryTask> task;
// statistics
// global statistics
int nodeVisited, internalVisited; // leafVisited; //number of index nodes
// visited
int distNum, pivotDistNum; // dataDistNum; //number of distance calculation,
// calculations
// with pivots
int resultConfirmedByPathDist; // number of data points that are confirmed
// to be results by
// path distance list
// without computing the distance to the query directly. it is a subset of
// resultWithoutDist.
int pointPrunedByPivotDist; // number of data points that are pruned by the
// pivot distance
int nodePrunedByPivotDist; // number of nodes that are pruned by the pivot
// distance
int pointPrunedByPathDist; // number of data points that are pruned by path
// distance list
int nodeWithoutDist; // number of nodes that are determined to be all
// results without
// computing distance directly.
int resultWithoutDist; // number of results that are determined to be
// results without
// computing distance directly.
int pivotAsResult; // number of pivots that are also search results
int pivotRowIDAsResult; // number of row IDs corresponding to those pivots
// being search
// results
// layer statistics, computed in temp variables, and finally added to the
// lists when values are fixed.
ArrayList<double[]> queryPivotDistance; // distance between the query and
// the pivots of each level, -1
// means no such value
// each element is a 1-d array, is the average value of one level, for each
// pivot (column)
// computed during processing the layer, and value added to this list at the
// end of the layer
ArrayList<Integer> levelNodeNum, levelNodeVisited; // from the perspective
// of last level, these
// are the total
// children node number,
// number of children visited, and the ratio. thus, these values are
// computed // during the processing of last
// layer, and are added to the list at the begin // of the current layer.
ArrayList<Integer> levelPointNum, levelPointVisited; // number of data
// objects of leaf
// nodes of each level,
// number of data object directly compute distance on, ratio. //computed
// during processing the layer, //and value
// added to this list at the end of the layer
// counters, indecies
private int level; // the id of the current level, top-down from 0
private int insideLevel; // the id of the node on current level, left to
// right from 0
int levelNodeToVisit; // number of nodes of current layer to be visited. The
// value
// is initially levelNodeVisited set during previous
// layer
boolean isLeftMost; // whether next node is the first node (left-most) of a
// layer, i.e., start a new layer.
// temp variable, for each layer, to be added to the ArrayLists
double[] tempQueryPivotDistance;
int[] childWithPivotCounter; // the ith element is the number of index nodes
// of the layer
// that has ith pivot.
int tempLevelNode, tempLevelNodeVisited;
int tempLevelPoint, tempLevelPointVisited;
int search_policy;
int guranteeNN;
int queue_size;
double targetRadius, cachedRadius;
LinkedList<DoubleIndexObjectPair> cachedResults;
int result_needed, nnFinished;
boolean foundResult;
private final double EPSILON = 0.001;
public VPKNNCursor(KNNQuery query, ObjectIOManager oiom, Metric metric,
long rootAddress) {
super(oiom, metric, rootAddress);
task = new LinkedList<KNNQueryTask>();
this.q = query.getQueryObject();
this.r = query.getRadius();
this.maxListLength = query.getMaxDistanceListSize();
this.search_policy = query.getSearchPolicy();
if (search_policy == KNNQuery.KNNSEARCH
|| search_policy == KNNQuery.RADIUSLIMITED)
this.guranteeNN = Integer.MAX_VALUE;
else
this.guranteeNN = search_policy;
this.queue_size = query.getK();
this.targetRadius = 0;
this.cachedRadius = Double.MAX_VALUE;
cachedResults = new LinkedList<DoubleIndexObjectPair>();
this.result_needed = query.getK();
this.nnFinished = 0;
this.foundResult = false;
// add the first task
task.add(new KNNQueryTask(rootAddress, new double[0], 0));
if (Debug.debug) {
logger = Logger.getLogger("GeDBIT.index");
try {
logger.addHandler(new FileHandler("knn.log"));
logger.setLevel(Level.FINEST);
} catch (Exception e) {
System.out.println("Exception when creating logging file ");
e.printStackTrace();
System.exit(-1);
}
}
if (Debug.statistics) {
initializeStatistics();
}
}
/**
* initialize the statistics, not the temp variables. this method only be
* called once at the begining, where the temp variables will be initialized
* each time at the begining of a new layer.
*/
private void initializeStatistics() {
level = -1; // before the root (0,0), the level should be -1
insideLevel = 0;
levelNodeToVisit = 1; // node number of root level is 1
isLeftMost = true;
// global statistics
nodeVisited = 0;
internalVisited = 0;
distNum = 0;
pivotDistNum = 0;
this.resultConfirmedByPathDist = 0;
this.pointPrunedByPathDist = 0;
this.pivotAsResult = 0;
this.pivotRowIDAsResult = 0;
this.nodeWithoutDist = 0;
this.resultWithoutDist = 0;
this.nodePrunedByPivotDist = 0;
this.pointPrunedByPivotDist = 0;
// level statistics
queryPivotDistance = new ArrayList<double[]>();
levelNodeNum = new ArrayList<Integer>();
levelNodeVisited = new ArrayList<Integer>();
levelPointNum = new ArrayList<Integer>();
levelPointVisited = new ArrayList<Integer>();
// temp variables, these variables are supposed to be computed during
// the processing of last layer
tempLevelNode = 1;
tempLevelNodeVisited = 1;
}
/**
* the method to be called at the begining of each layer. Actions includes:
* 1. add the temp varaibles to the list, which should be add at the
* begining of a layer 2. set related counters 3. initialize temp variables
* for the new layer
*/
protected void layerBegining() {
// step1. add temp variables to lists
levelNodeNum.add(tempLevelNode);
levelNodeVisited.add(tempLevelNodeVisited);
// step2. set counters
level++;
insideLevel = 0;
levelNodeToVisit = tempLevelNodeVisited;
// step3. initialize temp variables
tempQueryPivotDistance = new double[1];
tempQueryPivotDistance[0] = 0;
childWithPivotCounter = new int[1];
childWithPivotCounter[0] = 0;
tempLevelNode = 0;
tempLevelNodeVisited = 0;
tempLevelPoint = 0;
tempLevelPointVisited = 0;
}
/**
* to be called at the end of a layer. actions includes: 1. add temp
* variables, which should be added to lists at the end of a layer, to lists
* 2. set related counters
*/
protected void layerEnd() {
// step1. add temp variables
for (int i = 0; i < tempQueryPivotDistance.length; i++)
tempQueryPivotDistance[i] /= childWithPivotCounter[i];
queryPivotDistance.add(tempQueryPivotDistance);
levelPointNum.add(tempLevelPoint);
levelPointVisited.add(tempLevelPointVisited);
// step2. set related counters
isLeftMost = true;
}
/**
* to be call at the begining of visiting a node
*/
protected void nodeBegining() {
if (isLeftMost) {
isLeftMost = false;
layerBegining();
}
nodeVisited++;
}
/**
* to be called at the end of visiting a node
*/
protected void nodeEnd() {
insideLevel++;
// System.out.println("level=" + level + ", layer = " + layer +
// ", layersize=" + layerSize);
if (insideLevel == levelNodeToVisit)
layerEnd();
}
// methods to return statistics
/**
* @return the height the of index tree
*/
public int getHeight() {
return level + 1;
}
/**
* Return the number of index node visited during the search
*
* @return an int array of length 2, the first element is the total number
* of index nodes visited during the search, the second is the
* number of internal nodes visited.
*/
public int[] getNodeVisitedNumber() {
return new int[] { nodeVisited, internalVisited };
}
/**
* return some counters
*
* @return an array of counters: 0: int resultConfirmedByPathDist; //number
* of data points that are confirmed to be results by path distance
* list //without computing the distance directly. it is a subset of
* resultWithoutDist. 1: int pointPrunedByPivotDist; //number of
* data points that are pruned by the pivot distance 2: int
* nodePrunedByPivotDist; //number of nodes that are pruned by the
* pivot distance 3: int pointPrunedByPathDist; //number of data
* points that are pruned by path distance list 4: int
* nodeWithoutDist; //number of nodes that are determined to be all
* results without computing distance directly. 5: int
* resultWithoutDist; //number of results that are determined to be
* results without computing distance directly. 6: int
* pivotAsResult; //number of pivots that are also search results 7:
* int pivotRowIDAsResult; //number of row IDs corresponding to
* those pivots being search results
*/
public int[] getCounters() {
int result[] = new int[8];
result[0] = this.resultConfirmedByPathDist;
result[1] = this.pointPrunedByPathDist;
result[2] = this.nodePrunedByPivotDist;
result[3] = this.pointPrunedByPivotDist;
result[4] = this.nodeWithoutDist;
result[5] = this.resultWithoutDist;
result[6] = this.pivotAsResult;
result[7] = this.pivotRowIDAsResult;
return result;
}
/**
* return the number of distance calculations during the search
*
* @return an int array of length 2, the first element is total number of
* distance calculations during the search, the second is the number
* of distance calculations between the query and the pivots
*/
public int[] getDistanceCalculationNumber() {
return new int[] { distNum, pivotDistNum };
}
/**
* return the average distance between the query and the pivots for each
* layer.
*
* @return a 2-d double array, average distance between the query to the
* pivots (column) on each level (row). the length of a row is the
* max number of pivots in the level.
*/
public double[][] getQueryPivotDistance() {
double[][] result = new double[level + 1][];
for (int i = 0; i <= level; i++)
result[i] = (double[]) queryPivotDistance.get(i);
return result;
}
/**
* return the number of nodes visited in each level of the index tree.
*
* @return a 2-d int array, each row is the statistics of a level, starting
* from level 0, the root. each row is an int array of length 2, the
* first is the number of index nodes of the level that belong to
* the parent nodes of the nodes counted; the second is the number
* of nodes visited in the layer,
*/
public int[][] getLevelNodeVisited() {
int[][] result = new int[level + 1][2];
for (int i = 0; i <= level; i++) {
result[i][0] = levelNodeNum.get(i);
result[i][1] = levelNodeVisited.get(i);
}
return result;
}
/**
* return the number of data objects that the query directly compute
* distance with in the leaf nodes of each level of the index tree.
*
* @return a 2-d int array, each row is the statistics of a level, starting
* from level 0, the root. each row is an int array of length 2, the
* first the total number of data objects in the leaf node visited
* of the level, the second is the number of data object that the
* query directly compute distance with. Note that the total number
* of index nodes of a level can not be found during the search.
* Also note that the first element can be 0.
*/
public int[][] getLevelPointVisited() {
int[][] result = new int[level + 1][2];
for (int i = 0; i <= level; i++) {
result[i][0] = levelPointNum.get(i);
result[i][1] = levelPointVisited.get(i);
}
return result;
}
/**
* @return a string representation of the Visitor
*/
public String toString() {
StringBuffer result = new StringBuffer();
result.append("BFSProximityVisitor:\n" + "Query: metric="
+ metric.toString() + ", center=" + q.toString() + ", radius="
+ r + "\nOIOM: " + oiom.toString() + ", height = "
+ getHeight() + "\n");
int[] temp1 = getDistanceCalculationNumber();
result.append("#Distance calculation: " + temp1[0] + " (internal: "
+ temp1[1] + " , leaf: " + temp1[2] + " ), ");
temp1 = getNodeVisitedNumber();
result.append("#Node visited: " + temp1[0] + " (center: " + temp1[1]
+ " , data object: " + temp1[2] + " )\n");
double[][] temp2 = getQueryPivotDistance();
int[][] temp3 = getLevelNodeVisited();
int[][] temp4 = getLevelPointVisited();
result.append("Layer statistics: layer id, node visited(total), data object visited(total), average query-center distances:\n");
for (int i = 0; i < getHeight(); i++) {
result.append(i + ": " + temp3[i][1] + " ( " + temp3[i][0]
+ " ), " + temp4[i][1] + " ( " + temp4[i][0]
+ " ), [");
for (int j = 0; j < temp2[i].length; j++)
result.append(temp2[i][j] + ", ");
result.append("]\n");
}
return result.toString();
}
protected void visit(QueryTask t) {
if (!(t instanceof VPKNNCursor.KNNQueryTask))
throw new UnsupportedOperationException(
"Only KNNQueryTask is supported by VPKNNCursor, not "
+ t.getClass());
visit((KNNQueryTask) t);
}
protected void visit(KNNQueryTask task) {
// if already k results has been returned stop the search.
if (result_needed < 0)
return;
double distance = task.estimation;
if (nnFinished >= guranteeNN) {
targetRadius = distance;
} else if (distance > targetRadius) {
targetRadius = distance;
if (foundResult) {
// nnFinished = true;
// changed 021506
nnFinished++;
foundResult = false; // to track next results found event.
}
// to do,
// if cachedRadius <= target Radius, return good results.
}
Object node = null;
try {
node = oiom.readObject(new Long(task.nodeAddress));
} catch (Exception e) {
e.printStackTrace();
}
if (node instanceof VPInternalNode)
visitInternalNode((VPInternalNode) node, task.distList);
else if (node instanceof VPLeafNode)
visitLeafNode((VPLeafNode) node, task.distList);
else
throw new UnsupportedOperationException(
"only VPInternalNode or VPLeafNode is supported by VPKNNCursor, not "
+ node.getClass());
}
boolean contains(ObjectIOManager oiom, long address, IndexObject query) {
List<IndexObject> linearIndex = getAllPoints(oiom, address);
for (IndexObject o : linearIndex)
if (this.metric.getDistance(o, query) == 0)
return true;
return false;
}
void continueSearch() {
while ((task.size() > 0) && (result.size() == 0)) {
visit((QueryTask) task.remove());
}
if (task.size() == 0)
getCachedResult();
}
private class KNNQueryTask extends QueryTask {
KNNQueryTask(long address, double[] distList, double lb) {
this.nodeAddress = address;
this.distList = distList;
this.estimation = lb;
}
// TODO
long nodeAddress;
double[] distList;
double estimation;
}
private void visitInternalNode(VPInternalNode node, double[] distList) {
// comment out debuging code
/*
* for (int i=0; i<node.numChildren(); i++) if (contains(this.oiom,
* node.getChildAddress(i), this.q) ) System.out.println("chid " + i +
* " contains the query");
*/
// check whether this is the first node of a layer, and set some
// statistics.
if (Debug.debug) // operation of statistics
{
logger.finest("\n(" + level + ", " + insideLevel
+ "): [d(q,pivot) : ");
}
if (Debug.statistics) // operation of statistics
{
nodeBegining();
internalVisited++;
}
// calculate distances from the query to all pivots
final int numPivot = node.numPivots();
if (Debug.statistics) // operation of statistics
{
// calculate statistics: queryCenterDistance
if (tempQueryPivotDistance.length < numPivot) // check whether more
// centers/vantage
// points emerge.
{
double[] temp1 = tempQueryPivotDistance;
int[] temp2 = childWithPivotCounter;
tempQueryPivotDistance = new double[numPivot];
childWithPivotCounter = new int[numPivot];
System.arraycopy(temp1, 0, tempQueryPivotDistance, 0,
temp1.length);
System.arraycopy(temp2, 0, childWithPivotCounter, 0,
temp2.length);
for (int i = temp1.length; i < numPivot; i++) {
tempQueryPivotDistance[i] = 0;
childWithPivotCounter[i] = 0;
}
}
// set the statistics
for (int i = 0; i < numPivot; i++)
childWithPivotCounter[i]++;
pivotDistNum += numPivot;
distNum += numPivot;
}
// search step1: compute distances between pivot and query
double queryPivotDistance[] = new double[numPivot];
// add more distance to the distance list if necessary
int oldDistListLength = distList.length;
if (distList.length < this.maxListLength) {
double[] temp = distList;
distList = new double[((oldDistListLength + numPivot) <= this.maxListLength) ? (oldDistListLength + numPivot)
: this.maxListLength];
System.arraycopy(temp, 0, distList, 0, oldDistListLength);
}
for (int pivot = 0; pivot < numPivot; ++pivot) {
queryPivotDistance[pivot] = metric.getDistance(q,
node.getPivot(pivot));
if (Debug.debug) // operation of statistics
{
logger.finest("Checking pivot " + pivot + ", d(query, pivot["
+ pivot + "] ) =" + queryPivotDistance);
}
if (Debug.statistics) // operation of statistics
{
childWithPivotCounter[pivot]++;
tempQueryPivotDistance[pivot] += queryPivotDistance[pivot];
}
// return pivot as query result if it is satisfies the limiting
// radius
if (queryPivotDistance[pivot] <= r) {
if (Debug.statistics) // operation of statistics
{
this.pivotAsResult++;
}
queueResults(node.getPivot(pivot), queryPivotDistance[pivot]);
} else {
if (Debug.debug) // operation of statistics
{
logger.finest(", not satisfying the query.");
}
}
// add query pivot distance to list if list is not long enough
if (oldDistListLength + pivot < distList.length)
distList[oldDistListLength + pivot] = queryPivotDistance[pivot];
}
// search step 2: check each child node.
final int numChild = node.numChildren();
if (Debug.debug) // operation of statistics
{
logger.finest("], children : ");
}
if (Debug.statistics) // operation of statistics
{
tempLevelNode += numChild;
}
boolean done = false;
// check range for each child and prune
for (int i = 0; i < numChild; ++i) {
done = false;
double[][] range = node.getChildPredicate(i);
double pruneAt = targetRadius;
if (Debug.debug) // operation of statistics
{
logger.finest("[" + i + ":");
}
for (int j = 0; j < numPivot && !done; ++j) {
// 1st rule: if upper(child,p) + d(p,q) <= r, then all points of
// child are results.
if (!done && (range[1][j] + queryPivotDistance[j] <= r)) {
done = true;
List<IndexObject> allResult = getAllPoints(this.oiom,
node.getChildAddress(i));
if (Debug.statistics) // operation of statistics
{
this.nodeWithoutDist++;
this.resultWithoutDist += allResult.size();
}
if (Debug.debug) // operation of statistics
{
logger.finest("(" + queryPivotDistance[j] + "+"
+ range[1][j] + ">" + r
+ ") : all are results], ");
}
for (IndexObject o : allResult) {
queueResults(o, metric.getDistance(q, o));
}
}
if (!done) {
double ld = range[0][j] - queryPivotDistance[j];
double ud = queryPivotDistance[j] - range[1][j];
double d = ld > ud ? ld : ud;
pruneAt = d > pruneAt ? d : pruneAt;
// 2nd rule:
// if (!done && ((queryPivotDistance[j] + r < range[0][j])
// || (queryPivotDistance[j] - r > range[1][j])))
if (pruneAt > r) {
done = true;
if (Debug.statistics) // operation of statistics
{
this.nodePrunedByPivotDist++;
this.pointPrunedByPivotDist += getAllPoints(
this.oiom, node.getChildAddress(i)).size();
}
if (Debug.debug) // operation of statistics
{
logger.finest("(" + queryPivotDistance[j] + "+" + r
+ "<" + range[0][j] + ") : pruned], ");
}
}
}
}
// add to task list to be searched if the child has not been pruned
if (!done) {
// TODO
if (oldDistListLength < distList.length)
queueTask(new KNNQueryTask(node.getChildAddress(i),
(double[]) distList.clone(), pruneAt));
else
queueTask(new KNNQueryTask(node.getChildAddress(i),
distList, pruneAt));
if (Debug.debug) // operation of statistics
{
logger.finest("to search], ");
}
if (Debug.statistics) // operation of statistics
{
tempLevelNodeVisited += 1;
}
}
}
if (Debug.debug) // operation of statistics
{
logger.finest("");
}
if (Debug.statistics) // operation of statistics
{
// check whether this is the last node of the layer, and set
// statistics
nodeEnd();
}
if (task.isEmpty())
getCachedResult();
}
private void visitLeafNode(VPLeafNode node, double[] distList) {
if (Debug.debug) // operation of statistics
{
logger.finest("entering a vp leaf node (" + level + ", "
+ insideLevel + ")\n" + node);
}
if (Debug.statistics) // operation of statistics
{
// check whether this is the first node of a layer, and set some
// statistics.
nodeBegining();
// set statistics
tempLevelPoint += node.size();
// final int size = node.size();
if (tempQueryPivotDistance.length < node.numPivots()) // check
// whether
// more
// centers/vantage
// points
// emerge.
{
double[] temp1 = tempQueryPivotDistance;
int[] temp2 = childWithPivotCounter;
tempQueryPivotDistance = new double[node.numPivots()];
childWithPivotCounter = new int[node.numPivots()];
System.arraycopy(temp1, 0, tempQueryPivotDistance, 0,
temp1.length);
System.arraycopy(temp2, 0, childWithPivotCounter, 0,
temp2.length);
for (int i = temp1.length; i < node.numPivots(); i++) {
tempQueryPivotDistance[i] = 0;
childWithPivotCounter[i] = 0;
}
}
}
final int distinctSize = node.numChildren();
// check the pivots
double[] queryPivotDistance = new double[node.numPivots()];
if (Debug.statistics) // operation of statistics
{
pivotDistNum += node.numPivots();
distNum += node.numPivots();
}
for (int pivot = 0; pivot < node.numPivots(); ++pivot) {
queryPivotDistance[pivot] = metric.getDistance(q,
node.getPivot(pivot));
if (Debug.statistics) // operation of statistics
{
childWithPivotCounter[pivot]++;
tempQueryPivotDistance[pivot] += queryPivotDistance[pivot];
}
if (Debug.debug) // operation of statistics
{
logger.finest("Checking pivot " + pivot + ", d(query, pivot["
+ pivot + "] ) =" + queryPivotDistance);
}
// return pivot as query result if it is satisfies the range query
if (queryPivotDistance[pivot] <= r) {
queueResults(node.getPivot(pivot), queryPivotDistance[pivot]);
if (Debug.statistics) // operation of statistics
{
pivotAsResult++;
}
} else {
if (Debug.debug) // operation of statistics
{
logger.finest(", not satisfying the query.");
}
}
}
for (int child = 0; child < distinctSize; child++) {
boolean done = false;
// 1. try to prune by the path distances
double[] pathDist = node.getDataPointPathDistance(child);
for (int pivot = 0; pivot < distList.length
&& pivot < pathDist.length; pivot++) {
// if | d(p,c) - d(p,q) | > r, then c can be pruned.
if (Math.abs(pathDist[pivot] - distList[pivot]) > r) {
if (Debug.statistics) // operation of statistics
{
this.pointPrunedByPathDist++;
}
if (Debug.debug) // operation of statistics
{
logger.finest("child: " + child + ", path distance: "
+ pivot + ": | " + pathDist[pivot] + " - "
+ distList[pivot] + " | > " + r + ", pruned!");
}
done = true;
break;
}
// if d(p,c) + d(p,q) <= r, then c is a result;
if (pathDist[pivot] + distList[pivot] <= r) {
if (Debug.statistics) // operation of statistics
{
this.resultConfirmedByPathDist++;
this.resultWithoutDist++;
}
if (Debug.debug) // operation of statistics
{
logger.finest("child: " + child + ", path distance: "
+ pivot + ": " + pathDist[pivot] + " + "
+ distList[pivot] + " <= " + r
+ ", is a result!");
}
done = true;
queueResults(node.getChild(child),
metric.getDistance(q, node.getChild(child)));
break;
}
}
if (done)
continue;
// 2, try to prune by each point's distance to the node pivot, check
// in the order of pivot
// compute distance with each pivot, then try to prune
for (int pivot = 0; pivot < node.numPivots(); pivot++) {
double[] dataPivotDistance = node
.getDataPointPivotDistance(child);
// if | d(p,c) - d(p,q) | > r, then c can be pruned.
if (Math.abs(dataPivotDistance[pivot]
- queryPivotDistance[pivot]) > r) {
if (Debug.statistics) // operation of statistics
{
this.pointPrunedByPivotDist++;
}
if (Debug.debug) // operation of statistics
{
logger.finest("child: " + child + ", pivot distance: "
+ pivot + ": | " + dataPivotDistance[pivot]
+ " - " + queryPivotDistance[pivot] + " | > "
+ r + ", pruned!");
}
done = true;
break;
}
// if d(p,c) + d(p,q) <= r, then c is a result;
if (dataPivotDistance[pivot] + queryPivotDistance[pivot] <= r) {
if (Debug.statistics) // operation of statistics
{
this.resultWithoutDist++;
}
if (Debug.debug) // operation of statistics
{
logger.finest("child: " + child + ", pivot distance: "
+ pivot + ": " + dataPivotDistance[pivot]
+ " + " + queryPivotDistance[pivot] + " <= "
+ r + ", is a result!");
}
done = true;
queueResults(node.getChild(child),
metric.getDistance(q, node.getChild(child)));
break;
}
}
if (done)
continue;
// 3 the data can not be pruned or sure to be a result, so, compute
// distance directly.
double distance = metric.getDistance(q, node.getChild(child));
if (Debug.statistics) // operation of statistics
{
distNum++;
tempLevelPointVisited++;
}
if (distance <= r) {
if (Debug.debug) // operation of statistics
{
logger.finest("Point: " + child + ": " + distance + " <= "
+ r + " ), is a result. ");
}
queueResults(node.getChild(child), distance);
} else {
if (Debug.debug) // operation of statistics
{
logger.finest("Point: " + child + ": " + distance + " > "
+ r + " ), is not a result.");
}
}
}
if (Debug.statistics) // operation of statistics
{
// check whether this is the last node of a layer, and set
// statistics
nodeEnd();
}
getCachedResult();
}
/**
* Queue the results found during the search
*
* @param obj
* matching object
* @param distance
* distance of this matches
*/
public void queueResults(IndexObject obj, double distance) {
if (Debug.debug) // operation of statistics
{
logger.finest("queue cached Result " + cachedResults.size()
+ " to " + result.size() + " " + result_needed
+ "results needed");
}
if (nnFinished >= guranteeNN) {
// no queue needed
cachedResults.add(new DoubleIndexObjectPair(distance, obj));
} else {
// need queue results in order of distance
if (cachedResults.isEmpty()) {
cachedResults.add(new DoubleIndexObjectPair(distance, obj));
cachedRadius = distance;
} else {
double p = ((DoubleIndexObjectPair) cachedResults.getLast())
.getDouble();
if (distance >= p) {
cachedResults.add(new DoubleIndexObjectPair(distance, obj));//
} else {
ListIterator<DoubleIndexObjectPair> i = cachedResults
.listIterator();
int index = 0;
boolean stop = false;
while (!stop && i.hasNext()) {
p = ((DoubleIndexObjectPair) i.next()).getDouble();
// temprorarly changed should change back to <=
if (distance <= p) {
stop = true;
cachedResults.add(index, new DoubleIndexObjectPair(
distance, obj));
if (index == 0) {
cachedRadius = distance;
}
} else
index++;
}
}
}
if (cachedResults.size() > result_needed) {
double d = Math.ceil(((DoubleIndexObjectPair) cachedResults
.removeLast()).getDouble()) - EPSILON;
if (d < r)
r = d;
}
}
}
/**
* Gets the results that have been cached based on current search stratagey,
* target radius and search status. move qulaified results from
* cahcedResults to result.
*
*/
public void getCachedResult() {
if (Debug.debug) // operation of statistics
{
logger.finest("getting cached Result " + cachedResults.size()
+ " to " + result.size());
}
// queue is empty or cachedRadus > targetRadius, no results to return.
if (cachedResults.isEmpty()) {
return;
}
// return all results in queue if there is no more nodes to visit.
if (task.isEmpty()) {
// return cachedResults;
result.addAll(cachedResults);
result_needed -= cachedResults.size();
if (Debug.debug) // operation of statistics
{
logger.finest("return all cached Result: result queue("
+ result.size() + ") results needed " + result_needed);
}
return;
}
ListIterator<DoubleIndexObjectPair> lir = cachedResults.listIterator();
if (search_policy >= 0 && nnFinished >= guranteeNN) {
// no queue return all founded
while (lir.hasNext()) {
result.add(lir.next());
result_needed--;
lir.remove();
if (!foundResult)
foundResult = true;
}
} else if (cachedRadius <= targetRadius) { // need return results in
// order of distance
// TODO what is index for?
boolean stop = false;
DoubleIndexObjectPair tempPair;
while (lir.hasNext() && !stop) {
tempPair = (DoubleIndexObjectPair) lir.next();
double p = tempPair.getDouble();
if (p > targetRadius) {
stop = true;
cachedRadius = p;
} else {
result.add(tempPair);
lir.remove();
result_needed--;
if (!foundResult)
foundResult = true;
}
}
if (!stop) {
cachedRadius = Double.MAX_VALUE;
}
}
if (Debug.debug) // operation of statistics
{
logger.finest("got cached Result " + cachedResults.size() + " to "
+ result.size() + " " + result_needed + " results needed"
+ "task size(" + task.size() + ")");
}
}
// find the first index in targetStack that <= pruneAt
private final int getInsertionIndex(int from, int to, double pruneAt) {
if (from > to)
return from;
double p = ((KNNQueryTask) task.get(to)).estimation;
if (pruneAt > p)
return to + 1;
p = ((KNNQueryTask) task.get(from)).estimation;
if (pruneAt <= p)
return from;
int mid = (to + from) >> 1;
p = ((KNNQueryTask) task.get(mid)).estimation;
if (pruneAt > p)
return getInsertionIndex(mid + 1, to - 1, pruneAt);
else
return getInsertionIndex(from + 1, mid - 1, pruneAt);
}
/**
* This method is used to queue the current node in target list
*
* @param obj
* target node
*/
private final void queueTask(KNNQueryTask obj) {
double pruneAt = obj.estimation;
if (task.isEmpty()) {
task.add(obj);
} else {
double p = ((KNNQueryTask) task.getLast()).estimation;
if (pruneAt > p) {
task.add(obj);
} else {
int index = getInsertionIndex(0, task.size() - 2, pruneAt);
task.add(index, obj);
}
}
}
@Override
void searchResults() {
}
}