Discrete conditional TE now supports different bases for each variable.

Also added some generic unit tests.
This commit is contained in:
Pedro Martinez Mediano 2016-06-01 18:54:57 +01:00
parent 08bb2dd609
commit a721f26734
2 changed files with 184 additions and 5 deletions

View File

@ -104,10 +104,15 @@ import infodynamics.utils.RandomGenerator;
*
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*
* @author Pedro AM Mediano (<a href="pmediano at imperial.ac.uk">email</a>,
* <a href="http://www.doc.ic.ac.uk/~pam213">www</a>)
*
*/
public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCalculatorDiscrete {
protected int k = 0; // history length k.
protected int base_others = 0; // base of the conditional variables
protected int base_power_k = 0;
protected int base_power_num_others = 0;
protected int numOtherInfoContributors = 0;
@ -150,6 +155,56 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
}
*/
}
/**
* Construct a new instance. This constructor allows the source and
* destination variables to have a different base from the other contributors
* (i.e. the conditionals).
*
* @param base number of symbols for each variable.
* E.g. binary variables are in base-2.
* @param history embedded history length of the destination to condition on -
* this is k in Schreiber's notation.
* @param numOtherInfoContributors number of information contributors
* (other than the past of the destination
* or the source) to condition on.
* @param base_others base of the variables in the conditional, if different
* from the base of the source and target variables.
*/
public ConditionalTransferEntropyCalculatorDiscrete
(int base, int history, int numOtherInfoContributors, int base_others) {
super(base);
k = history;
this.base_others = base_others;
this.numOtherInfoContributors = numOtherInfoContributors;
base_power_k = MathsUtils.power(base, k);
base_power_num_others = MathsUtils.power(base_others, numOtherInfoContributors);
startObservationTime = Math.max(k, 1);
// check that we can convert the base tuple into an integer ok
if (k > Math.log(Integer.MAX_VALUE) / log_base) {
throw new RuntimeException("Base and history combination too large");
}
if (numOtherInfoContributors < 1) {
throw new RuntimeException("Number of other info contributors < 1 for CompleteTECalculator");
}
// Create storage for counts of observations
sourceDestPastOthersCount = new int[base][base][base_power_k][base_power_num_others];
sourcePastOthersCount = new int[base][base_power_k][base_power_num_others];
destPastOthersCount = new int [base][base_power_k][base_power_num_others];
pastOthersCount = new int[base_power_k][base_power_num_others];
// Create constants for tracking prevValues
maxShiftedValue = new int[base];
for (int v = 0; v < base; v++) {
maxShiftedValue[v] = v * MathsUtils.power(base, k-1);
}
}
/**
* Construct a new instance
@ -168,6 +223,7 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
super(base);
k = history;
base_others = base; // If unspecified, the base of the conditional variables is the same as src and tgt
this.numOtherInfoContributors = numOtherInfoContributors;
base_power_k = MathsUtils.power(base, k);
base_power_num_others = MathsUtils.power(base, numOtherInfoContributors);
@ -262,7 +318,7 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
othersVal = 0;
for (int o = 0; o < numOtherInfoContributors; o++) {
// Include this other contributor
othersVal *= base;
othersVal *= base_others;
othersVal += conditionals[r-1][o];
}
sourceDestPastOthersCount[sourceVal][destVal][pastVal][othersVal]++;
@ -302,6 +358,10 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
public void addObservations(int[] source, int[] dest, int[] conditionals)
throws Exception {
// PEDRO: should we perhaps check here that numOtherInfoContributors == 1?
// Probably the user shouldn't be left the hassle of computeCombinedValues, given
// that there's also a method addObservations(int[], int[], int[][])
int rows = dest.length;
if ((source.length != rows) || (conditionals.length != rows)) {
@ -416,7 +476,7 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
othersVal = 0;
for (int o = 0; o < cleanedOthersOffsets.length; o++) {
// Include this other contributor
othersVal *= base;
othersVal *= base_others;
othersVal += states[r-1][(c-cleanedOthersOffsets[o]+columns) % columns];
}
sourceDestPastOthersCount[sourceVal][destVal][pastVal[c]][othersVal]++;
@ -497,7 +557,7 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
othersVal = 0;
for (int o = 0; o < cleanedOthersAbsolute.length; o++) {
// Include this other contributor
othersVal *= base;
othersVal *= base_others;
othersVal += states[r-1][cleanedOthersAbsolute[o]];
}
sourceDestPastOthersCount[sourceVal][destVal][pastVal][othersVal]++;
@ -754,7 +814,7 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
othersVal = 0;
for (int o = 0; o < cleanedOthersOffsets.length; o++) {
// Include this other contributor
othersVal *= base;
othersVal *= base_others;
othersVal += states[r-1][(c-cleanedOthersOffsets[o]+columns) % columns];
}
@ -853,7 +913,7 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
othersVal = 0;
for (int o = 0; o < cleanedOthersAbsolute.length; o++) {
// Include this other contributor
othersVal *= base;
othersVal *= base_others;
othersVal += states[r-1][cleanedOthersAbsolute[o]];
}
// Now compute the local value

View File

@ -0,0 +1,119 @@
package infodynamics.measures.discrete;
import infodynamics.utils.RandomGenerator;
import junit.framework.TestCase;
public class ConditionalTransferEntropyTester extends TestCase {
protected RandomGenerator rand = new RandomGenerator();
protected double tol = 1e-5;
/**
* Source and other are random binary ints, and dest is equal to other.
* Since other doesn't explain anything apart from dest's past, the result
* should be the same as ignoring other and using TransferEntropyCalculator.
*/
public void testNoInfoForPredictableDest() {
int nb_samples = 100;
int[] source = rand.generateRandomInts(nb_samples, 2);
int[] other = rand.generateRandomInts(nb_samples, 2);
int[] dest = other;
boolean exceptionCaught = false;
double cteResult = Double.NaN, teResult = Double.NaN;
try {
ConditionalTransferEntropyCalculatorDiscrete cteCalc =
new ConditionalTransferEntropyCalculatorDiscrete(2, 1, 1);
cteCalc.initialise();
cteCalc.addObservations(source, dest, other);
cteResult = cteCalc.computeAverageLocalOfObservations();
} catch (Throwable e) {
exceptionCaught = true;
}
assertFalse(exceptionCaught);
TransferEntropyCalculatorDiscrete teCalc =
new TransferEntropyCalculatorDiscrete(2, 1);
teCalc.initialise();
teCalc.addObservations(source, dest);
teResult = teCalc.computeAverageLocalOfObservations();
assertEquals(cteResult, teResult, tol);
}
/**
* Generate a time series such that dest = XOR(source, other)
*/
public void testOthersSameBase() {
int nb_samples = 10000;
int[] source = rand.generateRandomInts(nb_samples, 2);
int[] other = rand.generateRandomInts(nb_samples, 2);
int[] dest = new int[nb_samples];
boolean exceptionCaught = false;
double result = Double.NaN;
for (int i = 1; i < nb_samples; i++) {
dest[i] = (source[i-1] + other[i-1]) % 2;
}
try {
ConditionalTransferEntropyCalculatorDiscrete cteCalc =
new ConditionalTransferEntropyCalculatorDiscrete(2, 1, 1);
cteCalc.initialise();
cteCalc.addObservations(source, dest, other);
result = cteCalc.computeAverageLocalOfObservations();
} catch (Throwable e) {
exceptionCaught = true;
}
assertFalse(exceptionCaught);
assertEquals(1, result, 1e-3);
}
/**
* Go through a few examples that should/shouldn't throw exceptions.
*/
public void testDifferentBases() {
assertFalse(checkForException(2, 2, 1, 1, 100));
assertFalse(checkForException(2, 3, 1, 1, 100));
assertFalse(checkForException(2, 2, 3, 3, 100));
assertFalse(checkForException(2, 3, 3, 3, 100));
assertTrue(checkForException(2, 2, 3, 4, 100));
assertTrue(checkForException(2, 2, 4, 3, 100));
}
/**
* Basic usage of the calculator on random data.
*/
private boolean checkForException(int base, int base_others, int col_others,
int num_info_contributors, int rows) {
boolean exceptionCaught = false;
try {
ConditionalTransferEntropyCalculatorDiscrete cteCalc =
new ConditionalTransferEntropyCalculatorDiscrete(base, 1, num_info_contributors, base_others);
int[][] other = rand.generateRandomInts(rows, col_others, base_others);
int[] src = rand.generateRandomInts(rows, base);
int[] tgt = rand.generateRandomInts(rows, base);
cteCalc.initialise();
cteCalc.addObservations(src, tgt, other);
cteCalc.computeAverageLocalOfObservations();
} catch (Throwable e) {
exceptionCaught = true;
}
return exceptionCaught;
}
}