mirror of https://github.com/jlizier/jidt
747 lines
29 KiB
Java
Executable File
747 lines
29 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.measures.continuous.MutualInfoCalculatorMultiVariate;
|
|
import infodynamics.measures.continuous.MutualInfoMultiVariateCommon;
|
|
|
|
/**
|
|
* <p>Computes the differential mutual information of two given multivariate sets of
|
|
* observations (implementing {@link MutualInfoCalculatorMultiVariate}),
|
|
* using box-kernel estimation.
|
|
* For details on box-kernel estimation, see Kantz and Schreiber (below).</p>
|
|
*
|
|
* <p>Usage is as per the paradigm outlined for {@link MutualInfoCalculatorMultiVariate},
|
|
* with:
|
|
* <ul>
|
|
* <li>The constructor step being a simple call to {@link #MutualInfoCalculatorMultiVariateKernel()}.</li>
|
|
* <li>Further properties are available, see {@link #setProperty(String, String)};</li>
|
|
* <li>An additional {@link #initialise(int, int, double)} option;</li>
|
|
* <li>Additional utility methods for computing other information-theoretic values
|
|
* are available here (e.g. {@link #computeAverageJointEntropy()}) which
|
|
* can be called after all observations are supplied.</li>
|
|
* </ul>
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* TODO Use only a single kernel estimator class for the joint space, and compute other
|
|
* probabilities from this. This will save much time.
|
|
* </p>
|
|
*
|
|
* @see "H. Kantz and T. Schreiber, 'Nonlinear Time Series Analysis'.
|
|
* Cambridge, MA: Cambridge University Press, 1997"
|
|
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
|
|
* <a href="http://lizier.me/joseph/">www</a>)
|
|
*/
|
|
public class MutualInfoCalculatorMultiVariateKernel
|
|
extends MutualInfoMultiVariateCommon
|
|
implements MutualInfoCalculatorMultiVariate, Cloneable {
|
|
|
|
protected KernelEstimatorMultiVariate mvkeSource = null;
|
|
protected KernelEstimatorMultiVariate mvkeDest = null;
|
|
protected KernelEstimatorMultiVariate mvkeJoint = null;
|
|
|
|
private boolean normalise = true;
|
|
/**
|
|
* Property name for whether to normalise the incoming variables
|
|
* to mean 0, standard deviation 1, or not (default false)
|
|
*/
|
|
public static final String NORMALISE_PROP_NAME = "NORMALISE";
|
|
|
|
private boolean dynCorrExcl = false;
|
|
private int dynCorrExclTime = 0;
|
|
/**
|
|
* Property name for a dynamics exclusion time window (see Kantz and Schreiber),
|
|
* default is 0 which means no dynamic exclusion window.
|
|
*/
|
|
public static final String DYN_CORR_EXCL_TIME_NAME = "DYN_CORR_EXCL";
|
|
|
|
private boolean forceCompareToAll = false;
|
|
/**
|
|
* Property name for whether to force the underlying kernel estimators to compare
|
|
* each data point to each other (or else allow it to use optimisations)
|
|
*/
|
|
public static final String FORCE_KERNEL_COMPARE_TO_ALL = "FORCE_KERNEL_COMPARE_TO_ALL";
|
|
|
|
/**
|
|
* Default value for kernel width
|
|
*/
|
|
public static final double DEFAULT_KERNEL_WIDTH = 0.25;
|
|
/**
|
|
* Kernel width currently in use
|
|
*/
|
|
private double kernelWidth = DEFAULT_KERNEL_WIDTH;
|
|
/**
|
|
* Property name for the kernel width
|
|
*/
|
|
public static final String KERNEL_WIDTH_PROP_NAME = "KERNEL_WIDTH";
|
|
/**
|
|
* Legacy property name for the kernel width
|
|
*/
|
|
public static final String EPSILON_PROP_NAME = "EPSILON";
|
|
|
|
/**
|
|
* Construct an instance of the box-kernel MI calculator
|
|
*/
|
|
public MutualInfoCalculatorMultiVariateKernel() {
|
|
// Create our kernel estimator objects:
|
|
mvkeSource = new KernelEstimatorMultiVariate();
|
|
mvkeDest = new KernelEstimatorMultiVariate();
|
|
mvkeJoint = new KernelEstimatorMultiVariate();
|
|
mvkeSource.setNormalise(normalise);
|
|
mvkeDest.setNormalise(normalise);
|
|
mvkeJoint.setNormalise(normalise);
|
|
}
|
|
|
|
public void initialise(int sourceDimensions, int destDimensions) {
|
|
initialise(sourceDimensions, destDimensions, kernelWidth);
|
|
}
|
|
|
|
/**
|
|
* Initialise the calculator for (re-)use, with a specific kernel width
|
|
* with number of source
|
|
* and destination joint variables specified, and existing
|
|
* (or default) values of other parameters,.
|
|
* Clears an PDFs of previously supplied observations.
|
|
*
|
|
* @param sourceDimensions the number of joint variables in the source
|
|
* @param destDimensions the number of joint variables in the destination
|
|
* @param kernelWidth if {@link #NORMALISE_PROP_NAME} property has
|
|
* been set, then this kernel width corresponds to the number of
|
|
* standard deviations from the mean (otherwise it is an absolute value)
|
|
*/
|
|
public void initialise(int sourceDimensions, int destDimensions, double kernelWidth) {
|
|
super.initialise(sourceDimensions, destDimensions);
|
|
// Store kernel width for local use
|
|
this.kernelWidth = kernelWidth;
|
|
// Initialise the kernel estimators:
|
|
mvkeSource.initialise(sourceDimensions, kernelWidth);
|
|
mvkeDest.initialise(destDimensions, kernelWidth);
|
|
mvkeJoint.initialise(sourceDimensions + destDimensions, kernelWidth);
|
|
}
|
|
|
|
public void finaliseAddObservations() {
|
|
// Get the observations properly stored in the sourceObservations[][] and
|
|
// destObservations[][] arrays.
|
|
try {
|
|
// Currently, the throws declaration in super is only there to
|
|
// allow other children to throw Exceptions - there are no compile
|
|
// time Exceptions being thrown there.
|
|
super.finaliseAddObservations();
|
|
} catch (Exception e) {
|
|
// So we cast any found Exception to a RuntimeException
|
|
throw new RuntimeException(e);
|
|
}
|
|
|
|
// Now assign these observations to the underlying kernel estimators:
|
|
mvkeSource.setObservations(sourceObservations);
|
|
mvkeDest.setObservations(destObservations);
|
|
try {
|
|
// This will only throw an exception, in theory, if
|
|
// the time length of the observations is different
|
|
// or if they are null - since we constructed them
|
|
// neither of these should be the case.
|
|
mvkeJoint.setObservations(sourceObservations, destObservations);
|
|
} catch (Exception e) {
|
|
throw new RuntimeException("Unhandled exception from MultivariateKernelEstimator.setObservations(double[][], double[][])", e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void normaliseData() {
|
|
// Here we actually do nothing -- normalising is implemented for
|
|
// the kernel estimator by adjusting the kernel widths only, not by
|
|
// changing the data we operate on!!
|
|
}
|
|
|
|
/**
|
|
* Compute the average MI from the previously supplied observations.
|
|
*
|
|
* @return the average MI in bits
|
|
*/
|
|
public double computeAverageLocalOfObservations() {
|
|
double mi = 0.0;
|
|
for (int b = 0; b < totalObservations; b++) {
|
|
double prob1 = mvkeSource.getProbability(sourceObservations[b], b);
|
|
double prob2 = mvkeDest.getProbability(destObservations[b], b);
|
|
double probJoint = mvkeJoint.getProbability(sourceObservations[b], destObservations[b], b);
|
|
double logTerm = 0.0;
|
|
double cont = 0.0;
|
|
if (probJoint > 0.0) {
|
|
// If we have counted joint correlations, we must have marginals for each
|
|
logTerm = probJoint / (prob1 * prob2);
|
|
cont = Math.log(logTerm);
|
|
}
|
|
mi += cont;
|
|
if (debug) {
|
|
System.out.printf("%d: (%.5f, %.5f, %.5f) %.5f -> %.5f -> %.5f\n",
|
|
b, prob1, prob2, probJoint, logTerm, cont, mi);
|
|
}
|
|
}
|
|
lastAverage = mi / (double) totalObservations / Math.log(2.0);
|
|
miComputed = true;
|
|
return lastAverage;
|
|
}
|
|
|
|
/**
|
|
* Extra utility method to return the joint entropy, for the source
|
|
* and destination variables considered jointly, using the previously supplied
|
|
* observations.
|
|
*
|
|
* @return the average joint entropy in bits.
|
|
*/
|
|
public double computeAverageJointEntropy() {
|
|
double entropy = 0.0;
|
|
for (int b = 0; b < totalObservations; b++) {
|
|
double prob = mvkeJoint.getProbability(sourceObservations[b], destObservations[b], b);
|
|
double cont = 0.0;
|
|
if (prob > 0.0) {
|
|
cont = - Math.log(prob);
|
|
}
|
|
entropy += cont;
|
|
if (debug) {
|
|
System.out.println(b + ": " + prob + " -> " + cont/Math.log(2.0) + " -> sum: " + (entropy/Math.log(2.0)));
|
|
}
|
|
}
|
|
return entropy / (double) totalObservations / Math.log(2.0);
|
|
}
|
|
|
|
/**
|
|
* Extra utility method to return the entropy of the (first) source variable
|
|
*
|
|
* @return entropy of source variable in bits.
|
|
*/
|
|
public double computeAverageEntropyOfObservation1() {
|
|
double entropy = 0.0;
|
|
for (int b = 0; b < totalObservations; b++) {
|
|
double prob = mvkeSource.getProbability(sourceObservations[b], b);
|
|
double cont = 0.0;
|
|
// Comparing the prob to 0.0 should be fine - it would have to be
|
|
// an impossible number of samples for us to hit machine resolution here.
|
|
if (prob > 0.0) {
|
|
cont = -Math.log(prob);
|
|
}
|
|
entropy += cont;
|
|
if (debug) {
|
|
System.out.println(b + ": " + prob + " -> " + cont/Math.log(2.0) + " -> sum: " + (entropy/Math.log(2.0)));
|
|
}
|
|
}
|
|
return entropy / (double) totalObservations / Math.log(2.0);
|
|
}
|
|
|
|
/**
|
|
* Extra utility method to return the entropy of the second (destination) variable
|
|
*
|
|
* @return entropy of the destination in bits
|
|
*/
|
|
public double computeAverageEntropyOfObservation2() {
|
|
double entropy = 0.0;
|
|
for (int b = 0; b < totalObservations; b++) {
|
|
double prob = mvkeDest.getProbability(destObservations[b], b);
|
|
double cont = 0.0;
|
|
if (prob > 0.0) {
|
|
cont = -Math.log(prob);
|
|
}
|
|
entropy += cont;
|
|
if (debug) {
|
|
System.out.println(b + ": " + prob + " -> " + cont/Math.log(2.0) + " -> sum: " + (entropy/Math.log(2.0)));
|
|
}
|
|
}
|
|
return entropy / (double) totalObservations / Math.log(2.0);
|
|
}
|
|
|
|
/**
|
|
* Extra utility method to return the information distance between the source
|
|
* and destination
|
|
*
|
|
* @return information distance between the source and destination in bits.
|
|
*/
|
|
public double computeAverageInfoDistanceOfObservations() {
|
|
double infoDistance = 0.0;
|
|
for (int b = 0; b < totalObservations; b++) {
|
|
double prob1 = mvkeSource.getProbability(sourceObservations[b], b);
|
|
double prob2 = mvkeDest.getProbability(destObservations[b], b);
|
|
double probJoint = mvkeJoint.getProbability(sourceObservations[b], destObservations[b], b);
|
|
double logTerm = 0.0;
|
|
double cont = 0.0;
|
|
if (probJoint > 0.0) {
|
|
logTerm = (prob1 * prob2) / (probJoint * probJoint);
|
|
cont = Math.log(logTerm);
|
|
}
|
|
infoDistance += cont;
|
|
if (debug) {
|
|
System.out.println(b + ": " + logTerm + " -> " + (cont/Math.log(2.0)) + " -> sum: " + (infoDistance/Math.log(2.0)));
|
|
}
|
|
}
|
|
return infoDistance / (double) totalObservations / Math.log(2.0);
|
|
}
|
|
|
|
/**
|
|
* <p>Computes the local values of the MI,
|
|
* for each valid observation in the previously supplied observations
|
|
* (with PDFs computed using all of the previously supplied observation sets).</p>
|
|
*
|
|
* <p>If the samples were supplied via a single call such as
|
|
* {@link #setObservations(double[])},
|
|
* then the return value is a single time-series of local
|
|
* channel measure values corresponding to these samples.</p>
|
|
*
|
|
* <p>Otherwise where disjoint time-series observations were supplied using several
|
|
* calls such as {@link addObservations(double[])}
|
|
* then the local values for each disjoint observation set will be appended here
|
|
* to create a single "time-series" return array.</p>
|
|
*
|
|
* @return the "time-series" of local MIs in bits
|
|
* @throws Exception
|
|
*/
|
|
public double[] computeLocalOfPreviousObservations() throws Exception {
|
|
return computeLocalUsingPreviousObservations(sourceObservations, destObservations, true);
|
|
}
|
|
|
|
|
|
/**
|
|
* Compute the local MI values for each of the
|
|
* supplied samples in <code>states1</code> and <code>states2</code>.
|
|
*
|
|
* <p>PDFs are computed using all of the previously supplied
|
|
* observations, but not those in <code>states1</code> and <code>states2</code>
|
|
* (unless they were
|
|
* some of the previously supplied samples).</p>
|
|
*
|
|
* <p>Calls to this method will not harness dynamic correlation exclusion (if set)
|
|
* since we don't know whether it's the same time set or not.
|
|
* </p>
|
|
*
|
|
* @param states1 series of multivariate observations for the source variable
|
|
* (first index is time or observation index, second is variable number)
|
|
* @param states2 series of multivariate observations for the destination variable
|
|
* (first index is time or observation index, second is variable number).
|
|
* Length must match <code>source</code>, and their indices must correspond.
|
|
* @return the local values in bits.
|
|
* If the {@link MutualInfoCalculatorMultiVariate#PROP_TIME_DIFF}
|
|
* property was set to say k, then the local values align with the
|
|
* destination value (i.e. after the given delay k). As such, the
|
|
* first k values of the array will be zeros.
|
|
*/
|
|
public double[] computeLocalUsingPreviousObservations(double states1[][], double states2[][]) {
|
|
return computeLocalUsingPreviousObservations(states1, states2, false);
|
|
}
|
|
|
|
/**
|
|
* Protected utility function to compute the local MI values for each of the
|
|
* supplied samples in <code>states1</code> and <code>states2</code>.
|
|
*
|
|
* <p>PDFs are computed using all of the previously supplied
|
|
* observations. <code>isOurPreviousObservations</code> indicates whether
|
|
* those in <code>states1</code> and <code>states2</code>
|
|
* were some of the previously supplied samples.</p>
|
|
*
|
|
* @param states1 provided source observations
|
|
* @param states2 provided destination observations
|
|
* @param isOurPreviousObservations whether these are our previous
|
|
* observations - this determines whether to add zeros for the first
|
|
* timeDiff local values, and also
|
|
* whether to set the internal lastAverage field,
|
|
* which is returned by later calls to {@link #getLastAverage()}
|
|
* @return the local values in bits.
|
|
* If the {@link MutualInfoCalculatorMultiVariate#PROP_TIME_DIFF}
|
|
* property was set to say k, then the local values align with the
|
|
* destination value (i.e. after the given delay k). As such, the
|
|
* first k values of the array will be zeros.
|
|
*/
|
|
protected double[] computeLocalUsingPreviousObservations(double states1[][],
|
|
double states2[][], boolean isOurPreviousObservations) {
|
|
|
|
double mi = 0.0;
|
|
int timeSteps = states1.length;
|
|
double[] localMi = new double[timeSteps];
|
|
double prob1, prob2, probJoint;
|
|
for (int b = 0; b < timeSteps; b++) {
|
|
if (isOurPreviousObservations) {
|
|
// We've been called with our previous observations, so we
|
|
// can pass the time step through for dynamic correlation exclusion
|
|
prob1 = mvkeSource.getProbability(states1[b], b);
|
|
prob2 = mvkeDest.getProbability(states2[b], b);
|
|
probJoint = mvkeJoint.getProbability(states1[b], states2[b], b);
|
|
} else {
|
|
// We don't know whether these were our previous observation or not
|
|
// so we don't do dynamic correlation exclusion
|
|
prob1 = mvkeSource.getProbability(states1[b]);
|
|
prob2 = mvkeDest.getProbability(states2[b]);
|
|
probJoint = mvkeJoint.getProbability(states1[b], states2[b]);
|
|
}
|
|
double logTerm = 0.0;
|
|
localMi[b] = 0.0;
|
|
if (probJoint > 0.0) {
|
|
// By necessity prob1 and prob2 will be > 0.0
|
|
logTerm = probJoint / (prob1 * prob2);
|
|
localMi[b] = Math.log(logTerm) / Math.log(2.0);
|
|
}
|
|
mi += localMi[b];
|
|
if (debug) {
|
|
System.out.printf("%d: (%.5f, %.5f, %.5f) %.5f -> %.5f -> %.5f\n",
|
|
b, prob1, prob2, probJoint, logTerm, localMi[b], mi);
|
|
}
|
|
}
|
|
lastAverage = mi / (double) totalObservations;
|
|
miComputed = true;
|
|
return localMi;
|
|
}
|
|
|
|
|
|
/**
|
|
* Compute the local joint entropy values of the previously provided
|
|
* observations.
|
|
*
|
|
* @param states1 provided source observations
|
|
* @param states2 provided destination observations
|
|
* @return the local joint entropies in bits
|
|
*/
|
|
public double[] computeLocalJointEntropyOfPreviousObservations() throws Exception {
|
|
return computeLocalJointEntropyUsingPreviousObservations(sourceObservations,
|
|
destObservations, true);
|
|
}
|
|
|
|
/**
|
|
* Compute the local joint entropy values for these given values, using the previously provided
|
|
* observations to compute the probabilities.
|
|
* Calls to this method will not harness dynamic correlation exclusion (if set)
|
|
* since we don't know whether it's the same time set or not.
|
|
*
|
|
* @param states1 provided source observations
|
|
* @param states2 provided destination observations
|
|
* @return the local joint entropies in bits
|
|
*/
|
|
public double[] computeLocalJointEntropyUsingPreviousObservations(double states1[][], double states2[][]) {
|
|
return computeLocalJointEntropyUsingPreviousObservations(states1,
|
|
states2, false);
|
|
}
|
|
|
|
/**
|
|
* Internal implementation
|
|
*
|
|
* @param states1
|
|
* @param states2
|
|
* @param isOurPreviousObservations
|
|
* @return
|
|
*/
|
|
private double[] computeLocalJointEntropyUsingPreviousObservations(
|
|
double states1[][], double states2[][], boolean isOurPreviousObservations) {
|
|
int timeSteps = states1.length;
|
|
double[] localJoint = new double[timeSteps];
|
|
double prob;
|
|
for (int b = 0; b < totalObservations; b++) {
|
|
if (isOurPreviousObservations) {
|
|
prob = mvkeJoint.getProbability(sourceObservations[b], destObservations[b], b);
|
|
} else {
|
|
prob = mvkeJoint.getProbability(sourceObservations[b], destObservations[b]);
|
|
}
|
|
localJoint[b] = 0.0;
|
|
if (prob > 0.0) {
|
|
localJoint[b] = - Math.log(prob) / Math.log(2.0);
|
|
}
|
|
if (debug) {
|
|
System.out.println(b + ": " + prob + " -> " + localJoint[b]);
|
|
}
|
|
}
|
|
return localJoint;
|
|
}
|
|
|
|
/**
|
|
* Compute the local entropy values for the previously provided
|
|
* observations for the (first) source variable
|
|
* (using those previous observations to compute the PDFs).
|
|
*
|
|
* @return array of local entropies for the source observations
|
|
*/
|
|
public double[] computeLocalEntropy1OfPreviousObservations() {
|
|
return computeLocalEntropyFromPreviousObservations(sourceObservations, 1, true);
|
|
}
|
|
|
|
/**
|
|
* Compute the local entropy values for these given source values,
|
|
* using the previously provided
|
|
* observations for the (first) source variable to compute the probabilities.
|
|
* Calls to this method will not harness dynamic correlation exclusion (if set)
|
|
* since we don't know whether it's the same time set or not.
|
|
*
|
|
* @param states1 provided source observations
|
|
*
|
|
* @return array of local entropies for these source observations
|
|
*/
|
|
public double[] computeLocalEntropy1UsingPreviousObservations(double[][] states) {
|
|
return computeLocalEntropyFromPreviousObservations(states, 1, false);
|
|
}
|
|
|
|
/**
|
|
* Compute the local entropy values for the previously provided
|
|
* observations for the (second) destination variable
|
|
* (using those previous observations to compute the PDFs).
|
|
*
|
|
* @return array of local entropies for the destination observations
|
|
*/
|
|
public double[] computeLocalEntropy2OfPreviousObservations() {
|
|
return computeLocalEntropyFromPreviousObservations(destObservations, 2, true);
|
|
}
|
|
|
|
/**
|
|
* Compute the local entropy values for these given destination values,
|
|
* using the previously provided
|
|
* observations for the (second) destination variable to compute the probabilities.
|
|
* Calls to this method will not harness dynamic correlation exclusion (if set)
|
|
* since we don't know whether it's the same time set or not.
|
|
*
|
|
* @param states2 provided destination observations
|
|
*
|
|
* @return array of local entropies for these destination observations
|
|
*/
|
|
public double[] computeLocalEntropy2UsingPreviousObservations(double states[][]) {
|
|
return computeLocalEntropyFromPreviousObservations(states, 2, false);
|
|
}
|
|
|
|
/**
|
|
* Private utility function to implement {@link #computeLocalEntropy1OfPreviousObservations()},
|
|
* {@link #computeLocalEntropy1UsingPreviousObservations(double[][])},
|
|
* {@link #computeLocalEntropy2OfPreviousObservations()} and
|
|
* {@link #computeLocalEntropy2UsingPreviousObservations(double[][])}.
|
|
*
|
|
* @param states provided set of observations
|
|
* @param useProbsForWhichVar use 1 for variable 1, 2 for variable 2
|
|
* @param isOurPreviousObservations whether these are the previously provided
|
|
* observations or not.
|
|
* @return array of local entropies for these observations
|
|
*/
|
|
private double[] computeLocalEntropyFromPreviousObservations(
|
|
double states[][], int useProbsForWhichVar, boolean isOurPreviousObservations) {
|
|
int timeSteps = states.length;
|
|
double[] localEntropy = new double[timeSteps];
|
|
double prob;
|
|
for (int b = 0; b < totalObservations; b++) {
|
|
if (useProbsForWhichVar == 1) {
|
|
if (isOurPreviousObservations) {
|
|
prob = mvkeSource.getProbability(states[b], b);
|
|
} else {
|
|
prob = mvkeSource.getProbability(states[b]);
|
|
}
|
|
} else {
|
|
if (isOurPreviousObservations) {
|
|
prob = mvkeDest.getProbability(states[b], b);
|
|
} else {
|
|
prob = mvkeDest.getProbability(states[b]);
|
|
}
|
|
}
|
|
localEntropy[b] = 0.0;
|
|
if (prob > 0.0) {
|
|
localEntropy[b] = - Math.log(prob) / Math.log(2.0);
|
|
}
|
|
if (debug) {
|
|
System.out.println(b + ": " + prob + " -> " + localEntropy[b]);
|
|
}
|
|
}
|
|
return localEntropy;
|
|
}
|
|
|
|
/**
|
|
* Compute the local Info distance values for the previously provided
|
|
* observations to compute the probabilities.
|
|
*
|
|
* @return array of local information distances
|
|
*/
|
|
public double[] computeLocalInfoDistanceOfPreviousObservations() {
|
|
return computeLocalInfoDistanceUsingPreviousObservations(sourceObservations,
|
|
destObservations, true);
|
|
}
|
|
|
|
/**
|
|
* Compute the local Info distance values for these given values, using the previously provided
|
|
* observations to compute the probabilities.
|
|
* Calls to this method will not harness dynamic correlation exclusion (if set)
|
|
* since we don't know whether it's the same time set or not.
|
|
*
|
|
* @return array of local information distances.
|
|
*/
|
|
public double[] computeLocalInfoDistanceUsingPreviousObservations(double[][] states1, double[][] states2) {
|
|
return computeLocalInfoDistanceUsingPreviousObservations(states1,
|
|
states2, false);
|
|
}
|
|
|
|
/**
|
|
* Protected utility function to implement {@link #computeLocalInfoDistanceOfPreviousObservations()}
|
|
* and {@link #computeLocalInfoDistanceUsingPreviousObservations(double[][], double[][])}.
|
|
*
|
|
* @param states1 provided source observations
|
|
* @param states2 provided destination observations
|
|
* @param isOurPreviousObservations whether these are the previously provided
|
|
* observations or not.
|
|
* @return array of local information distances.
|
|
*/
|
|
protected double[] computeLocalInfoDistanceUsingPreviousObservations(
|
|
double[][] states1, double[][] states2, boolean isOurPreviousObservations) {
|
|
int timeSteps = states1.length;
|
|
double[] localInfoDistance = new double[timeSteps];
|
|
double prob1, prob2, probJoint;
|
|
for (int b = 0; b < timeSteps; b++) {
|
|
if (isOurPreviousObservations) {
|
|
prob1 = mvkeSource.getProbability(states1[b], b);
|
|
prob2 = mvkeDest.getProbability(states2[b], b);
|
|
probJoint = mvkeJoint.getProbability(states1[b], states2[b], b);
|
|
} else {
|
|
prob1 = mvkeSource.getProbability(states1[b]);
|
|
prob2 = mvkeDest.getProbability(states2[b]);
|
|
probJoint = mvkeJoint.getProbability(states1[b], states2[b]);
|
|
}
|
|
double logTerm = 0.0;
|
|
localInfoDistance[b] = 0.0;
|
|
if (probJoint > 0.0) {
|
|
logTerm = (prob1 * prob2) / (probJoint * probJoint);
|
|
localInfoDistance[b] = Math.log(logTerm) / Math.log(2.0);
|
|
}
|
|
if (debug) {
|
|
System.out.println(b + ": " + logTerm + " -> " + localInfoDistance[b]);
|
|
}
|
|
}
|
|
return localInfoDistance;
|
|
}
|
|
|
|
/**
|
|
* <p>Set properties for the kernel MI calculator.
|
|
* New property values are not guaranteed to take effect until the next call
|
|
* to an initialise method.
|
|
*
|
|
* <p>Valid property names, and what their
|
|
* values should represent, include:</p>
|
|
* <ul>
|
|
* <li>{@link #KERNEL_WIDTH_PROP_NAME} (legacy value is {@link #EPSILON_PROP_NAME}) --
|
|
* kernel width to be used in the calculation. If {@link #normalise} is set,
|
|
* then this is a number of standard deviations; otherwise it
|
|
* is an absolute value. Default is {@link #DEFAULT_KERNEL_WIDTH}.</li>
|
|
* <li>{@link #DYN_CORR_EXCL_TIME_NAME} -- a dynamics exclusion time window (see Kantz and Schreiber),
|
|
* default is 0 which means no dynamic exclusion window.</li>
|
|
* <li>{@link #FORCE_KERNEL_COMPARE_TO_ALL} -- whether to force the underlying kernel estimators to compare
|
|
* each data point to each other (or else allow it to use optimisations).</li>
|
|
* <li>any valid properties for {@link MutualInfoMultiVariateCommon#setProperty(String, String)}.</li>
|
|
* </ul>
|
|
* </p>
|
|
*
|
|
* <p>Note that dynamic correlation exclusion (set with {@link #DYN_CORR_EXCL_TIME_NAME})
|
|
* may have unexpected results if multiple
|
|
* observation sets have been added. This is because multiple observation sets
|
|
* are treated as though they are from a single time series, so observations from
|
|
* near the end of observation set i will be excluded from comparison to
|
|
* observations near the beginning of observation set (i+1).
|
|
*
|
|
* <p>Unknown property values are ignored.</p>
|
|
*
|
|
* @param propertyName name of the property
|
|
* @param propertyValue value of the property
|
|
* @throws Exception for invalid property values
|
|
*/
|
|
public void setProperty(String propertyName, String propertyValue)
|
|
throws Exception {
|
|
|
|
boolean propertySet = true;
|
|
if (propertyName.equalsIgnoreCase(KERNEL_WIDTH_PROP_NAME) ||
|
|
propertyName.equalsIgnoreCase(EPSILON_PROP_NAME)) {
|
|
kernelWidth = Double.parseDouble(propertyValue);
|
|
} else if (propertyName.equalsIgnoreCase(NORMALISE_PROP_NAME)) {
|
|
// This is nominally a property of the super class, but we
|
|
// have extra work to do with this one
|
|
normalise = Boolean.parseBoolean(propertyValue);
|
|
mvkeSource.setNormalise(normalise);
|
|
mvkeDest.setNormalise(normalise);
|
|
mvkeJoint.setNormalise(normalise);
|
|
} else if (propertyName.equalsIgnoreCase(DYN_CORR_EXCL_TIME_NAME)) {
|
|
dynCorrExclTime = Integer.parseInt(propertyValue);
|
|
dynCorrExcl = (dynCorrExclTime > 0);
|
|
if (dynCorrExcl) {
|
|
mvkeSource.setDynamicCorrelationExclusion(dynCorrExclTime);
|
|
mvkeDest.setDynamicCorrelationExclusion(dynCorrExclTime);
|
|
mvkeJoint.setDynamicCorrelationExclusion(dynCorrExclTime);
|
|
} else {
|
|
mvkeSource.clearDynamicCorrelationExclusion();
|
|
mvkeDest.clearDynamicCorrelationExclusion();
|
|
mvkeJoint.clearDynamicCorrelationExclusion();
|
|
}
|
|
} else if (propertyName.equalsIgnoreCase(FORCE_KERNEL_COMPARE_TO_ALL)) {
|
|
forceCompareToAll = Boolean.parseBoolean(propertyValue);
|
|
mvkeSource.setForceCompareToAll(forceCompareToAll);
|
|
mvkeDest.setForceCompareToAll(forceCompareToAll);
|
|
mvkeJoint.setForceCompareToAll(forceCompareToAll);
|
|
} else {
|
|
// No property was set here
|
|
propertySet = false;
|
|
// try the superclass:
|
|
super.setProperty(propertyName, propertyValue);
|
|
}
|
|
if (debug && propertySet) {
|
|
System.out.println(this.getClass().getSimpleName() + ": Set property " + propertyName +
|
|
" to " + propertyValue);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getProperty(String propertyName)
|
|
throws Exception {
|
|
|
|
if (propertyName.equalsIgnoreCase(KERNEL_WIDTH_PROP_NAME) ||
|
|
propertyName.equalsIgnoreCase(EPSILON_PROP_NAME)) {
|
|
return Double.toString(kernelWidth);
|
|
} else if (propertyName.equalsIgnoreCase(DYN_CORR_EXCL_TIME_NAME)) {
|
|
return Integer.toString(dynCorrExclTime);
|
|
} else if (propertyName.equalsIgnoreCase(FORCE_KERNEL_COMPARE_TO_ALL)) {
|
|
return Boolean.toString(forceCompareToAll);
|
|
} else {
|
|
// try the superclass:
|
|
return super.getProperty(propertyName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @return the kernel width in use in the calculator
|
|
*/
|
|
public double getKernelWidth() {
|
|
return kernelWidth;
|
|
}
|
|
|
|
/**
|
|
* Clone the object - note: while it does create new cloned instances of
|
|
* the {@link KernelEstimatorMultiVariate} objects, I think these only
|
|
* have shallow copies to the data.
|
|
* This is enough though to maintain the structure across
|
|
* various {@link #computeSignificance(int)} calls.
|
|
*
|
|
* @see java.lang.Object#clone()
|
|
*/
|
|
@Override
|
|
protected Object clone() throws CloneNotSupportedException {
|
|
MutualInfoCalculatorMultiVariateKernel theClone =
|
|
(MutualInfoCalculatorMultiVariateKernel) super.clone();
|
|
// Now assign clones of the KernelEstimatorMultiVariate objects:
|
|
theClone.mvkeSource =
|
|
(KernelEstimatorMultiVariate) mvkeSource.clone();
|
|
theClone.mvkeDest =
|
|
(KernelEstimatorMultiVariate) mvkeDest.clone();
|
|
theClone.mvkeJoint =
|
|
(KernelEstimatorMultiVariate) mvkeJoint.clone();
|
|
return theClone;
|
|
}
|
|
|
|
}
|