From 3e017d1be4a3c7cceed7caa2d0e5be40809d8c6f Mon Sep 17 00:00:00 2001
From: "joseph.lizier"
+ * Implements an active information storage calculator using kernel estimation.
+ *
+ * Usage:
+ *
+ *
+ *
+ * TODO Use only a single kernel estimator class for the joint space, and compute other + * probabilities from this. This will save much time. + *
+ * + *+ * Matched against Oliver Obst's c++ implementation on 23/4/2010. + *
+ * + * @author Joseph Lizier + * @see ActiveInfoStorageCalculator + * @see ActiveInfoStorageCalculatorCorrelationIntegrals + * + */ +public class ActiveInfoStorageCalculatorKernelDirect + extends ActiveInfoStorageCalculatorCorrelationIntegrals { + + protected MutualInfoCalculatorMultiVariateKernel miKernel = null; + + // Keep joint vectors so we don't need to regenerate them + protected double[][] destNextVectors; + protected double[][] destPastVectors; + + /** + * Default value for epsilon + */ + public static final double DEFAULT_EPSILON = 0.25; + /** + * Kernel width + */ + private double epsilon = DEFAULT_EPSILON; + + /** + * Creates a new instance of the kernel-estimate style transfer entropy calculator + * + */ + public ActiveInfoStorageCalculatorKernelDirect() { + super(); + miKernel = new MutualInfoCalculatorMultiVariateKernel(); + } + + /** + * Initialises the calculator with the existing values for k and epsilon + * + */ + public void initialise() throws Exception { + initialise(k, epsilon); + } + + /** + * Initialises the calculator with the existing value for epsilon + * + * @param k history length + */ + public void initialise(int k) throws Exception { + this.k = k; + initialise(k, epsilon); + } + + /** + * Initialises the calculator + * + * @param k history length + * @param epsilon kernel width + */ + public void initialise(int k, double epsilon) throws Exception { + super.initialise(k); + this.epsilon = epsilon; + miKernel.initialise(k, 1, epsilon); + destPastVectors = null; + destNextVectors = null; + } + + /** + * Set properties for the calculator. + * Can include any of the accepted values for + * {@link MutualInfoCalculatorMultiVariateKernel#setProperty(String, String)} + * except {@link infodynamics.measures.continuous.MutualInfoCalculatorMultiVariate#PROP_TIME_DIFF} + * + * @param propertyName + * @param propertyValue + * @throws Exception + */ + public void setProperty(String propertyName, String propertyValue) throws Exception { + super.setProperty(propertyName, propertyValue); + if (propertyName.equalsIgnoreCase(MutualInfoCalculatorMultiVariateKernel.EPSILON_PROP_NAME)) { + // Grab epsilon here - we need it for the initialisation: + epsilon = Double.parseDouble(propertyValue); + // It will get passed onto miKernel in our initialisation routine + if (debug) { + System.out.println(this.getClass().getSimpleName() + ": Set property " + propertyName + + " to " + propertyValue); + } + } if (propertyName.equalsIgnoreCase(MutualInfoCalculatorMultiVariate.PROP_TIME_DIFF)) { + throw new Exception("Cannot set " + MutualInfoCalculatorMultiVariate.PROP_TIME_DIFF + + " property on the ActiveInfoStorageCalculator"); + } else { + // Pass any other property through to the miKernel object + miKernel.setProperty(propertyName, propertyValue); + } + } + + /** + * Flag that the observations are complete, probability distribution functions can now be built. + * + */ + public void finaliseAddObservations() { + // First work out the size to allocate the joint vectors, and do the allocation: + totalObservations = 0; + for (double[] currentObservation : vectorOfObservations) { + totalObservations += currentObservation.length - k; + } + destPastVectors = new double[totalObservations][k]; + destNextVectors = new double[totalObservations][1]; + + // Construct the joint vectors from the given observations + int startObservation = 0; + for (double[] currentObservation : vectorOfObservations) { + double[][] currentDestPastVectors = makeJointVectorForPast(currentObservation); + MatrixUtils.arrayCopy(currentDestPastVectors, 0, 0, + destPastVectors, startObservation, 0, currentDestPastVectors.length, k); + double[][] currentDestNextVectors = makeJointVectorForNext(currentObservation); + MatrixUtils.arrayCopy(currentDestNextVectors, 0, 0, + destNextVectors, startObservation, 0, currentDestNextVectors.length, 1); + startObservation += currentObservation.length - k; + } + + // Now set the joint vectors in the kernel estimator + try { + miKernel.setObservations(destPastVectors, destNextVectors); + } catch (Exception e) { + // Should only throw where our vector sizes don't match + throw new RuntimeException(e); + } + + // Store whether there was more than one observation set: + addedMoreThanOneObservationSet = vectorOfObservations.size() > 1; + + // And clear the vector of observations + vectorOfObservations = null; + } + + /** + *Computes the average Active Info Storage for the previously supplied observations
+ * + */ + public double computeAverageLocalOfObservations() throws Exception { + lastAverage = miKernel.computeAverageLocalOfObservations(); + return lastAverage; + } + + /** + * Computes the local active information storage for the previous supplied observations. + * + * Where more than one time series has been added, the array + * contains the local values for each tuple in the order in + * which they were added. + * + * If there was only a single time series added, the array + * contains k zero values before the local values. + * (This means the length of the return array is the same + * as the length of the input time series). + * + */ + public double[] computeLocalOfPreviousObservations() throws Exception { + double[] local = miKernel.computeLocalOfPreviousObservations(); + lastAverage = miKernel.getLastAverage(); + if (!addedMoreThanOneObservationSet) { + double[] localsToReturn = new double[local.length + k]; + System.arraycopy(local, 0, localsToReturn, k, local.length); + return localsToReturn; + } else { + return local; + } + } + + /** + * Compute the significance of obtaining the given average TE from the given observations + * + * This is as per Chavez et. al., "Statistical assessment of nonlinear causality: + * application to epileptic EEG signals", Journal of Neuroscience Methods 124 (2003) 113-128. + * + * Basically, we shuffle the source observations against the destination tuples. + * This keeps the marginal PDFs the same (including the entropy rate of the destination) + * but destroys any correlation between the source and state change of the destination. + * + * @param numPermutationsToCheck number of new orderings of the source values to compare against + * @return + */ + public EmpiricalMeasurementDistribution computeSignificance( + int numPermutationsToCheck) throws Exception { + return miKernel.computeSignificance(numPermutationsToCheck); + } + + /** + * As per {@link computeSignificance(int) computeSignificance()} but supplies + * the re-orderings of the observations of the source variables. + * + * + * @param newOrderings first index is permutation number, i.e. newOrderings[i] + * is an array of 1 permutation of 0..n-1, where there were n observations. + * @return + * @throws Exception + */ + public EmpiricalMeasurementDistribution computeSignificance( + int[][] newOrderings) throws Exception { + + return miKernel.computeSignificance(newOrderings); + } + + public void setDebug(boolean debug) { + super.setDebug(debug); + miKernel.setDebug(debug); + } + + public double[] computeLocalUsingPreviousObservations( + double[] newObservations) throws Exception { + // TODO Auto-generated method stub + return null; + } +} diff --git a/java/unittests/infodynamics/measures/continuous/kernel/ActiveInfoStorageTester.java b/java/unittests/infodynamics/measures/continuous/kernel/ActiveInfoStorageTester.java new file mode 100755 index 0000000..0d1181c --- /dev/null +++ b/java/unittests/infodynamics/measures/continuous/kernel/ActiveInfoStorageTester.java @@ -0,0 +1,63 @@ +package infodynamics.measures.continuous.kernel; + +import infodynamics.utils.ArrayFileReader; +import infodynamics.utils.MatrixUtils; +import junit.framework.TestCase; + +public class ActiveInfoStorageTester extends TestCase { + + public void testEmbedding() throws Exception { + ArrayFileReader afr = new ArrayFileReader("demos/data/2coupledRandomCols-1.txt"); + double[][] data = afr.getDouble2DMatrix(); + + int[] ks = {1,2,3,4}; + int[] taus = {1,2,3,4}; + + for (int kIndex = 0; kIndex < ks.length; kIndex++) { + int k = ks[kIndex]; + for (int tauIndex = 0; tauIndex < taus.length; tauIndex++) { + int tau = taus[tauIndex]; + + ActiveInfoStorageCalculatorKernel ais = new ActiveInfoStorageCalculatorKernel(); + ais.initialise(k, tau, 0.5); + ais.setObservations(MatrixUtils.selectColumn(data, 0)); + + assertEquals(data.length - (k-1)*tau - 1, + ais.getNumObservations()); + } + } + } + + + public void testAgainstDirectAISCalculator() throws Exception { + + int[] ks = {1,2,3,4}; + double[] kernelwidths = {0.2, 0.5, 1}; + + ArrayFileReader afr = new ArrayFileReader("demos/data/2coupledRandomCols-1.txt"); + double[][] data = afr.getDouble2DMatrix(); + + for (int kIndex = 0; kIndex < ks.length; kIndex++) { + int k = ks[kIndex]; + for (int kwIndex = 0; kwIndex < kernelwidths.length; kwIndex++) { + double kernelwidth = kernelwidths[kwIndex]; + + ActiveInfoStorageCalculatorKernelDirect aisOld = new ActiveInfoStorageCalculatorKernelDirect(); + aisOld.initialise(k, kernelwidth); + aisOld.setObservations(MatrixUtils.selectColumn(data, 0)); + double aisOldValue = aisOld.computeAverageLocalOfObservations(); + + ActiveInfoStorageCalculatorKernel aisNew = new ActiveInfoStorageCalculatorKernel(); + aisNew.initialise(k, kernelwidth); + aisNew.setObservations(MatrixUtils.selectColumn(data, 0)); + double aisNewValue = aisNew.computeAverageLocalOfObservations(); + + + System.out.printf("k=%d, kw=%.2f, Kernel calculator: %.5f bits; Direct kernel calculator: %.5f bits\n", + k, kernelwidth, aisNewValue, aisOldValue); + assertEquals(aisOldValue, aisNewValue, 0.00000001); + } + } + + } +} diff --git a/java/unittests/infodynamics/measures/continuous/kraskov/MutualInfoMultiVariateTester.java b/java/unittests/infodynamics/measures/continuous/kraskov/MutualInfoMultiVariateTester.java index d4161a7..97d7049 100755 --- a/java/unittests/infodynamics/measures/continuous/kraskov/MutualInfoMultiVariateTester.java +++ b/java/unittests/infodynamics/measures/continuous/kraskov/MutualInfoMultiVariateTester.java @@ -25,6 +25,9 @@ public class MutualInfoMultiVariateTester /** * Confirm that the local values average correctly back to the average value * + * TODO Add a test with say 10000 time steps, after we introduce fast nearest + * neighbour searching. + * */ public void checkLocalsAverageCorrectly(int algNumber) throws Exception { diff --git a/java/unittests/infodynamics/utils/MatrixUtilsTest.java b/java/unittests/infodynamics/utils/MatrixUtilsTest.java index b4b9494..f6c6f53 100755 --- a/java/unittests/infodynamics/utils/MatrixUtilsTest.java +++ b/java/unittests/infodynamics/utils/MatrixUtilsTest.java @@ -252,6 +252,42 @@ public class MatrixUtilsTest extends TestCase { checkArray(new int[] {1, 4, 0, 3, 2}, MatrixUtils.sortIndices(array3)); } + public void testDelayEmbeddings() throws Exception { + double[] array1 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + + // Do a standard delay embedding with tau 1 + checkMatrix(new double[][] { {4, 3, 2, 1, 0}, {5, 4, 3, 2, 1}, + {6, 5, 4, 3, 2}, {7, 6, 5, 4, 3}, + {8, 7, 6, 5, 4}, {9, 8, 7, 6, 5} }, + MatrixUtils.makeDelayEmbeddingVector(array1, 5, 4, 6), + 0.00001); + // Now specify tau explicitly + checkMatrix(new double[][] { {4, 3, 2, 1, 0}, {5, 4, 3, 2, 1}, + {6, 5, 4, 3, 2}, {7, 6, 5, 4, 3}, + {8, 7, 6, 5, 4}, {9, 8, 7, 6, 5} }, + MatrixUtils.makeDelayEmbeddingVector(array1, 5, 1, 4, 6), + 0.00001); + + // Do same standard delay embedding but starting at an offset + checkMatrix(new double[][] { {8, 7, 6, 5, 4}, {9, 8, 7, 6, 5} }, + MatrixUtils.makeDelayEmbeddingVector(array1, 5, 8, 2), + 0.00001); + // Now specify tau explicitly + checkMatrix(new double[][] { {8, 7, 6, 5, 4}, {9, 8, 7, 6, 5} }, + MatrixUtils.makeDelayEmbeddingVector(array1, 5, 1, 8, 2), + 0.00001); + + // Try with tau 2 + checkMatrix(new double[][] { {8, 6, 4, 2, 0}, {9, 7, 5, 3, 1} }, + MatrixUtils.makeDelayEmbeddingVector(array1, 5, 2, 8, 2), + 0.00001); + + // Try with tau 3 + checkMatrix(new double[][] { {6, 3, 0}, {7, 4, 1}, {8, 5, 2}, {9, 6, 3} }, + MatrixUtils.makeDelayEmbeddingVector(array1, 3, 3, 6, 4), + 0.00001); + } + /** * Check that all entries in the given matrix match those of the expected * matrix