Merge branch 'master' into gpu

This commit is contained in:
Pedro Martinez Mediano 2017-11-21 14:58:15 +00:00
commit 446ac4ae59
165 changed files with 34616 additions and 1828 deletions

View File

@ -1,6 +1,6 @@
# Java Information Dynamics Toolkit (JIDT)
Copyright (C) 2012-2014 [Joseph T. Lizier](http://lizier.me/joseph/); 2014-2016 [Joseph T. Lizier](http://lizier.me/joseph/) and Ipek Özdemir
Copyright (C) 2012-2014 [Joseph T. Lizier](http://lizier.me/joseph/); 2014-2016 [Joseph T. Lizier](http://lizier.me/joseph/) and Ipek Özdemir; 2017- [Joseph T. Lizier](http://lizier.me/joseph/), Ipek Özdemir and [Pedro Mediano](https://www.doc.ic.ac.uk/~pam213/)
*JIDT* provides a stand-alone, open-source code Java implementation (also usable in [Matlab, Octave](../../wiki/UseInOctaveMatlab), [Python](../../wiki/UseInPython), [R](../../wiki/UseInR), [Julia](../../wiki/UseInJulia) and [Clojure](../../wiki/UseInClojure)) of information-theoretic measures of distributed computation in complex systems: i.e. information storage, transfer and modification.

View File

@ -22,7 +22,7 @@
<property name="javadocsdir" location="javadocs"/>
<property name="versionfile" value="version-${version}.txt"/>
<!-- To enable GPU code, set the following variable to true -->
<property name="enablegpu" value="true"/>
<property name="enablegpu" value="false"/>
<path id="project.classpath">
<pathelement location="bin"/>

View File

@ -0,0 +1,8 @@
@ECHO OFF
REM Make sure the latest example source file is compiled.
javac -classpath "..\java;..\..\infodynamics.jar" "..\java\infodynamics\demos\autoanalysis\AutoAnalyserAIS.java"
REM Run the example:
java -classpath "..\java;..\..\infodynamics.jar" infodynamics.demos.autoanalysis.AutoAnalyserAIS

View File

@ -0,0 +1,8 @@
#!/bin/bash
# Make sure the latest example source file is compiled.
javac -classpath "../java:../../infodynamics.jar" "../java/infodynamics/demos/autoanalysis/AutoAnalyserAIS.java"
# Run the example:
java -classpath "../java:../../infodynamics.jar" infodynamics.demos.autoanalysis.AutoAnalyserAIS

View File

@ -3,6 +3,8 @@
# Make sure the latest example source file is compiled.
javac -classpath "../java:../../infodynamics.jar" "../java/infodynamics/demos/autoanalysis/GeneratedCalculator.java"
# Run the example:
java -classpath "../java:../../infodynamics.jar" infodynamics.demos.autoanalysis.GeneratedCalculator
if [ $? == 0 ]; then
# Run the example:
java -classpath "../java:../../infodynamics.jar" infodynamics.demos.autoanalysis.GeneratedCalculator
fi

View File

@ -0,0 +1,8 @@
@ECHO OFF
REM Make sure the latest example source file is compiled.
javac -classpath "..\java;..\..\infodynamics.jar" "..\java\infodynamics\demos\autoanalysis\AutoAnalyserCMI.java"
REM Run the example:
java -classpath "..\java;..\..\infodynamics.jar" infodynamics.demos.autoanalysis.AutoAnalyserCMI

View File

@ -0,0 +1,8 @@
#!/bin/bash
# Make sure the latest example source file is compiled.
javac -classpath "../java:../../infodynamics.jar" "../java/infodynamics/demos/autoanalysis/AutoAnalyserCMI.java"
# Run the example:
java -classpath "../java:../../infodynamics.jar" infodynamics.demos.autoanalysis.AutoAnalyserCMI

View File

@ -0,0 +1,8 @@
@ECHO OFF
REM Make sure the latest example source file is compiled.
javac -classpath "..\java;..\..\infodynamics.jar" "..\java\infodynamics\demos\autoanalysis\AutoAnalyserCTE.java"
REM Run the example:
java -classpath "..\java;..\..\infodynamics.jar" infodynamics.demos.autoanalysis.AutoAnalyserCTE

View File

@ -0,0 +1,8 @@
#!/bin/bash
# Make sure the latest example source file is compiled.
javac -classpath "../java:../../infodynamics.jar" "../java/infodynamics/demos/autoanalysis/AutoAnalyserCTE.java"
# Run the example:
java -classpath "../java:../../infodynamics.jar" infodynamics.demos.autoanalysis.AutoAnalyserCTE

View File

@ -0,0 +1,8 @@
@ECHO OFF
REM Make sure the latest example source file is compiled.
javac -classpath "..\java;..\..\infodynamics.jar" "..\java\infodynamics\demos\autoanalysis\AutoAnalyserEntropy.java"
REM Run the example:
java -classpath "..\java;..\..\infodynamics.jar" infodynamics.demos.autoanalysis.AutoAnalyserEntropy

View File

@ -0,0 +1,8 @@
#!/bin/bash
# Make sure the latest example source file is compiled.
javac -classpath "../java:../../infodynamics.jar" "../java/infodynamics/demos/autoanalysis/AutoAnalyserEntropy.java"
# Run the example:
java -classpath "../java:../../infodynamics.jar" infodynamics.demos.autoanalysis.AutoAnalyserEntropy

View File

@ -45,6 +45,7 @@
(.setObservations teCalc sourceArray destArray)
; For copied source, should give something close to expected value for correlated Gaussians:
; TODO The analytic result quoted here isn't quite right, see e.g. octave demos (can't be bothered fixing here...)
(println "TE result " (.computeAverageLocalOfObservations teCalc)
" expected to be close to " (/ (Math/log (/ 1 (- 1 (* covariance covariance)))) (Math/log 2))
" for these correlated Gaussians but biased upward")

View File

@ -49,6 +49,7 @@
; data is a set of random variables) - the result will be of the order
; of what we expect, but not exactly equal to it; in fact, there will
; be a large variance around it.
; TODO The analytic result quoted here isn't quite right, see e.g. octave demos (can't be bothered fixing here...)
(println "TE result " (.computeAverageLocalOfObservations teCalc)
" nats expected to be close to " (Math/log (/ 1 (- 1 (* covariance covariance))))
" nats for these correlated Gaussians")

View File

@ -1,4 +1,4 @@
# First column is random; Second column is parity of its two previous values and previous value of first column
% First column is random; Second column is parity of its two previous values and previous value of first column
1 0
0 1
1 1

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
# Two random columns of data in [0..1) each
% Two random columns of data in [0..1) each
2.97438473e-01 1.58412125e-01
3.72029104e-02 7.49930621e-01
4.30819357e-01 4.23417397e-01

View File

@ -58,11 +58,16 @@ public class Example3TeContinuousDataKernel {
teCalc.setProperty("NORMALISE", "true"); // Normalise the individual variables (default)
teCalc.initialise(1, 0.5); // Use history length 1 (Schreiber k=1), kernel width of 0.5 normalised units
teCalc.setObservations(sourceArray, destArray);
// For copied source, should give something close to expected value for correlated Gaussians:
double result = teCalc.computeAverageLocalOfObservations();
// For copied source, should give something close to expected value for correlated Gaussians:.
// Expected correlation is expected covariance / product of expected standard deviations:
// (where square of destArray standard dev is sum of squares of std devs of
// underlying distributions)
double corr_expected = covariance /
(1.0 * Math.sqrt(Math.pow(covariance,2) + Math.pow(1-covariance,2)));
System.out.printf("TE result %.4f bits; expected to be close to " +
"%.4f bits for these correlated Gaussians but biased upwards\n",
result, Math.log(1.0/(1-Math.pow(covariance,2)))/Math.log(2));
result, -0.5 * Math.log(1-Math.pow(corr_expected,2))/Math.log(2));
teCalc.initialise(); // Initialise leaving the parameters the same
teCalc.setObservations(sourceArray2, destArray);

View File

@ -65,9 +65,14 @@ public class Example4TeContinuousDataKraskov {
// data is a set of random variables) - the result will be of the order
// of what we expect, but not exactly equal to it; in fact, there will
// be a large variance around it.
// Expected correlation is expected covariance / product of expected standard deviations:
// (where square of destArray standard dev is sum of squares of std devs of
// underlying distributions)
double corr_expected = covariance /
(1.0 * Math.sqrt(Math.pow(covariance,2) + Math.pow(1-covariance,2)));
System.out.printf("TE result %.4f nats; expected to be close to " +
"%.4f nats for these correlated Gaussians\n",
result, Math.log(1.0/(1-Math.pow(covariance,2))));
result, -0.5 * Math.log(1-Math.pow(corr_expected,2)));
// Perform calculation with uncorrelated source:
teCalc.initialise(); // Initialise leaving the parameters the same

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,370 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2015, 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.demos.autoanalysis;
import infodynamics.measures.continuous.ActiveInfoStorageCalculator;
import infodynamics.measures.continuous.InfoMeasureCalculatorContinuous;
import infodynamics.measures.continuous.gaussian.ActiveInfoStorageCalculatorGaussian;
import infodynamics.measures.continuous.kernel.ActiveInfoStorageCalculatorKernel;
import infodynamics.measures.continuous.kernel.MutualInfoCalculatorMultiVariateKernel;
import infodynamics.measures.continuous.kraskov.ActiveInfoStorageCalculatorKraskov;
import infodynamics.measures.continuous.kraskov.MutualInfoCalculatorMultiVariateKraskov;
import infodynamics.measures.discrete.ActiveInformationCalculatorDiscrete;
import infodynamics.measures.discrete.InfoMeasureCalculatorDiscrete;
import infodynamics.utils.MatrixUtils;
import java.util.Vector;
import javax.swing.JOptionPane;
/**
* This class provides a GUI to build a simple calculation
* of active information storage,
* and supply the code to execute it.
*
*
* @author Joseph Lizier
*
*/
public class AutoAnalyserAIS extends AutoAnalyser {
/**
* Need serialVersionUID to be serializable
*/
private static final long serialVersionUID = 1L;
protected static final String DISCRETE_PROPNAME_K = "k_HISTORY";
// Property names for specific continuous calculators:
protected String[] gaussianProperties;
protected String[] gaussianPropertiesFieldNames;
protected String[] gaussianPropertyDescriptions;
protected String[] kernelProperties;
protected String[] kernelPropertiesFieldNames;
protected String[] kernelPropertyDescriptions;
protected String[] kraskovProperties;
protected String[] kraskovPropertiesFieldNames;
protected String[] kraskovPropertyDescriptions;
/**
* Constructor to initialise the GUI for a channel calculator
*/
protected void makeSpecificInitialisations() {
numVariables = 1;
variableColNumLabels = new String[] {"Variable"};
useAllCombosCheckBox = true;
useStatSigCheckBox = true;
wordForCombinations = "variables";
variableRelationshipFormatString = "col_%d";
disableVariableColTextFieldsForAllCombos = new boolean[] {true};
indentsForAllCombos = 1;
// Set up the properties for Entropy:
measureAcronym = "AIS";
appletTitle = "JIDT Active Information Storage Auto-Analyser";
calcTypes = new String[] {
CALC_TYPE_DISCRETE, CALC_TYPE_BINNED, CALC_TYPE_GAUSSIAN,
CALC_TYPE_KRASKOV, CALC_TYPE_KERNEL};
unitsForEachCalc = new String[] {"bits", "bits", "nats", "nats", "bits"};
// Discrete:
discreteClass = ActiveInformationCalculatorDiscrete.class;
discreteProperties = new String[] {
DISCRETE_PROPNAME_BASE,
DISCRETE_PROPNAME_K
};
discretePropertyDefaultValues = new String[] {
"2",
"1"
};
discretePropertyDescriptions = new String[] {
"Number of discrete states available for each variable (i.e. 2 for binary)",
"History embedding length (k_HISTORY)"
};
// Continuous:
abstractContinuousClass = ActiveInfoStorageCalculator.class;
// Common properties for all continuous calcs:
commonContPropertyNames = new String[] {
ActiveInfoStorageCalculator.K_PROP_NAME,
ActiveInfoStorageCalculator.TAU_PROP_NAME
};
commonContPropertiesFieldNames = new String[] {
"K_PROP_NAME",
"TAU_PROP_NAME"
};
commonContPropertyDescriptions = new String[] {
"History embedding length (k_HISTORY)",
"History embedding delay (k_TAU)"
};
// Gaussian properties:
gaussianProperties = new String[] {
};
gaussianPropertiesFieldNames = new String[] {
};
gaussianPropertyDescriptions = new String[] {
};
// Kernel:
kernelProperties = new String[] {
MutualInfoCalculatorMultiVariateKernel.KERNEL_WIDTH_PROP_NAME,
MutualInfoCalculatorMultiVariateKernel.DYN_CORR_EXCL_TIME_NAME,
MutualInfoCalculatorMultiVariateKernel.NORMALISE_PROP_NAME,
};
kernelPropertiesFieldNames = new String[] {
"KERNEL_WIDTH_PROP_NAME",
"DYN_CORR_EXCL_TIME_NAME",
"NORMALISE_PROP_NAME"
};
kernelPropertyDescriptions = new String[] {
"Kernel width to be used in the calculation. <br/>If the property " +
MutualInfoCalculatorMultiVariateKernel.NORMALISE_PROP_NAME +
" is set, then this is a number of standard deviations; " +
"otherwise it is an absolute value.",
"Dynamic correlation exclusion time or <br/>Theiler window (see Kantz and Schreiber); " +
"0 (default) means no dynamic exclusion window",
"(boolean) whether to normalise <br/>each incoming time-series to mean 0, standard deviation 1, or not (recommended)",
};
// KSG (Kraskov):
kraskovProperties = new String[] {
MutualInfoCalculatorMultiVariateKraskov.PROP_NORMALISE,
MutualInfoCalculatorMultiVariateKraskov.PROP_K,
MutualInfoCalculatorMultiVariateKraskov.PROP_ADD_NOISE,
MutualInfoCalculatorMultiVariateKraskov.PROP_DYN_CORR_EXCL_TIME,
MutualInfoCalculatorMultiVariateKraskov.PROP_NORM_TYPE,
MutualInfoCalculatorMultiVariateKraskov.PROP_NUM_THREADS,
ActiveInfoStorageCalculatorKraskov.PROP_AUTO_EMBED_METHOD,
ActiveInfoStorageCalculatorKraskov.PROP_K_SEARCH_MAX,
ActiveInfoStorageCalculatorKraskov.PROP_TAU_SEARCH_MAX,
ActiveInfoStorageCalculatorKraskov.PROP_RAGWITZ_NUM_NNS,
};
kraskovPropertiesFieldNames = new String[] {
"MutualInfoCalculatorMultiVariateKraskov.PROP_NORMALISE",
"MutualInfoCalculatorMultiVariateKraskov.PROP_K",
"MutualInfoCalculatorMultiVariateKraskov.PROP_ADD_NOISE",
"MutualInfoCalculatorMultiVariateKraskov.PROP_DYN_CORR_EXCL_TIME",
"MutualInfoCalculatorMultiVariateKraskov.PROP_NORM_TYPE",
"MutualInfoCalculatorMultiVariateKraskov.PROP_NUM_THREADS",
"PROP_AUTO_EMBED_METHOD",
"PROP_K_SEARCH_MAX",
"PROP_TAU_SEARCH_MAX",
"PROP_RAGWITZ_NUM_NNS"
};
kraskovPropertyDescriptions = new String[] {
"(boolean) whether to normalise <br/>each incoming time-series to mean 0, standard deviation 1, or not (recommended)",
"Number of k nearest neighbours to use <br/>in the full joint kernel space in the KSG algorithm",
"Standard deviation for an amount <br/>of random Gaussian noise to add to each variable, " +
"to avoid having neighbourhoods with artificially large counts. <br/>" +
"(\"false\" may be used to indicate \"0\".). The amount is added in after any normalisation.",
"Dynamic correlation exclusion time or <br/>Theiler window (see Kantz and Schreiber); " +
"0 (default) means no dynamic exclusion window",
"<br/>Norm type to use in KSG algorithm between the points in each marginal space. <br/>Options are: " +
"\"MAX_NORM\" (default), otherwise \"EUCLIDEAN\" or \"EUCLIDEAN_SQUARED\" (both equivalent here)",
"Number of parallel threads to use <br/>in computation: an integer > 0 or \"USE_ALL\" " +
"(default, to indicate to use all available processors)",
"Method to automatically determine embedding length (k_HISTORY)<br/> and delay (k_TAU) for " +
"the samples. Default is \"" + ActiveInfoStorageCalculatorKraskov.AUTO_EMBED_METHOD_NONE +
"\" meaning values are set manually; other values include: <br/> -- \"" + ActiveInfoStorageCalculatorKraskov.AUTO_EMBED_METHOD_RAGWITZ +
"\" for use of the Ragwitz criteria for both source and destination (searching up to \"" + ActiveInfoStorageCalculatorKraskov.PROP_K_SEARCH_MAX +
"\" and \"" + ActiveInfoStorageCalculatorKraskov.PROP_TAU_SEARCH_MAX + "\"); <br/> -- \"" + ActiveInfoStorageCalculatorKraskov.AUTO_EMBED_METHOD_MAX_CORR_AIS +
"\" for maximising the (bias corrected) Active Info Storage (searching up to \"" + ActiveInfoStorageCalculatorKraskov.PROP_K_SEARCH_MAX +
"\" and \"" + ActiveInfoStorageCalculatorKraskov.PROP_TAU_SEARCH_MAX + "\"); <br/>Use of values other than \"" + ActiveInfoStorageCalculatorKraskov.AUTO_EMBED_METHOD_NONE +
"\" leads to any previous settings for embedding lengths and delays to be overwritten after observations are supplied",
"Max. embedding length to search to <br/>if auto embedding (as determined by " + ActiveInfoStorageCalculatorKraskov.PROP_AUTO_EMBED_METHOD + ")",
"Max. embedding delay to search to <br/>if auto embedding (as determined by " + ActiveInfoStorageCalculatorKraskov.PROP_AUTO_EMBED_METHOD + ")",
"Number of k nearest neighbours for <br/>Ragwitz auto embedding (if used; defaults to match property \"k\")"
};
}
@Override
protected void fillOutAllCombinations(Vector<int[]> variableCombinations) {
// All combinations here means all variables
for (int s = 0; s < dataColumns; s++) {
variableCombinations.add(new int[] {s});
}
}
@Override
protected String[] setUpLoopsForAllCombos(StringBuffer javaCode,
StringBuffer pythonCode, StringBuffer matlabCode) {
// Set up loops in the code:
// 1. Java code
javaCode.append(" \n");
javaCode.append(" // Compute for all variables:\n");
javaCode.append(" for (int v = 0; v < " + dataColumns +
"; v++) {\n");
String javaPrefix = " ";
javaCode.append(javaPrefix + "// For each variable:\n");
// 2. Python code
pythonCode.append("\n");
pythonCode.append("# Compute for all variables:\n");
pythonCode.append("for v in range(" + dataColumns + "):\n");
String pythonPrefix = " ";
pythonCode.append(pythonPrefix+ "# For each variable:\n");
// 3. Matlab code
matlabCode.append("\n");
matlabCode.append("% Compute for all variables:\n");
matlabCode.append("for v = 1:" + dataColumns + "\n");
String matlabPrefix = "\t";
matlabCode.append(matlabPrefix + "% For each variable:\n");
// Return the variables to index each column:
return new String[] {"v"};
}
@Override
protected void finaliseLoopsForAllCombos(StringBuffer javaCode,
StringBuffer pythonCode, StringBuffer matlabCode) {
// 1. Java code
javaCode.append(" }\n");
// 2. Python code
// Nothing to do
// 3. Matlab code
matlabCode.append("end\n");
}
@Override
protected String formatStringWithColumnNumbers(String formatStr, int[] columnNumbers) {
// We format the variable number into the
// return string here:
return String.format(formatStr,
columnNumbers[0]);
}
@Override
protected boolean skipColumnCombo(int[] columnCombo) {
// No reason to skip any columns here
return false;
}
@Override
protected void setObservations(InfoMeasureCalculatorDiscrete calcDiscrete,
InfoMeasureCalculatorContinuous calcContinuous,
int[] columnCombo) throws Exception {
String selectedCalcType = (String)
calcTypeComboBox.getSelectedItem();
int variableColumn = columnCombo[0];
// Set observations
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
ActiveInformationCalculatorDiscrete calc = (ActiveInformationCalculatorDiscrete) calcDiscrete;
calc.addObservations(
MatrixUtils.selectColumn(dataDiscrete, variableColumn));
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_BINNED)) {
ActiveInformationCalculatorDiscrete calc = (ActiveInformationCalculatorDiscrete) calcDiscrete;
calc.addObservations(
MatrixUtils.discretise(
MatrixUtils.selectColumn(data, variableColumn),
// Should be no parse error on the alphabet size by now
Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE))));
} else {
ActiveInfoStorageCalculator calc = (ActiveInfoStorageCalculator) calcContinuous;
calc.setObservations(
MatrixUtils.selectColumn(data, variableColumn));
}
}
protected CalcProperties assignCalcProperties(String selectedCalcType)
throws Exception {
// Let the super class handle discrete calculators
CalcProperties calcProperties = super.assignCalcProperties(selectedCalcType);
if (calcProperties == null) {
// We need to assign properties for a continuous calculator
calcProperties = new CalcProperties();
calcProperties.calc = assignCalcObjectContinuous(selectedCalcType);
calcProperties.calcClass = calcProperties.calc.getClass();
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) {
calcProperties.classSpecificPropertyNames = gaussianProperties;
calcProperties.classSpecificPropertiesFieldNames = gaussianPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = gaussianPropertyDescriptions;
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KRASKOV)) {
calcProperties.classSpecificPropertyNames = kraskovProperties;
calcProperties.classSpecificPropertiesFieldNames = kraskovPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = kraskovPropertyDescriptions;
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KERNEL)) {
calcProperties.classSpecificPropertyNames = kernelProperties;
calcProperties.classSpecificPropertiesFieldNames = kernelPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = kernelPropertyDescriptions;
} else {
calcProperties = null;
throw new Exception("No recognised calculator selected: " +
selectedCalcType);
}
}
return calcProperties;
}
/**
* Method to assign and initialise our continuous calculator class
*/
@Override
protected ActiveInfoStorageCalculator assignCalcObjectContinuous(String selectedCalcType) throws Exception {
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) {
return new ActiveInfoStorageCalculatorGaussian();
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KRASKOV)) {
return new ActiveInfoStorageCalculatorKraskov();
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KERNEL)) {
return new ActiveInfoStorageCalculatorKernel();
} else {
throw new Exception("No recognised continuous calculator selected: " +
selectedCalcType);
}
}
/**
* Method to assign and initialise our discrete calculator class
*/
protected DiscreteCalcAndArguments assignCalcObjectDiscrete() throws Exception {
int base, k;
try {
String basePropValueStr = propertyValues.get(DISCRETE_PROPNAME_BASE);
base = Integer.parseInt(basePropValueStr);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this,
ex.getMessage());
resultsLabel.setText("Cannot read a value for property " + DISCRETE_PROPNAME_BASE);
return null;
}
try {
String kPropValueStr = propertyValues.get(DISCRETE_PROPNAME_K);
k = Integer.parseInt(kPropValueStr);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this,
ex.getMessage());
resultsLabel.setText("Cannot read a value for property " + DISCRETE_PROPNAME_K);
return null;
}
return new DiscreteCalcAndArguments(
new ActiveInformationCalculatorDiscrete(base, k),
base,
base + ", " + k);
}
/**
* @param args
*/
public static void main(String[] args) {
new AutoAnalyserAIS();
}
}

View File

@ -0,0 +1,364 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2015, 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.demos.autoanalysis;
import infodynamics.measures.continuous.ConditionalMutualInfoCalculatorMultiVariate;
import infodynamics.measures.continuous.ConditionalMutualInfoMultiVariateCommon;
import infodynamics.measures.continuous.InfoMeasureCalculatorContinuous;
import infodynamics.measures.continuous.gaussian.ConditionalMutualInfoCalculatorMultiVariateGaussian;
import infodynamics.measures.continuous.kraskov.ConditionalMutualInfoCalculatorMultiVariateKraskov;
import infodynamics.measures.continuous.kraskov.ConditionalMutualInfoCalculatorMultiVariateKraskov1;
import infodynamics.measures.continuous.kraskov.ConditionalMutualInfoCalculatorMultiVariateKraskov2;
import infodynamics.measures.discrete.ConditionalMutualInformationCalculatorDiscrete;
import infodynamics.measures.discrete.InfoMeasureCalculatorDiscrete;
import infodynamics.utils.MatrixUtils;
import javax.swing.JOptionPane;
import javax.swing.event.DocumentListener;
import java.awt.event.ActionListener;
import java.awt.event.MouseListener;
import java.util.Vector;
/**
* This class provides a GUI to build a simple conditional mutual information calculation,
* and supply the code to execute it.
*
*
* @author Joseph Lizier
*
*/
public class AutoAnalyserCMI extends AutoAnalyser
implements ActionListener, DocumentListener, MouseListener {
/**
* Need serialVersionUID to be serializable
*/
private static final long serialVersionUID = 1L;
// Property names for specific continuous calculators:
protected String[] gaussianProperties;
protected String[] gaussianPropertiesFieldNames;
protected String[] gaussianPropertyDescriptions;
protected String[] kraskovProperties;
protected String[] kraskovPropertiesFieldNames;
protected String[] kraskovPropertyDescriptions;
protected static final String CALC_TYPE_KRASKOV_ALG1 = CALC_TYPE_KRASKOV + " alg. 1";
protected static final String CALC_TYPE_KRASKOV_ALG2 = CALC_TYPE_KRASKOV + " alg. 2";
/**
* Constructor to initialise the GUI for CMI
*/
protected void makeSpecificInitialisations() {
numVariables = 3;
variableColNumLabels = new String[] {"Source", "Destination", "Conditional"};
useAllCombosCheckBox = true;
useStatSigCheckBox = true;
wordForCombinations = "pairs";
variableRelationshipFormatString = "col_%d -> col_%d | col_%d";
disableVariableColTextFieldsForAllCombos = new boolean[] {true, true, false};
indentsForAllCombos = 2;
// Set up the properties for CMI:
measureAcronym = "CMI";
appletTitle = "JIDT Conditional MI Auto-Analyser";
calcTypes = new String[] {
CALC_TYPE_DISCRETE, CALC_TYPE_BINNED, CALC_TYPE_GAUSSIAN,
CALC_TYPE_KRASKOV_ALG1, CALC_TYPE_KRASKOV_ALG2};
// No kernel calculator defined for CMI (yet, and unlikely to happen)
unitsForEachCalc = new String[] {"bits", "bits", "nats", "nats", "nats"};
// Discrete:
discreteClass = ConditionalMutualInformationCalculatorDiscrete.class;
discreteProperties = new String[] {
DISCRETE_PROPNAME_BASE
};
discretePropertyDefaultValues = new String[] {
"2"
};
discretePropertyDescriptions = new String[] {
"Number of discrete states available for each variable (i.e. 2 for binary).<br/>" +
"Can be set individually for each variable -- see code."
};
// Continuous:
abstractContinuousClass = ConditionalMutualInfoCalculatorMultiVariate.class;
// Common properties for all continuous calcs:
commonContPropertyNames = new String[] {
// None
};
commonContPropertiesFieldNames = new String[] {
// None
};
commonContPropertyDescriptions = new String[] {
// None
};
// Gaussian properties:
gaussianProperties = new String[] {
};
gaussianPropertiesFieldNames = new String[] {
};
gaussianPropertyDescriptions = new String[] {
};
// KSG (Kraskov):
kraskovProperties = new String[] {
ConditionalMutualInfoMultiVariateCommon.PROP_NORMALISE,
ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_K,
ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_ADD_NOISE,
ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_DYN_CORR_EXCL_TIME,
ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NORM_TYPE,
ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NUM_THREADS,
};
kraskovPropertiesFieldNames = new String[] {
"ConditionalMutualInfoMultiVariateCommon.PROP_NORMALISE",
"ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_K",
"ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_ADD_NOISE",
"ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_DYN_CORR_EXCL_TIME",
"ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NORM_TYPE",
"ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NUM_THREADS",
};
kraskovPropertyDescriptions = new String[] {
"(boolean) whether to normalise <br/>each incoming time-series to mean 0, standard deviation 1, or not (recommended)",
"Number of k nearest neighbours to use <br/>in the full joint kernel space in the KSG algorithm",
"Standard deviation for an amount <br/>of random Gaussian noise to add to each variable, " +
"to avoid having neighbourhoods with artificially large counts. <br/>" +
"(\"false\" may be used to indicate \"0\".). The amount is added in after any normalisation.",
"Dynamic correlation exclusion time or <br/>Theiler window (see Kantz and Schreiber); " +
"0 (default) means no dynamic exclusion window",
"<br/>Norm type to use in KSG algorithm between the points in each marginal space. <br/>Options are: " +
"\"MAX_NORM\" (default), otherwise \"EUCLIDEAN\" or \"EUCLIDEAN_SQUARED\" (both equivalent here)",
"Number of parallel threads to use <br/>in computation: an integer > 0 or \"USE_ALL\" " +
"(default, to indicate to use all available processors)",
};
}
@Override
protected void fillOutAllCombinations(Vector<int[]> variableCombinations) throws Exception {
// All combinations here means all pairs of sources and destinations,
// with the conditional fixed.
int conditional = Integer.parseInt(variableColTextFields[2].getText());
if (conditional >= dataColumns) {
throw new Exception(String.format("%s column must be between 0 and %d for this data set",
variableColNumLabels[2], dataColumns-1));
}
for (int s = 0; s < dataColumns; s++) {
for (int d = 0; d < dataColumns; d++) {
variableCombinations.add(new int[] {s, d, conditional});
}
}
}
@Override
protected String[] setUpLoopsForAllCombos(StringBuffer javaCode,
StringBuffer pythonCode, StringBuffer matlabCode) {
// Set up loops in the code:
int conditional = Integer.parseInt(variableColTextFields[2].getText());
// 1. Java code
javaCode.append(" \n");
javaCode.append(" int c = " + conditional + ";\n");
javaCode.append(" // Compute for all source-destination pairs:\n");
javaCode.append(" for (int s = 0; s < " + dataColumns +
"; s++) {\n");
javaCode.append(" for (int d = 0; d < " + dataColumns +
"; d++) {\n");
String javaPrefix = " ";
javaCode.append(javaPrefix + "// For each source-dest pair (given conditional):\n");
javaCode.append(javaPrefix + "if ((s == d) || (s == c) || (d == c)) {\n");
javaCode.append(javaPrefix + " continue;\n");
javaCode.append(javaPrefix + "}\n");
// 2. Python code
pythonCode.append("\n");
pythonCode.append("c = " + conditional + "\n");
pythonCode.append("# Compute for all pairs:\n");
pythonCode.append("for s in range(" + dataColumns + "):\n");
pythonCode.append(" for d in range(" + dataColumns + "):\n");
String pythonPrefix = " ";
pythonCode.append(pythonPrefix+ "# For each source-dest pair (given conditional):\n");
pythonCode.append(pythonPrefix + "if ((s == d) or (s == c) or (d == c)):\n");
pythonCode.append(pythonPrefix + " continue\n");
// 3. Matlab code
matlabCode.append("\n");
matlabCode.append("c = " + (conditional+1) + ";\n");
matlabCode.append("% Compute for all pairs:\n");
matlabCode.append("for s = 1:" + dataColumns + "\n");
matlabCode.append("\tfor d = 1:" + dataColumns + "\n");
String matlabPrefix = "\t\t";
matlabCode.append(matlabPrefix + "% For each source-dest pair (given conditional):\n");
matlabCode.append(matlabPrefix + "if ((s == d) || (s == c) || (d == c))\n");
matlabCode.append(matlabPrefix + "\tcontinue;\n");
matlabCode.append(matlabPrefix + "end\n");
// Return the variables to index each column:
return new String[] {"s", "d", "c"};
}
@Override
protected void finaliseLoopsForAllCombos(StringBuffer javaCode,
StringBuffer pythonCode, StringBuffer matlabCode) {
// 1. Java code
javaCode.append(" }\n");
javaCode.append(" }\n");
// 2. Python code
// Nothing to do
// 3. Matlab code
matlabCode.append("\tend\n");
matlabCode.append("end\n");
}
@Override
protected String formatStringWithColumnNumbers(String formatStr, int[] columnNumbers) {
// We format the source and target variable numbers into the
// return string here:
return String.format(formatStr,
columnNumbers[0], columnNumbers[1], columnNumbers[2]);
}
@Override
protected boolean skipColumnCombo(int[] columnCombo) {
if ((columnCombo[0] == columnCombo[1]) ||
(columnCombo[0] == columnCombo[2]) ||
(columnCombo[1] == columnCombo[2])) {
// Two columns are the same here,
// so don't compute the conditional MI
return true;
}
return false;
}
@Override
protected void setObservations(InfoMeasureCalculatorDiscrete calcDiscrete,
InfoMeasureCalculatorContinuous calcContinuous,
int[] columnCombo) throws Exception {
String selectedCalcType = (String)
calcTypeComboBox.getSelectedItem();
int sourceColumn = columnCombo[0];
int destColumn = columnCombo[1];
int condColumn = columnCombo[2];
// Set observations
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
ConditionalMutualInformationCalculatorDiscrete cmiCalc =
(ConditionalMutualInformationCalculatorDiscrete) calcDiscrete;
cmiCalc.addObservations(
MatrixUtils.selectColumn(dataDiscrete, sourceColumn),
MatrixUtils.selectColumn(dataDiscrete, destColumn),
MatrixUtils.selectColumn(dataDiscrete, condColumn));
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_BINNED)) {
ConditionalMutualInformationCalculatorDiscrete cmiCalc =
(ConditionalMutualInformationCalculatorDiscrete) calcDiscrete;
cmiCalc.addObservations(
MatrixUtils.discretise(
MatrixUtils.selectColumn(data, sourceColumn),
Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE))),
MatrixUtils.discretise(
MatrixUtils.selectColumn(data, destColumn),
Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE))),
MatrixUtils.discretise(
MatrixUtils.selectColumn(data, condColumn),
Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE))));
} else {
ConditionalMutualInfoCalculatorMultiVariate cmiCalc =
(ConditionalMutualInfoCalculatorMultiVariate) calcContinuous;
cmiCalc.setObservations(
MatrixUtils.selectColumn(data, sourceColumn),
MatrixUtils.selectColumn(data, destColumn),
MatrixUtils.selectColumn(data, condColumn));
}
}
protected CalcProperties assignCalcProperties(String selectedCalcType)
throws Exception {
// Let the super class handle discrete calculators
CalcProperties calcProperties = super.assignCalcProperties(selectedCalcType);
if (calcProperties == null) {
// We need to assign properties for a continuous calculator
calcProperties = new CalcProperties();
calcProperties.calc = assignCalcObjectContinuous(selectedCalcType);
calcProperties.calcClass = calcProperties.calc.getClass();
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) {
calcProperties.classSpecificPropertyNames = gaussianProperties;
calcProperties.classSpecificPropertiesFieldNames = gaussianPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = gaussianPropertyDescriptions;
} else if (selectedCalcType.startsWith(CALC_TYPE_KRASKOV)) {
// The if statement will work for both MI Kraskov calculators
calcProperties.classSpecificPropertyNames = kraskovProperties;
calcProperties.classSpecificPropertiesFieldNames = kraskovPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = kraskovPropertyDescriptions;
} else {
calcProperties = null;
throw new Exception("No recognised calculator selected: " +
selectedCalcType);
}
}
return calcProperties;
}
/**
* Method to assign and initialise our continuous calculator class
*/
@Override
protected ConditionalMutualInfoCalculatorMultiVariate assignCalcObjectContinuous(String selectedCalcType) throws Exception {
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) {
return new ConditionalMutualInfoCalculatorMultiVariateGaussian();
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KRASKOV_ALG1)) {
return new ConditionalMutualInfoCalculatorMultiVariateKraskov1();
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KRASKOV_ALG2)) {
return new ConditionalMutualInfoCalculatorMultiVariateKraskov2();
} else {
throw new Exception("No recognised continuous calculator selected: " +
selectedCalcType);
}
}
/**
* Method to assign and initialise our discrete calculator class
*/
protected DiscreteCalcAndArguments assignCalcObjectDiscrete() throws Exception {
int base;
try {
String basePropValueStr = propertyValues.get(DISCRETE_PROPNAME_BASE);
base = Integer.parseInt(basePropValueStr);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this,
ex.getMessage());
resultsLabel.setText("Cannot find a value for property " + DISCRETE_PROPNAME_BASE);
return null;
}
return new DiscreteCalcAndArguments(
new ConditionalMutualInformationCalculatorDiscrete(base, base, base),
base,
base + ", " + base + ", " + base);
}
/**
* @param args
*/
public static void main(String[] args) {
new AutoAnalyserCMI();
}
}

View File

@ -0,0 +1,397 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2015, 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.demos.autoanalysis;
import infodynamics.measures.continuous.ConditionalMutualInfoMultiVariateCommon;
import infodynamics.measures.continuous.ConditionalTransferEntropyCalculator;
import infodynamics.measures.continuous.InfoMeasureCalculatorContinuous;
import infodynamics.measures.continuous.gaussian.ConditionalTransferEntropyCalculatorGaussian;
import infodynamics.measures.continuous.kraskov.ConditionalMutualInfoCalculatorMultiVariateKraskov;
import infodynamics.measures.continuous.kraskov.ConditionalTransferEntropyCalculatorKraskov;
import infodynamics.measures.discrete.ConditionalTransferEntropyCalculatorDiscrete;
import infodynamics.measures.discrete.InfoMeasureCalculatorDiscrete;
import infodynamics.utils.MatrixUtils;
import javax.swing.JOptionPane;
import javax.swing.event.DocumentListener;
import java.awt.event.ActionListener;
import java.awt.event.MouseListener;
import java.util.Vector;
/**
* This class provides a GUI to build a simple conditional transfer entropy calculation,
* and supply the code to execute it.
*
*
* @author Joseph Lizier
*
*/
public class AutoAnalyserCTE extends AutoAnalyser
implements ActionListener, DocumentListener, MouseListener {
/**
* Need serialVersionUID to be serializable
*/
private static final long serialVersionUID = 1L;
protected static final String DISCRETE_PROPNAME_K = "k_HISTORY";
// Property names for specific continuous calculators:
protected String[] gaussianProperties;
protected String[] gaussianPropertiesFieldNames;
protected String[] gaussianPropertyDescriptions;
protected String[] kraskovProperties;
protected String[] kraskovPropertiesFieldNames;
protected String[] kraskovPropertyDescriptions;
/**
* Constructor to initialise the GUI for Conditional TE
*/
protected void makeSpecificInitialisations() {
numVariables = 3;
variableColNumLabels = new String[] {"Source", "Destination", "Conditional"};
useAllCombosCheckBox = true;
useStatSigCheckBox = true;
wordForCombinations = "pairs";
variableRelationshipFormatString = "col_%d -> col_%d | col_%d";
disableVariableColTextFieldsForAllCombos = new boolean[] {true, true, false};
indentsForAllCombos = 2;
// Set up the properties for CTE:
measureAcronym = "CTE";
appletTitle = "JIDT Conditional TE Auto-Analyser";
calcTypes = new String[] {
CALC_TYPE_DISCRETE, CALC_TYPE_BINNED, CALC_TYPE_GAUSSIAN,
CALC_TYPE_KRASKOV};
// No kernel calculator defined for CMI (yet, and unlikely to happen)
unitsForEachCalc = new String[] {"bits", "bits", "nats", "nats"};
// Discrete:
discreteClass = ConditionalTransferEntropyCalculatorDiscrete.class;
discreteProperties = new String[] {
DISCRETE_PROPNAME_BASE,
DISCRETE_PROPNAME_K
};
discretePropertyDefaultValues = new String[] {
"2",
"1"
};
discretePropertyDescriptions = new String[] {
"Number of discrete states available for each variable (i.e. 2 for binary).<br/>" +
"Can be set individually for each variable -- see code.",
"Destination history embedding length (k_HISTORY)",
};
// Continuous:
abstractContinuousClass = ConditionalTransferEntropyCalculator.class;
// Common properties for all continuous calcs:
commonContPropertyNames = new String[] {
ConditionalTransferEntropyCalculator.K_PROP_NAME,
ConditionalTransferEntropyCalculator.K_TAU_PROP_NAME,
ConditionalTransferEntropyCalculator.L_PROP_NAME,
ConditionalTransferEntropyCalculator.L_TAU_PROP_NAME,
ConditionalTransferEntropyCalculator.DELAY_PROP_NAME,
ConditionalTransferEntropyCalculator.COND_EMBED_LENGTHS_PROP_NAME,
ConditionalTransferEntropyCalculator.COND_EMBED_DELAYS_PROP_NAME,
ConditionalTransferEntropyCalculator.COND_DELAYS_PROP_NAME
};
commonContPropertiesFieldNames = new String[] {
"K_PROP_NAME",
"K_TAU_PROP_NAME",
"L_PROP_NAME",
"L_TAU_PROP_NAME",
"DELAY_PROP_NAME",
"COND_EMBED_LENGTHS_PROP_NAME",
"COND_EMBED_DELAYS_PROP_NAME",
"COND_DELAYS_PROP_NAME"
};
commonContPropertyDescriptions = new String[] {
"Destination history embedding length (k_HISTORY)",
"Destination history embedding delay (k_TAU)",
"Source history embedding length (l_HISTORY)",
"Source history embeding delay (l_TAU)",
"Delay from source to destination (in time steps)",
"Conditional history embedding length",
"Conditional history embeding delay",
"Delay from conditional to destination (in time steps)"
};
// Gaussian properties:
gaussianProperties = new String[] {
};
gaussianPropertiesFieldNames = new String[] {
};
gaussianPropertyDescriptions = new String[] {
};
// KSG (Kraskov):
kraskovProperties = new String[] {
ConditionalMutualInfoMultiVariateCommon.PROP_NORMALISE,
ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_K,
ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_ADD_NOISE,
ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_DYN_CORR_EXCL_TIME,
ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NORM_TYPE,
ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NUM_THREADS,
ConditionalTransferEntropyCalculatorKraskov.PROP_KRASKOV_ALG_NUM
};
kraskovPropertiesFieldNames = new String[] {
"ConditionalMutualInfoMultiVariateCommon.PROP_NORMALISE",
"ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_K",
"ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_ADD_NOISE",
"ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_DYN_CORR_EXCL_TIME",
"ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NORM_TYPE",
"ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NUM_THREADS",
"PROP_KRASKOV_ALG_NUM"
};
kraskovPropertyDescriptions = new String[] {
"(boolean) whether to normalise <br/>each incoming time-series to mean 0, standard deviation 1, or not (recommended)",
"Number of k nearest neighbours to use <br/>in the full joint kernel space in the KSG algorithm",
"Standard deviation for an amount <br/>of random Gaussian noise to add to each variable, " +
"to avoid having neighbourhoods with artificially large counts. <br/>" +
"(\"false\" may be used to indicate \"0\".). The amount is added in after any normalisation.",
"Dynamic correlation exclusion time or <br/>Theiler window (see Kantz and Schreiber); " +
"0 (default) means no dynamic exclusion window",
"<br/>Norm type to use in KSG algorithm between the points in each marginal space. <br/>Options are: " +
"\"MAX_NORM\" (default), otherwise \"EUCLIDEAN\" or \"EUCLIDEAN_SQUARED\" (both equivalent here)",
"Number of parallel threads to use <br/>in computation: an integer > 0 or \"USE_ALL\" " +
"(default, to indicate to use all available processors)",
"Which KSG algorithm to use (1 or 2)",
};
}
@Override
protected void fillOutAllCombinations(Vector<int[]> variableCombinations) throws Exception {
// All combinations here means all pairs of sources and destinations,
// with the conditional fixed.
int conditional = Integer.parseInt(variableColTextFields[2].getText());
if (conditional >= dataColumns) {
throw new Exception(String.format("%s column must be between 0 and %d for this data set",
variableColNumLabels[2], dataColumns-1));
}
for (int s = 0; s < dataColumns; s++) {
for (int d = 0; d < dataColumns; d++) {
variableCombinations.add(new int[] {s, d, conditional});
}
}
}
@Override
protected String[] setUpLoopsForAllCombos(StringBuffer javaCode,
StringBuffer pythonCode, StringBuffer matlabCode) {
// Set up loops in the code:
int conditional = Integer.parseInt(variableColTextFields[2].getText());
// 1. Java code
javaCode.append(" \n");
javaCode.append(" int c = " + conditional + ";\n");
javaCode.append(" // Compute for all source-destination pairs:\n");
javaCode.append(" for (int s = 0; s < " + dataColumns +
"; s++) {\n");
javaCode.append(" for (int d = 0; d < " + dataColumns +
"; d++) {\n");
String javaPrefix = " ";
javaCode.append(javaPrefix + "// For each source-dest pair (given conditional):\n");
javaCode.append(javaPrefix + "if ((s == d) || (s == c) || (d == c)) {\n");
javaCode.append(javaPrefix + " continue;\n");
javaCode.append(javaPrefix + "}\n");
// 2. Python code
pythonCode.append("\n");
pythonCode.append("c = " + conditional + "\n");
pythonCode.append("# Compute for all pairs:\n");
pythonCode.append("for s in range(" + dataColumns + "):\n");
pythonCode.append(" for d in range(" + dataColumns + "):\n");
String pythonPrefix = " ";
pythonCode.append(pythonPrefix+ "# For each source-dest pair (given conditional):\n");
pythonCode.append(pythonPrefix + "if ((s == d) or (s == c) or (d == c)):\n");
pythonCode.append(pythonPrefix + " continue\n");
// 3. Matlab code
matlabCode.append("\n");
matlabCode.append("c = " + (conditional+1) + ";\n");
matlabCode.append("% Compute for all pairs:\n");
matlabCode.append("for s = 1:" + dataColumns + "\n");
matlabCode.append("\tfor d = 1:" + dataColumns + "\n");
String matlabPrefix = "\t\t";
matlabCode.append(matlabPrefix + "% For each source-dest pair (given conditional):\n");
matlabCode.append(matlabPrefix + "if ((s == d) || (s == c) || (d == c))\n");
matlabCode.append(matlabPrefix + "\tcontinue;\n");
matlabCode.append(matlabPrefix + "end\n");
// Return the variables to index each column:
return new String[] {"s", "d", "c"};
}
@Override
protected void finaliseLoopsForAllCombos(StringBuffer javaCode,
StringBuffer pythonCode, StringBuffer matlabCode) {
// 1. Java code
javaCode.append(" }\n");
javaCode.append(" }\n");
// 2. Python code
// Nothing to do
// 3. Matlab code
matlabCode.append("\tend\n");
matlabCode.append("end\n");
}
@Override
protected String formatStringWithColumnNumbers(String formatStr, int[] columnNumbers) {
// We format the source and target variable numbers into the
// return string here:
return String.format(formatStr,
columnNumbers[0], columnNumbers[1], columnNumbers[2]);
}
@Override
protected boolean skipColumnCombo(int[] columnCombo) {
if ((columnCombo[0] == columnCombo[1]) ||
(columnCombo[0] == columnCombo[2]) ||
(columnCombo[1] == columnCombo[2])) {
// Two columns are the same here,
// so don't compute the conditional TE
return true;
}
return false;
}
@Override
protected void setObservations(InfoMeasureCalculatorDiscrete calcDiscrete,
InfoMeasureCalculatorContinuous calcContinuous,
int[] columnCombo) throws Exception {
String selectedCalcType = (String)
calcTypeComboBox.getSelectedItem();
int sourceColumn = columnCombo[0];
int destColumn = columnCombo[1];
int condColumn = columnCombo[2];
// Set observations
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
ConditionalTransferEntropyCalculatorDiscrete cteCalc =
(ConditionalTransferEntropyCalculatorDiscrete) calcDiscrete;
cteCalc.addObservations(
MatrixUtils.selectColumn(dataDiscrete, sourceColumn),
MatrixUtils.selectColumn(dataDiscrete, destColumn),
MatrixUtils.selectColumn(dataDiscrete, condColumn));
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_BINNED)) {
ConditionalTransferEntropyCalculatorDiscrete cteCalc =
(ConditionalTransferEntropyCalculatorDiscrete) calcDiscrete;
cteCalc.addObservations(
MatrixUtils.discretise(
MatrixUtils.selectColumn(data, sourceColumn),
Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE))),
MatrixUtils.discretise(
MatrixUtils.selectColumn(data, destColumn),
Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE))),
MatrixUtils.discretise(
MatrixUtils.selectColumn(data, condColumn),
Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE))));
} else {
ConditionalTransferEntropyCalculator cteCalc =
(ConditionalTransferEntropyCalculator) calcContinuous;
cteCalc.setObservations(
MatrixUtils.selectColumn(data, sourceColumn),
MatrixUtils.selectColumn(data, destColumn),
MatrixUtils.selectColumn(data, condColumn));
}
}
protected CalcProperties assignCalcProperties(String selectedCalcType)
throws Exception {
// Let the super class handle discrete calculators
CalcProperties calcProperties = super.assignCalcProperties(selectedCalcType);
if (calcProperties == null) {
// We need to assign properties for a continuous calculator
calcProperties = new CalcProperties();
calcProperties.calc = assignCalcObjectContinuous(selectedCalcType);
calcProperties.calcClass = calcProperties.calc.getClass();
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) {
calcProperties.classSpecificPropertyNames = gaussianProperties;
calcProperties.classSpecificPropertiesFieldNames = gaussianPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = gaussianPropertyDescriptions;
} else if (selectedCalcType.startsWith(CALC_TYPE_KRASKOV)) {
// The if statement will work for both MI Kraskov calculators
calcProperties.classSpecificPropertyNames = kraskovProperties;
calcProperties.classSpecificPropertiesFieldNames = kraskovPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = kraskovPropertyDescriptions;
} else {
calcProperties = null;
throw new Exception("No recognised calculator selected: " +
selectedCalcType);
}
}
return calcProperties;
}
/**
* Method to assign and initialise our continuous calculator class
*/
@Override
protected ConditionalTransferEntropyCalculator assignCalcObjectContinuous(String selectedCalcType) throws Exception {
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) {
return new ConditionalTransferEntropyCalculatorGaussian();
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KRASKOV)) {
return new ConditionalTransferEntropyCalculatorKraskov();
} else {
throw new Exception("No recognised continuous calculator selected: " +
selectedCalcType);
}
}
/**
* Method to assign and initialise our discrete calculator class
*/
protected DiscreteCalcAndArguments assignCalcObjectDiscrete() throws Exception {
int base, k;
try {
String basePropValueStr = propertyValues.get(DISCRETE_PROPNAME_BASE);
base = Integer.parseInt(basePropValueStr);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this,
ex.getMessage());
resultsLabel.setText("Cannot find a value for property " + DISCRETE_PROPNAME_BASE);
return null;
}
try {
String kPropValueStr = propertyValues.get(DISCRETE_PROPNAME_K);
k = Integer.parseInt(kPropValueStr);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this,
ex.getMessage());
resultsLabel.setText("Cannot read a value for property " + DISCRETE_PROPNAME_K);
return null;
}
return new DiscreteCalcAndArguments(
// Initialise always with one other info contributor for now
new ConditionalTransferEntropyCalculatorDiscrete(base, k, 1),
base,
base + ", " + k + ", " + 1);
}
/**
* @param args
*/
public static void main(String[] args) {
new AutoAnalyserCTE();
}
}

View File

@ -0,0 +1,219 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2015, 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.demos.autoanalysis;
import infodynamics.measures.continuous.ChannelCalculator;
import infodynamics.measures.continuous.InfoMeasureCalculatorContinuous;
import infodynamics.measures.discrete.ChannelCalculatorDiscrete;
import infodynamics.measures.discrete.InfoMeasureCalculatorDiscrete;
import infodynamics.utils.MatrixUtils;
import java.util.Vector;
/**
* This abstract class provides a GUI to build a simple calculation
* over a source-target channel,
* and supply the code to execute it.
* Child classes fix this to a TE or MI calculation.
*
*
* @author Joseph Lizier
*
*/
public abstract class AutoAnalyserChannelCalculator extends AutoAnalyser {
/**
* Need serialVersionUID to be serializable
*/
private static final long serialVersionUID = 1L;
// Property names for specific continuous calculators:
protected String[] gaussianProperties;
protected String[] gaussianPropertiesFieldNames;
protected String[] gaussianPropertyDescriptions;
protected String[] kernelProperties;
protected String[] kernelPropertiesFieldNames;
protected String[] kernelPropertyDescriptions;
protected String[] kraskovProperties;
protected String[] kraskovPropertiesFieldNames;
protected String[] kraskovPropertyDescriptions;
/**
* Constructor to initialise the GUI for a channel calculator
*/
protected void makeSpecificInitialisations() {
numVariables = 2;
variableColNumLabels = new String[] {"Source", "Destination"};
useAllCombosCheckBox = true;
useStatSigCheckBox = true;
wordForCombinations = "pairs";
variableRelationshipFormatString = "col_%d -> col_%d";
disableVariableColTextFieldsForAllCombos = new boolean[] {true, true};
indentsForAllCombos = 2;
}
@Override
protected void fillOutAllCombinations(Vector<int[]> variableCombinations) {
// All combinations here means all pairs
for (int s = 0; s < dataColumns; s++) {
for (int d = 0; d < dataColumns; d++) {
variableCombinations.add(new int[] {s, d});
}
}
}
@Override
protected String[] setUpLoopsForAllCombos(StringBuffer javaCode,
StringBuffer pythonCode, StringBuffer matlabCode) {
// Set up loops in the code:
// 1. Java code
javaCode.append(" \n");
javaCode.append(" // Compute for all pairs:\n");
javaCode.append(" for (int s = 0; s < " + dataColumns +
"; s++) {\n");
javaCode.append(" for (int d = 0; d < " + dataColumns +
"; d++) {\n");
String javaPrefix = " ";
javaCode.append(javaPrefix + "// For each source-dest pair:\n");
javaCode.append(javaPrefix + "if (s == d) {\n");
javaCode.append(javaPrefix + " continue;\n");
javaCode.append(javaPrefix + "}\n");
// 2. Python code
pythonCode.append("\n");
pythonCode.append("# Compute for all pairs:\n");
pythonCode.append("for s in range(" + dataColumns + "):\n");
pythonCode.append(" for d in range(" + dataColumns + "):\n");
String pythonPrefix = " ";
pythonCode.append(pythonPrefix+ "# For each source-dest pair:\n");
pythonCode.append(pythonPrefix + "if (s == d):\n");
pythonCode.append(pythonPrefix + " continue\n");
// 3. Matlab code
matlabCode.append("\n");
matlabCode.append("% Compute for all pairs:\n");
matlabCode.append("for s = 1:" + dataColumns + "\n");
matlabCode.append("\tfor d = 1:" + dataColumns + "\n");
String matlabPrefix = "\t\t";
matlabCode.append(matlabPrefix + "% For each source-dest pair:\n");
matlabCode.append(matlabPrefix + "if (s == d)\n");
matlabCode.append(matlabPrefix + "\tcontinue;\n");
matlabCode.append(matlabPrefix + "end\n");
// Return the variables to index each column:
return new String[] {"s", "d"};
}
@Override
protected void finaliseLoopsForAllCombos(StringBuffer javaCode,
StringBuffer pythonCode, StringBuffer matlabCode) {
// 1. Java code
javaCode.append(" }\n");
javaCode.append(" }\n");
// 2. Python code
// Nothing to do
// 3. Matlab code
matlabCode.append("\tend\n");
matlabCode.append("end\n");
}
@Override
protected String formatStringWithColumnNumbers(String formatStr, int[] columnNumbers) {
// We format the source and target variable numbers into the
// return string here:
return String.format(formatStr,
columnNumbers[0], columnNumbers[1]);
}
@Override
protected boolean skipColumnCombo(int[] columnCombo) {
if (columnCombo[0] == columnCombo[1]) {
// source and destination columns are the same here,
// so don't compute the channel measure
return true;
}
return false;
}
@Override
protected void setObservations(InfoMeasureCalculatorDiscrete calcDiscrete,
InfoMeasureCalculatorContinuous calcContinuous,
int[] columnCombo) throws Exception {
String selectedCalcType = (String)
calcTypeComboBox.getSelectedItem();
int sourceColumn = columnCombo[0];
int destColumn = columnCombo[1];
// Set observations
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
ChannelCalculatorDiscrete cc = (ChannelCalculatorDiscrete) calcDiscrete;
cc.addObservations(
MatrixUtils.selectColumn(dataDiscrete, sourceColumn),
MatrixUtils.selectColumn(dataDiscrete, destColumn));
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_BINNED)) {
ChannelCalculatorDiscrete cc = (ChannelCalculatorDiscrete) calcDiscrete;
cc.addObservations(
MatrixUtils.discretise(
MatrixUtils.selectColumn(data, sourceColumn),
Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE))),
MatrixUtils.discretise(
MatrixUtils.selectColumn(data, destColumn),
Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE))));
} else {
ChannelCalculator cc = (ChannelCalculator) calcContinuous;
cc.setObservations(
MatrixUtils.selectColumn(data, sourceColumn),
MatrixUtils.selectColumn(data, destColumn));
}
}
protected CalcProperties assignCalcProperties(String selectedCalcType)
throws Exception {
// Let the super class handle discrete calculators
CalcProperties calcProperties = super.assignCalcProperties(selectedCalcType);
if (calcProperties == null) {
// We need to assign properties for a continuous calculator
calcProperties = new CalcProperties();
calcProperties.calc = assignCalcObjectContinuous(selectedCalcType);
calcProperties.calcClass = calcProperties.calc.getClass();
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) {
calcProperties.classSpecificPropertyNames = gaussianProperties;
calcProperties.classSpecificPropertiesFieldNames = gaussianPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = gaussianPropertyDescriptions;
} else if (selectedCalcType.startsWith(CALC_TYPE_KRASKOV)) {
// The if statement will work for both MI Kraskov calculators
calcProperties.classSpecificPropertyNames = kraskovProperties;
calcProperties.classSpecificPropertiesFieldNames = kraskovPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = kraskovPropertyDescriptions;
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KERNEL)) {
calcProperties.classSpecificPropertyNames = kernelProperties;
calcProperties.classSpecificPropertiesFieldNames = kernelPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = kernelPropertyDescriptions;
} else {
calcProperties = null;
throw new Exception("No recognised calculator selected: " +
selectedCalcType);
}
}
return calcProperties;
}
}

View File

@ -0,0 +1,318 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2015, 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.demos.autoanalysis;
import infodynamics.measures.continuous.EntropyCalculator;
import infodynamics.measures.continuous.InfoMeasureCalculatorContinuous;
import infodynamics.measures.continuous.gaussian.EntropyCalculatorGaussian;
import infodynamics.measures.continuous.kernel.EntropyCalculatorKernel;
import infodynamics.measures.continuous.kozachenko.EntropyCalculatorMultiVariateKozachenko;
import infodynamics.measures.discrete.EntropyCalculatorDiscrete;
import infodynamics.measures.discrete.InfoMeasureCalculatorDiscrete;
import infodynamics.measures.discrete.MutualInformationCalculatorDiscrete;
import infodynamics.utils.MatrixUtils;
import java.util.Vector;
import javax.swing.JOptionPane;
/**
* This class provides a GUI to build a simple calculation
* of entropy,
* and supply the code to execute it.
*
*
* @author Joseph Lizier
*
*/
public class AutoAnalyserEntropy extends AutoAnalyser {
/**
* Need serialVersionUID to be serializable
*/
private static final long serialVersionUID = 1L;
// Property names for specific continuous calculators:
protected String[] gaussianProperties;
protected String[] gaussianPropertiesFieldNames;
protected String[] gaussianPropertyDescriptions;
protected String[] kernelProperties;
protected String[] kernelPropertiesFieldNames;
protected String[] kernelPropertyDescriptions;
protected String[] klProperties;
protected String[] klPropertiesFieldNames;
protected String[] klPropertyDescriptions;
/**
* Constructor to initialise the GUI for a channel calculator
*/
protected void makeSpecificInitialisations() {
numVariables = 1;
variableColNumLabels = new String[] {"Variable"};
useAllCombosCheckBox = true;
useStatSigCheckBox = false;
wordForCombinations = "variables";
variableRelationshipFormatString = "col_%d";
disableVariableColTextFieldsForAllCombos = new boolean[] {true};
indentsForAllCombos = 1;
// Set up the properties for Entropy:
measureAcronym = "H";
appletTitle = "JIDT Entropy Auto-Analyser";
calcTypes = new String[] {
CALC_TYPE_DISCRETE, CALC_TYPE_BINNED, CALC_TYPE_GAUSSIAN,
CALC_TYPE_KOZ_LEO, CALC_TYPE_KERNEL};
unitsForEachCalc = new String[] {"bits", "bits", "nats", "nats", "bits"};
// Discrete:
discreteClass = MutualInformationCalculatorDiscrete.class;
discreteProperties = new String[] {
DISCRETE_PROPNAME_BASE
};
discretePropertyDefaultValues = new String[] {
"2"
};
discretePropertyDescriptions = new String[] {
"Number of discrete states available for each variable (i.e. 2 for binary)"
};
// Continuous:
abstractContinuousClass = EntropyCalculator.class;
// Common properties for all continuous calcs:
commonContPropertyNames = new String[] {
// None
};
commonContPropertiesFieldNames = new String[] {
// None
};
commonContPropertyDescriptions = new String[] {
// None
};
// Gaussian properties:
gaussianProperties = new String[] {
};
gaussianPropertiesFieldNames = new String[] {
};
gaussianPropertyDescriptions = new String[] {
};
// Kernel:
kernelProperties = new String[] {
EntropyCalculatorKernel.KERNEL_WIDTH_PROP_NAME,
EntropyCalculatorKernel.NORMALISE_PROP_NAME,
};
kernelPropertiesFieldNames = new String[] {
"KERNEL_WIDTH_PROP_NAME",
"NORMALISE_PROP_NAME"
};
kernelPropertyDescriptions = new String[] {
"Kernel width to be used in the calculation. <br/>If the property " +
EntropyCalculatorKernel.NORMALISE_PROP_NAME +
" is set, then this is a number of standard deviations; " +
"otherwise it is an absolute value.",
"(boolean) whether to normalise <br/>the incoming time-series to mean 0, standard deviation 1, or not (recommended)",
};
// KSG (Kraskov):
klProperties = new String[] {
// There is a NUM_DIMENSIONS_PROP_NAME property, but this will
// only be relevant if we go to multivariate later.
};
klPropertiesFieldNames = new String[] {
};
klPropertyDescriptions = new String[] {
};
}
@Override
protected void fillOutAllCombinations(Vector<int[]> variableCombinations) {
// All combinations here means all pairs
for (int s = 0; s < dataColumns; s++) {
variableCombinations.add(new int[] {s});
}
}
@Override
protected String[] setUpLoopsForAllCombos(StringBuffer javaCode,
StringBuffer pythonCode, StringBuffer matlabCode) {
// Set up loops in the code:
// 1. Java code
javaCode.append(" \n");
javaCode.append(" // Compute for all variables:\n");
javaCode.append(" for (int v = 0; v < " + dataColumns +
"; v++) {\n");
String javaPrefix = " ";
javaCode.append(javaPrefix + "// For each variable:\n");
// 2. Python code
pythonCode.append("\n");
pythonCode.append("# Compute for all variables:\n");
pythonCode.append("for v in range(" + dataColumns + "):\n");
String pythonPrefix = " ";
pythonCode.append(pythonPrefix+ "# For each variable:\n");
// 3. Matlab code
matlabCode.append("\n");
matlabCode.append("% Compute for all variables:\n");
matlabCode.append("for v = 1:" + dataColumns + "\n");
String matlabPrefix = "\t";
matlabCode.append(matlabPrefix + "% For each variable:\n");
// Return the variables to index each column:
return new String[] {"v"};
}
@Override
protected void finaliseLoopsForAllCombos(StringBuffer javaCode,
StringBuffer pythonCode, StringBuffer matlabCode) {
// 1. Java code
javaCode.append(" }\n");
// 2. Python code
// Nothing to do
// 3. Matlab code
matlabCode.append("end\n");
}
@Override
protected String formatStringWithColumnNumbers(String formatStr, int[] columnNumbers) {
// We format the variable number into the
// return string here:
return String.format(formatStr,
columnNumbers[0]);
}
@Override
protected boolean skipColumnCombo(int[] columnCombo) {
// No reason to skip any columns here
return false;
}
@Override
protected void setObservations(InfoMeasureCalculatorDiscrete calcDiscrete,
InfoMeasureCalculatorContinuous calcContinuous,
int[] columnCombo) throws Exception {
String selectedCalcType = (String)
calcTypeComboBox.getSelectedItem();
int variableColumn = columnCombo[0];
// Set observations
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
EntropyCalculatorDiscrete calc = (EntropyCalculatorDiscrete) calcDiscrete;
calc.addObservations(
MatrixUtils.selectColumn(dataDiscrete, variableColumn));
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_BINNED)) {
EntropyCalculatorDiscrete calc = (EntropyCalculatorDiscrete) calcDiscrete;
calc.addObservations(
MatrixUtils.discretise(
MatrixUtils.selectColumn(data, variableColumn),
// Should be no parse error on the alphabet size by now
Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE))));
} else {
EntropyCalculator calc = (EntropyCalculator) calcContinuous;
calc.setObservations(
MatrixUtils.selectColumn(data, variableColumn));
}
}
protected CalcProperties assignCalcProperties(String selectedCalcType)
throws Exception {
// Let the super class handle discrete calculators
CalcProperties calcProperties = super.assignCalcProperties(selectedCalcType);
if (calcProperties == null) {
// We need to assign properties for a continuous calculator
calcProperties = new CalcProperties();
calcProperties.calc = assignCalcObjectContinuous(selectedCalcType);
calcProperties.calcClass = calcProperties.calc.getClass();
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) {
calcProperties.classSpecificPropertyNames = gaussianProperties;
calcProperties.classSpecificPropertiesFieldNames = gaussianPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = gaussianPropertyDescriptions;
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KOZ_LEO)) {
calcProperties.classSpecificPropertyNames = klProperties;
calcProperties.classSpecificPropertiesFieldNames = klPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = klPropertyDescriptions;
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KERNEL)) {
calcProperties.classSpecificPropertyNames = kernelProperties;
calcProperties.classSpecificPropertiesFieldNames = kernelPropertiesFieldNames;
calcProperties.classSpecificPropertyDescriptions = kernelPropertyDescriptions;
} else {
calcProperties = null;
throw new Exception("No recognised calculator selected: " +
selectedCalcType);
}
}
return calcProperties;
}
/**
* Method to assign and initialise our continuous calculator class
*/
@Override
protected EntropyCalculator assignCalcObjectContinuous(String selectedCalcType) throws Exception {
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) {
return new EntropyCalculatorGaussian();
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KOZ_LEO)) {
return new EntropyCalculatorMultiVariateKozachenko();
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KERNEL)) {
return new EntropyCalculatorKernel();
} else {
throw new Exception("No recognised continuous calculator selected: " +
selectedCalcType);
}
}
/**
* Method to assign and initialise our discrete calculator class
*/
protected DiscreteCalcAndArguments assignCalcObjectDiscrete() throws Exception {
String basePropValueStr;
try {
basePropValueStr = propertyValues.get(DISCRETE_PROPNAME_BASE);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this,
ex.getMessage());
resultsLabel.setText("Cannot find a value for property " + DISCRETE_PROPNAME_BASE);
return null;
}
int base;
try {
base = Integer.parseInt(basePropValueStr);
} catch (NumberFormatException nfe) {
JOptionPane.showMessageDialog(this,
"Cannot parse number for property " + DISCRETE_PROPNAME_BASE
+ ": " + nfe.getMessage());
resultsLabel.setText("Cannot parse number for property " +
DISCRETE_PROPNAME_BASE + ": " + nfe.getMessage());
return null;
}
return new DiscreteCalcAndArguments(
new EntropyCalculatorDiscrete(base),
base,
Integer.toString(base));
}
/**
* @param args
*/
public static void main(String[] args) {
new AutoAnalyserEntropy();
}
}

View File

@ -18,7 +18,6 @@
package infodynamics.demos.autoanalysis;
import infodynamics.measures.continuous.ChannelCalculatorCommon;
import infodynamics.measures.continuous.MutualInfoCalculatorMultiVariate;
import infodynamics.measures.continuous.gaussian.MutualInfoCalculatorMultiVariateGaussian;
import infodynamics.measures.continuous.kernel.MutualInfoCalculatorMultiVariateKernel;
@ -41,7 +40,7 @@ import java.awt.event.MouseListener;
* @author Joseph Lizier
*
*/
public class AutoAnalyserMI extends AutoAnalyser
public class AutoAnalyserMI extends AutoAnalyserChannelCalculator
implements ActionListener, DocumentListener, MouseListener {
/**
@ -59,15 +58,17 @@ public class AutoAnalyserMI extends AutoAnalyser
*/
protected void makeSpecificInitialisations() {
super.makeSpecificInitialisations();
// Set up the properties for MI:
measureAcronym = "MI";
appletTitle = "JIDT Mutual Information Auto-Analyser";
calcTypes = new String[] {
CALC_TYPE_DISCRETE, CALC_TYPE_GAUSSIAN,
CALC_TYPE_DISCRETE, CALC_TYPE_BINNED, CALC_TYPE_GAUSSIAN,
CALC_TYPE_KRASKOV_ALG1, CALC_TYPE_KRASKOV_ALG2,
CALC_TYPE_KERNEL};
unitsForEachCalc = new String[] {"bits", "nats", "nats", "nats", "bits"};
unitsForEachCalc = new String[] {"bits", "bits", "nats", "nats", "nats", "bits"};
// Discrete:
discreteClass = MutualInformationCalculatorDiscrete.class;
@ -159,7 +160,8 @@ public class AutoAnalyserMI extends AutoAnalyser
/**
* Method to assign and initialise our continuous calculator class
*/
protected ChannelCalculatorCommon assignCalcObjectContinuous(String selectedCalcType) throws Exception {
@Override
protected MutualInfoCalculatorMultiVariate assignCalcObjectContinuous(String selectedCalcType) throws Exception {
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) {
return new MutualInfoCalculatorMultiVariateGaussian();
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KRASKOV_ALG1)) {
@ -179,9 +181,10 @@ public class AutoAnalyserMI extends AutoAnalyser
* Method to assign and initialise our discrete calculator class
*/
protected DiscreteCalcAndArguments assignCalcObjectDiscrete() throws Exception {
String timeDiffPropValueStr, basePropValueStr;
int timeDiff, base;
try {
timeDiffPropValueStr = propertyValues.get(DISCRETE_PROPNAME_TIME_DIFF);
String timeDiffPropValueStr = propertyValues.get(DISCRETE_PROPNAME_TIME_DIFF);
timeDiff = Integer.parseInt(timeDiffPropValueStr);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this,
ex.getMessage());
@ -189,15 +192,14 @@ public class AutoAnalyserMI extends AutoAnalyser
return null;
}
try {
basePropValueStr = propertyValues.get(DISCRETE_PROPNAME_BASE);
String basePropValueStr = propertyValues.get(DISCRETE_PROPNAME_BASE);
base = Integer.parseInt(basePropValueStr);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this,
ex.getMessage());
resultsLabel.setText("Cannot find a value for property " + DISCRETE_PROPNAME_BASE);
return null;
}
int timeDiff = Integer.parseInt(timeDiffPropValueStr);
int base = Integer.parseInt(basePropValueStr);
return new DiscreteCalcAndArguments(
new MutualInformationCalculatorDiscrete(base, timeDiff),
@ -205,13 +207,6 @@ public class AutoAnalyserMI extends AutoAnalyser
base + ", " + timeDiff);
}
protected void setObservations(ChannelCalculatorCommon calc,
double[] source, double[] dest) throws Exception {
// We know this is a MutualInfoCalculatorMultiVariate
MutualInfoCalculatorMultiVariate miCalc = (MutualInfoCalculatorMultiVariate) calc;
miCalc.setObservations(source, dest);
}
/**
* @param args
*/

View File

@ -18,7 +18,6 @@
package infodynamics.demos.autoanalysis;
import infodynamics.measures.continuous.ChannelCalculatorCommon;
import infodynamics.measures.continuous.ConditionalMutualInfoMultiVariateCommon;
import infodynamics.measures.continuous.TransferEntropyCalculator;
import infodynamics.measures.continuous.gaussian.TransferEntropyCalculatorGaussian;
@ -41,7 +40,7 @@ import java.awt.event.MouseListener;
* @author Joseph Lizier
*
*/
public class AutoAnalyserTE extends AutoAnalyser
public class AutoAnalyserTE extends AutoAnalyserChannelCalculator
implements ActionListener, DocumentListener, MouseListener {
/**
@ -60,10 +59,17 @@ public class AutoAnalyserTE extends AutoAnalyser
*/
protected void makeSpecificInitialisations() {
super.makeSpecificInitialisations();
// Set up the properties for TE:
measureAcronym = "TE";
appletTitle = "JIDT Transfer Entropy Auto-Analyser";
calcTypes = new String[] {
CALC_TYPE_DISCRETE, CALC_TYPE_BINNED, CALC_TYPE_GAUSSIAN,
CALC_TYPE_KRASKOV, CALC_TYPE_KERNEL};
unitsForEachCalc = new String[] {"bits", "bits", "nats", "nats", "bits"};
// Discrete:
discreteClass = TransferEntropyCalculatorDiscrete.class;
discreteProperties = new String[] {
@ -214,7 +220,7 @@ public class AutoAnalyserTE extends AutoAnalyser
/**
* Method to assign and initialise our continuous calculator class
*/
protected ChannelCalculatorCommon assignCalcObjectContinuous(String selectedCalcType) throws Exception {
protected TransferEntropyCalculator assignCalcObjectContinuous(String selectedCalcType) throws Exception {
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) {
return new TransferEntropyCalculatorGaussian();
} else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KRASKOV)) {
@ -294,13 +300,6 @@ public class AutoAnalyserTE extends AutoAnalyser
base + ", " + k + ", " + k_tau + ", " + l + ", " + l_tau + ", " + delay);
}
protected void setObservations(ChannelCalculatorCommon calc,
double[] source, double[] dest) throws Exception {
// We know this is a TransferEntropyCalculator
TransferEntropyCalculator teCalc = (TransferEntropyCalculator) calc;
teCalc.setObservations(source, dest);
}
/**
* @param args
*/

View File

@ -42,9 +42,13 @@ jcall(teCalc, "setProperty", Void, (JString, JString), "NORMALISE", "true"); # N
jcall(teCalc, "initialise", Void, (jint, jdouble), 1, 0.5); # Use history length 1 (Schreiber k=1), kernel width of 0.5 normalised units
jcall(teCalc, "setObservations", Void, (Array{jdouble,1}, Array{jdouble,1}),
sourceArray, destArray);
# For copied source, should give something close to expected value for correlated Gaussians:
result = jcall(teCalc, "computeAverageLocalOfObservations", jdouble, ());
@printf("TE result %.4f bits; expected to be close to %.4f bits for these correlated Gaussians but biased upwards\n", result, log(1/(1-covariance^2))/log(2));
# For copied source, should give something close to expected value for correlated Gaussians:
# Expected correlation is expected covariance / product of expected standard deviations:
# (where square of destArray standard dev is sum of squares of std devs of
# underlying distributions)
corr_expected = covariance / (1 * sqrt(covariance^2 + (1-covariance)^2));
@printf("TE result %.4f bits; expected to be close to %.4f bits for these correlated Gaussians but biased upwards\n", result, -0.5*log(1-corr_expected^2)/log(2));
jcall(teCalc, "initialise", Void, ()); # Initialise leaving the parameters the same
jcall(teCalc, "setObservations", Void, (Array{jdouble,1}, Array{jdouble,1}),

View File

@ -49,8 +49,12 @@ result = jcall(teCalc, "computeAverageLocalOfObservations", jdouble, ());
# data is a set of random variables) - the result will be of the order
# of what we expect, but not exactly equal to it; in fact, there will
# be a large variance around it.
# Expected correlation is expected covariance / product of expected standard deviations:
# (where square of destArray standard dev is sum of squares of std devs of
# underlying distributions)
corr_expected = covariance / (1 * sqrt(covariance^2 + (1-covariance)^2));
@printf("TE result %.4f nats; expected to be close to %.4f nats for these correlated Gaussians\n",
result, log(1/(1-covariance^2)));
result, -0.5*log(1-corr_expected^2));
# Perform calculation with uncorrelated source:
jcall(teCalc, "initialise", Void, ()); # Initialise leaving the parameters the same

View File

@ -40,7 +40,7 @@ function plotRawCa(states, rule, plotOptions, saveIt, saveFormat)
plotOptions.plotRows = size(states, 1);
end
if not(isfield(plotOptions, 'plotCols'))
plotOptions.plotCols = columns(states);
plotOptions.plotCols = size(states, 2);
end
if not(isfield(plotOptions, 'plotStartRow'))
plotOptions.plotStartRow = 1;

View File

@ -44,8 +44,8 @@
% - cells - number of cells in the CA
% - steps - number of rows to execute the CA for (including the random initial row)
% - debug - turn on various debug messages
% - seedOrState - if a scalar, it is the state input for the random number generator (so one can repeat CA investigations for the same initial state).
% - if a vector, it is the initial state for the CA (must be of length cells)
% - seedOrState (optional) - if a scalar, it is the state input for the random number generator (so one can repeat CA investigations for the same initial state).
% - if a vector, it is the initial state for the CA (must be of length cells)
%
% Outputs:
% - caStates - a run, from random initial conditions, of a CA of the given parameters.
@ -128,7 +128,7 @@ function [caStates, ruleTable, executedRules] = runCA(neighbourhood, base, rule,
fprintf('%d', ruleTable(i));
end
fprintf('\n');
else
elseif (isscalar(rule))
% The rule is specified as an integer
if (rule > base .^ (base .^ neighbourhood) - 1)
error(sprintf('Rule %d is not within the limits of this base %d and neighbourhood %d (max is %d)', ...
@ -156,6 +156,12 @@ function [caStates, ruleTable, executedRules] = runCA(neighbourhood, base, rule,
fprintf('%d', ruleTable(i));
end
fprintf('\n');
else
% We have a vector supplied for the rule, as a rule table already
if (length(rule) ~= base .^ neighbourhood)
error('Rule table vector (length %d) is not of expected length %d\n', length(rule), base .^ neighbourhood);
end
ruleTable = rule;
end
caStates = zeros(steps, cells);

View File

@ -0,0 +1,16 @@
%
% Utility function to save raw CA data to a text file format.
% Unfortunately MAtlab's -ascii format only writes full doubles, making much larger files than we need.
% So I wrote this.
function saveCA(filename, caStates)
warning('off','MATLAB:Java:DuplicateClass');
javaaddpath('../../../infodynamics.jar');
addpath('..');
arrayWriter = javaObject('infodynamics.utils.ArrayFileWriter');
arrayWriter.makeIntMatrixFile(octaveToJavaIntMatrix(caStates), filename);
end

View File

@ -34,10 +34,14 @@ teCalc=javaObject('infodynamics.measures.continuous.kernel.TransferEntropyCalcul
teCalc.setProperty('NORMALISE', 'true'); % Normalise the individual variables
teCalc.initialise(1, 0.5); % Use history length 1 (Schreiber k=1), kernel width of 0.5 normalised units
teCalc.setObservations(sourceArray, destArray);
% For copied source, should give something close to expected value for correlated Gaussians:
result = teCalc.computeAverageLocalOfObservations();
% For copied source, should give something close to expected value for correlated Gaussians.
% Expected correlation is expected covariance / product of expected standard deviations:
% (where square of destArray standard dev is sum of squares of std devs of
% underlying distributions)
corr_expected = covariance ./ (1 * sqrt(covariance^2 + (1-covariance)^2));
fprintf('TE result %.4f bits; expected to be close to %.4f bits for these correlated Gaussians but biased upwards\n', ...
result, log(1/(1-covariance^2))/log(2));
result, -0.5*log(1-corr_expected^2)/log(2));
teCalc.initialise(); % Initialise leaving the parameters the same
teCalc.setObservations(sourceArray2, destArray);
% For random source, it should give something close to 0 bits

View File

@ -40,8 +40,12 @@ result = teCalc.computeAverageLocalOfObservations();
% data is a set of random variables) - the result will be of the order
% of what we expect, but not exactly equal to it; in fact, there will
% be a large variance around it.
% Expected correlation is expected covariance / product of expected standard deviations:
% (where square of destArray standard dev is sum of squares of std devs of
% underlying distributions)
corr_expected = covariance ./ (1 * sqrt(covariance^2 + (1-covariance)^2));
fprintf('TE result %.4f nats; expected to be close to %.4f nats for these correlated Gaussians\n', ...
result, log(1/(1-covariance^2)));
result, - 0.5 * log(1-corr_expected^2));
% Perform calculation with uncorrelated source:
teCalc.initialise(); % Initialise leaving the parameters the same
teCalc.setObservations(sourceArray2, destArray);

View File

@ -45,10 +45,14 @@ teCalc = teCalcClass()
teCalc.setProperty("NORMALISE", "true") # Normalise the individual variables
teCalc.initialise(1, 0.5) # Use history length 1 (Schreiber k=1), kernel width of 0.5 normalised units
teCalc.setObservations(JArray(JDouble, 1)(sourceArray), JArray(JDouble, 1)(destArray))
# For copied source, should give something close to 1 bit:
result = teCalc.computeAverageLocalOfObservations()
# For copied source, should give something close to 1 bit:
# Expected correlation is expected covariance / product of expected standard deviations:
# (where square of destArray standard dev is sum of squares of std devs of
# underlying distributions)
corr_expected = covariance / (1 * math.sqrt(covariance**2 + (1-covariance)**2));
print("TE result %.4f bits; expected to be close to %.4f bits for these correlated Gaussians but biased upwards" % \
(result, math.log(1/(1-math.pow(covariance,2)))/math.log(2)))
(result, -0.5*math.log(1-math.pow(corr_expected,2))/math.log(2)))
teCalc.initialise() # Initialise leaving the parameters the same
teCalc.setObservations(JArray(JDouble, 1)(sourceArray2), JArray(JDouble, 1)(destArray))
# For random source, it should give something close to 0 bits

View File

@ -52,8 +52,12 @@ result = teCalc.computeAverageLocalOfObservations()
# data is a set of random variables) - the result will be of the order
# of what we expect, but not exactly equal to it; in fact, there will
# be a large variance around it.
# Expected correlation is expected covariance / product of expected standard deviations:
# (where square of destArray standard dev is sum of squares of std devs of
# underlying distributions)
corr_expected = covariance / (1 * math.sqrt(covariance**2 + (1-covariance)**2));
print("TE result %.4f nats; expected to be close to %.4f nats for these correlated Gaussians" % \
(result, math.log(1/(1-math.pow(covariance,2)))))
(result, -0.5 * math.log(1-corr_expected**2)))
# Perform calculation with uncorrelated source:
teCalc.initialise() # Initialise leaving the parameters the same
teCalc.setObservations(JArray(JDouble, 1)(sourceArray2), JArray(JDouble, 1)(destArray))

View File

@ -41,9 +41,13 @@ teCalc<-.jnew("infodynamics/measures/continuous/kernel/TransferEntropyCalculator
.jcall(teCalc,"V","setProperty", "NORMALISE", "true") # Normalise the individual variables
.jcall(teCalc,"V","initialise", 1L, 0.5) # Use history length 1 (Schreiber k=1), kernel width of 0.5 normalised units
.jcall(teCalc,"V","setObservations", sourceArray, destArray)
# For copied source, should give something close to expected value for correlated Gaussians:
result <- .jcall(teCalc,"D","computeAverageLocalOfObservations")
cat("TE result ", result, "bits; expected to be close to ", log(1/(1-covariance^2))/log(2), " bits for these correlated Gaussians but biased upwards\n")
# For copied source, should give something close to expected value for correlated Gaussians.
# Expected correlation is expected covariance / product of expected standard deviations:
# (where square of destArray standard dev is sum of squares of std devs of
# underlying distributions)
corr_expected <- covariance / (1 * sqrt(covariance^2 + (1-covariance)^2));
cat("TE result ", result, "bits; expected to be close to ", -0.5*log(1-corr_expected^2)/log(2), " bits for these correlated Gaussians but biased upwards\n")
.jcall(teCalc,"V","initialise") # Initialise leaving the parameters the same
.jcall(teCalc,"V","setObservations", sourceArray2, destArray)

View File

@ -48,7 +48,11 @@ result <- .jcall(teCalc,"D","computeAverageLocalOfObservations")
# data is a set of random variables) - the result will be of the order
# of what we expect, but not exactly equal to it; in fact, there will
# be a large variance around it.
cat("TE result ", result, "nats; expected to be close to ", log(1/(1-covariance^2)), " nats for these correlated Gaussians\n")
# Expected correlation is expected covariance / product of expected standard deviations:
# (where square of destArray standard dev is sum of squares of std devs of
# underlying distributions)
corr_expected <- covariance / (1 * sqrt(covariance^2 + (1-covariance)^2));
cat("TE result ", result, "nats; expected to be close to ", -0.5*log(1-corr_expected^2), " nats for these correlated Gaussians\n")
# Perform calculation with uncorrelated source:
.jcall(teCalc,"V","initialise") # Initialise leaving the parameters the same

View File

@ -18,7 +18,7 @@
package infodynamics.measures.continuous;
import infodynamics.utils.EmpiricalMeasurementDistribution;
import infodynamics.utils.EmpiricalNullDistributionComputer;
/**
* Interface for implementations of
@ -84,7 +84,8 @@ import infodynamics.utils.EmpiricalMeasurementDistribution;
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
public interface ActiveInfoStorageCalculator {
public interface ActiveInfoStorageCalculator extends
InfoMeasureCalculatorContinuous, EmpiricalNullDistributionComputer {
/**
* Property name for embedding length <code>k</code> of
@ -100,13 +101,6 @@ public interface ActiveInfoStorageCalculator {
*/
public static final String TAU_PROP_NAME = "TAU";
/**
* Initialise the calculator for (re-)use, with the existing (or default) values of parameters
* Clears any PDFs of previously supplied observations.
*
*/
public void initialise() throws Exception;
/**
* Initialise the calculator for (re-)use, with some parameters
* supplied here, and existing (or default) values of other parameters
@ -148,23 +142,9 @@ public interface ActiveInfoStorageCalculator {
* @param propertyValue value of the property
* @throws Exception for invalid property values
*/
@Override
public void setProperty(String propertyName, String propertyValue) throws Exception;
/**
* Get current property values for the calculator.
*
* <p>Valid property names, and what their
* values should represent, are the same as those for
* {@link #setProperty(String, String)}</p>
*
* <p>Unknown property values are responded to with a null return value.</p>
*
* @param propertyName name of the property
* @return current value of the property
* @throws Exception for invalid property values
*/
public String getProperty(String propertyName) throws Exception;
/**
* Sets a single time-series from which to compute the PDF for the AIS.
* Cannot be called in conjunction with other methods for setting/adding
@ -247,13 +227,6 @@ public interface ActiveInfoStorageCalculator {
*/
public void addObservations(double[] observations, boolean[] valid) throws Exception;
/**
* Compute the AIS from the previously-supplied samples.
*
* @return the AIS estimate
*/
public double computeAverageLocalOfObservations() throws Exception;
/**
* Compute the local AIS values for each of the
* previously-supplied samples.
@ -294,104 +267,4 @@ public interface ActiveInfoStorageCalculator {
*/
public double[] computeLocalUsingPreviousObservations(double[] newObservations) throws Exception;
/**
* Generate a bootstrapped distribution of what the AIS would look like,
* under a null hypothesis that the previous <code>k</code> values of our
* samples had no relation to the next value in the time-series.
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for AIS
* as a mutual information. Basically, the marginal PDFs
* of the past <code>k</code> values, and that of the next value,
* are preserved, while their joint PDF is destroyed, and the
* distribution of AIS under these conditions is generated.</p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(double[])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* <p>This method (in contrast to {@link #computeSignificance(int[][])})
* creates <i>random</i> shufflings of the next values for the surrogate AIS
* calculations.</p>
*
* @param numPermutationsToCheck number of surrogate samples to bootstrap
* to generate the distribution.
* @return the distribution of AIS scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
* @throws Exception
*/
public EmpiricalMeasurementDistribution computeSignificance(int numPermutationsToCheck) throws Exception;
/**
* Generate a bootstrapped distribution of what the AIS would look like,
* under a null hypothesis that the previous <code>k</code> values of our
* samples had no relation to the next value in the time-series.
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for AIS
* as a mutual information. Basically, the marginal PDFs
* of the past <code>k</code> values, and that of the next value,
* are preserved, while their joint PDF is destroyed, and the
* distribution of AIS under these conditions is generated.</p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(double[])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* <p>This method (in contrast to {@link #computeSignificance(int)})
* allows the user to specify how to construct the surrogates,
* such that repeatable results may be obtained.</p>
*
* @param newOrderings a specification of how to shuffle the next values
* to create the surrogates to generate the distribution with. The first
* index is the permutation number (i.e. newOrderings.length is the number
* of surrogate samples we use to bootstrap to generate the distribution here.)
* Each array newOrderings[i] should be an array of length L being
* the value returned by {@link #getNumObservations()},
* containing a permutation of the values in 0..(L-1).
* @return the distribution of AIS scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
* @throws Exception where the length of each permutation in newOrderings
* is not equal to the number L observations that were previously supplied.
*/
public EmpiricalMeasurementDistribution computeSignificance(
int[][] newOrderings) throws Exception;
/**
* Set or clear debug mode for extra debug printing to stdout
*
* @param debug new setting for debug mode (on/off)
*/
public void setDebug(boolean debug);
/**
* Return the AIS last calculated in a call to {@link #computeAverageLocalOfObservations()}
* or {@link #computeLocalOfPreviousObservations()} after the previous
* {@link #initialise()} call.
*
* @return the last computed AIS value
*/
public double getLastAverage();
/**
* Get the number of samples to be used for the PDFs here
* which have been supplied by calls to
* {@link #setObservations(double[])}, {@link #addObservations(double[])}
* etc.
*
* <p>Note that the number of samples is not equal to the length of time-series
* supplied (since we need to accumulate the first
* <code>(k-1)*tau + 1</code>
* values of each time-series before taking a sample for AIS).
* </p>
*
* @return the number of samples to be used for the PDFs
* @throws Exception
*/
public int getNumObservations() throws Exception;
}

View File

@ -18,8 +18,6 @@
package infodynamics.measures.continuous;
import infodynamics.utils.EmpiricalMeasurementDistribution;
/**
* Interface for implementations of Active Information Storage estimators on
* multivariate continuous time-series data. That is, it is applied to
@ -103,4 +101,6 @@ public interface ActiveInfoStorageCalculatorMultiVariate {
*/
public void initialise(int dimensions, int k, int tau) throws Exception;
// TODO We seem to be missing a lot of functionality which should be defined here,
// as implemented in the univariate measure. will have to go back and do this.
}

View File

@ -21,7 +21,6 @@ package infodynamics.measures.continuous;
import java.util.Iterator;
import java.util.Vector;
import infodynamics.utils.EmpiricalMeasurementDistribution;
import infodynamics.utils.MatrixUtils;
/**

View File

@ -18,7 +18,7 @@
package infodynamics.measures.continuous;
import infodynamics.utils.EmpiricalMeasurementDistribution;
import infodynamics.utils.EmpiricalNullDistributionComputer;
/**
* A basic interface for calculators computing measures on a <i>channel</i> from a
@ -76,45 +76,9 @@ import infodynamics.utils.EmpiricalMeasurementDistribution;
* @see ChannelCalculatorMultiVariate
*
*/
public abstract interface ChannelCalculatorCommon {
public abstract interface ChannelCalculatorCommon
extends InfoMeasureCalculatorContinuous, EmpiricalNullDistributionComputer {
/**
* Initialise the calculator for (re-)use, with the existing
* (or default) values of parameters.
*
* @throws Exception
*/
public void initialise() throws Exception;
/**
* Set properties for the calculator.
* New property values are not guaranteed to take effect until the next call
* to an initialise method.
*
* <p>No general properties are defined at the interface level here, i.e.
* there are only properties defined by child interfaces and classes.</p>
*
* @param propertyName name of the property
* @param propertyValue value of the property
* @throws Exception for invalid property values
*/
public void setProperty(String propertyName, String propertyValue) throws Exception;
/**
* Get current property values for the calculator.
*
* <p>Valid property names, and what their
* values should represent, are the same as those for
* {@link #setProperty(String, String)}</p>
*
* <p>Unknown property values are responded to with a null return value.</p>
*
* @param propertyName name of the property
* @return current value of the property
* @throws Exception for invalid property values
*/
public String getProperty(String propertyName) throws Exception;
/**
* Signal that we will add in the samples for computing the PDF
* from several disjoint time-series or trials via calls to
@ -140,13 +104,6 @@ public abstract interface ChannelCalculatorCommon {
*/
public boolean getAddedMoreThanOneObservationSet();
/**
* Compute the channel measure from the previously-supplied samples.
*
* @return the estimate of the channel measure
*/
public double computeAverageLocalOfObservations() throws Exception;
/**
* <p>Computes the local values of the implemented channel measure,
* for each valid observation in the previously supplied observations
@ -166,96 +123,4 @@ public abstract interface ChannelCalculatorCommon {
*/
public double[] computeLocalOfPreviousObservations() throws Exception;
/**
* Generate a bootstrapped distribution of what the channel measure would look like,
* under a null hypothesis that the source values of our
* samples had no relation to the destination value.
* (Precise null hypothesis varies between MI and TE).
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for MI and TE.
* </p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(double[])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* <p>This method (in contrast to {@link #computeSignificance(int[][])})
* creates <i>random</i> shufflings of the next values for the surrogate AIS
* calculations.</p>
*
* @param numPermutationsToCheck number of surrogate samples to bootstrap
* to generate the distribution.
* @return the distribution of channel measure scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
*/
public EmpiricalMeasurementDistribution computeSignificance(int numPermutationsToCheck) throws Exception;
/**
* Generate a bootstrapped distribution of what the channel measure would look like,
* under a null hypothesis that the source values of our
* samples had no relation to the destination value.
* (Precise null hypothesis varies between MI and TE).
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for MI and TE.
* </p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(double[])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* <p>This method (in contrast to {@link #computeSignificance(int)})
* allows the user to specify how to construct the surrogates,
* such that repeatable results may be obtained.</p>
*
* @param newOrderings a specification of how to shuffle the source values
* to create the surrogates to generate the distribution with. The first
* index is the permutation number (i.e. newOrderings.length is the number
* of surrogate samples we use to bootstrap to generate the distribution here.)
* Each array newOrderings[i] should be an array of length N (where
* would be the value returned by {@link #getNumObservations()}),
* containing a permutation of the values in 0..(N-1).
* @return the distribution of channel measure scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
* @throws Exception where the length of each permutation in newOrderings
* is not equal to the number N samples that were previously supplied.
*/
public EmpiricalMeasurementDistribution computeSignificance(
int[][] newOrderings) throws Exception;
/**
* Set or clear debug mode for extra debug printing to stdout
*
* @param debug new setting for debug mode (on/off)
*/
public void setDebug(boolean debug);
/**
* Return the channel measure last calculated in a call to {@link #computeAverageLocalOfObservations()}
* or {@link #computeLocalOfPreviousObservations()} after the previous
* {@link #initialise()} call.
*
* @return the last computed channel measure value
*/
public double getLastAverage();
/**
* Get the number of samples to be used for the PDFs here
* which have been supplied by calls to
* {@link #setObservations(double[])}, {@link #addObservations(double[])}
* etc.
*
* <p>Note that the number of samples may not be equal to the length of time-series
* supplied (e.g. for transfer entropy, where we need to accumulate
* a number of samples for the past history of the destination).
* </p>
*
* @return the number of samples to be used for the PDFs
*/
public int getNumObservations() throws Exception;
}

View File

@ -19,6 +19,7 @@
package infodynamics.measures.continuous;
import infodynamics.utils.EmpiricalMeasurementDistribution;
import infodynamics.utils.EmpiricalNullDistributionComputer;
/**
* <p>Interface for implementations of the <b>conditional mutual information</b>,
@ -75,7 +76,8 @@ import infodynamics.utils.EmpiricalMeasurementDistribution;
* @see "T. M. Cover and J. A. Thomas, 'Elements of Information
Theory' (John Wiley & Sons, New York, 1991)."
*/
public interface ConditionalMutualInfoCalculatorMultiVariate {
public interface ConditionalMutualInfoCalculatorMultiVariate
extends InfoMeasureCalculatorContinuous, EmpiricalNullDistributionComputer {
/**
* Initialise the calculator for (re-)use, clearing PDFs,
@ -89,35 +91,6 @@ public interface ConditionalMutualInfoCalculatorMultiVariate {
*/
public void initialise(int var1Dimensions, int var2Dimensions, int condDimentions) throws Exception;
/**
* Set properties for the calculator.
* New property values are not guaranteed to take effect until the next call
* to an initialise method.
*
* <p>No general properties are defined at the interface level here, i.e.
* there are only properties defined by child interfaces and classes.</p>
*
* @param propertyName name of the property
* @param propertyValue value of the property
* @throws Exception for invalid property values
*/
public void setProperty(String propertyName, String propertyValue) throws Exception;
/**
* Get current property values for the calculator.
*
* <p>Valid property names, and what their
* values should represent, are the same as those for
* {@link #setProperty(String, String)}</p>
*
* <p>Unknown property values are responded to with a null return value.</p>
*
* @param propertyName name of the property
* @return current value of the property
* @throws Exception for invalid property values
*/
public String getProperty(String propertyName) throws Exception;
/**
* Sets a single series from which to compute the PDF.
* Cannot be called in conjunction with
@ -141,6 +114,29 @@ public interface ConditionalMutualInfoCalculatorMultiVariate {
public void setObservations(double[][] var1, double[][] var2,
double[][] cond) throws Exception;
/**
* Sets a single series from which to compute the PDF.
* Cannot be called in conjunction with
* {@link #startAddObservations()} / {@link #addObservations(double[][], double[][], double[][])} or
* {@link #addObservations(double[][], double[][], double[][], int, int)} /
* {@link #finaliseAddObservations()}.</p>
*
* <p>The supplied series may be (multivariate) time-series or
* simply a set of separate observations without a time interpretation.
*
* <p>This method can only be used where dimensions of all
* variables have been set to 1.</p>
*
* @param var1 univariate observations for variable 1
* @param var2 univariate observations for variable 2
* Length must match <code>var1</code>, and their indices must correspond.
* @param cond univariate observations for the conditional
* Length must match <code>var1</code>, and their indices must correspond.
* @throws Exception
*/
public void setObservations(double[] var1, double[] var2,
double[] cond) throws Exception;
/**
* Sets a single series from which to compute the PDF,
* where all the various observations are valid.
@ -237,6 +233,31 @@ public interface ConditionalMutualInfoCalculatorMultiVariate {
public void addObservations(double[][] var1, double[][] var2,
double[][] cond) throws Exception;
/**
* <p>Adds a new set of observations to update the PDFs with - is
* intended to be called multiple times.
* Must be called after {@link #startAddObservations()}; call
* {@link #finaliseAddObservations()} once all observations have
* been supplied.</p>
*
* <p>Note that the arrays must not be over-written by the user
* until after finaliseAddObservations() has been called
* (they are not copied by this method necessarily, but the method
* may simply hold a pointer to them).</p>
*
* <p>This method can only be used where dimensions of all
* variables have been set to 1.</p>
*
* @param var1 univariate observations for variable 1
* @param var2 univariate observations for variable 2
* Length must match <code>var1</code>, and their indices must correspond.
* @param cond univariate observations for the conditional
* Length must match <code>var1</code>, and their indices must correspond.
* @throws Exception
*/
public void addObservations(double[] var1, double[] var2,
double[] cond) throws Exception;
/**
* <p>Adds a new sub-series of observations to update the PDFs with - is
* intended to be called multiple times.
@ -272,15 +293,6 @@ public interface ConditionalMutualInfoCalculatorMultiVariate {
*/
public void finaliseAddObservations() throws Exception;
/**
* Compute the average conditional MI from the previously-supplied samples.
*
* @return the estimate of the conditional MI in either bits or nats
* depending on the estimator
* @throws Exception
*/
public double computeAverageLocalOfObservations() throws Exception;
/**
* <p>Computes the local values of the conditional mutual information,
* for each valid observation in the previously supplied observations
@ -303,66 +315,53 @@ public interface ConditionalMutualInfoCalculatorMultiVariate {
public double[] computeLocalOfPreviousObservations() throws Exception;
/**
* Generate a bootstrapped distribution of what the conditional MI would look like,
* under a null hypothesis that the variable identified by
* <code>variableToReorder</code> had no relation to the
* other variable, given the conditional.
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for
* conditional MI. Basically, we shuffle the observations of
* <code>variableToReorder</code> against the other variable
* and the conditional.
* This keeps the marginal and joint PDFs of the unshuffled variable
* and conditional the same
* but destroys any correlation between the named variable and the others.
* <p><b>Note</b> -- in contrast to the generic computeSignificance() method
* described in the documentation below, this method for a conditional MI calculator currently fixes the relationship
* between variable 2 and the conditional, and shuffles
* variable 1 with respect to these.
* To shuffle variable 2 instead, call {@link #computeSignificance(int, int)}.
* </p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(double[][], double[][], double[][])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* <p>This method (in contrast to {@link #computeSignificance(int, int[][])})
* creates <i>random</i> shufflings of the next values for the surrogate AIS
* calculations.</p>
* @inheritDoc
*/
@Override
public EmpiricalMeasurementDistribution computeSignificance(
int numPermutationsToCheck) throws Exception;
/**
* Defined as per {@link #computeSignificance(int)} except that this method allows
* the user to specify which variable is shuffled (whilst the other has its
* relationship with the conditional preserved).
*
* @param variableToReorder which variable to shuffle:
* 1 for variable 1, 2 for variable 2.
* @param numPermutationsToCheck number of surrogate samples to bootstrap
* to generate the distribution.
* @return the distribution of channel measure scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
* @see {@link #computeSignificance(int)}
* @throws Exception
*/
public EmpiricalMeasurementDistribution computeSignificance(int variableToReorder,
int numPermutationsToCheck) throws Exception;
/**
* Generate a bootstrapped distribution of what the conditional MI would look like,
* under a null hypothesis that the variable identified by
* <code>variableToReorder</code> had no relation to the
* other variable, given the conditional.
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for
* conditional MI. Basically, we shuffle the observations of
* <code>variableToReorder</code> against the other variable
* and the conditional.
* This keeps the marginal and joint PDFs of the unshuffled variable
* and conditional the same
* but destroys any correlation between the named variable and the others.
* <p><b>Note</b> -- in contrast to the generic computeSignificance() method
* described in the documentation below, this method for a conditional MI calculator currently fixes the relationship
* between variable 2 and the conditional, and shuffles
* variable 1 with respect to these.
* To shuffle variable 2 instead, call {@link #computeSignificance(int, int[][])}.
* </p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(double[][], double[][], double[][])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* <p>This method (in contrast to {@link #computeSignificance(int, int)})
* allows the user to specify how to construct the surrogates,
* such that repeatable results may be obtained.</p>
* @inheritDoc
*/
@Override
public EmpiricalMeasurementDistribution computeSignificance(
int[][] newOrderings) throws Exception;
/**
* Defined as per {@link #computeSignificance(int[][])} except that this method allows
* the user to specify which variable is shuffled (whilst the other has its
* relationship with the conditional preserved).
*
* @param variableToReorder which variable to shuffle:
* 1 for variable 1, 2 for variable 2.
@ -375,8 +374,7 @@ public interface ConditionalMutualInfoCalculatorMultiVariate {
* would be the value returned by {@link #getNumObservations()}),
* containing a permutation of the values in 0..(N-1).
* @return the distribution of channel measure scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
* @see {@link #computeSignificance(int[][])} except that this method all
* @throws Exception where the length of each permutation in newOrderings
* is not equal to the number N samples that were previously supplied.
*/
@ -425,32 +423,11 @@ public interface ConditionalMutualInfoCalculatorMultiVariate {
throws Exception;
/**
* Set or clear debug mode for extra debug printing to stdout
*
* @param debug new setting for debug mode (on/off)
*/
public void setDebug(boolean debug);
/**
* Return the conditional MI last calculated in a call to {@link #computeAverageLocalOfObservations()}
* or {@link #computeLocalOfPreviousObservations()} after the previous
* {@link #initialise(int, int, int)} call.
*
* @return the last computed conditional MI value
*/
public double getLastAverage();
/**
* Get the number of samples to be used for the PDFs here
* which have been supplied by calls to
* {@link #setObservations(double[][], double[][], double[][])}, {@link #addObservations(double[][], double[][], double[][])}
* etc.
*
* @return the number of samples to be used for the PDFs
* @throws Exception if the implementing class computes MI without
* explicit observations (e.g. see
* {@link infodynamics.measures.continuous.gaussian.ConditionalMutualInfoCalculatorMultiVariateGaussian})
*/
@Override
public int getNumObservations() throws Exception;
/**

View File

@ -165,7 +165,10 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
*/
protected double[] condStds = null;
@Override
public void initialise() {
initialise(dimensionsVar1, dimensionsVar2, dimensionsCond);
}
@Override
public void initialise(int var1Dimensions, int var2Dimensions, int condDimensions) {
@ -234,6 +237,15 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
addedMoreThanOneObservationSet = false;
}
@Override
public void setObservations(double[] var1, double[] var2,
double[] cond) throws Exception {
startAddObservations();
addObservations(var1, var2, cond);
finaliseAddObservations();
addedMoreThanOneObservationSet = false;
}
@Override
public void startAddObservations() {
vectorOfVar1Observations = new Vector<double[][]>();
@ -273,6 +285,23 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
}
}
@Override
public void addObservations(double[] var1, double[] var2,
double[] cond) throws Exception {
if ((dimensionsVar1 != 1) || (dimensionsVar2 != 1) ||
(dimensionsCond != 1)) {
throw new Exception("The number of dimensions for each variable (having been initialised to " +
dimensionsVar1 + ", " + dimensionsVar2 + " & " +
dimensionsCond + ") can only be 1 when " +
"the univariate addObservations(double[],double[],double[]) and " +
"setObservations(double[],double[],double[]) methods are called");
}
addObservations(MatrixUtils.reshape(var1, var1.length, 1),
MatrixUtils.reshape(var2, var2.length, 1),
MatrixUtils.reshape(cond, cond.length, 1));
}
@Override
public void addObservations(double[][] var1, double[][] var2,
double[][] cond,
@ -390,6 +419,12 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
vectorOfCondObservations = null;
}
@Override
public EmpiricalMeasurementDistribution computeSignificance(
int numPermutationsToCheck) throws Exception {
return computeSignificance(1, numPermutationsToCheck);
}
@Override
public EmpiricalMeasurementDistribution computeSignificance(
int variableToReorder, int numPermutationsToCheck) throws Exception {
@ -403,6 +438,12 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
return computeSignificance(variableToReorder, newOrderings);
}
@Override
public EmpiricalMeasurementDistribution computeSignificance(
int[][] newOrderings) throws Exception {
return computeSignificance(1, newOrderings);
}
/**
* <p>As described in
* {@link ConditionalMutualInfoCalculatorMultiVariate#computeSignificance(int, int[][])}

View File

@ -98,17 +98,17 @@ public class ConditionalTransferEntropyCalculatorViaCondMutualInfo implements
* Array of embedding lengths for each conditional variable.
* Can be an empty array or null if there are no conditional variables.
*/
protected int[] condEmbedDims = null;
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 = null;
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 = null;
protected int[] condDelays = new int[] {1};
/**
* Time index of the last point in the destination embedding of the first
@ -380,11 +380,23 @@ public class ConditionalTransferEntropyCalculatorViaCondMutualInfo implements
} else if (propertyName.equalsIgnoreCase(DELAY_PROP_NAME)) {
return Integer.toString(delay);
} else if (propertyName.equalsIgnoreCase(COND_EMBED_LENGTHS_PROP_NAME)) {
return MatrixUtils.arrayToString(condEmbedDims);
if (condEmbedDims == null) {
return "";
} else {
return MatrixUtils.arrayToString(condEmbedDims);
}
} else if (propertyName.equalsIgnoreCase(COND_EMBED_DELAYS_PROP_NAME)) {
return MatrixUtils.arrayToString(cond_taus);
if (cond_taus == null) {
return "";
} else {
return MatrixUtils.arrayToString(cond_taus);
}
} else if (propertyName.equalsIgnoreCase(COND_DELAYS_PROP_NAME)) {
return MatrixUtils.arrayToString(condDelays);
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

View File

@ -46,29 +46,7 @@ Theory' (John Wiley & Sons, New York, 1991).</li>
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
public interface EntropyCalculator {
/**
* Initialise the calculator for (re-)use, with the existing (or default) values of calculator-specific parameters
* Clears any PDFs of previously supplied observations.
*/
public void initialise() throws Exception;
/**
* Set properties for the underlying calculator implementation.
* New property values are not guaranteed to take effect until the next call
* to an initialise method.
*
* <p>No general properties are defined at the interface level, i.e.
* there are only calculator-specific properties.</p>
*
* <p>Unknown property values are ignored.</p>
*
* @param propertyName name of the property
* @param propertyValue value of the property
* @throws Exception for invalid property values
*/
public void setProperty(String propertyName, String propertyValue) throws Exception;
public interface EntropyCalculator extends InfoMeasureCalculatorContinuous {
// TODO Add addObservations() methods for entropy calculator
@ -81,17 +59,4 @@ public interface EntropyCalculator {
*/
public void setObservations(double observations[]);
/**
* Compute the entropy from the previously-supplied samples.
*
* @return the entropy estimate
*/
public double computeAverageLocalOfObservations();
/**
* Set or clear debug mode for extra debug printing to stdout
*
* @param debug new setting for debug mode (on/off)
*/
public void setDebug(boolean debug);
}

View File

@ -54,8 +54,36 @@ Theory' (John Wiley & Sons, New York, 1991).</li>
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
public interface EntropyCalculatorMultiVariate {
public interface EntropyCalculatorMultiVariate
extends InfoMeasureCalculatorContinuous {
/**
* Property name for the number of dimensions
*/
public static final String NUM_DIMENSIONS_PROP_NAME = "NUM_DIMENSIONS";
/**
* Set properties for the underlying calculator implementation.
* New property values are not guaranteed to take effect until the next call
* to an initialise method.
*
* <p>Property names defined at the interface level, and what their
* values should represent, include:</p>
* <ul>
* <li>{@link #NUM_DIMENSIONS_PROP_NAME} -- number of dimensions in the joint
* variable that we are computing the entropy of.</li>
* </ul>
*
* <p>Unknown property values are ignored.</p>
*
* <p>Note that implementing classes may defined additional properties.</p>
*
* @param propertyName name of the property
* @param propertyValue value of the property
* @throws Exception for invalid property values
*/
public void setProperty(String propertyName, String propertyValue) throws Exception;
/**
* Initialise the calculator for (re-)use, with the existing (or default) values
* of calculator-specific parameters.
@ -65,20 +93,6 @@ public interface EntropyCalculatorMultiVariate {
*/
public void initialise(int dimensions);
/**
* Set properties for the underlying calculator implementation.
* New property values are not guaranteed to take effect until the next call
* to an initialise method.
*
* <p>No general properties are defined at the interface level, i.e.
* there are only calculator-specific properties.</p>
*
* @param propertyName name of the property
* @param propertyValue value of the property
* @throws Exception for invalid property values
*/
public void setProperty(String propertyName, String propertyValue) throws Exception;
/**
* Set the observations for which to compute the PDFs for the entropy
* Should only be called once, the last call contains the
@ -93,13 +107,6 @@ public interface EntropyCalculatorMultiVariate {
*/
public void setObservations(double observations[][]) throws Exception;
/**
* Compute the entropy from the previously-supplied samples.
*
* @return the entropy estimate, in bits or nats depending on the estimator.
*/
public double computeAverageLocalOfObservations();
/**
* Compute the local entropy values for each of the
* supplied samples in <code>newObservations</code>.
@ -127,29 +134,4 @@ public interface EntropyCalculatorMultiVariate {
*/
public double[] computeLocalOfPreviousObservations() throws Exception;
/**
* Return the entropy last calculated in a call to {@link #computeAverageLocalOfObservations()}
* or {@link #computeLocalOfPreviousObservations()} after the previous
* {@link #initialise(int)} call.
*
* @return the last computed entropy value
*/
public double getLastAverage();
/**
* Set or clear debug mode for extra debug printing to stdout
*
* @param debug new setting for debug mode (on/off)
*/
public void setDebug(boolean debug);
/**
* Get the number of samples to be used for the PDFs here
* which have been supplied by calls to
* {@link #setObservations(double[][])}.
*
* @return the number of samples to be used for the PDFs
* @throws Exception
*/
public int getNumObservations() throws Exception;
}

View File

@ -0,0 +1,129 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, Joseph T. Lizier, Ipek Oezdemir and Pedro Mediano
*
* 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;
/**
* <p>Base interface for our information-theoretic calculators
* on continuous (double[]) data,
* providing common functionality
* for user-level measure classes.</p>
*
* <p>
* Usage of the child classes implementing this interface is intended to follow this paradigm:
* </p>
* <ol>
* <li>Construct the calculator;</li>
* <li>set properties via {@link #setProperty(String, String)};</li>
* <li>Initialise the calculator using {@link #initialise()} or
* other initialise methods defined by child classes;</li>
* <li>Provide the observations/samples for the calculator
* to set up the PDFs, using one or more calls to
* sets of "addObservations" methods defined by child classes, then</li>
* <li>Compute the required quantities, being one or more of:
* <ul>
* <li>the average measure: {@link #computeAverageLocalOfObservations()};</li>
* <li>or other quantities as defined by child classes.</li>
* </ul>
* </li>
* <li>
* Return to step 3 to re-use the calculator on a new data set.
* </li>
* </ol>
*
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
public interface InfoMeasureCalculatorContinuous {
/**
* Initialise the calculator for (re-)use, with the existing (or default) values of parameters.
* Clears any PDFs of previously supplied observations.
*/
public void initialise() throws Exception;
/**
* Set properties for the underlying calculator implementation.
* New property values are not guaranteed to take effect until the next call
* to an initialise method.
*
* <p>Property names are defined by the implementing class
* (generally in their documentation).
* Unknown property values are ignored.</p>
*
* @param propertyName name of the property
* @param propertyValue value of the property
* @throws Exception for invalid property values
*/
public void setProperty(String propertyName, String propertyValue) throws Exception;
/**
* Get current property values for the calculator.
*
* <p>Valid property names, and what their
* values should represent, are the same as those for
* {@link #setProperty(String, String)}</p>
*
* <p>Unknown property values are responded to with a null return value.</p>
*
* @param propertyName name of the property
* @return current value of the property
* @throws Exception for invalid property values
*/
public String getProperty(String propertyName) throws Exception;
/**
* Get the number of samples to be used for the PDFs here
* which have been supplied by calls to
* "setObservations", "addObservations" etc.
*
* <p>Note that the number of samples may not be equal to the length of time-series
* supplied (e.g. for transfer entropy, where we need to accumulate
* a number of samples for the past history of the destination).
* </p>
*
* @return the number of samples to be used for the PDFs
*/
public int getNumObservations() throws Exception;
/**
* Compute the average value of the measure
* from the previously-supplied samples.
*
* @return the estimate of the measure
* (in bits or nats, depending on the estimator)
*/
public abstract double computeAverageLocalOfObservations() throws Exception;
/**
* Return the measure last calculated in a call to
* {@link #computeAverageLocalOfObservations()}
* or related methods after the previous
* {@link #initialise()} call.
*
* @return the last computed measure value
*/
public double getLastAverage();
/**
* Set or clear debug mode for extra debug printing to stdout
*
* @param debug new setting for debug mode (on/off)
*/
public void setDebug(boolean debug);
}

View File

@ -76,7 +76,8 @@ import infodynamics.utils.EmpiricalMeasurementDistribution;
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
public interface MultiInfoCalculator {
public interface MultiInfoCalculator
extends InfoMeasureCalculatorContinuous {
/**
* Property name for whether to normalise incoming values to mean 0,
@ -120,6 +121,7 @@ public interface MultiInfoCalculator {
* @param propertyValue value of the property
* @throws Exception for invalid property values
*/
@Override
public void setProperty(String propertyName, String propertyValue) throws Exception;
/**
@ -189,14 +191,6 @@ public interface MultiInfoCalculator {
*/
public void finaliseAddObservations() throws Exception;
/**
* Compute the multi-information from the previously-supplied samples.
*
* @return the estimate of the multi-information
* @throws Exception
*/
public double computeAverageLocalOfObservations() throws Exception;
/**
* <p>Computes the local values of the multi-information,
* for each valid observation in the previously supplied observations
@ -337,20 +331,4 @@ public interface MultiInfoCalculator {
* is not equal to the number N samples that were previously supplied.
*/
public EmpiricalMeasurementDistribution computeSignificance(int[][][] newOrderings) throws Exception;
/**
* Set or clear debug mode for extra debug printing to stdout
*
* @param debug new setting for debug mode (on/off)
*/
public void setDebug(boolean debug);
/**
* Return the multi-information last calculated in a call to {@link #computeAverageLocalOfObservations()}
* or {@link #computeLocalOfPreviousObservations()} after the previous
* {@link #initialise(int)} call.
*
* @return the last computed multi-information value
*/
public double getLastAverage();
}

View File

@ -45,7 +45,7 @@ public abstract class MultiInfoCalculatorCommon implements MultiInfoCalculator {
/**
* Number of joint variables to consider
*/
protected int dimensions = 0;
protected int dimensions = 1;
/**
* Number of samples supplied
*/
@ -81,6 +81,11 @@ public abstract class MultiInfoCalculatorCommon implements MultiInfoCalculator {
private double samplingFactor = 0.1;
private Random rand;
@Override
public void initialise() {
initialise(dimensions);
}
@Override
public void initialise(int dimensions) {
this.dimensions = dimensions;
@ -127,6 +132,17 @@ public abstract class MultiInfoCalculatorCommon implements MultiInfoCalculator {
}
}
@Override
public String getProperty(String propertyName) throws Exception {
if (propertyName.equalsIgnoreCase(PROP_NORMALISE)) {
return Boolean.toString(normalise);
} else if (propertyName.equalsIgnoreCase(SAMPLING_FACTOR_PROP_NAME)) {
return Double.toString(samplingFactor);
} else {
return null;
}
}
@Override
public void setObservations(double[][] observations) throws Exception {
startAddObservations();
@ -258,6 +274,11 @@ public abstract class MultiInfoCalculatorCommon implements MultiInfoCalculator {
return miSurrogateCalculator.computeAverageLocalOfObservations();
}
@Override
public int getNumObservations() throws Exception {
return totalObservations;
}
@Override
public void setDebug(boolean debug) {
this.debug = debug;

View File

@ -71,7 +71,8 @@ package infodynamics.measures.continuous;
* @see "T. M. Cover and J. A. Thomas, 'Elements of Information
Theory' (John Wiley & Sons, New York, 1991)."
*/
public interface MutualInfoCalculatorMultiVariate extends ChannelCalculatorMultiVariate {
public interface MutualInfoCalculatorMultiVariate
extends ChannelCalculator, ChannelCalculatorMultiVariate {
/**
* Property name for time difference between source and destination (0 by default,

View File

@ -259,6 +259,21 @@ public abstract class MutualInfoMultiVariateCommon implements
vectorOfDestinationObservations.add(destToAdd);
}
public void addObservations(double[] source, double[] destination,
int startTime, int numTimeSteps) throws Exception {
if ((dimensionsDest != 1) || (dimensionsSource != 1)) {
throw new Exception("The number of source and dest dimensions (having been initialised to " +
dimensionsSource + " and " + dimensionsDest + ") can only be 1 when " +
"the univariate addObservations(double[],double[]) and " +
"setObservations(double[],double[]) methods are called");
}
addObservations(MatrixUtils.reshape(source, source.length, 1),
MatrixUtils.reshape(destination, destination.length, 1),
startTime, numTimeSteps);
}
public void setObservations(double[][] source, double[][] destination,
boolean[] sourceValid, boolean[] destValid) throws Exception {
@ -274,6 +289,20 @@ public abstract class MutualInfoMultiVariateCommon implements
finaliseAddObservations();
}
public void setObservations(double[] source, double[] destination,
boolean[] sourceValid, boolean[] destValid) throws Exception {
if ((dimensionsDest != 1) || (dimensionsSource != 1)) {
throw new Exception("The number of source and dest dimensions (having been initialised to " +
dimensionsSource + " and " + dimensionsDest + ") can only be 1 when " +
"the univariate addObservations(double[],double[]) and " +
"setObservations(double[],double[]) methods are called");
}
setObservations(MatrixUtils.reshape(source, source.length, 1),
MatrixUtils.reshape(destination, destination.length, 1),
sourceValid, destValid);
}
public void setObservations(double[][] source, double[][] destination,
boolean[][] sourceValid, boolean[][] destValid) throws Exception {
@ -485,6 +514,20 @@ public abstract class MutualInfoMultiVariateCommon implements
return miSurrogateCalculator.computeAverageLocalOfObservations();
}
public double[] computeLocalUsingPreviousObservations(
double[] newSourceObservations, double[] newDestObservations)
throws Exception {
if ((dimensionsDest != 1) || (dimensionsSource != 1)) {
throw new Exception("The number of source and dest dimensions (having been initialised to " +
dimensionsSource + " and " + dimensionsDest + ") can only be 1 when " +
"the univariate addObservations(double[],double[]) and " +
"setObservations(double[],double[]) methods are called");
}
return computeLocalUsingPreviousObservations(
MatrixUtils.reshape(newSourceObservations, newSourceObservations.length, 1),
MatrixUtils.reshape(newDestObservations, newDestObservations.length, 1));
}
public void setDebug(boolean debug) {
this.debug = debug;
}

View File

@ -18,7 +18,7 @@
package infodynamics.measures.continuous;
import infodynamics.utils.EmpiricalMeasurementDistribution;
import infodynamics.utils.EmpiricalNullDistributionComputer;
/**
* Interface for implementations of
@ -94,7 +94,8 @@ import infodynamics.utils.EmpiricalMeasurementDistribution;
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
public interface PredictiveInfoCalculator {
public interface PredictiveInfoCalculator
extends InfoMeasureCalculatorContinuous, EmpiricalNullDistributionComputer {
/**
* Property name for embedding length <code>k</code> of
@ -117,13 +118,6 @@ public interface PredictiveInfoCalculator {
*/
public static final String TAU_PROP_NAME = "TAU";
/**
* Initialise the calculator for (re-)use, with the existing (or default) values of parameters
* Clears any PDFs of previously supplied observations.
*
*/
public void initialise() throws Exception;
/**
* Initialise the calculator for (re-)use, with some parameters
* supplied here, and existing (or default) values of other parameters
@ -166,6 +160,7 @@ public interface PredictiveInfoCalculator {
* @param propertyValue value of the property
* @throws Exception for invalid property values
*/
@Override
public void setProperty(String propertyName, String propertyValue) throws Exception;
/**
@ -234,13 +229,6 @@ public interface PredictiveInfoCalculator {
public void setObservations(double[] observations,
boolean[] valid) throws Exception;
/**
* Compute the PI from the previously-supplied samples.
*
* @return the PI estimate
*/
public double computeAverageLocalOfObservations() throws Exception;
/**
* Compute the local PI values for each of the
* previously-supplied samples.
@ -282,103 +270,4 @@ public interface PredictiveInfoCalculator {
*/
public double[] computeLocalUsingPreviousObservations(double[] newObservations) throws Exception;
/**
* Generate a bootstrapped distribution of what the PI would look like,
* under a null hypothesis that the previous <code>k</code> values of our
* samples had no relation to the next <code>k</code> values in the time-series.
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for PI
* as a mutual information. Basically, the marginal PDFs
* of the past <code>k</code> values, and that of the next <code>k</code> values,
* are preserved, while their joint PDF is destroyed, and the
* distribution of PI under these conditions is generated.</p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(double[])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* <p>This method (in contrast to {@link #computeSignificance(int[][])})
* creates <i>random</i> shufflings of the next vectors for the surrogate PI
* calculations.</p>
*
* @param numPermutationsToCheck number of surrogate samples to bootstrap
* to generate the distribution.
* @return the distribution of PI scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
* @throws Exception
*/
public EmpiricalMeasurementDistribution computeSignificance(int numPermutationsToCheck) throws Exception;
/**
* Generate a bootstrapped distribution of what the PI would look like,
* under a null hypothesis that the previous <code>k</code> values of our
* samples had no relation to the next <code>k</code> values in the time-series.
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for PI
* as a mutual information. Basically, the marginal PDFs
* of the past <code>k</code> values, and that of the next <code>k</code> values,
* are preserved, while their joint PDF is destroyed, and the
* distribution of PI under these conditions is generated.</p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(double[])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* <p>This method (in contrast to {@link #computeSignificance(int)})
* allows the user to specify how to construct the surrogates,
* such that repeatable results may be obtained.</p>
*
* @param newOrderings a specification of how to shuffle the next <code>k</code> values vectors
* to create the surrogates to generate the distribution with. The first
* index is the permutation number (i.e. newOrderings.length is the number
* of surrogate samples we use to bootstrap to generate the distribution here.)
* Each array newOrderings[i] should be an array of length L being
* the value returned by {@link #getNumObservations()},
* containing a permutation of the values in 0..(L-1).
* @return the distribution of PI scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
* @throws Exception where the length of each permutation in newOrderings
* is not equal to the number L observations that were previously supplied.
*/
public EmpiricalMeasurementDistribution computeSignificance(
int[][] newOrderings) throws Exception;
/**
* Set or clear debug mode for extra debug printing to stdout
*
* @param debug new setting for debug mode (on/off)
*/
public void setDebug(boolean debug);
/**
* Return the PI last calculated in a call to {@link #computeAverageLocalOfObservations()}
* or {@link #computeLocalOfPreviousObservations()} after the previous
* {@link #initialise()} call.
*
* @return the last computed PI value
*/
public double getLastAverage();
/**
* Get the number of samples to be used for the PDFs here
* which have been supplied by calls to
* {@link #setObservations(double[])}, {@link #addObservations(double[])}
* etc.
*
* <p>Note that the number of samples is not equal to the length of time-series
* supplied (since we need to accumulate the first and last
* <code>(k-1)*tau + 1</code>
* values of each time-series).
* </p>
*
* @return the number of samples to be used for the PDFs
* @throws Exception
*/
public int getNumObservations() throws Exception;
}

View File

@ -217,6 +217,22 @@ public class PredictiveInfoCalculatorViaMutualInfo implements
}
}
@Override
public String getProperty(String propertyName) throws Exception {
if (propertyName.equalsIgnoreCase(K_PROP_NAME) ||
propertyName.equalsIgnoreCase(K_EMBEDDING_PROP_NAME)) {
return Integer.toString(k);
} else if (propertyName.equalsIgnoreCase(TAU_PROP_NAME)) {
return Integer.toString(tau);
} else {
// No property was set on this class, assume it is for the underlying
// MI calculator, even if it is for
// MutualInfoCalculatorMultiVariate.PROP_TIME_DIFF which
// is not a valid property for the PI calculator:
return miCalc.getProperty(propertyName);
}
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.PredictiveInfoCalculator#setObservations(double[])
*/
@ -372,9 +388,6 @@ public class PredictiveInfoCalculatorViaMutualInfo implements
return startAndEndTimePairs;
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.PredictiveInfoCalculator#computeAverageLocalOfObservations()
*/
@Override
public double computeAverageLocalOfObservations() throws Exception {
return miCalc.computeAverageLocalOfObservations();
@ -417,7 +430,7 @@ public class PredictiveInfoCalculatorViaMutualInfo implements
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.PredictiveInfoCalculator#computeSignificance(int)
* @see infodynamics.utils.EmpiricalNullDistributionComputer#computeSignificance(int)
*/
@Override
public EmpiricalMeasurementDistribution computeSignificance(
@ -426,7 +439,7 @@ public class PredictiveInfoCalculatorViaMutualInfo implements
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.PredictiveInfoCalculator#computeSignificance(int[][])
* @see infodynamics.utils.EmpiricalNullDistributionComputer#computeSignificance(int[][])
*/
@Override
public EmpiricalMeasurementDistribution computeSignificance(
@ -434,26 +447,17 @@ public class PredictiveInfoCalculatorViaMutualInfo implements
return miCalc.computeSignificance(newOrderings);
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.PredictiveInfoCalculator#setDebug(boolean)
*/
@Override
public void setDebug(boolean debug) {
this.debug = debug;
miCalc.setDebug(debug);
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.PredictiveInfoCalculator#getLastAverage()
*/
@Override
public double getLastAverage() {
return miCalc.getLastAverage();
}
/* (non-Javadoc)
* @see infodynamics.measures.continuous.PredictiveInfoCalculator#getNumObservations()
*/
@Override
public int getNumObservations() throws Exception {
return miCalc.getNumObservations();

View File

@ -528,7 +528,8 @@ public class ConditionalMutualInfoCalculatorMultiVariateGaussian
// return new ChiSquareMeasurementDistribution(2.0*((double)totalObservations)*lastAverage,
// dimensionsVar1 * dimensionsVar2);
// Taking the subsets into account:
return new ChiSquareMeasurementDistribution(2.0*((double)totalObservations)*lastAverage,
return new ChiSquareMeasurementDistribution(lastAverage,
totalObservations,
var1IndicesInCovariance.length * var2IndicesInCovariance.length);
}

View File

@ -59,6 +59,16 @@ public class EntropyCalculatorGaussian implements EntropyCalculator {
*/
protected boolean debug;
/**
* Total number of observations supplied.
*/
protected int totalObservations;
/**
* Store the last computed average Entropy
*/
protected double lastAverage;
/**
* Construct an instance
*/
@ -66,6 +76,7 @@ public class EntropyCalculatorGaussian implements EntropyCalculator {
// Nothing to do
}
@Override
public void initialise() {
// Nothing to do
}
@ -73,6 +84,7 @@ public class EntropyCalculatorGaussian implements EntropyCalculator {
public void setObservations(double[] observations) {
variance = MatrixUtils.stdDev(observations);
variance *= variance;
totalObservations = observations.length;
}
/**
@ -100,10 +112,13 @@ public class EntropyCalculatorGaussian implements EntropyCalculator {
* @return the entropy of the previously provided observations or from the supplied
* covariance matrix. Entropy returned in <b>nats</b>, not bits!
*/
@Override
public double computeAverageLocalOfObservations() {
return 0.5 * Math.log(2.0*Math.PI*Math.E*variance);
lastAverage = 0.5 * Math.log(2.0*Math.PI*Math.E*variance);
return lastAverage;
}
@Override
public void setDebug(boolean debug) {
this.debug = debug;
}
@ -111,9 +126,29 @@ public class EntropyCalculatorGaussian implements EntropyCalculator {
/**
* No properties are defined here, so this method will have no effect.
*/
@Override
public void setProperty(String propertyName, String propertyValue)
throws Exception {
// No properties to set here
}
/**
* No properties are defined here, so this method will always return null.
*/
@Override
public String getProperty(String propertyName)
throws Exception {
// No properties to return here
return null;
}
@Override
public int getNumObservations() throws Exception {
return totalObservations;
}
@Override
public double getLastAverage() {
return lastAverage;
}
}

View File

@ -72,22 +72,22 @@ public class EntropyCalculatorMultiVariateGaussian
/**
* Number of dimensions for our multivariate data
*/
protected int dimensions;
protected int dimensions = 1;
/**
* Determinant of the covariance matrix; stored to save computation time
*/
protected double detCovariance;
protected double detCovariance = 0.0;
/**
* Last average entropy we computed
*/
protected double lastAverage;
protected double lastAverage = 0;
/**
* Whether we are in debug mode
*/
protected boolean debug;
protected boolean debug = false;
/**
* Construct an instance
@ -96,12 +96,18 @@ public class EntropyCalculatorMultiVariateGaussian
// Nothing to do
}
@Override
public void initialise() throws Exception {
initialise(dimensions);
}
public void initialise(int dimensions) {
means = null;
L = null;
observations = null;
this.dimensions = dimensions;
detCovariance = 0;
lastAverage = 0.0;
}
/**
@ -109,6 +115,7 @@ public class EntropyCalculatorMultiVariateGaussian
* dimensions, or covariance matrix is not positive definite (reflecting
* redundant variables in the observations)
*/
@Override
public void setObservations(double[][] observations) throws Exception {
// Check that the observations was of the correct number of dimensions:
if (observations[0].length != dimensions) {
@ -187,6 +194,7 @@ public class EntropyCalculatorMultiVariateGaussian
* @return the joint entropy of the previously provided observations or from the
* supplied covariance matrix. Returned in nats (NOT bits).
*/
@Override
public double computeAverageLocalOfObservations() {
// Simple way:
// detCovariance = MatrixUtils.determinantSymmPosDefMatrix(covariance);
@ -197,18 +205,38 @@ public class EntropyCalculatorMultiVariateGaussian
return lastAverage;
}
@Override
public void setDebug(boolean debug) {
this.debug = debug;
}
/**
* No properties are defined here, so this method will have no effect.
*/
@Override
public void setProperty(String propertyName, String propertyValue)
throws Exception {
// No properties to set here
boolean propertySet = true;
if (propertyName.equalsIgnoreCase(NUM_DIMENSIONS_PROP_NAME)) {
dimensions = Integer.parseInt(propertyValue);
} else {
// No property was set
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(NUM_DIMENSIONS_PROP_NAME)) {
return Integer.toString(dimensions);
} else {
// No property was set, and no superclass to call:
return null;
}
}
@Override
public double getLastAverage() {
return lastAverage;
}

View File

@ -2,7 +2,6 @@ package infodynamics.measures.continuous.gaussian;
import infodynamics.measures.continuous.MultiInfoCalculatorCommon;
import infodynamics.utils.MatrixUtils;
import infodynamics.utils.MathsUtils;
/**
* <p>Computes the differential multi-information of a given multivariate

View File

@ -342,7 +342,8 @@ public class MutualInfoCalculatorMultiVariateGaussian
* @throws Exception
*/
public ChiSquareMeasurementDistribution computeSignificance() {
return new ChiSquareMeasurementDistribution(2*totalObservations*lastAverage,
return new ChiSquareMeasurementDistribution(lastAverage,
totalObservations,
dimensionsSource * dimensionsDest);
}
@ -517,5 +518,4 @@ public class MutualInfoCalculatorMultiVariateGaussian
return localValues;
}
}

View File

@ -50,6 +50,10 @@ public class EntropyCalculatorKernel implements EntropyCalculator {
* Number of observations supplied
*/
protected int totalObservations = 0;
/**
* Last computed average
*/
private double lastAverage;
/**
* Whether we're in debug mode
*/
@ -93,8 +97,10 @@ public class EntropyCalculatorKernel implements EntropyCalculator {
svke = new KernelEstimatorUniVariate();
svke.setDebug(debug);
svke.setNormalise(normalise);
lastAverage = 0.0;
}
@Override
public void initialise() {
initialise(kernelWidth);
}
@ -110,15 +116,18 @@ public class EntropyCalculatorKernel implements EntropyCalculator {
*/
public void initialise(double kernelWidth) {
this.kernelWidth = kernelWidth;
lastAverage = 0.0;
svke.initialise(kernelWidth);
}
@Override
public void setObservations(double observations[]) {
this.observations = observations;
svke.setObservations(observations);
totalObservations = observations.length;
}
@Override
public double computeAverageLocalOfObservations() {
double entropy = 0.0;
for (int t = 0; t < observations.length; t++) {
@ -131,9 +140,11 @@ public class EntropyCalculatorKernel implements EntropyCalculator {
(entropy/Math.log(2.0)));
}
}
return entropy / (double) totalObservations / Math.log(2.0);
lastAverage = entropy / (double) totalObservations / Math.log(2.0);
return lastAverage;
}
@Override
public void setDebug(boolean debug) {
this.debug = debug;
if (svke != null) {
@ -163,6 +174,7 @@ public class EntropyCalculatorKernel implements EntropyCalculator {
* @param propertyValue value of the property
* @throws Exception for invalid property values
*/
@Override
public void setProperty(String propertyName, String propertyValue) throws Exception {
boolean propertySet = true;
@ -186,4 +198,27 @@ public class EntropyCalculatorKernel implements EntropyCalculator {
}
}
@Override
public String getProperty(String propertyName) throws Exception {
if (propertyName.equalsIgnoreCase(KERNEL_WIDTH_PROP_NAME) ||
propertyName.equalsIgnoreCase(EPSILON_PROP_NAME)) {
return Double.toString(kernelWidth);
} else if (propertyName.equalsIgnoreCase(NORMALISE_PROP_NAME)) {
return Boolean.toString(normalise);
} else {
// no superclass to try:
return null;
}
}
@Override
public int getNumObservations() throws Exception {
return totalObservations;
}
@Override
public double getLastAverage() {
return lastAverage;
}
}

View File

@ -74,6 +74,11 @@ public class EntropyCalculatorMultiVariateKernel implements EntropyCalculatorMul
*/
public static final String NORMALISE_PROP_NAME = "NORMALISE";
/**
* Number of joint variables/dimensions
*/
protected int dimensions = 1;
/**
* Default value for kernel width
*/
@ -101,6 +106,11 @@ public class EntropyCalculatorMultiVariateKernel implements EntropyCalculatorMul
lastEntropy = 0.0;
}
@Override
public void initialise() throws Exception {
initialise(dimensions);
}
public void initialise(int dimensions) {
initialise(dimensions, kernelWidth);
}
@ -118,17 +128,20 @@ public class EntropyCalculatorMultiVariateKernel implements EntropyCalculatorMul
*/
public void initialise(int dimensions, double kernelWidth) {
this.kernelWidth = kernelWidth;
this.dimensions = dimensions;
mvke.initialise(dimensions, kernelWidth);
// this.dimensions = dimensions;
lastEntropy = 0.0;
}
@Override
public void setObservations(double observations[][]) {
mvke.setObservations(observations);
totalObservations = observations.length;
this.observations = observations;
}
@Override
public double computeAverageLocalOfObservations() {
double entropy = 0.0;
for (int b = 0; b < totalObservations; b++) {
@ -143,12 +156,28 @@ public class EntropyCalculatorMultiVariateKernel implements EntropyCalculatorMul
return lastEntropy;
}
@Override
public double[] computeLocalOfPreviousObservations() {
return computeLocalUsingPreviousObservations(observations);
return computeLocalUsingPreviousObservations(true, observations);
}
@Override
public double[] computeLocalUsingPreviousObservations(double states[][]) {
return computeLocalUsingPreviousObservations(false, states);
}
/**
* Internal method for computing locals of a set of observations
* based on existing PDFs.
*
* @param isPreviousObservations whether we're using the points
* that the PDFs were computed from or not.
* @param states samples to compute the local joint entropies on.
* @return
*/
protected double[] computeLocalUsingPreviousObservations(
boolean isPreviousObservations, double states[][]) {
double entropy = 0.0;
double[] localEntropy = new double[states.length];
for (int b = 0; b < states.length; b++) {
@ -161,14 +190,19 @@ public class EntropyCalculatorMultiVariateKernel implements EntropyCalculatorMul
}
}
entropy /= (double) totalObservations / Math.log(2.0);
if (isPreviousObservations) {
lastEntropy = entropy;
}
return localEntropy;
}
@Override
public void setDebug(boolean debug) {
this.debug = debug;
mvke.setDebug(debug);
}
@Override
public double getLastAverage() {
return lastEntropy;
}
@ -187,6 +221,7 @@ public class EntropyCalculatorMultiVariateKernel implements EntropyCalculatorMul
* is an absolute value. Default is {@link #DEFAULT_KERNEL_WIDTH}.</li>
* <li>{@link #NORMALISE_PROP_NAME} -- whether to normalise the incoming variable values
* to mean 0, standard deviation 1, or not (default false). Sets {@link #normalise}.</li>
* <li>any valid properties for {@link EntropyCalculatorMultiVariate#setProperty(String, String)}.</li>
* </ul>
*
* <p>Unknown property values are ignored.</p>
@ -195,6 +230,7 @@ public class EntropyCalculatorMultiVariateKernel implements EntropyCalculatorMul
* @param propertyValue value of the property
* @throws Exception for invalid property values
*/
@Override
public void setProperty(String propertyName, String propertyValue) throws Exception {
boolean propertySet = true;
@ -208,6 +244,8 @@ public class EntropyCalculatorMultiVariateKernel implements EntropyCalculatorMul
} else if (propertyName.equalsIgnoreCase(NORMALISE_PROP_NAME)) {
normalise = Boolean.parseBoolean(propertyValue);
mvke.setNormalise(normalise);
} else if (propertyName.equalsIgnoreCase(NUM_DIMENSIONS_PROP_NAME)) {
dimensions = Integer.parseInt(propertyValue);
} else {
// No property was set
propertySet = false;
@ -218,6 +256,22 @@ public class EntropyCalculatorMultiVariateKernel implements EntropyCalculatorMul
}
}
@Override
public String getProperty(String propertyName) throws Exception {
if (propertyName.equalsIgnoreCase(KERNEL_WIDTH_PROP_NAME) ||
propertyName.equalsIgnoreCase(EPSILON_PROP_NAME)) {
return Double.toString(kernelWidth);
} else if (propertyName.equalsIgnoreCase(NORMALISE_PROP_NAME)) {
return Boolean.toString(normalise);
} else if (propertyName.equalsIgnoreCase(NUM_DIMENSIONS_PROP_NAME)) {
return Integer.toString(dimensions);
} else {
// No property was set, and no superclass to call:
return null;
}
}
@Override
public int getNumObservations() throws Exception {
return totalObservations;
}

View File

@ -209,6 +209,19 @@ public class MultiInfoCalculatorKernel
}
}
@Override
public String getProperty(String propertyName) throws Exception {
if (propertyName.equalsIgnoreCase(KERNEL_WIDTH_PROP_NAME) ||
propertyName.equalsIgnoreCase(EPSILON_PROP_NAME)) {
return Double.toString(kernelWidth);
} else if (propertyName.equalsIgnoreCase(DYN_CORR_EXCL_TIME_NAME)) {
return Integer.toString(dynCorrExclTime);
} else {
// Try the superclass, including for PROP_NORMALISE
return super.getProperty(propertyName);
}
}
@Override
public void startAddObservations() {
if (dynCorrExcl) {

View File

@ -65,7 +65,7 @@ public class MutualInfoCalculatorMultiVariateKernel
public static final String NORMALISE_PROP_NAME = "NORMALISE";
private boolean dynCorrExcl = false;
private int dynCorrExclTime = 100;
private int dynCorrExclTime = 0;
/**
* Property name for a dynamics exclusion time window (see Kantz and Schreiber),
* default is 0 which means no dynamic exclusion window.

View File

@ -18,9 +18,11 @@
package infodynamics.measures.continuous.kozachenko;
import infodynamics.measures.continuous.EntropyCalculator;
import infodynamics.measures.continuous.EntropyCalculatorMultiVariate;
import infodynamics.utils.EuclideanUtils;
import infodynamics.utils.MathsUtils;
import infodynamics.utils.MatrixUtils;
/**
* <p>Computes the differential entropy of a given set of observations
@ -56,13 +58,13 @@ import infodynamics.utils.MathsUtils;
* <a href="http://lizier.me/joseph/">www</a>)
*/
public class EntropyCalculatorMultiVariateKozachenko
implements EntropyCalculatorMultiVariate {
implements EntropyCalculator, EntropyCalculatorMultiVariate {
protected boolean debug = false;
private int totalObservations;
private int dimensions;
private int dimensions = 1;
protected double[][] rawData;
private double lastEntropy;
private double lastAverage = 0.0;
private double[] lastLocalEntropy;
private boolean isComputed;
@ -73,11 +75,15 @@ public class EntropyCalculatorMultiVariateKozachenko
*/
public EntropyCalculatorMultiVariateKozachenko() {
totalObservations = 0;
dimensions = 0;
isComputed = false;
lastLocalEntropy = null;
}
@Override
public void initialise() throws Exception {
initialise(dimensions);
}
public void initialise(int dimensions) {
this.dimensions = dimensions;
rawData = null;
@ -86,14 +92,33 @@ public class EntropyCalculatorMultiVariateKozachenko
lastLocalEntropy = null;
}
/**
* No properties are defined here, so this method will have no effect.
*/
@Override
public void setProperty(String propertyName, String propertyValue)
throws Exception {
// No properties here to set
boolean propertySet = true;
if (propertyName.equalsIgnoreCase(NUM_DIMENSIONS_PROP_NAME)) {
dimensions = Integer.parseInt(propertyValue);
} else {
// No property was set
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(NUM_DIMENSIONS_PROP_NAME)) {
return Integer.toString(dimensions);
} else {
// No property was set, and no superclass to call:
return null;
}
}
@Override
public void setObservations(double[][] observations) {
rawData = observations;
totalObservations = observations.length;
@ -101,6 +126,19 @@ public class EntropyCalculatorMultiVariateKozachenko
lastLocalEntropy = null;
}
/*
* (non-Javadoc)
* @see infodynamics.measures.continuous.EntropyCalculator#setObservations(double[])
*
* This method here to ensure we make compatibility with the
* EntropyCalculator interface.
*/
@Override
public void setObservations(double[] observations) {
rawData = MatrixUtils.reshape(observations, observations.length, 1);
setObservations(rawData);
}
/**
* Each row of the data is an observation; each column of
* the row is a new variable in the multivariate observation.
@ -137,9 +175,10 @@ public class EntropyCalculatorMultiVariateKozachenko
/**
* @return entropy in natural units
*/
@Override
public double computeAverageLocalOfObservations() {
if (isComputed) {
return lastEntropy;
return lastAverage;
}
double sdTermHere = sdTerm(totalObservations, dimensions);
double emConstHere = eulerMacheroniTerm(totalObservations);
@ -167,7 +206,7 @@ public class EntropyCalculatorMultiVariateKozachenko
}
entropy += emConstHere;
entropy += sdTermHere;
lastEntropy = entropy;
lastAverage = entropy;
isComputed = true;
return entropy;
}
@ -175,6 +214,7 @@ public class EntropyCalculatorMultiVariateKozachenko
/**
* @return local entropies in natural units
*/
@Override
public double[] computeLocalOfPreviousObservations() {
if (lastLocalEntropy != null) {
return lastLocalEntropy;
@ -205,7 +245,7 @@ public class EntropyCalculatorMultiVariateKozachenko
}
}
entropy /= (double) totalObservations;
lastEntropy = entropy;
lastAverage = entropy;
lastLocalEntropy = localEntropy;
return localEntropy;
}
@ -213,6 +253,7 @@ public class EntropyCalculatorMultiVariateKozachenko
/**
* Not implemented yet
*/
@Override
public double[] computeLocalUsingPreviousObservations(double[][] states) throws Exception {
throw new Exception("Local method for other data not implemented");
}
@ -289,14 +330,17 @@ public class EntropyCalculatorMultiVariateKozachenko
return result;
}
@Override
public void setDebug(boolean debug) {
this.debug = debug;
}
@Override
public double getLastAverage() {
return lastEntropy;
return lastAverage;
}
@Override
public int getNumObservations() {
return totalObservations;
}

View File

@ -232,4 +232,14 @@ public class ConditionalTransferEntropyCalculatorKraskov
props.put(propertyName, propertyValue); // This will keep properties for the super class as well as the cond MI calculator, but this is ok
}
}
@Override
public String getProperty(String propertyName) throws Exception {
if (propertyName.equalsIgnoreCase(PROP_KRASKOV_ALG_NUM)) {
return Integer.toString(kraskovAlgorithmNumber);
} else {
// Assume it was a property for the parent class or underlying conditional MI calculator
return super.getProperty(propertyName);
}
}
}

View File

@ -183,7 +183,7 @@ public abstract class MultiInfoCalculatorKraskov
* <ul>
* <li>{@link #PROP_K} -- number of k nearest neighbours to use in joint kernel space
* in the KSG algorithm (default is 4).</li>
* <li>{@link #PROP_NORM_TYPE} -- normalization type to apply to
* <li>{@link #PROP_NORM_TYPE} -- norm type to apply to
* working out the norms between the points in each marginal space.
* Options are defined by {@link KdTree#setNormType(String)} -
* default is {@link EuclideanUtils#NORM_MAX_NORM}.</li>
@ -198,7 +198,9 @@ public abstract class MultiInfoCalculatorKraskov
* so can be considered as a number of standard deviations of the data.
* (Recommended by Kraskov. MILCA uses 1e-8; but adds in
* a random amount of noise in [0,noiseLevel) ).
* Default 1e-8 to match the noise order in MILCA toolkit..</li>
* Default 1e-8 to match the noise order in MILCA toolkit.</li>
* <li>Any property accepted by the superclass
* {@link MultiInfoCalculatorCommon#setProperty(String, String)}</li>
* </ul>
*
* <p>Unknown property values are ignored.</p>
@ -244,6 +246,24 @@ public abstract class MultiInfoCalculatorKraskov
}
}
@Override
public String getProperty(String propertyName) throws Exception {
if (propertyName.equalsIgnoreCase(PROP_K)) {
return Integer.toString(k);
} else if (propertyName.equalsIgnoreCase(PROP_NORM_TYPE)) {
return KdTree.convertNormTypeToString(normType);
} else if (propertyName.equalsIgnoreCase(PROP_DYN_CORR_EXCL_TIME)) {
return Integer.toString(dynCorrExclTime);
} else if (propertyName.equalsIgnoreCase(PROP_ADD_NOISE)) {
return Double.toString(noiseLevel);
} else if (propertyName.equalsIgnoreCase(PROP_NUM_THREADS)) {
return Integer.toString(numThreads);
} else {
// try the superclass, including for PROP_NORMALISE:
return super.getProperty(propertyName);
}
}
/**
* Set observations from two separate time series: join the rows at each time step
* together to make a joint vector, then effectively call {@link #setObservations(double[][])}

View File

@ -120,6 +120,49 @@ public class MutualInfoCalculatorMultiVariateKraskov1
} else {
return new double[] {sumDiGammas, sumNx, sumNy};
}
}
public int[][] partialNeighbourCountFromObservations(
int startTimePoint, int numTimePoints) throws Exception {
double startTime = Calendar.getInstance().getTimeInMillis();
ensureKdTreesConstructed();
int[][] neighbourCounts = new int[numTimePoints][2];
for (int t = startTimePoint; t < startTimePoint + numTimePoints; t++) {
// Compute eps for this time step by
// finding the kth closest neighbour for point t:
PriorityQueue<NeighbourNodeData> nnPQ =
kdTreeJoint.findKNearestNeighbours(k, t, dynCorrExclTime);
// First element in the PQ is the kth NN,
// and epsilon = kthNnData.distance
NeighbourNodeData kthNnData = nnPQ.poll();
// Count the number of points whose x distance is less
// than eps, and whose y distance is less than
// epsilon = kthNnData.distance
int n_x = nnSearcherSource.countPointsStrictlyWithinR(
t, kthNnData.distance, dynCorrExclTime);
int n_y = nnSearcherDest.countPointsStrictlyWithinR(
t, kthNnData.distance, dynCorrExclTime);
neighbourCounts[t - startTimePoint][0] = n_x;
neighbourCounts[t - startTimePoint][1] = n_y;
}
if (debug) {
Calendar rightNow2 = Calendar.getInstance();
long endTime = rightNow2.getTimeInMillis();
System.out.println("Subset " + startTimePoint + ":" +
(startTimePoint + numTimePoints) + " Calculation time: " +
((endTime - startTime)/1000.0) + " sec" );
}
return neighbourCounts;
}
}

View File

@ -18,7 +18,11 @@
package infodynamics.measures.discrete;
import infodynamics.utils.AnalyticMeasurementDistribution;
import infodynamics.utils.AnalyticNullDistributionComputer;
import infodynamics.utils.ChiSquareMeasurementDistribution;
import infodynamics.utils.EmpiricalMeasurementDistribution;
import infodynamics.utils.EmpiricalNullDistributionComputer;
import infodynamics.utils.MatrixUtils;
import infodynamics.utils.RandomGenerator;
@ -68,8 +72,11 @@ import infodynamics.utils.RandomGenerator;
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscreteInContextOfPastCalculator {
public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscreteInContextOfPastCalculator
implements EmpiricalNullDistributionComputer, AnalyticNullDistributionComputer {
protected boolean aisComputed = false;
/**
* User was formerly forced to create new instances through this factory method.
* Retained for backwards compatibility.
@ -95,6 +102,12 @@ public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscr
super(base, history);
}
@Override
public void initialise() {
super.initialise();
aisComputed = false;
}
@Override
public void addObservations(int states[]) {
int timeSteps = states.length;
@ -282,7 +295,7 @@ public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscr
// Compute MI contribution:
if (p_joint > 0.0) {
double logTerm = p_joint / (p_next * p_prev);
double localValue = Math.log(logTerm) / log_base;
double localValue = Math.log(logTerm) / log_2;
miCont = p_joint * localValue;
if (localValue > max) {
max = localValue;
@ -297,6 +310,7 @@ public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscr
}
average = mi;
aisComputed = true;
return mi;
}
@ -320,7 +334,7 @@ public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscr
if (p_joint > 0.0) {
double logTerm = p_joint / p_prev;
// Entropy rate takes the negative log:
double localValue = - Math.log(logTerm) / log_base;
double localValue = - Math.log(logTerm) / log_2;
entRateCont = p_joint * localValue;
} else {
entRateCont = 0.0;
@ -348,7 +362,7 @@ public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscr
( (double) nextCount[next] *
(double) pastCount[past] );
logTerm *= (double) observations;
return Math.log(logTerm) / log_base;
return Math.log(logTerm) / log_2;
}
@Override
@ -380,7 +394,7 @@ public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscr
// and we've got two counts on the bottom
// but one count on the top:
logTerm *= (double) observations;
localActive[t] = Math.log(logTerm) / log_base;
localActive[t] = Math.log(logTerm) / log_2;
average += localActive[t];
if (localActive[t] > max) {
max = localActive[t];
@ -431,7 +445,7 @@ public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscr
// and we've got two counts on the bottom
// but one count on the top:
logTerm *= (double) observations;
localActive[r][c] = Math.log(logTerm) / log_base;
localActive[r][c] = Math.log(logTerm) / log_2;
average += localActive[r][c];
if (localActive[r][c] > max) {
max = localActive[r][c];
@ -487,7 +501,7 @@ public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscr
// and we've got two counts on the bottom
// but one count on the top:
logTerm *= (double) observations;
localActive[t][r][c] = Math.log(logTerm) / log_base;
localActive[t][r][c] = Math.log(logTerm) / log_2;
average += localActive[t][r][c];
if (localActive[t][r][c] > max) {
max = localActive[t][r][c];
@ -536,7 +550,7 @@ public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscr
// and we've got two counts on the bottom
// but one count on the top:
logTerm *= (double) observations;
localActive[r] = Math.log(logTerm) / log_base;
localActive[r] = Math.log(logTerm) / log_2;
average += localActive[r];
if (localActive[r] > max) {
max = localActive[r];
@ -585,7 +599,7 @@ public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscr
// and we've got two counts on the bottom
// but one count on the top:
logTerm *= (double) observations;
localActive[t] = Math.log(logTerm) / log_base;
localActive[t] = Math.log(logTerm) / log_2;
average += localActive[t];
if (localActive[t] > max) {
max = localActive[t];
@ -721,6 +735,17 @@ public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscr
return measDistribution;
}
@Override
public AnalyticMeasurementDistribution computeSignificance()
throws Exception {
if (!aisComputed) {
computeAverageLocalOfObservations();
}
return new ChiSquareMeasurementDistribution(average,
observations,
(base - 1) * (base_power_k - 1));
}
/**
* Debug method to write the current probability distribution functions
*
@ -742,7 +767,7 @@ public class ActiveInformationCalculatorDiscrete extends SingleAgentMeasureDiscr
// Compute MI contribution:
if (p_joint * p_next * p_prev > 0.0) {
double logTerm = p_joint / (p_next * p_prev);
double localValue = Math.log(logTerm) / log_base;
double localValue = Math.log(logTerm) / log_2;
miCont = p_joint * localValue;
System.out.println(String.format("%7d %.2f %7d %.2f %.2f %.2f %.2f",
nextVal, p_next, prevVal, p_prev, p_joint, logTerm, localValue));

View File

@ -18,7 +18,7 @@
package infodynamics.measures.discrete;
import infodynamics.utils.EmpiricalMeasurementDistribution;
import infodynamics.utils.EmpiricalNullDistributionComputer;
/**
* A basic interface for calculators computing measures on a univariate <i>channel</i>
@ -54,7 +54,7 @@ import infodynamics.utils.EmpiricalMeasurementDistribution;
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
public interface ChannelCalculatorDiscrete {
public interface ChannelCalculatorDiscrete extends EmpiricalNullDistributionComputer {
/**
* Initialise the calculator for (re-)use, with the existing
@ -104,27 +104,4 @@ public interface ChannelCalculatorDiscrete {
*/
public double computeAverageLocalOfObservations();
/**
* Generate a bootstrapped distribution of what the channel measure would look like,
* under a null hypothesis that the source values of our
* samples had no relation to the destination value.
* (Precise null hypothesis varies between MI and TE).
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for MI,
* conditional MI and TE.
* </p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(int[], int[])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* @param numPermutationsToCheck number of surrogate samples to bootstrap
* to generate the distribution.
* @return the distribution of channel measure scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
*/
public EmpiricalMeasurementDistribution computeSignificance(int numPermutationsToCheck);
}

View File

@ -21,6 +21,7 @@ package infodynamics.measures.discrete;
import infodynamics.utils.AnalyticMeasurementDistribution;
import infodynamics.utils.AnalyticNullDistributionComputer;
import infodynamics.utils.ChiSquareMeasurementDistribution;
import infodynamics.utils.EmpiricalNullDistributionComputer;
import infodynamics.utils.MatrixUtils;
import infodynamics.utils.EmpiricalMeasurementDistribution;
import infodynamics.utils.RandomGenerator;
@ -64,7 +65,8 @@ Theory' (John Wiley & Sons, New York, 1991).</li>
* <a href="http://lizier.me/joseph/">www</a>)
*/
public class ConditionalMutualInformationCalculatorDiscrete
extends InfoMeasureCalculatorDiscrete implements AnalyticNullDistributionComputer {
extends InfoMeasureCalculatorDiscrete
implements EmpiricalNullDistributionComputer, AnalyticNullDistributionComputer {
/**
* Store the number of symbols for each variable
@ -403,29 +405,13 @@ public class ConditionalMutualInformationCalculatorDiscrete
}
/**
* Generate a bootstrapped distribution of what the conditional MI would look like,
* under a null hypothesis that the source values of our
* samples had no relation to the destination value (in the
* context of the conditional).
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for MI,
* conditional MI and TE.
* <b>Note that this method currently fixes the relationship
* <p><b>Note</b> -- in contrast to the generic computeSignificance() method
* described in the documentation below, this method for a conditional MI calculator currently fixes the relationship
* between variable 2 and the conditional, and shuffles
* variable 1 with respect to these.</b>
* variable 1 with respect to these.
* </p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(int[], int[])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* @param numPermutationsToCheck number of surrogate samples to bootstrap
* to generate the distribution.
* @return the distribution of conditional MI scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
* @inheritDoc
*/
public EmpiricalMeasurementDistribution computeSignificance(int numPermutationsToCheck) {
RandomGenerator rg = new RandomGenerator();
@ -435,46 +421,17 @@ public class ConditionalMutualInformationCalculatorDiscrete
}
/**
* Generate a bootstrapped distribution of what the conditional MI would look like,
* under a null hypothesis that the source values of our
* samples had no relation to the destination values.
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for
* a conditional mutual information. Basically, the marginal PDFs
* of each marginal
* are preserved, while their joint PDF is destroyed, and the
* distribution of conditional MI under these conditions is generated.
* <b>Note that this method currently fixes the relationship
* <p><b>Note</b> -- in contrast to the generic computeSignificance() method
* described in the documentation below, this method for a conditional MI calculator currently fixes the relationship
* between variable 2 and the conditional, and shuffles
* variable 1 with respect to these.</b>
* variable 1 with respect to these.
* </p>
*
* TODO Need to alter the method signature to allow callers to specify
* which variable is shuffled. (Note to self: when doing this, will
* need to update machine learning code to the new method signature)
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(double[])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* <p>This method (in contrast to {@link #computeSignificance(int)})
* allows the user to specify how to construct the surrogates,
* such that repeatable results may be obtained.</p>
*
* @param newOrderings a specification of how to shuffle the values
* of variable 1
* to create the surrogates to generate the distribution with. The first
* index is the permutation number (i.e. newOrderings.length is the number
* of surrogate samples we use to bootstrap to generate the distribution here.)
* Each array newOrderings[i] should be an array of length N (where
* would be the value returned by {@link #getNumObservations()}),
* containing a permutation of the values in 0..(N-1).
* @return the distribution of conditional MI scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
* @throws Exception where the length of each permutation in newOrderings
* is not equal to the number N samples that were previously supplied.
* @inheritDoc
*/
public EmpiricalMeasurementDistribution computeSignificance(int[][] newOrderings) {
@ -544,7 +501,8 @@ public class ConditionalMutualInformationCalculatorDiscrete
if (!condMiComputed) {
computeAverageLocalOfObservations();
}
return new ChiSquareMeasurementDistribution(2.0*((double)observations)*average,
return new ChiSquareMeasurementDistribution(average,
observations,
(base1 - 1)*(base2 - 1)*condBase);
}

View File

@ -18,6 +18,7 @@
package infodynamics.measures.discrete;
import infodynamics.utils.EmpiricalNullDistributionComputer;
import infodynamics.utils.MathsUtils;
import infodynamics.utils.MatrixUtils;
import infodynamics.utils.EmpiricalMeasurementDistribution;
@ -109,10 +110,12 @@ import infodynamics.utils.RandomGenerator;
* <a href="http://www.doc.ic.ac.uk/~pam213">www</a>)
*
*/
public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCalculatorDiscrete {
public class ConditionalTransferEntropyCalculatorDiscrete
extends InfoMeasureCalculatorDiscrete
implements EmpiricalNullDistributionComputer {
protected int k = 0; // history length k.
protected int base_others = 0; // base of the conditional variables
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;
@ -223,7 +226,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
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);
@ -624,32 +627,20 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
return te;
}
/**
* Generate a bootstrapped distribution of what the
* conditional TE would look like,
* under a null hypothesis that the source values of our
* samples had no relation to the destination value
* (in the context of the destination past and conditionals).
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for MI,
* conditional MI and TE.
* </p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(int[], int[])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* @param numPermutationsToCheck number of surrogate samples to bootstrap
* to generate the distribution.
* @return the distribution of conditional TE scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
*/
@Override
public EmpiricalMeasurementDistribution computeSignificance(int numPermutationsToCheck) {
RandomGenerator rg = new RandomGenerator();
// (Not necessary to check for distinct random perturbations)
int[][] newOrderings = rg.generateRandomPerturbations(observations, numPermutationsToCheck);
return computeSignificance(newOrderings);
}
@Override
public EmpiricalMeasurementDistribution computeSignificance(int[][] newOrderings) {
double actualTE = computeAverageLocalOfObservations();
int numPermutationsToCheck = newOrderings.length;
// Reconstruct the source values (not necessarily in order)
int[] sourceValues = new int[observations];
int t_s = 0;
@ -694,12 +685,7 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
}
}
// Construct new source orderings based on the source probabilities only
// Generate the re-ordered indices:
RandomGenerator rg = new RandomGenerator();
// (Not necessary to check for distinct random perturbations)
int[][] newOrderings = rg.generateRandomPerturbations(observations, numPermutationsToCheck);
// TODO stop using deprecated method
ConditionalTransferEntropyCalculatorDiscrete cte = newInstance(base, k, numOtherInfoContributors);
cte.initialise();
cte.observations = observations;
@ -957,7 +943,7 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
* (indexed only by time step)
* @return time-series of local conditional TE values
*/
private double[] computeLocalFromPreviousObservations
public double[] computeLocalFromPreviousObservations
(int source[], int dest[], int[] conditional){
int rows = source.length;
@ -1015,7 +1001,7 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
* (indexed first by time step then by variable number)
* @return time-series of local conditional TE values
*/
private double[] computeLocalFromPreviousObservations
public double[] computeLocalFromPreviousObservations
(int source[], int dest[], int[][] conditionals){
int rows = source.length;

View File

@ -222,40 +222,7 @@ public class MutualInformationCalculatorDiscrete extends InfoMeasureCalculatorDi
return computeSignificance(newOrderings);
}
/**
* Generate a bootstrapped distribution of what the MI would look like,
* under a null hypothesis that the source values of our
* samples had no relation to the destination values.
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for
* a mutual information. Basically, the marginal PDFs
* of each marginal
* are preserved, while their joint PDF is destroyed, and the
* distribution of MI under these conditions is generated.</p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using {@link #addObservations(double[])} etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* <p>This method (in contrast to {@link #computeSignificance(int)})
* allows the user to specify how to construct the surrogates,
* such that repeatable results may be obtained.</p>
*
* @param newOrderings a specification of how to shuffle the next values
* to create the surrogates to generate the distribution with. The first
* index is the permutation number (i.e. newOrderings.length is the number
* of surrogate samples we use to bootstrap to generate the distribution here.)
* Each array newOrderings[i] should be an array of length N (where
* would be the value returned by {@link #getNumObservations()}),
* containing a permutation of the values in 0..(N-1).
* @return the distribution of MI scores under this null hypothesis.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
* @throws Exception where the length of each permutation in newOrderings
* is not equal to the number N samples that were previously supplied.
*/
@Override
public EmpiricalMeasurementDistribution computeSignificance(int[][] newOrderings) {
double actualMI = computeAverageLocalOfObservations();
@ -317,7 +284,8 @@ public class MutualInformationCalculatorDiscrete extends InfoMeasureCalculatorDi
if (!miComputed) {
computeAverageLocalOfObservations();
}
return new ChiSquareMeasurementDistribution(2.0*((double)observations)*average,
return new ChiSquareMeasurementDistribution(average,
observations,
(base - 1) * (base - 1));
}
@ -386,7 +354,6 @@ public class MutualInformationCalculatorDiscrete extends InfoMeasureCalculatorDi
}
}
average = average/(double) observations;
miComputed = true;
return localMI;
}

View File

@ -1107,14 +1107,17 @@ public class TransferEntropyCalculatorDiscrete extends ContextOfPastMeasureCalcu
for (int pastVal = 0; pastVal < base_power_k; pastVal++) {
// compute p(past)
// double p_past = (double) pastCount[pastVal] / (double) observations;
if (pastCount[pastVal] == 0) {
continue;
}
for (int destVal = 0; destVal < base; destVal++) {
// compute p(dest,past)
// double p_dest_past = (double) destPastCount[destVal][pastVal] / (double) observations;
if (nextPastCount[destVal][pastVal] == 0) {
continue;
}
double denom = (double) nextPastCount[destVal][pastVal] / (double) pastCount[pastVal];
for (int sourceVal = 0; sourceVal < base_power_l; sourceVal++) {
// compute p(source,dest,past)
double p_source_dest_past = (double) sourceNextPastCount[sourceVal][destVal][pastVal] / (double) observations;
// compute p(source,past)
// double p_source_past = (double) sourcePastCount[sourceVal][pastVal] / (double) observations;
// Compute TE contribution:
if (sourceNextPastCount[sourceVal][destVal][pastVal] != 0) {
/* Double check: should never happen
@ -1125,9 +1128,13 @@ public class TransferEntropyCalculatorDiscrete extends ContextOfPastMeasureCalcu
}
*/
// compute p(source,dest,past)
double p_source_dest_past = (double) sourceNextPastCount[sourceVal][destVal][pastVal] / (double) observations;
// compute p(source,past)
// double p_source_past = (double) sourcePastCount[sourceVal][pastVal] / (double) observations;
double logTerm = ((double) sourceNextPastCount[sourceVal][destVal][pastVal] / (double) sourcePastCount[sourceVal][pastVal]) /
((double) nextPastCount[destVal][pastVal] / (double) pastCount[pastVal]);
double localValue = Math.log(logTerm) / log_2;
(denom);
double localValue = Math.log(logTerm); // We'll / log_2 later, to save a floating pt op;
teCont = p_source_dest_past * localValue;
if (localValue > max) {
max = localValue;
@ -1145,6 +1152,11 @@ public class TransferEntropyCalculatorDiscrete extends ContextOfPastMeasureCalcu
}
}
te /= log_2;
max /= log_2;
min /= log_2;
meanSqLocals /= (log_2 * log_2);
average = te;
std = Math.sqrt(meanSqLocals - average * average);
estimateComputed = true;
@ -1205,8 +1217,18 @@ public class TransferEntropyCalculatorDiscrete extends ContextOfPastMeasureCalcu
@Override
public EmpiricalMeasurementDistribution computeSignificance(int numPermutationsToCheck) {
RandomGenerator rg = new RandomGenerator();
// (Not necessary to check for distinct random perturbations)
int[][] newOrderings = rg.generateRandomPerturbations(observations, numPermutationsToCheck);
return computeSignificance(newOrderings);
}
@Override
public EmpiricalMeasurementDistribution computeSignificance(int[][] newOrderings) {
double actualTE = computeAverageLocalOfObservations();
int numPermutationsToCheck = newOrderings.length;
// Reconstruct the *joint* source values (not necessarily in order, but using joint values retains their l-tuples)
int[] sourceValues = new int[observations];
int t_s = 0;
@ -1238,12 +1260,6 @@ public class TransferEntropyCalculatorDiscrete extends ContextOfPastMeasureCalcu
}
}
// Construct new source orderings based on the source probabilities only
// Generate the re-ordered indices:
RandomGenerator rg = new RandomGenerator();
// (Not necessary to check for distinct random perturbations)
int[][] newOrderings = rg.generateRandomPerturbations(observations, numPermutationsToCheck);
// If we want a calculator just like this one, we should provide all of
// the same parameters:
TransferEntropyCalculatorDiscrete ate2 =
@ -1287,10 +1303,9 @@ public class TransferEntropyCalculatorDiscrete extends ContextOfPastMeasureCalcu
if (!estimateComputed) {
computeAverageLocalOfObservations();
}
// We set the "actual value" as 2*N*TE, since this marries up properly
// with the requisite chi^2 distribution
return new ChiSquareMeasurementDistribution(2.0*((double)observations)*average,
(sourceHistoryEmbedLength*base - 1)*(base - 1)*(k*base));
return new ChiSquareMeasurementDistribution(average,
observations,
(base_power_l - 1)*(base - 1)*(base_power_k));
}
/**
@ -1386,6 +1401,7 @@ public class TransferEntropyCalculatorDiscrete extends ContextOfPastMeasureCalcu
(double) sourcePastCount[thisSourceVal][thisPastVal]) /
((double) nextPastCount[destVal][thisPastVal] / (double) pastCount[thisPastVal]);
localTE[t] = Math.log(logTerm) / log_2;
average += localTE[t];
if (localTE[t] > max) {
max = localTE[t];
} else if (localTE[t] < min) {

View File

@ -77,7 +77,8 @@ public interface MutualInfoCalculatorMultiVariateWithDiscrete {
* @param propertyName name of property
* @param propertyValue value of property
*/
public void setProperty(String propertyName, String propertyValue);
public void setProperty(String propertyName, String propertyValue)
throws Exception;
/**
* Set the properties from which the mutual information should be computed.

View File

@ -25,6 +25,12 @@ import infodynamics.utils.MathsUtils;
import infodynamics.utils.MatrixUtils;
import infodynamics.utils.EmpiricalMeasurementDistribution;
import infodynamics.utils.RandomGenerator;
import infodynamics.utils.KdTree;
import java.util.Iterator;
import java.util.Vector;
import java.util.Arrays;
import java.util.Random;
/**
* <p>Compute the Mutual Information between a vector of continuous variables and discrete
@ -57,19 +63,32 @@ import infodynamics.utils.RandomGenerator;
*
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
* @author Pedro A.M. Mediano (<a href="pmediano at imperial.ac.uk">email</a>,
* <a href="http://www.doc.ic.ac.uk/~pam213">www</a>)
*/
public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements MutualInfoCalculatorMultiVariateWithDiscrete {
public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements MutualInfoCalculatorMultiVariateWithDiscrete, Cloneable {
// Multiplier used in hueristic for determining whether to use a linear search
// for min kth element or a binary search.
protected static final double CUTOFF_MULTIPLIER = 1.5;
/**
* we compute distances to the kth neighbour
*/
protected int k = 4;
/**
* The set of continuous data observations, retained in case the user wants
* to retrieve the local entropy values of these.
* They're held in the order in which they were supplied in the
* {@link addObservations(double[][], int[])} functions.
*/
protected double[][] continuousData;
/**
* The set of discrete data observations, retained in case the user wants
* to retrieve the local entropy values of these.
* They're held in the order in which they were supplied in the
* {@link addObservations(double[][], int[])} functions.
*/
protected int[] discreteData;
/**
* Counts of how many observations belong to each discrete bin.
*/
protected int[] counts;
/**
* Number of possible states of the discrete variable
@ -79,22 +98,88 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
* Number of dimenions of the joint continuous variable
*/
protected int dimensions;
/**
* Number of observations added to the calculator
*/
protected int totalObservations;
/**
* Whether to print debug information
*/
protected boolean debug;
/**
* Last computed value of MI
*/
protected double mi;
/**
* Whether MI has been computed with the current observations
*/
protected boolean miComputed;
protected EuclideanUtils normCalculator;
// Storage for the norms from each observation to each other one
protected double[][] xNorms;
// Keep the norms each time (making reordering very quick)
// (Should only be set to false for testing)
public static boolean tryKeepAllPairsNorms = true;
public static int MAX_DATA_SIZE_FOR_KEEP_ALL_PAIRS_NORM = 2000;
/**
* Saved digammaN for average and local computations
*/
protected double digammaN;
/**
* Saved digammaK for average and local computations
*/
protected double digammaK;
/**
* Storage for continuous observations supplied via {@link #addObservations(double[][], int[])}
* type calls
*/
protected Vector<double[][]> vectorOfContinuousObservations;
/**
* Storage for discrete observations supplied via {@link #addObservations(double[][], int[])}
* type calls
*/
protected Vector<int[]> vectorOfDiscreteObservations;
/**
* KdTree for range searches in full continuous dataset
*/
protected KdTree kdTreeJoint;
/**
* KdTrees for neighbour searches in binned continuous datasets
*/
protected KdTree[] kdTreeBins;
/**
* The norm type in use (see {@link #PROP_NORM_TYPE})
*/
protected int normType = EuclideanUtils.NORM_MAX_NORM;
/**
* Property name for the number of K nearest neighbours used in
* the KSG algorithm in the full joint space (default 4).
*/
public final static String PROP_K = "k";
/**
* Property name for what type of norm to use between data points
* for each marginal variable -- Options are defined by
* {@link KdTree#setNormType(String)} and the
* default is {@link EuclideanUtils#NORM_MAX_NORM}.
*/
public final static String PROP_NORM_TYPE = "NORM_TYPE";
/**
* Property name for whether to normalise the incoming data to
* mean 0, standard deviation 1 (default true)
*/
public static final String PROP_NORMALISE = "NORMALISE";
/**
* Property name for time difference between source and destination (0 by default,
* must be >= 0)
*/
public static final String PROP_TIME_DIFF = "TIME_DIFF";
/**
* Property name for an amount of random Gaussian noise to be
* added to the data (default is 1e-8, matching the MILCA toolkit).
*/
public static final String PROP_ADD_NOISE = "NOISE_LEVEL_TO_ADD";
// TODO: properly implement dynCorrExclTime
// /**
// * Property name for a dynamics exclusion time window
// * otherwise known as Theiler window (see Kantz and Schreiber).
// * Default is 0 which means no dynamic exclusion window.
// */
// public static final String PROP_DYN_CORR_EXCL_TIME = "DYN_CORR_EXCL";
/**
* Track whether we're going to normalise the joint variables individually
*/
@ -107,10 +192,34 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
* Track the std devs of the joint variables if we are normalising them
*/
protected double[] stds;
/**
* Time difference from the source to the destination observations
* (ie destination lags the source by this time:
* we compute I(source_{n}; dest_{n+timeDiff}).
* (Note that our internal sourceObservations and destObservations
* are adjusted so that there is no timeDiff between them).
*/
protected int timeDiff = 0;
/**
* Whether to add an amount of random noise to the incoming data
*/
protected boolean addNoise = true;
/**
* Amount of random Gaussian noise to add to the incoming data
*/
protected double noiseLevel = (double) 1e-8;
/**
* Size of dynamic correlation exclusion window. By default is set to 0.
*
* NOTE non-zero exclusion windows are not yet supported in mixed calculators.
*/
protected int dynCorrExclTime = 0;
/**
* Construct an instance of the KSG mixed MI calculator
*/
public MutualInfoCalculatorMultiVariateWithDiscreteKraskov() {
super();
normCalculator = new EuclideanUtils(EuclideanUtils.NORM_MAX_NORM);
}
/**
@ -122,13 +231,15 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
public void initialise(int dimensions, int base) {
mi = 0.0;
miComputed = false;
xNorms = null;
totalObservations = 0;
continuousData = null;
means = null;
stds = null;
discreteData = null;
this.dimensions = dimensions;
this.base = base;
kdTreeJoint = null;
kdTreeBins = null;
}
/**
@ -142,23 +253,68 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
* default is {@link EuclideanUtils#NORM_MAX_NORM}.
* <li>{@link #PROP_NORMALISE} - whether to normalise the individual
* variables (true by default)</li>
* <li>{@link #PROP_TIME_DIFF} - Time difference between source and
* destination (0 by default). Must be >= 0.</li>
* <li>{@link #PROP_ADD_NOISE} -- a standard deviation for an amount of
* random Gaussian noise to add to
* each variable, to avoid having neighbourhoods with artificially
* large counts. (We also accept "false" to indicate "0".)
* The amount is added in after any normalisation,
* so can be considered as a number of standard deviations of the data.
* (Recommended by Kraskov. MILCA uses 1e-8; but adds in
* a random amount of noise in [0,noiseLevel) ).
* Default 1e-8 to match the noise order in MILCA toolkit.</li>
* <li>{@link #PROP_DYN_CORR_EXCL_TIME} -- a dynamics exclusion time window,
* also known as Theiler window (see Kantz and Schreiber);
* default is 0 which means no dynamic exclusion window.</li>
* </ul>
*
* @param propertyName name of the property to set
* @param propertyValue value to set on that property
*/
public void setProperty(String propertyName, String propertyValue) {
public void setProperty(String propertyName, String propertyValue)
throws Exception {
if (propertyName.equalsIgnoreCase(PROP_K)) {
k = Integer.parseInt(propertyValue);
} else if (propertyName.equalsIgnoreCase(PROP_NORM_TYPE)) {
normCalculator.setNormToUse(propertyValue);
normType = KdTree.validateNormType(propertyValue);
} else if (propertyName.equalsIgnoreCase(PROP_NORMALISE)) {
normalise = Boolean.parseBoolean(propertyValue);
// TODO: properly implement dynCorrExclTime
// } else if (propertyName.equalsIgnoreCase(PROP_DYN_CORR_EXCL_TIME)) {
// dynCorrExclTime = Integer.parseInt(propertyValue);
} else if (propertyName.equalsIgnoreCase(PROP_ADD_NOISE)) {
if (propertyValue.equals("0") ||
propertyValue.equalsIgnoreCase("false")) {
addNoise = false;
noiseLevel = 0;
} else {
addNoise = true;
noiseLevel = Double.parseDouble(propertyValue);
}
} else if (propertyName.equalsIgnoreCase(PROP_TIME_DIFF)) {
timeDiff = Integer.parseInt(propertyValue);
}
}
public void addObservations(double[][] source, double[][] destination) throws Exception {
throw new RuntimeException("Not implemented yet");
public void addObservations(double[][] continuousObservations,
int[] discreteObservations) throws Exception {
if (vectorOfContinuousObservations == null) {
// startAddObservations was not called first
throw new RuntimeException("User did not call startAddObservations before addObservations");
}
if (continuousObservations.length != discreteObservations.length) {
throw new Exception("Time steps for observations2 " +
discreteObservations.length + " does not match the length " +
"of observations1 " + continuousObservations.length);
}
if (continuousObservations[0].length != dimensions) {
throw new Exception("The continuous observations do not have the expected number of variables (" + dimensions + ")");
}
if (continuousObservations.length > Math.abs(timeDiff)) {
vectorOfContinuousObservations.add(continuousObservations);
vectorOfDiscreteObservations.add(discreteObservations);
}
}
public void addObservations(double[][] source, double[][] destination, int startTime, int numTimeSteps) throws Exception {
@ -173,69 +329,141 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
throw new RuntimeException("Not implemented yet");
}
/**
* Prepare the calculator to receive observations.
*
* Must be called between {@link #initialise()} and any addObservations
* method. Does not need to be called if
* {@link setObservations(double[][], int[])} is used.
*/
public void startAddObservations() {
throw new RuntimeException("Not implemented yet");
vectorOfContinuousObservations = new Vector<double[][]>();
vectorOfDiscreteObservations = new Vector<int[]>();
}
public void finaliseAddObservations() {
throw new RuntimeException("Not implemented yet");
}
/**
* Prepare observations previously added through any addObservations method
* for calculations.
*
* Must be called between any addObservations method and any compute method.
* Does not need to be called if {@link setObservations(double[][], int[])}
* is used.
*/
public void finaliseAddObservations() throws Exception {
if (vectorOfContinuousObservations.size() < 1) {
throw new Exception("Cannot compute MI with a null set of data");
}
public void setObservations(double[][] continuousObservations,
int[] discreteObservations) throws Exception {
if (continuousObservations.length != discreteObservations.length) {
throw new Exception("Time steps for observations2 " +
discreteObservations.length + " does not match the length " +
"of observations1 " + continuousObservations.length);
// First work out the size to allocate the joint vectors, and do the allocation:
totalObservations = 0;
for (double[][] destination : vectorOfContinuousObservations) {
totalObservations += destination.length - Math.abs(timeDiff);
}
if (continuousObservations[0].length == 0) {
throw new Exception("Computing MI with a null set of data");
continuousData = new double[totalObservations][dimensions];
discreteData = new int[totalObservations];
// Construct the joint vectors from the given observations
// (removing redundant data which is outside any timeDiff)
int startObservation = 0;
Iterator<double[][]> iterator = vectorOfContinuousObservations.iterator();
for (int[] dct : vectorOfDiscreteObservations) {
double[][] cnt = iterator.next();
// Copy the data from these given observations into our master
// array, aligning them incorporating the timeDiff. Depending on whether
// timeDiff >= 0, we have to put first the elements of one array or the
// other.
if (timeDiff >= 0) {
MatrixUtils.arrayCopy(cnt, 0, 0,
continuousData, startObservation, 0,
cnt.length - timeDiff, dimensions);
System.arraycopy(dct, timeDiff, discreteData, startObservation, dct.length - timeDiff);
startObservation += cnt.length - timeDiff;
} else {
MatrixUtils.arrayCopy(cnt, Math.abs(timeDiff), 0,
continuousData, startObservation, 0,
cnt.length - Math.abs(timeDiff), dimensions);
System.arraycopy(dct, 0, discreteData, startObservation, dct.length - Math.abs(timeDiff));
startObservation += cnt.length - Math.abs(timeDiff);
}
}
if (continuousObservations[0].length != dimensions) {
throw new Exception("The continuous observations do not have the expected number of variables (" + dimensions + ")");
}
continuousData = continuousObservations;
discreteData = discreteObservations;
// We don't need to keep the vectors of observation sets anymore:
vectorOfContinuousObservations = null;
vectorOfDiscreteObservations = null;
if (normalise) {
// Take a copy since we're going to normalise it
// And we need to keep the means/stds ready to normalise local values
// We need to keep the means/stds ready to normalise local values
// that are supplied later:
means = MatrixUtils.means(continuousObservations);
stds = MatrixUtils.stdDevs(continuousObservations, means);
continuousData = MatrixUtils.normaliseIntoNewArray(continuousObservations, means, stds);
means = MatrixUtils.means(continuousData);
stds = MatrixUtils.stdDevs(continuousData, means);
MatrixUtils.normalise(continuousData, means, stds);
}
// count the discrete states:
counts = new int[base];
for (int t = 0; t < discreteData.length; t++) {
counts[discreteData[t]]++;
}
try {
for (int t = 0; t < discreteData.length; t++) {
counts[discreteData[t]]++;
}
} catch (ArrayIndexOutOfBoundsException e) {
totalObservations = 0;
continuousData = null;
discreteData = null;
throw new RuntimeException("Values of the discrete variable must range from 0 to base-1");
}
for (int b = 0; b < counts.length; b++) {
if (counts[b] < k) {
throw new RuntimeException("This implementation assumes there are at least k items in each discrete bin");
}
}
if (addNoise) {
Random random = new Random();
// Add Gaussian noise of std dev noiseLevel to the data
for (int r = 0; r < totalObservations; r++) {
for (int c = 0; c < dimensions; c++) {
continuousData[r][c] +=
random.nextGaussian()*noiseLevel;
}
}
}
digammaN = MathsUtils.digamma(totalObservations);
digammaK = MathsUtils.digamma(k);
ensureKdTreesConstructed();
}
/**
* Compute the norms for each marginal time series
*
*/
protected void computeNorms() {
int N = continuousData.length; // number of observations
xNorms = new double[N][N];
for (int t = 0; t < N; t++) {
// Compute the norms from t to all other time points
for (int t2 = 0; t2 < N; t2++) {
if (t2 == t) {
xNorms[t][t2] = Double.POSITIVE_INFINITY;
continue;
}
// Compute norm in the continuous space
xNorms[t][t2] = normCalculator.norm(continuousData[t], continuousData[t2]);
}
}
public void setObservations(double[][] continuousObservations,
int[] discreteObservations) throws Exception {
startAddObservations();
addObservations(continuousObservations, discreteObservations);
finaliseAddObservations();
}
/**
* Internal method to ensure that the Kd-tree data structures to represent the
* observational data have been constructed (should be called prior to attempting
* to use these data structures)
*/
public void ensureKdTreesConstructed() {
if (kdTreeJoint == null) {
kdTreeJoint = new KdTree(continuousData);
kdTreeJoint.setNormType(normType);
}
if (kdTreeBins == null) {
kdTreeBins = new KdTree[base];
for (int b = 0; b < base; b++) {
kdTreeBins[b] = new KdTree(
MatrixUtils.extractSelectedPointsMatchingCondition(
continuousData, discreteData, b));
kdTreeBins[b].setNormType(normType);
}
}
}
/**
* Compute what the average MI would look like were the second time series reordered
@ -247,84 +475,81 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
* @return
* @throws Exception
*/
public double computeAverageLocalOfObservations(int[] reordering) throws Exception {
int N = continuousData.length; // number of observations
if (!tryKeepAllPairsNorms || (N > MAX_DATA_SIZE_FOR_KEEP_ALL_PAIRS_NORM)) {
// Generate a new re-ordered set of discrete data
int[] originalDiscreteData = discreteData;
discreteData = MatrixUtils.extractSelectedTimePoints(discreteData, reordering);
// Compute the MI
double newMI = computeAverageLocalOfObservationsWhileComputingDistances();
// restore data2
discreteData = originalDiscreteData;
return newMI;
}
public double computeAverageLocalOfObservations(int[] newOrdering)
throws Exception {
// Otherwise we will use the norms we've already computed, and use a "virtual"
// reordered data2.
int[] reorderedDiscreteData = MatrixUtils.extractSelectedTimePoints(discreteData, reordering);
if (xNorms == null) {
computeNorms();
if (newOrdering == null) {
return computeAverageLocalOfObservations();
}
// Count the average number of points within eps_x and eps_y
double averageDiGammas = 0;
double avNx = 0;
double avNy = 0;
// Take a clone of the object to compute the MI of the surrogates:
// (this is a shallow copy, it doesn't make new copies of all
// the arrays)
MutualInfoCalculatorMultiVariateWithDiscreteKraskov miSurrogateCalculator =
(MutualInfoCalculatorMultiVariateWithDiscreteKraskov) this.clone();
for (int t = 0; t < N; t++) {
// Compute eps_x and eps_y for this time step:
// using x norms to all neighbours
// (note that norm of point t to itself will be set to infinity).
// Then find the k closest neighbours in the same discrete bin
double eps_x = MatrixUtils.kthMinSubjectTo(xNorms[t], k, reorderedDiscreteData, reorderedDiscreteData[t]);
// Count the number of points whose x distance is less
// than or equal to eps_x
int n_x = 0;
for (int t2 = 0; t2 < N; t2++) {
if (xNorms[t][t2] <= eps_x) {
n_x++;
}
}
// n_y is number of points in that bin minus that point
int n_y = counts[reorderedDiscreteData[t]] - 1;
avNx += n_x;
avNy += n_y;
// And take the digamma before adding into the
// average:
averageDiGammas += MathsUtils.digamma(n_x) + MathsUtils.digamma(n_y);
}
averageDiGammas /= (double) N;
if (debug) {
avNx /= (double)N;
avNy /= (double)N;
System.out.println(String.format("Average n_x=%.3f, Average n_y=%.3f", avNx, avNy));
}
// Don't need the 1/k correction here because the conditional entropy term
// is taken over the continuous space only. The correction is (m-1)/k
// for an entropy over m subspaces.
// mi = MathsUtils.digamma(k) - 1.0/(double)k - averageDiGammas + MathsUtils.digamma(N);
// Instead do:
mi = MathsUtils.digamma(k) - averageDiGammas + MathsUtils.digamma(N);
miComputed = true;
return mi;
// Generate a new re-ordered data set
int[] shuffledDiscreteData = MatrixUtils.extractSelectedTimePoints(discreteData, newOrdering);
// Since the observations in the original calculator have already been
// shifted according to timeDiff, set timeDiff = 0 in the surrogate calculator.
miSurrogateCalculator.setProperty(PROP_TIME_DIFF, "0");
// Perform new initialisations
miSurrogateCalculator.initialise(dimensions, base);
// Set new observations
miSurrogateCalculator.setObservations(continuousData, shuffledDiscreteData);
// Compute the MI
return miSurrogateCalculator.computeAverageLocalOfObservations();
}
public double computeAverageLocalOfObservations() throws Exception {
if (!tryKeepAllPairsNorms || (continuousData.length > MAX_DATA_SIZE_FOR_KEEP_ALL_PAIRS_NORM)) {
return computeAverageLocalOfObservationsWhileComputingDistances();
}
// Postcondition: we'll compute the norms before the main loop,
// unless they have already been computed:
if (xNorms == null) {
computeNorms();
}
int N = continuousData.length; // number of observations
/**
* Calculate the average MI in nats.
*/
public double computeAverageLocalOfObservations() throws Exception {
return computeFromObservations(false)[0];
}
/**
* <p>Computes the local values of the MI,
* for each valid observation in the previously supplied observations
* (with PDFs computed using all of the previously supplied observation sets).</p>
*
* <p>If the samples were supplied via a single call such as
* {@link #setObservations(double[][], int[])},
* then the return value is a single time-series of local
* channel measure values corresponding to these samples.</p>
*
* <p>Otherwise where disjoint time-series observations were supplied using several
* calls such as {@link #addObservations(double[][], int[])}
* then the local values for each disjoint observation set will be appended here
* to create a single "time-series" return array.</p>
*
* @return the "time-series" of local MIs in nats
* @throws Exception
*/
public double[] computeLocalOfPreviousObservations() throws Exception {
return computeFromObservations(true);
}
/**
* This protected method handles the computation of either the average or
* local MI (over parts of the total observations).
*
* <p>The method returns:<ol>
* <li>for (returnLocals == false), an array of size 1,
* containing the average MI </li>
* <li>for local MIs (returnLocals == true), the array of local MI values</li>
* </ol>
*
* @param returnLocals whether to return an array or local values, or else
* sums of these values
* @return either the average MI, or array of local MI value, in nats not bits
* @throws Exception
*/
protected double[] computeFromObservations(boolean returnLocals) throws Exception {
int[] cumcount = new int[base];
Arrays.fill(cumcount, 0);
// Count the average number of points within eps_x and eps_y
double averageDiGammas = 0;
@ -332,25 +557,43 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
double avNy = 0;
double testSum = 0.0; // Used for debugging prints
for (int t = 0; t < N; t++) {
// Compute eps_x and eps_y for this time step:
// using x norms to all neighbours
// (note that norm of point t to itself will be set to infinity).
// Then find the k closest neighbours in the same discrete bin
double eps_x = MatrixUtils.kthMinSubjectTo(xNorms[t], k, discreteData, discreteData[t]);
int N = totalObservations;
double[] locals = null;
if (returnLocals) {
locals = new double[N];
}
for (int t = 0; t < N; t++) {
// Compute eps_x for this time step:
// find neighbours in the same discrete bin, then
// count points within eps_x in the whole dataset.
int b = discreteData[t];
double eps_x = kdTreeBins[b].findKNearestNeighbours(k, cumcount[b]++).poll().distance;
int n_x = kdTreeJoint.countPointsWithinOrOnR(t, eps_x, dynCorrExclTime);
// Number of points in discrete bin b, not counting itself
int n_y = counts[b] - 1;
// TODO: properly implement dynCorrExclTime. Code left here for future
// development. To implement exclusion window in the full joint space we need
// to track the sample indices in each point in the kdTreeBins. See comments in
// https://github.com/jlizier/jidt/pull/58#pullrequestreview-64865317
//
// if (dynCorrExclTime > 0) {
// // Add one here because later we are going to subtract the point itself
// // again in the for-loop when tt == t
// n_y++;
// for (int tt = t - dynCorrExclTime; tt <= t + dynCorrExclTime; tt++) {
// if ((tt < 0) || (tt >= totalObservations)) {
// continue;
// }
// if (discreteData[tt] == b) {
// n_y--;
// }
// }
// }
// Count the number of points whose x distance is less
// than or equal to eps_x (not including this point)
int n_x = 0;
for (int t2 = 0; t2 < N; t2++) {
if ((t2 != t) && (xNorms[t][t2] <= eps_x)) {
// xNorms has infinity for t == t2 anyway ...
n_x++;
}
}
// n_y is number of points in that bin, minus this point
int n_y = counts[discreteData[t]] - 1;
avNx += n_x;
avNy += n_y;
// And take the digamma before adding into the
@ -358,13 +601,17 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
double localSum = MathsUtils.digamma(n_x) + MathsUtils.digamma(n_y);
averageDiGammas += localSum;
// Don't need the 1/k correction here because the conditional entropy term
// is taken over the continuous space only. The correction is (m-1)/k
// for an entropy over m subspaces.
// double localValue = MathsUtils.digamma(k) - 1.0/(double)k - localSum + MathsUtils.digamma(N);
// Instead do:
double localValue = digammaK + digammaN - localSum;
if (returnLocals) {
locals[t] = localValue;
}
if (debug) {
// Don't need the 1/k correction here because the conditional entropy term
// is taken over the continuous space only. The correction is (m-1)/k
// for an entropy over m subspaces.
// double localValue = MathsUtils.digamma(k) - 1.0/(double)k - localSum + MathsUtils.digamma(N);
// Instead do:
double localValue = MathsUtils.digamma(k) - localSum + MathsUtils.digamma(N);
testSum += localValue;
if (dimensions == 1) {
System.out.printf("t=%d: x=%.3f, eps_x=%.3f, n_x=%d, n_y=%d, local=%.3f, running total = %.5f\n",
@ -383,7 +630,7 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
avNx, MathsUtils.digamma((int) avNx), MathsUtils.digamma((int) avNx - 1), avNy, MathsUtils.digamma((int) avNy)));
System.out.printf("Independent average num in joint box is %.3f\n", (avNx * avNy / (double) N));
System.out.println(String.format("digamma(k)=%.3f - averageDiGammas=%.3f + digamma(N)=%.3f\n",
MathsUtils.digamma(k), averageDiGammas, MathsUtils.digamma(N)));
digammaK, averageDiGammas, digammaN));
}
// Don't need the 1/k correction here because the conditional entropy term
@ -391,110 +638,34 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
// for an entropy over m subspaces.
// mi = MathsUtils.digamma(k) - 1.0/(double)k - averageDiGammas + MathsUtils.digamma(N);
// Instead, do:
mi = MathsUtils.digamma(k) - averageDiGammas + MathsUtils.digamma(N);
mi = digammaK + digammaN - averageDiGammas;
miComputed = true;
return mi;
double[] returnValues;
if (returnLocals) {
returnValues = locals;
} else {
returnValues = new double[] {mi};
}
return returnValues;
}
/**
* This method correctly computes the average local MI, but recomputes the x and y
* distances between all tuples in time.
* Kept here for cases where we have too many observations
* to keep the norm between all pairs, and for testing purposes.
*
* @return
* @throws Exception
*/
public double computeAverageLocalOfObservationsWhileComputingDistances() throws Exception {
int N = continuousData.length; // number of observations
// Count the average number of points within eps_x and eps_y
double averageDiGammas = 0;
double avNx = 0;
double avNy = 0;
double testSum = 0.0; // Used for debugging prints
for (int t = 0; t < N; t++) {
// Compute eps_x and eps_y for this time step:
// First get x norms to all neighbours
// (note that norm of point t to itself will be set to infinity).
double[] norms = new double[N];
for (int t2 = 0; t2 < N; t2++) {
if (t2 == t) {
norms[t2] = Double.POSITIVE_INFINITY;
continue;
}
// Compute norm in the continuous space
norms[t2] = normCalculator.norm(continuousData[t], continuousData[t2]);
}
// Then find the k closest neighbours in the same discrete bin
double eps_x = MatrixUtils.kthMinSubjectTo(norms, k, discreteData, discreteData[t]);
// Count the number of points whose x distance is less
// than or equal to eps_x
int n_x = 0;
for (int t2 = 0; t2 < N; t2++) {
if (norms[t2] <= eps_x) {
n_x++;
}
}
// n_y is number of points in that bin, minus that point
int n_y = counts[discreteData[t]] - 1;
avNx += n_x;
avNy += n_y;
// And take the digamma before adding into the
// average:
double localSum = MathsUtils.digamma(n_x) + MathsUtils.digamma(n_y);
averageDiGammas += localSum;
if (debug) {
// Don't need the 1/k correction here because the conditional entropy term
// is taken over the continuous space only. The correction is (m-1)/k
// for an entropy over m subspaces.
// double localValue = MathsUtils.digamma(k) - 1.0/(double)k - localSum + MathsUtils.digamma(N);
// Instead do:
double localValue = MathsUtils.digamma(k) - localSum + MathsUtils.digamma(N);
testSum += localValue;
if (dimensions == 1) {
System.out.printf("t=%d: x=%.3f, eps_x=%.3f, n_x=%d, n_y=%d, local=%.3f, running total = %.5f\n",
t, continuousData[t][0], eps_x, n_x, n_y, localValue, testSum);
} else {
System.out.printf("t=%d: eps_x=%.3f, n_x=%d, n_y=%d, local=%.3f, running total = %.5f\n",
t, eps_x, n_x, n_y, localValue, testSum);
}
}
}
averageDiGammas /= (double) N;
if (debug) {
avNx /= (double)N;
avNy /= (double)N;
System.out.println(String.format("Average n_x=%.3f, Average n_y=%.3f", avNx, avNy));
}
// Don't need the 1/k correction here because the conditional entropy term
// is taken over the continuous space only. The correction is (m-1)/k
// for an entropy over m subspaces.
// mi = MathsUtils.digamma(k) - 1.0/(double)k - averageDiGammas + MathsUtils.digamma(N);
// Instead do:
mi = MathsUtils.digamma(k) - averageDiGammas + MathsUtils.digamma(N);
miComputed = true;
return mi;
}
/**
* Compute the significance of the mutual information of the previously supplied observations.
* We destroy the p(x,y) correlations, while retaining the p(x), p(y) marginals, to check how
* significant this mutual information actually was.
* Compute the significance of the mutual information of the previously
* supplied observations.
*
* We destroy the p(x,y) correlations, while retaining the p(x), p(y)
* marginals, to check how significant this mutual information actually was.
*
* This is in the spirit of Chavez et. al., "Statistical assessment of nonlinear causality:
* application to epileptic EEG signals", Journal of Neuroscience Methods 124 (2003) 113-128
* which was performed for Transfer entropy.
* This is in the spirit of Chavez et. al., "Statistical assessment of
* nonlinear causality: application to epileptic EEG signals", Journal of
* Neuroscience Methods 124 (2003) 113-128 which was performed for transfer
* entropy.
*
* @param numPermutationsToCheck
* @return the proportion of MI scores from the distribution which have higher or equal MIs to ours.
* (i.e. 1 - CDF of our score)
* @return the proportion of MI scores from the distribution which have
* higher or equal MIs to ours. (i.e. 1 - CDF of our score)
*/
public synchronized EmpiricalMeasurementDistribution computeSignificance(int numPermutationsToCheck) throws Exception {
// Generate the re-ordered indices:
@ -505,16 +676,20 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
}
/**
* Compute the significance of the mutual information of the previously supplied observations.
* We destroy the p(x,y) correlations, while retaining the p(x), p(y) marginals, to check how
* significant this mutual information actually was.
* Compute the significance of the mutual information of the previously
* supplied observations.
*
* We destroy the p(x,y) correlations, while retaining the p(x), p(y)
* marginals, to check how significant this mutual information actually was.
*
* This is in the spirit of Chavez et. al., "Statistical assessment of nonlinear causality:
* application to epileptic EEG signals", Journal of Neuroscience Methods 124 (2003) 113-128
* which was performed for Transfer entropy.
* This is in the spirit of Chavez et. al., "Statistical assessment of
* nonlinear causality: application to epileptic EEG signals", Journal of
* Neuroscience Methods 124 (2003) 113-128 which was performed for transfer
* entropy.
*
* @param newOrderings the specific new orderings to use
* @return the proportion of MI scores from the distribution which have higher or equal MIs to ours.
* @return the proportion of MI scores from the distribution which have
* higher or equal MIs to ours.
*/
public EmpiricalMeasurementDistribution computeSignificance(int[][] newOrderings) throws Exception {
@ -561,8 +736,8 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
* it should be ignored, since the data point was not counted in its
* own counts in the standard version anyway.</p>
*
* <p><b>Importantly</b>, the supplied observations are intended to be new observations,
* not those fed in to compute the PDFs from. There would be
* <p><b>Importantly</b>, the supplied observations are intended to be new
* observations, not those fed in to compute the PDFs from. There would be
* some subtle changes necessary to accomodate computing locals on
* the same data set (e.g. not counting the current point as one
* of those within eps_x etc.).
@ -584,58 +759,27 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
continuousNewStates = MatrixUtils.normaliseIntoNewArray(
continuousNewStates, means, stds);
}
// Thanks to Rosalind Wang for picking up the bug in not using two different N's here
int N_newObservations = continuousNewStates.length; // number of new observations we're evaluating
int N_samplesForPdfs = continuousData.length; // number of observations we're using to compute the PDFs
double[] locals = new double[N_newObservations];
// Don't need the 1/k correction here because the conditional entropy term
// is taken over the continuous space only. The correction is (m-1)/k
// for an entropy over m subspaces.
// double fixedPartOfLocals = MathsUtils.digamma(k) - 1.0/(double)k +
// MathsUtils.digamma(N);
// Instead do:
double fixedPartOfLocals = MathsUtils.digamma(k) + MathsUtils.digamma(N_samplesForPdfs); // Use N_samplesForPdfs here because that's what would be in denominator of probability functions
double testSum = 0.0;
if (debug) {
System.out.printf("digamma(k)=%.3f + digamma(N)=%.3f\n",
MathsUtils.digamma(k), MathsUtils.digamma(N_samplesForPdfs));
}
double avNx = 0;
double avNy = 0;
for (int t = 0; t < N_newObservations; t++) {
// Compute eps_x and eps_y for this time step:
// First get x norms to all points in the previously
// given observations.
double[] norms = new double[continuousData.length];
for (int t2 = 0; t2 < continuousData.length; t2++) {
// Compute norm in the continuous space
norms[t2] = normCalculator.norm(continuousNewStates[t], continuousData[t2]);
}
// Then find the k closest neighbours in the same discrete bin
double eps_x = MatrixUtils.kthMinSubjectTo(norms, k, discreteData, discreteNewStates[t]);
double fixedPartOfLocals = digammaK + MathsUtils.digamma(totalObservations); // Use N_samplesForPdfs here because that's what would be in denominator of probability functions
double testSum = 0.0, avNx = 0.0, avNy = 0.0;
double[] locals = new double[discreteNewStates.length];
for (int t = 0; t < discreteNewStates.length; t++) {
int b = discreteNewStates[t];
double[][] x = new double[][] {continuousNewStates[t]};
double eps_x = kdTreeBins[b].findKNearestNeighbours(k, x).poll().distance;
int n_x = kdTreeJoint.countPointsWithinR(x, eps_x, true);
int n_y = counts[b];
// Count the number of points whose x distance is less
// than or equal to eps_x
int n_x = 0;
for (int t2 = 0; t2 < continuousData.length; t2++) {
if (norms[t2] <= eps_x) {
n_x++;
}
}
// n_y is number of points in that discrete bin
int n_y = counts[discreteData[t]];
avNx += n_x;
avNy += n_y;
// Now compute the local value:
locals[t] = fixedPartOfLocals -
MathsUtils.digamma(n_x) - MathsUtils.digamma(n_y);
if (debug) {
if (debug) {
testSum += locals[t];
avNx += n_x;
avNy += n_y;
if (dimensions == 1) {
System.out.printf("t=%d: x=%.3f, eps_x=%.3f, n_x=%d, n_y=%d, local=%.3f, running total = %.5f\n",
t, continuousNewStates[t][0], eps_x, n_x, n_y, locals[t], testSum);
@ -643,11 +787,11 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
System.out.printf("t=%d: eps_x=%.3f, n_x=%d, n_y=%d, local=%.3f, running total = %.5f\n",
t, eps_x, n_x, n_y, locals[t], testSum);
}
}
}
}
}
if (debug) {
avNx /= (double)N_newObservations;
avNy /= (double)N_newObservations;
avNx /= (double) discreteNewStates.length;
avNy /= (double) discreteNewStates.length;
System.out.printf("Average n_x=%.3f, Average n_y=%.3f\n", avNx, avNy);
}
@ -658,11 +802,30 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
this.debug = debug;
}
/**
* Return the MI last calculated in a call to {@link #computeAverageLocalOfObservations()}
* or {@link #computeLocalOfPreviousObservations()} after the previous
* {@link #initialise()} call.
*
* @return the last computed average mutual information
*/
public double getLastAverage() {
return mi;
}
/**
* Get the number of samples to be used for the PDFs here
* which have been supplied by calls to
* {@link #setObservations(double[][], int[])},
* {@link #addObservations(double[][], int[])}
* etc.
*
* <p>Note that the number of samples may not be equal to the length of time-series
* supplied (i.e. where a {@link PROP_TIME_DIFF}
* is set).
* </p>
*/
public int getNumObservations() {
return continuousData.length;
return totalObservations;
}
}

View File

@ -27,16 +27,120 @@ package infodynamics.utils;
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
public class AnalyticMeasurementDistribution extends MeasurementDistribution {
public abstract class AnalyticMeasurementDistribution extends MeasurementDistribution {
/**
* Construct the instance
*
* @param actualValue observed value of the information-theoretic measure
* @param pValue p-value that surrogate measurements are greater than
* the observed value.
* the observed value. (1 - CDF of null at the observed value)
*/
protected AnalyticMeasurementDistribution(double actualValue, double pValue) {
super(actualValue, pValue);
}
/**
* Compute the <b>analytic</b> p-value
* for the given estimate (i.e. the input argument, not the
* estimate produced by any particular observations supplied)
* under a null hypothesis that the source values of our
* samples had no relation to the destination value (possibly
* in the context of a conditional value).
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for MI,
* conditional MI and TE as per the references below.
* </p>
*
* <p><b>References:</b><br/>
* <ul>
* <li>J.T. Lizier, "JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems", 2014.</li>
* <li>Brillinger, <a href="http://www.stat.berkeley.edu/~brill/Papers/MIBJPS.pdf">
* "Some data analyses using mutual information"</a>,
* Brazilian Journal of Probability and Statistics, <b>18</b>, p. 163, (2004)</li>
* <li>Cheng et al., <a href="http://www.jds-online.com/file_download/112/JDS-369.pdf">
* "Data Information in Contingency Tables: A Fallacy of Hierarchical Loglinear Models"</a>,
* Journal of Data Science, <b>4</b>, p. 387 (2006).</li>
* <li>Geweke, <a href="http://dx.doi.org/10.1080/01621459.1982.10477803">
* "Measurement of Linear Dependence and Feedback between Multiple Time Series"</a>,
* Journal of the American Statistical Association, <b>77</b>, p. 304-313 (1982).</li>
* <li>Barnett and Bossomaier, <a href="http://arxiv.org/abs/1205.6339">
* "Transfer Entropy as a Log-likelihood Ratio"</a>,
* Physical Review Letters, <b>109</b>, p. 138105+ (2012).</li>
* </ul>
*
* @return p-value for the given channel measure score under this null hypothesis.
* @throws Exception
*/
public abstract double computePValueForGivenEstimate(double estimate);
/**
* Computes p-values corresponding to a set of estimates, each done via
* {@link #computePValueForGivenEstimate(double)}
*
* @param estimates array of estimates to return corresponding p-values for.
* @return
*/
public double[] computePValuesForGivenEstimates(double[] estimates) {
double[] pValues = new double[estimates.length];
for (int i = 0; i < estimates.length; i++) {
pValues[i] = computePValueForGivenEstimate(estimates[i]);
}
return estimates;
}
/**
* Compute the estimated observed measured value corresponding to
* a given p-value
* (derived from how the given estimates are <b>analytically</b> distributed
* under a null hypothesis that the source values of our
* samples had no relation to the destination value, possibly
* in the context of a conditional value).
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for MI,
* conditional MI and TE as per the references below.
* </p>
*
* <p><b>References:</b><br/>
* <ul>
* <li>J.T. Lizier, "JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems", 2014.</li>
* <li>Brillinger, <a href="http://www.stat.berkeley.edu/~brill/Papers/MIBJPS.pdf">
* "Some data analyses using mutual information"</a>,
* Brazilian Journal of Probability and Statistics, <b>18</b>, p. 163, (2004)</li>
* <li>Cheng et al., <a href="http://www.jds-online.com/file_download/112/JDS-369.pdf">
* "Data Information in Contingency Tables: A Fallacy of Hierarchical Loglinear Models"</a>,
* Journal of Data Science, <b>4</b>, p. 387 (2006).</li>
* <li>Geweke, <a href="http://dx.doi.org/10.1080/01621459.1982.10477803">
* "Measurement of Linear Dependence and Feedback between Multiple Time Series"</a>,
* Journal of the American Statistical Association, <b>77</b>, p. 304-313 (1982).</li>
* <li>Barnett and Bossomaier, <a href="http://arxiv.org/abs/1205.6339">
* "Transfer Entropy as a Log-likelihood Ratio"</a>,
* Physical Review Letters, <b>109</b>, p. 138105+ (2012).</li>
* </ul>
*
* @param pValue the sample p-value for the given channel measure score under this null hypothesis.
* @return the estimate of the channel measure score corresponding to
* the given p-value under this null hypothesis.
* @throws Exception
*/
public abstract double computeEstimateForGivenPValue(double pValue);
/**
* Computes estimates corresponding to a set of p-values, each done via
* {@link #computeEstimateForGivenPValue(double)}
*
* @param pValues array of p-values to return corresponding estimates for.
* @return
*/
public double[] computeEstimatesForGivenPValues(double[] pValues) {
double[] estimates = new double[pValues.length];
for (int i = 0; i < pValues.length; i++) {
estimates[i] = computeEstimateForGivenPValue(pValues[i]);
}
return estimates;
}
}

View File

@ -22,7 +22,7 @@ package infodynamics.utils;
* Calculators implementing this interface must provide a
* {@link #computeSignificance()} method to compute
* the statistical significance of their measurement, returning an analytically
* determined distribution {@link AnalyticNullDistributionComputer}.
* determined distribution {@link AnalyticMeasurementDistribution}.
*
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)

View File

@ -18,10 +18,13 @@
package infodynamics.utils;
import infodynamics.utils.commonsmath3.distribution.ChiSquaredDistribution;
/**
* Class to represent analytic distributions of info theoretic measurements under
* some null hypothesis of a relationship between the variables, where that
* distribution is a Chi Square distribution.
* some null hypothesis of a relationship between the variables, where the
* distribution of <b>a function of</b> those information-theoretic measurements
* is a Chi Square distribution.
*
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
@ -35,13 +38,43 @@ public class ChiSquareMeasurementDistribution extends
protected int degreesOfFreedom;
/**
* Construct the distribution
* The number of observations that the information theoretic estimate
* is computed from
*/
protected int numObservations;
/**
* An object of the chi2dist class from commons.math3,
* which we'll use to access the distribution values
*/
protected ChiSquaredDistribution chi2dist;
/**
* Construct the distribution.
* Note: the Chi squared distribution is technically
* of 2*numObservations*(the info theoretic estimate), not
* of the info theoretic measurement itself.
*
* @param actualValue actual observed value
* @param actualValue actual observed information-theoretic value
* @param numObservations the number of observations that the information theoretic estimate
* is computed from
* @param degreesOfFreedom degrees of freedom for the distribution
*/
public ChiSquareMeasurementDistribution(double actualValue, int degreesOfFreedom) {
super(actualValue, 1 - MathsUtils.chiSquareCdf(actualValue, degreesOfFreedom));
public ChiSquareMeasurementDistribution(double actualValue,
int numObservations, int degreesOfFreedom) {
super(actualValue, 1 - MathsUtils.chiSquareCdf(2.0*((double)numObservations)*actualValue, degreesOfFreedom));
this.numObservations = numObservations;
this.degreesOfFreedom = degreesOfFreedom;
chi2dist = new ChiSquaredDistribution(degreesOfFreedom);
}
public double computePValueForGivenEstimate(double estimate) {
return 1 - MathsUtils.chiSquareCdf(2.0*((double)numObservations)*estimate, degreesOfFreedom);
}
public double computeEstimateForGivenPValue(double pValue) {
return chi2dist.inverseCumulativeProbability(1 - pValue) / (2.0*((double)numObservations));
// Could also call the following, but this doesn't re-use our objects:
// return MathsUtils.chiSquareInv(1 - pValue, degreesOfFreedom);
}
}

View File

@ -0,0 +1,104 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, Joseph T. Lizier, Ipek Oezdemir and Pedro Mediano
*
* 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.utils;
/**
* Calculators implementing this interface must provide
* {@link #computeSignificance(int)} and {@link #computeSignificance(int[][])}
* methods to compute
* the statistical significance of their measurement, returning an empirically
* determined distribution {@link EmpiricalMeasurementDistribution}.
*
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
* <a href="http://lizier.me/joseph/">www</a>)
*/
public interface EmpiricalNullDistributionComputer {
/**
* Generate an <b>empirical</b> (bootstrapped) distribution of what the given measure would look like,
* under a null hypothesis that the source values of our
* samples had no relation to the destination value (possibly
* in the context of a conditional value).
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done for MI,
* conditional MI and TE.
* </p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using addObservations methods etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* <p><b>References:</b><br/>
* <ul>
* <li>J.T. Lizier, "JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems", 2014.</li>
* </ul>
*
* @param numPermutationsToCheck number of surrogate samples to bootstrap
* to generate the distribution.
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
* @return the empirical distribution of measure scores under this null hypothesis.
*/
public EmpiricalMeasurementDistribution computeSignificance(int numPermutationsToCheck) throws Exception;
/**
* Generate an <b>empirical</b> (bootstrapped) distribution of what the given measure would look like,
* under a null hypothesis that the source values of our
* samples had no relation to the destination value (possibly
* in the context of a conditional value).
*
* <p>See Section II.E "Statistical significance testing" of
* the JIDT paper below for a description of how this is done MI,
* conditional MI and TE.
* </p>
*
* <p>Note that if several disjoint time-series have been added
* as observations using addObservations methods etc.,
* then these separate "trials" will be mixed up in the generation
* of surrogates here.</p>
*
* <p>This method (in contrast to {@link #computeSignificance(int)})
* allows the user to specify how to construct the surrogates,
* such that repeatable results may be obtained.</p>
*
* <p><b>References:</b><br/>
* <ul>
* <li>J.T. Lizier, "JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems", 2014.</li>
* </ul>
*
* @param newOrderings a specification of how to shuffle the next values
* to create the surrogates to generate the distribution with. The first
* index is the permutation number (i.e. newOrderings.length is the number
* of surrogate samples we use to bootstrap to generate the distribution here.)
* Each array newOrderings[i] should be an array of length N (where
* would be the value returned by a call to getNumObservations() for the given measure)
* containing a permutation of the values in 0..(N-1).
* @see "J.T. Lizier, 'JIDT: An information-theoretic
* toolkit for studying the dynamics of complex systems', 2014."
* @return the empirical distribution of measure scores under this null hypothesis.
* @throws Exception where e.g. the newOrderings don't supply arrays of the correct
* length matching the number of observations that we have.
*/
public EmpiricalMeasurementDistribution computeSignificance(int[][] newOrderings) throws Exception;
}

View File

@ -18,6 +18,7 @@
package infodynamics.utils;
import infodynamics.utils.commonsmath3.distribution.ChiSquaredDistribution;
import infodynamics.utils.commonsmath3.special.Gamma;
/**
@ -336,7 +337,7 @@ public class MathsUtils {
}
/**
* <p>Return the value of the cummulative distribution function of the
* <p>Return the value of the cumulative distribution function of the
* chi-square distribution, evaluated at x, for k degrees of freedom.</p>
*
* <p>Note that this relies on our approximation of the error function,
@ -360,6 +361,20 @@ public class MathsUtils {
return Gamma.regularizedGammaP(((double)k)/2.0, x/2.0);
}
/**
* Return the value of the quantile (inverse of the CDF) for the
* given cdf value (between 0 and 1) of the chi-square distribution
* with l degrees of freedom.
*
* @param cdf value of the CDF to evaluate the inverse for
* @param k degrees of freedom (must have k>0)
* @return
*/
public static double chiSquareInv(double cdf, int k) {
ChiSquaredDistribution chi2dist = new ChiSquaredDistribution(k);
return chi2dist.inverseCumulativeProbability(cdf);
}
/**
* Return the value of the lower Incomplete Gamma function,
* given arguments s/2 and x/2.

View File

@ -0,0 +1,87 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3;
/**
* Interface representing a <a href="http://mathworld.wolfram.com/Field.html">field</a>.
* <p>
* Classes implementing this interface will often be singletons.
* </p>
* @param <T> the type of the field elements
* @see FieldElement
* @since 2.0
*/
public interface Field<T> {
/** Get the additive identity of the field.
* <p>
* The additive identity is the element e<sub>0</sub> of the field such that
* for all elements a of the field, the equalities a + e<sub>0</sub> =
* e<sub>0</sub> + a = a hold.
* </p>
* @return additive identity of the field
*/
T getZero();
/** Get the multiplicative identity of the field.
* <p>
* The multiplicative identity is the element e<sub>1</sub> of the field such that
* for all elements a of the field, the equalities a &times; e<sub>1</sub> =
* e<sub>1</sub> &times; a = a hold.
* </p>
* @return multiplicative identity of the field
*/
T getOne();
/**
* Returns the runtime class of the FieldElement.
*
* @return The {@code Class} object that represents the runtime
* class of this object.
*/
Class<? extends FieldElement<T>> getRuntimeClass();
}

View File

@ -0,0 +1,116 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3;
import infodynamics.utils.commonsmath3.exception.MathArithmeticException;
import infodynamics.utils.commonsmath3.exception.NullArgumentException;
/**
* Interface representing <a href="http://mathworld.wolfram.com/Field.html">field</a> elements.
* @param <T> the type of the field elements
* @see Field
* @since 2.0
*/
public interface FieldElement<T> {
/** Compute this + a.
* @param a element to add
* @return a new element representing this + a
* @throws NullArgumentException if {@code a} is {@code null}.
*/
T add(T a) throws NullArgumentException;
/** Compute this - a.
* @param a element to subtract
* @return a new element representing this - a
* @throws NullArgumentException if {@code a} is {@code null}.
*/
T subtract(T a) throws NullArgumentException;
/**
* Returns the additive inverse of {@code this} element.
* @return the opposite of {@code this}.
*/
T negate();
/** Compute n &times; this. Multiplication by an integer number is defined
* as the following sum
* <center>
* n &times; this = &sum;<sub>i=1</sub><sup>n</sup> this.
* </center>
* @param n Number of times {@code this} must be added to itself.
* @return A new element representing n &times; this.
*/
T multiply(int n);
/** Compute this &times; a.
* @param a element to multiply
* @return a new element representing this &times; a
* @throws NullArgumentException if {@code a} is {@code null}.
*/
T multiply(T a) throws NullArgumentException;
/** Compute this &divide; a.
* @param a element to divide by
* @return a new element representing this &divide; a
* @throws NullArgumentException if {@code a} is {@code null}.
* @throws MathArithmeticException if {@code a} is zero
*/
T divide(T a) throws NullArgumentException, MathArithmeticException;
/**
* Returns the multiplicative inverse of {@code this} element.
* @return the inverse of {@code this}.
* @throws MathArithmeticException if {@code this} is zero
*/
T reciprocal() throws MathArithmeticException;
/** Get the {@link Field} to which the instance belongs.
* @return {@link Field} to which the instance belongs
*/
Field<T> getField();
}

View File

@ -0,0 +1,431 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3;
import infodynamics.utils.commonsmath3.exception.DimensionMismatchException;
/**
* Interface representing a <a href="http://mathworld.wolfram.com/RealNumber.html">real</a>
* <a href="http://mathworld.wolfram.com/Field.html">field</a>.
* @param <T> the type of the field elements
* @see FieldElement
* @since 3.2
*/
public interface RealFieldElement<T> extends FieldElement<T> {
/** Get the real value of the number.
* @return real value
*/
double getReal();
/** '+' operator.
* @param a right hand side parameter of the operator
* @return this+a
*/
T add(double a);
/** '-' operator.
* @param a right hand side parameter of the operator
* @return this-a
*/
T subtract(double a);
/** '&times;' operator.
* @param a right hand side parameter of the operator
* @return this&times;a
*/
T multiply(double a);
/** '&divide;' operator.
* @param a right hand side parameter of the operator
* @return this&divide;a
*/
T divide(double a);
/** IEEE remainder operator.
* @param a right hand side parameter of the operator
* @return this - n &times; a where n is the closest integer to this/a
* (the even integer is chosen for n if this/a is halfway between two integers)
*/
T remainder(double a);
/** IEEE remainder operator.
* @param a right hand side parameter of the operator
* @return this - n &times; a where n is the closest integer to this/a
* (the even integer is chosen for n if this/a is halfway between two integers)
* @exception DimensionMismatchException if number of free parameters or orders are inconsistent
*/
T remainder(T a)
throws DimensionMismatchException;
/** absolute value.
* @return abs(this)
*/
T abs();
/** Get the smallest whole number larger than instance.
* @return ceil(this)
*/
T ceil();
/** Get the largest whole number smaller than instance.
* @return floor(this)
*/
T floor();
/** Get the whole number that is the nearest to the instance, or the even one if x is exactly half way between two integers.
* @return a double number r such that r is an integer r - 0.5 &le; this &le; r + 0.5
*/
T rint();
/** Get the closest long to instance value.
* @return closest long to {@link #getReal()}
*/
long round();
/** Compute the signum of the instance.
* The signum is -1 for negative numbers, +1 for positive numbers and 0 otherwise
* @return -1.0, -0.0, +0.0, +1.0 or NaN depending on sign of a
*/
T signum();
/**
* Returns the instance with the sign of the argument.
* A NaN {@code sign} argument is treated as positive.
*
* @param sign the sign for the returned value
* @return the instance with the same sign as the {@code sign} argument
*/
T copySign(T sign);
/**
* Returns the instance with the sign of the argument.
* A NaN {@code sign} argument is treated as positive.
*
* @param sign the sign for the returned value
* @return the instance with the same sign as the {@code sign} argument
*/
T copySign(double sign);
/**
* Multiply the instance by a power of 2.
* @param n power of 2
* @return this &times; 2<sup>n</sup>
*/
T scalb(int n);
/**
* Returns the hypotenuse of a triangle with sides {@code this} and {@code y}
* - sqrt(<i>this</i><sup>2</sup>&nbsp;+<i>y</i><sup>2</sup>)
* avoiding intermediate overflow or underflow.
*
* <ul>
* <li> If either argument is infinite, then the result is positive infinity.</li>
* <li> else, if either argument is NaN then the result is NaN.</li>
* </ul>
*
* @param y a value
* @return sqrt(<i>this</i><sup>2</sup>&nbsp;+<i>y</i><sup>2</sup>)
* @exception DimensionMismatchException if number of free parameters or orders are inconsistent
*/
T hypot(T y)
throws DimensionMismatchException;
/** {@inheritDoc} */
T reciprocal();
/** Square root.
* @return square root of the instance
*/
T sqrt();
/** Cubic root.
* @return cubic root of the instance
*/
T cbrt();
/** N<sup>th</sup> root.
* @param n order of the root
* @return n<sup>th</sup> root of the instance
*/
T rootN(int n);
/** Power operation.
* @param p power to apply
* @return this<sup>p</sup>
*/
T pow(double p);
/** Integer power operation.
* @param n power to apply
* @return this<sup>n</sup>
*/
T pow(int n);
/** Power operation.
* @param e exponent
* @return this<sup>e</sup>
* @exception DimensionMismatchException if number of free parameters or orders are inconsistent
*/
T pow(T e)
throws DimensionMismatchException;
/** Exponential.
* @return exponential of the instance
*/
T exp();
/** Exponential minus 1.
* @return exponential minus one of the instance
*/
T expm1();
/** Natural logarithm.
* @return logarithm of the instance
*/
T log();
/** Shifted natural logarithm.
* @return logarithm of one plus the instance
*/
T log1p();
// TODO: add this method in 4.0, as it is not possible to do it in 3.2
// due to incompatibility of the return type in the Dfp class
// /** Base 10 logarithm.
// * @return base 10 logarithm of the instance
// */
// T log10();
/** Cosine operation.
* @return cos(this)
*/
T cos();
/** Sine operation.
* @return sin(this)
*/
T sin();
/** Tangent operation.
* @return tan(this)
*/
T tan();
/** Arc cosine operation.
* @return acos(this)
*/
T acos();
/** Arc sine operation.
* @return asin(this)
*/
T asin();
/** Arc tangent operation.
* @return atan(this)
*/
T atan();
/** Two arguments arc tangent operation.
* @param x second argument of the arc tangent
* @return atan2(this, x)
* @exception DimensionMismatchException if number of free parameters or orders are inconsistent
*/
T atan2(T x)
throws DimensionMismatchException;
/** Hyperbolic cosine operation.
* @return cosh(this)
*/
T cosh();
/** Hyperbolic sine operation.
* @return sinh(this)
*/
T sinh();
/** Hyperbolic tangent operation.
* @return tanh(this)
*/
T tanh();
/** Inverse hyperbolic cosine operation.
* @return acosh(this)
*/
T acosh();
/** Inverse hyperbolic sine operation.
* @return asin(this)
*/
T asinh();
/** Inverse hyperbolic tangent operation.
* @return atanh(this)
*/
T atanh();
/**
* Compute a linear combination.
* @param a Factors.
* @param b Factors.
* @return <code>&Sigma;<sub>i</sub> a<sub>i</sub> b<sub>i</sub></code>.
* @throws DimensionMismatchException if arrays dimensions don't match
* @since 3.2
*/
T linearCombination(T[] a, T[] b)
throws DimensionMismatchException;
/**
* Compute a linear combination.
* @param a Factors.
* @param b Factors.
* @return <code>&Sigma;<sub>i</sub> a<sub>i</sub> b<sub>i</sub></code>.
* @throws DimensionMismatchException if arrays dimensions don't match
* @since 3.2
*/
T linearCombination(double[] a, T[] b)
throws DimensionMismatchException;
/**
* Compute a linear combination.
* @param a1 first factor of the first term
* @param b1 second factor of the first term
* @param a2 first factor of the second term
* @param b2 second factor of the second term
* @return a<sub>1</sub>&times;b<sub>1</sub> +
* a<sub>2</sub>&times;b<sub>2</sub>
* @see #linearCombination(Object, Object, Object, Object, Object, Object)
* @see #linearCombination(Object, Object, Object, Object, Object, Object, Object, Object)
* @since 3.2
*/
T linearCombination(T a1, T b1, T a2, T b2);
/**
* Compute a linear combination.
* @param a1 first factor of the first term
* @param b1 second factor of the first term
* @param a2 first factor of the second term
* @param b2 second factor of the second term
* @return a<sub>1</sub>&times;b<sub>1</sub> +
* a<sub>2</sub>&times;b<sub>2</sub>
* @see #linearCombination(double, Object, double, Object, double, Object)
* @see #linearCombination(double, Object, double, Object, double, Object, double, Object)
* @since 3.2
*/
T linearCombination(double a1, T b1, double a2, T b2);
/**
* Compute a linear combination.
* @param a1 first factor of the first term
* @param b1 second factor of the first term
* @param a2 first factor of the second term
* @param b2 second factor of the second term
* @param a3 first factor of the third term
* @param b3 second factor of the third term
* @return a<sub>1</sub>&times;b<sub>1</sub> +
* a<sub>2</sub>&times;b<sub>2</sub> + a<sub>3</sub>&times;b<sub>3</sub>
* @see #linearCombination(Object, Object, Object, Object)
* @see #linearCombination(Object, Object, Object, Object, Object, Object, Object, Object)
* @since 3.2
*/
T linearCombination(T a1, T b1, T a2, T b2, T a3, T b3);
/**
* Compute a linear combination.
* @param a1 first factor of the first term
* @param b1 second factor of the first term
* @param a2 first factor of the second term
* @param b2 second factor of the second term
* @param a3 first factor of the third term
* @param b3 second factor of the third term
* @return a<sub>1</sub>&times;b<sub>1</sub> +
* a<sub>2</sub>&times;b<sub>2</sub> + a<sub>3</sub>&times;b<sub>3</sub>
* @see #linearCombination(double, Object, double, Object)
* @see #linearCombination(double, Object, double, Object, double, Object, double, Object)
* @since 3.2
*/
T linearCombination(double a1, T b1, double a2, T b2, double a3, T b3);
/**
* Compute a linear combination.
* @param a1 first factor of the first term
* @param b1 second factor of the first term
* @param a2 first factor of the second term
* @param b2 second factor of the second term
* @param a3 first factor of the third term
* @param b3 second factor of the third term
* @param a4 first factor of the third term
* @param b4 second factor of the third term
* @return a<sub>1</sub>&times;b<sub>1</sub> +
* a<sub>2</sub>&times;b<sub>2</sub> + a<sub>3</sub>&times;b<sub>3</sub> +
* a<sub>4</sub>&times;b<sub>4</sub>
* @see #linearCombination(Object, Object, Object, Object)
* @see #linearCombination(Object, Object, Object, Object, Object, Object)
* @since 3.2
*/
T linearCombination(T a1, T b1, T a2, T b2, T a3, T b3, T a4, T b4);
/**
* Compute a linear combination.
* @param a1 first factor of the first term
* @param b1 second factor of the first term
* @param a2 first factor of the second term
* @param b2 second factor of the second term
* @param a3 first factor of the third term
* @param b3 second factor of the third term
* @param a4 first factor of the third term
* @param b4 second factor of the third term
* @return a<sub>1</sub>&times;b<sub>1</sub> +
* a<sub>2</sub>&times;b<sub>2</sub> + a<sub>3</sub>&times;b<sub>3</sub> +
* a<sub>4</sub>&times;b<sub>4</sub>
* @see #linearCombination(double, Object, double, Object)
* @see #linearCombination(double, Object, double, Object, double, Object)
* @since 3.2
*/
T linearCombination(double a1, T b1, double a2, T b2, double a3, T b3, double a4, T b4);
}

View File

@ -0,0 +1,110 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.analysis;
/**
* An interface representing a univariate real function.
* <p>
* When a <em>user-defined</em> function encounters an error during
* evaluation, the {@link #value(double) value} method should throw a
* <em>user-defined</em> unchecked exception.</p>
* <p>
* The following code excerpt shows the recommended way to do that using
* a root solver as an example, but the same construct is applicable to
* ODE integrators or optimizers.</p>
*
* <pre>
* private static class LocalException extends RuntimeException {
* // The x value that caused the problem.
* private final double x;
*
* public LocalException(double x) {
* this.x = x;
* }
*
* public double getX() {
* return x;
* }
* }
*
* private static class MyFunction implements UnivariateFunction {
* public double value(double x) {
* double y = hugeFormula(x);
* if (somethingBadHappens) {
* throw new LocalException(x);
* }
* return y;
* }
* }
*
* public void compute() {
* try {
* solver.solve(maxEval, new MyFunction(a, b, c), min, max);
* } catch (LocalException le) {
* // Retrieve the x value.
* }
* }
* </pre>
*
* As shown, the exception is local to the user's code and it is guaranteed
* that Apache Commons Math will not catch it.
*
*/
public interface UnivariateFunction {
/**
* Compute the value of the function.
*
* @param x Point at which the function value should be computed.
* @return the value of the function.
* @throws IllegalArgumentException when the activated method itself can
* ascertain that a precondition, specified in the API expressed at the
* level of the activated method, has been violated.
* When Commons Math throws an {@code IllegalArgumentException}, it is
* usually the consequence of checking the actual parameters passed to
* the method.
*/
double value(double x);
}

View File

@ -0,0 +1,89 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.analysis.solvers;
import infodynamics.utils.commonsmath3.analysis.UnivariateFunction;
/**
* Base class for solvers.
*
* @since 3.0
*/
public abstract class AbstractUnivariateSolver
extends BaseAbstractUnivariateSolver<UnivariateFunction>
implements UnivariateSolver {
/**
* Construct a solver with given absolute accuracy.
*
* @param absoluteAccuracy Maximum absolute error.
*/
protected AbstractUnivariateSolver(final double absoluteAccuracy) {
super(absoluteAccuracy);
}
/**
* Construct a solver with given accuracies.
*
* @param relativeAccuracy Maximum relative error.
* @param absoluteAccuracy Maximum absolute error.
*/
protected AbstractUnivariateSolver(final double relativeAccuracy,
final double absoluteAccuracy) {
super(relativeAccuracy, absoluteAccuracy);
}
/**
* Construct a solver with given accuracies.
*
* @param relativeAccuracy Maximum relative error.
* @param absoluteAccuracy Maximum absolute error.
* @param functionValueAccuracy Maximum function value error.
*/
protected AbstractUnivariateSolver(final double relativeAccuracy,
final double absoluteAccuracy,
final double functionValueAccuracy) {
super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
}
}

View File

@ -0,0 +1,104 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.analysis.solvers;
/** The kinds of solutions that a {@link BracketedUnivariateSolver
* (bracketed univariate real) root-finding algorithm} may accept as solutions.
* This basically controls whether or not under-approximations and
* over-approximations are allowed.
*
* <p>If all solutions are accepted ({@link #ANY_SIDE}), then the solution
* that the root-finding algorithm returns for a given root may be equal to the
* actual root, but it may also be an approximation that is slightly smaller
* or slightly larger than the actual root. Root-finding algorithms generally
* only guarantee that the returned solution is within the requested
* tolerances. In certain cases however, in particular for
* {@link infodynamics.utils.commonsmath3.ode.events.EventHandler state events} of
* {@link infodynamics.utils.commonsmath3.ode.ODEIntegrator ODE solvers}, it
* may be necessary to guarantee that a solution is returned that lies on a
* specific side the solution.</p>
*
* @see BracketedUnivariateSolver
* @since 3.0
*/
public enum AllowedSolution {
/** There are no additional side restriction on the solutions for
* root-finding. That is, both under-approximations and over-approximations
* are allowed. So, if a function f(x) has a root at x = x0, then the
* root-finding result s may be smaller than x0, equal to x0, or greater
* than x0.
*/
ANY_SIDE,
/** Only solutions that are less than or equal to the actual root are
* acceptable as solutions for root-finding. In other words,
* over-approximations are not allowed. So, if a function f(x) has a root
* at x = x0, then the root-finding result s must satisfy s &lt;= x0.
*/
LEFT_SIDE,
/** Only solutions that are greater than or equal to the actual root are
* acceptable as solutions for root-finding. In other words,
* under-approximations are not allowed. So, if a function f(x) has a root
* at x = x0, then the root-finding result s must satisfy s &gt;= x0.
*/
RIGHT_SIDE,
/** Only solutions for which values are less than or equal to zero are
* acceptable as solutions for root-finding. So, if a function f(x) has
* a root at x = x0, then the root-finding result s must satisfy f(s) &lt;= 0.
*/
BELOW_SIDE,
/** Only solutions for which values are greater than or equal to zero are
* acceptable as solutions for root-finding. So, if a function f(x) has
* a root at x = x0, then the root-finding result s must satisfy f(s) &gt;= 0.
*/
ABOVE_SIDE;
}

View File

@ -0,0 +1,347 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.analysis.solvers;
import infodynamics.utils.commonsmath3.analysis.UnivariateFunction;
import infodynamics.utils.commonsmath3.exception.MaxCountExceededException;
import infodynamics.utils.commonsmath3.exception.NoBracketingException;
import infodynamics.utils.commonsmath3.exception.TooManyEvaluationsException;
import infodynamics.utils.commonsmath3.exception.NumberIsTooLargeException;
import infodynamics.utils.commonsmath3.exception.NullArgumentException;
import infodynamics.utils.commonsmath3.util.IntegerSequence;
import infodynamics.utils.commonsmath3.util.MathUtils;
/**
* Provide a default implementation for several functions useful to generic
* solvers.
* The default values for relative and function tolerances are 1e-14
* and 1e-15, respectively. It is however highly recommended to not
* rely on the default, but rather carefully consider values that match
* user's expectations, as well as the specifics of each implementation.
*
* @param <FUNC> Type of function to solve.
*
* @since 2.0
*/
public abstract class BaseAbstractUnivariateSolver<FUNC extends UnivariateFunction>
implements BaseUnivariateSolver<FUNC> {
/** Default relative accuracy. */
private static final double DEFAULT_RELATIVE_ACCURACY = 1e-14;
/** Default function value accuracy. */
private static final double DEFAULT_FUNCTION_VALUE_ACCURACY = 1e-15;
/** Function value accuracy. */
private final double functionValueAccuracy;
/** Absolute accuracy. */
private final double absoluteAccuracy;
/** Relative accuracy. */
private final double relativeAccuracy;
/** Evaluations counter. */
private IntegerSequence.Incrementor evaluations;
/** Lower end of search interval. */
private double searchMin;
/** Higher end of search interval. */
private double searchMax;
/** Initial guess. */
private double searchStart;
/** Function to solve. */
private FUNC function;
/**
* Construct a solver with given absolute accuracy.
*
* @param absoluteAccuracy Maximum absolute error.
*/
protected BaseAbstractUnivariateSolver(final double absoluteAccuracy) {
this(DEFAULT_RELATIVE_ACCURACY,
absoluteAccuracy,
DEFAULT_FUNCTION_VALUE_ACCURACY);
}
/**
* Construct a solver with given accuracies.
*
* @param relativeAccuracy Maximum relative error.
* @param absoluteAccuracy Maximum absolute error.
*/
protected BaseAbstractUnivariateSolver(final double relativeAccuracy,
final double absoluteAccuracy) {
this(relativeAccuracy,
absoluteAccuracy,
DEFAULT_FUNCTION_VALUE_ACCURACY);
}
/**
* Construct a solver with given accuracies.
*
* @param relativeAccuracy Maximum relative error.
* @param absoluteAccuracy Maximum absolute error.
* @param functionValueAccuracy Maximum function value error.
*/
protected BaseAbstractUnivariateSolver(final double relativeAccuracy,
final double absoluteAccuracy,
final double functionValueAccuracy) {
this.absoluteAccuracy = absoluteAccuracy;
this.relativeAccuracy = relativeAccuracy;
this.functionValueAccuracy = functionValueAccuracy;
this.evaluations = IntegerSequence.Incrementor.create();
}
/** {@inheritDoc} */
public int getMaxEvaluations() {
return evaluations.getMaximalCount();
}
/** {@inheritDoc} */
public int getEvaluations() {
return evaluations.getCount();
}
/**
* @return the lower end of the search interval.
*/
public double getMin() {
return searchMin;
}
/**
* @return the higher end of the search interval.
*/
public double getMax() {
return searchMax;
}
/**
* @return the initial guess.
*/
public double getStartValue() {
return searchStart;
}
/**
* {@inheritDoc}
*/
public double getAbsoluteAccuracy() {
return absoluteAccuracy;
}
/**
* {@inheritDoc}
*/
public double getRelativeAccuracy() {
return relativeAccuracy;
}
/**
* {@inheritDoc}
*/
public double getFunctionValueAccuracy() {
return functionValueAccuracy;
}
/**
* Compute the objective function value.
*
* @param point Point at which the objective function must be evaluated.
* @return the objective function value at specified point.
* @throws TooManyEvaluationsException if the maximal number of evaluations
* is exceeded.
*/
protected double computeObjectiveValue(double point)
throws TooManyEvaluationsException {
incrementEvaluationCount();
return function.value(point);
}
/**
* Prepare for computation.
* Subclasses must call this method if they override any of the
* {@code solve} methods.
*
* @param f Function to solve.
* @param min Lower bound for the interval.
* @param max Upper bound for the interval.
* @param startValue Start value to use.
* @param maxEval Maximum number of evaluations.
* @exception NullArgumentException if f is null
*/
protected void setup(int maxEval,
FUNC f,
double min, double max,
double startValue)
throws NullArgumentException {
// Checks.
MathUtils.checkNotNull(f);
// Reset.
searchMin = min;
searchMax = max;
searchStart = startValue;
function = f;
evaluations = evaluations.withMaximalCount(maxEval).withStart(0);
}
/** {@inheritDoc} */
public double solve(int maxEval, FUNC f, double min, double max, double startValue)
throws TooManyEvaluationsException,
NoBracketingException {
// Initialization.
setup(maxEval, f, min, max, startValue);
// Perform computation.
return doSolve();
}
/** {@inheritDoc} */
public double solve(int maxEval, FUNC f, double min, double max) {
return solve(maxEval, f, min, max, min + 0.5 * (max - min));
}
/** {@inheritDoc} */
public double solve(int maxEval, FUNC f, double startValue)
throws TooManyEvaluationsException,
NoBracketingException {
return solve(maxEval, f, Double.NaN, Double.NaN, startValue);
}
/**
* Method for implementing actual optimization algorithms in derived
* classes.
*
* @return the root.
* @throws TooManyEvaluationsException if the maximal number of evaluations
* is exceeded.
* @throws NoBracketingException if the initial search interval does not bracket
* a root and the solver requires it.
*/
protected abstract double doSolve()
throws TooManyEvaluationsException, NoBracketingException;
/**
* Check whether the function takes opposite signs at the endpoints.
*
* @param lower Lower endpoint.
* @param upper Upper endpoint.
* @return {@code true} if the function values have opposite signs at the
* given points.
*/
protected boolean isBracketing(final double lower,
final double upper) {
return UnivariateSolverUtils.isBracketing(function, lower, upper);
}
/**
* Check whether the arguments form a (strictly) increasing sequence.
*
* @param start First number.
* @param mid Second number.
* @param end Third number.
* @return {@code true} if the arguments form an increasing sequence.
*/
protected boolean isSequence(final double start,
final double mid,
final double end) {
return UnivariateSolverUtils.isSequence(start, mid, end);
}
/**
* Check that the endpoints specify an interval.
*
* @param lower Lower endpoint.
* @param upper Upper endpoint.
* @throws NumberIsTooLargeException if {@code lower >= upper}.
*/
protected void verifyInterval(final double lower,
final double upper)
throws NumberIsTooLargeException {
UnivariateSolverUtils.verifyInterval(lower, upper);
}
/**
* Check that {@code lower < initial < upper}.
*
* @param lower Lower endpoint.
* @param initial Initial value.
* @param upper Upper endpoint.
* @throws NumberIsTooLargeException if {@code lower >= initial} or
* {@code initial >= upper}.
*/
protected void verifySequence(final double lower,
final double initial,
final double upper)
throws NumberIsTooLargeException {
UnivariateSolverUtils.verifySequence(lower, initial, upper);
}
/**
* Check that the endpoints specify an interval and the function takes
* opposite signs at the endpoints.
*
* @param lower Lower endpoint.
* @param upper Upper endpoint.
* @throws NullArgumentException if the function has not been set.
* @throws NoBracketingException if the function has the same sign at
* the endpoints.
*/
protected void verifyBracketing(final double lower,
final double upper)
throws NullArgumentException,
NoBracketingException {
UnivariateSolverUtils.verifyBracketing(function, lower, upper);
}
/**
* Increment the evaluation count by one.
* Method {@link #computeObjectiveValue(double)} calls this method internally.
* It is provided for subclasses that do not exclusively use
* {@code computeObjectiveValue} to solve the function.
* See e.g. {@link AbstractUnivariateDifferentiableSolver}.
*
* @throws TooManyEvaluationsException when the allowed number of function
* evaluations has been exhausted.
*/
protected void incrementEvaluationCount()
throws TooManyEvaluationsException {
try {
evaluations.increment();
} catch (MaxCountExceededException e) {
throw new TooManyEvaluationsException(e.getMax());
}
}
}

View File

@ -0,0 +1,171 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.analysis.solvers;
import infodynamics.utils.commonsmath3.analysis.UnivariateFunction;
import infodynamics.utils.commonsmath3.exception.MathIllegalArgumentException;
import infodynamics.utils.commonsmath3.exception.TooManyEvaluationsException;
/**
* Interface for (univariate real) rootfinding algorithms.
* Implementations will search for only one zero in the given interval.
*
* This class is not intended for use outside of the Apache Commons Math
* library, regular user should rely on more specific interfaces like
* {@link UnivariateSolver}, {@link PolynomialSolver} or {@link
* DifferentiableUnivariateSolver}.
* @param <FUNC> Type of function to solve.
*
* @since 3.0
* @see UnivariateSolver
* @see PolynomialSolver
* @see DifferentiableUnivariateSolver
*/
public interface BaseUnivariateSolver<FUNC extends UnivariateFunction> {
/**
* Get the maximum number of function evaluations.
*
* @return the maximum number of function evaluations.
*/
int getMaxEvaluations();
/**
* Get the number of evaluations of the objective function.
* The number of evaluations corresponds to the last call to the
* {@code optimize} method. It is 0 if the method has not been
* called yet.
*
* @return the number of evaluations of the objective function.
*/
int getEvaluations();
/**
* Get the absolute accuracy of the solver. Solutions returned by the
* solver should be accurate to this tolerance, i.e., if &epsilon; is the
* absolute accuracy of the solver and {@code v} is a value returned by
* one of the {@code solve} methods, then a root of the function should
* exist somewhere in the interval ({@code v} - &epsilon;, {@code v} + &epsilon;).
*
* @return the absolute accuracy.
*/
double getAbsoluteAccuracy();
/**
* Get the relative accuracy of the solver. The contract for relative
* accuracy is the same as {@link #getAbsoluteAccuracy()}, but using
* relative, rather than absolute error. If &rho; is the relative accuracy
* configured for a solver and {@code v} is a value returned, then a root
* of the function should exist somewhere in the interval
* ({@code v} - &rho; {@code v}, {@code v} + &rho; {@code v}).
*
* @return the relative accuracy.
*/
double getRelativeAccuracy();
/**
* Get the function value accuracy of the solver. If {@code v} is
* a value returned by the solver for a function {@code f},
* then by contract, {@code |f(v)|} should be less than or equal to
* the function value accuracy configured for the solver.
*
* @return the function value accuracy.
*/
double getFunctionValueAccuracy();
/**
* Solve for a zero root in the given interval.
* A solver may require that the interval brackets a single zero root.
* Solvers that do require bracketing should be able to handle the case
* where one of the endpoints is itself a root.
*
* @param maxEval Maximum number of evaluations.
* @param f Function to solve.
* @param min Lower bound for the interval.
* @param max Upper bound for the interval.
* @return a value where the function is zero.
* @throws MathIllegalArgumentException
* if the arguments do not satisfy the requirements specified by the solver.
* @throws TooManyEvaluationsException if
* the allowed number of evaluations is exceeded.
*/
double solve(int maxEval, FUNC f, double min, double max)
throws MathIllegalArgumentException, TooManyEvaluationsException;
/**
* Solve for a zero in the given interval, start at {@code startValue}.
* A solver may require that the interval brackets a single zero root.
* Solvers that do require bracketing should be able to handle the case
* where one of the endpoints is itself a root.
*
* @param maxEval Maximum number of evaluations.
* @param f Function to solve.
* @param min Lower bound for the interval.
* @param max Upper bound for the interval.
* @param startValue Start value to use.
* @return a value where the function is zero.
* @throws MathIllegalArgumentException
* if the arguments do not satisfy the requirements specified by the solver.
* @throws TooManyEvaluationsException if
* the allowed number of evaluations is exceeded.
*/
double solve(int maxEval, FUNC f, double min, double max, double startValue)
throws MathIllegalArgumentException, TooManyEvaluationsException;
/**
* Solve for a zero in the vicinity of {@code startValue}.
*
* @param f Function to solve.
* @param startValue Start value to use.
* @return a value where the function is zero.
* @param maxEval Maximum number of evaluations.
* @throws infodynamics.utils.commonsmath3.exception.MathIllegalArgumentException
* if the arguments do not satisfy the requirements specified by the solver.
* @throws infodynamics.utils.commonsmath3.exception.TooManyEvaluationsException if
* the allowed number of evaluations is exceeded.
*/
double solve(int maxEval, FUNC f, double startValue);
}

View File

@ -0,0 +1,121 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.analysis.solvers;
import infodynamics.utils.commonsmath3.analysis.UnivariateFunction;
/** Interface for {@link UnivariateSolver (univariate real) root-finding
* algorithms} that maintain a bracketed solution. There are several advantages
* to having such root-finding algorithms:
* <ul>
* <li>The bracketed solution guarantees that the root is kept within the
* interval. As such, these algorithms generally also guarantee
* convergence.</li>
* <li>The bracketed solution means that we have the opportunity to only
* return roots that are greater than or equal to the actual root, or
* are less than or equal to the actual root. That is, we can control
* whether under-approximations and over-approximations are
* {@link AllowedSolution allowed solutions}. Other root-finding
* algorithms can usually only guarantee that the solution (the root that
* was found) is around the actual root.</li>
* </ul>
*
* <p>For backwards compatibility, all root-finding algorithms must have
* {@link AllowedSolution#ANY_SIDE ANY_SIDE} as default for the allowed
* solutions.</p>
* @param <FUNC> Type of function to solve.
*
* @see AllowedSolution
* @since 3.0
*/
public interface BracketedUnivariateSolver<FUNC extends UnivariateFunction>
extends BaseUnivariateSolver<FUNC> {
/**
* Solve for a zero in the given interval.
* A solver may require that the interval brackets a single zero root.
* Solvers that do require bracketing should be able to handle the case
* where one of the endpoints is itself a root.
*
* @param maxEval Maximum number of evaluations.
* @param f Function to solve.
* @param min Lower bound for the interval.
* @param max Upper bound for the interval.
* @param allowedSolution The kind of solutions that the root-finding algorithm may
* accept as solutions.
* @return A value where the function is zero.
* @throws infodynamics.utils.commonsmath3.exception.MathIllegalArgumentException
* if the arguments do not satisfy the requirements specified by the solver.
* @throws infodynamics.utils.commonsmath3.exception.TooManyEvaluationsException if
* the allowed number of evaluations is exceeded.
*/
double solve(int maxEval, FUNC f, double min, double max,
AllowedSolution allowedSolution);
/**
* Solve for a zero in the given interval, start at {@code startValue}.
* A solver may require that the interval brackets a single zero root.
* Solvers that do require bracketing should be able to handle the case
* where one of the endpoints is itself a root.
*
* @param maxEval Maximum number of evaluations.
* @param f Function to solve.
* @param min Lower bound for the interval.
* @param max Upper bound for the interval.
* @param startValue Start value to use.
* @param allowedSolution The kind of solutions that the root-finding algorithm may
* accept as solutions.
* @return A value where the function is zero.
* @throws infodynamics.utils.commonsmath3.exception.MathIllegalArgumentException
* if the arguments do not satisfy the requirements specified by the solver.
* @throws infodynamics.utils.commonsmath3.exception.TooManyEvaluationsException if
* the allowed number of evaluations is exceeded.
*/
double solve(int maxEval, FUNC f, double min, double max, double startValue,
AllowedSolution allowedSolution);
}

View File

@ -0,0 +1,272 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.analysis.solvers;
import infodynamics.utils.commonsmath3.exception.NoBracketingException;
import infodynamics.utils.commonsmath3.exception.NumberIsTooLargeException;
import infodynamics.utils.commonsmath3.exception.TooManyEvaluationsException;
import infodynamics.utils.commonsmath3.util.FastMath;
import infodynamics.utils.commonsmath3.util.Precision;
/**
* This class implements the <a href="http://mathworld.wolfram.com/BrentsMethod.html">
* Brent algorithm</a> for finding zeros of real univariate functions.
* The function should be continuous but not necessarily smooth.
* The {@code solve} method returns a zero {@code x} of the function {@code f}
* in the given interval {@code [a, b]} to within a tolerance
* {@code 2 eps abs(x) + t} where {@code eps} is the relative accuracy and
* {@code t} is the absolute accuracy.
* <p>The given interval must bracket the root.</p>
* <p>
* The reference implementation is given in chapter 4 of
* <blockquote>
* <b>Algorithms for Minimization Without Derivatives</b>,
* <em>Richard P. Brent</em>,
* Dover, 2002
* </blockquote>
*
* @see BaseAbstractUnivariateSolver
*/
public class BrentSolver extends AbstractUnivariateSolver {
/** Default absolute accuracy. */
private static final double DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
/**
* Construct a solver with default absolute accuracy (1e-6).
*/
public BrentSolver() {
this(DEFAULT_ABSOLUTE_ACCURACY);
}
/**
* Construct a solver.
*
* @param absoluteAccuracy Absolute accuracy.
*/
public BrentSolver(double absoluteAccuracy) {
super(absoluteAccuracy);
}
/**
* Construct a solver.
*
* @param relativeAccuracy Relative accuracy.
* @param absoluteAccuracy Absolute accuracy.
*/
public BrentSolver(double relativeAccuracy,
double absoluteAccuracy) {
super(relativeAccuracy, absoluteAccuracy);
}
/**
* Construct a solver.
*
* @param relativeAccuracy Relative accuracy.
* @param absoluteAccuracy Absolute accuracy.
* @param functionValueAccuracy Function value accuracy.
*
* @see BaseAbstractUnivariateSolver#BaseAbstractUnivariateSolver(double,double,double)
*/
public BrentSolver(double relativeAccuracy,
double absoluteAccuracy,
double functionValueAccuracy) {
super(relativeAccuracy, absoluteAccuracy, functionValueAccuracy);
}
/**
* {@inheritDoc}
*/
@Override
protected double doSolve()
throws NoBracketingException,
TooManyEvaluationsException,
NumberIsTooLargeException {
double min = getMin();
double max = getMax();
final double initial = getStartValue();
final double functionValueAccuracy = getFunctionValueAccuracy();
verifySequence(min, initial, max);
// Return the initial guess if it is good enough.
double yInitial = computeObjectiveValue(initial);
if (FastMath.abs(yInitial) <= functionValueAccuracy) {
return initial;
}
// Return the first endpoint if it is good enough.
double yMin = computeObjectiveValue(min);
if (FastMath.abs(yMin) <= functionValueAccuracy) {
return min;
}
// Reduce interval if min and initial bracket the root.
if (yInitial * yMin < 0) {
return brent(min, initial, yMin, yInitial);
}
// Return the second endpoint if it is good enough.
double yMax = computeObjectiveValue(max);
if (FastMath.abs(yMax) <= functionValueAccuracy) {
return max;
}
// Reduce interval if initial and max bracket the root.
if (yInitial * yMax < 0) {
return brent(initial, max, yInitial, yMax);
}
throw new NoBracketingException(min, max, yMin, yMax);
}
/**
* Search for a zero inside the provided interval.
* This implementation is based on the algorithm described at page 58 of
* the book
* <blockquote>
* <b>Algorithms for Minimization Without Derivatives</b>,
* <it>Richard P. Brent</it>,
* Dover 0-486-41998-3
* </blockquote>
*
* @param lo Lower bound of the search interval.
* @param hi Higher bound of the search interval.
* @param fLo Function value at the lower bound of the search interval.
* @param fHi Function value at the higher bound of the search interval.
* @return the value where the function is zero.
*/
private double brent(double lo, double hi,
double fLo, double fHi) {
double a = lo;
double fa = fLo;
double b = hi;
double fb = fHi;
double c = a;
double fc = fa;
double d = b - a;
double e = d;
final double t = getAbsoluteAccuracy();
final double eps = getRelativeAccuracy();
while (true) {
if (FastMath.abs(fc) < FastMath.abs(fb)) {
a = b;
b = c;
c = a;
fa = fb;
fb = fc;
fc = fa;
}
final double tol = 2 * eps * FastMath.abs(b) + t;
final double m = 0.5 * (c - b);
if (FastMath.abs(m) <= tol ||
Precision.equals(fb, 0)) {
return b;
}
if (FastMath.abs(e) < tol ||
FastMath.abs(fa) <= FastMath.abs(fb)) {
// Force bisection.
d = m;
e = d;
} else {
double s = fb / fa;
double p;
double q;
// The equality test (a == c) is intentional,
// it is part of the original Brent's method and
// it should NOT be replaced by proximity test.
if (a == c) {
// Linear interpolation.
p = 2 * m * s;
q = 1 - s;
} else {
// Inverse quadratic interpolation.
q = fa / fc;
final double r = fb / fc;
p = s * (2 * m * q * (q - r) - (b - a) * (r - 1));
q = (q - 1) * (r - 1) * (s - 1);
}
if (p > 0) {
q = -q;
} else {
p = -p;
}
s = e;
e = d;
if (p >= 1.5 * m * q - FastMath.abs(tol * q) ||
p >= FastMath.abs(0.5 * s * q)) {
// Inverse quadratic interpolation gives a value
// in the wrong direction, or progress is slow.
// Fall back to bisection.
d = m;
e = d;
} else {
d = p / q;
}
}
a = b;
fa = fb;
if (FastMath.abs(d) > tol) {
b += d;
} else if (m > 0) {
b += tol;
} else {
b -= tol;
}
fb = computeObjectiveValue(b);
if ((fb > 0 && fc > 0) ||
(fb <= 0 && fc <= 0)) {
c = a;
fc = fa;
d = b - a;
e = d;
}
}
}
}

View File

@ -0,0 +1,57 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.analysis.solvers;
import infodynamics.utils.commonsmath3.analysis.UnivariateFunction;
/**
* Interface for (univariate real) root-finding algorithms.
* Implementations will search for only one zero in the given interval.
*
*/
public interface UnivariateSolver
extends BaseUnivariateSolver<UnivariateFunction> {}

View File

@ -0,0 +1,496 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.analysis.solvers;
import infodynamics.utils.commonsmath3.analysis.UnivariateFunction;
import infodynamics.utils.commonsmath3.exception.NoBracketingException;
import infodynamics.utils.commonsmath3.exception.NotStrictlyPositiveException;
import infodynamics.utils.commonsmath3.exception.NullArgumentException;
import infodynamics.utils.commonsmath3.exception.NumberIsTooLargeException;
import infodynamics.utils.commonsmath3.exception.util.LocalizedFormats;
import infodynamics.utils.commonsmath3.util.FastMath;
/**
* Utility routines for {@link UnivariateSolver} objects.
*
*/
public class UnivariateSolverUtils {
/**
* Class contains only static methods.
*/
private UnivariateSolverUtils() {}
/**
* Convenience method to find a zero of a univariate real function. A default
* solver is used.
*
* @param function Function.
* @param x0 Lower bound for the interval.
* @param x1 Upper bound for the interval.
* @return a value where the function is zero.
* @throws NoBracketingException if the function has the same sign at the
* endpoints.
* @throws NullArgumentException if {@code function} is {@code null}.
*/
public static double solve(UnivariateFunction function, double x0, double x1)
throws NullArgumentException,
NoBracketingException {
if (function == null) {
throw new NullArgumentException(LocalizedFormats.FUNCTION);
}
final UnivariateSolver solver = new BrentSolver();
return solver.solve(Integer.MAX_VALUE, function, x0, x1);
}
/**
* Convenience method to find a zero of a univariate real function. A default
* solver is used.
*
* @param function Function.
* @param x0 Lower bound for the interval.
* @param x1 Upper bound for the interval.
* @param absoluteAccuracy Accuracy to be used by the solver.
* @return a value where the function is zero.
* @throws NoBracketingException if the function has the same sign at the
* endpoints.
* @throws NullArgumentException if {@code function} is {@code null}.
*/
public static double solve(UnivariateFunction function,
double x0, double x1,
double absoluteAccuracy)
throws NullArgumentException,
NoBracketingException {
if (function == null) {
throw new NullArgumentException(LocalizedFormats.FUNCTION);
}
final UnivariateSolver solver = new BrentSolver(absoluteAccuracy);
return solver.solve(Integer.MAX_VALUE, function, x0, x1);
}
/**
* Force a root found by a non-bracketing solver to lie on a specified side,
* as if the solver were a bracketing one.
*
* @param maxEval maximal number of new evaluations of the function
* (evaluations already done for finding the root should have already been subtracted
* from this number)
* @param f function to solve
* @param bracketing bracketing solver to use for shifting the root
* @param baseRoot original root found by a previous non-bracketing solver
* @param min minimal bound of the search interval
* @param max maximal bound of the search interval
* @param allowedSolution the kind of solutions that the root-finding algorithm may
* accept as solutions.
* @return a root approximation, on the specified side of the exact root
* @throws NoBracketingException if the function has the same sign at the
* endpoints.
*/
public static double forceSide(final int maxEval, final UnivariateFunction f,
final BracketedUnivariateSolver<UnivariateFunction> bracketing,
final double baseRoot, final double min, final double max,
final AllowedSolution allowedSolution)
throws NoBracketingException {
if (allowedSolution == AllowedSolution.ANY_SIDE) {
// no further bracketing required
return baseRoot;
}
// find a very small interval bracketing the root
final double step = FastMath.max(bracketing.getAbsoluteAccuracy(),
FastMath.abs(baseRoot * bracketing.getRelativeAccuracy()));
double xLo = FastMath.max(min, baseRoot - step);
double fLo = f.value(xLo);
double xHi = FastMath.min(max, baseRoot + step);
double fHi = f.value(xHi);
int remainingEval = maxEval - 2;
while (remainingEval > 0) {
if ((fLo >= 0 && fHi <= 0) || (fLo <= 0 && fHi >= 0)) {
// compute the root on the selected side
return bracketing.solve(remainingEval, f, xLo, xHi, baseRoot, allowedSolution);
}
// try increasing the interval
boolean changeLo = false;
boolean changeHi = false;
if (fLo < fHi) {
// increasing function
if (fLo >= 0) {
changeLo = true;
} else {
changeHi = true;
}
} else if (fLo > fHi) {
// decreasing function
if (fLo <= 0) {
changeLo = true;
} else {
changeHi = true;
}
} else {
// unknown variation
changeLo = true;
changeHi = true;
}
// update the lower bound
if (changeLo) {
xLo = FastMath.max(min, xLo - step);
fLo = f.value(xLo);
remainingEval--;
}
// update the higher bound
if (changeHi) {
xHi = FastMath.min(max, xHi + step);
fHi = f.value(xHi);
remainingEval--;
}
}
throw new NoBracketingException(LocalizedFormats.FAILED_BRACKETING,
xLo, xHi, fLo, fHi,
maxEval - remainingEval, maxEval, baseRoot,
min, max);
}
/**
* This method simply calls {@link #bracket(UnivariateFunction, double, double, double,
* double, double, int) bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)}
* with {@code q} and {@code r} set to 1.0 and {@code maximumIterations} set to {@code Integer.MAX_VALUE}.
* <p>
* <strong>Note: </strong> this method can take {@code Integer.MAX_VALUE}
* iterations to throw a {@code ConvergenceException.} Unless you are
* confident that there is a root between {@code lowerBound} and
* {@code upperBound} near {@code initial}, it is better to use
* {@link #bracket(UnivariateFunction, double, double, double, double,double, int)
* bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)},
* explicitly specifying the maximum number of iterations.</p>
*
* @param function Function.
* @param initial Initial midpoint of interval being expanded to
* bracket a root.
* @param lowerBound Lower bound (a is never lower than this value)
* @param upperBound Upper bound (b never is greater than this
* value).
* @return a two-element array holding a and b.
* @throws NoBracketingException if a root cannot be bracketted.
* @throws NotStrictlyPositiveException if {@code maximumIterations <= 0}.
* @throws NullArgumentException if {@code function} is {@code null}.
*/
public static double[] bracket(UnivariateFunction function,
double initial,
double lowerBound, double upperBound)
throws NullArgumentException,
NotStrictlyPositiveException,
NoBracketingException {
return bracket(function, initial, lowerBound, upperBound, 1.0, 1.0, Integer.MAX_VALUE);
}
/**
* This method simply calls {@link #bracket(UnivariateFunction, double, double, double,
* double, double, int) bracket(function, initial, lowerBound, upperBound, q, r, maximumIterations)}
* with {@code q} and {@code r} set to 1.0.
* @param function Function.
* @param initial Initial midpoint of interval being expanded to
* bracket a root.
* @param lowerBound Lower bound (a is never lower than this value).
* @param upperBound Upper bound (b never is greater than this
* value).
* @param maximumIterations Maximum number of iterations to perform
* @return a two element array holding a and b.
* @throws NoBracketingException if the algorithm fails to find a and b
* satisfying the desired conditions.
* @throws NotStrictlyPositiveException if {@code maximumIterations <= 0}.
* @throws NullArgumentException if {@code function} is {@code null}.
*/
public static double[] bracket(UnivariateFunction function,
double initial,
double lowerBound, double upperBound,
int maximumIterations)
throws NullArgumentException,
NotStrictlyPositiveException,
NoBracketingException {
return bracket(function, initial, lowerBound, upperBound, 1.0, 1.0, maximumIterations);
}
/**
* This method attempts to find two values a and b satisfying <ul>
* <li> {@code lowerBound <= a < initial < b <= upperBound} </li>
* <li> {@code f(a) * f(b) <= 0} </li>
* </ul>
* If {@code f} is continuous on {@code [a,b]}, this means that {@code a}
* and {@code b} bracket a root of {@code f}.
* <p>
* The algorithm checks the sign of \( f(l_k) \) and \( f(u_k) \) for increasing
* values of k, where \( l_k = max(lower, initial - \delta_k) \),
* \( u_k = min(upper, initial + \delta_k) \), using recurrence
* \( \delta_{k+1} = r \delta_k + q, \delta_0 = 0\) and starting search with \( k=1 \).
* The algorithm stops when one of the following happens: <ul>
* <li> at least one positive and one negative value have been found -- success!</li>
* <li> both endpoints have reached their respective limits -- NoBracketingException </li>
* <li> {@code maximumIterations} iterations elapse -- NoBracketingException </li></ul>
* <p>
* If different signs are found at first iteration ({@code k=1}), then the returned
* interval will be \( [a, b] = [l_1, u_1] \). If different signs are found at a later
* iteration {@code k>1}, then the returned interval will be either
* \( [a, b] = [l_{k+1}, l_{k}] \) or \( [a, b] = [u_{k}, u_{k+1}] \). A root solver called
* with these parameters will therefore start with the smallest bracketing interval known
* at this step.
* </p>
* <p>
* Interval expansion rate is tuned by changing the recurrence parameters {@code r} and
* {@code q}. When the multiplicative factor {@code r} is set to 1, the sequence is a
* simple arithmetic sequence with linear increase. When the multiplicative factor {@code r}
* is larger than 1, the sequence has an asymptotically exponential rate. Note than the
* additive parameter {@code q} should never be set to zero, otherwise the interval would
* degenerate to the single initial point for all values of {@code k}.
* </p>
* <p>
* As a rule of thumb, when the location of the root is expected to be approximately known
* within some error margin, {@code r} should be set to 1 and {@code q} should be set to the
* order of magnitude of the error margin. When the location of the root is really a wild guess,
* then {@code r} should be set to a value larger than 1 (typically 2 to double the interval
* length at each iteration) and {@code q} should be set according to half the initial
* search interval length.
* </p>
* <p>
* As an example, if we consider the trivial function {@code f(x) = 1 - x} and use
* {@code initial = 4}, {@code r = 1}, {@code q = 2}, the algorithm will compute
* {@code f(4-2) = f(2) = -1} and {@code f(4+2) = f(6) = -5} for {@code k = 1}, then
* {@code f(4-4) = f(0) = +1} and {@code f(4+4) = f(8) = -7} for {@code k = 2}. Then it will
* return the interval {@code [0, 2]} as the smallest one known to be bracketing the root.
* As shown by this example, the initial value (here {@code 4}) may lie outside of the returned
* bracketing interval.
* </p>
* @param function function to check
* @param initial Initial midpoint of interval being expanded to
* bracket a root.
* @param lowerBound Lower bound (a is never lower than this value).
* @param upperBound Upper bound (b never is greater than this
* value).
* @param q additive offset used to compute bounds sequence (must be strictly positive)
* @param r multiplicative factor used to compute bounds sequence
* @param maximumIterations Maximum number of iterations to perform
* @return a two element array holding the bracketing values.
* @exception NoBracketingException if function cannot be bracketed in the search interval
*/
public static double[] bracket(final UnivariateFunction function, final double initial,
final double lowerBound, final double upperBound,
final double q, final double r, final int maximumIterations)
throws NoBracketingException {
if (function == null) {
throw new NullArgumentException(LocalizedFormats.FUNCTION);
}
if (q <= 0) {
throw new NotStrictlyPositiveException(q);
}
if (maximumIterations <= 0) {
throw new NotStrictlyPositiveException(LocalizedFormats.INVALID_MAX_ITERATIONS, maximumIterations);
}
verifySequence(lowerBound, initial, upperBound);
// initialize the recurrence
double a = initial;
double b = initial;
double fa = Double.NaN;
double fb = Double.NaN;
double delta = 0;
for (int numIterations = 0;
(numIterations < maximumIterations) && (a > lowerBound || b < upperBound);
++numIterations) {
final double previousA = a;
final double previousFa = fa;
final double previousB = b;
final double previousFb = fb;
delta = r * delta + q;
a = FastMath.max(initial - delta, lowerBound);
b = FastMath.min(initial + delta, upperBound);
fa = function.value(a);
fb = function.value(b);
if (numIterations == 0) {
// at first iteration, we don't have a previous interval
// we simply compare both sides of the initial interval
if (fa * fb <= 0) {
// the first interval already brackets a root
return new double[] { a, b };
}
} else {
// we have a previous interval with constant sign and expand it,
// we expect sign changes to occur at boundaries
if (fa * previousFa <= 0) {
// sign change detected at near lower bound
return new double[] { a, previousA };
} else if (fb * previousFb <= 0) {
// sign change detected at near upper bound
return new double[] { previousB, b };
}
}
}
// no bracketing found
throw new NoBracketingException(a, b, fa, fb);
}
/**
* Compute the midpoint of two values.
*
* @param a first value.
* @param b second value.
* @return the midpoint.
*/
public static double midpoint(double a, double b) {
return (a + b) * 0.5;
}
/**
* Check whether the interval bounds bracket a root. That is, if the
* values at the endpoints are not equal to zero, then the function takes
* opposite signs at the endpoints.
*
* @param function Function.
* @param lower Lower endpoint.
* @param upper Upper endpoint.
* @return {@code true} if the function values have opposite signs at the
* given points.
* @throws NullArgumentException if {@code function} is {@code null}.
*/
public static boolean isBracketing(UnivariateFunction function,
final double lower,
final double upper)
throws NullArgumentException {
if (function == null) {
throw new NullArgumentException(LocalizedFormats.FUNCTION);
}
final double fLo = function.value(lower);
final double fHi = function.value(upper);
return (fLo >= 0 && fHi <= 0) || (fLo <= 0 && fHi >= 0);
}
/**
* Check whether the arguments form a (strictly) increasing sequence.
*
* @param start First number.
* @param mid Second number.
* @param end Third number.
* @return {@code true} if the arguments form an increasing sequence.
*/
public static boolean isSequence(final double start,
final double mid,
final double end) {
return (start < mid) && (mid < end);
}
/**
* Check that the endpoints specify an interval.
*
* @param lower Lower endpoint.
* @param upper Upper endpoint.
* @throws NumberIsTooLargeException if {@code lower >= upper}.
*/
public static void verifyInterval(final double lower,
final double upper)
throws NumberIsTooLargeException {
if (lower >= upper) {
throw new NumberIsTooLargeException(LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL,
lower, upper, false);
}
}
/**
* Check that {@code lower < initial < upper}.
*
* @param lower Lower endpoint.
* @param initial Initial value.
* @param upper Upper endpoint.
* @throws NumberIsTooLargeException if {@code lower >= initial} or
* {@code initial >= upper}.
*/
public static void verifySequence(final double lower,
final double initial,
final double upper)
throws NumberIsTooLargeException {
verifyInterval(lower, initial);
verifyInterval(initial, upper);
}
/**
* Check that the endpoints specify an interval and the end points
* bracket a root.
*
* @param function Function.
* @param lower Lower endpoint.
* @param upper Upper endpoint.
* @throws NoBracketingException if the function has the same sign at the
* endpoints.
* @throws NullArgumentException if {@code function} is {@code null}.
*/
public static void verifyBracketing(UnivariateFunction function,
final double lower,
final double upper)
throws NullArgumentException,
NoBracketingException {
if (function == null) {
throw new NullArgumentException(LocalizedFormats.FUNCTION);
}
verifyInterval(lower, upper);
if (!isBracketing(function, lower, upper)) {
throw new NoBracketingException(lower, upper,
function.value(lower),
function.value(upper));
}
}
}

View File

@ -0,0 +1,282 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.distribution;
import java.io.Serializable;
import infodynamics.utils.commonsmath3.exception.MathInternalError;
import infodynamics.utils.commonsmath3.exception.NotStrictlyPositiveException;
import infodynamics.utils.commonsmath3.exception.NumberIsTooLargeException;
import infodynamics.utils.commonsmath3.exception.OutOfRangeException;
import infodynamics.utils.commonsmath3.exception.util.LocalizedFormats;
import infodynamics.utils.commonsmath3.random.RandomGenerator;
import infodynamics.utils.commonsmath3.util.FastMath;
/**
* Base class for integer-valued discrete distributions. Default
* implementations are provided for some of the methods that do not vary
* from distribution to distribution.
*
*/
public abstract class AbstractIntegerDistribution implements IntegerDistribution, Serializable {
/** Serializable version identifier */
private static final long serialVersionUID = -1146319659338487221L;
/**
* RandomData instance used to generate samples from the distribution.
* @deprecated As of 3.1, to be removed in 4.0. Please use the
* {@link #random} instance variable instead.
*/
@Deprecated
protected final infodynamics.utils.commonsmath3.random.RandomDataImpl randomData =
new infodynamics.utils.commonsmath3.random.RandomDataImpl();
/**
* RNG instance used to generate samples from the distribution.
* @since 3.1
*/
protected final RandomGenerator random;
/**
* @deprecated As of 3.1, to be removed in 4.0. Please use
* {@link #AbstractIntegerDistribution(RandomGenerator)} instead.
*/
@Deprecated
protected AbstractIntegerDistribution() {
// Legacy users are only allowed to access the deprecated "randomData".
// New users are forbidden to use this constructor.
random = null;
}
/**
* @param rng Random number generator.
* @since 3.1
*/
protected AbstractIntegerDistribution(RandomGenerator rng) {
random = rng;
}
/**
* {@inheritDoc}
*
* The default implementation uses the identity
* <p>{@code P(x0 < X <= x1) = P(X <= x1) - P(X <= x0)}</p>
*/
public double cumulativeProbability(int x0, int x1) throws NumberIsTooLargeException {
if (x1 < x0) {
throw new NumberIsTooLargeException(LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT,
x0, x1, true);
}
return cumulativeProbability(x1) - cumulativeProbability(x0);
}
/**
* {@inheritDoc}
*
* The default implementation returns
* <ul>
* <li>{@link #getSupportLowerBound()} for {@code p = 0},</li>
* <li>{@link #getSupportUpperBound()} for {@code p = 1}, and</li>
* <li>{@link #solveInverseCumulativeProbability(double, int, int)} for
* {@code 0 < p < 1}.</li>
* </ul>
*/
public int inverseCumulativeProbability(final double p) throws OutOfRangeException {
if (p < 0.0 || p > 1.0) {
throw new OutOfRangeException(p, 0, 1);
}
int lower = getSupportLowerBound();
if (p == 0.0) {
return lower;
}
if (lower == Integer.MIN_VALUE) {
if (checkedCumulativeProbability(lower) >= p) {
return lower;
}
} else {
lower -= 1; // this ensures cumulativeProbability(lower) < p, which
// is important for the solving step
}
int upper = getSupportUpperBound();
if (p == 1.0) {
return upper;
}
// use the one-sided Chebyshev inequality to narrow the bracket
// cf. AbstractRealDistribution.inverseCumulativeProbability(double)
final double mu = getNumericalMean();
final double sigma = FastMath.sqrt(getNumericalVariance());
final boolean chebyshevApplies = !(Double.isInfinite(mu) || Double.isNaN(mu) ||
Double.isInfinite(sigma) || Double.isNaN(sigma) || sigma == 0.0);
if (chebyshevApplies) {
double k = FastMath.sqrt((1.0 - p) / p);
double tmp = mu - k * sigma;
if (tmp > lower) {
lower = ((int) FastMath.ceil(tmp)) - 1;
}
k = 1.0 / k;
tmp = mu + k * sigma;
if (tmp < upper) {
upper = ((int) FastMath.ceil(tmp)) - 1;
}
}
return solveInverseCumulativeProbability(p, lower, upper);
}
/**
* This is a utility function used by {@link
* #inverseCumulativeProbability(double)}. It assumes {@code 0 < p < 1} and
* that the inverse cumulative probability lies in the bracket {@code
* (lower, upper]}. The implementation does simple bisection to find the
* smallest {@code p}-quantile <code>inf{x in Z | P(X<=x) >= p}</code>.
*
* @param p the cumulative probability
* @param lower a value satisfying {@code cumulativeProbability(lower) < p}
* @param upper a value satisfying {@code p <= cumulativeProbability(upper)}
* @return the smallest {@code p}-quantile of this distribution
*/
protected int solveInverseCumulativeProbability(final double p, int lower, int upper) {
while (lower + 1 < upper) {
int xm = (lower + upper) / 2;
if (xm < lower || xm > upper) {
/*
* Overflow.
* There will never be an overflow in both calculation methods
* for xm at the same time
*/
xm = lower + (upper - lower) / 2;
}
double pm = checkedCumulativeProbability(xm);
if (pm >= p) {
upper = xm;
} else {
lower = xm;
}
}
return upper;
}
/** {@inheritDoc} */
public void reseedRandomGenerator(long seed) {
random.setSeed(seed);
randomData.reSeed(seed);
}
/**
* {@inheritDoc}
*
* The default implementation uses the
* <a href="http://en.wikipedia.org/wiki/Inverse_transform_sampling">
* inversion method</a>.
*/
public int sample() {
return inverseCumulativeProbability(random.nextDouble());
}
/**
* {@inheritDoc}
*
* The default implementation generates the sample by calling
* {@link #sample()} in a loop.
*/
public int[] sample(int sampleSize) {
if (sampleSize <= 0) {
throw new NotStrictlyPositiveException(
LocalizedFormats.NUMBER_OF_SAMPLES, sampleSize);
}
int[] out = new int[sampleSize];
for (int i = 0; i < sampleSize; i++) {
out[i] = sample();
}
return out;
}
/**
* Computes the cumulative probability function and checks for {@code NaN}
* values returned. Throws {@code MathInternalError} if the value is
* {@code NaN}. Rethrows any exception encountered evaluating the cumulative
* probability function. Throws {@code MathInternalError} if the cumulative
* probability function returns {@code NaN}.
*
* @param argument input value
* @return the cumulative probability
* @throws MathInternalError if the cumulative probability is {@code NaN}
*/
private double checkedCumulativeProbability(int argument)
throws MathInternalError {
double result = Double.NaN;
result = cumulativeProbability(argument);
if (Double.isNaN(result)) {
throw new MathInternalError(LocalizedFormats
.DISCRETE_CUMULATIVE_PROBABILITY_RETURNED_NAN, argument);
}
return result;
}
/**
* For a random variable {@code X} whose values are distributed according to
* this distribution, this method returns {@code log(P(X = x))}, where
* {@code log} is the natural logarithm. In other words, this method
* represents the logarithm of the probability mass function (PMF) for the
* distribution. Note that due to the floating point precision and
* under/overflow issues, this method will for some distributions be more
* precise and faster than computing the logarithm of
* {@link #probability(int)}.
* <p>
* The default implementation simply computes the logarithm of {@code probability(x)}.</p>
*
* @param x the point at which the PMF is evaluated
* @return the logarithm of the value of the probability mass function at {@code x}
*/
public double logProbability(int x) {
return FastMath.log(probability(x));
}
}

View File

@ -0,0 +1,336 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.distribution;
import java.io.Serializable;
import infodynamics.utils.commonsmath3.analysis.UnivariateFunction;
import infodynamics.utils.commonsmath3.analysis.solvers.UnivariateSolverUtils;
import infodynamics.utils.commonsmath3.exception.NotStrictlyPositiveException;
import infodynamics.utils.commonsmath3.exception.NumberIsTooLargeException;
import infodynamics.utils.commonsmath3.exception.OutOfRangeException;
import infodynamics.utils.commonsmath3.exception.util.LocalizedFormats;
import infodynamics.utils.commonsmath3.random.RandomGenerator;
import infodynamics.utils.commonsmath3.util.FastMath;
/**
* Base class for probability distributions on the reals.
* Default implementations are provided for some of the methods
* that do not vary from distribution to distribution.
*
* @since 3.0
*/
public abstract class AbstractRealDistribution
implements RealDistribution, Serializable {
/** Default accuracy. */
public static final double SOLVER_DEFAULT_ABSOLUTE_ACCURACY = 1e-6;
/** Serializable version identifier */
private static final long serialVersionUID = -38038050983108802L;
/**
* RandomData instance used to generate samples from the distribution.
* @deprecated As of 3.1, to be removed in 4.0. Please use the
* {@link #random} instance variable instead.
*/
@Deprecated
protected infodynamics.utils.commonsmath3.random.RandomDataImpl randomData =
new infodynamics.utils.commonsmath3.random.RandomDataImpl();
/**
* RNG instance used to generate samples from the distribution.
* @since 3.1
*/
protected final RandomGenerator random;
/** Solver absolute accuracy for inverse cumulative computation */
private double solverAbsoluteAccuracy = SOLVER_DEFAULT_ABSOLUTE_ACCURACY;
/**
* @deprecated As of 3.1, to be removed in 4.0. Please use
* {@link #AbstractRealDistribution(RandomGenerator)} instead.
*/
@Deprecated
protected AbstractRealDistribution() {
// Legacy users are only allowed to access the deprecated "randomData".
// New users are forbidden to use this constructor.
random = null;
}
/**
* @param rng Random number generator.
* @since 3.1
*/
protected AbstractRealDistribution(RandomGenerator rng) {
random = rng;
}
/**
* {@inheritDoc}
*
* The default implementation uses the identity
* <p>{@code P(x0 < X <= x1) = P(X <= x1) - P(X <= x0)}</p>
*
* @deprecated As of 3.1 (to be removed in 4.0). Please use
* {@link #probability(double,double)} instead.
*/
@Deprecated
public double cumulativeProbability(double x0, double x1) throws NumberIsTooLargeException {
return probability(x0, x1);
}
/**
* For a random variable {@code X} whose values are distributed according
* to this distribution, this method returns {@code P(x0 < X <= x1)}.
*
* @param x0 Lower bound (excluded).
* @param x1 Upper bound (included).
* @return the probability that a random variable with this distribution
* takes a value between {@code x0} and {@code x1}, excluding the lower
* and including the upper endpoint.
* @throws NumberIsTooLargeException if {@code x0 > x1}.
*
* The default implementation uses the identity
* {@code P(x0 < X <= x1) = P(X <= x1) - P(X <= x0)}
*
* @since 3.1
*/
public double probability(double x0,
double x1) {
if (x0 > x1) {
throw new NumberIsTooLargeException(LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT,
x0, x1, true);
}
return cumulativeProbability(x1) - cumulativeProbability(x0);
}
/**
* {@inheritDoc}
*
* The default implementation returns
* <ul>
* <li>{@link #getSupportLowerBound()} for {@code p = 0},</li>
* <li>{@link #getSupportUpperBound()} for {@code p = 1}.</li>
* </ul>
*/
public double inverseCumulativeProbability(final double p) throws OutOfRangeException {
/*
* IMPLEMENTATION NOTES
* --------------------
* Where applicable, use is made of the one-sided Chebyshev inequality
* to bracket the root. This inequality states that
* P(X - mu >= k * sig) <= 1 / (1 + k^2),
* mu: mean, sig: standard deviation. Equivalently
* 1 - P(X < mu + k * sig) <= 1 / (1 + k^2),
* F(mu + k * sig) >= k^2 / (1 + k^2).
*
* For k = sqrt(p / (1 - p)), we find
* F(mu + k * sig) >= p,
* and (mu + k * sig) is an upper-bound for the root.
*
* Then, introducing Y = -X, mean(Y) = -mu, sd(Y) = sig, and
* P(Y >= -mu + k * sig) <= 1 / (1 + k^2),
* P(-X >= -mu + k * sig) <= 1 / (1 + k^2),
* P(X <= mu - k * sig) <= 1 / (1 + k^2),
* F(mu - k * sig) <= 1 / (1 + k^2).
*
* For k = sqrt((1 - p) / p), we find
* F(mu - k * sig) <= p,
* and (mu - k * sig) is a lower-bound for the root.
*
* In cases where the Chebyshev inequality does not apply, geometric
* progressions 1, 2, 4, ... and -1, -2, -4, ... are used to bracket
* the root.
*/
if (p < 0.0 || p > 1.0) {
throw new OutOfRangeException(p, 0, 1);
}
double lowerBound = getSupportLowerBound();
if (p == 0.0) {
return lowerBound;
}
double upperBound = getSupportUpperBound();
if (p == 1.0) {
return upperBound;
}
final double mu = getNumericalMean();
final double sig = FastMath.sqrt(getNumericalVariance());
final boolean chebyshevApplies;
chebyshevApplies = !(Double.isInfinite(mu) || Double.isNaN(mu) ||
Double.isInfinite(sig) || Double.isNaN(sig));
if (lowerBound == Double.NEGATIVE_INFINITY) {
if (chebyshevApplies) {
lowerBound = mu - sig * FastMath.sqrt((1. - p) / p);
} else {
lowerBound = -1.0;
while (cumulativeProbability(lowerBound) >= p) {
lowerBound *= 2.0;
}
}
}
if (upperBound == Double.POSITIVE_INFINITY) {
if (chebyshevApplies) {
upperBound = mu + sig * FastMath.sqrt(p / (1. - p));
} else {
upperBound = 1.0;
while (cumulativeProbability(upperBound) < p) {
upperBound *= 2.0;
}
}
}
final UnivariateFunction toSolve = new UnivariateFunction() {
/** {@inheritDoc} */
public double value(final double x) {
return cumulativeProbability(x) - p;
}
};
double x = UnivariateSolverUtils.solve(toSolve,
lowerBound,
upperBound,
getSolverAbsoluteAccuracy());
if (!isSupportConnected()) {
/* Test for plateau. */
final double dx = getSolverAbsoluteAccuracy();
if (x - dx >= getSupportLowerBound()) {
double px = cumulativeProbability(x);
if (cumulativeProbability(x - dx) == px) {
upperBound = x;
while (upperBound - lowerBound > dx) {
final double midPoint = 0.5 * (lowerBound + upperBound);
if (cumulativeProbability(midPoint) < px) {
lowerBound = midPoint;
} else {
upperBound = midPoint;
}
}
return upperBound;
}
}
}
return x;
}
/**
* Returns the solver absolute accuracy for inverse cumulative computation.
* You can override this method in order to use a Brent solver with an
* absolute accuracy different from the default.
*
* @return the maximum absolute error in inverse cumulative probability estimates
*/
protected double getSolverAbsoluteAccuracy() {
return solverAbsoluteAccuracy;
}
/** {@inheritDoc} */
public void reseedRandomGenerator(long seed) {
random.setSeed(seed);
randomData.reSeed(seed);
}
/**
* {@inheritDoc}
*
* The default implementation uses the
* <a href="http://en.wikipedia.org/wiki/Inverse_transform_sampling">
* inversion method.
* </a>
*/
public double sample() {
return inverseCumulativeProbability(random.nextDouble());
}
/**
* {@inheritDoc}
*
* The default implementation generates the sample by calling
* {@link #sample()} in a loop.
*/
public double[] sample(int sampleSize) {
if (sampleSize <= 0) {
throw new NotStrictlyPositiveException(LocalizedFormats.NUMBER_OF_SAMPLES,
sampleSize);
}
double[] out = new double[sampleSize];
for (int i = 0; i < sampleSize; i++) {
out[i] = sample();
}
return out;
}
/**
* {@inheritDoc}
*
* @return zero.
* @since 3.1
*/
public double probability(double x) {
return 0d;
}
/**
* Returns the natural logarithm of the probability density function (PDF) of this distribution
* evaluated at the specified point {@code x}. In general, the PDF is the derivative of the
* {@link #cumulativeProbability(double) CDF}. If the derivative does not exist at {@code x},
* then an appropriate replacement should be returned, e.g. {@code Double.POSITIVE_INFINITY},
* {@code Double.NaN}, or the limit inferior or limit superior of the difference quotient. Note
* that due to the floating point precision and under/overflow issues, this method will for some
* distributions be more precise and faster than computing the logarithm of
* {@link #density(double)}. The default implementation simply computes the logarithm of
* {@code density(x)}.
*
* @param x the point at which the PDF is evaluated
* @return the logarithm of the value of the probability density function at point {@code x}
*/
public double logDensity(double x) {
return FastMath.log(density(x));
}
}

View File

@ -0,0 +1,435 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.distribution;
import infodynamics.utils.commonsmath3.exception.NumberIsTooSmallException;
import infodynamics.utils.commonsmath3.exception.util.LocalizedFormats;
import infodynamics.utils.commonsmath3.random.RandomGenerator;
import infodynamics.utils.commonsmath3.random.Well19937c;
import infodynamics.utils.commonsmath3.special.Beta;
import infodynamics.utils.commonsmath3.special.Gamma;
import infodynamics.utils.commonsmath3.util.FastMath;
import infodynamics.utils.commonsmath3.util.Precision;
/**
* Implements the Beta distribution.
*
* @see <a href="http://en.wikipedia.org/wiki/Beta_distribution">Beta distribution</a>
* @since 2.0 (changed to concrete class in 3.0)
*/
public class BetaDistribution extends AbstractRealDistribution {
/**
* Default inverse cumulative probability accuracy.
* @since 2.1
*/
public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
/** Serializable version identifier. */
private static final long serialVersionUID = -1221965979403477668L;
/** First shape parameter. */
private final double alpha;
/** Second shape parameter. */
private final double beta;
/** Normalizing factor used in density computations.
* updated whenever alpha or beta are changed.
*/
private double z;
/** Inverse cumulative probability accuracy. */
private final double solverAbsoluteAccuracy;
/**
* Build a new instance.
* <p>
* <b>Note:</b> this constructor will implicitly create an instance of
* {@link Well19937c} as random generator to be used for sampling only (see
* {@link #sample()} and {@link #sample(int)}). In case no sampling is
* needed for the created distribution, it is advised to pass {@code null}
* as random generator via the appropriate constructors to avoid the
* additional initialisation overhead.
*
* @param alpha First shape parameter (must be positive).
* @param beta Second shape parameter (must be positive).
*/
public BetaDistribution(double alpha, double beta) {
this(alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
}
/**
* Build a new instance.
* <p>
* <b>Note:</b> this constructor will implicitly create an instance of
* {@link Well19937c} as random generator to be used for sampling only (see
* {@link #sample()} and {@link #sample(int)}). In case no sampling is
* needed for the created distribution, it is advised to pass {@code null}
* as random generator via the appropriate constructors to avoid the
* additional initialisation overhead.
*
* @param alpha First shape parameter (must be positive).
* @param beta Second shape parameter (must be positive).
* @param inverseCumAccuracy Maximum absolute error in inverse
* cumulative probability estimates (defaults to
* {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
* @since 2.1
*/
public BetaDistribution(double alpha, double beta, double inverseCumAccuracy) {
this(new Well19937c(), alpha, beta, inverseCumAccuracy);
}
/**
* Creates a &beta; distribution.
*
* @param rng Random number generator.
* @param alpha First shape parameter (must be positive).
* @param beta Second shape parameter (must be positive).
* @since 3.3
*/
public BetaDistribution(RandomGenerator rng, double alpha, double beta) {
this(rng, alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
}
/**
* Creates a &beta; distribution.
*
* @param rng Random number generator.
* @param alpha First shape parameter (must be positive).
* @param beta Second shape parameter (must be positive).
* @param inverseCumAccuracy Maximum absolute error in inverse
* cumulative probability estimates (defaults to
* {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
* @since 3.1
*/
public BetaDistribution(RandomGenerator rng,
double alpha,
double beta,
double inverseCumAccuracy) {
super(rng);
this.alpha = alpha;
this.beta = beta;
z = Double.NaN;
solverAbsoluteAccuracy = inverseCumAccuracy;
}
/**
* Access the first shape parameter, {@code alpha}.
*
* @return the first shape parameter.
*/
public double getAlpha() {
return alpha;
}
/**
* Access the second shape parameter, {@code beta}.
*
* @return the second shape parameter.
*/
public double getBeta() {
return beta;
}
/** Recompute the normalization factor. */
private void recomputeZ() {
if (Double.isNaN(z)) {
z = Gamma.logGamma(alpha) + Gamma.logGamma(beta) - Gamma.logGamma(alpha + beta);
}
}
/** {@inheritDoc} */
public double density(double x) {
final double logDensity = logDensity(x);
return logDensity == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logDensity);
}
/** {@inheritDoc} **/
@Override
public double logDensity(double x) {
recomputeZ();
if (x < 0 || x > 1) {
return Double.NEGATIVE_INFINITY;
} else if (x == 0) {
if (alpha < 1) {
throw new NumberIsTooSmallException(LocalizedFormats.CANNOT_COMPUTE_BETA_DENSITY_AT_0_FOR_SOME_ALPHA, alpha, 1, false);
}
return Double.NEGATIVE_INFINITY;
} else if (x == 1) {
if (beta < 1) {
throw new NumberIsTooSmallException(LocalizedFormats.CANNOT_COMPUTE_BETA_DENSITY_AT_1_FOR_SOME_BETA, beta, 1, false);
}
return Double.NEGATIVE_INFINITY;
} else {
double logX = FastMath.log(x);
double log1mX = FastMath.log1p(-x);
return (alpha - 1) * logX + (beta - 1) * log1mX - z;
}
}
/** {@inheritDoc} */
public double cumulativeProbability(double x) {
if (x <= 0) {
return 0;
} else if (x >= 1) {
return 1;
} else {
return Beta.regularizedBeta(x, alpha, beta);
}
}
/**
* Return the absolute accuracy setting of the solver used to estimate
* inverse cumulative probabilities.
*
* @return the solver absolute accuracy.
* @since 2.1
*/
@Override
protected double getSolverAbsoluteAccuracy() {
return solverAbsoluteAccuracy;
}
/**
* {@inheritDoc}
*
* For first shape parameter {@code alpha} and second shape parameter
* {@code beta}, the mean is {@code alpha / (alpha + beta)}.
*/
public double getNumericalMean() {
final double a = getAlpha();
return a / (a + getBeta());
}
/**
* {@inheritDoc}
*
* For first shape parameter {@code alpha} and second shape parameter
* {@code beta}, the variance is
* {@code (alpha * beta) / [(alpha + beta)^2 * (alpha + beta + 1)]}.
*/
public double getNumericalVariance() {
final double a = getAlpha();
final double b = getBeta();
final double alphabetasum = a + b;
return (a * b) / ((alphabetasum * alphabetasum) * (alphabetasum + 1));
}
/**
* {@inheritDoc}
*
* The lower bound of the support is always 0 no matter the parameters.
*
* @return lower bound of the support (always 0)
*/
public double getSupportLowerBound() {
return 0;
}
/**
* {@inheritDoc}
*
* The upper bound of the support is always 1 no matter the parameters.
*
* @return upper bound of the support (always 1)
*/
public double getSupportUpperBound() {
return 1;
}
/** {@inheritDoc} */
public boolean isSupportLowerBoundInclusive() {
return false;
}
/** {@inheritDoc} */
public boolean isSupportUpperBoundInclusive() {
return false;
}
/**
* {@inheritDoc}
*
* The support of this distribution is connected.
*
* @return {@code true}
*/
public boolean isSupportConnected() {
return true;
}
/** {@inheritDoc}
* <p>
* Sampling is performed using Cheng algorithms:
* </p>
* <p>
* R. C. H. Cheng, "Generating beta variates with nonintegral shape parameters.".
* Communications of the ACM, 21, 317â322, 1978.
* </p>
*/
@Override
public double sample() {
return ChengBetaSampler.sample(random, alpha, beta);
}
/** Utility class implementing Cheng's algorithms for beta distribution sampling.
* <p>
* R. C. H. Cheng, "Generating beta variates with nonintegral shape parameters.".
* Communications of the ACM, 21, 317â322, 1978.
* </p>
* @since 3.6
*/
private static final class ChengBetaSampler {
/**
* Returns one sample using Cheng's sampling algorithm.
* @param random random generator to use
* @param alpha distribution first shape parameter
* @param beta distribution second shape parameter
* @return sampled value
*/
static double sample(RandomGenerator random, final double alpha, final double beta) {
final double a = FastMath.min(alpha, beta);
final double b = FastMath.max(alpha, beta);
if (a > 1) {
return algorithmBB(random, alpha, a, b);
} else {
return algorithmBC(random, alpha, b, a);
}
}
/**
* Returns one sample using Cheng's BB algorithm, when both &alpha; and &beta; are greater than 1.
* @param random random generator to use
* @param a0 distribution first shape parameter (&alpha;)
* @param a min(&alpha;, &beta;) where &alpha;, &beta; are the two distribution shape parameters
* @param b max(&alpha;, &beta;) where &alpha;, &beta; are the two distribution shape parameters
* @return sampled value
*/
private static double algorithmBB(RandomGenerator random,
final double a0,
final double a,
final double b) {
final double alpha = a + b;
final double beta = FastMath.sqrt((alpha - 2.) / (2. * a * b - alpha));
final double gamma = a + 1. / beta;
double r;
double w;
double t;
do {
final double u1 = random.nextDouble();
final double u2 = random.nextDouble();
final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1));
w = a * FastMath.exp(v);
final double z = u1 * u1 * u2;
r = gamma * v - 1.3862944;
final double s = a + r - w;
if (s + 2.609438 >= 5 * z) {
break;
}
t = FastMath.log(z);
if (s >= t) {
break;
}
} while (r + alpha * (FastMath.log(alpha) - FastMath.log(b + w)) < t);
w = FastMath.min(w, Double.MAX_VALUE);
return Precision.equals(a, a0) ? w / (b + w) : b / (b + w);
}
/**
* Returns one sample using Cheng's BC algorithm, when at least one of &alpha; and &beta; is smaller than 1.
* @param random random generator to use
* @param a0 distribution first shape parameter (&alpha;)
* @param a max(&alpha;, &beta;) where &alpha;, &beta; are the two distribution shape parameters
* @param b min(&alpha;, &beta;) where &alpha;, &beta; are the two distribution shape parameters
* @return sampled value
*/
private static double algorithmBC(RandomGenerator random,
final double a0,
final double a,
final double b) {
final double alpha = a + b;
final double beta = 1. / b;
final double delta = 1. + a - b;
final double k1 = delta * (0.0138889 + 0.0416667 * b) / (a * beta - 0.777778);
final double k2 = 0.25 + (0.5 + 0.25 / delta) * b;
double w;
for (;;) {
final double u1 = random.nextDouble();
final double u2 = random.nextDouble();
final double y = u1 * u2;
final double z = u1 * y;
if (u1 < 0.5) {
if (0.25 * u2 + z - y >= k1) {
continue;
}
} else {
if (z <= 0.25) {
final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1));
w = a * FastMath.exp(v);
break;
}
if (z >= k2) {
continue;
}
}
final double v = beta * (FastMath.log(u1) - FastMath.log1p(-u1));
w = a * FastMath.exp(v);
if (alpha * (FastMath.log(alpha) - FastMath.log(b + w) + v) - 1.3862944 >= FastMath.log(z)) {
break;
}
}
w = FastMath.min(w, Double.MAX_VALUE);
return Precision.equals(a, a0) ? w / (b + w) : b / (b + w);
}
}
}

View File

@ -0,0 +1,227 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.distribution;
import infodynamics.utils.commonsmath3.exception.NotPositiveException;
import infodynamics.utils.commonsmath3.exception.OutOfRangeException;
import infodynamics.utils.commonsmath3.exception.util.LocalizedFormats;
import infodynamics.utils.commonsmath3.random.RandomGenerator;
import infodynamics.utils.commonsmath3.random.Well19937c;
import infodynamics.utils.commonsmath3.special.Beta;
import infodynamics.utils.commonsmath3.util.FastMath;
/**
* Implementation of the binomial distribution.
*
* @see <a href="http://en.wikipedia.org/wiki/Binomial_distribution">Binomial distribution (Wikipedia)</a>
* @see <a href="http://mathworld.wolfram.com/BinomialDistribution.html">Binomial Distribution (MathWorld)</a>
*/
public class BinomialDistribution extends AbstractIntegerDistribution {
/** Serializable version identifier. */
private static final long serialVersionUID = 6751309484392813623L;
/** The number of trials. */
private final int numberOfTrials;
/** The probability of success. */
private final double probabilityOfSuccess;
/**
* Create a binomial distribution with the given number of trials and
* probability of success.
* <p>
* <b>Note:</b> this constructor will implicitly create an instance of
* {@link Well19937c} as random generator to be used for sampling only (see
* {@link #sample()} and {@link #sample(int)}). In case no sampling is
* needed for the created distribution, it is advised to pass {@code null}
* as random generator via the appropriate constructors to avoid the
* additional initialisation overhead.
*
* @param trials Number of trials.
* @param p Probability of success.
* @throws NotPositiveException if {@code trials < 0}.
* @throws OutOfRangeException if {@code p < 0} or {@code p > 1}.
*/
public BinomialDistribution(int trials, double p) {
this(new Well19937c(), trials, p);
}
/**
* Creates a binomial distribution.
*
* @param rng Random number generator.
* @param trials Number of trials.
* @param p Probability of success.
* @throws NotPositiveException if {@code trials < 0}.
* @throws OutOfRangeException if {@code p < 0} or {@code p > 1}.
* @since 3.1
*/
public BinomialDistribution(RandomGenerator rng,
int trials,
double p) {
super(rng);
if (trials < 0) {
throw new NotPositiveException(LocalizedFormats.NUMBER_OF_TRIALS,
trials);
}
if (p < 0 || p > 1) {
throw new OutOfRangeException(p, 0, 1);
}
probabilityOfSuccess = p;
numberOfTrials = trials;
}
/**
* Access the number of trials for this distribution.
*
* @return the number of trials.
*/
public int getNumberOfTrials() {
return numberOfTrials;
}
/**
* Access the probability of success for this distribution.
*
* @return the probability of success.
*/
public double getProbabilityOfSuccess() {
return probabilityOfSuccess;
}
/** {@inheritDoc} */
public double probability(int x) {
final double logProbability = logProbability(x);
return logProbability == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logProbability);
}
/** {@inheritDoc} **/
@Override
public double logProbability(int x) {
if (numberOfTrials == 0) {
return (x == 0) ? 0. : Double.NEGATIVE_INFINITY;
}
double ret;
if (x < 0 || x > numberOfTrials) {
ret = Double.NEGATIVE_INFINITY;
} else {
ret = SaddlePointExpansion.logBinomialProbability(x,
numberOfTrials, probabilityOfSuccess,
1.0 - probabilityOfSuccess);
}
return ret;
}
/** {@inheritDoc} */
public double cumulativeProbability(int x) {
double ret;
if (x < 0) {
ret = 0.0;
} else if (x >= numberOfTrials) {
ret = 1.0;
} else {
ret = 1.0 - Beta.regularizedBeta(probabilityOfSuccess,
x + 1.0, numberOfTrials - x);
}
return ret;
}
/**
* {@inheritDoc}
*
* For {@code n} trials and probability parameter {@code p}, the mean is
* {@code n * p}.
*/
public double getNumericalMean() {
return numberOfTrials * probabilityOfSuccess;
}
/**
* {@inheritDoc}
*
* For {@code n} trials and probability parameter {@code p}, the variance is
* {@code n * p * (1 - p)}.
*/
public double getNumericalVariance() {
final double p = probabilityOfSuccess;
return numberOfTrials * p * (1 - p);
}
/**
* {@inheritDoc}
*
* The lower bound of the support is always 0 except for the probability
* parameter {@code p = 1}.
*
* @return lower bound of the support (0 or the number of trials)
*/
public int getSupportLowerBound() {
return probabilityOfSuccess < 1.0 ? 0 : numberOfTrials;
}
/**
* {@inheritDoc}
*
* The upper bound of the support is the number of trials except for the
* probability parameter {@code p = 0}.
*
* @return upper bound of the support (number of trials or 0)
*/
public int getSupportUpperBound() {
return probabilityOfSuccess > 0.0 ? numberOfTrials : 0;
}
/**
* {@inheritDoc}
*
* The support of this distribution is connected.
*
* @return {@code true}
*/
public boolean isSupportConnected() {
return true;
}
}

View File

@ -0,0 +1,285 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.distribution;
import infodynamics.utils.commonsmath3.exception.NotStrictlyPositiveException;
import infodynamics.utils.commonsmath3.exception.OutOfRangeException;
import infodynamics.utils.commonsmath3.exception.util.LocalizedFormats;
import infodynamics.utils.commonsmath3.random.RandomGenerator;
import infodynamics.utils.commonsmath3.random.Well19937c;
import infodynamics.utils.commonsmath3.util.FastMath;
/**
* Implementation of the Cauchy distribution.
*
* @see <a href="http://en.wikipedia.org/wiki/Cauchy_distribution">Cauchy distribution (Wikipedia)</a>
* @see <a href="http://mathworld.wolfram.com/CauchyDistribution.html">Cauchy Distribution (MathWorld)</a>
* @since 1.1 (changed to concrete class in 3.0)
*/
public class CauchyDistribution extends AbstractRealDistribution {
/**
* Default inverse cumulative probability accuracy.
* @since 2.1
*/
public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
/** Serializable version identifier */
private static final long serialVersionUID = 8589540077390120676L;
/** The median of this distribution. */
private final double median;
/** The scale of this distribution. */
private final double scale;
/** Inverse cumulative probability accuracy */
private final double solverAbsoluteAccuracy;
/**
* Creates a Cauchy distribution with the median equal to zero and scale
* equal to one.
*/
public CauchyDistribution() {
this(0, 1);
}
/**
* Creates a Cauchy distribution using the given median and scale.
* <p>
* <b>Note:</b> this constructor will implicitly create an instance of
* {@link Well19937c} as random generator to be used for sampling only (see
* {@link #sample()} and {@link #sample(int)}). In case no sampling is
* needed for the created distribution, it is advised to pass {@code null}
* as random generator via the appropriate constructors to avoid the
* additional initialisation overhead.
*
* @param median Median for this distribution.
* @param scale Scale parameter for this distribution.
*/
public CauchyDistribution(double median, double scale) {
this(median, scale, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
}
/**
* Creates a Cauchy distribution using the given median and scale.
* <p>
* <b>Note:</b> this constructor will implicitly create an instance of
* {@link Well19937c} as random generator to be used for sampling only (see
* {@link #sample()} and {@link #sample(int)}). In case no sampling is
* needed for the created distribution, it is advised to pass {@code null}
* as random generator via the appropriate constructors to avoid the
* additional initialisation overhead.
*
* @param median Median for this distribution.
* @param scale Scale parameter for this distribution.
* @param inverseCumAccuracy Maximum absolute error in inverse
* cumulative probability estimates
* (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
* @throws NotStrictlyPositiveException if {@code scale <= 0}.
* @since 2.1
*/
public CauchyDistribution(double median, double scale,
double inverseCumAccuracy) {
this(new Well19937c(), median, scale, inverseCumAccuracy);
}
/**
* Creates a Cauchy distribution.
*
* @param rng Random number generator.
* @param median Median for this distribution.
* @param scale Scale parameter for this distribution.
* @throws NotStrictlyPositiveException if {@code scale <= 0}.
* @since 3.3
*/
public CauchyDistribution(RandomGenerator rng, double median, double scale) {
this(rng, median, scale, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
}
/**
* Creates a Cauchy distribution.
*
* @param rng Random number generator.
* @param median Median for this distribution.
* @param scale Scale parameter for this distribution.
* @param inverseCumAccuracy Maximum absolute error in inverse
* cumulative probability estimates
* (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
* @throws NotStrictlyPositiveException if {@code scale <= 0}.
* @since 3.1
*/
public CauchyDistribution(RandomGenerator rng,
double median,
double scale,
double inverseCumAccuracy) {
super(rng);
if (scale <= 0) {
throw new NotStrictlyPositiveException(LocalizedFormats.SCALE, scale);
}
this.scale = scale;
this.median = median;
solverAbsoluteAccuracy = inverseCumAccuracy;
}
/** {@inheritDoc} */
public double cumulativeProbability(double x) {
return 0.5 + (FastMath.atan((x - median) / scale) / FastMath.PI);
}
/**
* Access the median.
*
* @return the median for this distribution.
*/
public double getMedian() {
return median;
}
/**
* Access the scale parameter.
*
* @return the scale parameter for this distribution.
*/
public double getScale() {
return scale;
}
/** {@inheritDoc} */
public double density(double x) {
final double dev = x - median;
return (1 / FastMath.PI) * (scale / (dev * dev + scale * scale));
}
/**
* {@inheritDoc}
*
* Returns {@code Double.NEGATIVE_INFINITY} when {@code p == 0}
* and {@code Double.POSITIVE_INFINITY} when {@code p == 1}.
*/
@Override
public double inverseCumulativeProbability(double p) throws OutOfRangeException {
double ret;
if (p < 0 || p > 1) {
throw new OutOfRangeException(p, 0, 1);
} else if (p == 0) {
ret = Double.NEGATIVE_INFINITY;
} else if (p == 1) {
ret = Double.POSITIVE_INFINITY;
} else {
ret = median + scale * FastMath.tan(FastMath.PI * (p - .5));
}
return ret;
}
/** {@inheritDoc} */
@Override
protected double getSolverAbsoluteAccuracy() {
return solverAbsoluteAccuracy;
}
/**
* {@inheritDoc}
*
* The mean is always undefined no matter the parameters.
*
* @return mean (always Double.NaN)
*/
public double getNumericalMean() {
return Double.NaN;
}
/**
* {@inheritDoc}
*
* The variance is always undefined no matter the parameters.
*
* @return variance (always Double.NaN)
*/
public double getNumericalVariance() {
return Double.NaN;
}
/**
* {@inheritDoc}
*
* The lower bound of the support is always negative infinity no matter
* the parameters.
*
* @return lower bound of the support (always Double.NEGATIVE_INFINITY)
*/
public double getSupportLowerBound() {
return Double.NEGATIVE_INFINITY;
}
/**
* {@inheritDoc}
*
* The upper bound of the support is always positive infinity no matter
* the parameters.
*
* @return upper bound of the support (always Double.POSITIVE_INFINITY)
*/
public double getSupportUpperBound() {
return Double.POSITIVE_INFINITY;
}
/** {@inheritDoc} */
public boolean isSupportLowerBoundInclusive() {
return false;
}
/** {@inheritDoc} */
public boolean isSupportUpperBoundInclusive() {
return false;
}
/**
* {@inheritDoc}
*
* The support of this distribution is connected.
*
* @return {@code true}
*/
public boolean isSupportConnected() {
return true;
}
}

View File

@ -0,0 +1,225 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.distribution;
import infodynamics.utils.commonsmath3.random.RandomGenerator;
import infodynamics.utils.commonsmath3.random.Well19937c;
/**
* Implementation of the chi-squared distribution.
*
* @see <a href="http://en.wikipedia.org/wiki/Chi-squared_distribution">Chi-squared distribution (Wikipedia)</a>
* @see <a href="http://mathworld.wolfram.com/Chi-SquaredDistribution.html">Chi-squared Distribution (MathWorld)</a>
*/
public class ChiSquaredDistribution extends AbstractRealDistribution {
/**
* Default inverse cumulative probability accuracy
* @since 2.1
*/
public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
/** Serializable version identifier */
private static final long serialVersionUID = -8352658048349159782L;
/** Internal Gamma distribution. */
private final GammaDistribution gamma;
/** Inverse cumulative probability accuracy */
private final double solverAbsoluteAccuracy;
/**
* Create a Chi-Squared distribution with the given degrees of freedom.
*
* @param degreesOfFreedom Degrees of freedom.
*/
public ChiSquaredDistribution(double degreesOfFreedom) {
this(degreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
}
/**
* Create a Chi-Squared distribution with the given degrees of freedom and
* inverse cumulative probability accuracy.
* <p>
* <b>Note:</b> this constructor will implicitly create an instance of
* {@link Well19937c} as random generator to be used for sampling only (see
* {@link #sample()} and {@link #sample(int)}). In case no sampling is
* needed for the created distribution, it is advised to pass {@code null}
* as random generator via the appropriate constructors to avoid the
* additional initialisation overhead.
*
* @param degreesOfFreedom Degrees of freedom.
* @param inverseCumAccuracy the maximum absolute error in inverse
* cumulative probability estimates (defaults to
* {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
* @since 2.1
*/
public ChiSquaredDistribution(double degreesOfFreedom,
double inverseCumAccuracy) {
this(new Well19937c(), degreesOfFreedom, inverseCumAccuracy);
}
/**
* Create a Chi-Squared distribution with the given degrees of freedom.
*
* @param rng Random number generator.
* @param degreesOfFreedom Degrees of freedom.
* @since 3.3
*/
public ChiSquaredDistribution(RandomGenerator rng, double degreesOfFreedom) {
this(rng, degreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
}
/**
* Create a Chi-Squared distribution with the given degrees of freedom and
* inverse cumulative probability accuracy.
*
* @param rng Random number generator.
* @param degreesOfFreedom Degrees of freedom.
* @param inverseCumAccuracy the maximum absolute error in inverse
* cumulative probability estimates (defaults to
* {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
* @since 3.1
*/
public ChiSquaredDistribution(RandomGenerator rng,
double degreesOfFreedom,
double inverseCumAccuracy) {
super(rng);
gamma = new GammaDistribution(degreesOfFreedom / 2, 2);
solverAbsoluteAccuracy = inverseCumAccuracy;
}
/**
* Access the number of degrees of freedom.
*
* @return the degrees of freedom.
*/
public double getDegreesOfFreedom() {
return gamma.getShape() * 2.0;
}
/** {@inheritDoc} */
public double density(double x) {
return gamma.density(x);
}
/** {@inheritDoc} **/
@Override
public double logDensity(double x) {
return gamma.logDensity(x);
}
/** {@inheritDoc} */
public double cumulativeProbability(double x) {
return gamma.cumulativeProbability(x);
}
/** {@inheritDoc} */
@Override
protected double getSolverAbsoluteAccuracy() {
return solverAbsoluteAccuracy;
}
/**
* {@inheritDoc}
*
* For {@code k} degrees of freedom, the mean is {@code k}.
*/
public double getNumericalMean() {
return getDegreesOfFreedom();
}
/**
* {@inheritDoc}
*
* @return {@code 2 * k}, where {@code k} is the number of degrees of freedom.
*/
public double getNumericalVariance() {
return 2 * getDegreesOfFreedom();
}
/**
* {@inheritDoc}
*
* The lower bound of the support is always 0 no matter the
* degrees of freedom.
*
* @return zero.
*/
public double getSupportLowerBound() {
return 0;
}
/**
* {@inheritDoc}
*
* The upper bound of the support is always positive infinity no matter the
* degrees of freedom.
*
* @return {@code Double.POSITIVE_INFINITY}.
*/
public double getSupportUpperBound() {
return Double.POSITIVE_INFINITY;
}
/** {@inheritDoc} */
public boolean isSupportLowerBoundInclusive() {
return true;
}
/** {@inheritDoc} */
public boolean isSupportUpperBoundInclusive() {
return false;
}
/**
* {@inheritDoc}
*
* The support of this distribution is connected.
*
* @return {@code true}
*/
public boolean isSupportConnected() {
return true;
}
}

View File

@ -0,0 +1,380 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.distribution;
import infodynamics.utils.commonsmath3.exception.NotStrictlyPositiveException;
import infodynamics.utils.commonsmath3.exception.OutOfRangeException;
import infodynamics.utils.commonsmath3.exception.util.LocalizedFormats;
import infodynamics.utils.commonsmath3.random.RandomGenerator;
import infodynamics.utils.commonsmath3.random.Well19937c;
import infodynamics.utils.commonsmath3.util.CombinatoricsUtils;
import infodynamics.utils.commonsmath3.util.FastMath;
import infodynamics.utils.commonsmath3.util.ResizableDoubleArray;
/**
* Implementation of the exponential distribution.
*
* @see <a href="http://en.wikipedia.org/wiki/Exponential_distribution">Exponential distribution (Wikipedia)</a>
* @see <a href="http://mathworld.wolfram.com/ExponentialDistribution.html">Exponential distribution (MathWorld)</a>
*/
public class ExponentialDistribution extends AbstractRealDistribution {
/**
* Default inverse cumulative probability accuracy.
* @since 2.1
*/
public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
/** Serializable version identifier */
private static final long serialVersionUID = 2401296428283614780L;
/**
* Used when generating Exponential samples.
* Table containing the constants
* q_i = sum_{j=1}^i (ln 2)^j/j! = ln 2 + (ln 2)^2/2 + ... + (ln 2)^i/i!
* until the largest representable fraction below 1 is exceeded.
*
* Note that
* 1 = 2 - 1 = exp(ln 2) - 1 = sum_{n=1}^infty (ln 2)^n / n!
* thus q_i -> 1 as i -> +inf,
* so the higher i, the closer to one we get (the series is not alternating).
*
* By trying, n = 16 in Java is enough to reach 1.0.
*/
private static final double[] EXPONENTIAL_SA_QI;
/** The mean of this distribution. */
private final double mean;
/** The logarithm of the mean, stored to reduce computing time. **/
private final double logMean;
/** Inverse cumulative probability accuracy. */
private final double solverAbsoluteAccuracy;
/**
* Initialize tables.
*/
static {
/**
* Filling EXPONENTIAL_SA_QI table.
* Note that we don't want qi = 0 in the table.
*/
final double LN2 = FastMath.log(2);
double qi = 0;
int i = 1;
/**
* ArithmeticUtils provides factorials up to 20, so let's use that
* limit together with Precision.EPSILON to generate the following
* code (a priori, we know that there will be 16 elements, but it is
* better to not hardcode it).
*/
final ResizableDoubleArray ra = new ResizableDoubleArray(20);
while (qi < 1) {
qi += FastMath.pow(LN2, i) / CombinatoricsUtils.factorial(i);
ra.addElement(qi);
++i;
}
EXPONENTIAL_SA_QI = ra.getElements();
}
/**
* Create an exponential distribution with the given mean.
* <p>
* <b>Note:</b> this constructor will implicitly create an instance of
* {@link Well19937c} as random generator to be used for sampling only (see
* {@link #sample()} and {@link #sample(int)}). In case no sampling is
* needed for the created distribution, it is advised to pass {@code null}
* as random generator via the appropriate constructors to avoid the
* additional initialisation overhead.
*
* @param mean mean of this distribution.
*/
public ExponentialDistribution(double mean) {
this(mean, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
}
/**
* Create an exponential distribution with the given mean.
* <p>
* <b>Note:</b> this constructor will implicitly create an instance of
* {@link Well19937c} as random generator to be used for sampling only (see
* {@link #sample()} and {@link #sample(int)}). In case no sampling is
* needed for the created distribution, it is advised to pass {@code null}
* as random generator via the appropriate constructors to avoid the
* additional initialisation overhead.
*
* @param mean Mean of this distribution.
* @param inverseCumAccuracy Maximum absolute error in inverse
* cumulative probability estimates (defaults to
* {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
* @throws NotStrictlyPositiveException if {@code mean <= 0}.
* @since 2.1
*/
public ExponentialDistribution(double mean, double inverseCumAccuracy) {
this(new Well19937c(), mean, inverseCumAccuracy);
}
/**
* Creates an exponential distribution.
*
* @param rng Random number generator.
* @param mean Mean of this distribution.
* @throws NotStrictlyPositiveException if {@code mean <= 0}.
* @since 3.3
*/
public ExponentialDistribution(RandomGenerator rng, double mean)
throws NotStrictlyPositiveException {
this(rng, mean, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
}
/**
* Creates an exponential distribution.
*
* @param rng Random number generator.
* @param mean Mean of this distribution.
* @param inverseCumAccuracy Maximum absolute error in inverse
* cumulative probability estimates (defaults to
* {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
* @throws NotStrictlyPositiveException if {@code mean <= 0}.
* @since 3.1
*/
public ExponentialDistribution(RandomGenerator rng,
double mean,
double inverseCumAccuracy)
throws NotStrictlyPositiveException {
super(rng);
if (mean <= 0) {
throw new NotStrictlyPositiveException(LocalizedFormats.MEAN, mean);
}
this.mean = mean;
logMean = FastMath.log(mean);
solverAbsoluteAccuracy = inverseCumAccuracy;
}
/**
* Access the mean.
*
* @return the mean.
*/
public double getMean() {
return mean;
}
/** {@inheritDoc} */
public double density(double x) {
final double logDensity = logDensity(x);
return logDensity == Double.NEGATIVE_INFINITY ? 0 : FastMath.exp(logDensity);
}
/** {@inheritDoc} **/
@Override
public double logDensity(double x) {
if (x < 0) {
return Double.NEGATIVE_INFINITY;
}
return -x / mean - logMean;
}
/**
* {@inheritDoc}
*
* The implementation of this method is based on:
* <ul>
* <li>
* <a href="http://mathworld.wolfram.com/ExponentialDistribution.html">
* Exponential Distribution</a>, equation (1).</li>
* </ul>
*/
public double cumulativeProbability(double x) {
double ret;
if (x <= 0.0) {
ret = 0.0;
} else {
ret = 1.0 - FastMath.exp(-x / mean);
}
return ret;
}
/**
* {@inheritDoc}
*
* Returns {@code 0} when {@code p= = 0} and
* {@code Double.POSITIVE_INFINITY} when {@code p == 1}.
*/
@Override
public double inverseCumulativeProbability(double p) throws OutOfRangeException {
double ret;
if (p < 0.0 || p > 1.0) {
throw new OutOfRangeException(p, 0.0, 1.0);
} else if (p == 1.0) {
ret = Double.POSITIVE_INFINITY;
} else {
ret = -mean * FastMath.log(1.0 - p);
}
return ret;
}
/**
* {@inheritDoc}
*
* <p><strong>Algorithm Description</strong>: this implementation uses the
* <a href="http://www.jesus.ox.ac.uk/~clifford/a5/chap1/node5.html">
* Inversion Method</a> to generate exponentially distributed random values
* from uniform deviates.</p>
*
* @return a random value.
* @since 2.2
*/
@Override
public double sample() {
// Step 1:
double a = 0;
double u = random.nextDouble();
// Step 2 and 3:
while (u < 0.5) {
a += EXPONENTIAL_SA_QI[0];
u *= 2;
}
// Step 4 (now u >= 0.5):
u += u - 1;
// Step 5:
if (u <= EXPONENTIAL_SA_QI[0]) {
return mean * (a + u);
}
// Step 6:
int i = 0; // Should be 1, be we iterate before it in while using 0
double u2 = random.nextDouble();
double umin = u2;
// Step 7 and 8:
do {
++i;
u2 = random.nextDouble();
if (u2 < umin) {
umin = u2;
}
// Step 8:
} while (u > EXPONENTIAL_SA_QI[i]); // Ensured to exit since EXPONENTIAL_SA_QI[MAX] = 1
return mean * (a + umin * EXPONENTIAL_SA_QI[0]);
}
/** {@inheritDoc} */
@Override
protected double getSolverAbsoluteAccuracy() {
return solverAbsoluteAccuracy;
}
/**
* {@inheritDoc}
*
* For mean parameter {@code k}, the mean is {@code k}.
*/
public double getNumericalMean() {
return getMean();
}
/**
* {@inheritDoc}
*
* For mean parameter {@code k}, the variance is {@code k^2}.
*/
public double getNumericalVariance() {
final double m = getMean();
return m * m;
}
/**
* {@inheritDoc}
*
* The lower bound of the support is always 0 no matter the mean parameter.
*
* @return lower bound of the support (always 0)
*/
public double getSupportLowerBound() {
return 0;
}
/**
* {@inheritDoc}
*
* The upper bound of the support is always positive infinity
* no matter the mean parameter.
*
* @return upper bound of the support (always Double.POSITIVE_INFINITY)
*/
public double getSupportUpperBound() {
return Double.POSITIVE_INFINITY;
}
/** {@inheritDoc} */
public boolean isSupportLowerBoundInclusive() {
return true;
}
/** {@inheritDoc} */
public boolean isSupportUpperBoundInclusive() {
return false;
}
/**
* {@inheritDoc}
*
* The support of this distribution is connected.
*
* @return {@code true}
*/
public boolean isSupportConnected() {
return true;
}
}

View File

@ -0,0 +1,357 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.distribution;
import infodynamics.utils.commonsmath3.exception.NotStrictlyPositiveException;
import infodynamics.utils.commonsmath3.exception.util.LocalizedFormats;
import infodynamics.utils.commonsmath3.random.RandomGenerator;
import infodynamics.utils.commonsmath3.random.Well19937c;
import infodynamics.utils.commonsmath3.special.Beta;
import infodynamics.utils.commonsmath3.util.FastMath;
/**
* Implementation of the F-distribution.
*
* @see <a href="http://en.wikipedia.org/wiki/F-distribution">F-distribution (Wikipedia)</a>
* @see <a href="http://mathworld.wolfram.com/F-Distribution.html">F-distribution (MathWorld)</a>
*/
public class FDistribution extends AbstractRealDistribution {
/**
* Default inverse cumulative probability accuracy.
* @since 2.1
*/
public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
/** Serializable version identifier. */
private static final long serialVersionUID = -8516354193418641566L;
/** The numerator degrees of freedom. */
private final double numeratorDegreesOfFreedom;
/** The numerator degrees of freedom. */
private final double denominatorDegreesOfFreedom;
/** Inverse cumulative probability accuracy. */
private final double solverAbsoluteAccuracy;
/** Cached numerical variance */
private double numericalVariance = Double.NaN;
/** Whether or not the numerical variance has been calculated */
private boolean numericalVarianceIsCalculated = false;
/**
* Creates an F distribution using the given degrees of freedom.
* <p>
* <b>Note:</b> this constructor will implicitly create an instance of
* {@link Well19937c} as random generator to be used for sampling only (see
* {@link #sample()} and {@link #sample(int)}). In case no sampling is
* needed for the created distribution, it is advised to pass {@code null}
* as random generator via the appropriate constructors to avoid the
* additional initialisation overhead.
*
* @param numeratorDegreesOfFreedom Numerator degrees of freedom.
* @param denominatorDegreesOfFreedom Denominator degrees of freedom.
* @throws NotStrictlyPositiveException if
* {@code numeratorDegreesOfFreedom <= 0} or
* {@code denominatorDegreesOfFreedom <= 0}.
*/
public FDistribution(double numeratorDegreesOfFreedom,
double denominatorDegreesOfFreedom)
throws NotStrictlyPositiveException {
this(numeratorDegreesOfFreedom, denominatorDegreesOfFreedom,
DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
}
/**
* Creates an F distribution using the given degrees of freedom
* and inverse cumulative probability accuracy.
* <p>
* <b>Note:</b> this constructor will implicitly create an instance of
* {@link Well19937c} as random generator to be used for sampling only (see
* {@link #sample()} and {@link #sample(int)}). In case no sampling is
* needed for the created distribution, it is advised to pass {@code null}
* as random generator via the appropriate constructors to avoid the
* additional initialisation overhead.
*
* @param numeratorDegreesOfFreedom Numerator degrees of freedom.
* @param denominatorDegreesOfFreedom Denominator degrees of freedom.
* @param inverseCumAccuracy the maximum absolute error in inverse
* cumulative probability estimates.
* @throws NotStrictlyPositiveException if
* {@code numeratorDegreesOfFreedom <= 0} or
* {@code denominatorDegreesOfFreedom <= 0}.
* @since 2.1
*/
public FDistribution(double numeratorDegreesOfFreedom,
double denominatorDegreesOfFreedom,
double inverseCumAccuracy)
throws NotStrictlyPositiveException {
this(new Well19937c(), numeratorDegreesOfFreedom,
denominatorDegreesOfFreedom, inverseCumAccuracy);
}
/**
* Creates an F distribution.
*
* @param rng Random number generator.
* @param numeratorDegreesOfFreedom Numerator degrees of freedom.
* @param denominatorDegreesOfFreedom Denominator degrees of freedom.
* @throws NotStrictlyPositiveException if {@code numeratorDegreesOfFreedom <= 0} or
* {@code denominatorDegreesOfFreedom <= 0}.
* @since 3.3
*/
public FDistribution(RandomGenerator rng,
double numeratorDegreesOfFreedom,
double denominatorDegreesOfFreedom)
throws NotStrictlyPositiveException {
this(rng, numeratorDegreesOfFreedom, denominatorDegreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
}
/**
* Creates an F distribution.
*
* @param rng Random number generator.
* @param numeratorDegreesOfFreedom Numerator degrees of freedom.
* @param denominatorDegreesOfFreedom Denominator degrees of freedom.
* @param inverseCumAccuracy the maximum absolute error in inverse
* cumulative probability estimates.
* @throws NotStrictlyPositiveException if {@code numeratorDegreesOfFreedom <= 0} or
* {@code denominatorDegreesOfFreedom <= 0}.
* @since 3.1
*/
public FDistribution(RandomGenerator rng,
double numeratorDegreesOfFreedom,
double denominatorDegreesOfFreedom,
double inverseCumAccuracy)
throws NotStrictlyPositiveException {
super(rng);
if (numeratorDegreesOfFreedom <= 0) {
throw new NotStrictlyPositiveException(LocalizedFormats.DEGREES_OF_FREEDOM,
numeratorDegreesOfFreedom);
}
if (denominatorDegreesOfFreedom <= 0) {
throw new NotStrictlyPositiveException(LocalizedFormats.DEGREES_OF_FREEDOM,
denominatorDegreesOfFreedom);
}
this.numeratorDegreesOfFreedom = numeratorDegreesOfFreedom;
this.denominatorDegreesOfFreedom = denominatorDegreesOfFreedom;
solverAbsoluteAccuracy = inverseCumAccuracy;
}
/**
* {@inheritDoc}
*
* @since 2.1
*/
public double density(double x) {
return FastMath.exp(logDensity(x));
}
/** {@inheritDoc} **/
@Override
public double logDensity(double x) {
final double nhalf = numeratorDegreesOfFreedom / 2;
final double mhalf = denominatorDegreesOfFreedom / 2;
final double logx = FastMath.log(x);
final double logn = FastMath.log(numeratorDegreesOfFreedom);
final double logm = FastMath.log(denominatorDegreesOfFreedom);
final double lognxm = FastMath.log(numeratorDegreesOfFreedom * x +
denominatorDegreesOfFreedom);
return nhalf * logn + nhalf * logx - logx +
mhalf * logm - nhalf * lognxm - mhalf * lognxm -
Beta.logBeta(nhalf, mhalf);
}
/**
* {@inheritDoc}
*
* The implementation of this method is based on
* <ul>
* <li>
* <a href="http://mathworld.wolfram.com/F-Distribution.html">
* F-Distribution</a>, equation (4).
* </li>
* </ul>
*/
public double cumulativeProbability(double x) {
double ret;
if (x <= 0) {
ret = 0;
} else {
double n = numeratorDegreesOfFreedom;
double m = denominatorDegreesOfFreedom;
ret = Beta.regularizedBeta((n * x) / (m + n * x),
0.5 * n,
0.5 * m);
}
return ret;
}
/**
* Access the numerator degrees of freedom.
*
* @return the numerator degrees of freedom.
*/
public double getNumeratorDegreesOfFreedom() {
return numeratorDegreesOfFreedom;
}
/**
* Access the denominator degrees of freedom.
*
* @return the denominator degrees of freedom.
*/
public double getDenominatorDegreesOfFreedom() {
return denominatorDegreesOfFreedom;
}
/** {@inheritDoc} */
@Override
protected double getSolverAbsoluteAccuracy() {
return solverAbsoluteAccuracy;
}
/**
* {@inheritDoc}
*
* For denominator degrees of freedom parameter {@code b}, the mean is
* <ul>
* <li>if {@code b > 2} then {@code b / (b - 2)},</li>
* <li>else undefined ({@code Double.NaN}).
* </ul>
*/
public double getNumericalMean() {
final double denominatorDF = getDenominatorDegreesOfFreedom();
if (denominatorDF > 2) {
return denominatorDF / (denominatorDF - 2);
}
return Double.NaN;
}
/**
* {@inheritDoc}
*
* For numerator degrees of freedom parameter {@code a} and denominator
* degrees of freedom parameter {@code b}, the variance is
* <ul>
* <li>
* if {@code b > 4} then
* {@code [2 * b^2 * (a + b - 2)] / [a * (b - 2)^2 * (b - 4)]},
* </li>
* <li>else undefined ({@code Double.NaN}).
* </ul>
*/
public double getNumericalVariance() {
if (!numericalVarianceIsCalculated) {
numericalVariance = calculateNumericalVariance();
numericalVarianceIsCalculated = true;
}
return numericalVariance;
}
/**
* used by {@link #getNumericalVariance()}
*
* @return the variance of this distribution
*/
protected double calculateNumericalVariance() {
final double denominatorDF = getDenominatorDegreesOfFreedom();
if (denominatorDF > 4) {
final double numeratorDF = getNumeratorDegreesOfFreedom();
final double denomDFMinusTwo = denominatorDF - 2;
return ( 2 * (denominatorDF * denominatorDF) * (numeratorDF + denominatorDF - 2) ) /
( (numeratorDF * (denomDFMinusTwo * denomDFMinusTwo) * (denominatorDF - 4)) );
}
return Double.NaN;
}
/**
* {@inheritDoc}
*
* The lower bound of the support is always 0 no matter the parameters.
*
* @return lower bound of the support (always 0)
*/
public double getSupportLowerBound() {
return 0;
}
/**
* {@inheritDoc}
*
* The upper bound of the support is always positive infinity
* no matter the parameters.
*
* @return upper bound of the support (always Double.POSITIVE_INFINITY)
*/
public double getSupportUpperBound() {
return Double.POSITIVE_INFINITY;
}
/** {@inheritDoc} */
public boolean isSupportLowerBoundInclusive() {
return false;
}
/** {@inheritDoc} */
public boolean isSupportUpperBoundInclusive() {
return false;
}
/**
* {@inheritDoc}
*
* The support of this distribution is connected.
*
* @return {@code true}
*/
public boolean isSupportConnected() {
return true;
}
}

View File

@ -0,0 +1,542 @@
/*
* Java Information Dynamics Toolkit (JIDT)
* Copyright (C) 2017, 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/>.
*/
/*
* This class was originally distributed as part of the Apache Commons
* Math3 library (3.6.1), under the Apache License Version 2.0, which is
* copied below. This Apache 2 software is now included as a derivative
* work in the GPLv3 licensed JIDT project, as per:
* http://www.apache.org/licenses/GPL-compatibility.html
*
* The original Apache source code has been modified as follows:
* -- We have modified package names to sit inside the JIDT structure.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package infodynamics.utils.commonsmath3.distribution;
import infodynamics.utils.commonsmath3.exception.NotStrictlyPositiveException;
import infodynamics.utils.commonsmath3.exception.util.LocalizedFormats;
import infodynamics.utils.commonsmath3.random.RandomGenerator;
import infodynamics.utils.commonsmath3.random.Well19937c;
import infodynamics.utils.commonsmath3.special.Gamma;
import infodynamics.utils.commonsmath3.util.FastMath;
/**
* Implementation of the Gamma distribution.
*
* @see <a href="http://en.wikipedia.org/wiki/Gamma_distribution">Gamma distribution (Wikipedia)</a>
* @see <a href="http://mathworld.wolfram.com/GammaDistribution.html">Gamma distribution (MathWorld)</a>
*/
public class GammaDistribution extends AbstractRealDistribution {
/**
* Default inverse cumulative probability accuracy.
* @since 2.1
*/
public static final double DEFAULT_INVERSE_ABSOLUTE_ACCURACY = 1e-9;
/** Serializable version identifier. */
private static final long serialVersionUID = 20120524L;
/** The shape parameter. */
private final double shape;
/** The scale parameter. */
private final double scale;
/**
* The constant value of {@code shape + g + 0.5}, where {@code g} is the
* Lanczos constant {@link Gamma#LANCZOS_G}.
*/
private final double shiftedShape;
/**
* The constant value of
* {@code shape / scale * sqrt(e / (2 * pi * (shape + g + 0.5))) / L(shape)},
* where {@code L(shape)} is the Lanczos approximation returned by
* {@link Gamma#lanczos(double)}. This prefactor is used in
* {@link #density(double)}, when no overflow occurs with the natural
* calculation.
*/
private final double densityPrefactor1;
/**
* The constant value of
* {@code log(shape / scale * sqrt(e / (2 * pi * (shape + g + 0.5))) / L(shape))},
* where {@code L(shape)} is the Lanczos approximation returned by
* {@link Gamma#lanczos(double)}. This prefactor is used in
* {@link #logDensity(double)}, when no overflow occurs with the natural
* calculation.
*/
private final double logDensityPrefactor1;
/**
* The constant value of
* {@code shape * sqrt(e / (2 * pi * (shape + g + 0.5))) / L(shape)},
* where {@code L(shape)} is the Lanczos approximation returned by
* {@link Gamma#lanczos(double)}. This prefactor is used in
* {@link #density(double)}, when overflow occurs with the natural
* calculation.
*/
private final double densityPrefactor2;
/**
* The constant value of
* {@code log(shape * sqrt(e / (2 * pi * (shape + g + 0.5))) / L(shape))},
* where {@code L(shape)} is the Lanczos approximation returned by
* {@link Gamma#lanczos(double)}. This prefactor is used in
* {@link #logDensity(double)}, when overflow occurs with the natural
* calculation.
*/
private final double logDensityPrefactor2;
/**
* Lower bound on {@code y = x / scale} for the selection of the computation
* method in {@link #density(double)}. For {@code y <= minY}, the natural
* calculation overflows.
*/
private final double minY;
/**
* Upper bound on {@code log(y)} ({@code y = x / scale}) for the selection
* of the computation method in {@link #density(double)}. For
* {@code log(y) >= maxLogY}, the natural calculation overflows.
*/
private final double maxLogY;
/** Inverse cumulative probability accuracy. */
private final double solverAbsoluteAccuracy;
/**
* Creates a new gamma distribution with specified values of the shape and
* scale parameters.
* <p>
* <b>Note:</b> this constructor will implicitly create an instance of
* {@link Well19937c} as random generator to be used for sampling only (see
* {@link #sample()} and {@link #sample(int)}). In case no sampling is
* needed for the created distribution, it is advised to pass {@code null}
* as random generator via the appropriate constructors to avoid the
* additional initialisation overhead.
*
* @param shape the shape parameter
* @param scale the scale parameter
* @throws NotStrictlyPositiveException if {@code shape <= 0} or
* {@code scale <= 0}.
*/
public GammaDistribution(double shape, double scale) throws NotStrictlyPositiveException {
this(shape, scale, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
}
/**
* Creates a new gamma distribution with specified values of the shape and
* scale parameters.
* <p>
* <b>Note:</b> this constructor will implicitly create an instance of
* {@link Well19937c} as random generator to be used for sampling only (see
* {@link #sample()} and {@link #sample(int)}). In case no sampling is
* needed for the created distribution, it is advised to pass {@code null}
* as random generator via the appropriate constructors to avoid the
* additional initialisation overhead.
*
* @param shape the shape parameter
* @param scale the scale parameter
* @param inverseCumAccuracy the maximum absolute error in inverse
* cumulative probability estimates (defaults to
* {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
* @throws NotStrictlyPositiveException if {@code shape <= 0} or
* {@code scale <= 0}.
* @since 2.1
*/
public GammaDistribution(double shape, double scale, double inverseCumAccuracy)
throws NotStrictlyPositiveException {
this(new Well19937c(), shape, scale, inverseCumAccuracy);
}
/**
* Creates a Gamma distribution.
*
* @param rng Random number generator.
* @param shape the shape parameter
* @param scale the scale parameter
* @throws NotStrictlyPositiveException if {@code shape <= 0} or
* {@code scale <= 0}.
* @since 3.3
*/
public GammaDistribution(RandomGenerator rng, double shape, double scale)
throws NotStrictlyPositiveException {
this(rng, shape, scale, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
}
/**
* Creates a Gamma distribution.
*
* @param rng Random number generator.
* @param shape the shape parameter
* @param scale the scale parameter
* @param inverseCumAccuracy the maximum absolute error in inverse
* cumulative probability estimates (defaults to
* {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY}).
* @throws NotStrictlyPositiveException if {@code shape <= 0} or
* {@code scale <= 0}.
* @since 3.1
*/
public GammaDistribution(RandomGenerator rng,
double shape,
double scale,
double inverseCumAccuracy)
throws NotStrictlyPositiveException {
super(rng);
if (shape <= 0) {
throw new NotStrictlyPositiveException(LocalizedFormats.SHAPE, shape);
}
if (scale <= 0) {
throw new NotStrictlyPositiveException(LocalizedFormats.SCALE, scale);
}
this.shape = shape;
this.scale = scale;
this.solverAbsoluteAccuracy = inverseCumAccuracy;
this.shiftedShape = shape + Gamma.LANCZOS_G + 0.5;
final double aux = FastMath.E / (2.0 * FastMath.PI * shiftedShape);
this.densityPrefactor2 = shape * FastMath.sqrt(aux) / Gamma.lanczos(shape);
this.logDensityPrefactor2 = FastMath.log(shape) + 0.5 * FastMath.log(aux) -
FastMath.log(Gamma.lanczos(shape));
this.densityPrefactor1 = this.densityPrefactor2 / scale *
FastMath.pow(shiftedShape, -shape) *
FastMath.exp(shape + Gamma.LANCZOS_G);
this.logDensityPrefactor1 = this.logDensityPrefactor2 - FastMath.log(scale) -
FastMath.log(shiftedShape) * shape +
shape + Gamma.LANCZOS_G;
this.minY = shape + Gamma.LANCZOS_G - FastMath.log(Double.MAX_VALUE);
this.maxLogY = FastMath.log(Double.MAX_VALUE) / (shape - 1.0);
}
/**
* Returns the shape parameter of {@code this} distribution.
*
* @return the shape parameter
* @deprecated as of version 3.1, {@link #getShape()} should be preferred.
* This method will be removed in version 4.0.
*/
@Deprecated
public double getAlpha() {
return shape;
}
/**
* Returns the shape parameter of {@code this} distribution.
*
* @return the shape parameter
* @since 3.1
*/
public double getShape() {
return shape;
}
/**
* Returns the scale parameter of {@code this} distribution.
*
* @return the scale parameter
* @deprecated as of version 3.1, {@link #getScale()} should be preferred.
* This method will be removed in version 4.0.
*/
@Deprecated
public double getBeta() {
return scale;
}
/**
* Returns the scale parameter of {@code this} distribution.
*
* @return the scale parameter
* @since 3.1
*/
public double getScale() {
return scale;
}
/** {@inheritDoc} */
public double density(double x) {
/* The present method must return the value of
*
* 1 x a - x
* ---------- (-) exp(---)
* x Gamma(a) b b
*
* where a is the shape parameter, and b the scale parameter.
* Substituting the Lanczos approximation of Gamma(a) leads to the
* following expression of the density
*
* a e 1 y a
* - sqrt(------------------) ---- (-----------) exp(a - y + g),
* x 2 pi (a + g + 0.5) L(a) a + g + 0.5
*
* where y = x / b. The above formula is the "natural" computation, which
* is implemented when no overflow is likely to occur. If overflow occurs
* with the natural computation, the following identity is used. It is
* based on the BOOST library
* http://www.boost.org/doc/libs/1_35_0/libs/math/doc/sf_and_dist/html/math_toolkit/special/sf_gamma/igamma.html
* Formula (15) needs adaptations, which are detailed below.
*
* y a
* (-----------) exp(a - y + g)
* a + g + 0.5
* y - a - g - 0.5 y (g + 0.5)
* = exp(a log1pm(---------------) - ----------- + g),
* a + g + 0.5 a + g + 0.5
*
* where log1pm(z) = log(1 + z) - z. Therefore, the value to be
* returned is
*
* a e 1
* - sqrt(------------------) ----
* x 2 pi (a + g + 0.5) L(a)
* y - a - g - 0.5 y (g + 0.5)
* * exp(a log1pm(---------------) - ----------- + g).
* a + g + 0.5 a + g + 0.5
*/
if (x < 0) {
return 0;
}
final double y = x / scale;
if ((y <= minY) || (FastMath.log(y) >= maxLogY)) {
/*
* Overflow.
*/
final double aux1 = (y - shiftedShape) / shiftedShape;
final double aux2 = shape * (FastMath.log1p(aux1) - aux1);
final double aux3 = -y * (Gamma.LANCZOS_G + 0.5) / shiftedShape +
Gamma.LANCZOS_G + aux2;
return densityPrefactor2 / x * FastMath.exp(aux3);
}
/*
* Natural calculation.
*/
return densityPrefactor1 * FastMath.exp(-y) * FastMath.pow(y, shape - 1);
}
/** {@inheritDoc} **/
@Override
public double logDensity(double x) {
/*
* see the comment in {@link #density(double)} for computation details
*/
if (x < 0) {
return Double.NEGATIVE_INFINITY;
}
final double y = x / scale;
if ((y <= minY) || (FastMath.log(y) >= maxLogY)) {
/*
* Overflow.
*/
final double aux1 = (y - shiftedShape) / shiftedShape;
final double aux2 = shape * (FastMath.log1p(aux1) - aux1);
final double aux3 = -y * (Gamma.LANCZOS_G + 0.5) / shiftedShape +
Gamma.LANCZOS_G + aux2;
return logDensityPrefactor2 - FastMath.log(x) + aux3;
}
/*
* Natural calculation.
*/
return logDensityPrefactor1 - y + FastMath.log(y) * (shape - 1);
}
/**
* {@inheritDoc}
*
* The implementation of this method is based on:
* <ul>
* <li>
* <a href="http://mathworld.wolfram.com/Chi-SquaredDistribution.html">
* Chi-Squared Distribution</a>, equation (9).
* </li>
* <li>Casella, G., & Berger, R. (1990). <i>Statistical Inference</i>.
* Belmont, CA: Duxbury Press.
* </li>
* </ul>
*/
public double cumulativeProbability(double x) {
double ret;
if (x <= 0) {
ret = 0;
} else {
ret = Gamma.regularizedGammaP(shape, x / scale);
}
return ret;
}
/** {@inheritDoc} */
@Override
protected double getSolverAbsoluteAccuracy() {
return solverAbsoluteAccuracy;
}
/**
* {@inheritDoc}
*
* For shape parameter {@code alpha} and scale parameter {@code beta}, the
* mean is {@code alpha * beta}.
*/
public double getNumericalMean() {
return shape * scale;
}
/**
* {@inheritDoc}
*
* For shape parameter {@code alpha} and scale parameter {@code beta}, the
* variance is {@code alpha * beta^2}.
*
* @return {@inheritDoc}
*/
public double getNumericalVariance() {
return shape * scale * scale;
}
/**
* {@inheritDoc}
*
* The lower bound of the support is always 0 no matter the parameters.
*
* @return lower bound of the support (always 0)
*/
public double getSupportLowerBound() {
return 0;
}
/**
* {@inheritDoc}
*
* The upper bound of the support is always positive infinity
* no matter the parameters.
*
* @return upper bound of the support (always Double.POSITIVE_INFINITY)
*/
public double getSupportUpperBound() {
return Double.POSITIVE_INFINITY;
}
/** {@inheritDoc} */
public boolean isSupportLowerBoundInclusive() {
return true;
}
/** {@inheritDoc} */
public boolean isSupportUpperBoundInclusive() {
return false;
}
/**
* {@inheritDoc}
*
* The support of this distribution is connected.
*
* @return {@code true}
*/
public boolean isSupportConnected() {
return true;
}
/**
* <p>This implementation uses the following algorithms: </p>
*
* <p>For 0 < shape < 1: <br/>
* Ahrens, J. H. and Dieter, U., <i>Computer methods for
* sampling from gamma, beta, Poisson and binomial distributions.</i>
* Computing, 12, 223-246, 1974.</p>
*
* <p>For shape >= 1: <br/>
* Marsaglia and Tsang, <i>A Simple Method for Generating
* Gamma Variables.</i> ACM Transactions on Mathematical Software,
* Volume 26 Issue 3, September, 2000.</p>
*
* @return random value sampled from the Gamma(shape, scale) distribution
*/
@Override
public double sample() {
if (shape < 1) {
// [1]: p. 228, Algorithm GS
while (true) {
// Step 1:
final double u = random.nextDouble();
final double bGS = 1 + shape / FastMath.E;
final double p = bGS * u;
if (p <= 1) {
// Step 2:
final double x = FastMath.pow(p, 1 / shape);
final double u2 = random.nextDouble();
if (u2 > FastMath.exp(-x)) {
// Reject
continue;
} else {
return scale * x;
}
} else {
// Step 3:
final double x = -1 * FastMath.log((bGS - p) / shape);
final double u2 = random.nextDouble();
if (u2 > FastMath.pow(x, shape - 1)) {
// Reject
continue;
} else {
return scale * x;
}
}
}
}
// Now shape >= 1
final double d = shape - 0.333333333333333333;
final double c = 1 / (3 * FastMath.sqrt(d));
while (true) {
final double x = random.nextGaussian();
final double v = (1 + c * x) * (1 + c * x) * (1 + c * x);
if (v <= 0) {
continue;
}
final double x2 = x * x;
final double u = random.nextDouble();
// Squeeze
if (u < 1 - 0.0331 * x2 * x2) {
return scale * d * v;
}
if (FastMath.log(u) < 0.5 * x2 + d * (1 - v + FastMath.log(v))) {
return scale * d * v;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More