mirror of https://github.com/jlizier/jidt
567 lines
23 KiB
Java
Executable File
567 lines
23 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.discrete;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.Random;
|
|
|
|
import infodynamics.utils.AnalyticMeasurementDistribution;
|
|
import infodynamics.utils.EmpiricalMeasurementDistribution;
|
|
import infodynamics.utils.MatrixUtils;
|
|
import infodynamics.utils.RandomGenerator;
|
|
import junit.framework.TestCase;
|
|
|
|
public class TransferEntropyTester extends TestCase {
|
|
|
|
protected RandomGenerator rand = new RandomGenerator();
|
|
|
|
public void testNoInfoForPredictableDest() {
|
|
int[] source = rand.generateRandomInts(100, 2);
|
|
int[] dest = source;
|
|
|
|
TransferEntropyCalculatorDiscrete teCalc =
|
|
new TransferEntropyCalculatorDiscrete(2, 1);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest);
|
|
double result = teCalc.computeAverageLocalOfObservations();
|
|
assertEquals(0, result, 0.0000001);
|
|
}
|
|
|
|
public void testFullyPredictableVariousEmbeddings() {
|
|
int[] source = new int[101];
|
|
int[] dest = new int[101];
|
|
|
|
for (int t = 0; t < source.length; t++) {
|
|
int phase = t % 4;
|
|
if (phase < 2) {
|
|
dest[t] = 1;
|
|
}
|
|
if (phase % 2 == 0) {
|
|
source[t] = 1;
|
|
}
|
|
}
|
|
|
|
// with k=1, we won't see the self-predictability of the dest
|
|
// and will assume all info is in TE
|
|
TransferEntropyCalculatorDiscrete teCalc =
|
|
new TransferEntropyCalculatorDiscrete(2, 1);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest);
|
|
double result = teCalc.computeAverageLocalOfObservations();
|
|
assertEquals(1, result, 0.0000001);
|
|
|
|
// with k=2, we will see the self-predictability of the dest
|
|
teCalc = new TransferEntropyCalculatorDiscrete(2, 2);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest);
|
|
result = teCalc.computeAverageLocalOfObservations();
|
|
assertEquals(0, result, 0.0000001);
|
|
|
|
// with k=1, we won't see the self-predictability of the dest
|
|
// but if we supply the same dest as the source and use a large
|
|
// source embedding length then we should see it.
|
|
// Create dest again to ensure we have balanced observations:
|
|
int[] dest2 = new int[102];
|
|
for (int t = 0; t < dest2.length; t++) {
|
|
int phase = t % 4;
|
|
if (phase < 2) {
|
|
dest2[t] = 1;
|
|
}
|
|
}
|
|
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 2);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(dest2, dest2);
|
|
result = teCalc.computeAverageLocalOfObservations();
|
|
assertEquals(1, result, 0.0000001);
|
|
}
|
|
|
|
public void testEmbeddings() throws Exception {
|
|
// Use a short length so that if anything is mis-embedded we'll
|
|
// definitely see a change in the result
|
|
int length = 50;
|
|
for (int type = 0; type < NUM_ORDINARY_TESTS; type++) {
|
|
System.out.println("Testing operation " + type + " for TE discrete embeddings...");
|
|
for (int delay = 1; delay < 4; delay++) {
|
|
for (int selfLag = 1; selfLag < 4; selfLag++) {
|
|
for (int k = 1; k < 4; k++) {
|
|
for (int l = 1; l < 4; l++) {
|
|
for (int k_tau = 1; k_tau < 4; k_tau++) {
|
|
for (int l_tau = 1; l_tau < 4; l_tau++) {
|
|
for (int cDelay = 1; cDelay < 4; cDelay++) {
|
|
for (int startOffset = 0; startOffset < 10; startOffset += 3) {
|
|
int[][] data = generateData(length, type, delay, selfLag);
|
|
int[] source = data[0];
|
|
int[] dest = data[1];
|
|
TransferEntropyCalculatorDiscrete teCalc =
|
|
new TransferEntropyCalculatorDiscrete(2, k, k_tau, l, l_tau, cDelay);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest, startOffset, dest.length - 1);
|
|
double result = teCalc.computeAverageLocalOfObservations();
|
|
// System.out.printf("Result = %.4f\n", result);
|
|
// Now check that against our own embeddings with conditional MI:
|
|
int startTimeBasedOnDestPast = (k-1)*k_tau + 1;
|
|
int startTimeBasedOnSourcePast = (l-1)*l_tau + cDelay;
|
|
int startObservationTime = Math.max(startTimeBasedOnDestPast, startTimeBasedOnSourcePast) + startOffset;
|
|
int[][] destPastVectors =
|
|
MatrixUtils.makeDelayEmbeddingVector(dest, k, k_tau,
|
|
startObservationTime-1,
|
|
dest.length - startObservationTime);
|
|
int[] destPastCombined = MatrixUtils.computeCombinedValues(destPastVectors, 2);
|
|
int[][] sourcePastVectors =
|
|
MatrixUtils.makeDelayEmbeddingVector(source, l, l_tau,
|
|
startObservationTime - cDelay,
|
|
source.length - startObservationTime);
|
|
int[] sourcePastCombined = MatrixUtils.computeCombinedValues(sourcePastVectors, 2);
|
|
int[] destNext = MatrixUtils.select(dest, startObservationTime, dest.length - startObservationTime);
|
|
ConditionalMutualInformationCalculatorDiscrete cmiCalc =
|
|
new ConditionalMutualInformationCalculatorDiscrete((int) Math.pow(2, l), 2, (int) Math.pow(2, k));
|
|
cmiCalc.initialise();
|
|
cmiCalc.addObservations(sourcePastCombined, destNext, destPastCombined);
|
|
double resultCmi = cmiCalc.computeAverageLocalOfObservations();
|
|
assertEquals(resultCmi, result, 0.000001);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public final static int TYPE_COPY = 0;
|
|
public final static int TYPE_OR = 1;
|
|
public final static int TYPE_AND = 2;
|
|
public final static int TYPE_XOR = 3;
|
|
public final static int TYPE_NOISY_XOR = 4;
|
|
public final static int NUM_ORDINARY_TESTS = 5;
|
|
|
|
public int[][] generateData(int length, int type, int delay, int selfLag) {
|
|
int[] source = rand.generateRandomInts(length, 2);
|
|
int[] dest = new int[length];
|
|
int startTime = Math.max(delay, selfLag);
|
|
Random rng = new Random();
|
|
for (int t = 0; t < startTime; t++) {
|
|
dest[t] = rng.nextInt(2);
|
|
}
|
|
for (int t = startTime; t < length; t++) {
|
|
switch(type){
|
|
case TYPE_COPY:
|
|
dest[t] = source[t-delay];
|
|
break;
|
|
case TYPE_OR:
|
|
dest[t] = source[t-delay] | dest[t-selfLag];
|
|
break;
|
|
case TYPE_AND:
|
|
dest[t] = source[t-delay] & dest[t-selfLag];
|
|
break;
|
|
case TYPE_XOR:
|
|
dest[t] = (source[t-delay] ^ dest[t-selfLag]) & 1; // Mask out the upper bits
|
|
break;
|
|
case TYPE_NOISY_XOR:
|
|
dest[t] = (source[t-delay] ^ dest[t-selfLag]) & 1; // Mask out the upper bits
|
|
if (rng.nextInt(4) == 1) {
|
|
// 1 in 4 chance we flip the output:
|
|
dest[t] = (dest[t] ^ 1) & 1; // Mask out the upper bits
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
int[][] retVal = new int[2][];
|
|
retVal[0] = source;
|
|
retVal[1] = dest;
|
|
return retVal;
|
|
}
|
|
|
|
public void testValidObservations() {
|
|
int length = 100;
|
|
int[][] data = generateData(100, 1, 1, 1);
|
|
int[] source = data[0];
|
|
int[] dest = data[1];
|
|
TransferEntropyCalculatorDiscrete teCalc =
|
|
new TransferEntropyCalculatorDiscrete(2, 1, 1, 1, 1, 1);
|
|
teCalc.initialise();
|
|
boolean[] validity = new boolean[length];
|
|
boolean[] allValid = new boolean[length];
|
|
|
|
// 1. check that all samples are counted if validity is always true
|
|
Arrays.fill(allValid, true);
|
|
teCalc.addObservations(source, dest, allValid);
|
|
assertEquals(length - 1, teCalc.getNumObservations());
|
|
|
|
// 2. check that only one samples is left out if first or last entry is invalid:
|
|
Arrays.fill(validity, true);
|
|
validity[0] = false;
|
|
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 1, 1, 1, 1);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest, validity);
|
|
assertEquals(length - 2, teCalc.getNumObservations());
|
|
validity[0] = true;
|
|
validity[length - 1] = false;
|
|
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 1, 1, 1, 1);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest, validity);
|
|
assertEquals(length - 2, teCalc.getNumObservations());
|
|
|
|
// 3. check that only two samples are left out if an entry in middle invalid:
|
|
Arrays.fill(validity, true);
|
|
validity[10] = false;
|
|
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 1, 1, 1, 1);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest, validity);
|
|
assertEquals(length - 3, teCalc.getNumObservations());
|
|
|
|
// 4. check that only three samples are left out if two consecutive entries in middle invalid:
|
|
Arrays.fill(validity, true);
|
|
validity[10] = false;
|
|
validity[11] = false;
|
|
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 1, 1, 1, 1);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest, validity);
|
|
assertEquals(length - 4, teCalc.getNumObservations());
|
|
|
|
// 5. check that four samples are left out if two non-consecutive entries in middle invalid:
|
|
Arrays.fill(validity, true);
|
|
validity[10] = false;
|
|
validity[12] = false;
|
|
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 1, 1, 1, 1);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest, validity);
|
|
assertEquals(length - 5, teCalc.getNumObservations());
|
|
|
|
// 6. check that only k+1 extra samples are left out if an entry in middle invalid:
|
|
Arrays.fill(validity, true);
|
|
validity[50] = false;
|
|
for (int k = 1; k < 5; k++) {
|
|
for (int k_tau = 1; k_tau < 5; k_tau++) {
|
|
int startTimeBasedOnDestPast = (k-1)*k_tau + 1;
|
|
// First check the number of observations when all valid:
|
|
System.out.printf("Testing addObservations(valid) for k=%d, k_tau=%d\n", k, k_tau);
|
|
teCalc = new TransferEntropyCalculatorDiscrete(2, k, k_tau, 1, 1, 1);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest, allValid);
|
|
assertEquals(length - startTimeBasedOnDestPast, teCalc.getNumObservations());
|
|
// Now check with an entry left out in the middle:
|
|
teCalc = new TransferEntropyCalculatorDiscrete(2, k, k_tau, 1, 1, 1);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest, validity);
|
|
assertEquals(length - startTimeBasedOnDestPast - ((k-1)*k_tau+2), teCalc.getNumObservations());
|
|
}
|
|
}
|
|
// 7. check correct number of extra samples left out
|
|
// for two nearby entries in middle being invalid:
|
|
Arrays.fill(validity, true);
|
|
int invalidtime = 50;
|
|
validity[invalidtime] = false;
|
|
for (int k = 1; k < 5; k++) {
|
|
for (int k_tau = 1; k_tau < 5; k_tau++) {
|
|
int startTimeBasedOnDestPast = (k-1)*k_tau + 1;
|
|
for (int t = 1; t < 20; t++) {
|
|
validity[invalidtime+t] = false;
|
|
teCalc = new TransferEntropyCalculatorDiscrete(2, k, k_tau, 1, 1, 1);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest, validity);
|
|
int expectedInvalidTimes = 0;
|
|
if (t > startTimeBasedOnDestPast) {
|
|
// Both invalid entries cause independent invalid periods
|
|
expectedInvalidTimes = (startTimeBasedOnDestPast + 1)*2;
|
|
} else {
|
|
// Their invalid times overlap
|
|
expectedInvalidTimes = (startTimeBasedOnDestPast + 1) + t;
|
|
}
|
|
assertEquals(length - startTimeBasedOnDestPast - expectedInvalidTimes,
|
|
teCalc.getNumObservations());
|
|
validity[invalidtime+t] = true;
|
|
}
|
|
}
|
|
}
|
|
// 8. check correct number of extra samples left out
|
|
// for two nearby entries in middle being invalid,
|
|
// but using a long source embedding
|
|
Arrays.fill(validity, true);
|
|
validity[invalidtime] = false;
|
|
for (int l = 1; l < 5; l++) {
|
|
for (int l_tau = 1; l_tau < 5; l_tau++) {
|
|
int startTimeBasedOnDestPast = (l-1)*l_tau + 1;
|
|
for (int t = 1; t < 20; t++) {
|
|
validity[invalidtime+t] = false;
|
|
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 1, l, l_tau, 1);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest, validity);
|
|
int expectedInvalidTimes = 0;
|
|
if (t > startTimeBasedOnDestPast) {
|
|
// Both invalid entries cause independent invalid periods
|
|
// Need the +1 here because when the invalid is next state
|
|
// it still adds 1 in addition to the source
|
|
// invalidities!
|
|
expectedInvalidTimes = (startTimeBasedOnDestPast + 1)*2;
|
|
} else {
|
|
// Their invalid times overlap
|
|
expectedInvalidTimes = (startTimeBasedOnDestPast + 1) + t;
|
|
}
|
|
assertEquals(length - startTimeBasedOnDestPast - expectedInvalidTimes,
|
|
teCalc.getNumObservations());
|
|
validity[invalidtime+t] = true;
|
|
}
|
|
}
|
|
}
|
|
// 9. check correct number of extra samples left out
|
|
// for two nearby entries in middle being invalid,
|
|
// but using a long source embedding AND a source-dest delay
|
|
for (int l = 1; l < 5; l++) {
|
|
for (int l_tau = 1; l_tau < 5; l_tau++) {
|
|
for (int k = 1; k < 5; k++) {
|
|
for (int k_tau = 1; k_tau < 5; k_tau++) {
|
|
for (int delay = 1; delay < 5; delay++) {
|
|
int startTimeBasedOnDestPast = (k-1)*k_tau + 1;
|
|
int startTimeBasedOnSourcePast = (l-1)*l_tau + delay;
|
|
int startObservationTime = Math.max(startTimeBasedOnDestPast, startTimeBasedOnSourcePast);
|
|
int numPointsRemoved = 0;
|
|
Arrays.fill(validity, true);
|
|
validity[startObservationTime+2] = false;
|
|
boolean overlap = false;
|
|
if (startTimeBasedOnDestPast >= delay) {
|
|
// The embeddings overlap, so number of points removed
|
|
// is solely based on startObservationTime
|
|
numPointsRemoved = (startObservationTime + 1);
|
|
overlap = true;
|
|
} else {
|
|
// The embeddings do not overlap, so will contribute
|
|
// to point removal separately
|
|
numPointsRemoved = (l-1)*l_tau + 1 + startTimeBasedOnDestPast + 1;
|
|
overlap = false;
|
|
}
|
|
System.out.printf("k=%d, k_tau=%d, l=%d, l_tau=%d, delay=%d, overlap=%b; expecting removal of %d points\n",
|
|
k, k_tau, l, l_tau, delay, overlap, numPointsRemoved);
|
|
teCalc = new TransferEntropyCalculatorDiscrete(2, k, k_tau, l, l_tau, delay);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest, validity);
|
|
assertEquals(length - startObservationTime - numPointsRemoved,
|
|
teCalc.getNumObservations());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void testSignificance() throws Exception {
|
|
int length = 1000;
|
|
int[][] data = generateData(length, 2, 2, 3);
|
|
int[] source = data[0];
|
|
int[] dest = data[1];
|
|
TransferEntropyCalculatorDiscrete teCalc =
|
|
new TransferEntropyCalculatorDiscrete(2, 2, 2, 2, 2, 2);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest);
|
|
double originalResult = teCalc.computeAverageLocalOfObservations();
|
|
// Just make sure the significance check returns the right number of calculations, etc
|
|
int numSurrogates = 151;
|
|
EmpiricalMeasurementDistribution emd = teCalc.computeSignificance(numSurrogates);
|
|
assertEquals(numSurrogates, emd.distribution.length);
|
|
// And now make an analytic calculation:
|
|
@SuppressWarnings("unused")
|
|
AnalyticMeasurementDistribution amd = teCalc.computeSignificance();
|
|
// Not really anything I can check here, just want to make sure it runs ok
|
|
double resultAfterSigTest = teCalc.computeAverageLocalOfObservations();
|
|
assertEquals(originalResult, resultAfterSigTest, 0.000001);
|
|
}
|
|
|
|
public void testLocals() throws Exception {
|
|
int length = 500;
|
|
int[][] data2D = new int[length][2];
|
|
int[][][] data3D = new int[length][2][2];
|
|
for (int type = 0; type < NUM_ORDINARY_TESTS; type++) {
|
|
System.out.println("Testing operation " + type + " for TE discrete embeddings with locals...");
|
|
for (int delay = 1; delay < 4; delay++) {
|
|
for (int selfLag = 1; selfLag < 4; selfLag++) {
|
|
for (int k = 1; k < 4; k++) {
|
|
for (int l = 1; l < 4; l++) {
|
|
for (int k_tau = 1; k_tau < 4; k_tau++) {
|
|
for (int l_tau = 1; l_tau < 4; l_tau++) {
|
|
for (int cDelay = 1; cDelay < 4; cDelay++) {
|
|
int[][] data = generateData(length, type, delay, selfLag);
|
|
int[] source = data[0];
|
|
int[] dest = data[1];
|
|
TransferEntropyCalculatorDiscrete teCalc =
|
|
new TransferEntropyCalculatorDiscrete(2, k, k_tau, l, l_tau, cDelay);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest);
|
|
double result = teCalc.computeAverageLocalOfObservations();
|
|
double[] locals = teCalc.computeLocalFromPreviousObservations(source, dest);
|
|
// Now check that the average of the locals gives us the correct value
|
|
int startTimeBasedOnDestPast = (k-1)*k_tau + 1;
|
|
int startTimeBasedOnSourcePast = (l-1)*l_tau + cDelay;
|
|
int startObservationTime = Math.max(startTimeBasedOnDestPast, startTimeBasedOnSourcePast);
|
|
double sumLocals = MatrixUtils.sum(locals);
|
|
double avLocals = sumLocals / (double) (length - startObservationTime);
|
|
// System.out.printf("Testing locals for k=%d, k_tau=%d, l=%d, l_tau=%d, delay=%d, expecting no locals for first %d points\n",
|
|
// k, k_tau, l, l_tau, delay, startObservationTime);
|
|
assertEquals(length - startObservationTime, teCalc.getNumObservations());
|
|
for (int t = 0; t < startObservationTime; t++) {
|
|
// First startObservationTime time steps should have
|
|
// local value of 0
|
|
assertEquals(0, locals[t], 0.000001);
|
|
}
|
|
assertEquals(result, avLocals, 0.000001);
|
|
|
|
// Now test for the 2D methods:
|
|
teCalc.initialise();
|
|
MatrixUtils.copyIntoColumn(data2D, 0, source);
|
|
MatrixUtils.copyIntoColumn(data2D, 1, dest);
|
|
// Make sure we only calculate source -> dest:
|
|
teCalc.setPeriodicBoundaryConditions(false);
|
|
teCalc.addObservations(data2D, 1);
|
|
double result2D = teCalc.computeAverageLocalOfObservations();
|
|
assertEquals(result, result2D, 0.00001);
|
|
int numObs = teCalc.getNumObservations();
|
|
// Now compute across both columns:
|
|
teCalc.initialise();
|
|
teCalc.setPeriodicBoundaryConditions(true);
|
|
teCalc.addObservations(data2D, 1);
|
|
assertEquals(numObs*2, teCalc.getNumObservations());
|
|
double result2DBoth = teCalc.computeAverageLocalOfObservations();
|
|
double[][] locals2D = teCalc.computeLocalFromPreviousObservations(data2D, 1);
|
|
sumLocals = MatrixUtils.sum(locals2D);
|
|
avLocals = sumLocals / (double) (teCalc.getNumObservations());
|
|
assertEquals(result2DBoth, avLocals, 0.000001);
|
|
// Now compute for one source and dest only:
|
|
teCalc.initialise();
|
|
teCalc.addObservations(data2D, 0, 1);
|
|
double resultAcrossCols = teCalc.computeAverageLocalOfObservations();
|
|
assertEquals(result, resultAcrossCols, 0.0000001);
|
|
|
|
// Now test the 3D methods
|
|
teCalc.initialise();
|
|
MatrixUtils.copyIntoColumn3D(data3D, 0, 0, source);
|
|
MatrixUtils.copyIntoColumn3D(data3D, 1, 1, dest);
|
|
// Make sure we only calculate source -> dest:
|
|
teCalc.setPeriodicBoundaryConditions(false);
|
|
teCalc.addObservations(data3D, 1, 1);
|
|
double result3D = teCalc.computeAverageLocalOfObservations();
|
|
assertEquals(result, result3D, 0.00001);
|
|
numObs = teCalc.getNumObservations();
|
|
// Now compute across all columns:
|
|
teCalc.initialise();
|
|
teCalc.setPeriodicBoundaryConditions(true);
|
|
teCalc.addObservations(data3D, 1, 1);
|
|
assertEquals(numObs*4, teCalc.getNumObservations());
|
|
double result3DAll = teCalc.computeAverageLocalOfObservations();
|
|
double[][][] locals3D = teCalc.computeLocalFromPreviousObservations(data3D, 1, 1);
|
|
sumLocals = MatrixUtils.sum(locals3D);
|
|
avLocals = sumLocals / (double) (teCalc.getNumObservations());
|
|
assertEquals(result3DAll, avLocals, 0.000001);
|
|
// Now compute for one source and dest only:
|
|
teCalc.initialise();
|
|
teCalc.addObservations(data3D, 0, 0, 1, 1);
|
|
resultAcrossCols = teCalc.computeAverageLocalOfObservations();
|
|
assertEquals(result, resultAcrossCols, 0.0000001);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public int[][] generateParityData(int length, int delay, int k, int l) {
|
|
int[][] data = new int[length][2];
|
|
int k_tau = 1;
|
|
int l_tau = 1;
|
|
int startTimeBasedOnDestPast = (k-1)*k_tau + 1;
|
|
int startTimeBasedOnSourcePast = (l-1)*l_tau + delay;
|
|
int startObservationTime = Math.max(startTimeBasedOnDestPast, startTimeBasedOnSourcePast);
|
|
Random random = new Random();
|
|
for (int t = 0; t < startObservationTime; t++) {
|
|
data[t][0] = random.nextInt(2);
|
|
data[t][1] = random.nextInt(2);
|
|
}
|
|
for (int t = startObservationTime; t < length; t++) {
|
|
data[t][1] = 0;
|
|
for (int ki = 0; ki < k; ki++) {
|
|
data[t][1] ^= data[t-1-ki][1];
|
|
}
|
|
for (int li = 0; li < l; li++) {
|
|
data[t][1] ^= data[t-delay-li][0];
|
|
}
|
|
data[t][0] = random.nextInt(2);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
public void testComplexParity() throws Exception {
|
|
int length = 10000;
|
|
for (int delay = 1; delay < 4; delay++) {
|
|
for (int k_dest = 1; k_dest < 4; k_dest++) {
|
|
for (int l_source = 1; l_source < 4; l_source++) {
|
|
for (int k = 1; k < 4; k++) {
|
|
for (int l = 1; l < 4; l++) {
|
|
for (int k_tau = 1; k_tau < 4; k_tau++) {
|
|
for (int l_tau = 1; l_tau < 4; l_tau++) {
|
|
for (int cDelay = 1; cDelay < 4; cDelay++) {
|
|
int[][] data = generateParityData(length, delay, k_dest, l_source);
|
|
int[] source = MatrixUtils.selectColumn(data, 0);
|
|
int[] dest = MatrixUtils.selectColumn(data, 1);
|
|
TransferEntropyCalculatorDiscrete teCalc =
|
|
new TransferEntropyCalculatorDiscrete(2, k, k_tau, l, l_tau, cDelay);
|
|
teCalc.initialise();
|
|
teCalc.addObservations(source, dest);
|
|
double result = teCalc.computeAverageLocalOfObservations();
|
|
if ((k >= k_dest) && (cDelay <= delay) &&
|
|
(l - delay + cDelay >= l_source) &&
|
|
((k_dest==1) || (k_tau == 1)) &&
|
|
(((l_source==1) && (cDelay==delay)) || (l_tau == 1))) {
|
|
// Our embedding parameters will be satisfactory
|
|
// to detect the parity operation
|
|
if (result <= 0.9) {
|
|
System.out.printf("(k=%d >= k_dest=%d) && (cDelay=%d <= delay=%d) && " +
|
|
"(l=%d - delay=%d + cDelay=%d >= l_source=%d) && ((k_dest=%d==1) || (k_tau=%d == 1)) && " +
|
|
"(((l_source=%d==1)&&(cDelay=%d==delay=%d)) || (l_tau=%d == 1)) was true but result=%.3f too low\n",
|
|
k, k_dest, cDelay, delay, l, delay, cDelay, l_source, k_dest, k_tau, l_source, cDelay, delay, l_tau, result);
|
|
}
|
|
assertTrue(result > 0.9);
|
|
} else {
|
|
// It's still quite possible that the parity operation is detected, because
|
|
// we can have some strange behaviour emerge here, e.g. initial conditions lock in
|
|
// dest behaviour as either a copy or inversion of source (in which case you always detect
|
|
// the one bit dependence with k=1).
|
|
// So instead, let's look for conditions where we know it *cannot* be detected!
|
|
if (cDelay > delay) {
|
|
if (result > 0.1) {
|
|
System.out.printf("(cDelay=%d > delay=%d) was false but result=%.3f too high\n",
|
|
cDelay, delay, result);
|
|
}
|
|
assertTrue(result < 0.1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|