mirror of https://github.com/jlizier/jidt
Fixes issue 31 -- implementing fast nearest neighbour search for Kraskov MI using a k-d tree structure.
This commit is contained in:
parent
02c22fcd23
commit
bc2783b971
|
|
@ -24,6 +24,7 @@ import java.util.Random;
|
|||
import infodynamics.measures.continuous.MutualInfoCalculatorMultiVariate;
|
||||
import infodynamics.measures.continuous.MutualInfoMultiVariateCommon;
|
||||
import infodynamics.utils.EuclideanUtils;
|
||||
import infodynamics.utils.KdTree;
|
||||
import infodynamics.utils.MathsUtils;
|
||||
import infodynamics.utils.MatrixUtils;
|
||||
|
||||
|
|
@ -31,6 +32,8 @@ import infodynamics.utils.MatrixUtils;
|
|||
* <p>Computes the differential mutual information of two given multivariate sets of
|
||||
* observations (implementing {@link MutualInfoCalculatorMultiVariate}),
|
||||
* using Kraskov-Stoegbauer-Grassberger (KSG) estimation (see Kraskov et al., below).
|
||||
* The implementation is made using fast-neighbour searches with an
|
||||
* underlying k-d tree algorithm.
|
||||
* This is an abstract class to gather common functionality between the two
|
||||
* algorithms defined by Kraskov et al.
|
||||
* Two child classes {@link MutualInfoCalculatorMultiVariateKraskov1} and
|
||||
|
|
@ -46,10 +49,6 @@ import infodynamics.utils.MatrixUtils;
|
|||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* TODO Add fast nearest neighbour searches to the child classes
|
||||
* </p>
|
||||
*
|
||||
* <p><b>References:</b><br/>
|
||||
* <ul>
|
||||
* <li>Kraskov, A., Stoegbauer, H., Grassberger, P.,
|
||||
|
|
@ -71,9 +70,9 @@ public abstract class MutualInfoCalculatorMultiVariateKraskov
|
|||
protected int k = 4;
|
||||
|
||||
/**
|
||||
* Calculator for the norm between data points
|
||||
* The norm type in use (see {@link #PROP_NORM_TYPE})
|
||||
*/
|
||||
protected EuclideanUtils normCalculator;
|
||||
protected int normType = EuclideanUtils.NORM_MAX_NORM;
|
||||
|
||||
/**
|
||||
* Property name for the number of K nearest neighbours used in
|
||||
|
|
@ -83,7 +82,7 @@ public abstract class MutualInfoCalculatorMultiVariateKraskov
|
|||
/**
|
||||
* Property name for what type of norm to use between data points
|
||||
* for each marginal variable -- Options are defined by
|
||||
* {@link EuclideanUtils#setNormToUse(String)} and the
|
||||
* {@link KdTree#setNormType(String)} and the
|
||||
* default is {@link EuclideanUtils#NORM_MAX_NORM}.
|
||||
*/
|
||||
public final static String PROP_NORM_TYPE = "NORM_TYPE";
|
||||
|
|
@ -126,16 +125,49 @@ public abstract class MutualInfoCalculatorMultiVariateKraskov
|
|||
*/
|
||||
protected int numThreads = Runtime.getRuntime().availableProcessors();
|
||||
/**
|
||||
* Private variable to record which algorithm this instance is implementing
|
||||
* Private variable to record which KSG algorithm number
|
||||
* this instance is implementing
|
||||
*/
|
||||
protected boolean isAlgorithm1 = false;
|
||||
|
||||
/**
|
||||
* protected k-d tree data structure (for fast nearest neighbour searches)
|
||||
* representing the joint source-dest space
|
||||
*/
|
||||
protected KdTree kdTreeJoint;
|
||||
/**
|
||||
* protected k-d tree data structure (for fast nearest neighbour searches)
|
||||
* representing the source space
|
||||
*/
|
||||
protected KdTree kdTreeSource;
|
||||
/**
|
||||
* protected k-d tree data structure (for fast nearest neighbour searches)
|
||||
* representing the dest space
|
||||
*/
|
||||
protected KdTree kdTreeDest;
|
||||
|
||||
/**
|
||||
* Constant for digamma(k), with k the number of nearest neighbours selected
|
||||
*/
|
||||
protected double digammaK;
|
||||
/**
|
||||
* Constant for digamma(N), with N the number of samples.
|
||||
*/
|
||||
protected double digammaN;
|
||||
|
||||
/**
|
||||
* Construct an instance of the KSG MI calculator
|
||||
*/
|
||||
public MutualInfoCalculatorMultiVariateKraskov() {
|
||||
super();
|
||||
normCalculator = new EuclideanUtils(EuclideanUtils.NORM_MAX_NORM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialise(int sourceDimensions, int destDimensions) {
|
||||
kdTreeJoint = null;
|
||||
kdTreeSource = null;
|
||||
kdTreeDest = null;
|
||||
super.initialise(sourceDimensions, destDimensions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -150,7 +182,7 @@ public abstract class MutualInfoCalculatorMultiVariateKraskov
|
|||
* in the KSG algorithm (default is 4).</li>
|
||||
* <li>{@link #PROP_NORM_TYPE}</li> -- normalization type to apply to
|
||||
* working out the norms between the points in each marginal space.
|
||||
* Options are defined by {@link EuclideanUtils#setNormToUse(String)} -
|
||||
* Options are defined by {@link KdTree#setNormType(String)} -
|
||||
* default is {@link EuclideanUtils#NORM_MAX_NORM}.
|
||||
* <li>{@link #PROP_NORMALISE} -- whether to normalise the incoming individual
|
||||
* variables to mean 0 and standard deviation 1 (true by default)</li>
|
||||
|
|
@ -179,7 +211,7 @@ public abstract class MutualInfoCalculatorMultiVariateKraskov
|
|||
if (propertyName.equalsIgnoreCase(PROP_K)) {
|
||||
k = Integer.parseInt(propertyValue);
|
||||
} else if (propertyName.equalsIgnoreCase(PROP_NORM_TYPE)) {
|
||||
normCalculator.setNormToUse(propertyValue);
|
||||
normType = KdTree.validateNormType(propertyValue);
|
||||
} else if (propertyName.equalsIgnoreCase(PROP_NORMALISE)) {
|
||||
normalise = Boolean.parseBoolean(propertyValue);
|
||||
} else if (propertyName.equalsIgnoreCase(PROP_ADD_NOISE)) {
|
||||
|
|
@ -211,6 +243,13 @@ public abstract class MutualInfoCalculatorMultiVariateKraskov
|
|||
// Allow the parent to generate the data for us first
|
||||
super.finaliseAddObservations();
|
||||
|
||||
if (totalObservations < k) {
|
||||
throw new Exception("There are less observations provided (" +
|
||||
totalObservations +
|
||||
") than the number of nearest neighbours parameter (" +
|
||||
k + ")");
|
||||
}
|
||||
|
||||
// Normalise the data if required
|
||||
if (normalise) {
|
||||
// We can overwrite these since they're already
|
||||
|
|
@ -233,6 +272,10 @@ public abstract class MutualInfoCalculatorMultiVariateKraskov
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the constants:
|
||||
digammaK = MathsUtils.digamma(k);
|
||||
digammaN = MathsUtils.digamma(totalObservations);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -262,13 +305,25 @@ public abstract class MutualInfoCalculatorMultiVariateKraskov
|
|||
if (reordering == null) {
|
||||
return computeAverageLocalOfObservations();
|
||||
}
|
||||
|
||||
// Save internal variables pertaining to the original order for
|
||||
// later reinstatement:
|
||||
// (don't need to save and reinstate kdTreeSource, since we're not
|
||||
// altering the source data order).
|
||||
KdTree originalKdTreeJoint = kdTreeJoint;
|
||||
KdTree originalKdTreeDest = kdTreeDest;
|
||||
double[][] originalData2 = destObservations;
|
||||
|
||||
// Generate a new re-ordered data2
|
||||
destObservations = MatrixUtils.extractSelectedTimePointsReusingArrays(originalData2, reordering);
|
||||
// Compute the MI
|
||||
double newMI = computeFromObservations(false)[0];
|
||||
// restore data2
|
||||
|
||||
// restore original variables:
|
||||
destObservations = originalData2;
|
||||
kdTreeJoint = originalKdTreeJoint;
|
||||
kdTreeDest = originalKdTreeDest;
|
||||
|
||||
return newMI;
|
||||
}
|
||||
|
||||
|
|
@ -330,6 +385,26 @@ public abstract class MutualInfoCalculatorMultiVariateKraskov
|
|||
|
||||
double[] returnValues = null;
|
||||
|
||||
// We need to construct the k-d trees for use by the child
|
||||
// classes. We check each tree for existence separately
|
||||
// since source can be used across original and surrogate data
|
||||
// TODO can parallelise these -- best done within the kdTree --
|
||||
// though it's unclear if there's much point given that
|
||||
// the tree construction itself afterwards can't really be well parallelised.
|
||||
if (kdTreeJoint == null) {
|
||||
kdTreeJoint = new KdTree(new int[] {dimensionsSource, dimensionsDest},
|
||||
new double[][][] {sourceObservations, destObservations});
|
||||
kdTreeJoint.setNormType(normType);
|
||||
}
|
||||
if (kdTreeSource == null) {
|
||||
kdTreeSource = new KdTree(sourceObservations);
|
||||
kdTreeSource.setNormType(normType);
|
||||
}
|
||||
if (kdTreeDest == null) {
|
||||
kdTreeDest = new KdTree(destObservations);
|
||||
kdTreeDest.setNormType(normType);
|
||||
}
|
||||
|
||||
if (numThreads == 1) {
|
||||
// Single-threaded implementation:
|
||||
returnValues = partialComputeFromObservations(0, N, returnLocals);
|
||||
|
|
@ -483,7 +558,8 @@ public abstract class MutualInfoCalculatorMultiVariateKraskov
|
|||
*/
|
||||
public void run() {
|
||||
try {
|
||||
returnValues = miCalc.partialComputeFromObservations(myStartTimePoint, numberOfTimePoints, computeLocals);
|
||||
returnValues = miCalc.partialComputeFromObservations(
|
||||
myStartTimePoint, numberOfTimePoints, computeLocals);
|
||||
} catch (Exception e) {
|
||||
// Store the exception for later retrieval
|
||||
problem = e;
|
||||
|
|
@ -491,14 +567,5 @@ public abstract class MutualInfoCalculatorMultiVariateKraskov
|
|||
}
|
||||
}
|
||||
}
|
||||
// end class MiKraskovThreadRunner
|
||||
|
||||
/**
|
||||
* Utility function used for debugging, printing digamma constants
|
||||
*
|
||||
* @param N
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public abstract String printConstants(int N) throws Exception ;
|
||||
// end class MiKraskovThreadRunner
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@
|
|||
package infodynamics.measures.continuous.kraskov;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
import infodynamics.measures.continuous.MutualInfoCalculatorMultiVariate;
|
||||
import infodynamics.utils.MathsUtils;
|
||||
import infodynamics.utils.MatrixUtils;
|
||||
import infodynamics.utils.KdTree.NeighbourNodeData;
|
||||
|
||||
/**
|
||||
* <p>Computes the differential mutual information of two given multivariate sets of
|
||||
|
|
@ -50,12 +51,6 @@ import infodynamics.utils.MatrixUtils;
|
|||
public class MutualInfoCalculatorMultiVariateKraskov1
|
||||
extends MutualInfoCalculatorMultiVariateKraskov {
|
||||
|
||||
/**
|
||||
* Multiplier used as hueristic for determining whether to use a linear search
|
||||
* for kth nearest neighbour or a binary search.
|
||||
*/
|
||||
protected static final double CUTOFF_MULTIPLIER = 1.5;
|
||||
|
||||
public MutualInfoCalculatorMultiVariateKraskov1() {
|
||||
super();
|
||||
isAlgorithm1 = true;
|
||||
|
|
@ -67,63 +62,33 @@ public class MutualInfoCalculatorMultiVariateKraskov1
|
|||
|
||||
double startTime = Calendar.getInstance().getTimeInMillis();
|
||||
|
||||
int N = sourceObservations.length; // number of observations
|
||||
int cutoffForKthMinLinear = (int) (CUTOFF_MULTIPLIER * Math.log(N) / Math.log(2.0));
|
||||
|
||||
double[] localMi = null;
|
||||
if (returnLocals) {
|
||||
localMi = new double[numTimePoints];
|
||||
}
|
||||
|
||||
// Constants:
|
||||
double digammaK = MathsUtils.digamma(k);
|
||||
double digammaN = MathsUtils.digamma(N);
|
||||
|
||||
// Count the average number of points within eps_x and eps_y of each point
|
||||
double sumDiGammas = 0;
|
||||
double sumNx = 0;
|
||||
double sumNy = 0;
|
||||
|
||||
for (int t = startTimePoint; t < startTimePoint + numTimePoints; t++) {
|
||||
// Compute eps for this time step:
|
||||
// First get x and y norms to all neighbours
|
||||
// (note that norm of point t to itself will be set to infinity).
|
||||
|
||||
// TODO Consider reintroducing caching of the norms here; this helps when we're
|
||||
// computing stat significance for various re-orderings. May not be useful
|
||||
// when we've gone to fast nearest neighbour searching though.
|
||||
|
||||
double[][] xyNorms = normCalculator.computeNorms(sourceObservations, destObservations, t);
|
||||
double[] jointNorm = new double[N];
|
||||
for (int t2 = 0; t2 < N; t2++) {
|
||||
jointNorm[t2] = Math.max(xyNorms[t2][0], xyNorms[t2][1]);
|
||||
}
|
||||
// Then find the kth closest neighbour, using a heuristic to
|
||||
// select whether to keep the k mins only or to do a sort.
|
||||
double epsilon = 0.0;
|
||||
if (k <= cutoffForKthMinLinear) {
|
||||
// just do a linear search for the minimum
|
||||
epsilon = MatrixUtils.kthMin(jointNorm, k);
|
||||
} else {
|
||||
// Sort the array of joint norms first
|
||||
java.util.Arrays.sort(jointNorm);
|
||||
// And find the distance to its kth closest neighbour
|
||||
// (we subtract one since the array is indexed from zero)
|
||||
epsilon = jointNorm[k-1];
|
||||
}
|
||||
// Compute eps for this time step by
|
||||
// finding the kth closest neighbour for point t:
|
||||
PriorityQueue<NeighbourNodeData> nnPQ =
|
||||
kdTreeJoint.findKNearestNeighbours(k, t);
|
||||
// First element in the PQ is the kth NN,
|
||||
// and epsilon = kthNnData.distance
|
||||
NeighbourNodeData kthNnData = nnPQ.poll();
|
||||
|
||||
// Count the number of points whose x distance is less
|
||||
// than eps, and whose y distance is less than eps
|
||||
int n_x = 0;
|
||||
int n_y = 0;
|
||||
for (int t2 = 0; t2 < N; t2++) {
|
||||
if (xyNorms[t2][0] < epsilon) {
|
||||
n_x++;
|
||||
}
|
||||
if (xyNorms[t2][1] < epsilon) {
|
||||
n_y++;
|
||||
}
|
||||
}
|
||||
// than eps, and whose y distance is less than
|
||||
// epsilon = kthNnData.distance
|
||||
int n_x = kdTreeSource.countPointsStrictlyWithinR(
|
||||
t, kthNnData.distance);
|
||||
int n_y = kdTreeDest.countPointsStrictlyWithinR(
|
||||
t, kthNnData.distance);
|
||||
|
||||
sumNx += n_x;
|
||||
sumNy += n_y;
|
||||
// And take the digammas:
|
||||
|
|
@ -150,13 +115,5 @@ public class MutualInfoCalculatorMultiVariateKraskov1
|
|||
} else {
|
||||
return new double[] {sumDiGammas, sumNx, sumNy};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String printConstants(int N) throws Exception {
|
||||
String constants = String.format("digamma(k=%d)=%.3e + digamma(N=%d)=%.3e => %.3e",
|
||||
k, MathsUtils.digamma(k), N, MathsUtils.digamma(N),
|
||||
(MathsUtils.digamma(k) + MathsUtils.digamma(N)));
|
||||
return constants;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@
|
|||
package infodynamics.measures.continuous.kraskov;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
import infodynamics.measures.continuous.MutualInfoCalculatorMultiVariate;
|
||||
import infodynamics.utils.FirstIndexComparatorDouble;
|
||||
import infodynamics.utils.MathsUtils;
|
||||
import infodynamics.utils.MatrixUtils;
|
||||
import infodynamics.utils.KdTree.NeighbourNodeData;
|
||||
|
||||
/**
|
||||
* <p>Computes the differential mutual information of two given multivariate sets of
|
||||
|
|
@ -50,15 +50,6 @@ import infodynamics.utils.MatrixUtils;
|
|||
*/
|
||||
public class MutualInfoCalculatorMultiVariateKraskov2
|
||||
extends MutualInfoCalculatorMultiVariateKraskov {
|
||||
|
||||
protected static final int JOINT_NORM_VAL_COLUMN = 0;
|
||||
protected static final int JOINT_NORM_TIMESTEP_COLUMN = 1;
|
||||
|
||||
/**
|
||||
* Multiplier used as hueristic for determining whether to use a linear search
|
||||
* for kth nearest neighbour or a binary search.
|
||||
*/
|
||||
protected static final double CUTOFF_MULTIPLIER = 1.5;
|
||||
|
||||
public MutualInfoCalculatorMultiVariateKraskov2() {
|
||||
super();
|
||||
|
|
@ -70,18 +61,13 @@ public class MutualInfoCalculatorMultiVariateKraskov2
|
|||
|
||||
double startTime = Calendar.getInstance().getTimeInMillis();
|
||||
|
||||
int N = sourceObservations.length; // number of observations
|
||||
int cutoffForKthMinLinear = (int) (CUTOFF_MULTIPLIER * Math.log(N) / Math.log(2.0));
|
||||
|
||||
double[] localMi = null;
|
||||
if (returnLocals) {
|
||||
localMi = new double[numTimePoints];
|
||||
}
|
||||
|
||||
// Constants:
|
||||
double digammaK = MathsUtils.digamma(k);
|
||||
double invK = 1.0 / (double)k;
|
||||
double digammaN = MathsUtils.digamma(N);
|
||||
|
||||
// Count the average number of points within eps_x and eps_y of each point
|
||||
double sumDiGammas = 0;
|
||||
|
|
@ -89,59 +75,33 @@ public class MutualInfoCalculatorMultiVariateKraskov2
|
|||
double sumNy = 0;
|
||||
|
||||
for (int t = startTimePoint; t < startTimePoint + numTimePoints; t++) {
|
||||
// Compute eps_x and eps_y for this time step:
|
||||
// First get x and y norms to all neighbours
|
||||
// (note that norm of point t to itself will be set to infinity).
|
||||
double[][] xyNorms = normCalculator.computeNorms(sourceObservations, destObservations, t);
|
||||
double[][] jointNorm = new double[N][2];
|
||||
for (int t2 = 0; t2 < N; t2++) {
|
||||
jointNorm[t2][JOINT_NORM_VAL_COLUMN] = Math.max(xyNorms[t2][0], xyNorms[t2][1]);
|
||||
// And store the time step for back reference after the
|
||||
// array is sorted.
|
||||
jointNorm[t2][JOINT_NORM_TIMESTEP_COLUMN] = t2;
|
||||
}
|
||||
// Then find the k closest neighbours, using a heuristic to
|
||||
// select whether to keep the k mins only or to do a sort.
|
||||
// Compute eps_x and eps_y for this time step by
|
||||
// finding the kth closest neighbours for point t:
|
||||
PriorityQueue<NeighbourNodeData> nnPQ =
|
||||
kdTreeJoint.findKNearestNeighbours(k, t);
|
||||
|
||||
// Find eps_{x,y} as the maximum x and y norms amongst this set:
|
||||
double eps_x = 0.0;
|
||||
double eps_y = 0.0;
|
||||
int[] timeStepsOfKthMins = null;
|
||||
if (k <= cutoffForKthMinLinear) {
|
||||
// just do a linear search for the minimum epsilon value
|
||||
timeStepsOfKthMins = MatrixUtils.kMinIndices(jointNorm, JOINT_NORM_VAL_COLUMN, k);
|
||||
} else {
|
||||
// Sort the array of joint norms
|
||||
java.util.Arrays.sort(jointNorm, FirstIndexComparatorDouble.getInstance());
|
||||
// and now we have the closest k points.
|
||||
timeStepsOfKthMins = new int[k];
|
||||
for (int j = 0; j < k; j++) {
|
||||
timeStepsOfKthMins[j] = (int) jointNorm[j][JOINT_NORM_TIMESTEP_COLUMN];
|
||||
}
|
||||
}
|
||||
// and now we have the closest k points.
|
||||
// Find eps_{x,y} as the maximum x and y norms amongst this set:
|
||||
for (int j = 0; j < k; j++) {
|
||||
int timeStepOfJthPoint = timeStepsOfKthMins[j];
|
||||
if (xyNorms[timeStepOfJthPoint][0] > eps_x) {
|
||||
eps_x = xyNorms[timeStepOfJthPoint][0];
|
||||
// Take the furthest remaining of the nearest neighbours from the PQ:
|
||||
NeighbourNodeData nnData = nnPQ.poll();
|
||||
if (nnData.norms[0] > eps_x) {
|
||||
eps_x = nnData.norms[0];
|
||||
}
|
||||
if (xyNorms[timeStepOfJthPoint][1] > eps_y) {
|
||||
eps_y = xyNorms[timeStepOfJthPoint][1];
|
||||
if (nnData.norms[1] > eps_y) {
|
||||
eps_y = nnData.norms[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Count the number of points whose x distance is less
|
||||
// than or equal to eps_x, and whose y distance is less
|
||||
// than or equal to eps_y
|
||||
int n_x = 0;
|
||||
int n_y = 0;
|
||||
for (int t2 = 0; t2 < N; t2++) {
|
||||
if (xyNorms[t2][0] <= eps_x) {
|
||||
n_x++;
|
||||
}
|
||||
if (xyNorms[t2][1] <= eps_y) {
|
||||
n_y++;
|
||||
}
|
||||
}
|
||||
int n_x = kdTreeSource.countPointsWithinOrOnR(
|
||||
t, eps_x);
|
||||
int n_y = kdTreeDest.countPointsWithinOrOnR(
|
||||
t, eps_y);
|
||||
|
||||
sumNx += n_x;
|
||||
sumNy += n_y;
|
||||
// And take the digammas:
|
||||
|
|
@ -169,11 +129,4 @@ public class MutualInfoCalculatorMultiVariateKraskov2
|
|||
return new double[] {sumDiGammas, sumNx, sumNy};
|
||||
}
|
||||
}
|
||||
|
||||
public String printConstants(int N) throws Exception {
|
||||
String constants = String.format("digamma(k=%d)=%.3e - 1/k=%.3e + digamma(N=%d)=%.3e => %.3e",
|
||||
k, MathsUtils.digamma(k), 1.0/(double)k, N, MathsUtils.digamma(N),
|
||||
(MathsUtils.digamma(k) - 1.0/(double)k + MathsUtils.digamma(N)));
|
||||
return constants;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ public class EuclideanUtils {
|
|||
public static final String NORM_EUCLIDEAN_NORMALISED_STRING = "EUCLIDEAN_NORMALISED";
|
||||
public static final int NORM_MAX_NORM = 2;
|
||||
public static final String NORM_MAX_NORM_STRING = "MAX_NORM";
|
||||
public static final int NORM_EUCLIDEAN_SQUARED = 3;
|
||||
public static final String NORM_EUCLIDEAN_SQUARED_STRING = "EUCLIDEAN_SQUARED";
|
||||
// Track which norm we should use here
|
||||
private int normToUse = 0;
|
||||
|
||||
|
|
@ -42,12 +44,26 @@ public class EuclideanUtils {
|
|||
* Construct a EuclideanUtils object, to take norms of the given type
|
||||
*
|
||||
* @param normToUse norm type, one of
|
||||
* {@link #NORM_EUCLIDEAN_STRING},
|
||||
* {@link #NORM_EUCLIDEAN_NORMALISED_STRING},
|
||||
* or {@link #NORM_MAX_NORM_STRING}
|
||||
* {@link #NORM_EUCLIDEAN},
|
||||
* {@link #NORM_EUCLIDEAN_NORMALISED},
|
||||
* {@link #NORM_MAX_NORM}
|
||||
* or {@link #NORM_MAX_NORM}
|
||||
*/
|
||||
public EuclideanUtils(int normToUse) {
|
||||
this.normToUse = normToUse;
|
||||
setNormToUse(normToUse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a EuclideanUtils object, to take norms of the given type
|
||||
*
|
||||
* @param normToUse norm type, one of
|
||||
* {@link #NORM_EUCLIDEAN_STRING},
|
||||
* {@link #NORM_EUCLIDEAN_NORMALISED_STRING},
|
||||
* {@link #NORM_MAX_NORM_STRING}
|
||||
* or {@link #NORM_MAX_NORM_STRING}
|
||||
*/
|
||||
public EuclideanUtils(String normToUse) {
|
||||
setNormToUse(normToUse);
|
||||
}
|
||||
|
||||
public static double[] computeMinEuclideanDistances(double[][] observations) {
|
||||
|
|
@ -199,12 +215,38 @@ public class EuclideanUtils {
|
|||
return euclideanNorm(x1, x2) / Math.sqrt(x1.length);
|
||||
case NORM_MAX_NORM:
|
||||
return maxNorm(x1, x2);
|
||||
case NORM_EUCLIDEAN_SQUARED:
|
||||
return euclideanNormSquared(x1, x2);
|
||||
case NORM_EUCLIDEAN:
|
||||
default:
|
||||
return euclideanNorm(x1, x2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computing the configured norm between vectors x1 and x2; if
|
||||
* it becomes clear that norm will be larger than limit,
|
||||
* then return Double.POSITIVE_INFINITY immediately.
|
||||
*
|
||||
* @param x1
|
||||
* @param x2
|
||||
* @param limit
|
||||
* @return
|
||||
*/
|
||||
public double normWithAbort(double[] x1, double[] x2, double limit) {
|
||||
switch (normToUse) {
|
||||
case NORM_EUCLIDEAN_NORMALISED:
|
||||
return euclideanNormWithAbort(x1, x2, limit) / Math.sqrt(x1.length);
|
||||
case NORM_MAX_NORM:
|
||||
return maxNormWithAbort(x1, x2, limit);
|
||||
case NORM_EUCLIDEAN_SQUARED:
|
||||
return euclideanNormSquaredWithAbort(x1, x2, limit);
|
||||
case NORM_EUCLIDEAN:
|
||||
default:
|
||||
return euclideanNormWithAbort(x1, x2, limit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computing the norm as the Euclidean norm.
|
||||
*
|
||||
|
|
@ -220,7 +262,70 @@ public class EuclideanUtils {
|
|||
}
|
||||
return Math.sqrt(distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computing the norm as the Euclidean norm; if
|
||||
* it becomes clear that norm will be larger than limit,
|
||||
* then return Double.POSITIVE_INFINITY immediately.
|
||||
*
|
||||
* @param x1
|
||||
* @param x2
|
||||
* @param limit
|
||||
* @return
|
||||
*/
|
||||
public static double euclideanNormWithAbort(double[] x1, double[] x2, double limit) {
|
||||
double distance = 0.0;
|
||||
limit *= limit;
|
||||
for (int d = 0; d < x1.length; d++) {
|
||||
double difference = x1[d] - x2[d];
|
||||
distance += difference * difference;
|
||||
if (distance > limit) {
|
||||
return Double.POSITIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
return Math.sqrt(distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computing the norm as the Euclidean norm squared
|
||||
* (i.e. avoids taking the square root).
|
||||
*
|
||||
* @param x1
|
||||
* @param x2
|
||||
* @return
|
||||
*/
|
||||
public static double euclideanNormSquared(double[] x1, double[] x2) {
|
||||
double distance = 0.0;
|
||||
for (int d = 0; d < x1.length; d++) {
|
||||
double difference = x1[d] - x2[d];
|
||||
distance += difference * difference;
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computing the norm as the Euclidean norm squared
|
||||
* (i.e. avoids taking the square root); if
|
||||
* it becomes clear that norm will be larger than limit,
|
||||
* then return Double.POSITIVE_INFINITY immediately.
|
||||
*
|
||||
* @param x1 vector 1
|
||||
* @param x2 vector 2
|
||||
* @return
|
||||
*/
|
||||
public static double euclideanNormSquaredWithAbort(
|
||||
double[] x1, double[] x2, double limit) {
|
||||
double distance = 0.0;
|
||||
for (int d = 0; d < x1.length; d++) {
|
||||
double difference = x1[d] - x2[d];
|
||||
distance += difference * difference;
|
||||
if (distance > limit) {
|
||||
return Double.POSITIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computing the norm as the Max norm.
|
||||
*
|
||||
|
|
@ -243,6 +348,34 @@ public class EuclideanUtils {
|
|||
return distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computing the norm as the Max norm; if
|
||||
* it becomes clear that norm will be larger than limit,
|
||||
* then return Double.POSITIVE_INFINITY immediately.
|
||||
*
|
||||
* @param x1 vector 1
|
||||
* @param x2 vector 2
|
||||
* @param limit
|
||||
* @return
|
||||
*/
|
||||
public static double maxNormWithAbort(double[] x1, double[] x2, double limit) {
|
||||
double distance = 0.0;
|
||||
for (int d = 0; d < x1.length; d++) {
|
||||
double difference = x1[d] - x2[d];
|
||||
// Take the abs
|
||||
if (difference < 0) {
|
||||
difference = -difference;
|
||||
}
|
||||
if (difference > distance) {
|
||||
if (difference > limit) {
|
||||
return Double.POSITIVE_INFINITY;
|
||||
}
|
||||
distance = difference;
|
||||
}
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the x and y configured norms of all other points from
|
||||
* the data points at time step t.
|
||||
|
|
@ -353,17 +486,25 @@ public class EuclideanUtils {
|
|||
normToUse = NORM_EUCLIDEAN_NORMALISED;
|
||||
} else if (normType.equalsIgnoreCase(NORM_MAX_NORM_STRING)) {
|
||||
normToUse = NORM_MAX_NORM;
|
||||
} else if (normType.equalsIgnoreCase(NORM_EUCLIDEAN_SQUARED_STRING)) {
|
||||
normToUse = NORM_EUCLIDEAN_SQUARED;
|
||||
} else {
|
||||
normToUse = NORM_EUCLIDEAN;
|
||||
}
|
||||
}
|
||||
|
||||
public String getNormInUse() {
|
||||
public int getNormInUse() {
|
||||
return normToUse;
|
||||
}
|
||||
|
||||
public String getNormInUseString() {
|
||||
switch (normToUse) {
|
||||
case NORM_EUCLIDEAN_NORMALISED:
|
||||
return NORM_EUCLIDEAN_NORMALISED_STRING;
|
||||
case NORM_MAX_NORM:
|
||||
return NORM_MAX_NORM_STRING;
|
||||
case NORM_EUCLIDEAN_SQUARED:
|
||||
return NORM_EUCLIDEAN_SQUARED_STRING;
|
||||
default:
|
||||
case NORM_EUCLIDEAN:
|
||||
return NORM_EUCLIDEAN_STRING;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,889 @@
|
|||
package infodynamics.utils;
|
||||
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
/**
|
||||
* K-d tree implementation to be used for fast neighbour searching
|
||||
* across several (multi-dimensional) variables.
|
||||
* Norms for the nearest neighbour searches are the max norm between
|
||||
* the (multi-dimensional) variables, and either max norm or Euclidean
|
||||
* norm (squared) within each variable.
|
||||
*
|
||||
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
|
||||
* <a href="http://lizier.me/joseph/">www</a>)
|
||||
*
|
||||
* @see <a href="http://en.wikipedia.org/wiki/K-d_tree">K-d tree page on wikipedia</a>
|
||||
*/
|
||||
public class KdTree {
|
||||
|
||||
/**
|
||||
* Cached reference to the data the tree is constructed from.
|
||||
* We have an array of double[][] 2D arrays -- each 2D array
|
||||
* originalDataSets[i] is
|
||||
* considered as a separate (multivariate) variable (indexed by sample
|
||||
* number then dimension number), whilst the set of all such
|
||||
* arrays is considered a joint variable of multivariates.
|
||||
*/
|
||||
protected double[][][] originalDataSets;
|
||||
|
||||
/**
|
||||
* For each dimension dim, sortedArrayIndices[dim] is an array
|
||||
* of indices to the data in sourceObservations and destObservations,
|
||||
* sorted in order (min to max) for the dimension dim;
|
||||
* plus we have a spare dimension for a temporary array.
|
||||
* This is only used in the construction of the kd tree.
|
||||
*/
|
||||
protected int[][] masterSortedArrayIndices = null;
|
||||
|
||||
/**
|
||||
* Maps dimension number (first index) to which
|
||||
* double[][] array holds the data for this dimension,
|
||||
* and which index we use in that array (dimensionToArrayIndex).
|
||||
*
|
||||
* I.e. for a dimension number d (out of the joint variables
|
||||
* across all the multivariates in originalDataSets),
|
||||
* dimensionToArray[d] points to the relevant originalDataSets[i]
|
||||
* multivariate, while dimensionToArrayIndex[d] tells us which
|
||||
* variable within originalDataSets[i] to use, i.e. the time series
|
||||
* originalDataSets[i][t][dimensionToArrayIndex[d]] for time variable t
|
||||
* is the relevant time-series for dimension number d.
|
||||
*/
|
||||
protected double[][][] dimensionToArray = null;
|
||||
protected int[] dimensionToArrayIndex = null;
|
||||
protected int totalDimensions = 0;
|
||||
|
||||
/**
|
||||
* The root node for this k-d tree
|
||||
*/
|
||||
protected KdTreeNode rootNode = null;
|
||||
|
||||
/**
|
||||
* Calculator for computing the norms for each variable; defaults
|
||||
* to a max norm.
|
||||
*/
|
||||
protected EuclideanUtils normCalculator =
|
||||
new EuclideanUtils(EuclideanUtils.NORM_MAX_NORM);
|
||||
|
||||
/**
|
||||
* Protected class to implement nodes of a k-d tree
|
||||
*
|
||||
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
|
||||
* <a href="http://lizier.me/joseph/">www</a>)
|
||||
*/
|
||||
protected class KdTreeNode {
|
||||
protected int indexOfThisPoint;
|
||||
protected KdTreeNode leftTree;
|
||||
protected KdTreeNode rightTree;
|
||||
|
||||
protected KdTreeNode(int indexOfThisPoint, KdTreeNode leftTree,
|
||||
KdTreeNode rightTree) {
|
||||
this.indexOfThisPoint = indexOfThisPoint;
|
||||
this.leftTree = leftTree;
|
||||
this.rightTree = rightTree;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the k-d tree from a set of double[][] data.
|
||||
*
|
||||
* @param data a double[][] 2D data set, first indexed
|
||||
* by time, second index by variable number.
|
||||
*/
|
||||
public KdTree(double[][] data) {
|
||||
this(new int[] {data[0].length}, new double[][][] {data});
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the k-d tree from a <b>set</b> of double[][] data,
|
||||
* considered jointly.
|
||||
*
|
||||
* @param dimensions an array of dimensions for each
|
||||
* of the 2D data sets.
|
||||
* @param data an array of double[][] 2D data sets
|
||||
* for each data[i]
|
||||
* (where i is the main variable number within data, then
|
||||
* after that the first index is sample number, second is dimension
|
||||
* within this data set)
|
||||
*/
|
||||
public KdTree(int[] dimensions, double[][][] data) {
|
||||
|
||||
this.originalDataSets = data;
|
||||
int numObservations = data[0].length;
|
||||
|
||||
// First work out how many dimensions we have in total:
|
||||
totalDimensions = 0;
|
||||
for (int i = 0; i < dimensions.length; i++) {
|
||||
totalDimensions += dimensions[i];
|
||||
}
|
||||
|
||||
// Cache which array and the index within that array
|
||||
// are used for each dimension of the data
|
||||
dimensionToArray = new double[totalDimensions][][];
|
||||
dimensionToArrayIndex = new int[totalDimensions];
|
||||
int cumulativeDimension = 0;
|
||||
int cumulativeDimsionsForPreviousArray = 0;
|
||||
for (int i = 0; i < dimensions.length; i++) {
|
||||
int dimensionsForThisVariable = dimensions[i];
|
||||
for (int j = 0; j < dimensionsForThisVariable; j++) {
|
||||
dimensionToArray[cumulativeDimension] = data[i];
|
||||
dimensionToArrayIndex[cumulativeDimension]
|
||||
= cumulativeDimension - cumulativeDimsionsForPreviousArray;
|
||||
cumulativeDimension++;
|
||||
}
|
||||
cumulativeDimsionsForPreviousArray = cumulativeDimension;
|
||||
}
|
||||
|
||||
// Sort the original data sets in each dimension:
|
||||
double[][] thisDimensionsData = new double[numObservations][2];
|
||||
|
||||
// Create storage for sorted arrays, plus a shared spare temporary
|
||||
// array
|
||||
masterSortedArrayIndices = new int[totalDimensions+1][numObservations];
|
||||
// Sort all the data first
|
||||
for (int i = 0; i < totalDimensions; i++) {
|
||||
// Extract the data for this dimension:
|
||||
double[][] fullData = dimensionToArray[i];
|
||||
MatrixUtils.arrayCopy(fullData, 0, dimensionToArrayIndex[i],
|
||||
thisDimensionsData, 0, 0, numObservations, 1);
|
||||
// Record original time indices:
|
||||
for (int t = 0; t < numObservations; t++) {
|
||||
thisDimensionsData[t][1] = t;
|
||||
}
|
||||
// Sort the data:
|
||||
java.util.Arrays.sort(thisDimensionsData, FirstIndexComparatorDouble.getInstance());
|
||||
// And extract the sorted indices:
|
||||
for (int t = 0; t < numObservations; t++) {
|
||||
masterSortedArrayIndices[i][t] = (int) thisDimensionsData[t][1];
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the k-d tree:masterSortedArrayIndices
|
||||
rootNode = constructKdTree(0, 0, numObservations, masterSortedArrayIndices);
|
||||
// And destroy the temporary storage of sorted array indices:
|
||||
masterSortedArrayIndices = null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param currentDim the dimension that we're currently working with
|
||||
* @param startPoint the index of the first point for us to add here,
|
||||
* in the sorted array of points for currentDim
|
||||
* @param numPoints the number of points for us to add here,
|
||||
* in the sorted array of points for currentDim
|
||||
* @param sortedArrayIndices for each dimension dim, sortedArrayIndices[dim] is an array
|
||||
* of indices to the data in sourceObservations and destObservations,
|
||||
* sorted in order (min to max) for the dimension dim. This is only valid
|
||||
* between startPoint and startPoint + numPoints-1 however; nothing outside
|
||||
* this should be touched. There is one extra dimension here, which may be used
|
||||
* as a temporary array (though again, only between startPoint and
|
||||
* startPoint + numPoints-1 should be touched).
|
||||
* @return
|
||||
*/
|
||||
protected KdTreeNode constructKdTree(int currentDim, int startPoint, int numPoints,
|
||||
int[][] sortedArrayIndices) {
|
||||
// Precondition: sortedArrayIndices[][] are currently sorted for all
|
||||
// dimensions
|
||||
|
||||
// Point to the correct array for the data
|
||||
double[][] data = dimensionToArray[currentDim];
|
||||
int actualDim = dimensionToArrayIndex[currentDim];
|
||||
|
||||
// Handle non-recursive solutions:
|
||||
if (numPoints == 0) {
|
||||
return null;
|
||||
}
|
||||
if (numPoints == 1) {
|
||||
return new KdTreeNode(sortedArrayIndices[currentDim][startPoint], null, null);
|
||||
}
|
||||
if (numPoints == 2) {
|
||||
// Make the first point the splitting point, in case
|
||||
// the two are equal in this dimension, then the left side definitely
|
||||
// contains a strictly less than branch.
|
||||
return new KdTreeNode(sortedArrayIndices[currentDim][startPoint], null,
|
||||
new KdTreeNode(sortedArrayIndices[currentDim][startPoint+1], null, null));
|
||||
}
|
||||
|
||||
// Identify the point on which to split here
|
||||
int candidateSplitPoint = startPoint + numPoints/2;
|
||||
while ((candidateSplitPoint > startPoint) &&
|
||||
(data[sortedArrayIndices[currentDim][candidateSplitPoint-1]][actualDim] ==
|
||||
data[sortedArrayIndices[currentDim][candidateSplitPoint]][actualDim])) {
|
||||
// The adjoining data points are equal in this dimension, so continue searching
|
||||
// for the median with no left points less than it
|
||||
candidateSplitPoint--;
|
||||
}
|
||||
// Postcondition: candidateSplitPoint holds our new median point
|
||||
|
||||
double medianInActualDim = data[sortedArrayIndices[currentDim][candidateSplitPoint]][actualDim];
|
||||
int sampleNumberForSplitPoint = sortedArrayIndices[currentDim][candidateSplitPoint];
|
||||
|
||||
int leftStart = startPoint;
|
||||
int leftNumPoints = candidateSplitPoint-startPoint;
|
||||
int rightStart = candidateSplitPoint+1;
|
||||
int rightNumPoints = startPoint+numPoints-1-candidateSplitPoint;
|
||||
|
||||
// Partition the other dimensions properly.
|
||||
int[][] newSortedArrayIndices = new int[totalDimensions+1][];
|
||||
// Grab the temporary array for us to use:
|
||||
int[] tempSortedIndices = sortedArrayIndices[totalDimensions];
|
||||
for (int dim = 0; dim < totalDimensions; dim++) {
|
||||
if (dim == currentDim) {
|
||||
newSortedArrayIndices[dim] = sortedArrayIndices[dim];
|
||||
continue;
|
||||
}
|
||||
int leftIndex = leftStart, rightIndex = rightStart;
|
||||
for (int i = startPoint; i < startPoint + numPoints; i++) {
|
||||
int sampleNumberInData = sortedArrayIndices[dim][i];
|
||||
if (sampleNumberInData == sampleNumberForSplitPoint) {
|
||||
// This is the split point
|
||||
continue;
|
||||
}
|
||||
// Check if this data point
|
||||
// was going to the left or right tree
|
||||
if (data[sampleNumberInData][actualDim] < medianInActualDim) {
|
||||
// This point will be in the left tree
|
||||
tempSortedIndices[leftIndex++] = sampleNumberInData;
|
||||
} else {
|
||||
// This point will be in the right tree
|
||||
tempSortedIndices[rightIndex++] = sampleNumberInData;
|
||||
}
|
||||
}
|
||||
// Check that we have not exceeded boundaries for either
|
||||
// left or right sets:
|
||||
// Could remove these since the code is now functional,
|
||||
// but may be better to leave them in just in case the code breaks:
|
||||
if (leftIndex > leftStart + leftNumPoints) {
|
||||
throw new RuntimeException("Exceeded expected number of points on left");
|
||||
}
|
||||
if (rightIndex > rightStart + rightNumPoints) {
|
||||
throw new RuntimeException("Exceeded expected number of points on right");
|
||||
}
|
||||
// Update the pointer for the sorted indices for this dimension,
|
||||
// and keep the new temporary array
|
||||
int[] temp = sortedArrayIndices[dim]; // Old array to become tempSortedIndices
|
||||
newSortedArrayIndices[dim] = tempSortedIndices;
|
||||
tempSortedIndices = temp;
|
||||
}
|
||||
newSortedArrayIndices[totalDimensions] = tempSortedIndices;
|
||||
|
||||
int newDim = (currentDim + 1) % totalDimensions;
|
||||
return new KdTreeNode(sampleNumberForSplitPoint,
|
||||
constructKdTree(newDim, leftStart, leftNumPoints, newSortedArrayIndices),
|
||||
constructKdTree(newDim, rightStart, rightNumPoints, newSortedArrayIndices));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the norm type to use in the nearest neighbour searches,
|
||||
* within each joint variable, to normType.
|
||||
*
|
||||
* @param normType norm type to use; must be either
|
||||
* {@link EuclideanUtils#NORM_EUCLIDEAN},
|
||||
* {@link EuclideanUtils#NORM_EUCLIDEAN_SQUARED} or
|
||||
* {@link EuclideanUtils#NORM_MAX_NORM}, otherwise an
|
||||
* UnsupportedOperationException is thrown.
|
||||
* {@link EuclideanUtils#NORM_EUCLIDEAN} will be nominally supported
|
||||
* but switched to
|
||||
* {@link EuclideanUtils#NORM_EUCLIDEAN_SQUARED} internally for speed.
|
||||
* @throws UnsupportedOperationException if the norm type is not
|
||||
* one of the above supported options.
|
||||
*/
|
||||
public void setNormType(int normType) {
|
||||
if ((normType != EuclideanUtils.NORM_EUCLIDEAN) &&
|
||||
(normType != EuclideanUtils.NORM_EUCLIDEAN_SQUARED) &&
|
||||
(normType != EuclideanUtils.NORM_MAX_NORM)) {
|
||||
throw new UnsupportedOperationException("Norm type " + normType +
|
||||
" is not supported in KdTree");
|
||||
}
|
||||
if (normType == EuclideanUtils.NORM_EUCLIDEAN) {
|
||||
normType = EuclideanUtils.NORM_EUCLIDEAN_SQUARED;
|
||||
}
|
||||
normCalculator.setNormToUse(normType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the norm type to use within each joint variable to normType.
|
||||
*
|
||||
* @param normType norm type to use; must be either
|
||||
* {@link EuclideanUtils#NORM_EUCLIDEAN_STRING},
|
||||
* {@link EuclideanUtils#NORM_EUCLIDEAN_SQUARED_STRING} or
|
||||
* {@link EuclideanUtils#NORM_MAX_NORM_STRING}, otherwise an
|
||||
* UnsupportedOperationException is thrown.
|
||||
* {@link EuclideanUtils#NORM_EUCLIDEAN} will be nominally supported
|
||||
* but switched to
|
||||
* {@link EuclideanUtils#NORM_EUCLIDEAN_SQUARED} internally for speed.
|
||||
* @throws UnsupportedOperationException if the norm type is not
|
||||
* one of the above supported options.
|
||||
*/
|
||||
public void setNormType(String normType) {
|
||||
if (!normType.equalsIgnoreCase(EuclideanUtils.NORM_EUCLIDEAN_STRING) &&
|
||||
!normType.equalsIgnoreCase(EuclideanUtils.NORM_EUCLIDEAN_SQUARED_STRING) &&
|
||||
!normType.equalsIgnoreCase(EuclideanUtils.NORM_MAX_NORM_STRING)) {
|
||||
throw new UnsupportedOperationException("Norm type " + normType +
|
||||
" is not supported in KdTree");
|
||||
}
|
||||
if (normType.equalsIgnoreCase(EuclideanUtils.NORM_EUCLIDEAN_STRING)) {
|
||||
normType = EuclideanUtils.NORM_EUCLIDEAN_SQUARED_STRING;
|
||||
}
|
||||
normCalculator.setNormToUse(normType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate whether a specified norm type is supported,
|
||||
* and return the int corresponding to that type,
|
||||
* otherwise through an exception.
|
||||
*
|
||||
* @param normType norm type to use; must be either
|
||||
* {@link EuclideanUtils#NORM_EUCLIDEAN_STRING},
|
||||
* {@link EuclideanUtils#NORM_EUCLIDEAN_SQUARED_STRING} or
|
||||
* {@link EuclideanUtils#NORM_MAX_NORM_STRING}, otherwise an
|
||||
* UnsupportedOperationException is thrown.
|
||||
* {@link EuclideanUtils#NORM_EUCLIDEAN} will be nominally supported
|
||||
* but switched to
|
||||
* {@link EuclideanUtils#NORM_EUCLIDEAN_SQUARED} internally for speed.
|
||||
* @throws UnsupportedOperationException if the norm type is not
|
||||
* one of the above supported options.
|
||||
*/
|
||||
public static int validateNormType(String normType) {
|
||||
if (normType.equalsIgnoreCase(EuclideanUtils.NORM_EUCLIDEAN_STRING)) {
|
||||
normType = EuclideanUtils.NORM_EUCLIDEAN_SQUARED_STRING;
|
||||
}
|
||||
if (normType.equalsIgnoreCase(EuclideanUtils.NORM_EUCLIDEAN_SQUARED_STRING)) {
|
||||
return EuclideanUtils.NORM_EUCLIDEAN_SQUARED;
|
||||
}
|
||||
if (normType.equalsIgnoreCase(EuclideanUtils.NORM_MAX_NORM_STRING)) {
|
||||
return EuclideanUtils.NORM_MAX_NORM;
|
||||
}
|
||||
throw new UnsupportedOperationException("Norm type " + normType +
|
||||
" is not supported in KdTree");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the node which is the nearest neighbour for a given
|
||||
* sample index in the data set. The node itself is
|
||||
* excluded from the search.
|
||||
* Nearest neighbour function to compare to r is a max norm between the
|
||||
* high-level variables, with norm for each variable being the specified norm.
|
||||
*
|
||||
* @param sampleIndex sample index in the data to find a nearest neighbour
|
||||
* for
|
||||
* @return the node for the nearest neighbour.
|
||||
*/
|
||||
public NeighbourNodeData findNearestNeighbour(int sampleIndex) {
|
||||
if (rootNode == null) {
|
||||
return null;
|
||||
}
|
||||
return findNearestNeighbour(sampleIndex, rootNode, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest neighbour to a given sample (sampleIndex), in the tree
|
||||
* rooted at node (which is at the specified level in the tree), or
|
||||
* return currentBest if no better match is found.
|
||||
* Nearest neighbour is a max norm between the high-level variables,
|
||||
* with norm for each variable being the specified norm.
|
||||
*
|
||||
*
|
||||
* @param sampleIndex sample index in the data to find a nearest neighbour
|
||||
* for
|
||||
* @param node node to start searching from in the kd-tree. Cannot be null
|
||||
* @param level which level we're currently at in the tree
|
||||
* @param currentBest a NeighbourNodeData structure capturing the current
|
||||
* closest neighbour and its distance
|
||||
* @return the node data for the nearest neighbour.
|
||||
*/
|
||||
protected NeighbourNodeData findNearestNeighbour(int sampleIndex,
|
||||
KdTreeNode node, int level, NeighbourNodeData currentBest) {
|
||||
|
||||
// Point to the correct array for the data at this level
|
||||
int currentDim = level % totalDimensions;
|
||||
double[][] data = dimensionToArray[currentDim];
|
||||
int actualDim = dimensionToArrayIndex[currentDim];
|
||||
|
||||
// Check the distance on this particular dimension
|
||||
double distOnThisDim = data[sampleIndex][actualDim] -
|
||||
data[node.indexOfThisPoint][actualDim];
|
||||
|
||||
double absDistOnThisDim;
|
||||
if (normCalculator.getNormInUse() == EuclideanUtils.NORM_MAX_NORM) {
|
||||
absDistOnThisDim = (distOnThisDim > 0) ? distOnThisDim : - distOnThisDim;
|
||||
} else {
|
||||
// norm type is EuclideanUtils#NORM_EUCLIDEAN_SQUARED
|
||||
// Track the square distance (this saves taking square roots anywhere)
|
||||
absDistOnThisDim = distOnThisDim * distOnThisDim;
|
||||
}
|
||||
|
||||
if ((node.indexOfThisPoint != sampleIndex) &&
|
||||
((currentBest == null) || (absDistOnThisDim < currentBest.distance))) {
|
||||
// Preliminary check says we need to compute the full distance
|
||||
// to use or at least to check if it should become our
|
||||
// currentBest properly.
|
||||
double maxNorm = 0;
|
||||
double[] norms = new double[originalDataSets.length];
|
||||
for (int v = 0; v < originalDataSets.length; v++) {
|
||||
// For each of our separate (multivariate) variables,
|
||||
// compute the (specified) norm in that variable's space:
|
||||
if (currentBest == null) {
|
||||
norms[v] = normCalculator.norm(
|
||||
originalDataSets[v][sampleIndex],
|
||||
originalDataSets[v][node.indexOfThisPoint]);
|
||||
} else {
|
||||
// Distance calculation terminates early with Double.POSITIVE_INFINITY
|
||||
// if it is clearly larger than currentBest.distance:
|
||||
norms[v] = normCalculator.normWithAbort(
|
||||
originalDataSets[v][sampleIndex],
|
||||
originalDataSets[v][node.indexOfThisPoint],
|
||||
currentBest.distance);
|
||||
}
|
||||
if (norms[v] > maxNorm) {
|
||||
maxNorm = norms[v];
|
||||
if (Double.isInfinite(maxNorm)) {
|
||||
// we've aborted the norm check early;
|
||||
// no point checking the other variables.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((currentBest == null) ||
|
||||
(maxNorm < currentBest.distance)) {
|
||||
// We set this as the current nearest neighbour:
|
||||
currentBest = new NeighbourNodeData(node.indexOfThisPoint,
|
||||
norms, maxNorm);
|
||||
}
|
||||
}
|
||||
|
||||
KdTreeNode closestSubTree = null;
|
||||
KdTreeNode furthestSubTree = null;
|
||||
// And translate this to which subtree is closer
|
||||
if (distOnThisDim < 0) {
|
||||
// We need to search the left tree
|
||||
closestSubTree = node.leftTree;
|
||||
furthestSubTree = node.rightTree;
|
||||
} else {
|
||||
// We need to search the right tree
|
||||
closestSubTree = node.rightTree;
|
||||
furthestSubTree = node.leftTree;
|
||||
}
|
||||
// Update the search on that subtree
|
||||
if (closestSubTree != null) {
|
||||
currentBest = findNearestNeighbour(sampleIndex, closestSubTree,
|
||||
level + 1, currentBest);
|
||||
}
|
||||
if ((currentBest == null) || (absDistOnThisDim < currentBest.distance)) {
|
||||
// It's possible we could have a closer node than the current best
|
||||
// in the other branch as well, so search there too.
|
||||
// (It's highly unlikely we would still have (currentBest == null)
|
||||
// here but remains possible, so we check that)
|
||||
if (furthestSubTree != null) {
|
||||
currentBest = findNearestNeighbour(sampleIndex, furthestSubTree,
|
||||
level + 1, currentBest);
|
||||
}
|
||||
}
|
||||
|
||||
return currentBest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the K nodes which are the K nearest neighbours for a given
|
||||
* sample index in the data set. The node itself is
|
||||
* excluded from the search.
|
||||
* Nearest neighbour function to compare to r is a max norm between the
|
||||
* high-level variables, with norm for each variable being the specified norm.
|
||||
*
|
||||
* @param K number of K nearest neighbours to return, sorted from
|
||||
* furthest away first to nearest last.
|
||||
* @param sampleIndex sample index in the data to find the K nearest neighbours
|
||||
* for
|
||||
* @return a PriorityQueue of nodes for the K nearest neighbours,
|
||||
* sorted with furthest neighbour first in the PQ.
|
||||
*/
|
||||
public PriorityQueue<NeighbourNodeData>
|
||||
findKNearestNeighbours(int K, int sampleIndex) {
|
||||
|
||||
PriorityQueue<NeighbourNodeData> pq = new PriorityQueue<NeighbourNodeData>(K);
|
||||
if (rootNode == null) {
|
||||
return pq;
|
||||
}
|
||||
findKNearestNeighbours(K, sampleIndex, rootNode, 0, pq);
|
||||
return pq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected method to Update the k nearest neighbours to a given sample (sampleIndex), from the tree
|
||||
* rooted at node (which is at the specified level in the tree).
|
||||
* Incorporate neighbours found in this sub-tree into the PriorityQueue
|
||||
* of the current K closest.
|
||||
* @param K the number of nearest neighbour
|
||||
* @param sampleIndex sample index in the data to find a nearest neighbour
|
||||
* for
|
||||
* @param node node to start searching from in the kd-tree. Cannot be null
|
||||
* @param level which level we're currently at in the tree
|
||||
* @param currentKBest a PriorityQueue of NeighbourNodeData objects
|
||||
* capturing the current K closest neighbours and their distances.
|
||||
* Assumed not to be null, but may be empty or with less than K elements
|
||||
* so far. It must be sorted from furthest away first to nearest last.
|
||||
*/
|
||||
protected void findKNearestNeighbours(int K,
|
||||
int sampleIndex, KdTreeNode node, int level,
|
||||
PriorityQueue<NeighbourNodeData> currentKBest) {
|
||||
|
||||
// Point to the correct array for the data at this level
|
||||
int currentDim = level % totalDimensions;
|
||||
double[][] data = dimensionToArray[currentDim];
|
||||
int actualDim = dimensionToArrayIndex[currentDim];
|
||||
|
||||
// Check the distance on this particular dimension
|
||||
double distOnThisDim = data[sampleIndex][actualDim] -
|
||||
data[node.indexOfThisPoint][actualDim];
|
||||
double absDistOnThisDim;
|
||||
if (normCalculator.getNormInUse() == EuclideanUtils.NORM_MAX_NORM) {
|
||||
absDistOnThisDim = (distOnThisDim > 0) ? distOnThisDim : - distOnThisDim;
|
||||
} else {
|
||||
// norm type is EuclideanUtils#NORM_EUCLIDEAN_SQUARED
|
||||
// Track the square distance (this saves taking square roots anywhere)
|
||||
absDistOnThisDim = distOnThisDim * distOnThisDim;
|
||||
}
|
||||
|
||||
// Grab the current furthest nearest neighbour in our cached list
|
||||
// (will not throw an Exception if the PQ is empty)
|
||||
NeighbourNodeData furthestCached = currentKBest.peek();
|
||||
|
||||
if ((node.indexOfThisPoint != sampleIndex) &&
|
||||
((currentKBest.size() < K) || (absDistOnThisDim < furthestCached.distance))) {
|
||||
// Preliminary check says we need to compute the full distance
|
||||
// to use or at least to check if it should be
|
||||
// added to our currentKBest properly.
|
||||
double maxNorm = 0;
|
||||
double[] norms = new double[originalDataSets.length];
|
||||
for (int v = 0; v < originalDataSets.length; v++) {
|
||||
// For each of our separate (multivariate) variables,
|
||||
// compute the (specified) norm in that variable's space:
|
||||
if (currentKBest.size() < K) {
|
||||
norms[v] = normCalculator.norm(
|
||||
originalDataSets[v][sampleIndex],
|
||||
originalDataSets[v][node.indexOfThisPoint]);
|
||||
} else {
|
||||
// Distance calculation terminates early with Double.POSITIVE_INFINITY
|
||||
// if it is clearly larger than currentBest.distance:
|
||||
norms[v] = normCalculator.normWithAbort(
|
||||
originalDataSets[v][sampleIndex],
|
||||
originalDataSets[v][node.indexOfThisPoint],
|
||||
furthestCached.distance);
|
||||
}
|
||||
if (norms[v] > maxNorm) {
|
||||
maxNorm = norms[v];
|
||||
if (Double.isInfinite(maxNorm)) {
|
||||
// we've aborted the norm check early;
|
||||
// no point checking the other variables.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((currentKBest.size() < K) ||
|
||||
(maxNorm < furthestCached.distance)) {
|
||||
// We add this to our cache of K nearest neighbours:
|
||||
if (currentKBest.size() == K) {
|
||||
// Remove the current Kth nearest neighbour
|
||||
// as it is about to be replaced.
|
||||
currentKBest.poll();
|
||||
}
|
||||
currentKBest.add(new NeighbourNodeData(node.indexOfThisPoint,
|
||||
norms, maxNorm));
|
||||
}
|
||||
}
|
||||
|
||||
KdTreeNode closestSubTree = null;
|
||||
KdTreeNode furthestSubTree = null;
|
||||
// And translate this to which subtree is closer
|
||||
if (distOnThisDim < 0) {
|
||||
// We need to search the left tree
|
||||
closestSubTree = node.leftTree;
|
||||
furthestSubTree = node.rightTree;
|
||||
} else {
|
||||
// We need to search the right tree
|
||||
closestSubTree = node.rightTree;
|
||||
furthestSubTree = node.leftTree;
|
||||
}
|
||||
// Update the search on that subtree
|
||||
if (closestSubTree != null) {
|
||||
findKNearestNeighbours(K, sampleIndex,
|
||||
closestSubTree, level + 1, currentKBest);
|
||||
}
|
||||
// Grab the current furthest nearest neighbour in our cached list again
|
||||
// (will not throw an Exception if the PQ is empty)
|
||||
// as it may have been changed above:
|
||||
furthestCached = currentKBest.peek();
|
||||
if ((currentKBest.size() < K) || (absDistOnThisDim < furthestCached.distance)) {
|
||||
// It's possible we could have a closer node than the current best
|
||||
// in the other branch as well, so search there too:
|
||||
if (furthestSubTree != null) {
|
||||
findKNearestNeighbours(K, sampleIndex,
|
||||
furthestSubTree, level + 1, currentKBest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected class for caching nearest neighbour values during the search
|
||||
*
|
||||
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
|
||||
* <a href="http://lizier.me/joseph/">www</a>)
|
||||
*/
|
||||
public class NeighbourNodeData implements Comparable<NeighbourNodeData> {
|
||||
public int sampleIndex;
|
||||
public double[] norms; // norms in each high-level variable
|
||||
public double distance; // Assertion: distance is the max of norms
|
||||
|
||||
/**
|
||||
* Create an instance representing data about one given nearest neighbour
|
||||
* to another data point.
|
||||
*
|
||||
*
|
||||
* @param sampleIndex index of the neighbour
|
||||
* @param norms norms between the neighbour and the other data point,
|
||||
* for each high-level variable.
|
||||
* @param distance the max of the norms (used for sorting
|
||||
* NeighbourNodeData objects in a PriorityQueue)
|
||||
*/
|
||||
public NeighbourNodeData(int sampleIndex, double[] norms, double distance) {
|
||||
super();
|
||||
this.norms = norms;
|
||||
this.sampleIndex = sampleIndex;
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override's {@link Comparable#compareTo(Object)} to provide
|
||||
* a natural comparison for the NeighbourNodeData class,
|
||||
* based on the underlying distance member.
|
||||
*
|
||||
* <p><b>IMPORTANT</b> -- we reverse the usual comparison return
|
||||
* values, returning a positive number if other is greater,
|
||||
* and a negative number if other is less. This is so that a
|
||||
* {@link java.util.PriorityQueue} of NeighbourNodeData objects
|
||||
* will hold that with the largest distance as the head (whereas
|
||||
* for standard return values from this method it would be
|
||||
* the other way around).
|
||||
*
|
||||
* @param other Other NeighbourNodeData to compare to
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(NeighbourNodeData other) {
|
||||
if (distance < other.distance) {
|
||||
// Normally would return -1 here but we flip it -- see
|
||||
// header comments
|
||||
return 1;
|
||||
} else if (distance > other.distance) {
|
||||
// Normally would return +1 here but we flip it -- see
|
||||
// header comments
|
||||
return -1;
|
||||
}
|
||||
// distances are equal
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of points within norm r for a given
|
||||
* sample index in the data set. The node itself is
|
||||
* excluded from the search.
|
||||
* Nearest neighbour function to compare to r is a max norm between the
|
||||
* high-level variables, with norm for each variable being the specified norm.
|
||||
*
|
||||
* @param sampleIndex sample index in the data to find a nearest neighbour
|
||||
* for
|
||||
* @param r radius within which to count points
|
||||
* @param allowEqualToR if true, then count points at radius r also,
|
||||
* otherwise only those strictly within r
|
||||
* @return the count of points within r.
|
||||
*/
|
||||
public int countPointsWithinR(int sampleIndex, double r, boolean allowEqualToR) {
|
||||
if (allowEqualToR) {
|
||||
return countPointsWithinOrOnR(sampleIndex, r);
|
||||
} else {
|
||||
return countPointsStrictlyWithinR(sampleIndex, r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of points strictly within norm r for a given
|
||||
* sample index in the data set. The node itself is
|
||||
* excluded from the search.
|
||||
* Nearest neighbour function to compare to r is a max norm between the
|
||||
* high-level variables, with norm for each variable being the specified norm.
|
||||
*
|
||||
* @param sampleIndex sample index in the data to find a nearest neighbour
|
||||
* for
|
||||
* @param r radius within which to count points
|
||||
* @return the count of points within r.
|
||||
*/
|
||||
public int countPointsStrictlyWithinR(int sampleIndex, double r) {
|
||||
if (rootNode == null) {
|
||||
return 0;
|
||||
}
|
||||
return countPointsWithinR(sampleIndex, rootNode, 0, r, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of points within or at norm r for a given
|
||||
* sample index in the data set. The node itself is
|
||||
* excluded from the search.
|
||||
* Nearest neighbour function to compare to r is a max norm between the
|
||||
* high-level variables, with norm for each variable being the specified norm.
|
||||
*
|
||||
* @param sampleIndex sample index in the data to find a nearest neighbour
|
||||
* for
|
||||
* @param r radius within which to count points
|
||||
* @return the count of points within or on r.
|
||||
*/
|
||||
public int countPointsWithinOrOnR(int sampleIndex, double r) {
|
||||
if (rootNode == null) {
|
||||
return 0;
|
||||
}
|
||||
return countPointsWithinR(sampleIndex, rootNode, 0, r, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of points within radius r of a given sample (sampleIndex),
|
||||
* in the tree rooted at node (which is at the specified level in the tree).
|
||||
* Nearest neighbour function to compare to r is a max norm between the
|
||||
* high-level variables, with norm for each variable being the specified norm.
|
||||
*
|
||||
* @param sampleIndex sample index in the data to find a nearest neighbour
|
||||
* for
|
||||
* @param node node to start searching from in the kd-tree. Cannot be null
|
||||
* @param level which level we're currently at in the tree
|
||||
* @param r radius within which to count points
|
||||
* @param allowEqualToR if true, then count points at radius r also,
|
||||
* otherwise only those strictly within r
|
||||
* @return count of points within r
|
||||
*/
|
||||
protected int countPointsWithinR(int sampleIndex,
|
||||
KdTreeNode node, int level, double r, boolean allowEqualToR) {
|
||||
|
||||
int count = 0;
|
||||
|
||||
// Point to the correct array for the data at this level
|
||||
int currentDim = level % totalDimensions;
|
||||
double[][] data = dimensionToArray[currentDim];
|
||||
int actualDim = dimensionToArrayIndex[currentDim];
|
||||
|
||||
// Check the distance on this particular dimension
|
||||
double distOnThisDim = data[sampleIndex][actualDim] -
|
||||
data[node.indexOfThisPoint][actualDim];
|
||||
|
||||
double absDistOnThisDim;
|
||||
if (normCalculator.getNormInUse() == EuclideanUtils.NORM_MAX_NORM) {
|
||||
absDistOnThisDim = (distOnThisDim > 0) ? distOnThisDim : - distOnThisDim;
|
||||
} else {
|
||||
// norm type is EuclideanUtils#NORM_EUCLIDEAN_SQUARED
|
||||
// Track the square distance
|
||||
absDistOnThisDim = distOnThisDim * distOnThisDim;
|
||||
}
|
||||
|
||||
if ((node.indexOfThisPoint != sampleIndex) &&
|
||||
((absDistOnThisDim < r) ||
|
||||
( allowEqualToR && (absDistOnThisDim == r)))) {
|
||||
// Preliminary check says we need to compute the full distance
|
||||
// to use or at least to check if it should be counted.
|
||||
double maxNorm = 0;
|
||||
for (int v = 0; v < originalDataSets.length; v++) {
|
||||
// For each of our separate (multivariate) variables,
|
||||
// compute the (specified) norm in that variable's space:
|
||||
double distForVariableV;
|
||||
// Distance calculation terminates early with Double.POSITIVE_INFINITY
|
||||
// if it is clearly larger than currentBest.distance:
|
||||
distForVariableV = normCalculator.normWithAbort(
|
||||
originalDataSets[v][sampleIndex],
|
||||
originalDataSets[v][node.indexOfThisPoint],
|
||||
r);
|
||||
if (distForVariableV > maxNorm) {
|
||||
maxNorm = distForVariableV;
|
||||
if (Double.isInfinite(maxNorm)) {
|
||||
// we've aborted the norm check early;
|
||||
// no point checking the other variables.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((maxNorm < r) ||
|
||||
( allowEqualToR && (maxNorm == r))) {
|
||||
// This node gets counted
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
KdTreeNode closestSubTree = null;
|
||||
KdTreeNode furthestSubTree = null;
|
||||
// And translate this to which subtree is closer
|
||||
if (distOnThisDim < 0) {
|
||||
// We need to search the left tree
|
||||
closestSubTree = node.leftTree;
|
||||
furthestSubTree = node.rightTree;
|
||||
} else {
|
||||
// We need to search the right tree
|
||||
closestSubTree = node.rightTree;
|
||||
furthestSubTree = node.leftTree;
|
||||
}
|
||||
// Update the search on that subtree
|
||||
if (closestSubTree != null) {
|
||||
count += countPointsWithinR(sampleIndex, closestSubTree,
|
||||
level + 1, r, allowEqualToR);
|
||||
}
|
||||
if ((absDistOnThisDim < r) ||
|
||||
( allowEqualToR && (distOnThisDim < 0) && (absDistOnThisDim == r))) {
|
||||
// It's possible we could have a node within (or on) r
|
||||
// in the other branch as well, so search there too.
|
||||
// (Note: we only check furthest subtree in the == case
|
||||
// when it's allowed
|
||||
// *if* it's the right subtree, as only the right sub-tree
|
||||
// can have node with distance in this coordinate *equal* to
|
||||
// that of the current node -- left subtree must be strictly
|
||||
// less than the coordinate of the current node, so
|
||||
// distance to any of those points could not be equal.)
|
||||
if (furthestSubTree != null) {
|
||||
count += countPointsWithinR(sampleIndex, furthestSubTree,
|
||||
level + 1, r, allowEqualToR);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal utility function for debug printing of a tree
|
||||
*
|
||||
*/
|
||||
public void print() {
|
||||
print(rootNode, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal utility function for debug printing of a node and
|
||||
* all of its descendants
|
||||
*
|
||||
* @param node current node
|
||||
* @param level which level we're at in the tree
|
||||
*/
|
||||
protected void print(KdTreeNode node, int level) {
|
||||
if (node == null) {
|
||||
System.out.print("null");
|
||||
return;
|
||||
}
|
||||
if (level > 0) {
|
||||
System.out.println();
|
||||
}
|
||||
for (int i = 0; i < level; i++) {
|
||||
System.out.print("\t");
|
||||
}
|
||||
System.out.print("((");
|
||||
for (int i = 0; i < totalDimensions; i++) {
|
||||
System.out.printf("%.3f,",
|
||||
dimensionToArray[i][node.indexOfThisPoint][dimensionToArrayIndex[i]]);
|
||||
}
|
||||
System.out.print("),");
|
||||
print(node.leftTree, level+1);
|
||||
System.out.print(", ");
|
||||
print(node.rightTree, level+1);
|
||||
System.out.println(")");
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ public class MathsUtils {
|
|||
|
||||
private static int highestDigammaArgCalced = 0;
|
||||
private static final int NUM_STORED_DIGAMMAS = 10000; // commons.math to handle beyond this
|
||||
private static double[] storedDigammas = new double[0];
|
||||
private static double[] storedDigammas = new double[NUM_STORED_DIGAMMAS];
|
||||
|
||||
/**
|
||||
* Returns the integer result of base^power
|
||||
|
|
@ -272,23 +272,11 @@ public class MathsUtils {
|
|||
if (d < 1) {
|
||||
return Double.NaN;
|
||||
}
|
||||
if (storedDigammas.length == 0) {
|
||||
synchronized(storedDigammas) { // Ensure no race condition here
|
||||
// We do two checks on whether the storage has been
|
||||
// created so that the first is very fast (without
|
||||
// requiring synchronization), and the second
|
||||
// ensures no race condition.
|
||||
if (storedDigammas.length == 0) {
|
||||
// Using length == 0 as proxy to null, since
|
||||
// we can't synchronize on a null object
|
||||
|
||||
// allocate space to store our results
|
||||
storedDigammas = new double[NUM_STORED_DIGAMMAS];
|
||||
storedDigammas[0] = Double.NaN;
|
||||
storedDigammas[1] = -EULER_MASCHERONI_CONSTANT;
|
||||
highestDigammaArgCalced = 1;
|
||||
}
|
||||
}
|
||||
if (highestDigammaArgCalced == 0) {
|
||||
// We're not initialised yet:
|
||||
storedDigammas[0] = Double.NaN;
|
||||
storedDigammas[1] = -EULER_MASCHERONI_CONSTANT;
|
||||
highestDigammaArgCalced = 1;
|
||||
}
|
||||
if (d <= highestDigammaArgCalced) {
|
||||
// We've already calculated this one
|
||||
|
|
|
|||
|
|
@ -2524,10 +2524,11 @@ public class MatrixUtils {
|
|||
/**
|
||||
* Works out the index of the k minimum values in the matrix in a given column
|
||||
*
|
||||
* @param matrix
|
||||
* @param column
|
||||
* @param k
|
||||
* @return
|
||||
* @param matrix data
|
||||
* @param column which column of the data to find the min values from
|
||||
* @param k how many min values to return
|
||||
* @return an array of the (row) indices in the array with the k min values,
|
||||
* with closest match first.
|
||||
* @throws Exception
|
||||
*/
|
||||
public static int[] kMinIndices(double[][] matrix, int column, int k) throws Exception {
|
||||
|
|
@ -2568,7 +2569,7 @@ public class MatrixUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Return the index of the kth min
|
||||
// Return the indices of the k mins
|
||||
return minIndices;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue