mirror of https://github.com/jlizier/jidt
484 lines
13 KiB
Java
Executable File
484 lines
13 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.discrete;
|
|
|
|
import infodynamics.utils.MatrixUtils;
|
|
|
|
/**
|
|
* <p>Entropy calculator for univariate discrete (int[]) data.</p>
|
|
*
|
|
* <p>Usage of the class is intended to follow this paradigm:</p>
|
|
* <ol>
|
|
* <li>Construct the calculator: {@link #EntropyCalculatorDiscrete(int)};</li>
|
|
* <li>Initialise the calculator using {@link #initialise()};</li>
|
|
* <li>Provide the observations/samples for the calculator
|
|
* to set up the PDFs, using one or more calls to
|
|
* sets of {@link #addObservations(int[])} methods, then</li>
|
|
* <li>Compute the required quantities, being one or more of:
|
|
* <ul>
|
|
* <li>the average entropy: {@link #computeAverageLocalOfObservations()};</li>
|
|
* <li>local entropy values, such as {@link #computeLocal(int[])};</li>
|
|
* <li>and variants of these.</li>
|
|
* </ul>
|
|
* </li>
|
|
* <li>As an alternative to steps 3 and 4, the user may undertake
|
|
* standalone computation from a single set of observations, via
|
|
* e.g.: {@link #computeLocal(int[])},
|
|
* {@link #computeAverageLocal(int[])} etc.</li>
|
|
* <li>
|
|
* Return to step 2 to re-use the calculator on a new data set.
|
|
* </li>
|
|
* </ol>
|
|
*
|
|
* <p><b>References:</b><br/>
|
|
* <ul>
|
|
* <li>T. M. Cover and J. A. Thomas, 'Elements of Information
|
|
Theory' (John Wiley & Sons, New York, 1991).</li>
|
|
* </ul>
|
|
*
|
|
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
|
|
* <a href="http://lizier.me/joseph/">www</a>)
|
|
*/
|
|
public class EntropyCalculatorDiscrete extends InfoMeasureCalculatorDiscrete
|
|
implements SingleAgentMeasureDiscrete
|
|
{
|
|
|
|
protected int[] stateCount = null; // Count for i[t]
|
|
|
|
/**
|
|
* User was formerly forced to create new instances through this factory method.
|
|
* Retained for backwards compatibility.
|
|
*
|
|
* @param base number of symbols for each variable.
|
|
* E.g. binary variables are in base-2.
|
|
* @param blocksize number of consecutive joint values to include
|
|
* in the calculation.
|
|
* @deprecated
|
|
* @return a new EntropyCalculator
|
|
*/
|
|
public static EntropyCalculatorDiscrete newInstance(int base, int blocksize) {
|
|
if (blocksize > 1) {
|
|
return BlockEntropyCalculatorDiscrete.newInstance(blocksize, base);
|
|
} else {
|
|
return EntropyCalculatorDiscrete.newInstance(base);
|
|
}
|
|
}
|
|
public static EntropyCalculatorDiscrete newInstance(int base) {
|
|
return new EntropyCalculatorDiscrete(base);
|
|
}
|
|
|
|
/**
|
|
* Construct a new instance with default base of 2
|
|
*/
|
|
public EntropyCalculatorDiscrete() {
|
|
this(2);
|
|
}
|
|
|
|
/**
|
|
* Contruct a new instance
|
|
*
|
|
* @param base number of quantisation levels for each variable.
|
|
* E.g. binary variables are in base-2.
|
|
*/
|
|
public EntropyCalculatorDiscrete(int base) {
|
|
super(base);
|
|
}
|
|
|
|
/**
|
|
* Initialise with new base
|
|
*
|
|
* @param base
|
|
*/
|
|
public void initialise(int base){
|
|
boolean baseChanged = (this.base != base);
|
|
super.initialise(base);
|
|
|
|
if (baseChanged || (stateCount == null)) {
|
|
// Create storage for counts of observations
|
|
try {
|
|
stateCount = new int[base];
|
|
} catch (OutOfMemoryError e) {
|
|
// Allow any Exceptions to be thrown, but catch and wrap
|
|
// Error as a RuntimeException
|
|
throw new RuntimeException("Requested memory for the base (" +
|
|
base + ") is too large for the JVM at this time", e);
|
|
}
|
|
} else {
|
|
MatrixUtils.fill(stateCount, 0);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void initialise(){
|
|
initialise(base);
|
|
}
|
|
|
|
@Override
|
|
public void addObservations(int states[]) {
|
|
int rows = states.length;
|
|
// increment the count of observations:
|
|
observations += rows;
|
|
|
|
// 1. Count the tuples observed
|
|
for (int r = 0; r < rows; r++) {
|
|
// Add to the count for this particular state:
|
|
stateCount[states[r]]++;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void addObservations(int states[][]) {
|
|
int rows = states.length;
|
|
int columns = states[0].length;
|
|
// increment the count of observations:
|
|
observations += rows * columns;
|
|
|
|
// 1. Count the tuples observed
|
|
for (int r = 0; r < rows; r++) {
|
|
for (int c = 0; c < columns; c++) {
|
|
// Add to the count for this particular state:
|
|
stateCount[states[r][c]]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void addObservations(int states[][][]) {
|
|
int timeSteps = states.length;
|
|
if (timeSteps == 0) {
|
|
return;
|
|
}
|
|
int agentRows = states[0].length;
|
|
if (agentRows == 0) {
|
|
return;
|
|
}
|
|
int agentColumns = states[0][0].length;
|
|
// increment the count of observations:
|
|
observations += timeSteps * agentRows * agentColumns;
|
|
|
|
// 1. Count the tuples observed
|
|
for (int t = 0; t < timeSteps; t++) {
|
|
for (int i = 0; i < agentRows; i++) {
|
|
for (int j = 0; j < agentColumns; j++) {
|
|
// Add to the count for this particular state:
|
|
stateCount[states[t][i][j]]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void addObservations(int states[][], int agentNumber) {
|
|
int rows = states.length;
|
|
// increment the count of observations:
|
|
observations += rows;
|
|
|
|
// 1. Count the tuples observed
|
|
for (int r = 0; r < rows; r++) {
|
|
// Add to the count for this particular state:
|
|
stateCount[states[r][agentNumber]]++;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void addObservations(int states[][][], int agentIndex1, int agentIndex2) {
|
|
int timeSteps = states.length;
|
|
// increment the count of observations:
|
|
observations += timeSteps;
|
|
|
|
// 1. Count the tuples observed
|
|
for (int r = 0; r < timeSteps; r++) {
|
|
// Add to the count for this particular state:
|
|
stateCount[states[r][agentIndex1][agentIndex2]]++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the current count for the given value
|
|
*
|
|
* @param stateVal given value
|
|
* @return count of observations of the given state
|
|
*/
|
|
public int getStateCount(int stateVal) {
|
|
return stateCount[stateVal];
|
|
}
|
|
|
|
/**
|
|
* Return the current probability for the given value
|
|
*
|
|
* @param stateVal given value
|
|
* @return probability of the given state
|
|
*/
|
|
public double getStateProbability(int stateVal) {
|
|
return (double) stateCount[stateVal] / (double) observations;
|
|
}
|
|
|
|
@Override
|
|
public double computeAverageLocalOfObservations() {
|
|
double ent = 0.0;
|
|
double entCont = 0.0;
|
|
|
|
max = 0;
|
|
min = 0;
|
|
for (int stateVal = 0; stateVal < base; stateVal++) {
|
|
// compute p_state
|
|
double p_state = (double) stateCount[stateVal] / (double) observations;
|
|
if (p_state > 0.0) {
|
|
// Entropy takes the negative log:
|
|
double localValue = - Math.log(p_state) / log_2;
|
|
entCont = p_state * localValue;
|
|
if (localValue > max) {
|
|
max = localValue;
|
|
} else if (localValue < min) {
|
|
min = localValue;
|
|
}
|
|
} else {
|
|
entCont = 0.0;
|
|
}
|
|
ent += entCont;
|
|
}
|
|
|
|
average = ent;
|
|
return ent;
|
|
}
|
|
|
|
@Override
|
|
public double[] computeLocalFromPreviousObservations(int states[]){
|
|
int rows = states.length;
|
|
|
|
double[] localEntropy = new double[rows];
|
|
average = 0;
|
|
max = 0;
|
|
min = 0;
|
|
for (int r = 0; r < rows; r++) {
|
|
double p_state = (double) stateCount[states[r]] / (double) observations;
|
|
// Entropy takes the negative log:
|
|
localEntropy[r] = - Math.log(p_state) / log_2;
|
|
average += localEntropy[r];
|
|
if (localEntropy[r] > max) {
|
|
max = localEntropy[r];
|
|
} else if (localEntropy[r] < min) {
|
|
min = localEntropy[r];
|
|
}
|
|
}
|
|
average = average/(double) rows;
|
|
|
|
return localEntropy;
|
|
|
|
}
|
|
|
|
@Override
|
|
public double[][] computeLocalFromPreviousObservations(int states[][]){
|
|
int rows = states.length;
|
|
int columns = states[0].length;
|
|
|
|
double[][] localEntropy = new double[rows][columns];
|
|
average = 0;
|
|
max = 0;
|
|
min = 0;
|
|
for (int r = 0; r < rows; r++) {
|
|
for (int c = 0; c < columns; c++) {
|
|
double p_state = (double) stateCount[states[r][c]] / (double) observations;
|
|
// Entropy takes the negative log:
|
|
localEntropy[r][c] = - Math.log(p_state) / log_2;
|
|
average += localEntropy[r][c];
|
|
if (localEntropy[r][c] > max) {
|
|
max = localEntropy[r][c];
|
|
} else if (localEntropy[r][c] < min) {
|
|
min = localEntropy[r][c];
|
|
}
|
|
}
|
|
}
|
|
average = average/(double) (columns * rows);
|
|
|
|
return localEntropy;
|
|
|
|
}
|
|
|
|
@Override
|
|
public double[][][] computeLocalFromPreviousObservations(int states[][][]){
|
|
int timeSteps = states.length;
|
|
int agentRows, agentColumns;
|
|
if (timeSteps == 0) {
|
|
agentRows = 0;
|
|
agentColumns = 0;
|
|
} else {
|
|
agentRows = states[0].length;
|
|
if (agentRows == 0) {
|
|
agentColumns = 0;
|
|
} else {
|
|
agentColumns = states[0][0].length;
|
|
}
|
|
}
|
|
|
|
double[][][] localEntropy = new double[timeSteps][agentRows][agentColumns];
|
|
average = 0;
|
|
max = 0;
|
|
min = 0;
|
|
for (int r = 0; r < timeSteps; r++) {
|
|
for (int i = 0; i < agentRows; i++) {
|
|
for (int j = 0; j < agentColumns; j++) {
|
|
double p_state = (double) stateCount[states[r][i][j]] / (double) observations;
|
|
// Entropy takes the negative log:
|
|
localEntropy[r][i][j] = - Math.log(p_state) / log_2;
|
|
average += localEntropy[r][i][j];
|
|
if (localEntropy[r][i][j] > max) {
|
|
max = localEntropy[r][i][j];
|
|
} else if (localEntropy[r][i][j] < min) {
|
|
min = localEntropy[r][i][j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
average = average/(double) (agentRows * agentColumns * timeSteps);
|
|
|
|
return localEntropy;
|
|
|
|
}
|
|
|
|
@Override
|
|
public double[] computeLocalFromPreviousObservations(int states[][], int agentNumber){
|
|
int rows = states.length;
|
|
//int columns = states[0].length;
|
|
|
|
double[] localEntropy = new double[rows];
|
|
average = 0;
|
|
max = 0;
|
|
min = 0;
|
|
for (int r = 0; r < rows; r++) {
|
|
double p_state = (double) stateCount[states[r][agentNumber]] / (double) observations;
|
|
// Entropy takes the negative log:
|
|
localEntropy[r] = - Math.log(p_state) / log_2;
|
|
average += localEntropy[r];
|
|
if (localEntropy[r] > max) {
|
|
max = localEntropy[r];
|
|
} else if (localEntropy[r] < min) {
|
|
min = localEntropy[r];
|
|
}
|
|
}
|
|
average = average/(double) (rows);
|
|
|
|
return localEntropy;
|
|
|
|
}
|
|
|
|
@Override
|
|
public double[] computeLocalFromPreviousObservations(int states[][][], int agentIndex1, int agentIndex2){
|
|
int timeSteps = states.length;
|
|
//int columns = states[0].length;
|
|
|
|
// Allocate for all rows even though we'll leave the first ones as zeros
|
|
double[] localEntropy = new double[timeSteps];
|
|
average = 0;
|
|
max = 0;
|
|
min = 0;
|
|
for (int r = 0; r < timeSteps; r++) {
|
|
double p_state = (double) stateCount[states[r][agentIndex1][agentIndex2]] / (double) observations;
|
|
// Entropy takes the negative log:
|
|
localEntropy[r] = - Math.log(p_state) / log_2;
|
|
average += localEntropy[r];
|
|
if (localEntropy[r] > max) {
|
|
max = localEntropy[r];
|
|
} else if (localEntropy[r] < min) {
|
|
min = localEntropy[r];
|
|
}
|
|
}
|
|
average = average/(double) (timeSteps);
|
|
|
|
return localEntropy;
|
|
|
|
}
|
|
|
|
@Override
|
|
public final double[] computeLocal(int states[]) {
|
|
|
|
initialise();
|
|
addObservations(states);
|
|
return computeLocalFromPreviousObservations(states);
|
|
}
|
|
|
|
@Override
|
|
public final double[][] computeLocal(int states[][]) {
|
|
|
|
initialise();
|
|
addObservations(states);
|
|
return computeLocalFromPreviousObservations(states);
|
|
}
|
|
|
|
@Override
|
|
public final double[][][] computeLocal(int states[][][]) {
|
|
|
|
initialise();
|
|
addObservations(states);
|
|
return computeLocalFromPreviousObservations(states);
|
|
}
|
|
|
|
@Override
|
|
public final double computeAverageLocal(int states[]) {
|
|
|
|
initialise();
|
|
addObservations(states);
|
|
return computeAverageLocalOfObservations();
|
|
}
|
|
|
|
@Override
|
|
public final double computeAverageLocal(int states[][]) {
|
|
|
|
initialise();
|
|
addObservations(states);
|
|
return computeAverageLocalOfObservations();
|
|
}
|
|
|
|
@Override
|
|
public final double computeAverageLocal(int states[][][]) {
|
|
initialise();
|
|
addObservations(states);
|
|
return computeAverageLocalOfObservations();
|
|
}
|
|
|
|
@Override
|
|
public final double[] computeLocal(int states[][], int col) {
|
|
initialise();
|
|
addObservations(states, col);
|
|
return computeLocalFromPreviousObservations(states, col);
|
|
}
|
|
|
|
@Override
|
|
public final double[] computeLocal(int states[][][],
|
|
int agentIndex1, int agentIndex2) {
|
|
initialise();
|
|
addObservations(states, agentIndex1, agentIndex2);
|
|
return computeLocalFromPreviousObservations(states, agentIndex1, agentIndex2);
|
|
}
|
|
|
|
@Override
|
|
public final double computeAverageLocal(int states[][], int col) {
|
|
initialise();
|
|
addObservations(states, col);
|
|
return computeAverageLocalOfObservations();
|
|
}
|
|
|
|
@Override
|
|
public final double computeAverageLocal(int states[][][], int agentIndex1, int agentIndex2) {
|
|
initialise();
|
|
addObservations(states, agentIndex1, agentIndex2);
|
|
return computeAverageLocalOfObservations();
|
|
}
|
|
}
|