Making conditional MI calculators (continuous) handle empty conditionals, and properly normalise new data according to the old (except for linear Gaussian calculator, which explicitly bars normalisation now).

This commit is contained in:
jlizier 2016-10-18 13:21:29 +11:00
parent b01c01ca3b
commit 3118e57384
5 changed files with 561 additions and 78 deletions

View File

@ -134,6 +134,39 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
public static final String PROP_NORMALISE = "NORMALISE";
protected boolean normalise = true;
/**
* Cache for the means of each dimension in variable 1, in case we need to normalise
* new observations later
*/
protected double[] var1Means = null;
/**
* Cache for the standard deviations of each dimension in variable 1, in case we need to normalise
* new observations later
*/
protected double[] var1Stds = null;
/**
* Cache for the means of each dimension in variable 2, in case we need to normalise
* new observations later
*/
protected double[] var2Means = null;
/**
* Cache for the standard deviations of each dimension in variable 2, in case we need to normalise
* new observations later
*/
protected double[] var2Stds = null;
/**
* Cache for the means of each dimension in conditional variable, in case we need to normalise
* new observations later
*/
protected double[] condMeans = null;
/**
* Cache for the standard deviations of each dimension in conditional variable, in case we need to normalise
* new observations later
*/
protected double[] condStds = null;
@Override
public void initialise(int var1Dimensions, int var2Dimensions, int condDimensions) {
dimensionsVar1 = var1Dimensions;
@ -146,6 +179,12 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
var2Observations = null;
condObservations = null;
addedMoreThanOneObservationSet = false;
var1Means = null;
var1Stds = null;
var2Means = null;
var2Stds = null;
condMeans = null;
condStds = null;
}
/**
@ -159,7 +198,8 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
* <li>{@link #PROP_NORMALISE} - whether to normalise the individual
* variables to mean 0, standard deviation 1
* (true by default, except for child class
* {@link infodynamics.measures.continuous.gaussian.ConditionalMutualInfoCalculatorMultiVariateGaussian})</li>
* {@link infodynamics.measures.continuous.gaussian.ConditionalMutualInfoCalculatorMultiVariateGaussian}
* for which this property is false and cannot be altered)</li>
* </ul>
*
* <p>Unknown property values are ignored.</p>
@ -208,7 +248,8 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
// startAddObservations was not called first
throw new RuntimeException("User did not call startAddObservations before addObservations");
}
if ((var1.length != var2.length) || (var1.length != cond.length)) {
if ((var1.length != var2.length) ||
((dimensionsCond != 0) && (var1.length != cond.length))) {
throw new Exception(String.format("Observation vector lengths (%d, %d and %d) must match!",
var1.length, var2.length, cond.length));
}
@ -220,7 +261,7 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
throw new Exception("Number of joint variables in var2 data " +
"does not match the initialised value");
}
if (cond[0].length != dimensionsCond) {
if ((dimensionsCond != 0) && (cond[0].length != dimensionsCond)) {
throw new Exception("Number of joint variables in cond data " +
"does not match the initialised value");
}
@ -244,8 +285,11 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
System.arraycopy(var1, startTime, var1ToAdd, 0, numTimeSteps);
double[][] var2ToAdd = new double[numTimeSteps][];
System.arraycopy(var2, startTime, var2ToAdd, 0, numTimeSteps);
double[][] condToAdd = new double[numTimeSteps][];
System.arraycopy(cond, startTime, condToAdd, 0, numTimeSteps);
double[][] condToAdd = null;
if (dimensionsCond != 0) {
condToAdd = new double[numTimeSteps][];
System.arraycopy(cond, startTime, condToAdd, 0, numTimeSteps);
}
addObservations(var1ToAdd, var2ToAdd, condToAdd);
}
@ -317,17 +361,27 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
MatrixUtils.arrayCopy(var2, 0, 0,
var2Observations, startObservation, 0,
var2.length, dimensionsVar2);
MatrixUtils.arrayCopy(cond, 0, 0,
if (dimensionsCond != 0) {
MatrixUtils.arrayCopy(cond, 0, 0,
condObservations, startObservation, 0,
cond.length, dimensionsCond);
} // else we can do nothing there
startObservation += var2.length;
}
// Normalise the data if required
if (normalise) {
MatrixUtils.normalise(var1Observations);
MatrixUtils.normalise(var2Observations);
MatrixUtils.normalise(condObservations);
var1Means = MatrixUtils.means(var1Observations);
var1Stds = MatrixUtils.stdDevs(var1Observations, var1Means);
MatrixUtils.normalise(var1Observations, var1Means, var1Stds);
var2Means = MatrixUtils.means(var2Observations);
var2Stds = MatrixUtils.stdDevs(var2Observations, var2Means);
MatrixUtils.normalise(var2Observations, var2Means, var2Stds);
if (dimensionsCond != 0) {
condMeans = MatrixUtils.means(condObservations);
condStds = MatrixUtils.stdDevs(condObservations, condMeans);
MatrixUtils.normalise(condObservations, condMeans, condStds);
}
}
// We don't need to keep the vectors of observation sets anymore:

View File

@ -158,6 +158,22 @@ public class ConditionalMutualInfoCalculatorMultiVariateGaussian
var2IndicesInCovariance = null;
}
@Override
public void setProperty(String propertyName, String propertyValue) {
if (propertyName.equalsIgnoreCase(PROP_NORMALISE)) {
// We do not allow the user to alter this property away from false,
// otherwise we would need to alter the functionality of the
// local calculations, etc.
return;
}
super.setProperty(propertyName, propertyValue);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* @throws Exception if the observation variables are not linearly independent
* (leading to a non-positive definite covariance matrix).

View File

@ -46,7 +46,8 @@ import infodynamics.utils.UnivariateNearestNeighbourSearcher;
* actually implement the two KSG algorithms.</p>
*
* <p>Crucially, the calculation is performed by examining
* neighbours in the full joint space (as specified by Frenzel and Pompe)
* neighbours in the full joint space (as specified by Frenzel and Pompe,
* and later by Vejmelka and Paluš, and Vlachos and Kugiumtzis)
* rather than two MI calculators.</p>
*
* <p>Usage is as per the paradigm outlined for {@link ConditionalMutualInfoCalculatorMultiVariate},
@ -69,6 +70,12 @@ import infodynamics.utils.UnivariateNearestNeighbourSearcher;
* <li>Frenzel and Pompe, <a href="http://dx.doi.org/10.1103/physrevlett.99.204101">
* "Partial Mutual Information for Coupling Analysis of Multivariate Time Series"</a>,
* Physical Review Letters, <b>99</b>, p. 204101+ (2007).</li>
* <li>Vejmelka and Paluš, <a href="http://dx.doi.org/10.1103/physreve.77.026214">
* "Inferring the directionality of coupling with conditional mutual information"</a>,
* Physical Review E, <b>77</b>, 026214, (2008)</li>
* <li>I. Vlachos and D. Kugiumtzis, <a href="http://dx.doi.org/10.1103/physreve.82.016207">
* "Nonuniform state-space reconstruction and coupling detection"</a>,
* Physical Review E, <b>82</b>, 016207, (2010)</li>
* <li>Kraskov, A., Stoegbauer, H., Grassberger, P.,
* <a href="http://dx.doi.org/10.1103/PhysRevE.69.066138">"Estimating mutual information"</a>,
* Physical Review E 69, (2004) 066138.</li>
@ -331,6 +338,7 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
var2Observations[r][c] +=
random.nextGaussian()*noiseLevel;
}
// This next loop will only execute if dimensionsCond > 0
for (int c = 0; c < dimensionsCond; c++) {
condObservations[r][c] +=
random.nextGaussian()*noiseLevel;
@ -351,7 +359,7 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
public double computeAverageLocalOfObservations() throws Exception {
// Compute the conditional MI
double startTime = Calendar.getInstance().getTimeInMillis();
lastAverage = computeFromObservations(false)[0];
lastAverage = computeFromObservations(false, null)[0];
condMiComputed = true;
if (debug) {
Calendar rightNow2 = Calendar.getInstance();
@ -395,7 +403,7 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
var2Observations = MatrixUtils.extractSelectedTimePointsReusingArrays(originalData, reordering);
}
// Compute the conditional MI
double newCondMI = computeFromObservations(false)[0];
double newCondMI = computeFromObservations(false, null)[0];
// restore original data
kdTreeJoint = originalKdTreeJoint;
if (variableToReorder == 1) {
@ -412,7 +420,7 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
@Override
public double[] computeLocalOfPreviousObservations() throws Exception {
double[] localValues = computeFromObservations(true);
double[] localValues = computeFromObservations(true, null);
lastAverage = MatrixUtils.mean(localValues);
condMiComputed = true;
return localValues;
@ -424,10 +432,25 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
@Override
public double[] computeLocalUsingPreviousObservations(double[][] states1,
double[][] states2, double[][] condStates) throws Exception {
// If implemented, will need to incorporate any normalisation here
// (normalising the incoming data the same way the previously
// supplied observations were normalised).
throw new Exception("Local method not implemented yet");
// Do normalisation of the incoming data if required:
double[][] states1ToUse, states2ToUse, condStatesToUse;
if (normalise) {
states1ToUse = MatrixUtils.normaliseIntoNewArray(states1, var1Means, var1Stds);
states2ToUse = MatrixUtils.normaliseIntoNewArray(states2, var2Means, var2Stds);
if (dimensionsCond != 0) {
condStatesToUse = MatrixUtils.normaliseIntoNewArray(condStates, condMeans, condStds);
} else {
condStatesToUse = null;
}
} else {
states1ToUse = states1;
states2ToUse = states2;
condStatesToUse = condStates;
}
// And call the algorithm:
double[] localValues = computeFromObservations(true,
new double[][][]{states1ToUse, states2ToUse, condStatesToUse});
return localValues;
}
/**
@ -444,10 +467,12 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
*
* @param returnLocals whether to return an array or local values, or else
* sums of these values
* @param newObservations set to null for computing for the observation set for the PDF, or pass in a new set
* of observations to compute the average/locals for (using the existing observations to construct the PDF)
* @return either the average conditional MI, or array of local conditional MI value, in nats not bits
* @throws Exception
*/
protected double[] computeFromObservations(boolean returnLocals) throws Exception {
protected double[] computeFromObservations(boolean returnLocals, double[][][] newObservations) throws Exception {
int N = var1Observations.length; // number of observations
double[] returnValues = null;
@ -488,28 +513,36 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
uniNNSearcherVar2 = new UnivariateNearestNeighbourSearcher(var2Observations);
}
}
if (nnSearcherConditional == null) {
if ((nnSearcherConditional == null) && (dimensionsCond > 0)) {
nnSearcherConditional = NearestNeighbourSearcher.create(condObservations);
nnSearcherConditional.setNormType(normType);
}
// How many time points are we averaging over?
int numTimePointsToComputeFor = (newObservations == null) ?
N : newObservations[0].length;
if (numThreads == 1) {
// Single-threaded implementation:
returnValues = partialComputeFromObservations(0, N, returnLocals);
if (newObservations == null) {
returnValues = partialComputeFromObservations(0, numTimePointsToComputeFor, returnLocals);
} else {
returnValues = partialComputeFromNewObservations(0, numTimePointsToComputeFor,
newObservations[0], newObservations[1], newObservations[2], returnLocals);
}
} else {
// We're going multithreaded:
if (returnLocals) {
// We're computing local conditional MI
returnValues = new double[N];
returnValues = new double[numTimePointsToComputeFor];
} else {
// We're computing average conditional MI
returnValues = new double[CondMiKraskovThreadRunner.RETURN_ARRAY_LENGTH];
}
// Distribute the observations to the threads for the parallel processing
int lTimesteps = N / numThreads; // each thread gets the same amount of data
int res = N % numThreads; // the first thread gets the residual data
int lTimesteps = numTimePointsToComputeFor / numThreads; // each thread gets the same amount of data
int res = numTimePointsToComputeFor % numThreads; // the first thread gets the residual data
if (debug) {
System.out.printf("Computing Kraskov conditional MI with %d threads (%d timesteps each, plus %d residual)\n",
numThreads, lTimesteps, res);
@ -523,7 +556,7 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
System.out.println(t + ".Thread: from " + startTime +
" to " + (startTime + numTimesteps)); // Trace Message
}
runners[t] = new CondMiKraskovThreadRunner(this, startTime, numTimesteps, returnLocals);
runners[t] = new CondMiKraskovThreadRunner(this, startTime, numTimesteps, newObservations, returnLocals);
tCalculators[t] = new Thread(runners[t]);
tCalculators[t].start();
}
@ -552,10 +585,10 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
return returnValues;
} else {
// Average out the components for the final equation(s) and for debugging:
double averageDiGammas = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_DIGAMMAS] / (double) N;
double avNxz = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_NXZ] / (double) N;
double avNyz = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_NYZ] / (double) N;
double avNz = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_NZ] / (double) N;
double averageDiGammas = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_DIGAMMAS] / (double) numTimePointsToComputeFor;
double avNxz = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_NXZ] / (double) numTimePointsToComputeFor;
double avNyz = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_NYZ] / (double) numTimePointsToComputeFor;
double avNz = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_NZ] / (double) numTimePointsToComputeFor;
if (debug) {
System.out.printf("<n_xz>=%.3f, <n_yz>=%.3f, <n_z>=%.3f\n",
avNxz, avNyz, avNz);
@ -573,17 +606,27 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
// Algorithm 2:
// We also retrieve the sums of inverses for debugging purposes:
double averageInverseCountInJointXZ =
returnValues[CondMiKraskovThreadRunner.INDEX_SUM_INV_NXZ] / (double) N;
returnValues[CondMiKraskovThreadRunner.INDEX_SUM_INV_NXZ] / (double) numTimePointsToComputeFor;
double averageInverseCountInJointYZ =
returnValues[CondMiKraskovThreadRunner.INDEX_SUM_INV_NYZ] / (double) N;
double averageMeasure = MathsUtils.digamma(k) - (2.0 / (double)k) + averageDiGammas +
returnValues[CondMiKraskovThreadRunner.INDEX_SUM_INV_NYZ] / (double) numTimePointsToComputeFor;
double inverseKTerm;
if (dimensionsCond > 0) {
// We will add in the 2/K term as usual
inverseKTerm = 2.0 / (double) k;
} else {
// We will only add in 1/K term since without a conditional there is
// technically one less variable in the full joint space
inverseKTerm = 1.0 / (double) k;
}
double averageMeasure = MathsUtils.digamma(k) - inverseKTerm + averageDiGammas +
averageInverseCountInJointXZ + averageInverseCountInJointYZ;
if (debug) {
System.out.printf("Av = digamma(k)=%.3f + <digammas>=%.3f +<inverses>=%.3f - 2/k=%.3f = %.3f" +
System.out.printf("Av = digamma(k)=%.3f + <digammas>=%.3f +<inverses>=%.3f - $d/k=%.3f = %.3f" +
" (<1/n_yz>=%.3f, <1/n_xz>=%.3f)\n",
MathsUtils.digamma(k), averageDiGammas,
averageInverseCountInJointXZ + averageInverseCountInJointYZ,
(2.0 / (double)k), averageMeasure,
dimensionsCond > 0 ? 2 : 1,
inverseKTerm, averageMeasure,
averageInverseCountInJointYZ, averageInverseCountInJointXZ);
}
double[] result = new double[1];
@ -619,6 +662,40 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
protected abstract double[] partialComputeFromObservations(
int startTimePoint, int numTimePoints, boolean returnLocals) throws Exception;
/**
* Protected method to be used internally for threaded implementations.
* This method implements the guts of each Kraskov algorithm, computing the number of
* nearest neighbours in each dimension for a sub-set of the data points.
* It is intended to be called by one thread to work on that specific
* sub-set of the data.
* In particular, this method differs from {@link #partialComputeFromObservations(int, int, boolean)}
* because it operates on a new set of observations (using the old set of observations for
* constructing the search spaces and PDFs)
*
* <p>The method returns:<ol>
* <li>for average conditional MIs (returnLocals == false), the relevant sums of digamma(n_{xz}), digamma(n_{yz})
* and digamma(n_z)
* for a partial set of the observations</li>
* <li>for local conditional MIs (returnLocals == true), the array of local conditional MI values</li>
* </ol>
*
* @param startTimePoint start time for the partial set we examine
* @param numTimePoints number of time points (including startTimePoint to examine)
* @param newVar1Observations new time series of observations for variable 1
* @param newVar2Observations new time series of observations for variable 1
* @param newCondObservations new time series of observations for variable 1
* @param returnLocals whether to return an array or local values, or else
* sums of these values
* @return an array of the relevant sum of digamma(n_xz+1) and digamma(n_yz+1) and digamma(n_z), then
* sum of n_xz, n_yz, n_z and for algorithm 2 only, sum of 1/n_xz and 1/n_yz
* (these latter five are only for debugging purposes).
* @throws Exception
*/
protected abstract double[] partialComputeFromNewObservations(
int startTimePoint, int numTimePoints,
double[][] newVar1Observations, double[][] newVar2Observations, double[][] newCondObservations,
boolean returnLocals) throws Exception;
/**
* Private class to handle multi-threading of the Kraskov algorithms.
* Each instance calls partialComputeFromObservations()
@ -633,6 +710,7 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
protected ConditionalMutualInfoCalculatorMultiVariateKraskov condMiCalc;
protected int myStartTimePoint;
protected int numberOfTimePoints;
protected double[][][] newObservations;
protected boolean computeLocals;
protected double[] returnValues = null;
@ -649,11 +727,13 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
public CondMiKraskovThreadRunner(
ConditionalMutualInfoCalculatorMultiVariateKraskov condMiCalc,
int myStartTimePoint, int numberOfTimePoints,
double[][][] newObservations,
boolean computeLocals) {
this.condMiCalc = condMiCalc;
this.myStartTimePoint = myStartTimePoint;
this.numberOfTimePoints = numberOfTimePoints;
this.computeLocals = computeLocals;
this.newObservations = newObservations;
}
/**
@ -676,7 +756,16 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
*/
public void run() {
try {
returnValues = condMiCalc.partialComputeFromObservations(myStartTimePoint, numberOfTimePoints, computeLocals);
if (newObservations == null) {
// Computing on existing observations
returnValues = condMiCalc.partialComputeFromObservations(myStartTimePoint, numberOfTimePoints, computeLocals);
} else {
// Computing on new observations
returnValues = condMiCalc.partialComputeFromNewObservations(
myStartTimePoint, numberOfTimePoints,
newObservations[0], newObservations[1],
newObservations[2], computeLocals);
}
} catch (Exception e) {
// Store the exception for later retrieval
problem = e;

View File

@ -18,12 +18,18 @@
package infodynamics.measures.continuous.kraskov;
import java.util.Arrays;
import java.util.Calendar;
import java.util.PriorityQueue;
import infodynamics.measures.continuous.ConditionalMutualInfoCalculatorMultiVariate;
import infodynamics.utils.EmpiricalMeasurementDistribution;
import infodynamics.utils.FirstIndexComparatorDouble;
import infodynamics.utils.KdTree;
import infodynamics.utils.MathsUtils;
import infodynamics.utils.MatrixUtils;
import infodynamics.utils.NeighbourNodeData;
import infodynamics.utils.UnivariateNearestNeighbourSearcher;
/**
* <p>Computes the differential conditional mutual information of two multivariate
@ -99,7 +105,20 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
// First element in the PQ is the kth NN,
// and epsilon = kthNnData.distance
NeighbourNodeData kthNnData = nnPQ.poll();
/*
if (debug) {
System.out.print("t = " + t + " : for data point: ");
MatrixUtils.printArray(System.out, var1Observations[t]);
MatrixUtils.printArray(System.out, var2Observations[t]);
if (dimensionsCond > 0) {
MatrixUtils.printArray(System.out, condObservations[t]);
} else {
System.out.print("[]");
}
System.out.printf("t=%d: K=%d NNs found at range %.5f (point %d)\n", t, k, kthNnData.distance, kthNnData.sampleIndex);
}
*/
// Now count the points in the conditional space, and
// the var1-conditional and var2-conditional spaces.
// We have 3 coded options for how to do this:
@ -112,7 +131,7 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
int n_yz = kdTreeVar2Conditional.countPointsStrictlyWithinR(
t, kthNnData.distance, dynCorrExclTime);
int n_z = nnSearcherConditional.countPointsStrictlyWithinR(
t, kthNnData.distance, dynCorrExclTime);
t, kthNnData.distance, dynCorrExclTime);
*/ // end option A
/* Option B --
@ -151,8 +170,10 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
if (debug) {
methodStartTime = Calendar.getInstance().getTimeInMillis();
}
nnSearcherConditional.findPointsWithinR(t, kthNnData.distance, dynCorrExclTime,
if (dimensionsCond > 0) {
nnSearcherConditional.findPointsWithinR(t, kthNnData.distance, dynCorrExclTime,
false, isWithinRForConditionals, indicesWithinRForConditionals);
}
if (debug) {
conditionalTime += Calendar.getInstance().getTimeInMillis() -
methodStartTime;
@ -160,15 +181,26 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
}
// 2. Then compute n_xz and n_yz harnessing our knowledge of
// which points qualified for the conditional already:
// Don't need to supply dynCorrExclTime in the following, because only
// Don't need to supply dynCorrExclTime in most of the following, because only
// points outside of it have been included in isWithinRForConditionals
int n_xz;
if (dimensionsVar1 > 1) {
n_xz = kdTreeVar1Conditional.countPointsWithinR(t, kthNnData.distance,
false, 1, isWithinRForConditionals);
} else { // Generally faster to search only the marginal space if it is univariate
n_xz = uniNNSearcherVar1.countPointsWithinR(t, kthNnData.distance,
false, isWithinRForConditionals);
if (dimensionsCond == 0) {
// We're really only counting whether the x space qualifies
if (dimensionsVar1 > 1) {
n_xz = kdTreeVar1Conditional.countPointsStrictlyWithinR(
t, kthNnData.distance, dynCorrExclTime);
} else {
n_xz = uniNNSearcherVar1.countPointsWithinR(t, kthNnData.distance,
dynCorrExclTime, false);
}
} else {
if (dimensionsVar1 > 1) {
n_xz = kdTreeVar1Conditional.countPointsWithinR(t, kthNnData.distance,
false, 1, isWithinRForConditionals);
} else { // Generally faster to search only the marginal space if it is univariate
n_xz = uniNNSearcherVar1.countPointsWithinR(t, kthNnData.distance,
false, isWithinRForConditionals);
}
}
if (debug) {
conditionalXTime += Calendar.getInstance().getTimeInMillis() -
@ -176,12 +208,23 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
methodStartTime = Calendar.getInstance().getTimeInMillis();
}
int n_yz;
if (dimensionsVar2 > 1) {
n_yz = kdTreeVar2Conditional.countPointsWithinR(t, kthNnData.distance,
false, 1, isWithinRForConditionals);
} else { // Generally faster to search only the marginal space if it is univariate
n_yz = uniNNSearcherVar2.countPointsWithinR(t, kthNnData.distance,
false, isWithinRForConditionals);
if (dimensionsCond == 0) {
// We're really only counting whether the y space qualifies
if (dimensionsVar2 > 1) {
n_yz = kdTreeVar2Conditional.countPointsStrictlyWithinR(
t, kthNnData.distance, dynCorrExclTime);
} else {
n_yz = uniNNSearcherVar2.countPointsWithinR(t, kthNnData.distance,
dynCorrExclTime, false);
}
} else {
if (dimensionsVar2 > 1) {
n_yz = kdTreeVar2Conditional.countPointsWithinR(t, kthNnData.distance,
false, 1, isWithinRForConditionals);
} else { // Generally faster to search only the marginal space if it is univariate
n_yz = uniNNSearcherVar2.countPointsWithinR(t, kthNnData.distance,
false, isWithinRForConditionals);
}
}
if (debug) {
conditionalYTime += Calendar.getInstance().getTimeInMillis() -
@ -189,8 +232,15 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
}
// 3. Finally, reset our boolean array for its next use while we count n_z:
int n_z;
for (n_z = 0; indicesWithinRForConditionals[n_z] != -1; n_z++) {
isWithinRForConditionals[indicesWithinRForConditionals[n_z]] = false;
if (dimensionsCond == 0) {
n_z = totalObservations - 1; // - 1 to remove the point itself.
// Note: This doesn't respect the dynamic correlation exclusion, but
// does align with a mutual information calculation here (to give the digamma(N) term).
// No need to reset the boolean array
} else {
for (n_z = 0; indicesWithinRForConditionals[n_z] != -1; n_z++) {
isWithinRForConditionals[indicesWithinRForConditionals[n_z]] = false;
}
}
// end option C
@ -206,11 +256,16 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
if (returnLocals) {
localCondMi[t-startTimePoint] = digammaK - digammaNxzPlusOne - digammaNyzPlusOne + digammaNzPlusOne;
if (debug) {
System.out.printf("t=%d, n_xz=%d, n_yz=%d, n_z=%d, local=%.4f," +
System.out.printf("t=%d, r=%.5f, n_xz=%d, n_yz=%d, n_z=%d, local=%.4f," +
" digamma(n_xz+1)=%.5f, digamma(n_yz+1)=%.5f, digamma(n_z+1)=%.5f, \n",
t, n_xz, n_yz, n_z, localCondMi[t-startTimePoint],
t, kthNnData.distance, n_xz, n_yz, n_z, localCondMi[t-startTimePoint],
digammaNxzPlusOne, digammaNyzPlusOne, digammaNzPlusOne);
}
} else if (debug) {
System.out.printf("t=%d, n_xz=%d, n_yz=%d, n_z=%d," +
" digamma(n_xz+1)=%.5f, digamma(n_yz+1)=%.5f, digamma(n_z+1)=%.5f, \n",
t, n_xz, n_yz, n_z,
digammaNxzPlusOne, digammaNyzPlusOne, digammaNzPlusOne);
}
}
@ -244,4 +299,214 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
return results;
}
}
/**
* Compute the local conditional mutual information values for a new set of
* observations, where the PDFs are constructed from the observations added earlier.
*
* @param startTimePoint
* @param numTimePoints
* @param newStates1
* @param newStates2
* @param newCondStates
* @return
* @throws Exception
*/
@Override
protected double[] partialComputeFromNewObservations(int startTimePoint,
int numTimePoints, double[][] newStates1,
double[][] newStates2, double[][] newCondStates, boolean returnLocals) throws Exception {
double startTime = Calendar.getInstance().getTimeInMillis();
double[] localCondMi = null;
if (returnLocals) {
localCondMi = new double[numTimePoints];
}
// Count the average number of points within eps_xz and eps_yz and eps_z of each point
double sumDiGammas = 0;
double sumNxz = 0;
double sumNyz = 0;
double sumNz = 0;
long knnTime = 0, conditionalTime = 0,
conditionalXTime = 0, conditionalYTime = 0;
// Arrays used for fast searching on conditionals with a marginal:
boolean[] isWithinRForConditionals = new boolean[totalObservations];
int[] indicesWithinRForConditionals = new int[totalObservations+1];
for (int t = startTimePoint; t < startTimePoint + numTimePoints; t++) {
// Compute eps for this time step by
// finding the kth closest neighbour for point t:
long methodStartTime = Calendar.getInstance().getTimeInMillis();
PriorityQueue<NeighbourNodeData> nnPQ =
kdTreeJoint.findKNearestNeighbours(k,
new double[][] {newStates1[t], newStates2[t], newCondStates[t]});
knnTime += Calendar.getInstance().getTimeInMillis() -
methodStartTime;
// First element in the PQ is the kth NN,
// and epsilon = kthNnData.distance
NeighbourNodeData kthNnData = nnPQ.poll();
if (debug) {
System.out.print("t = " + t + " : for data point: ");
MatrixUtils.printArray(System.out, newStates1[t]);
MatrixUtils.printArray(System.out, newStates2[t]);
if (dimensionsCond > 0) {
MatrixUtils.printArray(System.out, newCondStates[t]);
} else {
System.out.print("[]");
}
System.out.printf("t=%d : K=%d NNs found at range %.5f (point %d)\n", t, k, kthNnData.distance, kthNnData.sampleIndex);
}
// Now count the points in the conditional space, and
// the var1-conditional and var2-conditional spaces.
// We use Option C determined above to do this --
// Identify the points satisfying the conditional criteria, then use
// the knowledge of which points made this cut to speed up the searching
// in the conditional-marginal spaces:
// 1. Identify the n_z points within the conditional boundaries:
if (debug) {
methodStartTime = Calendar.getInstance().getTimeInMillis();
}
if (dimensionsCond > 0) {
nnSearcherConditional.findPointsWithinR(kthNnData.distance,
new double[][] {newCondStates[t]},
false, isWithinRForConditionals, indicesWithinRForConditionals);
}
if (debug) {
conditionalTime += Calendar.getInstance().getTimeInMillis() -
methodStartTime;
methodStartTime = Calendar.getInstance().getTimeInMillis();
}
// 2. Then compute n_xz and n_yz harnessing our knowledge of
// which points qualified for the conditional already:
// Don't need to supply dynCorrExclTime in the following, because
// it's not relevant for the new points
int n_xz;
if (dimensionsCond == 0) {
// We're really only counting whether the x space qualifies
if (dimensionsVar1 > 1) {
n_xz = kdTreeVar1Conditional.countPointsWithinR(
new double[][] {newStates1[t], newCondStates[t]},
kthNnData.distance, false);
} else {
n_xz = uniNNSearcherVar1.countPointsWithinR(
new double[][] {newStates1[t]}, kthNnData.distance, false);
}
} else {
if (dimensionsVar1 > 1) {
n_xz = kdTreeVar1Conditional.countPointsWithinR(kthNnData.distance,
new double[][] {newStates1[t], newCondStates[t]},
false, 1, isWithinRForConditionals);
} else { // Generally faster to search only the marginal space if it is univariate
n_xz = uniNNSearcherVar1.countPointsWithinR(
new double[][] {newStates1[t]}, kthNnData.distance,
false, isWithinRForConditionals);
}
}
if (debug) {
conditionalXTime += Calendar.getInstance().getTimeInMillis() -
methodStartTime;
methodStartTime = Calendar.getInstance().getTimeInMillis();
}
int n_yz;
if (dimensionsCond == 0) {
// We're really only counting whether the y space qualifies
if (dimensionsVar2 > 1) {
n_yz = kdTreeVar2Conditional.countPointsWithinR(
new double[][] {newStates2[t], newCondStates[t]},
kthNnData.distance, false);
} else {
n_yz = uniNNSearcherVar2.countPointsWithinR(
new double[][] {newStates2[t]},
kthNnData.distance, false);
}
} else {
if (dimensionsVar2 > 1) {
n_yz = kdTreeVar2Conditional.countPointsWithinR(kthNnData.distance,
new double[][] {newStates2[t], newCondStates[t]},
false, 1, isWithinRForConditionals);
} else { // Generally faster to search only the marginal space if it is univariate
n_yz = uniNNSearcherVar2.countPointsWithinR(
new double[][] {newStates2[t]}, kthNnData.distance,
false, isWithinRForConditionals);
}
}
if (debug) {
conditionalYTime += Calendar.getInstance().getTimeInMillis() -
methodStartTime;
}
// 3. Finally, reset our boolean array for its next use while we count n_z:
int n_z;
if (dimensionsCond == 0) {
n_z = totalObservations - 1; // - 1 to remove the point itself.
// no need to reset the boolean array
} else {
for (n_z = 0; indicesWithinRForConditionals[n_z] != -1; n_z++) {
isWithinRForConditionals[indicesWithinRForConditionals[n_z]] = false;
}
}
// end option C
sumNxz += n_xz;
sumNyz += n_yz;
sumNz += n_z;
// And take the digammas:
double digammaNxzPlusOne = MathsUtils.digamma(n_xz+1);
double digammaNyzPlusOne = MathsUtils.digamma(n_yz+1);
double digammaNzPlusOne = MathsUtils.digamma(n_z+1);
sumDiGammas += digammaNzPlusOne - digammaNxzPlusOne - digammaNyzPlusOne;
if (returnLocals) {
localCondMi[t-startTimePoint] = digammaK - digammaNxzPlusOne - digammaNyzPlusOne + digammaNzPlusOne;
if (debug) {
System.out.printf("t=%d, n_xz=%d, n_yz=%d, n_z=%d, local=%.4f," +
" digamma(n_xz+1)=%.5f, digamma(n_yz+1)=%.5f, digamma(n_z+1)=%.5f, \n",
t, n_xz, n_yz, n_z, localCondMi[t-startTimePoint],
digammaNxzPlusOne, digammaNyzPlusOne, digammaNzPlusOne);
}
} else if (debug) {
double localValue = digammaK - digammaNxzPlusOne - digammaNyzPlusOne + digammaNzPlusOne;
System.out.printf("t=%d, n_xz=%d, n_yz=%d, n_z=%d, local=%.4f," +
" digamma(n_xz+1)=%.5f, digamma(n_yz+1)=%.5f, digamma(n_z+1)=%.5f, \n",
t, n_xz, n_yz, n_z, localValue,
digammaNxzPlusOne, digammaNyzPlusOne, digammaNzPlusOne);
}
}
if (debug) {
Calendar rightNow2 = Calendar.getInstance();
long endTime = rightNow2.getTimeInMillis();
System.out.println("Subset " + startTimePoint + ":" +
(startTimePoint + numTimePoints) + " Calculation time: " +
((endTime - startTime)/1000.0) + " sec" );
System.out.println("Total exec times for: ");
System.out.println("\tknn search: " + (knnTime/1000.0));
System.out.println("\tz search: " + (conditionalTime/1000.0));
System.out.println("\tzx search: " + (conditionalXTime/1000.0));
System.out.println("\tzy search: " + (conditionalYTime/1000.0));
System.out.printf("%d:%d -- Returning: %.4f, %.4f, %.4f, %.4f\n",
startTimePoint, (startTimePoint + numTimePoints),
sumDiGammas, sumNxz, sumNyz, sumNz);
}
// Select what to return:
if (returnLocals) {
return localCondMi;
} else {
// Pad return array with two values, to allow compatibility in
// return length with algorithm 2
double[] results = new double[6];
results[0] = sumDiGammas;
results[1] = sumNxz;
results[2] = sumNyz;
results[3] = sumNz;
return results;
}
}
}

View File

@ -79,7 +79,15 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov2
}
// Constants:
double twoOnK = 2.0 / (double) k;
double inverseKTerm;
if (dimensionsCond > 0) {
// We will add in the 2/K term as usual
inverseKTerm = 2.0 / (double) k;
} else {
// We will only add in 1/K term since without a conditional there is
// technically one less variable in the full joint space
inverseKTerm = 1.0 / (double) k;
}
// Count the average number of points within eps_xz, eps_yz and eps_z
double sumDiGammas = 0;
@ -136,36 +144,68 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov2
// the knowledge of which points made this cut to speed up the searching
// in the conditional-marginal spaces:
// 1. Identify the n_z points within the conditional boundaries:
nnSearcherConditional.findPointsWithinR(t, eps_z, dynCorrExclTime,
if (dimensionsCond > 0) {
nnSearcherConditional.findPointsWithinR(t, eps_z, dynCorrExclTime,
true, isWithinRForConditionals, indicesWithinRForConditionals);
}
// 2. Then compute n_xz and n_yz harnessing our knowledge of
// which points qualified for the conditional already:
// Don't need to supply dynCorrExclTime in the following, because only
// points outside of it have been included in isWithinRForConditionals
int n_xz;
if (dimensionsVar1 > 1) {
// Check only the x variable against eps_x, use existing results for z
n_xz = kdTreeVar1Conditional.countPointsWithinRs(t,
new double[] {eps_x, eps_z},
true, 1, isWithinRForConditionals);
} else { // Generally faster to search only the marginal space if it is univariate
n_xz = uniNNSearcherVar1.countPointsWithinR(t, eps_x,
true, isWithinRForConditionals);
if (dimensionsCond == 0) {
// We're really only counting whether the x space qualifies
if (dimensionsVar1 > 1) {
n_xz = kdTreeVar1Conditional.countPointsWithinOrOnR(
t, eps_x, dynCorrExclTime); // No need to pass eps_z in
} else { // Generally faster to search only the marginal space if it is univariate
n_xz = uniNNSearcherVar1.countPointsWithinOrOnR(t, eps_x,
dynCorrExclTime);
}
} else {
if (dimensionsVar1 > 1) {
// Check only the x variable against eps_x, use existing results for z
n_xz = kdTreeVar1Conditional.countPointsWithinRs(t,
new double[] {eps_x, eps_z},
true, 1, isWithinRForConditionals);
} else { // Generally faster to search only the marginal space if it is univariate
n_xz = uniNNSearcherVar1.countPointsWithinR(t, eps_x,
true, isWithinRForConditionals);
}
}
int n_yz;
if (dimensionsVar2 > 1) {
// Check only the y variable against eps_y, use existing results for z
n_yz = kdTreeVar2Conditional.countPointsWithinRs(t,
new double[] {eps_y, eps_z},
true, 1, isWithinRForConditionals);
} else { // Generally faster to search only the marginal space if it is univariate
n_yz = uniNNSearcherVar2.countPointsWithinR(t, eps_y,
true, isWithinRForConditionals);
if (dimensionsCond == 0) {
if (dimensionsVar2 > 1) {
// Check only the y variable against eps_y, use existing results for z
n_yz = kdTreeVar2Conditional.countPointsWithinOrOnR(
t, eps_y, dynCorrExclTime); // No need to pass eps_z in
} else { // Generally faster to search only the marginal space if it is univariate
n_yz = uniNNSearcherVar2.countPointsWithinOrOnR(t, eps_y,
dynCorrExclTime);
}
} else {
// We're really only counting whether the y space qualifies
if (dimensionsVar2 > 1) {
// Check only the y variable against eps_y, use existing results for z
n_yz = kdTreeVar2Conditional.countPointsWithinRs(t,
new double[] {eps_y, eps_z},
true, 1, isWithinRForConditionals);
} else { // Generally faster to search only the marginal space if it is univariate
n_yz = uniNNSearcherVar2.countPointsWithinR(t, eps_y,
true, isWithinRForConditionals);
}
}
// 3. Finally, reset our boolean array for its next use while we count n_z:
int n_z;
for (n_z = 0; indicesWithinRForConditionals[n_z] != -1; n_z++) {
isWithinRForConditionals[indicesWithinRForConditionals[n_z]] = false;
if (dimensionsCond == 0) {
n_z = totalObservations; // Dropping - 1 to remove the point itself, and
// Note: This doesn't respect the dynamic correlation exclusion, but this
// does align with a mutual information calculation here (to give the digamma(N) term).
// No need to reset the boolean array
} else {
for (n_z = 0; indicesWithinRForConditionals[n_z] != -1; n_z++) {
isWithinRForConditionals[indicesWithinRForConditionals[n_z]] = false;
}
}
// end option C
@ -177,15 +217,25 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov2
double digammaNxz = MathsUtils.digamma(n_xz);
double digammaNyz = MathsUtils.digamma(n_yz);
double digammaNz = MathsUtils.digamma(n_z);
double invN_xz = 1.0/(double) n_xz;
double invN_yz = 1.0/(double) n_yz;
double invN_xz, invN_yz;
if (dimensionsCond > 0) {
// We will add in the inverse neighbour counts as usual
invN_xz = 1.0/(double) n_xz;
invN_yz = 1.0/(double) n_yz;
} else {
// We do not add in the inverse neighbour counts, because they
// are technically coming from a single variable neighbour search,
// and so there is a 0/n_xz and 0/n_yz contribution
invN_xz = 0.0;
invN_yz = 0.0;
}
sumInverseCountInJointXZ += invN_xz;
sumInverseCountInJointYZ += invN_yz;
double contributionDigammas = digammaNz - digammaNxz - digammaNyz;
sumDiGammas += contributionDigammas;
if (returnLocals) {
localCondMi[t-startTimePoint] = digammaK - twoOnK + contributionDigammas + invN_xz + invN_yz;
localCondMi[t-startTimePoint] = digammaK - inverseKTerm + contributionDigammas + invN_xz + invN_yz;
if (debug) {
// Only tracking this for debugging purposes:
System.out.printf("t=%d, n_xz=%d, n_yz=%d, n_z=%d, 1/n_yz=%.3f, 1/n_xz=%.3f, local=%.4f\n",
@ -216,4 +266,13 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov2
return returnValues;
}
}
@Override
protected double[] partialComputeFromNewObservations(int startTimePoint,
int numTimePoints, double[][] newVar1Observations,
double[][] newVar2Observations, double[][] newCondObservations,
boolean returnLocals) throws Exception {
// TODO implement me
throw new RuntimeException("Not implemented yet");
}
}