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; + + } + +}