All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.sandia.cognition.statistics.TransferEntropy Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * File:                TransferEntropy.java
 * Authors:             Tom Brounstein
 * Company:             Sandia National Laboratories
 * Project:             Cognitive Foundry
 * 
 * Copyright April 2, 2014, Sandia Corporation.
 * Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive
 * license for use of this work by or on behalf of the U.S. Government.
 * Export of this program may require a license from the United States
 * Government. See CopyrightHistory.txt for complete details.
 * 
 */

package gov.sandia.cognition.statistics;

import gov.sandia.cognition.annotation.PublicationReference;
import gov.sandia.cognition.annotation.PublicationType;
import gov.sandia.cognition.math.matrix.Vector;
import gov.sandia.cognition.statistics.bayesian.ConditionalProbability;
import gov.sandia.cognition.statistics.distribution.DefaultDataDistribution;
import gov.sandia.cognition.util.DefaultPair;
import gov.sandia.cognition.util.Pair;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * A class for calculating the transfer entropy of two vectors.  Note that in the
 * current implementation only values included in a transfer entropy object are
 * used in calculating the conditional probability.  For example: assume the two
 * vectors were [0,0,0,0,1] and [1,1,1,1,1] with window size 1.  Then, when 
 * computing the conditional probabilities, the state [1,1] will not be included
 * in the calculations since there's no triad that includes [1,1] in the prior.
 * This generates more intuitive results (the above example has a transfer entropy value of 0
 * in this implementation), and is easier to code.
 * @author trbroun
 * @since  3.4.2
 */
@PublicationReference(
    author="Thomas Schreiber",
    title="Measuring Information Transfer",
    type=PublicationType.Journal,
    year=2000,
    url="http://journals.aps.org/prl/pdf/10.1103/PhysRevLett.85.461"
)
public class TransferEntropy{
    private final DefaultDataDistribution oneTermPriorDistribution;
    private final DefaultDataDistribution oneTermPosteriorDistribution;
    private final DefaultDataDistribution twoTermPriorDistribution;
    private final DefaultDataDistribution twoTermPosteriorDistribution;

    /**
     * Creates a new transfer entropy object
     * @param destinationVector
     * @param sourceVector
     * @param windowSize  How many steps in the past will be used in the analysis. 
     */
    public TransferEntropy(Vector sourceVector, Vector destinationVector, 
        int windowSize) {
        
        this(sourceVector, destinationVector, windowSize, windowSize);
    }
    
    /**
     * Creates a new transfer entropy object.
     * @param destinationVector  
     * @param sourceVector
     * @param destinationWindowSize How many time steps into the past will be used with the vectorBeingActedOn
     * @param sourceWindowSize How many time steps into the past will be sued with the vectorActingOnOtherVector
     */
    public TransferEntropy(Vector sourceVector, Vector destinationVector, 
        int sourceWindowSize, int destinationWindowSize) {
        
        oneTermPriorDistribution = new DefaultDataDistribution();
        twoTermPriorDistribution = new DefaultDataDistribution();
        oneTermPosteriorDistribution = new DefaultDataDistribution();
        twoTermPosteriorDistribution = new DefaultDataDistribution();
        
        //Ensure window sizes are at least 1.
        destinationWindowSize = Math.max(destinationWindowSize, 1);
        sourceWindowSize = Math.max(sourceWindowSize, 1);
        
        int maxWindow = Math.max(destinationWindowSize, sourceWindowSize);
        
        //Generates the distributions by iterating over the vectors.
        for (int ii=0; ii < destinationVector.getDimensionality() - maxWindow; ii++) {
            
            ArrayList destinationVectorPrior = new ArrayList(destinationWindowSize);
            addVectorElements(destinationVector, ii + maxWindow - destinationWindowSize, ii + maxWindow, destinationVectorPrior);
            
            ArrayList sourceVectorPrior = new ArrayList(sourceWindowSize);
            addVectorElements(sourceVector, ii + maxWindow - sourceWindowSize, ii + maxWindow, sourceVectorPrior);
            
            Integer posterior = (int) destinationVector.getElement(ii + maxWindow);
            
            oneTermPriorDistribution.increment(new TransferEntropyDistributionObject(destinationVectorPrior, null, null));
            twoTermPriorDistribution.increment(new TransferEntropyDistributionObject(destinationVectorPrior, sourceVectorPrior, null));
            oneTermPosteriorDistribution.increment(new TransferEntropyDistributionObject(destinationVectorPrior, null, posterior));
            twoTermPosteriorDistribution.increment(new TransferEntropyDistributionObject(destinationVectorPrior, sourceVectorPrior, posterior));
        }
    }
    
    /**
     * Creates a new transfer entropy object using lists instead of vectors.
     * @param destinationList
     * @param sourceList
     * @param windowSize  How many steps in the past will be used in the analysis. 
     */
    public TransferEntropy(List sourceList, List destinationList, 
        int windowSize) {
        
        this(sourceList, destinationList, windowSize, windowSize);
    }
    
    /**
     * Creates a new transfer entropy object using lists instead of vectors.
     * @param destinationList  
     * @param sourceList
     * @param destinationWindowSize How many time steps into the past will be used with the vectorBeingActedOn
     * @param sourceWindowSize How many time steps into the past will be sued with the vectorActingOnOtherVector
     */
    public TransferEntropy(List sourceList, List destinationList, 
        int sourceWindowSize, int destinationWindowSize) {
        
        oneTermPriorDistribution = new DefaultDataDistribution();
        twoTermPriorDistribution = new DefaultDataDistribution();
        oneTermPosteriorDistribution = new DefaultDataDistribution();
        twoTermPosteriorDistribution = new DefaultDataDistribution();
        
        //Ensure window sizes are at least 1.
        destinationWindowSize = Math.max(destinationWindowSize, 1);
        sourceWindowSize = Math.max(sourceWindowSize, 1);
        
        int maxWindow = Math.max(destinationWindowSize, sourceWindowSize);
        
        //Generates the distributions by iterating over the vectors.
        for (int ii=0; ii < destinationList.size() - maxWindow; ii++) {
            
            List destinationListPrior = destinationList.subList(
                ii + maxWindow - destinationWindowSize, ii + maxWindow);
            
            List sourceListPrior = sourceList.subList(ii + maxWindow - sourceWindowSize,
                ii + maxWindow);
            
            Object posterior = destinationList.get(ii + maxWindow);
            
            oneTermPriorDistribution.increment(new TransferEntropyDistributionObject(destinationListPrior, null, null));
            twoTermPriorDistribution.increment(new TransferEntropyDistributionObject(destinationListPrior, sourceListPrior, null));
            oneTermPosteriorDistribution.increment(new TransferEntropyDistributionObject(destinationListPrior, null, posterior));
            twoTermPosteriorDistribution.increment(new TransferEntropyDistributionObject(destinationListPrior, sourceListPrior, posterior));
        }
    }
    
    /**
     * Creates a new transfer entropy object using distributions.
     * @param oneTermPriorDistribution  The prior distribution over one vector.  The prior one term prior should be filled in,
     * while the second vector prior and posterior should be null (or a consistent value over each element of the domain).
     * @param oneTermPosteriorDistribution  A distribution over the first term prior and the posterior.  Leave the second term vector prior
     * as null, or a consistent value across the domain space.
     * @param twoTermPriorDistribution  A distribution over TransferEntropyDistributionObjects where the prior for the first and second vector is supplied. 
     * @param twoTermPosteriorDistribution  A distribution over TransferEntropyDistributionObjects where all values are supplied.
     */
    public TransferEntropy(DefaultDataDistribution oneTermPriorDistribution,
        DefaultDataDistribution oneTermPosteriorDistribution,
        DefaultDataDistribution twoTermPriorDistribution,
        DefaultDataDistribution twoTermPosteriorDistribution) {
        
        this.oneTermPriorDistribution = oneTermPriorDistribution;
        this.oneTermPosteriorDistribution = oneTermPosteriorDistribution;
        this.twoTermPriorDistribution = twoTermPriorDistribution;
        this.twoTermPosteriorDistribution = twoTermPosteriorDistribution;
    }

    /**
     * Computes the transfer entropy value.
     * @return The transfer entropy from one vector to the other, or one distribution to the other.
     */
    public double compute() {
        
        double solution = 0.0;
        ConditionalProbability instance = new ConditionalProbability();
        
        for (TransferEntropyDistributionObject state : twoTermPosteriorDistribution.getDomain()) {
            
            double answer = partialSum(state, instance);
            solution += answer;
        }
        
        return solution;
    }
    
    /**
     * Computes the transfer entropy value and returns a map of the states with their corresponding partial sums.
     * Used to identify which states contribute the most or least to the transfer entropy.
     * @return A pair, where the first element is the transfer entropy value between the two vectors, and the second
     * element is a map from states to their corresponding partial sums.
     */
    public Pair> computeWithPartialSums() {
        
        double solution = 0.0;
        HashMap mapSolution = 
            new HashMap();
        ConditionalProbability instance = 
            new ConditionalProbability();
        
        for (TransferEntropyDistributionObject state : twoTermPosteriorDistribution.getDomain()) {
            
            TransferEntropyPartialSumObject answerWithWrapper = partialSumWithWrapper(state, instance);
            solution += answerWithWrapper.partialSum;
            mapSolution.put(state, answerWithWrapper);
        }
        
        Pair> finalAnswer = 
            new DefaultPair>(solution, mapSolution);
        return finalAnswer;
    }
    
    /**
     * A helper method to add values from the vector to an array list, and transform them to integers.
     * Gets all elements in range start to finish.  Is 0 indexed.
     * @param vector  The vector where elements are being taken from.
     * @param start  The start index for elements to be copied from the vector.
     * @param finish  The end index for elements to be copied from the vector.
     * @param temp The ArrayList that has elements being added to it.
     */
    private void addVectorElements(Vector vector, int start, int finish, ArrayList temp) {
        for (int ii=start; ii < finish; ii++) {
            temp.add((int) vector.getElement(ii));
        }
    }
    
    /**
     * A helper method that computes an individual term in the summation.
     * @param state  The TransferEntropyDistributionObjects state being analyzed in this element of the sum.
     * @param instance  A conditional probability object, used to calculate elements of the transfer entropy.
     * @return The transfer entropy partial sum for this specific state.
     */
    private double partialSum(TransferEntropyDistributionObject state, ConditionalProbability instance) {
        //Note that numerator and denominator can never be 0 (if they are, there's a bug in the code).
        //At this point in the code we're itereating over all observed two vector posterior states,
        //which take the form i_(n+1), i_n^k, j_n^l.  The numerator looks at probability of i_(n+1) 
        //given i_n^k and j_n^k; this probability is greater than 0 since we know there is at least
        //one case of i_(n+1), i_n^k, j_n^l is seen since it's included in the loop.  The denominator
        //follows similarly.  Thus, both of these values are non-0.
        double numerator = instance.computeConditionalProbabilityWhenDataTypeHasHistoricalData(
                new TransferEntropyDistributionObject(state.destinationVectorPrior, state.sourceVectorPrior), state, 
                twoTermPriorDistribution, twoTermPosteriorDistribution);
        double denominator = instance.computeConditionalProbabilityWhenDataTypeHasHistoricalData(
                new TransferEntropyDistributionObject(state.destinationVectorPrior), new TransferEntropyDistributionObject(state.destinationVectorPrior, state.posterior), 
                oneTermPriorDistribution, oneTermPosteriorDistribution);
        double multiple = twoTermPosteriorDistribution.getProbabilityFunction().evaluate(
            state);
        double answer = multiple * Math.log(numerator/denominator);
        //The return statement could have been in the previous statement, but this is to be explicit.
        return answer;
    }
    
    /**
     * A helper method that wraps a transfer entropy partial sum value with extra information.
     * @param state  The state being evaluated.
     * @param instance A conditional probability object, used to calculate elements of the transfer entropy.
     * @return An object which includes the state, the partial sum value, and how many times the state occurs.
     */
    private TransferEntropyPartialSumObject partialSumWithWrapper(TransferEntropyDistributionObject state, ConditionalProbability instance) {
        double answer = partialSum(state, instance);
        double numberOfAppearances = twoTermPosteriorDistribution.get(state);
        return new TransferEntropyPartialSumObject(state, answer, numberOfAppearances);
    }
    
    /**
     * A helper class that define the objects used by the distributions in transfer entropy.
     */
    public static class TransferEntropyDistributionObject{
        
        /** The prior for the destination vector. */
        public final List destinationVectorPrior;
        
        /** The prior for the source vector. */
        public final List sourceVectorPrior;
        
        /** The posterior. */
        public final Object posterior;
        
        /**
         * Basic constructor for a TransferEntropyDistributionObjects.  FirstVectPrior is assigned in the obvious manner, all other values null.
         * @param destinationVectorPrior 
         */
        public TransferEntropyDistributionObject(List destinationVectorPrior) {
            this(destinationVectorPrior, null, null);
        }
        
        /**
         * Basic constructor for a TransferEntropyDistributionObjects.  First and second vector priors are assigned in the obvious manner, posterior is null.
         * @param destinationVectorPrior
         * @param sourceVectorPrior 
         */
        public TransferEntropyDistributionObject(List destinationVectorPrior, List sourceVectorPrior) {
            this(destinationVectorPrior, sourceVectorPrior, null);
        }
        
        /**
         * Basic constructor for a TransferEntropyDistributionObjects.  First vector prior and posterior are assigned in the obvious manner, second vector prior is null.
         * @param destinationVectorPrior
         * @param posterior 
         */
        public TransferEntropyDistributionObject(List destinationVectorPrior, Object posterior) {
            this(destinationVectorPrior, null, posterior);
        }
        
        /**
         * Basic constructor for a TransferEntropyDistributionObjects.  All values are assigned in the obvious manner.
         * @param destinationVectorPrior
         * @param sourceVectorPrior
         * @param posterior 
         */
        public TransferEntropyDistributionObject(List destinationVectorPrior, List sourceVectorPrior, Object posterior) {
            this.destinationVectorPrior = destinationVectorPrior;
            this.sourceVectorPrior = sourceVectorPrior;
            this.posterior = posterior;
        }
        
        @Override
        public boolean equals(Object other) {
            if (other instanceof TransferEntropyDistributionObject) {
                return equals((TransferEntropyDistributionObject) other);
            }
            return false;
        }
        
        /**
         * Equals function for comparing two TransferEntropyDistributionObjects.
         * @param other  The other TransferEntropyDistributionObjects being compared.
         * @return If the two objects are equal.  Compares each component individually.
         */
        private boolean equals(TransferEntropyDistributionObject other) {
            return compareComponent(destinationVectorPrior, other.destinationVectorPrior) &&
                compareComponent(sourceVectorPrior, other.sourceVectorPrior) &&
                compareComponent(posterior, other.posterior);
        }
        
        /**
         * Compares two objects to see if they're both null.  Otherwise, checks if they're equal.
         * @param first
         * @param second
         * @return If the two objects are both null or equal.
         */
        private boolean compareComponent(Object first, Object second) {
            if (first==null) {
                return second==null;
            }
            return first.equals(second);
        }
        
        @Override
        public int hashCode() {
            ArrayList temp = new ArrayList(destinationVectorPrior);
            if (sourceVectorPrior !=  null){
                temp.addAll(sourceVectorPrior);
            }
            if (posterior != null){
                temp.add(posterior);
            }
            
            return temp.hashCode();
        }
        
        @Override
        public String toString() {
            return "Source vector state: " + sourceVectorPrior + 
                "\nDestiation vector state: " + destinationVectorPrior + 
                "\nPosterior: " + posterior;
        }
        
    }
    
    /**
     * Helper class for holding information about the partial sums.
     */
    public static class TransferEntropyPartialSumObject {
        
        /** The state. */
        public final TransferEntropy.TransferEntropyDistributionObject state;
        
        /** The partial sum. */
        public final double partialSum;
        
        /** The number of appearances. */
        public final double numberOfAppearances;
        
        /**
         * Basic constructor that assigns values in the intuitive way.
         * @param state The state represented by this partial sum;
         * @param partialSum The transfer entropy partial sum.
         * @param numberOfApperances The number of times 
         */
        public TransferEntropyPartialSumObject(TransferEntropy.TransferEntropyDistributionObject state, double partialSum, double numberOfApperances) {
            this.state = state;
            this.partialSum = partialSum;
            this.numberOfAppearances = numberOfApperances;
        }
        
        @Override
        public String toString() {
            return state.toString() + "\nPartial Sum: " + partialSum + "\nNumber of Appearances: " + numberOfAppearances;
        }
        
        /**
         * Checks if this partial sum is equal to the other one.
         * 
         * @param other
         *      The other sum.
         * @return 
         *      True if they're equal.
         */
        public boolean equals(TransferEntropyPartialSumObject other) {
            return (state.equals(other.state)) && (Math.abs(partialSum - other.partialSum) < 0.00001)
                && (Math.abs(numberOfAppearances - other.numberOfAppearances) < 0.5);
        }
    }
}