jidt/java/source/infodynamics/measures/continuous/ConditionalTransferEntropyC...

1058 lines
42 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.continuous;
import infodynamics.utils.EmpiricalMeasurementDistribution;
import infodynamics.utils.MatrixUtils;
import infodynamics.utils.ParsedProperties;
import java.util.Vector;
/**
* A Conditional Transfer Entropy (TE) calculator
* (implementing {@link ConditionalTransferEntropyCalculator})
* which is affected using a
* given Conditional Mutual Information (MI) calculator (implementing
* {@link ConditionalMutualInfoCalculatorMultiVariate}) to make the calculations.
*
* <p>Usage is as per the paradigm outlined for
* {@link ConditionalTransferEntropyCalculator},
* except that in the constructor(s) for this class the implementation for
* a {@link ConditionalMutualInfoCalculatorMultiVariate} must be supplied.
* </p>
*
* <p>This class <i>may</i> be used directly, however users are advised that
* several child classes are available which already plug-in the various
* conditional MI estimators
* to provide conditional TE calculators (taking specific caution associated with
* each type of estimator):</p>
* <ul>
* <li>{@link infodynamics.measures.continuous.gaussian.ConditionalTransferEntropyCalculatorGaussian}</li>
* <li>{@link infodynamics.measures.continuous.kraskov.ConditionalTransferEntropyCalculatorKraskov}</li>
* </ul>
*
* <p><b>References:</b><br/>
* <ul>
* <li>T. Schreiber, <a href="http://dx.doi.org/10.1103/PhysRevLett.85.461">
* "Measuring information transfer"</a>,
* Physical Review Letters 85 (2) pp.461-464, 2000.</li>
* <li>J. T. Lizier, M. Prokopenko and A. Zomaya,
* <a href="http://dx.doi.org/10.1103/PhysRevE.77.026110">
* "Local information transfer as a spatiotemporal filter for complex systems"</a>
* Physical Review E 77, 026110, 2008.</li>
* <li>J. T. Lizier, M. Prokopenko and A. Zomaya,
* <a href=http://dx.doi.org/10.1063/1.3486801">
* "Information modification and particle collisions in distributed computation"</a>
* Chaos 20, 3, 037109 (2010).</li>
* </ul>
*
* @author Joseph Lizier, <a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>
*/
public class ConditionalTransferEntropyCalculatorViaCondMutualInfo implements
ConditionalTransferEntropyCalculator {
/**
* Underlying conditional mutual information calculator
*/
protected ConditionalMutualInfoCalculatorMultiVariate condMiCalc;
/**
* Length of past destination history to consider (embedding length)
*/
protected int k = 1;
/**
* Embedding delay to use between elements of the destination embeding vector.
* We're hard-coding a delay of 1 between the history vector and the next
* observation however.
*/
protected int k_tau = 1;
/**
* Length of past source history to consider (embedding length)
*/
protected int l = 1;
/**
* Embedding delay to use between elements of the source embeding vector.
*/
protected int l_tau = 1;
/**
* Source-destination next observation delay
*/
protected int delay = 1;
/**
* Array of embedding lengths for each conditional variable.
* Can be an empty array or null if there are no conditional variables.
*/
protected int[] condEmbedDims = new int[] {1};
/**
* Array of embedding delays for the conditional variables.
* Must be same length as condEmbedDims array.
*/
protected int[] cond_taus = new int[] {1};
/**
* Array of time lags between last element of each conditional variable
* and destination next value.
*/
protected int[] condDelays = new int[] {1};
/**
* Time index of the last point in the destination embedding of the first
* (destination past, source past, destination next) tuple that can be
* taken from any set of time-series observations.
*/
protected int startTimeForFirstDestEmbedding;
/**
* The total dimensionality of our embedded conditional values
* (sum of condEmbedDims)
*/
protected int dimOfConditionals = 0;
/**
* Whether we are in debug mode
*/
protected boolean debug = false;
/**
* Construct a conditional transfer entropy calculator using an instance of
* condMiCalculatorClassName as the underlying conditional mutual information calculator.
*
* @param condMiCalculatorClassName name of the class which must implement
* {@link ConditionalMutualInfoCalculatorMultiVariate}
* @throws InstantiationException if the given class cannot be instantiated
* @throws IllegalAccessException if illegal access occurs while trying to create an instance
* of the class
* @throws ClassNotFoundException if the given class is not found
*/
public ConditionalTransferEntropyCalculatorViaCondMutualInfo(String condMiCalculatorClassName)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
@SuppressWarnings("unchecked")
Class<ConditionalMutualInfoCalculatorMultiVariate> condMiClass =
(Class<ConditionalMutualInfoCalculatorMultiVariate>) Class.forName(condMiCalculatorClassName);
ConditionalMutualInfoCalculatorMultiVariate condMiCalc = condMiClass.newInstance();
construct(condMiCalc);
}
/**
* Construct a conditional transfer entropy calculator using an instance of
* condMiCalcClass as the underlying conditional mutual information calculator.
*
* @param condMiCalcClass the class which must implement
* {@link ConditionalMutualInfoCalculatorMultiVariate}
* @throws InstantiationException if the given class cannot be instantiated
* @throws IllegalAccessException if illegal access occurs while trying to create an instance
* of the class
* @throws ClassNotFoundException if the given class is not found
*/
public ConditionalTransferEntropyCalculatorViaCondMutualInfo(Class<ConditionalMutualInfoCalculatorMultiVariate> condMiCalcClass)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
ConditionalMutualInfoCalculatorMultiVariate condMiCalc = condMiCalcClass.newInstance();
construct(condMiCalc);
}
/**
* Construct this calculator by passing in a constructed but not initialised
* underlying Conditional Mutual information calculator.
*
* @param condMiCalc An instantiated conditional mutual information calculator.
* @throws Exception if the supplied calculator has not yet been instantiated.
*/
public ConditionalTransferEntropyCalculatorViaCondMutualInfo(ConditionalMutualInfoCalculatorMultiVariate condMiCalc) throws Exception {
if (condMiCalc == null) {
throw new Exception("Conditional MI calculator used to construct ConditionalTransferEntropyCalculatorViaCondMutualInfo " +
" must have already been instantiated.");
}
construct(condMiCalc);
}
/**
* Internal method to set the conditional mutual information calculator.
* Can be overridden if anything else needs to be done with it by the child classes.
*
* @param condMiCalc
*/
protected void construct(ConditionalMutualInfoCalculatorMultiVariate condMiCalc) {
this.condMiCalc = condMiCalc;
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ChannelCalculatorCommon#initialise()
*/
@Override
public void initialise() throws Exception {
initialise(k, k_tau, l, l_tau, delay, condEmbedDims, cond_taus, condDelays);
}
@Override
public void initialise(int k) throws Exception {
initialise(k, k_tau, l, l_tau, delay, condEmbedDims, cond_taus, condDelays);
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#initialise(int, int, int)
*/
@Override
public void initialise(int k, int l, int condEmbedDim) throws Exception {
if (condEmbedDim == 0) {
// No conditional variables:
initialise(k, 1, l, 1, 1, null, null, null);
} else {
// We have a conditional variable:
int[] condEmbedDimsArray = new int[1];
condEmbedDimsArray[0] = condEmbedDim;
int[] cond_taus = new int[1];
cond_taus[0] = 1;
int[] cond_delays = new int[1];
cond_delays[0] = 1;
initialise(k, 1, l, 1, 1, condEmbedDimsArray, cond_taus, cond_delays);
}
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#initialise(int, int, int, int, int, int, int, int)
*/
@Override
public void initialise(int k, int k_tau, int l, int l_tau, int delay,
int condEmbedDim, int cond_tau, int condDelay) throws Exception {
if (condEmbedDim == 0) {
// No conditional variables:
initialise(k, k_tau, l, l_tau, delay, null, null, null);
} else {
// We have a conditional variable:
int[] condEmbedDimsArray = new int[1];
condEmbedDimsArray[0] = condEmbedDim;
int[] cond_taus = new int[1];
cond_taus[0] = cond_tau;
int[] cond_delays = new int[1];
cond_delays[0] = condDelay;
initialise(k, k_tau, l, l_tau, delay, condEmbedDimsArray, cond_taus, cond_delays);
}
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#initialise(int, int, int, int, int, int[], int[], int[])
*/
@Override
public void initialise(int k, int k_tau, int l, int l_tau, int delay,
int[] condEmbedDims, int[] cond_taus, int[] condDelays)
throws Exception {
// First, check consistency:
if (delay < 0) {
throw new Exception("Cannot compute TE with source-destination delay < 0");
}
if (condEmbedDims == null) {
// Allow this if all conditional parameter arrays null or 0 length
condEmbedDims = new int[0];
}
if (cond_taus == null) {
// Allow this if all conditional parameter arrays null or 0 length
cond_taus = new int[0];
}
if (condDelays == null) {
// Allow this if all conditional parameter arrays null or 0 length
condDelays = new int[0];
}
if ((condEmbedDims.length != cond_taus.length) ||
(condEmbedDims.length != condDelays.length)) {
throw new Exception("condEmbedDims, cond_taus and condDelays must have" +
" same length in argument to ConditionalTransferEntropyCalculatorViaCondMutualInfo.initialise()");
}
for (int i = 0; i < condDelays.length; i++) {
if (condDelays[i] < 0) {
throw new Exception("Cannot compute TE with conditional-destination delay < 0");
}
}
// Next, store the parameters.
this.k = k;
this.k_tau = k_tau;
this.l = l;
this.l_tau = l_tau;
this.delay = delay;
this.condEmbedDims = condEmbedDims;
this.cond_taus = cond_taus;
this.condDelays = condDelays;
// Now check which point we can start taking observations from in any
// addObservations call. These two integers represent the last
// point of the destination embedding, in the cases where the destination
// embedding itself determines where we can start taking observations, or
// the case where the source embedding plus delay is longer and so determines
// where we can start taking observations, or the case where
// the conditional embeding plus delay is longer and so determines
// where we can start taking observations
int startTimeBasedOnDestPast = (k-1)*k_tau;
int startTimeBasedOnSourcePast = (l-1)*l_tau + delay - 1;
int startTimeBasedOnCondPast = 0;
dimOfConditionals = 0;
for (int i = 0; i < condDelays.length; i++) {
// Check what the start time would be based on this conditional variable
int startTimeBasedOnThisConditional =
(condEmbedDims[i]-1)*cond_taus[i] + condDelays[i] - 1;
if (startTimeBasedOnThisConditional > startTimeBasedOnCondPast) {
startTimeBasedOnCondPast = startTimeBasedOnThisConditional;
}
// And while we're looping compute the total dimension of conditionals
dimOfConditionals += condEmbedDims[i];
}
startTimeForFirstDestEmbedding = Math.max(startTimeBasedOnDestPast,
Math.max(startTimeBasedOnSourcePast, startTimeBasedOnCondPast));
condMiCalc.initialise(l, 1, k + dimOfConditionals);
}
/**
* Sets properties for the conditional TE calculator.
* New property values are not guaranteed to take effect until the next call
* to an initialise method.
*
* <p>Valid property names, and what their
* values should represent, include:</p>
* <ul>
* <li>Any properties accepted by {@link ConditionalTransferEntropyCalculator};</li>
* <li>Or properties accepted by the underlying
* {@link ConditionalMutualInfoCalculatorMultiVariate#setProperty(String, String)} implementation.</li>
* </ul>
* <p><b>Note:</b> further properties may be defined by child classes.</p>
*
* <p>Unknown property values are ignored.</p>
*
* @param propertyName name of the property
* @param propertyValue value of the property.
* @throws Exception if there is a problem with the supplied value.
*/
public void setProperty(String propertyName, String propertyValue) throws Exception {
boolean propertySet = true;
if (propertyName.equalsIgnoreCase(K_PROP_NAME)) {
k = Integer.parseInt(propertyValue);
} else if (propertyName.equalsIgnoreCase(K_TAU_PROP_NAME)) {
k_tau = Integer.parseInt(propertyValue);
} else if (propertyName.equalsIgnoreCase(L_PROP_NAME)) {
l = Integer.parseInt(propertyValue);
} else if (propertyName.equalsIgnoreCase(L_TAU_PROP_NAME)) {
l_tau = Integer.parseInt(propertyValue);
} else if (propertyName.equalsIgnoreCase(DELAY_PROP_NAME)) {
delay = Integer.parseInt(propertyValue);
} else if (propertyName.equalsIgnoreCase(COND_EMBED_LENGTHS_PROP_NAME)) {
condEmbedDims = ParsedProperties.parseStringArrayOfInts(propertyValue);
} else if (propertyName.equalsIgnoreCase(COND_EMBED_DELAYS_PROP_NAME)) {
cond_taus = ParsedProperties.parseStringArrayOfInts(propertyValue);
} else if (propertyName.equalsIgnoreCase(COND_DELAYS_PROP_NAME)) {
condDelays = ParsedProperties.parseStringArrayOfInts(propertyValue);
} else {
// No property was set on this class, assume it is for the underlying
// conditional MI calculator
condMiCalc.setProperty(propertyName, propertyValue);
propertySet = false;
}
if (debug && propertySet) {
System.out.println(this.getClass().getSimpleName() + ": Set property " + propertyName +
" to " + propertyValue);
}
}
@Override
public String getProperty(String propertyName) throws Exception {
if (propertyName.equalsIgnoreCase(K_PROP_NAME)) {
return Integer.toString(k);
} else if (propertyName.equalsIgnoreCase(K_TAU_PROP_NAME)) {
return Integer.toString(k_tau);
} else if (propertyName.equalsIgnoreCase(L_PROP_NAME)) {
return Integer.toString(l);
} else if (propertyName.equalsIgnoreCase(L_TAU_PROP_NAME)) {
return Integer.toString(l_tau);
} else if (propertyName.equalsIgnoreCase(DELAY_PROP_NAME)) {
return Integer.toString(delay);
} else if (propertyName.equalsIgnoreCase(COND_EMBED_LENGTHS_PROP_NAME)) {
if (condEmbedDims == null) {
return "";
} else {
return MatrixUtils.arrayToString(condEmbedDims);
}
} else if (propertyName.equalsIgnoreCase(COND_EMBED_DELAYS_PROP_NAME)) {
if (cond_taus == null) {
return "";
} else {
return MatrixUtils.arrayToString(cond_taus);
}
} else if (propertyName.equalsIgnoreCase(COND_DELAYS_PROP_NAME)) {
if (condDelays == null) {
return "";
} else {
return MatrixUtils.arrayToString(condDelays);
}
} else {
// No property matches for this class, assume it is for the underlying
// conditional MI calculator
return condMiCalc.getProperty(propertyName);
}
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#setObservations(double[], double[], double[][])
*/
@Override
public void setObservations(double[] source, double[] destination,
double[][] conditionals) throws Exception {
startAddObservations();
addObservations(source, destination, conditionals);
finaliseAddObservations();
}
/**
* A non-overloaded method signature for setObservations with 2D arguments, as there have been
* some problems calling overloaded versions of setObservations from jpype.
*
* @param source
* @param destination
* @param conditionals
* @throws Exception
*/
public void setObservations2DCond(double[] source, double[] destination,
double[][] conditionals) throws Exception {
setObservations(source, destination, conditionals);
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#setObservations(double[], double[], double[])
*/
@Override
public void setObservations(double[] source, double[] destination,
double[] conditionals) throws Exception {
if (condEmbedDims.length != 1) {
throw new Exception("Cannot call setObservations(double[], double[], double[]) when the " +
"conditional TE calculator was not initialised for one conditional variable");
}
startAddObservations();
addObservations(source, destination, conditionals);
finaliseAddObservations();
}
/**
* A non-overloaded method signature for setObservations with 2D arguments, as there have been
* some problems calling overloaded versions of setObservations from jpype.
*
* @param source
* @param destination
* @param conditionals
* @throws Exception
*/
public void setObservations1DCond(double[] source, double[] destination,
double[] conditionals) throws Exception {
setObservations(source, destination, conditionals);
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#startAddObservations()
*/
@Override
public void startAddObservations() {
condMiCalc.startAddObservations();
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#addObservations(double[], double[], double[][])
*/
@Override
public void addObservations(double[] source, double[] destination,
double[][] conditionals) throws Exception {
if (source.length != destination.length) {
throw new Exception(String.format("Source and destination lengths (%d and %d) must match!",
source.length, destination.length));
}
if (conditionals == null) {
if (condEmbedDims.length > 0) {
throw new Exception(String.format("No conditionals supplied (expected %d-dimensional conditionals", condEmbedDims.length));
} else {
// This is allowed; make a dummy set of conditionals
conditionals = new double[destination.length][0];
}
}
if (conditionals.length != destination.length) {
throw new Exception(String.format("Conditionals and destination lengths (%d and %d) must match!",
conditionals.length, destination.length));
}
// Postcondition -- all time series have same length
if (source.length < startTimeForFirstDestEmbedding + 2) {
// There are no observations to add here, the time series is too short
// Don't throw an exception, do nothing since more observations
// can be added later.
return;
}
if (conditionals[0].length != condEmbedDims.length) {
throw new Exception(String.format("Number of conditional variables %d does not " +
"match the initialised number %d", conditionals[0].length, condEmbedDims.length));
}
// All parameters are as expected
double[][][] embeddedVectorsForCondMI =
embedSourceDestAndConditionalsForCondMI(source, destination, conditionals);
condMiCalc.addObservations(embeddedVectorsForCondMI[0],
embeddedVectorsForCondMI[1], embeddedVectorsForCondMI[2]);
}
/**
* A non-overloaded method signature for setObservations with 2D arguments, as there have been
* some problems calling overloaded versions of setObservations from jpype.
*
* @param source
* @param destination
* @param conditionals
* @throws Exception
*/
public void addObservations2DCond(double[] source, double[] destination,
double[][] conditionals) throws Exception {
addObservations(source, destination, conditionals);
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#addObservations(double[], double[], double[])
*/
@Override
public void addObservations(double[] source, double[] destination,
double[] conditionals) throws Exception {
if (condEmbedDims.length != 1) {
throw new Exception("Cannot call addObservations(double[], double[], double[]) when the " +
"conditional TE calculator was not initialised for one conditional variable");
}
double[][] conditionalsIn2D = null;
if (conditionals != null) {
// This isn't incredibly efficient, but is easy to code and doesn't cost more
// than an increase in the linear time multiplier.
conditionalsIn2D = new double[conditionals.length][1];
MatrixUtils.copyIntoColumn(conditionalsIn2D, 0, conditionals);
}
addObservations(source, destination, conditionalsIn2D);
}
/**
* A non-overloaded method signature for setObservations with 2D arguments, as there have been
* some problems calling overloaded versions of setObservations from jpype.
*
* @param source
* @param destination
* @param conditionals
* @throws Exception
*/
public void addObservations1DCond(double[] source, double[] destination,
double[][] conditionals) throws Exception {
addObservations(source, destination, conditionals);
}
/**
* Internal method to take (pre-screened) time-series for a source, destination
* and conditional variables, and embed them using the given embedding
* parameters, as well as combining the destination past and conditionals,
* making all ready for a conditional MI calculation.
*
* @param source source time-series observations
* @param destination destination time-series observations.
* Length must match source.
* @param conditionals 2D array of conditional time series observations
* (first index is time, second is variable number)
* Length must match source.
* @return double[][][] returnValue: where returnValue[0] is the embedded
* source vectors, returnValue[1] is the destination next values,
* and returnValue[2] is the joined embedded destination past
* and conditionals. The first index of each of these is (shifted) time,
* the second is embedding variable number.
* @throws Exception
*/
protected double[][][] embedSourceDestAndConditionalsForCondMI(double[] source, double[] destination,
double[][] conditionals) throws Exception {
double[][] currentDestPastVectors =
MatrixUtils.makeDelayEmbeddingVector(destination, k, k_tau,
startTimeForFirstDestEmbedding,
destination.length - startTimeForFirstDestEmbedding - 1);
double[][] currentDestNextVectors =
MatrixUtils.makeDelayEmbeddingVector(destination, 1,
startTimeForFirstDestEmbedding + 1,
destination.length - startTimeForFirstDestEmbedding - 1);
double[][] currentSourcePastVectors =
MatrixUtils.makeDelayEmbeddingVector(source, l, l_tau,
startTimeForFirstDestEmbedding + 1 - delay,
source.length - startTimeForFirstDestEmbedding - 1);
// Now combine the destination past vectors with the conditionals:
double[][] currentCombinedConditionalVectors =
new double[currentSourcePastVectors.length][k + dimOfConditionals];
MatrixUtils.arrayCopy(currentDestPastVectors, 0, 0,
currentCombinedConditionalVectors, 0, 0,
currentDestPastVectors.length, k);
int nextColumnToCopyInto = k;
for (int i = 0; i < condEmbedDims.length; i++) {
// Extract the embedding for conditional variable i
double[][] currentThisConditonalVectors =
MatrixUtils.makeDelayEmbeddingVector(conditionals, i,
condEmbedDims[i], this.cond_taus[i],
startTimeForFirstDestEmbedding + 1 - condDelays[i],
conditionals.length - startTimeForFirstDestEmbedding - 1);
// And add this embedding to our set of conditional variables
MatrixUtils.arrayCopy(currentThisConditonalVectors, 0, 0,
currentCombinedConditionalVectors, 0, nextColumnToCopyInto,
currentThisConditonalVectors.length, condEmbedDims[i]);
nextColumnToCopyInto += condEmbedDims[i];
}
double[][][] returnSet = new double[3][][];
returnSet[0] = currentSourcePastVectors;
returnSet[1] = currentDestNextVectors;
returnSet[2] = currentCombinedConditionalVectors;
return returnSet;
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#addObservations(double[], double[], double[][], int, int)
*/
@Override
public void addObservations(double[] source, double[] destination,
double[][] conditionals, int startTime, int numTimeSteps)
throws Exception {
if (source.length != destination.length) {
throw new Exception(String.format("Source and destination lengths (%d and %d) must match!",
source.length, destination.length));
}
if (conditionals == null) {
if (condEmbedDims.length > 0) {
throw new Exception(String.format("No conditionals supplied (expected %d-dimensional conditionals", condEmbedDims.length));
} else {
// This is allowed; make a dummy set of conditionals
conditionals = new double[destination.length][0];
}
}
if (conditionals.length != destination.length) {
throw new Exception(String.format("Conditionals and destination lengths (%d and %d) must match!",
conditionals.length, destination.length));
}
// Postcondition -- all time series have same length
if (source.length < startTime + numTimeSteps) {
// There are not enough observations given the arguments here
throw new Exception("Not enough observations to set here given startTime and numTimeSteps parameters");
}
addObservations(MatrixUtils.select(source, startTime, numTimeSteps),
MatrixUtils.select(destination, startTime, numTimeSteps),
MatrixUtils.selectRows(conditionals, startTime, numTimeSteps));
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#addObservations(double[], double[], double[], int, int)
*/
@Override
public void addObservations(double[] source, double[] destination,
double[] conditionals, int startTime, int numTimeSteps) throws Exception {
if (condEmbedDims.length != 1) {
throw new Exception("Cannot call addObservations(double[], double[], double[], int, int) when the " +
"conditional TE calculator was not initialised for one conditional variable");
}
double[][] conditionalsIn2D = null;
if (conditionals != null) {
// This isn't incredibly efficient, but is easy to code and doesn't cost more
// than an increase in the linear time multiplier.
conditionalsIn2D = new double[conditionals.length][1];
MatrixUtils.copyIntoColumn(conditionalsIn2D, 0, conditionals);
}
addObservations(source, destination, conditionalsIn2D, startTime, numTimeSteps);
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#finaliseAddObservations()
*/
@Override
public void finaliseAddObservations() throws Exception {
condMiCalc.finaliseAddObservations();
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#addObservations(double[], double[], double[][], boolean[], boolean[], boolean[][])
*/
@Override
public void addObservations(double[] source, double[] destination,
double[][] conditionals, boolean[] sourceValid,
boolean[] destValid, boolean[][] conditionalsValid)
throws Exception {
Vector<int[]> startAndEndTimePairs =
computeStartAndEndTimePairs(sourceValid, destValid, conditionalsValid);
// We've found the set of start and end times for this pair
for (int[] timePair : startAndEndTimePairs) {
int startTime = timePair[0];
int endTime = timePair[1];
addObservations(source, destination, conditionals, startTime, endTime - startTime + 1);
}
}
/**
* A non-overloaded method signature -- it seems JPype from Python cannot resolve
* overloaded method signatures where more than 2 variables change between univariate and
* multivariate.
*
* @param source
* @param destination
* @param conditionals
* @param sourceValid
* @param destValid
* @param conditionalsValid
* @throws Exception
*/
public void addObservations2D(double[] source, double[] destination,
double[][] conditionals, boolean[] sourceValid,
boolean[] destValid, boolean[][] conditionalsValid)
throws Exception {
addObservations(source, destination, conditionals,
sourceValid, destValid, conditionalsValid);
}
@Override
public void addObservations(double[] source, double[] destination, double[] conditionals, boolean[] sourceValid,
boolean[] destValid, boolean[] conditionalsValid) throws Exception {
if (condEmbedDims.length != 1) {
throw new Exception("Cannot call addObservations(double[], double[], double[], int, int) when the " +
"conditional TE calculator was not initialised for one conditional variable");
}
double[][] conditionalsIn2D = null;
boolean[][] conditionalsValidIn2D = null;
if (conditionals != null) {
// This isn't incredibly efficient, but is easy to code and doesn't cost more
// than an increase in the linear time multiplier.
conditionalsIn2D = new double[conditionals.length][1];
MatrixUtils.copyIntoColumn(conditionalsIn2D, 0, conditionals);
conditionalsValidIn2D = new boolean[conditionalsValid.length][1];
MatrixUtils.copyIntoColumn(conditionalsValidIn2D, 0, conditionalsValid);
}
addObservations(source, destination, conditionalsIn2D,
sourceValid, destValid, conditionalsValidIn2D);
}
/**
* A non-overloaded method signature -- it seems JPype from Python cannot resolve
* overloaded method signatures where more than 2 variables change between univariate and
* multivariate.
*
* @param source
* @param destination
* @param conditionals
* @param sourceValid
* @param destValid
* @param conditionalsValid
* @throws Exception
*/
public void addObservations1D(double[] source, double[] destination, double[] conditionals, boolean[] sourceValid,
boolean[] destValid, boolean[] conditionalsValid) throws Exception {
addObservations(source, destination, conditionals,
sourceValid, destValid, conditionalsValid);
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#setObservations(double[], double[], double[][], boolean[], boolean[], boolean[][])
*/
@Override
public void setObservations(double[] source, double[] destination,
double[][] conditionals, boolean[] sourceValid,
boolean[] destValid, boolean[][] conditionalsValid)
throws Exception {
Vector<int[]> startAndEndTimePairs =
computeStartAndEndTimePairs(sourceValid, destValid, conditionalsValid);
// We've found the set of start and end times for this pair
startAddObservations();
for (int[] timePair : startAndEndTimePairs) {
int startTime = timePair[0];
int endTime = timePair[1];
addObservations(source, destination, conditionals, startTime, endTime - startTime + 1);
}
finaliseAddObservations();
}
@Override
public void setObservations(double[] source, double[] destination, double[] conditionals, boolean[] sourceValid,
boolean[] destValid, boolean[] conditionalsValid) throws Exception {
if (condEmbedDims.length != 1) {
throw new Exception("Cannot call addObservations(double[], double[], double[], int, int) when the " +
"conditional TE calculator was not initialised for one conditional variable");
}
double[][] conditionalsIn2D = null;
boolean[][] conditionalsValidIn2D = null;
if (conditionals != null) {
// This isn't incredibly efficient, but is easy to code and doesn't cost more
// than an increase in the linear time multiplier.
conditionalsIn2D = new double[conditionals.length][1];
MatrixUtils.copyIntoColumn(conditionalsIn2D, 0, conditionals);
conditionalsValidIn2D = new boolean[conditionalsValid.length][1];
MatrixUtils.copyIntoColumn(conditionalsValidIn2D, 0, conditionalsValid);
}
setObservations(source, destination, conditionalsIn2D,
sourceValid, destValid, conditionalsValidIn2D);
}
/**
* Compute a vector of start and end pairs of time points, between which we have
* valid tuples of source, destinations and conditionals.
* (i.e. all points within the
* embedding vectors must be valid, even if the invalid points won't be included
* in any tuples)
*
* Made public so it can be used if one wants to compute the number of
* observations prior to setting the observations.
*
* @param sourceValid
* @param destValid
* @return
* @throws Exception
*/
public Vector<int[]> computeStartAndEndTimePairs(boolean[] sourceValid,
boolean[] destValid, boolean[][] condValid) throws Exception {
if (sourceValid.length != destValid.length) {
throw new Exception("Validity arrays must be of same length");
}
if (condValid.length != destValid.length) {
throw new Exception("Validity arrays must be of same length");
}
int lengthOfDestPastRequired = (k-1)*k_tau + 1;
int lengthOfSourcePastRequired = (l-1)*l_tau + 1;
int[] lengthOfConditionalsPastsRequired = new int[condEmbedDims.length];
for (int i = 0; i < condEmbedDims.length; i++) {
lengthOfConditionalsPastsRequired[i] = (condEmbedDims[i]-1)*cond_taus[i] + 1;
}
// Scan along the data avoiding invalid values
int startTime = 0;
Vector<int[]> startAndEndTimePairs = new Vector<int[]>();
// Simple solution -- this takes more complexity in time, but is
// much faster to code:
boolean previousWasOk = false;
for (int t = startTimeForFirstDestEmbedding; t < destValid.length - 1; t++) {
// Check the tuple with the history vector starting from
// t and running backwards
if (previousWasOk) {
// Just check the very next values of each:
boolean nextCondsValid = true;
for (int i = 0; i < condEmbedDims.length; i++) {
nextCondsValid &= condValid[t + 1 - condDelays[i]][i];
}
if (nextCondsValid && destValid[t + 1] && sourceValid[t + 1 - delay]) {
// We can continue adding to this sequence
continue;
} else {
// We need to shut down this sequence now
previousWasOk = false;
int[] timePair = new int[2];
timePair[0] = startTime;
timePair[1] = t; // Previous time step was last valid one
startAndEndTimePairs.add(timePair);
continue;
}
}
// Otherwise we're trying to start a new sequence, so check all values
if (!destValid[t + 1]) {
continue;
}
boolean allOk = true;
for (int tBack = 0; tBack < lengthOfDestPastRequired; tBack++) {
if (!destValid[t - tBack]) {
allOk = false;
break;
}
}
if (!allOk) {
continue;
}
// allOk == true at this point
for (int tBack = delay - 1; tBack < delay - 1 + lengthOfSourcePastRequired; tBack++) {
if (!sourceValid[t - tBack]) {
allOk = false;
break;
}
}
if (!allOk) {
continue;
}
// allOk == true at this point
for (int i = 0; i < condEmbedDims.length; i++) {
for (int tBack = condDelays[i] - 1; tBack < condDelays[i] - 1 + lengthOfConditionalsPastsRequired[i]; tBack++) {
if (!condValid[t - tBack][i]) {
allOk = false;
break;
}
}
if (!allOk) {
break;
}
}
if (!allOk) {
continue;
}
// allOk == true at this point
// Postcondition: We've got a first valid tuple:
startTime = t - startTimeForFirstDestEmbedding;
previousWasOk = true;
}
// Now check if we were running a sequence and terminate it:
if (previousWasOk) {
// We need to shut down this sequence now
previousWasOk = false;
int[] timePair = new int[2];
timePair[0] = startTime;
timePair[1] = destValid.length - 1;
startAndEndTimePairs.add(timePair);
}
return startAndEndTimePairs;
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ChannelCalculatorCommon#computeAverageLocalOfObservations()
*/
@Override
public double computeAverageLocalOfObservations() throws Exception {
return condMiCalc.computeAverageLocalOfObservations();
}
/**
* Returns the time series of local conditional TE values
* for the previously supplied observations.
*
* {@inheritDoc}
*
* @return a time-series array of local TE values of the previously submitted observations.
* If only a single series was submitted, then the first
* {@link #startTimeForFirstDestEmbedding} steps are filled with zeros
* (since local conditional TE is undefined here)
*/
@Override
public double[] computeLocalOfPreviousObservations() throws Exception {
double[] local = condMiCalc.computeLocalOfPreviousObservations();
if (!condMiCalc.getAddedMoreThanOneObservationSet()) {
double[] localsToReturn = new double[local.length + startTimeForFirstDestEmbedding + 1];
System.arraycopy(local, 0, localsToReturn, startTimeForFirstDestEmbedding + 1, local.length);
return localsToReturn;
} else {
return local;
}
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#computeLocalUsingPreviousObservations(double[], double[], double[][])
*/
@Override
public double[] computeLocalUsingPreviousObservations(
double[] newSourceObservations, double[] newDestObservations,
double[][] newCondObservations) throws Exception {
if (newSourceObservations.length != newDestObservations.length) {
throw new Exception(String.format("Source and destination lengths (%d and %d) must match!",
newSourceObservations.length, newDestObservations.length));
}
if (newCondObservations == null) {
if (condEmbedDims.length > 0) {
throw new Exception(String.format("No conditionals supplied (expected %d-dimensional conditionals)", condEmbedDims.length));
} else {
// This is allowed; make a dummy set of conditionals
newCondObservations = new double[newDestObservations.length][0];
}
}
if (newCondObservations.length != newDestObservations.length) {
throw new Exception(String.format("Conditionals and destination lengths (%d and %d) must match!",
newCondObservations.length, newDestObservations.length));
}
// Postcondition -- all time series have same length
if (newCondObservations[0].length != condEmbedDims.length) {
throw new Exception(String.format("Number of conditional variables %d does not " +
"match the initialised number %d", newCondObservations[0].length, condEmbedDims.length));
}
if (newDestObservations.length < startTimeForFirstDestEmbedding + 2) {
// There are no observations to compute for here
return new double[newDestObservations.length];
}
// All parameters are as expected
double[][][] embeddedVectorsForCondMI =
embedSourceDestAndConditionalsForCondMI(newSourceObservations,
newDestObservations, newCondObservations);
double[] local = condMiCalc.computeLocalUsingPreviousObservations(
embeddedVectorsForCondMI[0], embeddedVectorsForCondMI[1],
embeddedVectorsForCondMI[2]);
// Pad the front of the array with zeros where local TE isn't defined:
double[] localsToReturn = new double[local.length + startTimeForFirstDestEmbedding + 1];
System.arraycopy(local, 0, localsToReturn, startTimeForFirstDestEmbedding + 1, local.length);
return localsToReturn;
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.ConditionalTransferEntropyCalculator#computeLocalUsingPreviousObservations(double[], double[], double[])
*/
@Override
public double[] computeLocalUsingPreviousObservations(
double[] newSourceObservations, double[] newDestObservations,
double[] newCondObservations) throws Exception {
if (condEmbedDims.length != 1) {
throw new Exception("Cannot call computeLocalUsingPreviousObservations(double[], double[], double[]) when the " +
"conditional TE calculator was not initialised for one conditional variable");
}
double[][] conditionalsIn2D = null;
if (newCondObservations != null) {
// This isn't incredibly efficient, but is easy to code and doesn't cost more
// than an increase in the linear time multiplier.
conditionalsIn2D = new double[newCondObservations.length][1];
MatrixUtils.copyIntoColumn(conditionalsIn2D, 0, newCondObservations);
}
return computeLocalUsingPreviousObservations(newSourceObservations,
newDestObservations, conditionalsIn2D);
}
@Override
public EmpiricalMeasurementDistribution computeSignificance(
int numPermutationsToCheck) throws Exception {
return condMiCalc.computeSignificance(1, numPermutationsToCheck); // Reorder the source vectors
}
@Override
public EmpiricalMeasurementDistribution computeSignificance(
int[][] newOrderings) throws Exception {
return condMiCalc.computeSignificance(1, newOrderings); // Reorder the source vectors
}
@Override
public double getLastAverage() {
return condMiCalc.getLastAverage();
}
@Override
public int getNumObservations() throws Exception {
return condMiCalc.getNumObservations();
}
@Override
public boolean getAddedMoreThanOneObservationSet() {
return condMiCalc.getAddedMoreThanOneObservationSet();
}
@Override
public void setDebug(boolean debug) {
this.debug = debug;
condMiCalc.setDebug(debug);
}
}