jidt/java/source/infodynamics/measures/continuous/kernel/KernelEstimatorMultiVariate...

1050 lines
38 KiB
Java
Executable File

/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2012, Joseph T. Lizier
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package infodynamics.measures.continuous.kernel;
import infodynamics.utils.MatrixUtils;
import java.util.Arrays;
import java.util.Vector;
import java.util.Hashtable;
/**
* <p>Class to maintain probability distribution function for
* a multivariate set, using kernel estimates.</p>
*
* <p>
* For more details on kernel estimation for computing probability distribution functions,
* see Kantz and Schreiber (below).
* </p>
*
* <p>
* TODO More thoroughly check the Javadocs here
* </p>
*
* <p><b>References:</b><br/>
* <ul>
* <li>H. Kantz and T. Schreiber, "Nonlinear Time Series Analysis"
* (Cambridge University Press, Cambridge, MA, 1997).</li>
* </ul>
*
* @see KernelEstimatorUniVariate
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
public class KernelEstimatorMultiVariate implements Cloneable {
protected double[] suppliedKernelWidths = null;
protected double[] kernelWidthsInUse = null;
protected double kernelVolumeInUse = 0;
protected int dimensions = 1;
private int[] bins = null;
private boolean usingIntegerIndexBins = true;
private boolean useBins = true;
private double[] mins = null;
private int[] multipliers = null;
private int totalObservations = 0;
private Vector<TimeStampedObservation>[] observations = null;
private Hashtable<IntArray, Vector<TimeStampedObservation>> observationsByHash = null;
protected double[][] rawData = null;
private boolean debug;
// Overriding classes should set this to true if they want a callback for
// every point that is found to be correlated in time;
protected boolean makeCorrelatedPointAddedCallback = false;
protected boolean normalise = true;
private boolean excludeDynamicCorrelations = false;
private int timeProximityForDynamicCorrelationExclusion = 100;
// Force the kernel estimator to compare each point to every other rather
// than use fast bin techniques
private boolean forceCompareToAll = false;
private final static int MAX_NUMBER_INTEGER_BINS = 1000000;
/**
*
* Private class to store a time-stamped data point.
* This allows us to eliminate dynamic correlations later.
*
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
private class TimeStampedObservation {
public int timeStep;
public double[] observation;
TimeStampedObservation(int time, double[] data) {
timeStep = time;
observation = data;
}
}
/**
* Wrapper class for an integer array so we can hash it properly
*
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
private class IntArray {
public int[] array;
public IntArray(int[] array) {
this.array = array;
}
@Override
public int hashCode() {
return Arrays.hashCode(array);
}
@Override
public boolean equals(Object obj) {
if (!IntArray.class.isInstance(obj)) {
return false;
}
IntArray other = (IntArray) obj;
return Arrays.equals(array, other.array);
}
}
/**
* Empty constructor
*
*/
public KernelEstimatorMultiVariate() {
}
/**
* Initialise the estimator before passing any observations in.
*
* @param kernelWidth
* @param dimensions
*/
public void initialise(int dimensions, double kernelWidth) {
this.dimensions = dimensions;
this.suppliedKernelWidths = new double[dimensions];
for (int d = 0; d < dimensions; d++) {
this.suppliedKernelWidths[d] = kernelWidth;
}
finishInitialisation();
}
/**
* Initialise the estimator before passing any observations in.
*
* @param kernelWidths
*/
public void initialise(double[] kernelWidths) {
dimensions = kernelWidths.length;
this.suppliedKernelWidths = new double[dimensions];
for (int d = 0; d < dimensions; d++) {
this.suppliedKernelWidths[d] = kernelWidths[d];
}
finishInitialisation();
}
/**
* Private method to provide common variable initialisation
*
*/
private void finishInitialisation() {
observations = null;
observationsByHash = null;
bins = null;
useBins = true;
usingIntegerIndexBins = true;
mins = null;
multipliers = null;
rawData = null;
totalObservations = 0;
kernelWidthsInUse = new double[dimensions];
}
/**
* Each row of the data is an observation; each column of
* the row is a new variable in the multivariate observation.
*
* @param data
*/
@SuppressWarnings("unchecked")
public void setObservations(double[][] data) {
totalObservations = data.length;
bins = new int[dimensions];
mins = new double[dimensions];
multipliers = new int[dimensions];
int multiDimBins = 1;
// If we are forcing n^2 comparisons, don't even consider using integer index bins.
// Otherwise, we will assume we're doing this unless we find there would be too
// many bins:
usingIntegerIndexBins = !forceCompareToAll;
useBins = true;
kernelVolumeInUse = 0;
// Work out the max and min for each bin
for (int d = 0; d < dimensions; d++) {
mins[d] = MatrixUtils.min(data, d);
double max = MatrixUtils.max(data, d);
double std = 0.0;
if (normalise) {
// Compute what the epsilonInUse should be here:
// it should expand with the standard deviation.
// This saves us from normalising all of the incoming data points!
std = MatrixUtils.stdDev(data, d);
kernelWidthsInUse[d] = suppliedKernelWidths[d] * std;
} else {
kernelWidthsInUse[d] = suppliedKernelWidths[d];
}
if (d == 0) {
kernelVolumeInUse = 2.0 * kernelWidthsInUse[d];
} else {
kernelVolumeInUse *= 2.0 * kernelWidthsInUse[d];
}
bins[d] = (int) Math.ceil((max - mins[d]) / kernelWidthsInUse[d]);
if (bins[d] == 0) {
// This means the min and max are exactly the same:
// for our purposes this is akin to requiring one bin here.
// If we leave it as zero bins, this will screw up
// our calculation of the number of multidimensional bins
// and cause errors later.
bins[d] = 1;
}
multipliers[d] = multiDimBins;
multiDimBins *= bins[d];
if (usingIntegerIndexBins && (multiDimBins > MAX_NUMBER_INTEGER_BINS)) {
// the number of bins has breached our maximum, so we'll use
// a hash table instead of an integer array
usingIntegerIndexBins = false;
}
if (debug) {
System.out.println("Dim: " + d + " => Max: " + max + ", min: " + mins[d] +
", bins: " + bins[d] +
(normalise ? ", std: " + std : "") +
", eps: " + kernelWidthsInUse[d]);
}
}
observations = null;
observationsByHash = null;
rawData = data; // Store the raw data regardless of our
// indexing approach - we may need to
// access it for dynamic correlation exclusion
if (usingIntegerIndexBins) {
// multiDimBins is the number of multi-dimensional bins we have
if (debug) {
System.out.println("Multidimensional bins: " + multiDimBins);
}
// Create the bins
observations = (Vector<TimeStampedObservation>[]) new Vector[multiDimBins];
for (int v = 0; v < multiDimBins; v++) {
observations[v] = new Vector<TimeStampedObservation>();
}
// Add each observation
for (int t = 0; t < data.length; t++) {
int bin = getMultiDimBinIndex(data[t]);
// Create the time-stamped observation to store:
TimeStampedObservation tso = new TimeStampedObservation(t, data[t]);
observations[bin].add(tso);
}
} else {
// Could be using either hash table style bins or no bins at all.
// Work out how many bins we might be trawling through if we used bins:
// (need to take 3 ^ d, since we look at one bin either side for each dimension)
int possibleBinsToTrawl = 1;
for (int d = 0; d < dimensions; d++) {
if (possibleBinsToTrawl > (Integer.MAX_VALUE / 3)) {
// Next multiplication will overflow
possibleBinsToTrawl = Integer.MAX_VALUE;
break;
}
possibleBinsToTrawl *= 3;
}
// We might be forcing n^2 comparisons between all points, or
// as a rule of thumb, check if there are less observations than possible bins to trawl
if (forceCompareToAll || (totalObservations < possibleBinsToTrawl)) {
useBins = false;
// Simply store the raw data:
// rawData = data; - this is already done above
} else {
// Using hash table
useBins = true;
if (debug) {
System.out.println("Using array hash index bins");
}
// Create the hashtable
observationsByHash = new Hashtable<IntArray, Vector<TimeStampedObservation>>();
// Add each observation
for (int t = 0; t < data.length; t++) {
int[] multiDimBin = getMultiDimBinArray(data[t]);
IntArray intArrayMultiDimBin = new IntArray(multiDimBin);
Vector<TimeStampedObservation> hashedVector =
(Vector<TimeStampedObservation>) observationsByHash.get(intArrayMultiDimBin);
if (hashedVector == null) {
hashedVector = new Vector<TimeStampedObservation>();
}
// Create the time-stamped observation to store:
TimeStampedObservation tso = new TimeStampedObservation(t, data[t]);
hashedVector.add(tso);
// And put the observations for this multidimensional bin back in
observationsByHash.put(intArrayMultiDimBin, hashedVector);
}
}
}
// Not sorting the observations, as this can only
// be practically done for a single variable.
}
/**
* Each row of the data is an observation; each column of
* the row is a new variable in the multivariate observation.
* This method signature allows the user to call setObservations for
* joint time series without combining them into a single joint time
* series (we do the combining for them).
*
* @param data1
* @param data2
* @throws Exception When the length of the two arrays of observations do not match.
*/
public void setObservations(double[][] data1, double[][] data2) throws Exception {
int timeSteps = data1.length;
if ((data1 == null) || (data2 == null)) {
throw new Exception("Cannot have null data arguments");
}
if (data1.length != data2.length) {
throw new Exception("Length of data1 (" + data1.length + ") is not equal to the length of data2 (" +
data2.length + ")");
}
int data1Variables = data1[0].length;
int data2Variables = data2[0].length;
double[][] data = new double[timeSteps][data1Variables + data2Variables];
for (int t = 0; t < timeSteps; t++) {
System.arraycopy(data1[t], 0, data[t], 0, data1Variables);
System.arraycopy(data2[t], 0, data[t], data1Variables, data2Variables);
}
// Now defer to the normal setObservations method
setObservations(data);
}
/**
* This method signature allows the user to call setObservations for
* joint time series without combining them into a single joint time
* series (we do the combining for them).
*
* @param data1
* @param data2
* @throws Exception When the length of the two arrays of observations do not match.
*/
public void setObservations(double[] data1, double[] data2) throws Exception {
int timeSteps = data1.length;
if ((data1 == null) || (data2 == null)) {
throw new Exception("Cannot have null data arguments");
}
if (data1.length != data2.length) {
throw new Exception("Length of data1 (" + data1.length + ") is not equal to the length of data2 (" +
data2.length + ")");
}
double[][] data = new double[timeSteps][2];
MatrixUtils.copyIntoColumn(data, 0, data1);
MatrixUtils.copyIntoColumn(data, 1, data2);
// Now defer to the normal setObservations method
setObservations(data);
}
/**
* Return the kernel estimate for the probability without any dynamic
* correlation exlcusion
*
* @param observation
* @return
*/
public double getProbability(double[] observation) {
if (useBins) {
return getProbabilityFromBins(observation, 0, false);
} else {
return getProbabilityComparingToAll(observation, 0, false);
}
}
/**
* Return the kernel estimate for the probability with dynamic
* correlation exclusion if it has been switched on
*
* @param observation
* @param timeStep
* @return
*/
public double getProbability(double[] observation, int timeStep) {
if (useBins) {
return getProbabilityFromBins(observation, timeStep, excludeDynamicCorrelations);
} else {
return getProbabilityComparingToAll(observation, timeStep, excludeDynamicCorrelations);
}
}
/**
* Return the kernel estimate for the joint probability without any dynamic
* correlation exlcusion
*
* @param observation1
* @param observation2
* @return
*/
public double getProbability(double[] observation1, double[] observation2) {
// Make a joint array
double[] jointObservation = new double[observation1.length + observation2.length];
System.arraycopy(observation1, 0, jointObservation, 0, observation1.length);
System.arraycopy(observation2, 0, jointObservation, observation1.length, observation2.length);
return getProbability(jointObservation);
}
/**
* Return the kernel estimate for the joint probability with dynamic
* correlation exlcusion if it has been swithced on
*
* @param observation1
* @param observation2
* @param timeStep
* @return
*/
public double getProbability(double[] observation1, double[] observation2, int timeStep) {
// Make a joint array
double[] jointObservation = new double[observation1.length + observation2.length];
System.arraycopy(observation1, 0, jointObservation, 0, observation1.length);
System.arraycopy(observation2, 0, jointObservation, observation1.length, observation2.length);
return getProbability(jointObservation, timeStep);
}
/**
* Return the kernel count (i think this is the correlation integral effectively?)
* for this vector without any dynamic correlation exlcusion
*
* @param observation
* @return
*/
public int getCount(double[] observation) {
KernelCount kernelCount;
if (useBins) {
kernelCount = getCountFromBins(observation, 0, false);
} else {
kernelCount = getCountComparingToAll(observation, 0, false);
}
return kernelCount.count;
}
/**
* <p>Return the kernel count for the probability with dynamic
* correlation exlcusion if it has been switched on.</p>
*
* <p>The user should be aware that a call to getProbability(double[], int)
* will not return the same value as getCount(double[], int) / totalObservations,
* since dynamic correlation exclusion could be swithced on. To achieve this, the
* user could call getCompleteKernelCount(double[], int[], boolean) and use the
* returned count and totalObservationsForCount in the returned KernelCount object.
* </p>
*
* @param observation
* @param timeStep
* @return
*/
public int getCount(double[] observation, int timeStep) {
KernelCount kernelCount;
if (useBins) {
kernelCount = getCountFromBins(observation, timeStep, excludeDynamicCorrelations);
} else {
kernelCount = getCountComparingToAll(observation, timeStep, excludeDynamicCorrelations);
}
return kernelCount.count;
}
/**
* Return the kernel count for the probability with dynamic
* correlation exlcusion if it has been switched on
*
* @param observation
* @param timeStep
* @param giveListOfCorrelatedPoints
* @return
*/
public KernelCount getCompleteKernelCount(double[] observation, int timeStep,
boolean giveListOfCorrelatedPoints) {
KernelCount kernelCount;
if (useBins) {
kernelCount = getCountFromBins(observation, timeStep,
excludeDynamicCorrelations, giveListOfCorrelatedPoints);
} else {
kernelCount = getCountComparingToAll(observation, timeStep,
excludeDynamicCorrelations, giveListOfCorrelatedPoints);
}
return kernelCount;
}
/**
* Return the kernel count for the joint vector without any dynamic
* correlation exlcusion
*
* @param observation1
* @param observation2
* @return
*/
public int getCount(double[] observation1, double[] observation2) {
// Make a joint array
double[] jointObservation = new double[observation1.length + observation2.length];
System.arraycopy(observation1, 0, jointObservation, 0, observation1.length);
System.arraycopy(observation2, 0, jointObservation, observation1.length, observation2.length);
return getCount(jointObservation);
}
/**
* Return the kernel estimate for the joint probability with dynamic
* correlation exlcusion if it has been swithced on
*
* @param observation1
* @param observation2
* @param timeStep
* @return
*/
public int getCount(double[] observation1, double[] observation2, int timeStep) {
// Make a joint array
double[] jointObservation = new double[observation1.length + observation2.length];
System.arraycopy(observation1, 0, jointObservation, 0, observation1.length);
System.arraycopy(observation2, 0, jointObservation, observation1.length, observation2.length);
return getCount(jointObservation, timeStep);
}
/**
* Return the kernel count object for the joint probability with dynamic
* correlation exlcusion if it has been swithced on
*
* @param observation1
* @param observation2
* @param timeStep
* @return
*/
public KernelCount getCompleteKernelCount(double[] observation1, double[] observation2,
int timeStep, boolean giveListOfCorrelatedPoints) {
// Make a joint array
double[] jointObservation = new double[observation1.length + observation2.length];
System.arraycopy(observation1, 0, jointObservation, 0, observation1.length);
System.arraycopy(observation2, 0, jointObservation, observation1.length, observation2.length);
return getCompleteKernelCount(jointObservation, timeStep, giveListOfCorrelatedPoints);
}
/**
* Give kernel estimated count for this observation.
* Do this using bins, so we only need to compare to neighbouring bins
* and of course add in the observations in this bin.
* Achieves this by trawling through neighbouring bins in a recursive fashion.
*
* @param observation
* @param timeStep of the given observation (required for dynamic correlation exclusion)
* @param dynCorrExclusion whether to use dynamic correlation exclusion
* @return KernelCount object with the count and total observations the count was taken from
*/
private KernelCount getCountFromBins(double[] observation, int timeStep,
boolean dynCorrExclusion) {
return getCountFromBins(observation, timeStep, dynCorrExclusion, false);
}
/**
* Give kernel estimated count for this observation.
* Do this using bins, so we only need to compare to neighbouring bins
* and of course add in the observations in this bin.
* Achieves this by trawling through neighbouring bins in a recursive fashion.
*
* @param observation
* @param timeStep of the given observation (required for dynamic correlation exclusion)
* @param dynCorrExclusion whether to use dynamic correlation exclusion
* @param giveListOfCorrelatedPoints include the list of time indices whose vectors
* were correlated with the given vector
* @return KernelCount object with the count and total observations the count was taken from
*/
protected KernelCount getCountFromBins(double[] observation, int timeStep,
boolean dynCorrExclusion, boolean giveListOfCorrelatedPoints) {
// First count the number of observations in the same bin
int count;
boolean[] isCorrelatedWithArgument = null;
if (giveListOfCorrelatedPoints) {
isCorrelatedWithArgument = new boolean[totalObservations];
}
int multiDimBin = 0;
int[] multiDimBinArray = null;
IntArray intArrayMultiDimBin = null;
if (usingIntegerIndexBins) {
multiDimBin = getMultiDimBinIndex(observation);
count = observations[multiDimBin].size();
if (giveListOfCorrelatedPoints || makeCorrelatedPointAddedCallback) {
for (int i = 0; i < observations[multiDimBin].size(); i++) {
TimeStampedObservation tso = (TimeStampedObservation) observations[multiDimBin].elementAt(i);
if (giveListOfCorrelatedPoints) {
isCorrelatedWithArgument[tso.timeStep] = true;
}
if (makeCorrelatedPointAddedCallback) {
correlatedPointAddedCallback(tso.timeStep);
}
}
}
} else {
multiDimBinArray = getMultiDimBinArray(observation);
intArrayMultiDimBin = new IntArray(multiDimBinArray);
Vector<TimeStampedObservation> observationsInThisBin =
(Vector<TimeStampedObservation>) observationsByHash.get(intArrayMultiDimBin);
// Shouldn't need to check (observationsInThisBin != null) here since
// at least this observation itself should have been added in here at some point.
// Will need to change this though if we ever start using the kernel estimator to check
// probabilities for new vectors that were not previously added to the observations.
count = observationsInThisBin.size();
if (giveListOfCorrelatedPoints || makeCorrelatedPointAddedCallback) {
for (int i = 0; i < observationsInThisBin.size(); i++) {
TimeStampedObservation tso = (TimeStampedObservation) observationsInThisBin.elementAt(i);
if (giveListOfCorrelatedPoints) {
isCorrelatedWithArgument[tso.timeStep] = true;
}
if (makeCorrelatedPointAddedCallback) {
correlatedPointAddedCallback(tso.timeStep);
}
}
}
}
int totalTimePointsCompared = totalObservations;
if (dynCorrExclusion) {
// Need to remove any observations that were *closer* than timeProximityForDynamicCorrelationExclusion
int closeTimePointsToCompare = (timeStep >= timeProximityForDynamicCorrelationExclusion) ?
timeProximityForDynamicCorrelationExclusion - 1: timeStep;
closeTimePointsToCompare += (totalObservations - timeStep >= timeProximityForDynamicCorrelationExclusion) ?
timeProximityForDynamicCorrelationExclusion - 1: totalObservations - timeStep - 1;
closeTimePointsToCompare++; // Add one for comparison to self
totalTimePointsCompared -= closeTimePointsToCompare;
// Use a heuristic to select best way to eliminate dynamic correlations here:
int countToRemove = 0;
if (closeTimePointsToCompare * dimensions < count) {
// Check all the neighbouring points in time to see if
// they were in the same bin and have been counted
for (int t = Math.max(0,timeStep-timeProximityForDynamicCorrelationExclusion+1);
t < Math.min(totalObservations,timeStep+timeProximityForDynamicCorrelationExclusion); t++) {
int otherMultiDimBin = getMultiDimBinIndex(rawData[t]);
if (otherMultiDimBin == multiDimBin) {
countToRemove++;
if (giveListOfCorrelatedPoints) {
isCorrelatedWithArgument[t] = false;
}
if (makeCorrelatedPointAddedCallback) {
correlatedPointRemovedCallback(t);
}
}
}
} else {
// check all the points in this bin
if (usingIntegerIndexBins) {
for (int i = 0; i < observations[multiDimBin].size(); i++) {
TimeStampedObservation tso = (TimeStampedObservation) observations[multiDimBin].elementAt(i);
if (Math.abs(tso.timeStep - timeStep) < timeProximityForDynamicCorrelationExclusion) {
countToRemove++;
if (giveListOfCorrelatedPoints) {
isCorrelatedWithArgument[tso.timeStep] = false;
}
if (makeCorrelatedPointAddedCallback) {
correlatedPointRemovedCallback(tso.timeStep);
}
}
}
} else {
Vector<TimeStampedObservation> observationsInThisBin =
(Vector<TimeStampedObservation>) observationsByHash.get(intArrayMultiDimBin);
if (observationsInThisBin != null) {
for (int i = 0; i < observationsInThisBin.size(); i++) {
TimeStampedObservation tso = (TimeStampedObservation) observationsInThisBin.elementAt(i);
if (Math.abs(tso.timeStep - timeStep) < timeProximityForDynamicCorrelationExclusion) {
countToRemove++;
if (giveListOfCorrelatedPoints) {
isCorrelatedWithArgument[tso.timeStep] = false;
}
if (makeCorrelatedPointAddedCallback) {
correlatedPointRemovedCallback(tso.timeStep);
}
}
}
}
}
}
count -= countToRemove;
}
// Now look for correlated points in neighbouring bins
int[] currentNeighbourBinIndices = new int[dimensions];
// Run for one bin lower (assuming it exists)
count += addCount(observation, currentNeighbourBinIndices, 0,
getBinIndex(observation[0], 0) - 1, false,
dynCorrExclusion, timeStep, isCorrelatedWithArgument);
// Run along this bin for the moment, changing one of the other indices
count += addCount(observation,currentNeighbourBinIndices, 0,
getBinIndex(observation[0], 0), true,
dynCorrExclusion, timeStep, isCorrelatedWithArgument);
// Run for the next bin up (assuming it exists)
count += addCount(observation, currentNeighbourBinIndices, 0,
getBinIndex(observation[0], 0) + 1, false,
dynCorrExclusion, timeStep, isCorrelatedWithArgument);
KernelCount kernelCount = new KernelCount(count, totalTimePointsCompared, isCorrelatedWithArgument);
return kernelCount;
}
/**
* Give kernel estimated probability density for this observation.
* Do this using bins, so we only need to compare to neighbouring bins
* and of course add in the observations in this bin.
* Achieves this by trawling through neighbouring bins in a recursive fashion.
*
* @param observation
* @param timeStep of the given observation (required for dynamic correlation exclusion)
* @param dynCorrExclusion whether to use dynamic correlation exclusion
* @return
*/
private double getProbabilityFromBins(double[] observation, int timeStep,
boolean dynCorrExclusion) {
KernelCount kernelCount = getCountFromBins(observation, timeStep, dynCorrExclusion);
// Dividing by kernelVolume also to get a density
return (double) kernelCount.count / (double) kernelCount.totalObservationsForCount / kernelVolumeInUse;
}
/**
* Give kernel estimated count for this observation, by comparing to all
* given observations.
*
* @param observation
* @param timeStep of the given observation (required for dynamic correlation exclusion)
* @param dynCorrExclusion whether to use dynamic correlation exclusion
* @return KernelCount object with the count and total observations the count was taken from
*/
private KernelCount getCountComparingToAll(double[] observation, int timeStep,
boolean dynCorrExclusion) {
return getCountComparingToAll(observation, timeStep, dynCorrExclusion, false);
}
/**
* Give kernel estimated count for this observation, by comparing to all
* given observations.
*
* @param observation
* @param timeStep of the given observation (required for dynamic correlation exclusion)
* @param dynCorrExclusion whether to use dynamic correlation exclusion
* @return KernelCount object with the count and total observations the count was taken from
*/
protected KernelCount getCountComparingToAll(double[] observation, int timeStep,
boolean dynCorrExclusion, boolean giveListOfCorrelatedPoints) {
int count = 0;
boolean[] isCorrelatedWithArgument = null;
if (giveListOfCorrelatedPoints) {
isCorrelatedWithArgument = new boolean[totalObservations];
}
for (int t = 0; t < totalObservations; t++) {
if (!dynCorrExclusion ||
(Math.abs(t - timeStep) >= timeProximityForDynamicCorrelationExclusion)) {
// Only add in the contribution from this point
// when we're not doing dynamic correlation exclusion
// or if it's outside the exclusion zone
int thisStepKernelValue = stepKernel(observation, rawData[t]);
count += thisStepKernelValue;
if (giveListOfCorrelatedPoints) {
isCorrelatedWithArgument[t] = (thisStepKernelValue > 0);
}
if (makeCorrelatedPointAddedCallback && (thisStepKernelValue > 0)) {
correlatedPointAddedCallback(t);
}
}
}
int totalTimePointsCompared = totalObservations;
if (dynCorrExclusion) {
// Needed to remove any observations that were *closer* than timeProximityForDynamicCorrelationExclusion
// from the total of time points that were compared.
int closeTimePoints = (timeStep >= timeProximityForDynamicCorrelationExclusion) ?
timeProximityForDynamicCorrelationExclusion - 1: timeStep;
closeTimePoints += (totalObservations - timeStep >= timeProximityForDynamicCorrelationExclusion) ?
timeProximityForDynamicCorrelationExclusion - 1: totalObservations - timeStep - 1;
closeTimePoints++; // Add one for comparison to self
totalTimePointsCompared -= closeTimePoints;
}
KernelCount kernelCount = new KernelCount(count, totalTimePointsCompared, isCorrelatedWithArgument);
return kernelCount;
}
/**
* Give kernel estimated probability density for this observation, by comparing to all
* given observations.
*
* @param observation
* @param timeStep of the given observation (required for dynamic correlation exclusion)
* @param dynCorrExclusion whether to use dynamic correlation exclusion
* @return
*/
private double getProbabilityComparingToAll(double[] observation, int timeStep,
boolean dynCorrExclusion) {
KernelCount kernelCount = getCountComparingToAll(observation, timeStep, dynCorrExclusion);
// Dividing by kernelVolume also to get a density
return (double) kernelCount.count / (double) kernelCount.totalObservationsForCount / kernelVolumeInUse;
}
private int getBinIndex(double value, int dimension) {
int bin = (int) Math.floor((value - mins[dimension]) / kernelWidthsInUse[dimension]);
// Check for any rounding errors on the bin assignment:
if (bin >= bins[dimension]) {
bin = bins[dimension] - 1;
}
if (bin < 0) {
bin = 0;
}
return bin;
}
public int getMultiDimBinIndex(double[] value) {
int multiDimBin = 0;
for (int d = 0; d < dimensions; d++) {
multiDimBin += getBinIndex(value[d], d) * multipliers[d];
}
return multiDimBin;
}
public int[] getMultiDimBinArray(double[] value) {
int[] multiDimBinArray = new int[dimensions];
for (int d = 0; d < dimensions; d++) {
multiDimBinArray[d] = getBinIndex(value[d], d);
}
return multiDimBinArray;
}
public int getMultiDimBinIndexFromSingles(int[] singleBinIndices) {
int multiDimBin = 0;
for (int d = 0; d < dimensions; d++) {
multiDimBin += singleBinIndices[d] * multipliers[d];
}
return multiDimBin;
}
/**
* Count the number of data points within epsilon,
* for multi dimensional bin indices as specified
*
* @param neighbourBinIndices
* @param currentIndex
* @param currentIndexValue
* @param possibleCentralBin
* @param dynCorrExclusion
* @param timeStep
* @param isCorrelatedWithArgument set if the vector at each given time point is included
* @return
*/
private int addCount(double[] observation, int[] neighbourBinIndices,
int currentIndex, int currentIndexValue, boolean possibleCentralBin,
boolean dynCorrExclusion, int timeStep, boolean[] isCorrelatedWithArgument) {
int count = 0;
// Check that the current index is in range
if (currentIndexValue < 0) {
return 0;
}
if (currentIndexValue >= bins[currentIndex]) {
return 0;
}
neighbourBinIndices[currentIndex] = currentIndexValue;
if (currentIndex == dimensions - 1) {
// this is the last dimension, need to check the neighbouring bin here
if (possibleCentralBin) {
// This is definitely the central bin, don't
// count it, we've already done that outside
return 0;
}
if (usingIntegerIndexBins) {
int multiDimIndex = getMultiDimBinIndexFromSingles(neighbourBinIndices);
for (int i = 0; i < observations[multiDimIndex].size(); i++) {
TimeStampedObservation tso = (TimeStampedObservation) observations[multiDimIndex].elementAt(i);
if (!dynCorrExclusion ||
(Math.abs(tso.timeStep - timeStep) >= timeProximityForDynamicCorrelationExclusion)) {
// Only add in the contribution from this point
// when we're not doing dynamic correlation exclusion
// or if it's outside the exclusion zone
int thisStepKernelValue = stepKernel(observation, tso.observation);
count += thisStepKernelValue;
if (isCorrelatedWithArgument != null) {
isCorrelatedWithArgument[tso.timeStep] = (thisStepKernelValue > 0);
}
if (makeCorrelatedPointAddedCallback && (thisStepKernelValue > 0)) {
correlatedPointAddedCallback(tso.timeStep);
}
}
}
} else {
IntArray intArrayBinNeighbourIndices = new IntArray(neighbourBinIndices);
Vector<TimeStampedObservation> observationsInThisBin =
(Vector<TimeStampedObservation>) observationsByHash.get(intArrayBinNeighbourIndices);
if (observationsInThisBin != null) {
for (int i = 0; i < observationsInThisBin.size(); i++) {
TimeStampedObservation tso = (TimeStampedObservation) observationsInThisBin.elementAt(i);
if (!dynCorrExclusion ||
(Math.abs(tso.timeStep - timeStep) >= timeProximityForDynamicCorrelationExclusion)) {
// Only add in the contribution from this point
// when we're not doing dynamic correlation exclusion
// or if it's outside the exclusion zone
int thisStepKernelValue = stepKernel(observation, tso.observation);
count += thisStepKernelValue;
if (isCorrelatedWithArgument != null) {
isCorrelatedWithArgument[tso.timeStep] = (thisStepKernelValue > 0);
}
if (makeCorrelatedPointAddedCallback && (thisStepKernelValue > 0)) {
correlatedPointAddedCallback(tso.timeStep);
}
}
}
}
}
} else {
// pass on the responsibility to do the checks:
// Run for one index lower (assuming it exists)
count += addCount(observation, neighbourBinIndices, currentIndex+1,
getBinIndex(observation[currentIndex+1], currentIndex+1) - 1, false,
dynCorrExclusion, timeStep, isCorrelatedWithArgument);
// Run for this index
count += addCount(observation,neighbourBinIndices, currentIndex+1,
getBinIndex(observation[currentIndex+1], currentIndex+1), possibleCentralBin,
dynCorrExclusion, timeStep, isCorrelatedWithArgument);
// Run for the next index up (assuming it exists)
count += addCount(observation, neighbourBinIndices, currentIndex+1,
getBinIndex(observation[currentIndex+1], currentIndex+1) + 1, false,
dynCorrExclusion, timeStep, isCorrelatedWithArgument);
}
return count;
}
/**
* Return the step kernel for the two data points
*
* @param observation
* @param candidate
* @return
*/
public int stepKernel(double[] observation, double[] candidate) {
for (int d = 0; d < dimensions; d++) {
if (Math.abs(observation[d] - candidate[d]) > kernelWidthsInUse[d]) {
return 0;
}
}
// The candidate is within epsilon of the observation
return 1;
}
/**
* Return the kernel widths that are actually in use in the
* calculator (i.e. those after any normalisation is applied)
*
* @return the kernelWidthsInUse
*/
public double[] getKernelWidthsInUse() {
return kernelWidthsInUse;
}
public void setNormalise(boolean normalise) {
this.normalise = normalise;
}
public boolean isNormalise() {
return normalise;
}
public void setDynamicCorrelationExclusion(int timeWindow) {
excludeDynamicCorrelations = true;
timeProximityForDynamicCorrelationExclusion = timeWindow;
}
public void clearDynamicCorrelationExclusion() {
excludeDynamicCorrelations = false;
}
public boolean isExcludeDynamicCorrelations() {
return excludeDynamicCorrelations;
}
public boolean isForceCompareToAll() {
return forceCompareToAll;
}
/**
* Only recommended to set this for debugging purposes
*
* @param forceCompareToAll
*/
public void setForceCompareToAll(boolean forceCompareToAll) {
this.forceCompareToAll = forceCompareToAll;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
/**
* A callback for where a correlated point is found.
* Used in child classes, but not here since we have set makeCorrelatedPointAddedCallback to false.
*
*/
protected void correlatedPointAddedCallback(int correlatedTimeStep) {
}
/**
* A callback for where a correlated point is removed due to dynamic correlated exclusion.
* Used in child classes, but not here since we have set makeCorrelatedPointAddedCallback to false.
*
*/
protected void correlatedPointRemovedCallback(int removedCorrelatedTimeStep) {
}
/**
* Provide an implementation of the clone() method.
* This does not deeply copy all of the underlying data, just providing
* a copy of the references to it all.
* This is enough to protect the integrity of the kernel estimator
* however if the clone is supplied different data (though the
* clone should not alter the data).
*
* @see java.lang.Object#clone()
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}