diff --git a/java/source/infodynamics/measures/discrete/ConditionalTransferEntropyCalculatorDiscrete.java b/java/source/infodynamics/measures/discrete/ConditionalTransferEntropyCalculatorDiscrete.java
index 5fd4cd8..7cbaaba 100755
--- a/java/source/infodynamics/measures/discrete/ConditionalTransferEntropyCalculatorDiscrete.java
+++ b/java/source/infodynamics/measures/discrete/ConditionalTransferEntropyCalculatorDiscrete.java
@@ -104,10 +104,15 @@ import infodynamics.utils.RandomGenerator;
*
* @author Joseph Lizier (email,
* www)
+ *
+ * @author Pedro AM Mediano (email,
+ * www)
+ *
*/
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
diff --git a/java/unittests/infodynamics/measures/discrete/ConditionalTransferEntropyTester.java b/java/unittests/infodynamics/measures/discrete/ConditionalTransferEntropyTester.java
new file mode 100644
index 0000000..4b02aac
--- /dev/null
+++ b/java/unittests/infodynamics/measures/discrete/ConditionalTransferEntropyTester.java
@@ -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;
+
+ }
+
+}