mirror of https://github.com/jlizier/jidt
Merge remote-tracking branch 'ghub/master'. This merges Joe's v1.3.1 master into Pedro's master.
This commit is contained in:
commit
09548028e0
27
README.md
27
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# Java Information Dynamics Toolkit (JIDT)
|
||||
|
||||
Copyright (C) 2012-2014 [Joseph T. Lizier](http://lizier.me/joseph/); 2014-2015 [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
|
||||
|
||||
*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.
|
||||
|
||||
|
|
@ -15,11 +15,13 @@ JIDT is distributed under the [GNU GPL v3 license](http://www.gnu.org/licenses/g
|
|||
# Getting started
|
||||
|
||||
1. [Download](../../wiki/Downloads) and [Installation](../../wiki/Installation) is very easy!
|
||||
1. _Quick start_: download the latest [v1.3 full distribution](http://lizier.me/joseph/software/jidt/download.php?file=infodynamics-dist-1.3.zip) (suitable for all platforms) and see the readme.txt file therein.
|
||||
1. [Documentation](../../wiki/Documentation) including: the paper describing JIDT at [arXiv:1408.3270](http://arxiv.org/abs/1408.3270) (distributed with the toolkit), a [Tutorial](../../wiki/Tutorial), and [Javadocs (v1.3 here)](http://lizier.me/joseph/software/jidt/javadocs/v1.3/);
|
||||
1. [Demos](../../wiki/Demos) are included with the full distribution, including a [GUI app](../../wiki/AutoAnalyser) for automatic analysis and code generation, [simple java demos](../../wiki/SimpleJavaExamples) and [cellular automata (CA) demos](../../wiki/CellularAutomataDemos).
|
||||
1. _Quick start_: download the latest [v1.3.1 full distribution](http://lizier.me/joseph/software/jidt/download.php?file=infodynamics-dist-1.3.1.zip) (suitable for all platforms) and see the readme.txt file therein.
|
||||
1. [Documentation](../../wiki/Documentation) including: the paper describing JIDT at [arXiv:1408.3270](http://arxiv.org/abs/1408.3270) (distributed with the toolkit), a [Tutorial](../../wiki/Tutorial), and [Javadocs (v1.3.1 here)](http://lizier.me/joseph/software/jidt/javadocs/v1.3.1/);
|
||||
1. [Demos](../../wiki/Demos) are included with the full distribution, including a [GUI app](../../wiki/AutoAnalyser) for automatic analysis and code generation (see picture below), [simple java demos](../../wiki/SimpleJavaExamples) and [cellular automata (CA) demos](../../wiki/CellularAutomataDemos).
|
||||
1. These Java tools can easily be used in [Matlab/Octave](../../wiki/OctaveMatlabExamples), [Python](../../wiki/PythonExamples), [R](../../wiki/R_Examples), [Julia](../../wiki/JuliaExamples) and [Clojure](../../wiki/Clojure_Examples)! (click on each language here for examples)
|
||||
|
||||

|
||||
|
||||
For further information or announcements:
|
||||
* Join our discussion group: http://groups.google.com/d/forum/jidt-discuss
|
||||
* See also the [FAQs](../../wiki/FAQs)
|
||||
|
|
@ -37,6 +39,23 @@ See other [PublicationsUsingThisToolkit](../../wiki/PublicationsUsingThisToolkit
|
|||
|
||||
# News
|
||||
|
||||
_17/12/2016_ - New book out from J. Lizier et al., ["An Introduction to Transfer Entropy: Information Flow in Complex Systems"](http://bit.ly/te-book-2016) published by Springer, which contains various examples using JIDT (distributed in our releases)
|
||||
|
||||
_21/10/2016_ - New jar and full distribution files available for **release v1.3.1**; Changes for v1.3.1 include:
|
||||
Major update to TransferEntropyCalculatorDiscrete so as to implement arbitrary source and dest embeddings and source-dest delay;
|
||||
Conditional TE calculators (continuous) handle empty conditional variables;
|
||||
Added new auto-embedding method for AIS and TE which maximises bias corrected AIS;
|
||||
Added getNumSeparateObservations() method to TE calculators to make reconstructing/separating local values easier after multiple addObservations() calls;
|
||||
Fixed kernel estimator classes to return proper densities, not probabilities;
|
||||
Bug fix in mixed discrete-continuous MI (Kraskov) implementation;
|
||||
Added simple interface for adding joint observations for MultiInfoCalculatorDiscrete
|
||||
Including compiled class files for the AutoAnalyser demo in distribution;
|
||||
Updated Python demo 1 to show use of numpy arrays with ints;
|
||||
Added Python demo 7 and 9 for TE Kraskov with ensemble method and auto-embedding respectively;
|
||||
Added Matlab/Octave example 10 for conditional TE via Kraskov (KSG) algorithm;
|
||||
Added utilities to prepare for enhancing surrogate calculations with fast nearest neighbour search;
|
||||
Minor bug patch to Python readFloatsFile utility.
|
||||
|
||||
_19/7/2015_ - New jar and full distribution files available for **release v1.3**; Changes for v1.3 include:
|
||||
Added AutoAnalyser (Code Generator) GUI demo for MI and TE;
|
||||
Added auto-embedding capability via Ragwitz criteria for AIS and TE calculators (KSG estimators);
|
||||
|
|
|
|||
25
build.xml
25
build.xml
|
|
@ -5,7 +5,7 @@
|
|||
</description>
|
||||
|
||||
<!-- set global properties for this build -->
|
||||
<property name="version" value="1.3"/>
|
||||
<property name="version" value="1.3.1"/>
|
||||
<property name="mainfilename" value="infodynamics"/>
|
||||
<property name="jarplainname" value="${mainfilename}.jar" />
|
||||
<property name="jarversiondistnamezip" value="${mainfilename}-jar-${version}.zip" />
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
<property name="unittestsouttoplevel" location="unittests"/>
|
||||
<property name="unittestssrc" location="java/unittests"/>
|
||||
<property name="unittestsbin" location="${unittestsouttoplevel}/bin"/>
|
||||
<property name="autoanalyserdemo" location="demos/java"/>
|
||||
<property name="reports.tests" location="${unittestsouttoplevel}/reports"/>
|
||||
<property name="javadocsdir" location="javadocs"/>
|
||||
<property name="versionfile" value="version-${version}.txt"/>
|
||||
|
|
@ -79,6 +80,23 @@
|
|||
</junit>
|
||||
</target>
|
||||
|
||||
<!-- Compile the autoanalyser demo -->
|
||||
<target name="autoanalyser" depends="jar" description="compile the autoanalyser demo">
|
||||
<!-- Compile to Java 6 to provide compatibility for users with older JREs.
|
||||
Caveat: The flags here only check the language compatibility, but
|
||||
may still use newer libraries which may cause issues for users with JDK 6.
|
||||
Indeed, one gets the warning: "bootstrap class path not set in conjunction with -source 1.6"
|
||||
To fix this, one would use the bootstrap classpath to point our JDK to an rt.jar
|
||||
for Java 6.
|
||||
At this stage, I'm sure I'm not using new library calls from Java 7, so we can
|
||||
ignore the warning, and I don't want to bother installing Java 6 just to compile
|
||||
like this. I'll endeavour not to use JDK 7 libraries so as not to cause
|
||||
any issues here ... -->
|
||||
<javac srcdir="${autoanalyserdemo}" includes="infodynamics/demos/autoanalysis/*.java" excludes="infodynamics/demos/autoanalysis/GeneratedCalculator.java" includeAntRuntime="false" target="1.6" source="1.6">
|
||||
<classpath refid="project.classpath"/>
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<!-- Make javadocs, excluding the classes we've derived from Apache Commons Math -->
|
||||
<target name="javadocs" depends="compile" description="Make the javadocs for the toolkit">
|
||||
<delete dir="${javadocsdir}"/>
|
||||
|
|
@ -190,7 +208,7 @@
|
|||
</target>
|
||||
|
||||
<!-- Developer build - builds everything and makes the full distribution file -->
|
||||
<target name="dist" depends="jar,junit,javadocs,readmefiles" description="developer: generate the full distribution">
|
||||
<target name="dist" depends="jar,junit,autoanalyser,javadocs,readmefiles" description="developer: generate the full distribution">
|
||||
<echo message="${ant.project.name}: ${ant.file}"/>
|
||||
<zip destfile="${distnamezip}">
|
||||
<fileset file="build.xml"/>
|
||||
|
|
@ -198,13 +216,14 @@
|
|||
<fileset file="license-gplv3.txt"/>
|
||||
<fileset file="readme.txt"/>
|
||||
<fileset file="InfoDynamicsToolkit.pdf"/>
|
||||
<fileset file="JIDT-logo.png" erroronmissingdir="false"/> <!-- This file is missing in full repository versions -->
|
||||
<fileset file="${versionfile}"/>
|
||||
<zipfileset dir="java" includes="**/*.java" prefix="java"/>
|
||||
<zipfileset dir="demos" includes="**/*.*,**/*" excludes="clojure/deploy,clojure/deploy/*.*,python/*.pyc" prefix="demos"/>
|
||||
<zipfileset dir="javadocs" includes="**/*.*,**/*" prefix="javadocs"/>
|
||||
<zipfileset dir="notices" includes="**/*.*,**/*" prefix="notices"/>
|
||||
<zipfileset dir="tutorial" prefix="tutorial"/>
|
||||
<zipfileset dir="web" includes="JIDT-logo.png" prefix=""/>
|
||||
<zipfileset dir="web" includes="JIDT-logo.png" prefix="" erroronmissingdir="false"/> <!-- This file is missing in zip dist versions -->
|
||||
</zip>
|
||||
</target>
|
||||
</project>
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -20,7 +20,10 @@ package infodynamics.demos.autoanalysis;
|
|||
|
||||
import infodynamics.measures.continuous.ChannelCalculatorCommon;
|
||||
import infodynamics.measures.discrete.ChannelCalculatorDiscrete;
|
||||
import infodynamics.utils.AnalyticMeasurementDistribution;
|
||||
import infodynamics.utils.AnalyticNullDistributionComputer;
|
||||
import infodynamics.utils.ArrayFileReader;
|
||||
import infodynamics.utils.EmpiricalMeasurementDistribution;
|
||||
import infodynamics.utils.MatrixUtils;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
|
|
@ -30,6 +33,7 @@ import javax.swing.JFrame;
|
|||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JFileChooser;
|
||||
|
|
@ -39,6 +43,8 @@ import javax.swing.JTable;
|
|||
import javax.swing.JTextArea;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.ToolTipManager;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
|
@ -48,6 +54,7 @@ import javax.swing.table.TableColumn;
|
|||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.GridBagConstraints;
|
||||
|
|
@ -73,7 +80,7 @@ import java.util.Vector;
|
|||
*
|
||||
*/
|
||||
public abstract class AutoAnalyser extends JFrame
|
||||
implements ActionListener, DocumentListener, MouseListener {
|
||||
implements ActionListener, DocumentListener, MouseListener, ChangeListener {
|
||||
|
||||
/**
|
||||
* Need serialVersionUID to be serializable
|
||||
|
|
@ -142,6 +149,14 @@ public abstract class AutoAnalyser extends JFrame
|
|||
protected JTextField sourceColTextField;
|
||||
// Destination column field
|
||||
protected JTextField destColTextField;
|
||||
// CheckBox for "all pairs?"
|
||||
protected JCheckBox allPairsCheckBox;
|
||||
// CheckBox for statistical significance
|
||||
protected JCheckBox statSigCheckBox;
|
||||
// CheckBox for statistical significance analytically
|
||||
protected JCheckBox statSigAnalyticCheckBox;
|
||||
// Number of permutations to use for statistical significance
|
||||
protected int numPermutationsToCheck = 100;
|
||||
// Table for the properties
|
||||
protected JTable propertiesTable;
|
||||
// Table model (local class) for the table
|
||||
|
|
@ -154,6 +169,8 @@ public abstract class AutoAnalyser extends JFrame
|
|||
protected Vector<String> propertyDescriptions;
|
||||
// Values of the properties
|
||||
protected HashMap<String,String> propertyValues;
|
||||
// CheckBox for "Compute result?"
|
||||
protected JCheckBox computeResultCheckBox;
|
||||
// Results text
|
||||
protected JLabel resultsLabel;
|
||||
// Text area for the generated Java code
|
||||
|
|
@ -165,6 +182,9 @@ public abstract class AutoAnalyser extends JFrame
|
|||
|
||||
protected String codeDefaultText = " ... Awaiting new parameter selection (press compute) ...";
|
||||
|
||||
protected String buttonTextCodeAndCompute = "Generate code and Compute";
|
||||
protected String buttonTextCodeOnly = "Generate code";
|
||||
|
||||
protected String appletTitle;
|
||||
|
||||
public class TextAreaWithImage extends JTextArea {
|
||||
|
|
@ -232,7 +252,7 @@ public abstract class AutoAnalyser extends JFrame
|
|||
Image watermarkImage = (new ImageIcon("JIDT-logo-watermark.png")).getImage();
|
||||
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setSize(1100,530);
|
||||
setSize(1100,640);
|
||||
setTitle(appletTitle);
|
||||
// Centre in the middle of the screen
|
||||
setLocationRelativeTo(null);
|
||||
|
|
@ -274,11 +294,26 @@ public abstract class AutoAnalyser extends JFrame
|
|||
// To column:
|
||||
JLabel destLabel = new JLabel("Destination column:");
|
||||
destLabel.setToolTipText("First column is 0.");
|
||||
destLabel.setBorder(BorderFactory.createEmptyBorder(0,0,10,0));
|
||||
destColTextField = new JTextField(5);
|
||||
destColTextField.setEnabled(true);
|
||||
destColTextField.setText("1");
|
||||
destColTextField.getDocument().addDocumentListener(this);
|
||||
|
||||
// Checkbox for all pairs
|
||||
allPairsCheckBox = new JCheckBox("All pairs?");
|
||||
allPairsCheckBox.setToolTipText("Compute for all pairs of columns?");
|
||||
allPairsCheckBox.addChangeListener(this);
|
||||
|
||||
// CheckBox for statistical signficance
|
||||
statSigCheckBox = new JCheckBox("Add stat. signif.?");
|
||||
statSigCheckBox.setToolTipText("Compute the null surrogate distribution under the null hypothesis that source and target are not related?");
|
||||
statSigCheckBox.addChangeListener(this);
|
||||
statSigAnalyticCheckBox = new JCheckBox("analytically?");
|
||||
statSigAnalyticCheckBox.setToolTipText("Compute the null surrogate distribution analytically?");
|
||||
statSigAnalyticCheckBox.addChangeListener(this);
|
||||
statSigAnalyticCheckBox.setEnabled(false);
|
||||
|
||||
JLabel dummyLabel1 = new JLabel(" ");
|
||||
dummyLabel1.setSize(10, 10);
|
||||
JLabel dummyLabel2 = new JLabel(" ");
|
||||
|
|
@ -307,9 +342,14 @@ public abstract class AutoAnalyser extends JFrame
|
|||
new Dimension(d.width,rowHeight*17+6));
|
||||
System.out.println("Row height was " + rowHeight);
|
||||
|
||||
|
||||
// Checkbox for compute result
|
||||
computeResultCheckBox = new JCheckBox("Compute result?");
|
||||
computeResultCheckBox.setToolTipText("Compute result or only generate code?");
|
||||
computeResultCheckBox.setSelected(true);
|
||||
computeResultCheckBox.addChangeListener(this);
|
||||
|
||||
// Button to compute
|
||||
computeButton = new JButton("Compute");
|
||||
computeButton = new JButton(buttonTextCodeAndCompute);
|
||||
computeButton.addActionListener(this);
|
||||
|
||||
// Results label
|
||||
|
|
@ -336,7 +376,7 @@ public abstract class AutoAnalyser extends JFrame
|
|||
javaAreaScrollPane.setHorizontalScrollBarPolicy(
|
||||
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||
int codeTextAreaWidth = 560;
|
||||
int codeTextAreaHeight = 460;
|
||||
int codeTextAreaHeight = 480;
|
||||
Dimension codeTextAreaDimension =
|
||||
new Dimension(codeTextAreaWidth, codeTextAreaHeight);
|
||||
javaAreaScrollPane.setPreferredSize(codeTextAreaDimension);
|
||||
|
|
@ -425,6 +465,14 @@ public abstract class AutoAnalyser extends JFrame
|
|||
|
||||
paramsPanel.add(dataFileDescriptorLabel, c);
|
||||
|
||||
// Add the all pairs label and checkbox
|
||||
c.gridwidth = GridBagConstraints.REMAINDER; //end row
|
||||
c.gridx = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.weightx = 1.0;
|
||||
paramsPanel.add(allPairsCheckBox, c);
|
||||
c.gridx = -1; // reset to no indication
|
||||
|
||||
// Add the source selector
|
||||
c.gridwidth = GridBagConstraints.RELATIVE; //next-to-last
|
||||
c.fill = GridBagConstraints.NONE; //reset to default
|
||||
|
|
@ -444,20 +492,46 @@ public abstract class AutoAnalyser extends JFrame
|
|||
c.weightx = 1.0;
|
||||
paramsPanel.add(destColTextField, c);
|
||||
|
||||
// Add the CheckBoxes for statistical significance checks:
|
||||
c.gridwidth = GridBagConstraints.RELATIVE; //next-to-last
|
||||
c.fill = GridBagConstraints.NONE; //reset to default
|
||||
c.weightx = 0.0; //reset to default
|
||||
paramsPanel.add(statSigCheckBox, c);
|
||||
c.gridwidth = GridBagConstraints.REMAINDER; //end row
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.weightx = 1.0;
|
||||
paramsPanel.add(statSigAnalyticCheckBox, c);
|
||||
|
||||
// Add dummy label for spacing
|
||||
paramsPanel.add(dummyLabel1, c);
|
||||
// Add the properties table
|
||||
paramsPanel.add(propsTableScrollPane, c);
|
||||
// Add dummy label for spacing
|
||||
paramsPanel.add(dummyLabel2, c);
|
||||
// Add the "compute result" checkbox:
|
||||
c.gridwidth = GridBagConstraints.RELATIVE; //next-to-last
|
||||
c.fill = GridBagConstraints.NONE; //reset to default
|
||||
c.weightx = 0.0; //reset to default
|
||||
paramsPanel.add(computeResultCheckBox);
|
||||
// Add the compute button
|
||||
c.gridwidth = GridBagConstraints.REMAINDER; //end row
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
c.weightx = 1.0;
|
||||
paramsPanel.add(computeButton, c);
|
||||
|
||||
// Status panel
|
||||
JPanel statusPanel = new JPanel();
|
||||
statusPanel.setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createTitledBorder("Status"),
|
||||
BorderFactory.createEmptyBorder(5,5,5,5)));
|
||||
statusPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
|
||||
// Add the results text
|
||||
paramsPanel.add(resultsLabel, c);
|
||||
|
||||
// Add both panels into the frame with Border layout
|
||||
statusPanel.add(resultsLabel);
|
||||
|
||||
// Add all panels into the frame with Border layout
|
||||
add(paramsPanel, BorderLayout.WEST);
|
||||
add(codePanel);
|
||||
add(codePanel, BorderLayout.EAST);
|
||||
add(statusPanel, BorderLayout.SOUTH);
|
||||
|
||||
setVisible(true);
|
||||
|
||||
|
|
@ -492,6 +566,20 @@ public abstract class AutoAnalyser extends JFrame
|
|||
putCalcPropertiesInTable();
|
||||
propertiesTableModel.fireTableDataChanged(); // Alerts to refresh the table contents
|
||||
System.out.println("Added properties for new calculator");
|
||||
if (statSigCheckBox.isSelected()) {
|
||||
// We might allow the analytic box to be checked if the selected
|
||||
// calculator allows this:
|
||||
if (calcImplementsAnalyticStatSig()) {
|
||||
statSigAnalyticCheckBox.setEnabled(true);
|
||||
} else {
|
||||
statSigAnalyticCheckBox.setSelected(false);
|
||||
statSigAnalyticCheckBox.setEnabled(false);
|
||||
}
|
||||
} else {
|
||||
statSigAnalyticCheckBox.setSelected(false);
|
||||
statSigAnalyticCheckBox.setEnabled(false);
|
||||
}
|
||||
|
||||
}
|
||||
// Else nothing extra to do
|
||||
}
|
||||
|
|
@ -550,7 +638,8 @@ public abstract class AutoAnalyser extends JFrame
|
|||
dataRows, dataColumns);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace(System.err);
|
||||
JOptionPane.showMessageDialog(this, ex.getMessage());
|
||||
JOptionPane.showMessageDialog(this, ex.getMessage() +
|
||||
(isInts ? ".\nFor Discrete calculator make sure you load a file with integer data!" : ""));
|
||||
dataFileDescriptorLabel.setText("Invalid data file, please load another");
|
||||
if (isInts) {
|
||||
dataDiscrete = null;
|
||||
|
|
@ -574,26 +663,44 @@ public abstract class AutoAnalyser extends JFrame
|
|||
loadData(true);
|
||||
if (dataDiscrete == null) {
|
||||
// An error message will have been shown by loadData()
|
||||
resultsLabel.setText(" ");
|
||||
return;
|
||||
}
|
||||
} else if (data == null) {
|
||||
// We need a file of continuous data but no file has been selected
|
||||
JOptionPane.showMessageDialog(this, "No valid data source selected");
|
||||
resultsLabel.setText(" ");
|
||||
return;
|
||||
}
|
||||
int sourceColumn = Integer.parseInt(sourceColTextField.getText());
|
||||
int destColumn = Integer.parseInt(destColTextField.getText());
|
||||
if ((sourceColumn < 0) || (sourceColumn >= dataColumns)) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
String.format("Source column must be between 0 and %d for this data set",
|
||||
dataColumns-1));
|
||||
return;
|
||||
}
|
||||
if ((destColumn < 0) || (destColumn >= dataColumns)) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
String.format("Destination column must be between 0 and %d for this data set",
|
||||
dataColumns-1));
|
||||
return;
|
||||
|
||||
int singleSourceColumn = -1, singleDestColumn = -1;
|
||||
Vector<int[]> sourceDestPairs = new Vector<int[]>();
|
||||
if (allPairsCheckBox.isSelected()) {
|
||||
// We're doing all pairs
|
||||
for (int s = 0; s < dataColumns; s++) {
|
||||
for (int d = 0; d < dataColumns; d++) {
|
||||
sourceDestPairs.add(new int[] {s, d});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we're doing a single source-destination pair
|
||||
singleSourceColumn = Integer.parseInt(sourceColTextField.getText());
|
||||
singleDestColumn = Integer.parseInt(destColTextField.getText());
|
||||
sourceDestPairs.add(new int[] {singleSourceColumn, singleDestColumn});
|
||||
if ((singleSourceColumn < 0) || (singleSourceColumn >= dataColumns)) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
String.format("Source column must be between 0 and %d for this data set",
|
||||
dataColumns-1));
|
||||
resultsLabel.setText(" ");
|
||||
return;
|
||||
}
|
||||
if ((singleDestColumn < 0) || (singleDestColumn >= dataColumns)) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
String.format("Destination column must be between 0 and %d for this data set",
|
||||
dataColumns-1));
|
||||
resultsLabel.setText(" ");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate headers:
|
||||
|
|
@ -601,6 +708,9 @@ public abstract class AutoAnalyser extends JFrame
|
|||
StringBuffer javaCode = new StringBuffer();
|
||||
javaCode.append("package infodynamics.demos.autoanalysis;\n\n");
|
||||
javaCode.append("import infodynamics.utils.ArrayFileReader;\n");
|
||||
if (statSigCheckBox.isSelected()) {
|
||||
javaCode.append("import infodynamics.utils.EmpiricalMeasurementDistribution;\n");
|
||||
}
|
||||
javaCode.append("import infodynamics.utils.MatrixUtils;\n\n");
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
// Cover all children:
|
||||
|
|
@ -688,12 +798,16 @@ public abstract class AutoAnalyser extends JFrame
|
|||
javaCode.append(" ArrayFileReader afr = new ArrayFileReader(dataFile);\n");
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
javaCode.append(" int[][] data = afr.getInt2DMatrix();\n");
|
||||
javaCode.append(" int[] source = MatrixUtils.selectColumn(data, " + sourceColumn + ");\n");
|
||||
javaCode.append(" int[] dest = MatrixUtils.selectColumn(data, " + destColumn + ");\n\n");
|
||||
if (! allPairsCheckBox.isSelected()) {
|
||||
javaCode.append(" int[] source = MatrixUtils.selectColumn(data, " + singleSourceColumn + ");\n");
|
||||
javaCode.append(" int[] dest = MatrixUtils.selectColumn(data, " + singleDestColumn + ");\n\n");
|
||||
}
|
||||
} else {
|
||||
javaCode.append(" double[][] data = afr.getDouble2DMatrix();\n");
|
||||
javaCode.append(" double[] source = MatrixUtils.selectColumn(data, " + sourceColumn + ");\n");
|
||||
javaCode.append(" double[] dest = MatrixUtils.selectColumn(data, " + destColumn + ");\n\n");
|
||||
if (! allPairsCheckBox.isSelected()) {
|
||||
javaCode.append(" double[] source = MatrixUtils.selectColumn(data, " + singleSourceColumn + ");\n");
|
||||
javaCode.append(" double[] dest = MatrixUtils.selectColumn(data, " + singleDestColumn + ");\n\n");
|
||||
}
|
||||
}
|
||||
// 2. Python
|
||||
pythonCode.append("# " + loadDataComment);
|
||||
|
|
@ -704,18 +818,22 @@ public abstract class AutoAnalyser extends JFrame
|
|||
}
|
||||
pythonCode.append("# As numpy array:\n");
|
||||
pythonCode.append("data = numpy.array(dataRaw)\n");
|
||||
pythonCode.append("source = data[:," + sourceColumn + "]\n");
|
||||
pythonCode.append("dest = data[:," + destColumn + "]\n\n");
|
||||
if (! allPairsCheckBox.isSelected()) {
|
||||
pythonCode.append("source = JArray(JInt, 1)(data[:," + singleSourceColumn + "].tolist())\n");
|
||||
pythonCode.append("dest = JArray(JInt, 1)(data[:," + singleDestColumn + "].tolist())\n\n");
|
||||
}
|
||||
// 3. Matlab
|
||||
matlabCode.append("% " + loadDataComment);
|
||||
matlabCode.append("data = load('" + dataFile.getAbsolutePath() + "');\n");
|
||||
matlabCode.append("% Column indices start from 1 in Matlab:\n");
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
matlabCode.append("source = octaveToJavaIntArray(data(:," + (sourceColumn+1) + "));\n");
|
||||
matlabCode.append("dest = octaveToJavaIntArray(data(:," + (destColumn+1) + "));\n\n");
|
||||
} else {
|
||||
matlabCode.append("source = octaveToJavaDoubleArray(data(:," + (sourceColumn+1) + "));\n");
|
||||
matlabCode.append("dest = octaveToJavaDoubleArray(data(:," + (destColumn+1) + "));\n\n");
|
||||
if (! allPairsCheckBox.isSelected()) {
|
||||
matlabCode.append("% Column indices start from 1 in Matlab:\n");
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
matlabCode.append("source = octaveToJavaIntArray(data(:," + (singleSourceColumn+1) + "));\n");
|
||||
matlabCode.append("dest = octaveToJavaIntArray(data(:," + (singleDestColumn+1) + "));\n\n");
|
||||
} else {
|
||||
matlabCode.append("source = octaveToJavaDoubleArray(data(:," + (singleSourceColumn+1) + "));\n");
|
||||
matlabCode.append("dest = octaveToJavaDoubleArray(data(:," + (singleDestColumn+1) + "));\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the calculator and set relevant properties:
|
||||
|
|
@ -736,9 +854,12 @@ public abstract class AutoAnalyser extends JFrame
|
|||
int minInData = MatrixUtils.min(dataDiscrete);
|
||||
int maxInData = MatrixUtils.max(dataDiscrete);
|
||||
if ((minInData < 0) || (maxInData >= base)) {
|
||||
throw new Exception("Values in data file (in range " + minInData +
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Values in data file (in range " + minInData +
|
||||
":" + maxInData + ") lie outside the expected range 0:" +
|
||||
(base-1) + " for base " + base);
|
||||
resultsLabel.setText(" ");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Java
|
||||
|
|
@ -774,6 +895,7 @@ public abstract class AutoAnalyser extends JFrame
|
|||
pythonCode.append("# " + setPropertiesComment);
|
||||
matlabCode.append("% " + setPropertiesComment);
|
||||
int i = 0;
|
||||
boolean anyPropValueChanged = false;
|
||||
for (String propName : propertyNames) {
|
||||
String propValue = null;
|
||||
String propFieldName = propertyFieldNames.get(i++);
|
||||
|
|
@ -789,6 +911,7 @@ public abstract class AutoAnalyser extends JFrame
|
|||
// this calculator. This is more for generating the minimal code.
|
||||
if (!propValue.equalsIgnoreCase(calcContinuous.getProperty(propName))) {
|
||||
// We need to set this property:
|
||||
anyPropValueChanged = true;
|
||||
calcContinuous.setProperty(propName, propValue);
|
||||
// 1. Java Code -- use full field name here
|
||||
javaCode.append(" calc.setProperty(" + propFieldName +
|
||||
|
|
@ -802,80 +925,180 @@ public abstract class AutoAnalyser extends JFrame
|
|||
propValue + "');\n");
|
||||
}
|
||||
}
|
||||
if (!anyPropValueChanged) {
|
||||
String noPropValueChangedComment = "No properties were set to non-default values\n";
|
||||
javaCode.append(" // " + noPropValueChangedComment);
|
||||
pythonCode.append("# " + noPropValueChangedComment);
|
||||
matlabCode.append("% " + noPropValueChangedComment);
|
||||
}
|
||||
}
|
||||
|
||||
String javaPrefix = " ";
|
||||
String pythonPrefix = "";
|
||||
String matlabPrefix = "";
|
||||
if (sourceDestPairs.size() > 1) {
|
||||
// We're doing all pairs
|
||||
|
||||
// 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");
|
||||
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("\tfor d in range(" + dataColumns + "):\n");
|
||||
pythonPrefix = "\t\t";
|
||||
pythonCode.append(pythonPrefix+ "# For each source-dest pair:\n");
|
||||
pythonCode.append(pythonPrefix + "if (s == d):\n");
|
||||
pythonCode.append(pythonPrefix + "\tcontinue\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");
|
||||
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");
|
||||
|
||||
// Read in the data for these columns for all pairs
|
||||
matlabCode.append(matlabPrefix + "% Column indices start from 1 in Matlab:\n");
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
javaCode.append(javaPrefix + "int[] source = MatrixUtils.selectColumn(data, s);\n");
|
||||
javaCode.append(javaPrefix + "int[] dest = MatrixUtils.selectColumn(data, d);\n\n");
|
||||
matlabCode.append(matlabPrefix + "source = octaveToJavaIntArray(data(:, s));\n");
|
||||
matlabCode.append(matlabPrefix + "dest = octaveToJavaIntArray(data(:, d));\n\n");
|
||||
pythonCode.append(pythonPrefix + "source = JArray(JInt, 1)(data[:, s].tolist())\n");
|
||||
pythonCode.append(pythonPrefix + "dest = JArray(JInt, 1)(data[:, d].tolist())\n\n");
|
||||
} else {
|
||||
javaCode.append(javaPrefix + "double[] source = MatrixUtils.selectColumn(data, s);\n");
|
||||
javaCode.append(javaPrefix + "double[] dest = MatrixUtils.selectColumn(data, d);\n\n");
|
||||
matlabCode.append(matlabPrefix + "source = octaveToJavaDoubleArray(data(:, s));\n");
|
||||
matlabCode.append(matlabPrefix + "dest = octaveToJavaDoubleArray(data(:, d));\n\n");
|
||||
pythonCode.append(pythonPrefix + "source = data[:, s]\n");
|
||||
pythonCode.append(pythonPrefix + "dest = data[:, d]\n\n");
|
||||
}
|
||||
} else {
|
||||
// For a single pair, we don't need to set up loops, and
|
||||
// we've already read in the data to source and dest variables above
|
||||
}
|
||||
|
||||
// Initialise
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
calcDiscrete.initialise();
|
||||
} else {
|
||||
calcContinuous.initialise();
|
||||
}
|
||||
String initialiseComment = "3. Initialise the calculator for (re-)use:\n";
|
||||
javaCode.append(" // " + initialiseComment);
|
||||
javaCode.append(" calc.initialise();\n");
|
||||
pythonCode.append("# " + initialiseComment);
|
||||
pythonCode.append("calc.initialise()\n");
|
||||
matlabCode.append("% " + initialiseComment);
|
||||
matlabCode.append("calc.initialise();\n");
|
||||
|
||||
javaCode.append(javaPrefix + "// " + initialiseComment);
|
||||
javaCode.append(javaPrefix + "calc.initialise();\n");
|
||||
pythonCode.append(pythonPrefix + "# " + initialiseComment);
|
||||
pythonCode.append(pythonPrefix + "calc.initialise()\n");
|
||||
matlabCode.append(matlabPrefix + "% " + initialiseComment);
|
||||
matlabCode.append(matlabPrefix + "calc.initialise();\n");
|
||||
|
||||
// Set observations
|
||||
String supplyDataComment = "4. Supply the sample data:\n";
|
||||
String setObservationsMethod;
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
setObservationsMethod = "addObservations";
|
||||
calcDiscrete.addObservations(
|
||||
MatrixUtils.selectColumn(dataDiscrete, sourceColumn),
|
||||
MatrixUtils.selectColumn(dataDiscrete, destColumn));
|
||||
} else {
|
||||
setObservationsMethod = "setObservations";
|
||||
// TODO We should be able to directly call setObservations(double[], double[])
|
||||
// here but we can't right now because ChannelCalculator does not
|
||||
// define this method. It would be complicated to fix this
|
||||
// because it involves Conditional MI calculator it seems.
|
||||
// Revisit this later -- for now fix by deferring to child classes
|
||||
// calcContinuous.setObservations(
|
||||
// MatrixUtils.selectColumn(data, sourceColumn),
|
||||
// MatrixUtils.selectColumn(data, destColumn));
|
||||
setObservations(calcContinuous,
|
||||
MatrixUtils.selectColumn(data, sourceColumn),
|
||||
MatrixUtils.selectColumn(data, destColumn));
|
||||
}
|
||||
// 1. Java
|
||||
javaCode.append(" // " + supplyDataComment);
|
||||
javaCode.append(" calc." + setObservationsMethod + "(source, dest);\n");
|
||||
javaCode.append(javaPrefix + "// " + supplyDataComment);
|
||||
javaCode.append(javaPrefix + "calc." + setObservationsMethod + "(source, dest);\n");
|
||||
// 2. Python
|
||||
pythonCode.append("# " + supplyDataComment);
|
||||
pythonCode.append("calc." + setObservationsMethod + "(source, dest)\n");
|
||||
pythonCode.append(pythonPrefix + "# " + supplyDataComment);
|
||||
pythonCode.append(pythonPrefix + "calc." + setObservationsMethod + "(source, dest)\n");
|
||||
// 3. Matlab
|
||||
matlabCode.append("% " + supplyDataComment);
|
||||
matlabCode.append("calc." + setObservationsMethod + "(source, dest);\n");
|
||||
matlabCode.append(matlabPrefix + "% " + supplyDataComment);
|
||||
matlabCode.append(matlabPrefix + "calc." + setObservationsMethod + "(source, dest);\n");
|
||||
|
||||
// Compute the result
|
||||
double result;
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
result = calcDiscrete.computeAverageLocalOfObservations();
|
||||
} else {
|
||||
result = calcContinuous.computeAverageLocalOfObservations();
|
||||
}
|
||||
String computeComment = "5. Compute the estimate:\n";
|
||||
javaCode.append(" // " + computeComment);
|
||||
javaCode.append(" double result = calc.computeAverageLocalOfObservations();\n");
|
||||
pythonCode.append("# " + computeComment);
|
||||
pythonCode.append("result = calc.computeAverageLocalOfObservations()\n");
|
||||
matlabCode.append("% " + computeComment);
|
||||
matlabCode.append("result = calc.computeAverageLocalOfObservations();\n");
|
||||
String resultsPrefixString = String.format(measureAcronym + "_%s(col_%d -> col_%d) = ",
|
||||
selectedCalcType, sourceColumn, destColumn);
|
||||
resultsLabel.setText(String.format(resultsPrefixString + "%.4f %s", result, units));
|
||||
javaCode.append(javaPrefix + "// " + computeComment);
|
||||
javaCode.append(javaPrefix + "double result = calc.computeAverageLocalOfObservations();\n");
|
||||
pythonCode.append(pythonPrefix + "# " + computeComment);
|
||||
pythonCode.append(pythonPrefix + "result = calc.computeAverageLocalOfObservations()\n");
|
||||
matlabCode.append(matlabPrefix + "% " + computeComment);
|
||||
matlabCode.append(matlabPrefix + "result = calc.computeAverageLocalOfObservations();\n");
|
||||
|
||||
String resultsPrefixString, extraFormatTerms;
|
||||
if (sourceDestPairs.size() > 1) {
|
||||
resultsPrefixString = String.format(measureAcronym + "_%s(col_%%d -> col_%%d) = ",
|
||||
selectedCalcType);
|
||||
extraFormatTerms = "s, d, ";
|
||||
} else {
|
||||
resultsPrefixString = String.format(measureAcronym + "_%s(col_%d -> col_%d) = ",
|
||||
selectedCalcType, singleSourceColumn, singleDestColumn);
|
||||
extraFormatTerms = "";
|
||||
}
|
||||
|
||||
String resultsSuffixString = "";
|
||||
String statSigFormatTerms = "";
|
||||
if (statSigCheckBox.isSelected()) {
|
||||
// Compute statistical significance
|
||||
String statSigComment = "6. Compute the (statistical significance via) null distribution (e.g. 100 permutations):\n";
|
||||
javaCode.append(javaPrefix + "// " + statSigComment);
|
||||
javaCode.append(javaPrefix + "EmpiricalMeasurementDistribution measDist = calc.computeSignificance(100);\n");
|
||||
pythonCode.append(pythonPrefix + "# " + statSigComment);
|
||||
pythonCode.append(pythonPrefix + "measDist = calc.computeSignificance(100)\n");
|
||||
matlabCode.append(matlabPrefix + "% " + statSigComment);
|
||||
matlabCode.append(matlabPrefix + "measDist = calc.computeSignificance(100);\n");
|
||||
if (statSigAnalyticCheckBox.isSelected()) {
|
||||
resultsSuffixString = " (analytic p(surrogate > measured)=%.5f)";
|
||||
statSigFormatTerms = ", measDist.pValue";
|
||||
} else {
|
||||
resultsSuffixString = " (null: %.4f +/- %.4f std dev.; p(surrogate > measured)=%.5f from %d surrogates)";
|
||||
statSigFormatTerms = String.format(", measDist.getMeanOfDistribution(), measDist.getStdOfDistribution(), measDist.pValue, %d",
|
||||
numPermutationsToCheck);
|
||||
}
|
||||
}
|
||||
|
||||
// And generate code to write the results and finalise:
|
||||
// 1. Java
|
||||
javaCode.append(" System.out.printf(\"" + resultsPrefixString + "%.4f " + units + "\\n\", result);\n");
|
||||
javaCode.append("\n" + javaPrefix + "System.out.printf(\"" + resultsPrefixString +
|
||||
"%.4f " + units + resultsSuffixString + "\\n\",\n " + javaPrefix +
|
||||
extraFormatTerms + "result" + statSigFormatTerms + ");\n");
|
||||
// 2. Python
|
||||
pythonCode.append("\n" + pythonPrefix + "print(\"" + resultsPrefixString +
|
||||
"%.4f " + units + resultsSuffixString + "\" %\n\t" + pythonPrefix + "(" +
|
||||
extraFormatTerms + "result" + statSigFormatTerms + "))\n");
|
||||
// 3. Matlab
|
||||
matlabCode.append("\n" + matlabPrefix + "fprintf('" + resultsPrefixString +
|
||||
"%.4f " + units + resultsSuffixString + "\\n', ...\n\t" + matlabPrefix +
|
||||
extraFormatTerms + "result" + statSigFormatTerms + ");\n");
|
||||
|
||||
if (sourceDestPairs.size() > 1) {
|
||||
// We're doing all pairs
|
||||
|
||||
// Finalise the loops in the code:
|
||||
// 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");
|
||||
|
||||
// And tell the user to see results in the console:
|
||||
resultsLabel.setText("See console for all pairs calculation results");
|
||||
if (!selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
System.out.println("Property values not read back into GUI for all pairs calculation");
|
||||
}
|
||||
}
|
||||
|
||||
// Finalise the Java code:
|
||||
javaCode.append(" }\n");
|
||||
javaCode.append("}\n\n");
|
||||
// 2. Python
|
||||
pythonCode.append("print(\"" + resultsPrefixString + "%.4f " + units + "\\n\" % result)\n");
|
||||
// 3. Matlab
|
||||
matlabCode.append("fprintf('" + resultsPrefixString + "%.4f " + units + "\\n', result);\n");
|
||||
|
||||
// Now set the code in the panel for the user
|
||||
javaCodeTextArea.setText(javaCode.toString());
|
||||
|
|
@ -899,22 +1122,123 @@ public abstract class AutoAnalyser extends JFrame
|
|||
codeFileWriter.write(matlabCode.toString());
|
||||
codeFileWriter.close();
|
||||
|
||||
if (!selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
// Read the current property values back out (in case of
|
||||
// automated parameter assignment)
|
||||
for (String propName : propertyNames) {
|
||||
String propValue = null;
|
||||
try {
|
||||
propValue = calcContinuous.getProperty(propName);
|
||||
propertyValues.put(propName, propValue);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace(System.err);
|
||||
JOptionPane.showMessageDialog(this,
|
||||
ex.getMessage());
|
||||
resultsLabel.setText("Cannot find a value for property " + propName);
|
||||
if (computeResultCheckBox.isSelected()) {
|
||||
// Now make our own calculations here:
|
||||
// TODO make the calculation in a separate thread, and have a
|
||||
// button to cancel the calculation whilst it's running
|
||||
for (int[] pair : sourceDestPairs) {
|
||||
int sourceColumn = pair[0];
|
||||
int destColumn = pair[1];
|
||||
|
||||
if (sourceColumn == destColumn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Initialise
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
calcDiscrete.initialise();
|
||||
} else {
|
||||
calcContinuous.initialise();
|
||||
}
|
||||
|
||||
// Set observations
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
calcDiscrete.addObservations(
|
||||
MatrixUtils.selectColumn(dataDiscrete, sourceColumn),
|
||||
MatrixUtils.selectColumn(dataDiscrete, destColumn));
|
||||
} else {
|
||||
// TODO We should be able to directly call setObservations(double[], double[])
|
||||
// here but we can't right now because ChannelCalculator does not
|
||||
// define this method. It would be complicated to fix this
|
||||
// because it involves Conditional MI calculator it seems.
|
||||
// Revisit this later -- for now fix by deferring to child classes
|
||||
// calcContinuous.setObservations(
|
||||
// MatrixUtils.selectColumn(data, sourceColumn),
|
||||
// MatrixUtils.selectColumn(data, destColumn));
|
||||
setObservations(calcContinuous,
|
||||
MatrixUtils.selectColumn(data, sourceColumn),
|
||||
MatrixUtils.selectColumn(data, destColumn));
|
||||
}
|
||||
|
||||
// Compute the result:
|
||||
double result;
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
result = calcDiscrete.computeAverageLocalOfObservations();
|
||||
} else {
|
||||
result = calcContinuous.computeAverageLocalOfObservations();
|
||||
}
|
||||
|
||||
String resultsSuffixStringFormatted = "";
|
||||
if (statSigCheckBox.isSelected()) {
|
||||
// Check statistical significance:
|
||||
if (statSigAnalyticCheckBox.isSelected()) {
|
||||
// analytic check of statistical significance:
|
||||
AnalyticNullDistributionComputer analyticCalc;
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
analyticCalc =
|
||||
(AnalyticNullDistributionComputer) calcDiscrete;
|
||||
} else {
|
||||
analyticCalc =
|
||||
(AnalyticNullDistributionComputer) calcContinuous;
|
||||
}
|
||||
AnalyticMeasurementDistribution measDist =
|
||||
analyticCalc.computeSignificance();
|
||||
resultsSuffixStringFormatted = String.format(
|
||||
resultsSuffixString,
|
||||
measDist.pValue);
|
||||
} else {
|
||||
// permutation test of statistical significance:
|
||||
EmpiricalMeasurementDistribution measDist;
|
||||
if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
measDist = calcDiscrete.computeSignificance(numPermutationsToCheck);
|
||||
} else {
|
||||
measDist = calcContinuous.computeSignificance(numPermutationsToCheck);
|
||||
}
|
||||
resultsSuffixStringFormatted = String.format(
|
||||
resultsSuffixString,
|
||||
measDist.getMeanOfDistribution(),
|
||||
measDist.getStdOfDistribution(),
|
||||
measDist.pValue,
|
||||
numPermutationsToCheck);
|
||||
}
|
||||
}
|
||||
|
||||
String resultsText;
|
||||
if (sourceDestPairs.size() > 1) {
|
||||
// We're doing all pairs
|
||||
resultsText = String.format(
|
||||
resultsPrefixString + "%.4f %s" + resultsSuffixStringFormatted,
|
||||
sourceColumn, destColumn, result, units);
|
||||
} else {
|
||||
resultsText = String.format(
|
||||
resultsPrefixString + "%.4f %s" + resultsSuffixStringFormatted,
|
||||
result, units);
|
||||
resultsLabel.setText(resultsText);
|
||||
}
|
||||
// Write the results to the console:
|
||||
System.out.println(resultsText);
|
||||
}
|
||||
propertiesTableModel.fireTableDataChanged(); // Alerts to refresh the table contents
|
||||
|
||||
if ((sourceDestPairs.size() == 1) &&
|
||||
!selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) {
|
||||
// Read the current property values back out (in case of
|
||||
// automated parameter assignment)
|
||||
for (String propName : propertyNames) {
|
||||
String propValue = null;
|
||||
try {
|
||||
propValue = calcContinuous.getProperty(propName);
|
||||
propertyValues.put(propName, propValue);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace(System.err);
|
||||
JOptionPane.showMessageDialog(this,
|
||||
ex.getMessage());
|
||||
resultsLabel.setText("Cannot find a value for property " + propName);
|
||||
}
|
||||
}
|
||||
propertiesTableModel.fireTableDataChanged(); // Alerts to refresh the table contents
|
||||
}
|
||||
} else {
|
||||
resultsLabel.setText("Code generated (no computation performed)");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace(System.err);
|
||||
|
|
@ -1263,4 +1587,68 @@ public abstract class AutoAnalyser extends JFrame
|
|||
public void mouseReleased(MouseEvent me) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
// I'm not sure how to check which checkbox changed,
|
||||
// so we'll just act on potential changes from both.
|
||||
// This won't cause a problem here
|
||||
|
||||
if (e.getSource() == allPairsCheckBox) {
|
||||
// "All pairs" checkbox -- update in case changed:
|
||||
if (allPairsCheckBox.isSelected()) {
|
||||
sourceColTextField.setEnabled(false);
|
||||
destColTextField.setEnabled(false);
|
||||
} else {
|
||||
sourceColTextField.setEnabled(true);
|
||||
destColTextField.setEnabled(true);
|
||||
}
|
||||
} else if (e.getSource() == computeResultCheckBox) {
|
||||
// Compute checkbox -- update in case changed:
|
||||
if (computeResultCheckBox.isSelected()) {
|
||||
computeButton.setText(buttonTextCodeAndCompute);
|
||||
} else {
|
||||
computeButton.setText(buttonTextCodeOnly);
|
||||
}
|
||||
} else if (e.getSource() == statSigCheckBox) {
|
||||
System.out.println("StatSigCheckBox state changed to " + statSigCheckBox.isSelected());
|
||||
// statSigCheckBox -- update statSigAnalyticCheckBox in case changed:
|
||||
if (statSigCheckBox.isSelected()) {
|
||||
// We might allow the analytic box to be checked if the selected
|
||||
// calculator allows this:
|
||||
if (calcImplementsAnalyticStatSig()) {
|
||||
statSigAnalyticCheckBox.setEnabled(true);
|
||||
} else {
|
||||
statSigAnalyticCheckBox.setSelected(false);
|
||||
statSigAnalyticCheckBox.setEnabled(false);
|
||||
}
|
||||
} else {
|
||||
statSigAnalyticCheckBox.setSelected(false);
|
||||
statSigAnalyticCheckBox.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current calculator class implements the
|
||||
* AnalyticNullDistributionComputer interface
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected boolean calcImplementsAnalyticStatSig() {
|
||||
Class<?> currentClass = calcClass;
|
||||
while (currentClass != null) {
|
||||
// This getInterfaces call doesn't seem to return the interfaces
|
||||
// implemented by the superclass, so we need to go up the tree
|
||||
// to check all of those. That seems odd, surely there is a simpler way?
|
||||
Class<?>[] interfacesOfThisClass = currentClass.getInterfaces();
|
||||
for (Class<?> theInterface : interfacesOfThisClass) {
|
||||
if (theInterface == AnalyticNullDistributionComputer.class) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
currentClass = currentClass.getSuperclass();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ public class AutoAnalyserTE extends AutoAnalyser
|
|||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected static final String DISCRETE_PROPNAME_K = "k_HISTORY";
|
||||
protected static final String DISCRETE_PROPNAME_K_TAU = "k_TAU";
|
||||
protected static final String DISCRETE_PROPNAME_L = "l_HISTORY";
|
||||
protected static final String DISCRETE_PROPNAME_L_TAU = "l_TAU";
|
||||
protected static final String DISCRETE_PROPNAME_DELAY = "DELAY";
|
||||
|
||||
/**
|
||||
* Constructor to initialise the GUI for TE
|
||||
|
|
@ -63,16 +67,28 @@ public class AutoAnalyserTE extends AutoAnalyser
|
|||
// Discrete:
|
||||
discreteClass = TransferEntropyCalculatorDiscrete.class;
|
||||
discreteProperties = new String[] {
|
||||
DISCRETE_PROPNAME_BASE,
|
||||
DISCRETE_PROPNAME_K,
|
||||
DISCRETE_PROPNAME_BASE
|
||||
DISCRETE_PROPNAME_K_TAU,
|
||||
DISCRETE_PROPNAME_L,
|
||||
DISCRETE_PROPNAME_L_TAU,
|
||||
DISCRETE_PROPNAME_DELAY
|
||||
};
|
||||
discretePropertyDefaultValues = new String[] {
|
||||
"2",
|
||||
"1",
|
||||
"2"
|
||||
"1",
|
||||
"1",
|
||||
"1",
|
||||
"1"
|
||||
};
|
||||
discretePropertyDescriptions = new String[] {
|
||||
"Destination history embedding length",
|
||||
"Number of discrete states available for each variable (i.e. 2 for binary)"
|
||||
"Number of discrete states available for each variable (i.e. 2 for binary)",
|
||||
"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)",
|
||||
};
|
||||
|
||||
// Continuous:
|
||||
|
|
@ -183,7 +199,10 @@ public class AutoAnalyserTE extends AutoAnalyser
|
|||
"\" meaning values are set manually; other values include: <br/> -- \"" + TransferEntropyCalculatorKraskov.AUTO_EMBED_METHOD_RAGWITZ +
|
||||
"\" for use of the Ragwitz criteria for both source and destination (searching up to \"" + TransferEntropyCalculatorKraskov.PROP_K_SEARCH_MAX +
|
||||
"\" and \"" + TransferEntropyCalculatorKraskov.PROP_TAU_SEARCH_MAX + "\"); <br/> -- \"" + TransferEntropyCalculatorKraskov.AUTO_EMBED_METHOD_RAGWITZ_DEST_ONLY +
|
||||
"\" for use of the Ragwitz criteria for the destination only. <br/>Use of values other than \"" + TransferEntropyCalculatorKraskov.AUTO_EMBED_METHOD_NONE +
|
||||
"\" for use of the Ragwitz criteria for the destination only. <br/> -- \"" + TransferEntropyCalculatorKraskov.AUTO_EMBED_METHOD_MAX_CORR_AIS +
|
||||
"\" for maximising the (bias corrected) Active Info Storage for both source and destination (searching up to \"" + TransferEntropyCalculatorKraskov.PROP_K_SEARCH_MAX +
|
||||
"\" and \"" + TransferEntropyCalculatorKraskov.PROP_TAU_SEARCH_MAX + "\"); <br/> -- \"" + TransferEntropyCalculatorKraskov.AUTO_EMBED_METHOD_MAX_CORR_AIS_DEST_ONLY +
|
||||
"\" for maximising the (bias corrected) Active Info Storage for the destination only. <br/>Use of values other than \"" + TransferEntropyCalculatorKraskov.AUTO_EMBED_METHOD_NONE +
|
||||
"\" leads to any previous settings for embedding lengths and delays for the destination and perhaps source to be overwritten after observations are supplied",
|
||||
"Max. embedding length to search to <br/>if auto embedding (as determined by " + TransferEntropyCalculatorKraskov.PROP_AUTO_EMBED_METHOD + ")",
|
||||
"Max. embedding delay to search to <br/>if auto embedding (as determined by " + TransferEntropyCalculatorKraskov.PROP_AUTO_EMBED_METHOD + ")",
|
||||
|
|
@ -213,30 +232,66 @@ public class AutoAnalyserTE extends AutoAnalyser
|
|||
* Method to assign and initialise our discrete calculator class
|
||||
*/
|
||||
protected DiscreteCalcAndArguments assignCalcObjectDiscrete() throws Exception {
|
||||
String kPropValueStr, basePropValueStr;
|
||||
int base, k, k_tau, l, l_tau, delay;
|
||||
try {
|
||||
kPropValueStr = propertyValues.get(DISCRETE_PROPNAME_K);
|
||||
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_K);
|
||||
resultsLabel.setText("Cannot read a value for property " + DISCRETE_PROPNAME_BASE);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
basePropValueStr = propertyValues.get(DISCRETE_PROPNAME_BASE);
|
||||
String kPropValueStr = propertyValues.get(DISCRETE_PROPNAME_K);
|
||||
k = Integer.parseInt(kPropValueStr);
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
ex.getMessage());
|
||||
resultsLabel.setText("Cannot find a value for property " + DISCRETE_PROPNAME_BASE);
|
||||
resultsLabel.setText("Cannot read a value for property " + DISCRETE_PROPNAME_K);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String kTauPropValueStr = propertyValues.get(DISCRETE_PROPNAME_K_TAU);
|
||||
k_tau = Integer.parseInt(kTauPropValueStr);
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
ex.getMessage());
|
||||
resultsLabel.setText("Cannot read a value for property " + DISCRETE_PROPNAME_K_TAU);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String lPropValueStr = propertyValues.get(DISCRETE_PROPNAME_L);
|
||||
l = Integer.parseInt(lPropValueStr);
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
ex.getMessage());
|
||||
resultsLabel.setText("Cannot read a value for property " + DISCRETE_PROPNAME_L);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String lTauPropValueStr = propertyValues.get(DISCRETE_PROPNAME_L_TAU);
|
||||
l_tau = Integer.parseInt(lTauPropValueStr);
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
ex.getMessage());
|
||||
resultsLabel.setText("Cannot read a value for property " + DISCRETE_PROPNAME_L_TAU);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String delayPropValueStr = propertyValues.get(DISCRETE_PROPNAME_DELAY);
|
||||
delay = Integer.parseInt(delayPropValueStr);
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(this,
|
||||
ex.getMessage());
|
||||
resultsLabel.setText("Cannot read a value for property " + DISCRETE_PROPNAME_DELAY);
|
||||
return null;
|
||||
}
|
||||
int k = Integer.parseInt(kPropValueStr);
|
||||
int base = Integer.parseInt(basePropValueStr);
|
||||
|
||||
return new DiscreteCalcAndArguments(
|
||||
new TransferEntropyCalculatorDiscrete(base, k),
|
||||
new TransferEntropyCalculatorDiscrete(base, k, k_tau, l, l_tau, delay),
|
||||
base,
|
||||
base + ", " + k);
|
||||
base + ", " + k + ", " + k_tau + ", " + l + ", " + l_tau + ", " + delay);
|
||||
}
|
||||
|
||||
protected void setObservations(ChannelCalculatorCommon calc,
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -0,0 +1,76 @@
|
|||
%%
|
||||
%% Java Information Dynamics Toolkit (JIDT)
|
||||
%% Copyright (C) 2016 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/>.
|
||||
%%
|
||||
|
||||
% = Example 10 - Conditional Transfer entropy on continuous multivariate data using Kraskov estimators =
|
||||
|
||||
% Conditional Transfer entropy (TE) calculation on multivariate continuous-valued data using the Kraskov-estimator TE calculator.
|
||||
|
||||
% Change location of jar to match yours:
|
||||
javaaddpath('../../infodynamics.jar')
|
||||
|
||||
% Generate some random normalised data.
|
||||
numObservations = 100000;
|
||||
% Keep the sum of squares of these covariances below 1 to allow proper calculation of noise term.
|
||||
covarianceToSource = 0.4;
|
||||
covarianceToConds = [0.3,0.3];
|
||||
if (sumsq([covarianceToSource, covarianceToConds]) >= 1)
|
||||
error('Sum of squares of the covariances must be < 1 here');
|
||||
end
|
||||
noiseCovar = sqrt(1 - sumsq([covarianceToSource, covarianceToConds]));
|
||||
|
||||
% Generate the random variables
|
||||
sourceArray = randn(numObservations, 1);
|
||||
condArray = randn(numObservations, length(covarianceToConds));
|
||||
destArray = [0; covarianceToSource*sourceArray(1:numObservations-1,:) + covarianceToConds(1)*condArray(1:numObservations-1,1) + covarianceToConds(2)*condArray(1:numObservations-1,2) + noiseCovar*randn(numObservations-1, 1)];
|
||||
|
||||
% Expected results:
|
||||
expectedConditional = -0.5 * log(noiseCovar .* noiseCovar ./ (1 - sumsq(covarianceToConds)));
|
||||
expectedPairwise = -0.5 * log(1-covarianceToSource.^2);
|
||||
|
||||
% Create a conditional TE calculator and run it:
|
||||
teCalc=javaObject('infodynamics.measures.continuous.kraskov.ConditionalTransferEntropyCalculatorKraskov');
|
||||
teCalc.initialise(1,1, ... % Destination embedding length (Schreiber k=1) and delays
|
||||
1,1, ... % Source embedding length (Schreiber l=1) and delays
|
||||
1, ... % Source-destination delay of 1 (default)
|
||||
octaveToJavaDoubleArray([1,1]), ... % Embedding lengths for each conditional variable
|
||||
octaveToJavaDoubleArray([1,1]), ... % Embedding delays for each conditional variable
|
||||
octaveToJavaDoubleArray([1,1]) ... % conditional-destination delays for each conditional variable
|
||||
);
|
||||
teCalc.setObservations(octaveToJavaDoubleArray(sourceArray), ...
|
||||
octaveToJavaDoubleArray(destArray), ...
|
||||
octaveToJavaDoubleMatrix(condArray));
|
||||
% Perform calculation with correlated source, but no conditioning on other sources:
|
||||
conditionalResult = teCalc.computeAverageLocalOfObservations();
|
||||
|
||||
% Create a pairwise TE calculator and run it:
|
||||
teCalc=javaObject('infodynamics.measures.continuous.kraskov.TransferEntropyCalculatorKraskov');
|
||||
teCalc.initialise(); % Use default embeddings of 1 (e.g. Schreiber k=1 and l=1)
|
||||
teCalc.setObservations(octaveToJavaDoubleArray(sourceArray), octaveToJavaDoubleArray(destArray));
|
||||
% Perform calculation with correlated source, but no conditioning on other sources:
|
||||
pairwiseResult = teCalc.computeAverageLocalOfObservations();
|
||||
|
||||
% Note that the calculation is a random variable (because the generated
|
||||
% 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 some variance around it. It will probably be biased down here
|
||||
% due to small correlations between the supposedly uncorrelated variables.
|
||||
fprintf('From %d samples:\nTE result conditional result = %.4f nats, pairwise = %.4f nats;\nexpected around %.4f nats (conditional) and %.4f nats (pairwise) for the correlated Gaussians\n', ...
|
||||
numObservations, conditionalResult, pairwiseResult, expectedConditional, expectedPairwise);
|
||||
|
||||
clear teCalc
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -20,13 +20,14 @@
|
|||
|
||||
# Simple transfer entropy (TE) calculation on binary data using the discrete TE calculator:
|
||||
|
||||
from jpype import *
|
||||
import jpype
|
||||
import random
|
||||
import numpy
|
||||
|
||||
# Change location of jar to match yours:
|
||||
jarLocation = "../../infodynamics.jar"
|
||||
# Start the JVM (add the "-Xmx" option with say 1024M if you get crashes due to not enough memory space)
|
||||
startJVM(getDefaultJVMPath(), "-ea", "-Djava.class.path=" + jarLocation)
|
||||
jpype.startJVM(jpype.getDefaultJVMPath(), "-ea", "-Djava.class.path=" + jarLocation)
|
||||
|
||||
# Generate some random binary data.
|
||||
sourceArray = [random.randint(0,1) for r in range(100)]
|
||||
|
|
@ -34,15 +35,29 @@ destArray = [0] + sourceArray[0:99]
|
|||
sourceArray2 = [random.randint(0,1) for r in range(100)]
|
||||
|
||||
# Create a TE calculator and run it:
|
||||
teCalcClass = JPackage("infodynamics.measures.discrete").TransferEntropyCalculatorDiscrete
|
||||
teCalcClass = jpype.JPackage("infodynamics.measures.discrete").TransferEntropyCalculatorDiscrete
|
||||
teCalc = teCalcClass(2,1)
|
||||
teCalc.initialise()
|
||||
# Since we have simple arrays of ints, we can directly pass these in:
|
||||
|
||||
# First use simple arrays of ints, which we can directly pass in:
|
||||
teCalc.addObservations(sourceArray, destArray)
|
||||
print("For copied source, result should be close to 1 bit : %.4f" % teCalc.computeAverageLocalOfObservations())
|
||||
teCalc.initialise()
|
||||
teCalc.addObservations(sourceArray2, destArray)
|
||||
print("For random source, result should be close to 0 bits: %.4f" % teCalc.computeAverageLocalOfObservations())
|
||||
|
||||
shutdownJVM()
|
||||
# Next, demonstrate how to do this with a numpy array
|
||||
teCalc.initialise()
|
||||
# Create the numpy arrays:
|
||||
sourceNumpy = numpy.array(sourceArray, dtype=numpy.int)
|
||||
destNumpy = numpy.array(destArray, dtype=numpy.int)
|
||||
# The above can be passed straight through to JIDT in python 2:
|
||||
# teCalc.addObservations(sourceNumpy, destNumpy)
|
||||
# But you need to do this in python 3:
|
||||
sourceNumpyJArray = jpype.JArray(jpype.JInt, 1)(sourceNumpy.tolist())
|
||||
destNumpyJArray = jpype.JArray(jpype.JInt, 1)(destNumpy.tolist())
|
||||
teCalc.addObservations(sourceNumpyJArray, destNumpyJArray)
|
||||
print("Using numpy array for copied source, result confirmed as: %.4f" % teCalc.computeAverageLocalOfObservations())
|
||||
|
||||
jpype.shutdownJVM()
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ jointVariable2 = A[:,jointVariable2Columns]
|
|||
# (in fact, all java object creation in python is dynamic - it has to be,
|
||||
# since the languages are interpreted. This makes our life slightly easier at this
|
||||
# point than it is in demos/java/example6 where we have to handle this manually)
|
||||
indexOfLastDot = string.rfind(implementingClass, ".")
|
||||
indexOfLastDot = implementingClass.rfind(".")
|
||||
implementingPackage = implementingClass[:indexOfLastDot]
|
||||
implementingBaseName = implementingClass[indexOfLastDot+1:]
|
||||
miCalcClass = eval('JPackage(\'%s\').%s' % (implementingPackage, implementingBaseName))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
##
|
||||
## Java Information Dynamics Toolkit (JIDT)
|
||||
## Copyright (C) 2012, Joseph T. Lizier
|
||||
##
|
||||
## This program is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
# = Example 7 - Ensemble method with transfer entropy on continuous data using Kraskov estimators =
|
||||
|
||||
# Calculation of transfer entropy (TE) by supplying an ensemble of samples from multiple time series.
|
||||
# We use continuous-valued data using the Kraskov-estimator TE calculator here.
|
||||
|
||||
from jpype import *
|
||||
import random
|
||||
import math
|
||||
|
||||
# Change location of jar to match yours:
|
||||
jarLocation = "../../infodynamics.jar"
|
||||
# Start the JVM (add the "-Xmx" option with say 1024M if you get crashes due to not enough memory space)
|
||||
startJVM(getDefaultJVMPath(), "-ea", "-Djava.class.path=" + jarLocation)
|
||||
|
||||
# Generate some random normalised data.
|
||||
numObservations = 1000
|
||||
covariance=0.4
|
||||
numTrials=10
|
||||
kHistoryLength=1
|
||||
|
||||
# Create a TE calculator and run it:
|
||||
teCalcClass = JPackage("infodynamics.measures.continuous.kraskov").TransferEntropyCalculatorKraskov
|
||||
teCalc = teCalcClass()
|
||||
teCalc.setProperty("k", "4") # Use Kraskov parameter K=4 for 4 nearest points
|
||||
teCalc.initialise(kHistoryLength) # Use target history length of kHistoryLength (Schreiber k)
|
||||
teCalc.startAddObservations()
|
||||
|
||||
for trial in range(0,numTrials):
|
||||
# Create a new trial, with destArray correlated to
|
||||
# previous value of sourceArray:
|
||||
sourceArray = [random.normalvariate(0,1) for r in range(numObservations)]
|
||||
destArray = [0] + [sum(pair) for pair in zip([covariance*y for y in sourceArray[0:numObservations-1]], \
|
||||
[(1-covariance)*y for y in [random.normalvariate(0,1) for r in range(numObservations-1)]] ) ]
|
||||
|
||||
# Add observations for this trial:
|
||||
print("Adding samples from trial %d ..." % trial)
|
||||
teCalc.addObservations(JArray(JDouble, 1)(sourceArray), JArray(JDouble, 1)(destArray))
|
||||
|
||||
# We've finished adding trials:
|
||||
print("Finished adding trials")
|
||||
teCalc.finaliseAddObservations()
|
||||
|
||||
# Compute the result:
|
||||
print("Computing TE ...")
|
||||
result = teCalc.computeAverageLocalOfObservations()
|
||||
# Note that the calculation is a random variable (because the generated
|
||||
# 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 some variance around it (smaller than example 4 since we have more samples).
|
||||
print("TE result %.4f nats; expected to be close to %.4f nats for these correlated Gaussians " % \
|
||||
(result, math.log(1.0/(1-math.pow(covariance,2)))))
|
||||
|
||||
# And here's how to pull the local TEs out corresponding to each input time
|
||||
# series under the ensemble method (i.e. for multiple trials).
|
||||
localTEs=teCalc.computeLocalOfPreviousObservations()
|
||||
localValuesPerTrial = teCalc.getSeparateNumObservations() # Need to convert to int for indices later
|
||||
startIndex = 0
|
||||
for localValuesInThisTrial in localValuesPerTrial:
|
||||
endIndex = startIndex + localValuesInThisTrial - 1
|
||||
print("Local TEs for trial %d go from array index %d to %d" % (trial, startIndex, endIndex))
|
||||
print(" corresponding to time points %d:%d (indexed from 0) of that trial" % (kHistoryLength, numObservations-1))
|
||||
# Access the local TEs for this trial as:
|
||||
localTEForThisTrial = localTEs[startIndex:endIndex]
|
||||
# Now update the startIndex before we go to the next trial
|
||||
startIndex = endIndex + 1
|
||||
# And make a sanity check that we've looked at all of the local values here:
|
||||
print("We've looked at %d local values in total, matching the number of samples we have (%d)" % (startIndex, teCalc.getNumObservations()))
|
||||
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
##
|
||||
## Java Information Dynamics Toolkit (JIDT)
|
||||
## Copyright (C) 2012, Joseph T. Lizier
|
||||
##
|
||||
## This program is free software: you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation, either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
# = Example 9 - Transfer entropy on continuous data using Kraskov estimators with auto-embedding =
|
||||
|
||||
# Transfer entropy (TE) calculation on continuous-valued data using the Kraskov-estimator TE calculator,
|
||||
# with automatic selection of embedding parameters
|
||||
|
||||
|
||||
from jpype import *
|
||||
import random
|
||||
import math
|
||||
import numpy
|
||||
import readFloatsFile
|
||||
|
||||
# Change location of jar to match yours:
|
||||
jarLocation = "../../infodynamics.jar"
|
||||
# Start the JVM (add the "-Xmx" option with say 1024M if you get crashes due to not enough memory space)
|
||||
startJVM(getDefaultJVMPath(), "-ea", "-Djava.class.path=" + jarLocation)
|
||||
|
||||
# Examine the heart-breath interaction that Schreiber originally looked at:
|
||||
datafile = '../data/SFI-heartRate_breathVol_bloodOx.txt'
|
||||
data = readFloatsFile.readFloatsFile(datafile)
|
||||
# As numpy array:
|
||||
A = numpy.array(data)
|
||||
# Select data points 2350:3550, pulling out the relevant columns:
|
||||
breathRate = A[2350:3551,1];
|
||||
heartRate = A[2350:3551,0];
|
||||
|
||||
# Create a Kraskov TE calculator:
|
||||
teCalcClass = JPackage("infodynamics.measures.continuous.kraskov").TransferEntropyCalculatorKraskov
|
||||
teCalc = teCalcClass()
|
||||
|
||||
# Set properties for auto-embedding of both source and destination
|
||||
# using the Ragwitz criteria:
|
||||
# a. Auto-embedding method
|
||||
teCalc.setProperty(teCalcClass.PROP_AUTO_EMBED_METHOD,
|
||||
teCalcClass.AUTO_EMBED_METHOD_RAGWITZ)
|
||||
# b. Search range for embedding dimension (k) and delay (tau)
|
||||
teCalc.setProperty(teCalcClass.PROP_K_SEARCH_MAX, "6")
|
||||
teCalc.setProperty(teCalcClass.PROP_TAU_SEARCH_MAX, "6")
|
||||
# Since we're auto-embedding, no need to supply k, l, k_tau, l_tau here:
|
||||
teCalc.initialise()
|
||||
# Compute TE from breath (column 1) to heart (column 0)
|
||||
teCalc.setObservations(breathRate, heartRate)
|
||||
teBreathToHeart = teCalc.computeAverageLocalOfObservations()
|
||||
|
||||
# Check the auto-selected parameters and print out the result:
|
||||
optimisedK = int(teCalc.getProperty(teCalcClass.K_PROP_NAME))
|
||||
optimisedKTau = int(teCalc.getProperty(teCalcClass.K_TAU_PROP_NAME))
|
||||
optimisedL = int(teCalc.getProperty(teCalcClass.L_PROP_NAME))
|
||||
optimisedLTau = int(teCalc.getProperty(teCalcClass.L_TAU_PROP_NAME))
|
||||
print(("TE(breath->heart) was %.3f nats for (heart embedding:) k=%d," + \
|
||||
"k_tau=%d, (breath embedding:) l=%d,l_tau=%d optimised via Ragwitz criteria") % \
|
||||
(teBreathToHeart, optimisedK, optimisedKTau, optimisedL, optimisedLTau))
|
||||
|
||||
# Next, embed the destination only using the Ragwitz criteria:
|
||||
teCalc.setProperty(teCalcClass.PROP_AUTO_EMBED_METHOD,
|
||||
teCalcClass.AUTO_EMBED_METHOD_RAGWITZ_DEST_ONLY)
|
||||
teCalc.setProperty(teCalcClass.PROP_K_SEARCH_MAX, "6")
|
||||
teCalc.setProperty(teCalcClass.PROP_TAU_SEARCH_MAX, "6")
|
||||
# Since we're only auto-embedding the destination, we supply
|
||||
# source embedding here (to overwrite the auto embeddings from above):
|
||||
teCalc.setProperty(teCalcClass.L_PROP_NAME, "1")
|
||||
teCalc.setProperty(teCalcClass.L_TAU_PROP_NAME, "1")
|
||||
# Since we're auto-embedding, no need to supply k and k_tau here:
|
||||
teCalc.initialise()
|
||||
# Compute TE from breath (column 1) to heart (column 0)
|
||||
teCalc.setObservations(breathRate, heartRate)
|
||||
teBreathToHeartDestEmbedding = teCalc.computeAverageLocalOfObservations()
|
||||
|
||||
# Check the auto-selected parameters and print out the result:
|
||||
optimisedK = int(teCalc.getProperty(teCalcClass.K_PROP_NAME))
|
||||
optimisedKTau = int(teCalc.getProperty(teCalcClass.K_TAU_PROP_NAME))
|
||||
print(("TE(breath->heart) was %.3f nats for (heart embedding:) k=%d," + \
|
||||
"k_tau=%d, optimised via Ragwitz criteria, plus (breath embedding:) l=1,l_tau=1") % \
|
||||
(teBreathToHeartDestEmbedding, optimisedK, optimisedKTau))
|
||||
|
||||
|
|
@ -19,15 +19,16 @@
|
|||
def readFloatsFile(filename):
|
||||
"Read a 2D array of floats from a given file"
|
||||
with open(filename) as f:
|
||||
# Space separate numbers, one time step per line, each column is a variable
|
||||
array = []
|
||||
for line in f: # read all lines
|
||||
if (line.startswith("%") or line.startswith("#")):
|
||||
# Assume this is a comment line
|
||||
continue
|
||||
if (len(line.split()) == 0):
|
||||
# Line is empty
|
||||
continue
|
||||
array.append([float(x) for x in line.split()])
|
||||
return array
|
||||
|
||||
# Space separate numbers, one time step per line, each column is a variable
|
||||
array = []
|
||||
for line in f:
|
||||
# read all lines
|
||||
if (line.startswith("%") or line.startswith("#")):
|
||||
# Assume this is a comment line
|
||||
continue
|
||||
if (len(line.split()) == 0):
|
||||
# Line is empty
|
||||
continue
|
||||
array.append([float(x) for x in line.split()])
|
||||
return array
|
||||
|
||||
|
|
|
|||
|
|
@ -19,15 +19,16 @@
|
|||
def readIntsFile(filename):
|
||||
"Read a 2D array of int from a given file"
|
||||
with open(filename) as f:
|
||||
# Space separate numbers, one time step per line, each column is a variable
|
||||
array = []
|
||||
for line in f: # read all lines
|
||||
if (line.startswith("%") or line.startswith("#")):
|
||||
# Assume this is a comment line
|
||||
continue
|
||||
if (len(line.split()) == 0):
|
||||
# Line is empty
|
||||
continue
|
||||
array.append([int(x) for x in line.split()])
|
||||
return array
|
||||
|
||||
# Space separate numbers, one time step per line, each column is a variable
|
||||
array = []
|
||||
for line in f:
|
||||
# read all lines
|
||||
if (line.startswith("%") or line.startswith("#")):
|
||||
# Assume this is a comment line
|
||||
continue
|
||||
if (len(line.split()) == 0):
|
||||
# Line is empty
|
||||
continue
|
||||
array.append([int(x) for x in line.split()])
|
||||
return array
|
||||
|
||||
|
|
|
|||
17
java/source/infodynamics/measures/continuous/ActiveInfoStorageCalculator.java
Executable file → Normal file
17
java/source/infodynamics/measures/continuous/ActiveInfoStorageCalculator.java
Executable file → Normal file
|
|
@ -231,6 +231,22 @@ public interface ActiveInfoStorageCalculator {
|
|||
public void setObservations(double[] observations,
|
||||
boolean[] valid) throws Exception;
|
||||
|
||||
/**
|
||||
* Add more time-series for the computation of the PDF,
|
||||
* subject to the validity of each sample in that series.
|
||||
* The array observations must not be over-written by the user
|
||||
* until after finaliseAddObservations() has been called.
|
||||
*
|
||||
* @param observations time-series array of (univariate) samples,
|
||||
* where the array index is time.
|
||||
* @param valid a time series (with indices the same as observations)
|
||||
* indicating whether the entry in observations at that index is valid;
|
||||
* we only take vectors as samples to add to the observation set where
|
||||
* all points in the time series (even between points in
|
||||
* the embedded k-vector with embedding delays) are valid.
|
||||
*/
|
||||
public void addObservations(double[] observations, boolean[] valid) throws Exception;
|
||||
|
||||
/**
|
||||
* Compute the AIS from the previously-supplied samples.
|
||||
*
|
||||
|
|
@ -377,4 +393,5 @@ public interface ActiveInfoStorageCalculator {
|
|||
* @throws Exception
|
||||
*/
|
||||
public int getNumObservations() throws Exception;
|
||||
|
||||
}
|
||||
|
|
|
|||
10
java/source/infodynamics/measures/continuous/ActiveInfoStorageCalculatorViaMutualInfo.java
Executable file → Normal file
10
java/source/infodynamics/measures/continuous/ActiveInfoStorageCalculatorViaMutualInfo.java
Executable file → Normal file
|
|
@ -268,6 +268,16 @@ public class ActiveInfoStorageCalculatorViaMutualInfo implements
|
|||
vectorOfObservationTimeSeries.add(observations);
|
||||
vectorOfValidityOfObservations.add(null); // All observations were valid
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see infodynamics.measures.continuous.ActiveInfoStorageCalculator#addObservations(double[])
|
||||
*/
|
||||
@Override
|
||||
public void addObservations(double[] observations, boolean[] valid) throws Exception {
|
||||
// Store these observations in our vector for now
|
||||
vectorOfObservationTimeSeries.add(observations);
|
||||
vectorOfValidityOfObservations.add(valid); // All observations were valid
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected method to internally parse and submit observations through
|
||||
|
|
|
|||
|
|
@ -134,6 +134,39 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
|
|||
public static final String PROP_NORMALISE = "NORMALISE";
|
||||
protected boolean normalise = true;
|
||||
|
||||
/**
|
||||
* Cache for the means of each dimension in variable 1, in case we need to normalise
|
||||
* new observations later
|
||||
*/
|
||||
protected double[] var1Means = null;
|
||||
/**
|
||||
* Cache for the standard deviations of each dimension in variable 1, in case we need to normalise
|
||||
* new observations later
|
||||
*/
|
||||
protected double[] var1Stds = null;
|
||||
/**
|
||||
* Cache for the means of each dimension in variable 2, in case we need to normalise
|
||||
* new observations later
|
||||
*/
|
||||
protected double[] var2Means = null;
|
||||
/**
|
||||
* Cache for the standard deviations of each dimension in variable 2, in case we need to normalise
|
||||
* new observations later
|
||||
*/
|
||||
protected double[] var2Stds = null;
|
||||
/**
|
||||
* Cache for the means of each dimension in conditional variable, in case we need to normalise
|
||||
* new observations later
|
||||
*/
|
||||
protected double[] condMeans = null;
|
||||
/**
|
||||
* Cache for the standard deviations of each dimension in conditional variable, in case we need to normalise
|
||||
* new observations later
|
||||
*/
|
||||
protected double[] condStds = null;
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void initialise(int var1Dimensions, int var2Dimensions, int condDimensions) {
|
||||
dimensionsVar1 = var1Dimensions;
|
||||
|
|
@ -146,6 +179,12 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
|
|||
var2Observations = null;
|
||||
condObservations = null;
|
||||
addedMoreThanOneObservationSet = false;
|
||||
var1Means = null;
|
||||
var1Stds = null;
|
||||
var2Means = null;
|
||||
var2Stds = null;
|
||||
condMeans = null;
|
||||
condStds = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -159,7 +198,8 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
|
|||
* <li>{@link #PROP_NORMALISE} - whether to normalise the individual
|
||||
* variables to mean 0, standard deviation 1
|
||||
* (true by default, except for child class
|
||||
* {@link infodynamics.measures.continuous.gaussian.ConditionalMutualInfoCalculatorMultiVariateGaussian})</li>
|
||||
* {@link infodynamics.measures.continuous.gaussian.ConditionalMutualInfoCalculatorMultiVariateGaussian}
|
||||
* for which this property is false and cannot be altered)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Unknown property values are ignored.</p>
|
||||
|
|
@ -208,7 +248,8 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
|
|||
// startAddObservations was not called first
|
||||
throw new RuntimeException("User did not call startAddObservations before addObservations");
|
||||
}
|
||||
if ((var1.length != var2.length) || (var1.length != cond.length)) {
|
||||
if ((var1.length != var2.length) ||
|
||||
((dimensionsCond != 0) && (var1.length != cond.length))) {
|
||||
throw new Exception(String.format("Observation vector lengths (%d, %d and %d) must match!",
|
||||
var1.length, var2.length, cond.length));
|
||||
}
|
||||
|
|
@ -220,7 +261,7 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
|
|||
throw new Exception("Number of joint variables in var2 data " +
|
||||
"does not match the initialised value");
|
||||
}
|
||||
if (cond[0].length != dimensionsCond) {
|
||||
if ((dimensionsCond != 0) && (cond[0].length != dimensionsCond)) {
|
||||
throw new Exception("Number of joint variables in cond data " +
|
||||
"does not match the initialised value");
|
||||
}
|
||||
|
|
@ -244,8 +285,11 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
|
|||
System.arraycopy(var1, startTime, var1ToAdd, 0, numTimeSteps);
|
||||
double[][] var2ToAdd = new double[numTimeSteps][];
|
||||
System.arraycopy(var2, startTime, var2ToAdd, 0, numTimeSteps);
|
||||
double[][] condToAdd = new double[numTimeSteps][];
|
||||
System.arraycopy(cond, startTime, condToAdd, 0, numTimeSteps);
|
||||
double[][] condToAdd = null;
|
||||
if (dimensionsCond != 0) {
|
||||
condToAdd = new double[numTimeSteps][];
|
||||
System.arraycopy(cond, startTime, condToAdd, 0, numTimeSteps);
|
||||
}
|
||||
addObservations(var1ToAdd, var2ToAdd, condToAdd);
|
||||
}
|
||||
|
||||
|
|
@ -317,17 +361,27 @@ public abstract class ConditionalMutualInfoMultiVariateCommon implements
|
|||
MatrixUtils.arrayCopy(var2, 0, 0,
|
||||
var2Observations, startObservation, 0,
|
||||
var2.length, dimensionsVar2);
|
||||
MatrixUtils.arrayCopy(cond, 0, 0,
|
||||
if (dimensionsCond != 0) {
|
||||
MatrixUtils.arrayCopy(cond, 0, 0,
|
||||
condObservations, startObservation, 0,
|
||||
cond.length, dimensionsCond);
|
||||
} // else we can do nothing there
|
||||
startObservation += var2.length;
|
||||
}
|
||||
|
||||
// Normalise the data if required
|
||||
if (normalise) {
|
||||
MatrixUtils.normalise(var1Observations);
|
||||
MatrixUtils.normalise(var2Observations);
|
||||
MatrixUtils.normalise(condObservations);
|
||||
var1Means = MatrixUtils.means(var1Observations);
|
||||
var1Stds = MatrixUtils.stdDevs(var1Observations, var1Means);
|
||||
MatrixUtils.normalise(var1Observations, var1Means, var1Stds);
|
||||
var2Means = MatrixUtils.means(var2Observations);
|
||||
var2Stds = MatrixUtils.stdDevs(var2Observations, var2Means);
|
||||
MatrixUtils.normalise(var2Observations, var2Means, var2Stds);
|
||||
if (dimensionsCond != 0) {
|
||||
condMeans = MatrixUtils.means(condObservations);
|
||||
condStds = MatrixUtils.stdDevs(condObservations, condMeans);
|
||||
MatrixUtils.normalise(condObservations, condMeans, condStds);
|
||||
}
|
||||
}
|
||||
|
||||
// We don't need to keep the vectors of observation sets anymore:
|
||||
|
|
|
|||
|
|
@ -196,222 +196,238 @@ public class TransferEntropyCalculatorMultiVariateViaCondMutualInfo
|
|||
condMiCalc.initialise(l*sourceDimensions, destDimensions, k*destDimensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets a single set of <b>univariate</b> observations to compute the PDFs from.
|
||||
* Can only be called on this multivariate calculator if the dimensions
|
||||
* of both source and destination are 1, otherwise throws an exception</p>
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param source univariate observations for the source variable
|
||||
* @param destination univariate observations for the destination variable
|
||||
* @throws Exception if initialised dimensions were not 1
|
||||
*/
|
||||
@Override
|
||||
public void setObservations(double[] source, double[] destination) throws Exception {
|
||||
|
||||
if ((sourceDimensions != 1) || (destDimensions != 1)) {
|
||||
throw new Exception("Cannot call the univariate setObservations if you " +
|
||||
"have initialised with dimension > 1 for either source or destination");
|
||||
}
|
||||
|
||||
super.setObservations(source, destination);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see infodynamics.measures.continuous.ChannelCalculatorMultiVariate#setObservations(double[][], double[][])
|
||||
*/
|
||||
@Override
|
||||
public void setObservations(double[][] source, double[][] destination)
|
||||
throws Exception {
|
||||
if (source.length != destination.length) {
|
||||
throw new Exception(String.format("Source and destination lengths (%d and %d) must match!",
|
||||
source.length, destination.length));
|
||||
}
|
||||
if (source.length < startTimeForFirstDestEmbedding + 2) {
|
||||
// There are no observations to add here, the time series is too short
|
||||
throw new Exception("Not enough observations to set here given k, k_tau, l, l_tau and delay parameters");
|
||||
}
|
||||
double[][] currentDestPastVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(destination, k, k_tau,
|
||||
startTimeForFirstDestEmbedding,
|
||||
destination.length - startTimeForFirstDestEmbedding - 1);
|
||||
double[][] currentDestNextVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(destination, 1,
|
||||
startTimeForFirstDestEmbedding + 1,
|
||||
destination.length - startTimeForFirstDestEmbedding - 1);
|
||||
double[][] currentSourcePastVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(source, l, l_tau,
|
||||
startTimeForFirstDestEmbedding + 1 - delay,
|
||||
source.length - startTimeForFirstDestEmbedding - 1);
|
||||
condMiCalc.setObservations(currentSourcePastVectors, currentDestNextVectors, currentDestPastVectors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startAddObservations() {
|
||||
if ((sourceDimensions == 1) && (destDimensions == 1)) {
|
||||
super.startAddObservations();
|
||||
} else {
|
||||
condMiCalc.startAddObservations();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finaliseAddObservations() throws Exception {
|
||||
if ((sourceDimensions == 1) && (destDimensions == 1)) {
|
||||
super.finaliseAddObservations();
|
||||
} else {
|
||||
condMiCalc.finaliseAddObservations();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds a new set of <b>univariate</b> observations to compute the PDFs from.
|
||||
* Can only be called on this multivariate calculator if the dimensions
|
||||
* of both source and destination are 1, otherwise throws an exception</p>
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param source univariate observations for the source variable
|
||||
* @param destination univariate observations for the destination variable
|
||||
* @throws Exception if initialised dimensions were not 1
|
||||
* @see {@link ChannelCalculator#addObservations(double[], double[])}
|
||||
*/
|
||||
@Override
|
||||
public void addObservations(double[] source, double[] destination) throws Exception {
|
||||
|
||||
if ((sourceDimensions != 1) || (destDimensions != 1)) {
|
||||
throw new Exception("Cannot call the univariate addObservations if you " +
|
||||
"have initialised with dimension > 1 for either source or destination");
|
||||
}
|
||||
super.addObservations(source, destination);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see infodynamics.measures.continuous.ChannelCalculatorMultiVariate#addObservations(double[][], double[][])
|
||||
*/
|
||||
@Override
|
||||
public void addObservations(double[][] source, double[][] destination)
|
||||
throws Exception {
|
||||
if (source.length != destination.length) {
|
||||
throw new Exception(String.format("Source and destination lengths (%d and %d) must match!",
|
||||
source.length, destination.length));
|
||||
}
|
||||
if (source.length < startTimeForFirstDestEmbedding + 2) {
|
||||
// There are no observations to add here, the time series is too short
|
||||
// Don't throw an exception, do nothing since more observations
|
||||
// can be added later.
|
||||
return;
|
||||
}
|
||||
double[][] currentDestPastVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(destination, k, k_tau,
|
||||
startTimeForFirstDestEmbedding,
|
||||
destination.length - startTimeForFirstDestEmbedding - 1);
|
||||
double[][] currentDestNextVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(destination, 1,
|
||||
startTimeForFirstDestEmbedding + 1,
|
||||
destination.length - startTimeForFirstDestEmbedding - 1);
|
||||
double[][] currentSourcePastVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(source, l, l_tau,
|
||||
startTimeForFirstDestEmbedding + 1 - delay,
|
||||
source.length - startTimeForFirstDestEmbedding - 1);
|
||||
condMiCalc.addObservations(currentSourcePastVectors, currentDestNextVectors, currentDestPastVectors);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds a new sub-series of <b>univariate</b> observations to compute the PDFs from.
|
||||
* Can only be called on this multivariate calculator if the dimensions
|
||||
* of both source and destination are 1, otherwise throws an exception</p>
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param source univariate observations for the source variable
|
||||
* @param destination univariate observations for the destination variable
|
||||
* @throws Exception if initialised dimensions were not 1
|
||||
* @see {@link ChannelCalculator#addObservations(double[], double[], int, int)}
|
||||
*/
|
||||
@Override
|
||||
public void addObservations(double[] source, double[] destination,
|
||||
int startTime, int numTimeSteps) throws Exception {
|
||||
if ((sourceDimensions != 1) || (destDimensions != 1)) {
|
||||
throw new Exception("Cannot call the univariate addObservations if you " +
|
||||
"have initialised with dimension > 1 for either source or destination");
|
||||
}
|
||||
super.addObservations(source, destination, startTime, numTimeSteps);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see infodynamics.measures.continuous.ChannelCalculatorMultiVariate#addObservations(double[][], double[][], int, int)
|
||||
*/
|
||||
@Override
|
||||
public void addObservations(double[][] source, double[][] destination,
|
||||
int startTime, int numTimeSteps) throws Exception {
|
||||
if (source.length != destination.length) {
|
||||
throw new Exception(String.format("Source and destination lengths (%d and %d) must match!",
|
||||
source.length, destination.length));
|
||||
}
|
||||
if (source.length < startTime + numTimeSteps) {
|
||||
// There are not enough observations given the arguments here
|
||||
throw new Exception("Not enough observations to set here given startTime and numTimeSteps parameters");
|
||||
}
|
||||
addObservations(MatrixUtils.selectRows(source, startTime, numTimeSteps),
|
||||
MatrixUtils.selectRows(destination, startTime, numTimeSteps));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds a new sub-series of <b>univariate</b> observations to compute the PDFs from.
|
||||
* Can only be called on this multivariate calculator if the dimensions
|
||||
* of both source and destination are 1, otherwise throws an exception</p>
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param source univariate observations for the source variable
|
||||
* @param destination univariate observations for the destination variable
|
||||
* @throws Exception if initialised dimensions were not 1
|
||||
* @see {@link ChannelCalculator#setObservations(double[], double[], boolean[], boolean[])}
|
||||
*/
|
||||
public void setObservations(double[] source, double[] destination,
|
||||
boolean[] sourceValid, boolean[] destValid) throws Exception {
|
||||
|
||||
if ((sourceDimensions != 1) || (destDimensions != 1)) {
|
||||
throw new Exception("Cannot call the univariate setObservations if you " +
|
||||
"have initialised with dimension > 1 for either source or destination");
|
||||
}
|
||||
super.setObservations(source, destination, sourceValid, destValid);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see infodynamics.measures.continuous.ChannelCalculatorMultiVariate#setObservations(double[][], double[][], boolean[], boolean[])
|
||||
*/
|
||||
@Override
|
||||
public void setObservations(double[][] source, double[][] destination,
|
||||
boolean[] sourceValid, boolean[] destValid) throws Exception {
|
||||
|
||||
Vector<int[]> startAndEndTimePairs = computeStartAndEndTimePairs(sourceValid, destValid);
|
||||
|
||||
// We've found the set of start and end times for this pair
|
||||
startAddObservations();
|
||||
for (int[] timePair : startAndEndTimePairs) {
|
||||
int startTime = timePair[0];
|
||||
int endTime = timePair[1];
|
||||
addObservations(source, destination, startTime, endTime - startTime + 1);
|
||||
}
|
||||
finaliseAddObservations();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see infodynamics.measures.continuous.ChannelCalculatorMultiVariate#setObservations(double[][], double[][], boolean[][], boolean[][])
|
||||
*/
|
||||
@Override
|
||||
public void setObservations(double[][] source, double[][] destination,
|
||||
boolean[][] sourceValid, boolean[][] destValid) throws Exception {
|
||||
boolean[] jointSourceValid = MatrixUtils.andRows(sourceValid);
|
||||
boolean[] jointDestValid = MatrixUtils.andRows(destValid);
|
||||
setObservations(source, destination, jointSourceValid, jointDestValid);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see infodynamics.measures.continuous.ChannelCalculatorMultiVariate#setObservations(double[][], double[][])
|
||||
*/
|
||||
@Override
|
||||
public void setObservations(double[][] source, double[][] destination)
|
||||
throws Exception {
|
||||
if (source.length != destination.length) {
|
||||
throw new Exception(String.format("Source and destination lengths (%d and %d) must match!",
|
||||
source.length, destination.length));
|
||||
}
|
||||
if ((sourceDimensions == 1) && (destDimensions == 1)) {
|
||||
// We'll be using the superclass for the computation
|
||||
super.setObservations(MatrixUtils.selectColumn(source, 0),
|
||||
MatrixUtils.selectColumn(destination, 0));
|
||||
return;
|
||||
}
|
||||
if (source.length < startTimeForFirstDestEmbedding + 2) {
|
||||
// There are no observations to add here, the time series is too short
|
||||
throw new Exception("Not enough observations to set here given k, k_tau, l, l_tau and delay parameters");
|
||||
}
|
||||
double[][] currentDestPastVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(destination, k, k_tau,
|
||||
startTimeForFirstDestEmbedding,
|
||||
destination.length - startTimeForFirstDestEmbedding - 1);
|
||||
double[][] currentDestNextVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(destination, 1,
|
||||
startTimeForFirstDestEmbedding + 1,
|
||||
destination.length - startTimeForFirstDestEmbedding - 1);
|
||||
double[][] currentSourcePastVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(source, l, l_tau,
|
||||
startTimeForFirstDestEmbedding + 1 - delay,
|
||||
source.length - startTimeForFirstDestEmbedding - 1);
|
||||
condMiCalc.setObservations(currentSourcePastVectors, currentDestNextVectors, currentDestPastVectors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startAddObservations() {
|
||||
if ((sourceDimensions == 1) && (destDimensions == 1)) {
|
||||
// We'll be using the superclass for the computation
|
||||
super.startAddObservations();
|
||||
return;
|
||||
}
|
||||
// Otherwise initialise ourselves:
|
||||
condMiCalc.startAddObservations();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds a new set of <b>univariate</b> observations to compute the PDFs from.
|
||||
* Can only be called on this multivariate calculator if the dimensions
|
||||
* of both source and destination are 1, otherwise throws an exception</p>
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param source univariate observations for the source variable
|
||||
* @param destination univariate observations for the destination variable
|
||||
* @throws Exception if initialised dimensions were not 1
|
||||
* @see {@link ChannelCalculator#addObservations(double[], double[])}
|
||||
*/
|
||||
@Override
|
||||
public void addObservations(double[] source, double[] destination) throws Exception {
|
||||
|
||||
if ((sourceDimensions != 1) || (destDimensions != 1)) {
|
||||
throw new Exception("Cannot call the univariate addObservations if you " +
|
||||
"have initialised with dimension > 1 for either source or destination");
|
||||
}
|
||||
super.addObservations(source, destination);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see infodynamics.measures.continuous.ChannelCalculatorMultiVariate#addObservations(double[][], double[][])
|
||||
*/
|
||||
@Override
|
||||
public void addObservations(double[][] source, double[][] destination)
|
||||
throws Exception {
|
||||
if (source.length != destination.length) {
|
||||
throw new Exception(String.format("Source and destination lengths (%d and %d) must match!",
|
||||
source.length, destination.length));
|
||||
}
|
||||
if ((sourceDimensions == 1) && (destDimensions == 1)) {
|
||||
// We'll be using the superclass for the computation
|
||||
super.addObservations(MatrixUtils.selectColumn(source, 0),
|
||||
MatrixUtils.selectColumn(destination, 0));
|
||||
return;
|
||||
}
|
||||
if (source.length < startTimeForFirstDestEmbedding + 2) {
|
||||
// There are no observations to add here, the time series is too short
|
||||
// Don't throw an exception, do nothing since more observations
|
||||
// can be added later.
|
||||
return;
|
||||
}
|
||||
double[][] currentDestPastVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(destination, k, k_tau,
|
||||
startTimeForFirstDestEmbedding,
|
||||
destination.length - startTimeForFirstDestEmbedding - 1);
|
||||
double[][] currentDestNextVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(destination, 1,
|
||||
startTimeForFirstDestEmbedding + 1,
|
||||
destination.length - startTimeForFirstDestEmbedding - 1);
|
||||
double[][] currentSourcePastVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(source, l, l_tau,
|
||||
startTimeForFirstDestEmbedding + 1 - delay,
|
||||
source.length - startTimeForFirstDestEmbedding - 1);
|
||||
condMiCalc.addObservations(currentSourcePastVectors, currentDestNextVectors, currentDestPastVectors);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds a new sub-series of <b>univariate</b> observations to compute the PDFs from.
|
||||
* Can only be called on this multivariate calculator if the dimensions
|
||||
* of both source and destination are 1, otherwise throws an exception</p>
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param source univariate observations for the source variable
|
||||
* @param destination univariate observations for the destination variable
|
||||
* @throws Exception if initialised dimensions were not 1
|
||||
* @see {@link ChannelCalculator#addObservations(double[], double[], int, int)}
|
||||
*/
|
||||
@Override
|
||||
public void addObservations(double[] source, double[] destination,
|
||||
int startTime, int numTimeSteps) throws Exception {
|
||||
if ((sourceDimensions != 1) || (destDimensions != 1)) {
|
||||
throw new Exception("Cannot call the univariate addObservations if you " +
|
||||
"have initialised with dimension > 1 for either source or destination");
|
||||
}
|
||||
super.addObservations(source, destination, startTime, numTimeSteps);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see infodynamics.measures.continuous.ChannelCalculatorMultiVariate#addObservations(double[][], double[][], int, int)
|
||||
*/
|
||||
@Override
|
||||
public void addObservations(double[][] source, double[][] destination,
|
||||
int startTime, int numTimeSteps) throws Exception {
|
||||
if (source.length != destination.length) {
|
||||
throw new Exception(String.format("Source and destination lengths (%d and %d) must match!",
|
||||
source.length, destination.length));
|
||||
}
|
||||
if ((sourceDimensions == 1) && (destDimensions == 1)) {
|
||||
// We'll be using the superclass for the computation
|
||||
super.addObservations(MatrixUtils.selectColumn(source, 0),
|
||||
MatrixUtils.selectColumn(destination, 0),
|
||||
startTime, numTimeSteps);
|
||||
return;
|
||||
}
|
||||
if (source.length < startTime + numTimeSteps) {
|
||||
// There are not enough observations given the arguments here
|
||||
throw new Exception("Not enough observations to set here given startTime and numTimeSteps parameters");
|
||||
}
|
||||
addObservations(MatrixUtils.selectRows(source, startTime, numTimeSteps),
|
||||
MatrixUtils.selectRows(destination, startTime, numTimeSteps));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Adds a new sub-series of <b>univariate</b> observations to compute the PDFs from.
|
||||
* Can only be called on this multivariate calculator if the dimensions
|
||||
* of both source and destination are 1, otherwise throws an exception</p>
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param source univariate observations for the source variable
|
||||
* @param destination univariate observations for the destination variable
|
||||
* @throws Exception if initialised dimensions were not 1
|
||||
* @see {@link ChannelCalculator#setObservations(double[], double[], boolean[], boolean[])}
|
||||
*/
|
||||
public void setObservations(double[] source, double[] destination,
|
||||
boolean[] sourceValid, boolean[] destValid) throws Exception {
|
||||
|
||||
if ((sourceDimensions != 1) || (destDimensions != 1)) {
|
||||
throw new Exception("Cannot call the univariate setObservations if you " +
|
||||
"have initialised with dimension > 1 for either source or destination");
|
||||
}
|
||||
super.setObservations(source, destination, sourceValid, destValid);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see infodynamics.measures.continuous.ChannelCalculatorMultiVariate#setObservations(double[][], double[][], boolean[], boolean[])
|
||||
*/
|
||||
@Override
|
||||
public void setObservations(double[][] source, double[][] destination,
|
||||
boolean[] sourceValid, boolean[] destValid) throws Exception {
|
||||
|
||||
if ((sourceDimensions == 1) && (destDimensions == 1)) {
|
||||
// We'll be using the superclass for the computation
|
||||
super.setObservations(MatrixUtils.selectColumn(source, 0),
|
||||
MatrixUtils.selectColumn(destination, 0),
|
||||
sourceValid, destValid);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<int[]> startAndEndTimePairs = computeStartAndEndTimePairs(sourceValid, destValid);
|
||||
|
||||
// We've found the set of start and end times for this pair
|
||||
startAddObservations();
|
||||
for (int[] timePair : startAndEndTimePairs) {
|
||||
int startTime = timePair[0];
|
||||
int endTime = timePair[1];
|
||||
addObservations(source, destination, startTime, endTime - startTime + 1);
|
||||
}
|
||||
finaliseAddObservations();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see infodynamics.measures.continuous.ChannelCalculatorMultiVariate#setObservations(double[][], double[][], boolean[][], boolean[][])
|
||||
*/
|
||||
@Override
|
||||
public void setObservations(double[][] source, double[][] destination,
|
||||
boolean[][] sourceValid, boolean[][] destValid) throws Exception {
|
||||
|
||||
if ((sourceDimensions == 1) && (destDimensions == 1)) {
|
||||
// We'll be using the superclass for the computation
|
||||
super.setObservations(MatrixUtils.selectColumn(source, 0),
|
||||
MatrixUtils.selectColumn(destination, 0),
|
||||
MatrixUtils.selectColumn(sourceValid, 0),
|
||||
MatrixUtils.selectColumn(destValid, 0));
|
||||
return;
|
||||
}
|
||||
boolean[] jointSourceValid = MatrixUtils.andRows(sourceValid);
|
||||
boolean[] jointDestValid = MatrixUtils.andRows(destValid);
|
||||
setObservations(source, destination, jointSourceValid, jointDestValid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finaliseAddObservations() throws Exception {
|
||||
if ((sourceDimensions == 1) && (destDimensions == 1)) {
|
||||
// We'll be using the superclass for the computation
|
||||
super.finaliseAddObservations();
|
||||
return;
|
||||
}
|
||||
// Otherwise finalise ourselves:
|
||||
condMiCalc.finaliseAddObservations();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Computes the local values of the transfer entropy
|
||||
* for each valid observation in the supplied univariate observations
|
||||
|
|
|
|||
|
|
@ -125,6 +125,13 @@ public class TransferEntropyCalculatorViaCondMutualInfo implements
|
|||
*/
|
||||
protected Vector<boolean[]> vectorOfValidityOfDestination;
|
||||
|
||||
/**
|
||||
* Array of the number of observations added by each separate call to
|
||||
* {@link #addObservations(double[], double[])}, in order of which those calls
|
||||
* were made. This is returned by {@link #getSeparateNumObservations()}
|
||||
*/
|
||||
protected int[] separateNumObservations;
|
||||
|
||||
/**
|
||||
* Construct a transfer entropy calculator using an instance of
|
||||
* condMiCalculatorClassName as the underlying conditional mutual information calculator.
|
||||
|
|
@ -228,6 +235,7 @@ public class TransferEntropyCalculatorViaCondMutualInfo implements
|
|||
vectorOfDestinationTimeSeries = null;
|
||||
vectorOfValidityOfSource = null;
|
||||
vectorOfValidityOfDestination = null;
|
||||
separateNumObservations = new int[] {};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -342,9 +350,10 @@ public class TransferEntropyCalculatorViaCondMutualInfo implements
|
|||
*
|
||||
* @param source time series of source observations
|
||||
* @param destination time series of destination observations
|
||||
* @return the number of observations added
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void addObservationsAfterParamsDetermined(double[] source, double[] destination) throws Exception {
|
||||
protected int addObservationsAfterParamsDetermined(double[] source, double[] destination) throws Exception {
|
||||
if (source.length != destination.length) {
|
||||
throw new Exception(String.format("Source and destination lengths (%d and %d) must match!",
|
||||
source.length, destination.length));
|
||||
|
|
@ -353,7 +362,7 @@ public class TransferEntropyCalculatorViaCondMutualInfo implements
|
|||
// There are no observations to add here, the time series is too short
|
||||
// Don't throw an exception, do nothing since more observations
|
||||
// can be added later.
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
double[][] currentDestPastVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(destination, k, k_tau,
|
||||
|
|
@ -368,6 +377,7 @@ public class TransferEntropyCalculatorViaCondMutualInfo implements
|
|||
startTimeForFirstDestEmbedding + 1 - delay,
|
||||
source.length - startTimeForFirstDestEmbedding - 1);
|
||||
condMiCalc.addObservations(currentSourcePastVectors, currentDestNextVectors, currentDestPastVectors);
|
||||
return destination.length - startTimeForFirstDestEmbedding - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -384,21 +394,24 @@ public class TransferEntropyCalculatorViaCondMutualInfo implements
|
|||
* the source at that index is valid.
|
||||
* @param destValid array (with indices the same as destination) indicating whether
|
||||
* the destination at that index is valid.
|
||||
* @return total number of observations added
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void addObservationsAfterParamsDetermined(double[] source, double[] destination,
|
||||
protected int addObservationsAfterParamsDetermined(double[] source, double[] destination,
|
||||
boolean[] sourceValid, boolean[] destValid) throws Exception {
|
||||
|
||||
// Compute the start and end time pairs using our embedding parameters:
|
||||
Vector<int[]> startAndEndTimePairs = computeStartAndEndTimePairs(sourceValid, destValid);
|
||||
|
||||
int totalObservationsAdded = 0;
|
||||
for (int[] timePair : startAndEndTimePairs) {
|
||||
int startTime = timePair[0];
|
||||
int endTime = timePair[1];
|
||||
addObservationsAfterParamsDetermined(
|
||||
totalObservationsAdded += addObservationsAfterParamsDetermined(
|
||||
MatrixUtils.select(source, startTime, endTime - startTime + 1),
|
||||
MatrixUtils.select(destination, startTime, endTime - startTime + 1));
|
||||
}
|
||||
return totalObservationsAdded;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -441,17 +454,21 @@ public class TransferEntropyCalculatorViaCondMutualInfo implements
|
|||
Iterator<double[]> destIterator = vectorOfDestinationTimeSeries.iterator();
|
||||
Iterator<boolean[]> sourceValidityIterator = vectorOfValidityOfSource.iterator();
|
||||
Iterator<boolean[]> destValidityIterator = vectorOfValidityOfDestination.iterator();
|
||||
separateNumObservations = new int[vectorOfDestinationTimeSeries.size()];
|
||||
int setNum = 0;
|
||||
for (double[] source : vectorOfSourceTimeSeries) {
|
||||
double[] destination = destIterator.next();
|
||||
boolean[] sourceValidity = sourceValidityIterator.next();
|
||||
boolean[] destValidity = destValidityIterator.next();
|
||||
int observationsAddedThisTime = 0;
|
||||
if (sourceValidity == null) {
|
||||
// Add the whole time-series
|
||||
addObservationsAfterParamsDetermined(source, destination);
|
||||
observationsAddedThisTime = addObservationsAfterParamsDetermined(source, destination);
|
||||
} else {
|
||||
addObservationsAfterParamsDetermined(source, destination,
|
||||
observationsAddedThisTime = addObservationsAfterParamsDetermined(source, destination,
|
||||
sourceValidity, destValidity);
|
||||
}
|
||||
separateNumObservations[setNum++] = observationsAddedThisTime;
|
||||
}
|
||||
vectorOfSourceTimeSeries = null; // No longer required
|
||||
vectorOfDestinationTimeSeries = null; // No longer required
|
||||
|
|
@ -642,6 +659,19 @@ public class TransferEntropyCalculatorViaCondMutualInfo implements
|
|||
return condMiCalc.getNumObservations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an array of the number of observations that
|
||||
* were added by each call to {@link #addObservations(double[], double[])} etc.,
|
||||
* in order of them being called.
|
||||
* The actual number of observations for each call is computed <b>after</b>
|
||||
* any auto-embedding is performed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int[] getSeparateNumObservations() {
|
||||
return separateNumObservations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAddedMoreThanOneObservationSet() {
|
||||
return condMiCalc.getAddedMoreThanOneObservationSet();
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ package infodynamics.measures.continuous.gaussian;
|
|||
import infodynamics.measures.continuous.ActiveInfoStorageCalculator;
|
||||
import infodynamics.measures.continuous.ActiveInfoStorageCalculatorViaMutualInfo;
|
||||
import infodynamics.measures.continuous.MutualInfoCalculatorMultiVariate;
|
||||
import infodynamics.utils.AnalyticNullDistributionComputer;
|
||||
import infodynamics.utils.ChiSquareMeasurementDistribution;
|
||||
|
||||
/**
|
||||
* An Active Information Storage (AIS) calculator (implementing {@link ActiveInfoStorageCalculator})
|
||||
|
|
@ -62,7 +64,8 @@ import infodynamics.measures.continuous.MutualInfoCalculatorMultiVariate;
|
|||
* @see MutualInfoCalculatorMultiVariateGaussian
|
||||
*/
|
||||
public class ActiveInfoStorageCalculatorGaussian
|
||||
extends ActiveInfoStorageCalculatorViaMutualInfo {
|
||||
extends ActiveInfoStorageCalculatorViaMutualInfo
|
||||
implements AnalyticNullDistributionComputer {
|
||||
|
||||
public static final String MI_CALCULATOR_GAUSSIAN = MutualInfoCalculatorMultiVariateGaussian.class.getName();
|
||||
|
||||
|
|
@ -76,4 +79,31 @@ public class ActiveInfoStorageCalculatorGaussian
|
|||
public ActiveInfoStorageCalculatorGaussian() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
|
||||
super(MI_CALCULATOR_GAUSSIAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an <b>analytic</b> distribution of what the AIS would look like,
|
||||
* under a null hypothesis that our variables had no relation.
|
||||
* This is performed without bootstrapping (which is done in
|
||||
* {@link #computeSignificance(int, int)} and {@link #computeSignificance(int, int[][])}).
|
||||
* The method is implemented using the corresponding method of the
|
||||
* underlying {@link MutualInfoCalculatorMultiVariateGaussian}
|
||||
*
|
||||
* <p>See Section II.E "Statistical significance testing" of
|
||||
* the JIDT paper below, and the other papers referenced in
|
||||
* {@link AnalyticNullDistributionComputer#computeSignificance()}
|
||||
* (in particular Brillinger and Geweke),
|
||||
* for a description of how this is done for MI.
|
||||
* Basically, the null distribution is a chi-square distribution.
|
||||
* </p>
|
||||
*
|
||||
* @return ChiSquareMeasurementDistribution object which describes
|
||||
* the proportion of AIS scores from the null distribution
|
||||
* which have higher or equal AISs to our actual value.
|
||||
* @see "J.T. Lizier, 'JIDT: An information-theoretic
|
||||
* toolkit for studying the dynamics of complex systems', 2014."
|
||||
* @throws Exception
|
||||
*/
|
||||
public ChiSquareMeasurementDistribution computeSignificance() {
|
||||
return ((MutualInfoCalculatorMultiVariateGaussian) miCalc).computeSignificance();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,6 +158,22 @@ public class ConditionalMutualInfoCalculatorMultiVariateGaussian
|
|||
var2IndicesInCovariance = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(String propertyName, String propertyValue) {
|
||||
if (propertyName.equalsIgnoreCase(PROP_NORMALISE)) {
|
||||
// We do not allow the user to alter this property away from false,
|
||||
// otherwise we would need to alter the functionality of the
|
||||
// local calculations, etc.
|
||||
return;
|
||||
}
|
||||
super.setProperty(propertyName, propertyValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object clone() throws CloneNotSupportedException {
|
||||
return super.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception if the observation variables are not linearly independent
|
||||
* (leading to a non-positive definite covariance matrix).
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ public class KernelEstimatorMultiVariate implements Cloneable {
|
|||
|
||||
protected double[] suppliedKernelWidths = null;
|
||||
protected double[] kernelWidthsInUse = null;
|
||||
protected double kernelVolumeInUse = 0;
|
||||
protected int dimensions = 1;
|
||||
private int[] bins = null;
|
||||
private boolean usingIntegerIndexBins = true;
|
||||
|
|
@ -196,6 +197,7 @@ public class KernelEstimatorMultiVariate implements Cloneable {
|
|||
// many bins:
|
||||
usingIntegerIndexBins = !forceCompareToAll;
|
||||
useBins = true;
|
||||
kernelVolumeInUse = 0;
|
||||
// Work out the max and min for each bin
|
||||
for (int d = 0; d < dimensions; d++) {
|
||||
mins[d] = MatrixUtils.min(data, d);
|
||||
|
|
@ -210,6 +212,11 @@ public class KernelEstimatorMultiVariate implements Cloneable {
|
|||
} else {
|
||||
kernelWidthsInUse[d] = suppliedKernelWidths[d];
|
||||
}
|
||||
if (d == 0) {
|
||||
kernelVolumeInUse = 2.0 * kernelWidthsInUse[d];
|
||||
} else {
|
||||
kernelVolumeInUse *= 2.0 * kernelWidthsInUse[d];
|
||||
}
|
||||
bins[d] = (int) Math.ceil((max - mins[d]) / kernelWidthsInUse[d]);
|
||||
if (bins[d] == 0) {
|
||||
// This means the min and max are exactly the same:
|
||||
|
|
@ -711,7 +718,7 @@ public class KernelEstimatorMultiVariate implements Cloneable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Give kernel estimated probability for this observation.
|
||||
* Give kernel estimated probability density for this observation.
|
||||
* Do this using bins, so we only need to compare to neighbouring bins
|
||||
* and of course add in the observations in this bin.
|
||||
* Achieves this by trawling through neighbouring bins in a recursive fashion.
|
||||
|
|
@ -725,8 +732,8 @@ public class KernelEstimatorMultiVariate implements Cloneable {
|
|||
boolean dynCorrExclusion) {
|
||||
|
||||
KernelCount kernelCount = getCountFromBins(observation, timeStep, dynCorrExclusion);
|
||||
|
||||
return (double) kernelCount.count / (double) kernelCount.totalObservationsForCount;
|
||||
// Dividing by kernelVolume also to get a density
|
||||
return (double) kernelCount.count / (double) kernelCount.totalObservationsForCount / kernelVolumeInUse;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -792,7 +799,7 @@ public class KernelEstimatorMultiVariate implements Cloneable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Give kernel estimated probability for this observation, by comparing to all
|
||||
* Give kernel estimated probability density for this observation, by comparing to all
|
||||
* given observations.
|
||||
*
|
||||
* @param observation
|
||||
|
|
@ -804,7 +811,8 @@ public class KernelEstimatorMultiVariate implements Cloneable {
|
|||
boolean dynCorrExclusion) {
|
||||
|
||||
KernelCount kernelCount = getCountComparingToAll(observation, timeStep, dynCorrExclusion);
|
||||
return (double) kernelCount.count / (double) kernelCount.totalObservationsForCount;
|
||||
// Dividing by kernelVolume also to get a density
|
||||
return (double) kernelCount.count / (double) kernelCount.totalObservationsForCount / kernelVolumeInUse;
|
||||
}
|
||||
|
||||
private int getBinIndex(double value, int dimension) {
|
||||
|
|
|
|||
|
|
@ -284,11 +284,17 @@ public class KernelEstimatorUniVariate {
|
|||
System.out.println("Count after upper bin " + (bin + 1) + " = " + count);
|
||||
}
|
||||
|
||||
// TODO Should we divide this result by the kernel width (in use)?
|
||||
// Should we divide this result by the kernel width (in use)?
|
||||
// Schreiber doesn't do this in the TE paper, but it is done in
|
||||
// the Kaiser and Schreiber paper. It won't matter to TE, but
|
||||
// will make a difference to individual entropy estimates.
|
||||
return (double) count / (double) totalTimePointsCompared;
|
||||
// Answer: YES we should. this makes it a proper density (for differential entropies)
|
||||
// so this has been included below.
|
||||
// (If you want to retain a (discrete) Shannon entropy, based on counts within a defined width then no)
|
||||
// ACTUALLY you divide by twice the kernel width I think (since this is the total range)
|
||||
// At least, I've validated that this gives the correct diff entropy for
|
||||
// the uniform distribution
|
||||
return (double) count / (double) totalTimePointsCompared / (double) (2.0 * kernelWidthInUse);
|
||||
}
|
||||
|
||||
private int getBinIndex(double value) {
|
||||
|
|
|
|||
|
|
@ -46,8 +46,10 @@ import infodynamics.utils.MatrixUtils;
|
|||
* (except {@link MutualInfoCalculatorMultiVariate#PROP_TIME_DIFF} as outlined
|
||||
* in {@link ActiveInfoStorageCalculatorViaMutualInfo#setProperty(String, String)}).
|
||||
* Embedding parameters may be automatically determined as per the Ragwitz criteria
|
||||
* by setting the property {@link #PROP_AUTO_EMBED_METHOD} to {@link #AUTO_EMBED_METHOD_RAGWITZ}
|
||||
* (plus additional parameter settings for this).</li>
|
||||
* by setting the property {@link #PROP_AUTO_EMBED_METHOD} to {@link #AUTO_EMBED_METHOD_RAGWITZ},
|
||||
* or as per the max. bias-corrected AIS criteria by
|
||||
* setting the property {@link #PROP_AUTO_EMBED_METHOD} to {@link #AUTO_EMBED_METHOD_MAX_CORR_AIS}
|
||||
* (plus additional parameter settings for these).</li>
|
||||
* <li>Computed values are in <b>nats</b>, not bits!</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
|
|
@ -60,6 +62,9 @@ import infodynamics.utils.MatrixUtils;
|
|||
* Information Sciences, vol. 208, pp. 39-54, 2012.</li>
|
||||
* <li>Ragwitz and Kantz, "Markov models from data by simple nonlinear time series
|
||||
* predictors in delay embedding spaces", Physical Review E, vol 65, 056201 (2002).</li>
|
||||
* <li>J. Garland, R. G. James, E. Bradley, <a href="http://dx.doi.org/10.1103/physreve.93.022221">
|
||||
* "Leveraging information storage to select forecast-optimal parameters for delay-coordinate reconstructions"</a>,
|
||||
* Physical Review E, Vol. 93 (2016), 022221, doi:</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
|
||||
|
|
@ -82,7 +87,9 @@ public class ActiveInfoStorageCalculatorKraskov
|
|||
public static final String MI_CALCULATOR_KRASKOV2 = MutualInfoCalculatorMultiVariateKraskov2.class.getName();
|
||||
|
||||
/**
|
||||
* Property name for the auto-embedding method. Defaults to {@link #AUTO_EMBED_METHOD_NONE}
|
||||
* Property name for the auto-embedding method. Defaults to {@link #AUTO_EMBED_METHOD_NONE}.
|
||||
* Other valid values are {@link #AUTO_EMBED_METHOD_RAGWITZ} or
|
||||
* {@link #AUTO_EMBED_METHOD_MAX_CORR_AIS}
|
||||
*/
|
||||
public static final String PROP_AUTO_EMBED_METHOD = "AUTO_EMBED_METHOD";
|
||||
/**
|
||||
|
|
@ -95,6 +102,12 @@ public class ActiveInfoStorageCalculatorKraskov
|
|||
* the Ragwitz optimisation technique should be used for automatic embedding
|
||||
*/
|
||||
public static final String AUTO_EMBED_METHOD_RAGWITZ = "RAGWITZ";
|
||||
/**
|
||||
* Valid value for the property {@link #PROP_AUTO_EMBED_METHOD} indicating that
|
||||
* the automatic embedding should be done by maximising the bias corrected
|
||||
* AIS (as per Garland et al. in the references above).
|
||||
*/
|
||||
public static final String AUTO_EMBED_METHOD_MAX_CORR_AIS = "MAX_CORR_AIS";
|
||||
/**
|
||||
* Internal variable tracking what type of auto embedding (if any)
|
||||
* we are using
|
||||
|
|
@ -201,8 +214,10 @@ public class ActiveInfoStorageCalculatorKraskov
|
|||
* automatically determines the embedding history length ({@link #K_PROP_NAME})
|
||||
* and embedding delay ({@link #TAU_PROP_NAME}). Default is {@link #AUTO_EMBED_METHOD_NONE} meaning
|
||||
* values are set manually; other accepted values include: {@link #AUTO_EMBED_METHOD_RAGWITZ} for use
|
||||
* of the Ragwitz criteria (searching up to {@link #PROP_K_SEARCH_MAX} and
|
||||
* {@link #PROP_TAU_SEARCH_MAX}). Use of any value other than {@link #AUTO_EMBED_METHOD_NONE}
|
||||
* of the Ragwitz criteria and {@link #AUTO_EMBED_METHOD_MAX_CORR_AIS} for using
|
||||
* the maz bias-corrected AIS criteria (both searching up to {@link #PROP_K_SEARCH_MAX} and
|
||||
* {@link #PROP_TAU_SEARCH_MAX}, as outlined by Garland et al. in the references list above).
|
||||
* Use of any value other than {@link #AUTO_EMBED_METHOD_NONE}
|
||||
* will lead to any previous settings for k and tau (via e.g. {@link #initialise(int, int)} or
|
||||
* auto-embedding during previous calculations) will be overwritten after observations
|
||||
* are supplied.</li>
|
||||
|
|
@ -297,11 +312,13 @@ public class ActiveInfoStorageCalculatorKraskov
|
|||
}
|
||||
// Else we need to auto embed
|
||||
|
||||
double bestPredictionError = Double.POSITIVE_INFINITY;
|
||||
// TODO Should make sure the rest of the code could handle k=0
|
||||
// as default if nothing can improve on this
|
||||
int k_candidate_best = 1;
|
||||
int tau_candidate_best = 1;
|
||||
|
||||
if (autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_RAGWITZ)) {
|
||||
double bestPredictionError = Double.POSITIVE_INFINITY;
|
||||
if (debug) {
|
||||
System.out.printf("Beginning Ragwitz auto-embedding with k_max=%d, tau_max=%d\n",
|
||||
k_search_max, tau_search_max);
|
||||
|
|
@ -358,13 +375,61 @@ public class ActiveInfoStorageCalculatorKraskov
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_MAX_CORR_AIS)) {
|
||||
double bestAIS = Double.NEGATIVE_INFINITY;
|
||||
if (debug) {
|
||||
System.out.printf("Beginning max bias corrected AIS auto-embedding with k_max=%d, tau_max=%d\n",
|
||||
k_search_max, tau_search_max);
|
||||
}
|
||||
|
||||
for (int k_candidate = 1; k_candidate <= k_search_max; k_candidate++) {
|
||||
for (int tau_candidate = 1; tau_candidate <= tau_search_max; tau_candidate++) {
|
||||
// Use our internal MI calculator in case it has any particular
|
||||
// properties we need to have been set already
|
||||
miCalc.initialise(k_candidate, 1);
|
||||
miCalc.startAddObservations();
|
||||
// Send all of the observations through:
|
||||
for (double[] observations : vectorOfObservationTimeSeries) {
|
||||
double[][] currentDestPastVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(observations, k_candidate,
|
||||
tau_candidate, (k_candidate-1)*tau_candidate,
|
||||
observations.length - (k_candidate-1)*tau_candidate - 1);
|
||||
double[][] currentDestNextVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(observations, 1,
|
||||
(k_candidate-1)*tau_candidate + 1,
|
||||
observations.length - (k_candidate-1)*tau_candidate - 1);
|
||||
miCalc.addObservations(currentDestPastVectors, currentDestNextVectors);
|
||||
}
|
||||
miCalc.finaliseAddObservations();
|
||||
// Now grab the AIS estimate here -- it's already bias corrected for
|
||||
double thisAIS = miCalc.computeAverageLocalOfObservations();
|
||||
if (debug) {
|
||||
System.out.printf("AIS for k=%d,tau=%d is %.3f\n",
|
||||
k_candidate, tau_candidate, thisAIS);
|
||||
}
|
||||
if (thisAIS > bestAIS) {
|
||||
// This parameter setting is the best so far:
|
||||
bestAIS = thisAIS;
|
||||
k_candidate_best = k_candidate;
|
||||
tau_candidate_best = tau_candidate;
|
||||
}
|
||||
if (k_candidate == 1) {
|
||||
// tau is irrelevant, so no point testing other values
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected value " + autoEmbeddingMethod +
|
||||
" for property " + PROP_AUTO_EMBED_METHOD);
|
||||
}
|
||||
|
||||
// Make sure the embedding length and delay are set here
|
||||
k = k_candidate_best;
|
||||
tau = tau_candidate_best;
|
||||
if (debug) {
|
||||
System.out.printf("Embedding parameters set to k=%d,tau=%d (for prediction error %.3f)\n",
|
||||
k, tau, bestPredictionError);
|
||||
System.out.printf("Embedding parameters set to k=%d,tau=%d\n",
|
||||
k, tau);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ import infodynamics.utils.UnivariateNearestNeighbourSearcher;
|
|||
* actually implement the two KSG algorithms.</p>
|
||||
*
|
||||
* <p>Crucially, the calculation is performed by examining
|
||||
* neighbours in the full joint space (as specified by Frenzel and Pompe)
|
||||
* neighbours in the full joint space (as specified by Frenzel and Pompe,
|
||||
* and later by Vejmelka and Paluš, and Vlachos and Kugiumtzis)
|
||||
* rather than two MI calculators.</p>
|
||||
*
|
||||
* <p>Usage is as per the paradigm outlined for {@link ConditionalMutualInfoCalculatorMultiVariate},
|
||||
|
|
@ -69,6 +70,12 @@ import infodynamics.utils.UnivariateNearestNeighbourSearcher;
|
|||
* <li>Frenzel and Pompe, <a href="http://dx.doi.org/10.1103/physrevlett.99.204101">
|
||||
* "Partial Mutual Information for Coupling Analysis of Multivariate Time Series"</a>,
|
||||
* Physical Review Letters, <b>99</b>, p. 204101+ (2007).</li>
|
||||
* <li>Vejmelka and Paluš, <a href="http://dx.doi.org/10.1103/physreve.77.026214">
|
||||
* "Inferring the directionality of coupling with conditional mutual information"</a>,
|
||||
* Physical Review E, <b>77</b>, 026214, (2008)</li>
|
||||
* <li>I. Vlachos and D. Kugiumtzis, <a href="http://dx.doi.org/10.1103/physreve.82.016207">
|
||||
* "Nonuniform state-space reconstruction and coupling detection"</a>,
|
||||
* Physical Review E, <b>82</b>, 016207, (2010)</li>
|
||||
* <li>Kraskov, A., Stoegbauer, H., Grassberger, P.,
|
||||
* <a href="http://dx.doi.org/10.1103/PhysRevE.69.066138">"Estimating mutual information"</a>,
|
||||
* Physical Review E 69, (2004) 066138.</li>
|
||||
|
|
@ -331,6 +338,7 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
var2Observations[r][c] +=
|
||||
random.nextGaussian()*noiseLevel;
|
||||
}
|
||||
// This next loop will only execute if dimensionsCond > 0
|
||||
for (int c = 0; c < dimensionsCond; c++) {
|
||||
condObservations[r][c] +=
|
||||
random.nextGaussian()*noiseLevel;
|
||||
|
|
@ -351,7 +359,7 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
public double computeAverageLocalOfObservations() throws Exception {
|
||||
// Compute the conditional MI
|
||||
double startTime = Calendar.getInstance().getTimeInMillis();
|
||||
lastAverage = computeFromObservations(false)[0];
|
||||
lastAverage = computeFromObservations(false, null)[0];
|
||||
condMiComputed = true;
|
||||
if (debug) {
|
||||
Calendar rightNow2 = Calendar.getInstance();
|
||||
|
|
@ -395,7 +403,7 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
var2Observations = MatrixUtils.extractSelectedTimePointsReusingArrays(originalData, reordering);
|
||||
}
|
||||
// Compute the conditional MI
|
||||
double newCondMI = computeFromObservations(false)[0];
|
||||
double newCondMI = computeFromObservations(false, null)[0];
|
||||
// restore original data
|
||||
kdTreeJoint = originalKdTreeJoint;
|
||||
if (variableToReorder == 1) {
|
||||
|
|
@ -412,7 +420,7 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
|
||||
@Override
|
||||
public double[] computeLocalOfPreviousObservations() throws Exception {
|
||||
double[] localValues = computeFromObservations(true);
|
||||
double[] localValues = computeFromObservations(true, null);
|
||||
lastAverage = MatrixUtils.mean(localValues);
|
||||
condMiComputed = true;
|
||||
return localValues;
|
||||
|
|
@ -424,10 +432,25 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
@Override
|
||||
public double[] computeLocalUsingPreviousObservations(double[][] states1,
|
||||
double[][] states2, double[][] condStates) throws Exception {
|
||||
// If implemented, will need to incorporate any normalisation here
|
||||
// (normalising the incoming data the same way the previously
|
||||
// supplied observations were normalised).
|
||||
throw new Exception("Local method not implemented yet");
|
||||
// Do normalisation of the incoming data if required:
|
||||
double[][] states1ToUse, states2ToUse, condStatesToUse;
|
||||
if (normalise) {
|
||||
states1ToUse = MatrixUtils.normaliseIntoNewArray(states1, var1Means, var1Stds);
|
||||
states2ToUse = MatrixUtils.normaliseIntoNewArray(states2, var2Means, var2Stds);
|
||||
if (dimensionsCond != 0) {
|
||||
condStatesToUse = MatrixUtils.normaliseIntoNewArray(condStates, condMeans, condStds);
|
||||
} else {
|
||||
condStatesToUse = null;
|
||||
}
|
||||
} else {
|
||||
states1ToUse = states1;
|
||||
states2ToUse = states2;
|
||||
condStatesToUse = condStates;
|
||||
}
|
||||
// And call the algorithm:
|
||||
double[] localValues = computeFromObservations(true,
|
||||
new double[][][]{states1ToUse, states2ToUse, condStatesToUse});
|
||||
return localValues;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -444,10 +467,12 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
*
|
||||
* @param returnLocals whether to return an array or local values, or else
|
||||
* sums of these values
|
||||
* @param newObservations set to null for computing for the observation set for the PDF, or pass in a new set
|
||||
* of observations to compute the average/locals for (using the existing observations to construct the PDF)
|
||||
* @return either the average conditional MI, or array of local conditional MI value, in nats not bits
|
||||
* @throws Exception
|
||||
*/
|
||||
protected double[] computeFromObservations(boolean returnLocals) throws Exception {
|
||||
protected double[] computeFromObservations(boolean returnLocals, double[][][] newObservations) throws Exception {
|
||||
int N = var1Observations.length; // number of observations
|
||||
|
||||
double[] returnValues = null;
|
||||
|
|
@ -488,28 +513,36 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
uniNNSearcherVar2 = new UnivariateNearestNeighbourSearcher(var2Observations);
|
||||
}
|
||||
}
|
||||
if (nnSearcherConditional == null) {
|
||||
if ((nnSearcherConditional == null) && (dimensionsCond > 0)) {
|
||||
nnSearcherConditional = NearestNeighbourSearcher.create(condObservations);
|
||||
nnSearcherConditional.setNormType(normType);
|
||||
}
|
||||
|
||||
// How many time points are we averaging over?
|
||||
int numTimePointsToComputeFor = (newObservations == null) ?
|
||||
N : newObservations[0].length;
|
||||
|
||||
if (numThreads == 1) {
|
||||
// Single-threaded implementation:
|
||||
returnValues = partialComputeFromObservations(0, N, returnLocals);
|
||||
|
||||
if (newObservations == null) {
|
||||
returnValues = partialComputeFromObservations(0, numTimePointsToComputeFor, returnLocals);
|
||||
} else {
|
||||
returnValues = partialComputeFromNewObservations(0, numTimePointsToComputeFor,
|
||||
newObservations[0], newObservations[1], newObservations[2], returnLocals);
|
||||
}
|
||||
} else {
|
||||
// We're going multithreaded:
|
||||
if (returnLocals) {
|
||||
// We're computing local conditional MI
|
||||
returnValues = new double[N];
|
||||
returnValues = new double[numTimePointsToComputeFor];
|
||||
} else {
|
||||
// We're computing average conditional MI
|
||||
returnValues = new double[CondMiKraskovThreadRunner.RETURN_ARRAY_LENGTH];
|
||||
}
|
||||
|
||||
// Distribute the observations to the threads for the parallel processing
|
||||
int lTimesteps = N / numThreads; // each thread gets the same amount of data
|
||||
int res = N % numThreads; // the first thread gets the residual data
|
||||
int lTimesteps = numTimePointsToComputeFor / numThreads; // each thread gets the same amount of data
|
||||
int res = numTimePointsToComputeFor % numThreads; // the first thread gets the residual data
|
||||
if (debug) {
|
||||
System.out.printf("Computing Kraskov conditional MI with %d threads (%d timesteps each, plus %d residual)\n",
|
||||
numThreads, lTimesteps, res);
|
||||
|
|
@ -523,7 +556,7 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
System.out.println(t + ".Thread: from " + startTime +
|
||||
" to " + (startTime + numTimesteps)); // Trace Message
|
||||
}
|
||||
runners[t] = new CondMiKraskovThreadRunner(this, startTime, numTimesteps, returnLocals);
|
||||
runners[t] = new CondMiKraskovThreadRunner(this, startTime, numTimesteps, newObservations, returnLocals);
|
||||
tCalculators[t] = new Thread(runners[t]);
|
||||
tCalculators[t].start();
|
||||
}
|
||||
|
|
@ -552,10 +585,10 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
return returnValues;
|
||||
} else {
|
||||
// Average out the components for the final equation(s) and for debugging:
|
||||
double averageDiGammas = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_DIGAMMAS] / (double) N;
|
||||
double avNxz = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_NXZ] / (double) N;
|
||||
double avNyz = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_NYZ] / (double) N;
|
||||
double avNz = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_NZ] / (double) N;
|
||||
double averageDiGammas = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_DIGAMMAS] / (double) numTimePointsToComputeFor;
|
||||
double avNxz = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_NXZ] / (double) numTimePointsToComputeFor;
|
||||
double avNyz = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_NYZ] / (double) numTimePointsToComputeFor;
|
||||
double avNz = returnValues[CondMiKraskovThreadRunner.INDEX_SUM_NZ] / (double) numTimePointsToComputeFor;
|
||||
if (debug) {
|
||||
System.out.printf("<n_xz>=%.3f, <n_yz>=%.3f, <n_z>=%.3f\n",
|
||||
avNxz, avNyz, avNz);
|
||||
|
|
@ -573,17 +606,27 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
// Algorithm 2:
|
||||
// We also retrieve the sums of inverses for debugging purposes:
|
||||
double averageInverseCountInJointXZ =
|
||||
returnValues[CondMiKraskovThreadRunner.INDEX_SUM_INV_NXZ] / (double) N;
|
||||
returnValues[CondMiKraskovThreadRunner.INDEX_SUM_INV_NXZ] / (double) numTimePointsToComputeFor;
|
||||
double averageInverseCountInJointYZ =
|
||||
returnValues[CondMiKraskovThreadRunner.INDEX_SUM_INV_NYZ] / (double) N;
|
||||
double averageMeasure = MathsUtils.digamma(k) - (2.0 / (double)k) + averageDiGammas +
|
||||
returnValues[CondMiKraskovThreadRunner.INDEX_SUM_INV_NYZ] / (double) numTimePointsToComputeFor;
|
||||
double inverseKTerm;
|
||||
if (dimensionsCond > 0) {
|
||||
// We will add in the 2/K term as usual
|
||||
inverseKTerm = 2.0 / (double) k;
|
||||
} else {
|
||||
// We will only add in 1/K term since without a conditional there is
|
||||
// technically one less variable in the full joint space
|
||||
inverseKTerm = 1.0 / (double) k;
|
||||
}
|
||||
double averageMeasure = MathsUtils.digamma(k) - inverseKTerm + averageDiGammas +
|
||||
averageInverseCountInJointXZ + averageInverseCountInJointYZ;
|
||||
if (debug) {
|
||||
System.out.printf("Av = digamma(k)=%.3f + <digammas>=%.3f +<inverses>=%.3f - 2/k=%.3f = %.3f" +
|
||||
System.out.printf("Av = digamma(k)=%.3f + <digammas>=%.3f +<inverses>=%.3f - $d/k=%.3f = %.3f" +
|
||||
" (<1/n_yz>=%.3f, <1/n_xz>=%.3f)\n",
|
||||
MathsUtils.digamma(k), averageDiGammas,
|
||||
averageInverseCountInJointXZ + averageInverseCountInJointYZ,
|
||||
(2.0 / (double)k), averageMeasure,
|
||||
dimensionsCond > 0 ? 2 : 1,
|
||||
inverseKTerm, averageMeasure,
|
||||
averageInverseCountInJointYZ, averageInverseCountInJointXZ);
|
||||
}
|
||||
double[] result = new double[1];
|
||||
|
|
@ -619,6 +662,40 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
protected abstract double[] partialComputeFromObservations(
|
||||
int startTimePoint, int numTimePoints, boolean returnLocals) throws Exception;
|
||||
|
||||
/**
|
||||
* Protected method to be used internally for threaded implementations.
|
||||
* This method implements the guts of each Kraskov algorithm, computing the number of
|
||||
* nearest neighbours in each dimension for a sub-set of the data points.
|
||||
* It is intended to be called by one thread to work on that specific
|
||||
* sub-set of the data.
|
||||
* In particular, this method differs from {@link #partialComputeFromObservations(int, int, boolean)}
|
||||
* because it operates on a new set of observations (using the old set of observations for
|
||||
* constructing the search spaces and PDFs)
|
||||
*
|
||||
* <p>The method returns:<ol>
|
||||
* <li>for average conditional MIs (returnLocals == false), the relevant sums of digamma(n_{xz}), digamma(n_{yz})
|
||||
* and digamma(n_z)
|
||||
* for a partial set of the observations</li>
|
||||
* <li>for local conditional MIs (returnLocals == true), the array of local conditional MI values</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param startTimePoint start time for the partial set we examine
|
||||
* @param numTimePoints number of time points (including startTimePoint to examine)
|
||||
* @param newVar1Observations new time series of observations for variable 1
|
||||
* @param newVar2Observations new time series of observations for variable 1
|
||||
* @param newCondObservations new time series of observations for variable 1
|
||||
* @param returnLocals whether to return an array or local values, or else
|
||||
* sums of these values
|
||||
* @return an array of the relevant sum of digamma(n_xz+1) and digamma(n_yz+1) and digamma(n_z), then
|
||||
* sum of n_xz, n_yz, n_z and for algorithm 2 only, sum of 1/n_xz and 1/n_yz
|
||||
* (these latter five are only for debugging purposes).
|
||||
* @throws Exception
|
||||
*/
|
||||
protected abstract double[] partialComputeFromNewObservations(
|
||||
int startTimePoint, int numTimePoints,
|
||||
double[][] newVar1Observations, double[][] newVar2Observations, double[][] newCondObservations,
|
||||
boolean returnLocals) throws Exception;
|
||||
|
||||
/**
|
||||
* Private class to handle multi-threading of the Kraskov algorithms.
|
||||
* Each instance calls partialComputeFromObservations()
|
||||
|
|
@ -633,6 +710,7 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
protected ConditionalMutualInfoCalculatorMultiVariateKraskov condMiCalc;
|
||||
protected int myStartTimePoint;
|
||||
protected int numberOfTimePoints;
|
||||
protected double[][][] newObservations;
|
||||
protected boolean computeLocals;
|
||||
|
||||
protected double[] returnValues = null;
|
||||
|
|
@ -649,11 +727,13 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
public CondMiKraskovThreadRunner(
|
||||
ConditionalMutualInfoCalculatorMultiVariateKraskov condMiCalc,
|
||||
int myStartTimePoint, int numberOfTimePoints,
|
||||
double[][][] newObservations,
|
||||
boolean computeLocals) {
|
||||
this.condMiCalc = condMiCalc;
|
||||
this.myStartTimePoint = myStartTimePoint;
|
||||
this.numberOfTimePoints = numberOfTimePoints;
|
||||
this.computeLocals = computeLocals;
|
||||
this.newObservations = newObservations;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -676,7 +756,16 @@ public abstract class ConditionalMutualInfoCalculatorMultiVariateKraskov
|
|||
*/
|
||||
public void run() {
|
||||
try {
|
||||
returnValues = condMiCalc.partialComputeFromObservations(myStartTimePoint, numberOfTimePoints, computeLocals);
|
||||
if (newObservations == null) {
|
||||
// Computing on existing observations
|
||||
returnValues = condMiCalc.partialComputeFromObservations(myStartTimePoint, numberOfTimePoints, computeLocals);
|
||||
} else {
|
||||
// Computing on new observations
|
||||
returnValues = condMiCalc.partialComputeFromNewObservations(
|
||||
myStartTimePoint, numberOfTimePoints,
|
||||
newObservations[0], newObservations[1],
|
||||
newObservations[2], computeLocals);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Store the exception for later retrieval
|
||||
problem = e;
|
||||
|
|
|
|||
|
|
@ -18,12 +18,18 @@
|
|||
|
||||
package infodynamics.measures.continuous.kraskov;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
import infodynamics.measures.continuous.ConditionalMutualInfoCalculatorMultiVariate;
|
||||
import infodynamics.utils.EmpiricalMeasurementDistribution;
|
||||
import infodynamics.utils.FirstIndexComparatorDouble;
|
||||
import infodynamics.utils.KdTree;
|
||||
import infodynamics.utils.MathsUtils;
|
||||
import infodynamics.utils.MatrixUtils;
|
||||
import infodynamics.utils.NeighbourNodeData;
|
||||
import infodynamics.utils.UnivariateNearestNeighbourSearcher;
|
||||
|
||||
/**
|
||||
* <p>Computes the differential conditional mutual information of two multivariate
|
||||
|
|
@ -99,7 +105,20 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
|
|||
// First element in the PQ is the kth NN,
|
||||
// and epsilon = kthNnData.distance
|
||||
NeighbourNodeData kthNnData = nnPQ.poll();
|
||||
|
||||
/*
|
||||
if (debug) {
|
||||
System.out.print("t = " + t + " : for data point: ");
|
||||
MatrixUtils.printArray(System.out, var1Observations[t]);
|
||||
MatrixUtils.printArray(System.out, var2Observations[t]);
|
||||
if (dimensionsCond > 0) {
|
||||
MatrixUtils.printArray(System.out, condObservations[t]);
|
||||
} else {
|
||||
System.out.print("[]");
|
||||
}
|
||||
System.out.printf("t=%d: K=%d NNs found at range %.5f (point %d)\n", t, k, kthNnData.distance, kthNnData.sampleIndex);
|
||||
}
|
||||
*/
|
||||
|
||||
// Now count the points in the conditional space, and
|
||||
// the var1-conditional and var2-conditional spaces.
|
||||
// We have 3 coded options for how to do this:
|
||||
|
|
@ -112,7 +131,7 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
|
|||
int n_yz = kdTreeVar2Conditional.countPointsStrictlyWithinR(
|
||||
t, kthNnData.distance, dynCorrExclTime);
|
||||
int n_z = nnSearcherConditional.countPointsStrictlyWithinR(
|
||||
t, kthNnData.distance, dynCorrExclTime);
|
||||
t, kthNnData.distance, dynCorrExclTime);
|
||||
*/ // end option A
|
||||
|
||||
/* Option B --
|
||||
|
|
@ -151,8 +170,10 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
|
|||
if (debug) {
|
||||
methodStartTime = Calendar.getInstance().getTimeInMillis();
|
||||
}
|
||||
nnSearcherConditional.findPointsWithinR(t, kthNnData.distance, dynCorrExclTime,
|
||||
if (dimensionsCond > 0) {
|
||||
nnSearcherConditional.findPointsWithinR(t, kthNnData.distance, dynCorrExclTime,
|
||||
false, isWithinRForConditionals, indicesWithinRForConditionals);
|
||||
}
|
||||
if (debug) {
|
||||
conditionalTime += Calendar.getInstance().getTimeInMillis() -
|
||||
methodStartTime;
|
||||
|
|
@ -160,15 +181,26 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
|
|||
}
|
||||
// 2. Then compute n_xz and n_yz harnessing our knowledge of
|
||||
// which points qualified for the conditional already:
|
||||
// Don't need to supply dynCorrExclTime in the following, because only
|
||||
// Don't need to supply dynCorrExclTime in most of the following, because only
|
||||
// points outside of it have been included in isWithinRForConditionals
|
||||
int n_xz;
|
||||
if (dimensionsVar1 > 1) {
|
||||
n_xz = kdTreeVar1Conditional.countPointsWithinR(t, kthNnData.distance,
|
||||
false, 1, isWithinRForConditionals);
|
||||
} else { // Generally faster to search only the marginal space if it is univariate
|
||||
n_xz = uniNNSearcherVar1.countPointsWithinR(t, kthNnData.distance,
|
||||
false, isWithinRForConditionals);
|
||||
if (dimensionsCond == 0) {
|
||||
// We're really only counting whether the x space qualifies
|
||||
if (dimensionsVar1 > 1) {
|
||||
n_xz = kdTreeVar1Conditional.countPointsStrictlyWithinR(
|
||||
t, kthNnData.distance, dynCorrExclTime);
|
||||
} else {
|
||||
n_xz = uniNNSearcherVar1.countPointsWithinR(t, kthNnData.distance,
|
||||
dynCorrExclTime, false);
|
||||
}
|
||||
} else {
|
||||
if (dimensionsVar1 > 1) {
|
||||
n_xz = kdTreeVar1Conditional.countPointsWithinR(t, kthNnData.distance,
|
||||
false, 1, isWithinRForConditionals);
|
||||
} else { // Generally faster to search only the marginal space if it is univariate
|
||||
n_xz = uniNNSearcherVar1.countPointsWithinR(t, kthNnData.distance,
|
||||
false, isWithinRForConditionals);
|
||||
}
|
||||
}
|
||||
if (debug) {
|
||||
conditionalXTime += Calendar.getInstance().getTimeInMillis() -
|
||||
|
|
@ -176,12 +208,23 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
|
|||
methodStartTime = Calendar.getInstance().getTimeInMillis();
|
||||
}
|
||||
int n_yz;
|
||||
if (dimensionsVar2 > 1) {
|
||||
n_yz = kdTreeVar2Conditional.countPointsWithinR(t, kthNnData.distance,
|
||||
false, 1, isWithinRForConditionals);
|
||||
} else { // Generally faster to search only the marginal space if it is univariate
|
||||
n_yz = uniNNSearcherVar2.countPointsWithinR(t, kthNnData.distance,
|
||||
false, isWithinRForConditionals);
|
||||
if (dimensionsCond == 0) {
|
||||
// We're really only counting whether the y space qualifies
|
||||
if (dimensionsVar2 > 1) {
|
||||
n_yz = kdTreeVar2Conditional.countPointsStrictlyWithinR(
|
||||
t, kthNnData.distance, dynCorrExclTime);
|
||||
} else {
|
||||
n_yz = uniNNSearcherVar2.countPointsWithinR(t, kthNnData.distance,
|
||||
dynCorrExclTime, false);
|
||||
}
|
||||
} else {
|
||||
if (dimensionsVar2 > 1) {
|
||||
n_yz = kdTreeVar2Conditional.countPointsWithinR(t, kthNnData.distance,
|
||||
false, 1, isWithinRForConditionals);
|
||||
} else { // Generally faster to search only the marginal space if it is univariate
|
||||
n_yz = uniNNSearcherVar2.countPointsWithinR(t, kthNnData.distance,
|
||||
false, isWithinRForConditionals);
|
||||
}
|
||||
}
|
||||
if (debug) {
|
||||
conditionalYTime += Calendar.getInstance().getTimeInMillis() -
|
||||
|
|
@ -189,8 +232,15 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
|
|||
}
|
||||
// 3. Finally, reset our boolean array for its next use while we count n_z:
|
||||
int n_z;
|
||||
for (n_z = 0; indicesWithinRForConditionals[n_z] != -1; n_z++) {
|
||||
isWithinRForConditionals[indicesWithinRForConditionals[n_z]] = false;
|
||||
if (dimensionsCond == 0) {
|
||||
n_z = totalObservations - 1; // - 1 to remove the point itself.
|
||||
// Note: This doesn't respect the dynamic correlation exclusion, but
|
||||
// does align with a mutual information calculation here (to give the digamma(N) term).
|
||||
// No need to reset the boolean array
|
||||
} else {
|
||||
for (n_z = 0; indicesWithinRForConditionals[n_z] != -1; n_z++) {
|
||||
isWithinRForConditionals[indicesWithinRForConditionals[n_z]] = false;
|
||||
}
|
||||
}
|
||||
// end option C
|
||||
|
||||
|
|
@ -206,11 +256,16 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
|
|||
if (returnLocals) {
|
||||
localCondMi[t-startTimePoint] = digammaK - digammaNxzPlusOne - digammaNyzPlusOne + digammaNzPlusOne;
|
||||
if (debug) {
|
||||
System.out.printf("t=%d, n_xz=%d, n_yz=%d, n_z=%d, local=%.4f," +
|
||||
System.out.printf("t=%d, r=%.5f, n_xz=%d, n_yz=%d, n_z=%d, local=%.4f," +
|
||||
" digamma(n_xz+1)=%.5f, digamma(n_yz+1)=%.5f, digamma(n_z+1)=%.5f, \n",
|
||||
t, n_xz, n_yz, n_z, localCondMi[t-startTimePoint],
|
||||
t, kthNnData.distance, n_xz, n_yz, n_z, localCondMi[t-startTimePoint],
|
||||
digammaNxzPlusOne, digammaNyzPlusOne, digammaNzPlusOne);
|
||||
}
|
||||
} else if (debug) {
|
||||
System.out.printf("t=%d, n_xz=%d, n_yz=%d, n_z=%d," +
|
||||
" digamma(n_xz+1)=%.5f, digamma(n_yz+1)=%.5f, digamma(n_z+1)=%.5f, \n",
|
||||
t, n_xz, n_yz, n_z,
|
||||
digammaNxzPlusOne, digammaNyzPlusOne, digammaNzPlusOne);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -244,4 +299,214 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov1
|
|||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the local conditional mutual information values for a new set of
|
||||
* observations, where the PDFs are constructed from the observations added earlier.
|
||||
*
|
||||
* @param startTimePoint
|
||||
* @param numTimePoints
|
||||
* @param newStates1
|
||||
* @param newStates2
|
||||
* @param newCondStates
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected double[] partialComputeFromNewObservations(int startTimePoint,
|
||||
int numTimePoints, double[][] newStates1,
|
||||
double[][] newStates2, double[][] newCondStates, boolean returnLocals) throws Exception {
|
||||
|
||||
double startTime = Calendar.getInstance().getTimeInMillis();
|
||||
|
||||
double[] localCondMi = null;
|
||||
if (returnLocals) {
|
||||
localCondMi = new double[numTimePoints];
|
||||
}
|
||||
|
||||
// Count the average number of points within eps_xz and eps_yz and eps_z of each point
|
||||
double sumDiGammas = 0;
|
||||
double sumNxz = 0;
|
||||
double sumNyz = 0;
|
||||
double sumNz = 0;
|
||||
|
||||
long knnTime = 0, conditionalTime = 0,
|
||||
conditionalXTime = 0, conditionalYTime = 0;
|
||||
|
||||
// Arrays used for fast searching on conditionals with a marginal:
|
||||
boolean[] isWithinRForConditionals = new boolean[totalObservations];
|
||||
int[] indicesWithinRForConditionals = new int[totalObservations+1];
|
||||
|
||||
for (int t = startTimePoint; t < startTimePoint + numTimePoints; t++) {
|
||||
// Compute eps for this time step by
|
||||
// finding the kth closest neighbour for point t:
|
||||
long methodStartTime = Calendar.getInstance().getTimeInMillis();
|
||||
PriorityQueue<NeighbourNodeData> nnPQ =
|
||||
kdTreeJoint.findKNearestNeighbours(k,
|
||||
new double[][] {newStates1[t], newStates2[t], newCondStates[t]});
|
||||
knnTime += Calendar.getInstance().getTimeInMillis() -
|
||||
methodStartTime;
|
||||
// First element in the PQ is the kth NN,
|
||||
// and epsilon = kthNnData.distance
|
||||
NeighbourNodeData kthNnData = nnPQ.poll();
|
||||
if (debug) {
|
||||
System.out.print("t = " + t + " : for data point: ");
|
||||
MatrixUtils.printArray(System.out, newStates1[t]);
|
||||
MatrixUtils.printArray(System.out, newStates2[t]);
|
||||
if (dimensionsCond > 0) {
|
||||
MatrixUtils.printArray(System.out, newCondStates[t]);
|
||||
} else {
|
||||
System.out.print("[]");
|
||||
}
|
||||
System.out.printf("t=%d : K=%d NNs found at range %.5f (point %d)\n", t, k, kthNnData.distance, kthNnData.sampleIndex);
|
||||
}
|
||||
|
||||
// Now count the points in the conditional space, and
|
||||
// the var1-conditional and var2-conditional spaces.
|
||||
// We use Option C determined above to do this --
|
||||
// Identify the points satisfying the conditional criteria, then use
|
||||
// the knowledge of which points made this cut to speed up the searching
|
||||
// in the conditional-marginal spaces:
|
||||
// 1. Identify the n_z points within the conditional boundaries:
|
||||
if (debug) {
|
||||
methodStartTime = Calendar.getInstance().getTimeInMillis();
|
||||
}
|
||||
if (dimensionsCond > 0) {
|
||||
nnSearcherConditional.findPointsWithinR(kthNnData.distance,
|
||||
new double[][] {newCondStates[t]},
|
||||
false, isWithinRForConditionals, indicesWithinRForConditionals);
|
||||
}
|
||||
if (debug) {
|
||||
conditionalTime += Calendar.getInstance().getTimeInMillis() -
|
||||
methodStartTime;
|
||||
methodStartTime = Calendar.getInstance().getTimeInMillis();
|
||||
}
|
||||
// 2. Then compute n_xz and n_yz harnessing our knowledge of
|
||||
// which points qualified for the conditional already:
|
||||
// Don't need to supply dynCorrExclTime in the following, because
|
||||
// it's not relevant for the new points
|
||||
int n_xz;
|
||||
if (dimensionsCond == 0) {
|
||||
// We're really only counting whether the x space qualifies
|
||||
if (dimensionsVar1 > 1) {
|
||||
n_xz = kdTreeVar1Conditional.countPointsWithinR(
|
||||
new double[][] {newStates1[t], newCondStates[t]},
|
||||
kthNnData.distance, false);
|
||||
} else {
|
||||
n_xz = uniNNSearcherVar1.countPointsWithinR(
|
||||
new double[][] {newStates1[t]}, kthNnData.distance, false);
|
||||
}
|
||||
} else {
|
||||
if (dimensionsVar1 > 1) {
|
||||
n_xz = kdTreeVar1Conditional.countPointsWithinR(kthNnData.distance,
|
||||
new double[][] {newStates1[t], newCondStates[t]},
|
||||
false, 1, isWithinRForConditionals);
|
||||
} else { // Generally faster to search only the marginal space if it is univariate
|
||||
n_xz = uniNNSearcherVar1.countPointsWithinR(
|
||||
new double[][] {newStates1[t]}, kthNnData.distance,
|
||||
false, isWithinRForConditionals);
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
conditionalXTime += Calendar.getInstance().getTimeInMillis() -
|
||||
methodStartTime;
|
||||
methodStartTime = Calendar.getInstance().getTimeInMillis();
|
||||
}
|
||||
int n_yz;
|
||||
if (dimensionsCond == 0) {
|
||||
// We're really only counting whether the y space qualifies
|
||||
if (dimensionsVar2 > 1) {
|
||||
n_yz = kdTreeVar2Conditional.countPointsWithinR(
|
||||
new double[][] {newStates2[t], newCondStates[t]},
|
||||
kthNnData.distance, false);
|
||||
} else {
|
||||
n_yz = uniNNSearcherVar2.countPointsWithinR(
|
||||
new double[][] {newStates2[t]},
|
||||
kthNnData.distance, false);
|
||||
}
|
||||
} else {
|
||||
if (dimensionsVar2 > 1) {
|
||||
n_yz = kdTreeVar2Conditional.countPointsWithinR(kthNnData.distance,
|
||||
new double[][] {newStates2[t], newCondStates[t]},
|
||||
false, 1, isWithinRForConditionals);
|
||||
} else { // Generally faster to search only the marginal space if it is univariate
|
||||
n_yz = uniNNSearcherVar2.countPointsWithinR(
|
||||
new double[][] {newStates2[t]}, kthNnData.distance,
|
||||
false, isWithinRForConditionals);
|
||||
}
|
||||
}
|
||||
if (debug) {
|
||||
conditionalYTime += Calendar.getInstance().getTimeInMillis() -
|
||||
methodStartTime;
|
||||
}
|
||||
// 3. Finally, reset our boolean array for its next use while we count n_z:
|
||||
int n_z;
|
||||
if (dimensionsCond == 0) {
|
||||
n_z = totalObservations - 1; // - 1 to remove the point itself.
|
||||
// no need to reset the boolean array
|
||||
} else {
|
||||
for (n_z = 0; indicesWithinRForConditionals[n_z] != -1; n_z++) {
|
||||
isWithinRForConditionals[indicesWithinRForConditionals[n_z]] = false;
|
||||
}
|
||||
}
|
||||
// end option C
|
||||
|
||||
sumNxz += n_xz;
|
||||
sumNyz += n_yz;
|
||||
sumNz += n_z;
|
||||
// And take the digammas:
|
||||
double digammaNxzPlusOne = MathsUtils.digamma(n_xz+1);
|
||||
double digammaNyzPlusOne = MathsUtils.digamma(n_yz+1);
|
||||
double digammaNzPlusOne = MathsUtils.digamma(n_z+1);
|
||||
sumDiGammas += digammaNzPlusOne - digammaNxzPlusOne - digammaNyzPlusOne;
|
||||
|
||||
if (returnLocals) {
|
||||
localCondMi[t-startTimePoint] = digammaK - digammaNxzPlusOne - digammaNyzPlusOne + digammaNzPlusOne;
|
||||
if (debug) {
|
||||
System.out.printf("t=%d, n_xz=%d, n_yz=%d, n_z=%d, local=%.4f," +
|
||||
" digamma(n_xz+1)=%.5f, digamma(n_yz+1)=%.5f, digamma(n_z+1)=%.5f, \n",
|
||||
t, n_xz, n_yz, n_z, localCondMi[t-startTimePoint],
|
||||
digammaNxzPlusOne, digammaNyzPlusOne, digammaNzPlusOne);
|
||||
}
|
||||
} else if (debug) {
|
||||
double localValue = digammaK - digammaNxzPlusOne - digammaNyzPlusOne + digammaNzPlusOne;
|
||||
System.out.printf("t=%d, n_xz=%d, n_yz=%d, n_z=%d, local=%.4f," +
|
||||
" digamma(n_xz+1)=%.5f, digamma(n_yz+1)=%.5f, digamma(n_z+1)=%.5f, \n",
|
||||
t, n_xz, n_yz, n_z, localValue,
|
||||
digammaNxzPlusOne, digammaNyzPlusOne, digammaNzPlusOne);
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
Calendar rightNow2 = Calendar.getInstance();
|
||||
long endTime = rightNow2.getTimeInMillis();
|
||||
System.out.println("Subset " + startTimePoint + ":" +
|
||||
(startTimePoint + numTimePoints) + " Calculation time: " +
|
||||
((endTime - startTime)/1000.0) + " sec" );
|
||||
System.out.println("Total exec times for: ");
|
||||
System.out.println("\tknn search: " + (knnTime/1000.0));
|
||||
System.out.println("\tz search: " + (conditionalTime/1000.0));
|
||||
System.out.println("\tzx search: " + (conditionalXTime/1000.0));
|
||||
System.out.println("\tzy search: " + (conditionalYTime/1000.0));
|
||||
System.out.printf("%d:%d -- Returning: %.4f, %.4f, %.4f, %.4f\n",
|
||||
startTimePoint, (startTimePoint + numTimePoints),
|
||||
sumDiGammas, sumNxz, sumNyz, sumNz);
|
||||
}
|
||||
|
||||
// Select what to return:
|
||||
if (returnLocals) {
|
||||
return localCondMi;
|
||||
} else {
|
||||
// Pad return array with two values, to allow compatibility in
|
||||
// return length with algorithm 2
|
||||
double[] results = new double[6];
|
||||
results[0] = sumDiGammas;
|
||||
results[1] = sumNxz;
|
||||
results[2] = sumNyz;
|
||||
results[3] = sumNz;
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,15 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov2
|
|||
}
|
||||
|
||||
// Constants:
|
||||
double twoOnK = 2.0 / (double) k;
|
||||
double inverseKTerm;
|
||||
if (dimensionsCond > 0) {
|
||||
// We will add in the 2/K term as usual
|
||||
inverseKTerm = 2.0 / (double) k;
|
||||
} else {
|
||||
// We will only add in 1/K term since without a conditional there is
|
||||
// technically one less variable in the full joint space
|
||||
inverseKTerm = 1.0 / (double) k;
|
||||
}
|
||||
|
||||
// Count the average number of points within eps_xz, eps_yz and eps_z
|
||||
double sumDiGammas = 0;
|
||||
|
|
@ -136,36 +144,68 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov2
|
|||
// the knowledge of which points made this cut to speed up the searching
|
||||
// in the conditional-marginal spaces:
|
||||
// 1. Identify the n_z points within the conditional boundaries:
|
||||
nnSearcherConditional.findPointsWithinR(t, eps_z, dynCorrExclTime,
|
||||
if (dimensionsCond > 0) {
|
||||
nnSearcherConditional.findPointsWithinR(t, eps_z, dynCorrExclTime,
|
||||
true, isWithinRForConditionals, indicesWithinRForConditionals);
|
||||
}
|
||||
// 2. Then compute n_xz and n_yz harnessing our knowledge of
|
||||
// which points qualified for the conditional already:
|
||||
// Don't need to supply dynCorrExclTime in the following, because only
|
||||
// points outside of it have been included in isWithinRForConditionals
|
||||
int n_xz;
|
||||
if (dimensionsVar1 > 1) {
|
||||
// Check only the x variable against eps_x, use existing results for z
|
||||
n_xz = kdTreeVar1Conditional.countPointsWithinRs(t,
|
||||
new double[] {eps_x, eps_z},
|
||||
true, 1, isWithinRForConditionals);
|
||||
} else { // Generally faster to search only the marginal space if it is univariate
|
||||
n_xz = uniNNSearcherVar1.countPointsWithinR(t, eps_x,
|
||||
true, isWithinRForConditionals);
|
||||
if (dimensionsCond == 0) {
|
||||
// We're really only counting whether the x space qualifies
|
||||
if (dimensionsVar1 > 1) {
|
||||
n_xz = kdTreeVar1Conditional.countPointsWithinOrOnR(
|
||||
t, eps_x, dynCorrExclTime); // No need to pass eps_z in
|
||||
} else { // Generally faster to search only the marginal space if it is univariate
|
||||
n_xz = uniNNSearcherVar1.countPointsWithinOrOnR(t, eps_x,
|
||||
dynCorrExclTime);
|
||||
}
|
||||
} else {
|
||||
if (dimensionsVar1 > 1) {
|
||||
// Check only the x variable against eps_x, use existing results for z
|
||||
n_xz = kdTreeVar1Conditional.countPointsWithinRs(t,
|
||||
new double[] {eps_x, eps_z},
|
||||
true, 1, isWithinRForConditionals);
|
||||
} else { // Generally faster to search only the marginal space if it is univariate
|
||||
n_xz = uniNNSearcherVar1.countPointsWithinR(t, eps_x,
|
||||
true, isWithinRForConditionals);
|
||||
}
|
||||
}
|
||||
int n_yz;
|
||||
if (dimensionsVar2 > 1) {
|
||||
// Check only the y variable against eps_y, use existing results for z
|
||||
n_yz = kdTreeVar2Conditional.countPointsWithinRs(t,
|
||||
new double[] {eps_y, eps_z},
|
||||
true, 1, isWithinRForConditionals);
|
||||
} else { // Generally faster to search only the marginal space if it is univariate
|
||||
n_yz = uniNNSearcherVar2.countPointsWithinR(t, eps_y,
|
||||
true, isWithinRForConditionals);
|
||||
if (dimensionsCond == 0) {
|
||||
if (dimensionsVar2 > 1) {
|
||||
// Check only the y variable against eps_y, use existing results for z
|
||||
n_yz = kdTreeVar2Conditional.countPointsWithinOrOnR(
|
||||
t, eps_y, dynCorrExclTime); // No need to pass eps_z in
|
||||
} else { // Generally faster to search only the marginal space if it is univariate
|
||||
n_yz = uniNNSearcherVar2.countPointsWithinOrOnR(t, eps_y,
|
||||
dynCorrExclTime);
|
||||
}
|
||||
} else {
|
||||
// We're really only counting whether the y space qualifies
|
||||
if (dimensionsVar2 > 1) {
|
||||
// Check only the y variable against eps_y, use existing results for z
|
||||
n_yz = kdTreeVar2Conditional.countPointsWithinRs(t,
|
||||
new double[] {eps_y, eps_z},
|
||||
true, 1, isWithinRForConditionals);
|
||||
} else { // Generally faster to search only the marginal space if it is univariate
|
||||
n_yz = uniNNSearcherVar2.countPointsWithinR(t, eps_y,
|
||||
true, isWithinRForConditionals);
|
||||
}
|
||||
}
|
||||
// 3. Finally, reset our boolean array for its next use while we count n_z:
|
||||
int n_z;
|
||||
for (n_z = 0; indicesWithinRForConditionals[n_z] != -1; n_z++) {
|
||||
isWithinRForConditionals[indicesWithinRForConditionals[n_z]] = false;
|
||||
if (dimensionsCond == 0) {
|
||||
n_z = totalObservations; // Dropping - 1 to remove the point itself, and
|
||||
// Note: This doesn't respect the dynamic correlation exclusion, but this
|
||||
// does align with a mutual information calculation here (to give the digamma(N) term).
|
||||
// No need to reset the boolean array
|
||||
} else {
|
||||
for (n_z = 0; indicesWithinRForConditionals[n_z] != -1; n_z++) {
|
||||
isWithinRForConditionals[indicesWithinRForConditionals[n_z]] = false;
|
||||
}
|
||||
}
|
||||
// end option C
|
||||
|
||||
|
|
@ -177,15 +217,25 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov2
|
|||
double digammaNxz = MathsUtils.digamma(n_xz);
|
||||
double digammaNyz = MathsUtils.digamma(n_yz);
|
||||
double digammaNz = MathsUtils.digamma(n_z);
|
||||
double invN_xz = 1.0/(double) n_xz;
|
||||
double invN_yz = 1.0/(double) n_yz;
|
||||
double invN_xz, invN_yz;
|
||||
if (dimensionsCond > 0) {
|
||||
// We will add in the inverse neighbour counts as usual
|
||||
invN_xz = 1.0/(double) n_xz;
|
||||
invN_yz = 1.0/(double) n_yz;
|
||||
} else {
|
||||
// We do not add in the inverse neighbour counts, because they
|
||||
// are technically coming from a single variable neighbour search,
|
||||
// and so there is a 0/n_xz and 0/n_yz contribution
|
||||
invN_xz = 0.0;
|
||||
invN_yz = 0.0;
|
||||
}
|
||||
sumInverseCountInJointXZ += invN_xz;
|
||||
sumInverseCountInJointYZ += invN_yz;
|
||||
double contributionDigammas = digammaNz - digammaNxz - digammaNyz;
|
||||
sumDiGammas += contributionDigammas;
|
||||
|
||||
if (returnLocals) {
|
||||
localCondMi[t-startTimePoint] = digammaK - twoOnK + contributionDigammas + invN_xz + invN_yz;
|
||||
localCondMi[t-startTimePoint] = digammaK - inverseKTerm + contributionDigammas + invN_xz + invN_yz;
|
||||
if (debug) {
|
||||
// Only tracking this for debugging purposes:
|
||||
System.out.printf("t=%d, n_xz=%d, n_yz=%d, n_z=%d, 1/n_yz=%.3f, 1/n_xz=%.3f, local=%.4f\n",
|
||||
|
|
@ -216,4 +266,13 @@ public class ConditionalMutualInfoCalculatorMultiVariateKraskov2
|
|||
return returnValues;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double[] partialComputeFromNewObservations(int startTimePoint,
|
||||
int numTimePoints, double[][] newVar1Observations,
|
||||
double[][] newVar2Observations, double[][] newCondObservations,
|
||||
boolean returnLocals) throws Exception {
|
||||
// TODO implement me
|
||||
throw new RuntimeException("Not implemented yet");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,8 +53,11 @@ import infodynamics.measures.continuous.TransferEntropyCalculatorViaCondMutualIn
|
|||
* as well as for {@link #PROP_KRASKOV_ALG_NUM}.
|
||||
* Embedding parameters may be automatically determined as per the Ragwitz criteria
|
||||
* by setting the property {@link #PROP_AUTO_EMBED_METHOD} to {@link #AUTO_EMBED_METHOD_RAGWITZ}
|
||||
* or {@link #AUTO_EMBED_METHOD_RAGWITZ_DEST_ONLY}
|
||||
* (plus additional parameter settings for this).</li>
|
||||
* or {@link #AUTO_EMBED_METHOD_RAGWITZ_DEST_ONLY},
|
||||
* or as per the max. bias-corrected AIS criteria by
|
||||
* setting the property {@link #PROP_AUTO_EMBED_METHOD} to {@link #AUTO_EMBED_METHOD_MAX_CORR_AIS}
|
||||
* (as per Garland et al. in the reference list)
|
||||
* plus additional parameter settings for these.</li>
|
||||
* </li>
|
||||
* <li>Computed values are in <b>nats</b>, not bits!</li>
|
||||
* </ul>
|
||||
|
|
@ -81,6 +84,9 @@ import infodynamics.measures.continuous.TransferEntropyCalculatorViaCondMutualIn
|
|||
* Physical Review E 77, 026110, 2008.</li>
|
||||
* <li>Ragwitz and Kantz, "Markov models from data by simple nonlinear time series
|
||||
* predictors in delay embedding spaces", Physical Review E, vol 65, 056201 (2002).</li>
|
||||
* <li>J. Garland, R. G. James, E. Bradley, <a href="http://dx.doi.org/10.1103/physreve.93.022221">
|
||||
* "Leveraging information storage to select forecast-optimal parameters for delay-coordinate reconstructions"</a>,
|
||||
* Physical Review E, Vol. 93 (2016), 022221, doi:</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
|
||||
|
|
@ -118,7 +124,8 @@ public class TransferEntropyCalculatorKraskov
|
|||
|
||||
/**
|
||||
* Property name for specifying which (if any) auto-embedding method to use.
|
||||
* Valid values include {@link #AUTO_EMBED_METHOD_RAGWITZ}, {@link #AUTO_EMBED_METHOD_RAGWITZ_DEST_ONLY}
|
||||
* Valid values include {@link #AUTO_EMBED_METHOD_RAGWITZ}, {@link #AUTO_EMBED_METHOD_RAGWITZ_DEST_ONLY},
|
||||
* {@link #AUTO_EMBED_METHOD_MAX_CORR_AIS}, {@link #AUTO_EMBED_METHOD_MAX_CORR_AIS_DEST_ONLY}
|
||||
* and {@link #AUTO_EMBED_METHOD_NONE}.
|
||||
* Defaults to {@link #AUTO_EMBED_METHOD_NONE}
|
||||
*/
|
||||
|
|
@ -140,6 +147,18 @@ public class TransferEntropyCalculatorKraskov
|
|||
* for the destination time-series only
|
||||
*/
|
||||
public static final String AUTO_EMBED_METHOD_RAGWITZ_DEST_ONLY = "RAGWITZ_DEST_ONLY";
|
||||
/**
|
||||
* Valid value for the property {@link #PROP_AUTO_EMBED_METHOD} indicating that
|
||||
* the automatic embedding should be done by maximising the bias corrected
|
||||
* AIS, for both source and destination time series
|
||||
*/
|
||||
public static final String AUTO_EMBED_METHOD_MAX_CORR_AIS = "MAX_CORR_AIS";
|
||||
/**
|
||||
* Valid value for the property {@link #PROP_AUTO_EMBED_METHOD} indicating that
|
||||
* the automatic embedding should be done by maximising the bias corrected
|
||||
* AIS, for destination time series only
|
||||
*/
|
||||
public static final String AUTO_EMBED_METHOD_MAX_CORR_AIS_DEST_ONLY = "MAX_CORR_AIS_DEST_ONLY";
|
||||
/**
|
||||
* Internal variable tracking what type of auto embedding (if any)
|
||||
* we are using
|
||||
|
|
@ -268,7 +287,11 @@ public class TransferEntropyCalculatorKraskov
|
|||
* values are set manually; other accepted values include: {@link #AUTO_EMBED_METHOD_RAGWITZ} for use
|
||||
* of the Ragwitz criteria for both source and destination (searching up to {@link #PROP_K_SEARCH_MAX} and
|
||||
* {@link #PROP_TAU_SEARCH_MAX}), and {@link #AUTO_EMBED_METHOD_RAGWITZ_DEST_ONLY} for use
|
||||
* of the Ragwitz criteria for the destination only.
|
||||
* of the Ragwitz criteria for the destination only;
|
||||
* {@link AUTO_EMBED_METHOD_MAX_CORR_AIS} for use of the max bias corrected AIS criteria
|
||||
* for both source and destination (searching up to {@link #PROP_K_SEARCH_MAX} and
|
||||
* {@link #PROP_TAU_SEARCH_MAX}) and {@link AUTO_EMBED_METHOD_MAX_CORR_AIS_DEST_ONLY} for use of
|
||||
* this criteria for the destination only.
|
||||
* Use of any value other than {@link #AUTO_EMBED_METHOD_NONE}
|
||||
* will lead to previous settings for embedding lengths and delays (via e.g. {@link #initialise(int, int)} or
|
||||
* auto-embedding during previous calculations) for the destination and perhaps source to
|
||||
|
|
@ -366,66 +389,78 @@ public class TransferEntropyCalculatorKraskov
|
|||
// Else we need to auto embed
|
||||
|
||||
// If we need to check which embedding method later:
|
||||
// if (autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_RAGWITZ) ||
|
||||
// autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_RAGWITZ_DEST_ONLY)) {
|
||||
if (autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_RAGWITZ) ||
|
||||
autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_RAGWITZ_DEST_ONLY) ||
|
||||
autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_MAX_CORR_AIS) ||
|
||||
autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_MAX_CORR_AIS_DEST_ONLY)) {
|
||||
|
||||
// Use a Kraskov AIS calculator to embed both time-series individually:
|
||||
ActiveInfoStorageCalculatorKraskov aisCalc = new ActiveInfoStorageCalculatorKraskov();
|
||||
// Set the properties for the underlying MI Kraskov calculator here to match ours:
|
||||
for (String key : props.keySet()) {
|
||||
aisCalc.setProperty(key, props.get(key));
|
||||
}
|
||||
// Set the auto-embedding properties as we require:
|
||||
aisCalc.setProperty(ActiveInfoStorageCalculatorKraskov.PROP_AUTO_EMBED_METHOD,
|
||||
ActiveInfoStorageCalculatorKraskov.AUTO_EMBED_METHOD_RAGWITZ);
|
||||
aisCalc.setProperty(ActiveInfoStorageCalculatorKraskov.PROP_K_SEARCH_MAX,
|
||||
Integer.toString(k_search_max));
|
||||
aisCalc.setProperty(ActiveInfoStorageCalculatorKraskov.PROP_TAU_SEARCH_MAX,
|
||||
Integer.toString(tau_search_max));
|
||||
// In case !ragwitz_num_nns_set and our condMiCalc has a different default number of
|
||||
// kNNs for Kraskov search than miCalc, we had best supply the number directly here:
|
||||
aisCalc.setProperty(ActiveInfoStorageCalculatorKraskov.PROP_RAGWITZ_NUM_NNS,
|
||||
getProperty(PROP_RAGWITZ_NUM_NNS));
|
||||
|
||||
// Embed the destination with the Ragwitz criteria:
|
||||
if (debug) {
|
||||
System.out.println("Starting embedding of destination:");
|
||||
}
|
||||
aisCalc.initialise();
|
||||
aisCalc.startAddObservations();
|
||||
for (double[] destination : vectorOfDestinationTimeSeries) {
|
||||
aisCalc.addObservations(destination);
|
||||
}
|
||||
aisCalc.finaliseAddObservations();
|
||||
// Set the auto-embedding parameters for the destination:
|
||||
k = Integer.parseInt(aisCalc.getProperty(ActiveInfoStorageCalculator.K_PROP_NAME));
|
||||
k_tau = Integer.parseInt(aisCalc.getProperty(ActiveInfoStorageCalculator.TAU_PROP_NAME));
|
||||
if (debug) {
|
||||
System.out.printf("Embedding parameters for destination set to k=%d,k_tau=%d\n",
|
||||
k, k_tau);
|
||||
}
|
||||
|
||||
if (autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_RAGWITZ)) {
|
||||
// Embed the source also with the Ragwitz criteria:
|
||||
// Use a Kraskov AIS calculator to embed both time-series individually:
|
||||
ActiveInfoStorageCalculatorKraskov aisCalc = new ActiveInfoStorageCalculatorKraskov();
|
||||
// Set the properties for the underlying MI Kraskov calculator here to match ours:
|
||||
for (String key : props.keySet()) {
|
||||
aisCalc.setProperty(key, props.get(key));
|
||||
}
|
||||
// Set the auto-embedding properties as we require:
|
||||
if (autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_RAGWITZ) ||
|
||||
autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_RAGWITZ_DEST_ONLY)) {
|
||||
// We're doing Ragwitz auto-embedding
|
||||
aisCalc.setProperty(ActiveInfoStorageCalculatorKraskov.PROP_AUTO_EMBED_METHOD,
|
||||
ActiveInfoStorageCalculatorKraskov.AUTO_EMBED_METHOD_RAGWITZ);
|
||||
// In case !ragwitz_num_nns_set and our condMiCalc has a different default number of
|
||||
// kNNs for Kraskov search than miCalc, we had best supply the number directly here:
|
||||
aisCalc.setProperty(ActiveInfoStorageCalculatorKraskov.PROP_RAGWITZ_NUM_NNS,
|
||||
getProperty(PROP_RAGWITZ_NUM_NNS));
|
||||
} else {
|
||||
// We're doing max bias-corrected AIS embeding:
|
||||
aisCalc.setProperty(ActiveInfoStorageCalculatorKraskov.PROP_AUTO_EMBED_METHOD,
|
||||
ActiveInfoStorageCalculatorKraskov.AUTO_EMBED_METHOD_MAX_CORR_AIS);
|
||||
}
|
||||
aisCalc.setProperty(ActiveInfoStorageCalculatorKraskov.PROP_K_SEARCH_MAX,
|
||||
Integer.toString(k_search_max));
|
||||
aisCalc.setProperty(ActiveInfoStorageCalculatorKraskov.PROP_TAU_SEARCH_MAX,
|
||||
Integer.toString(tau_search_max));
|
||||
|
||||
// Embed the destination:
|
||||
if (debug) {
|
||||
System.out.println("Starting embedding of source:");
|
||||
System.out.println("Starting embedding of destination:");
|
||||
}
|
||||
aisCalc.initialise();
|
||||
aisCalc.startAddObservations();
|
||||
for (double[] source : vectorOfSourceTimeSeries) {
|
||||
aisCalc.addObservations(source);
|
||||
for (double[] destination : vectorOfDestinationTimeSeries) {
|
||||
aisCalc.addObservations(destination);
|
||||
}
|
||||
aisCalc.finaliseAddObservations();
|
||||
// Set the auto-embedding parameters for the source:
|
||||
l = Integer.parseInt(aisCalc.getProperty(ActiveInfoStorageCalculator.K_PROP_NAME));
|
||||
l_tau = Integer.parseInt(aisCalc.getProperty(ActiveInfoStorageCalculator.TAU_PROP_NAME));
|
||||
// Set the auto-embedding parameters for the destination:
|
||||
k = Integer.parseInt(aisCalc.getProperty(ActiveInfoStorageCalculator.K_PROP_NAME));
|
||||
k_tau = Integer.parseInt(aisCalc.getProperty(ActiveInfoStorageCalculator.TAU_PROP_NAME));
|
||||
if (debug) {
|
||||
System.out.printf("Embedding parameters for source set to l=%d,l_tau=%d\n",
|
||||
l, l_tau);
|
||||
System.out.printf("Embedding parameters for destination set to k=%d,k_tau=%d\n",
|
||||
k, k_tau);
|
||||
}
|
||||
|
||||
if (autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_RAGWITZ) ||
|
||||
autoEmbeddingMethod.equalsIgnoreCase(AUTO_EMBED_METHOD_MAX_CORR_AIS)) {
|
||||
// Embed the source also:
|
||||
if (debug) {
|
||||
System.out.println("Starting embedding of source:");
|
||||
}
|
||||
aisCalc.initialise();
|
||||
aisCalc.startAddObservations();
|
||||
for (double[] source : vectorOfSourceTimeSeries) {
|
||||
aisCalc.addObservations(source);
|
||||
}
|
||||
aisCalc.finaliseAddObservations();
|
||||
// Set the auto-embedding parameters for the source:
|
||||
l = Integer.parseInt(aisCalc.getProperty(ActiveInfoStorageCalculator.K_PROP_NAME));
|
||||
l_tau = Integer.parseInt(aisCalc.getProperty(ActiveInfoStorageCalculator.TAU_PROP_NAME));
|
||||
if (debug) {
|
||||
System.out.printf("Embedding parameters for source set to l=%d,l_tau=%d\n",
|
||||
l, l_tau);
|
||||
}
|
||||
}
|
||||
|
||||
// Now that embedding parameters are finalised:
|
||||
setStartTimeForFirstDestEmbedding();
|
||||
}
|
||||
|
||||
// Now that embedding parameters are finalised:
|
||||
setStartTimeForFirstDestEmbedding();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
132
java/source/infodynamics/measures/discrete/ConditionalTransferEntropyCalculatorDiscrete.java
Executable file → Normal file
132
java/source/infodynamics/measures/discrete/ConditionalTransferEntropyCalculatorDiscrete.java
Executable file → Normal file
|
|
@ -498,7 +498,7 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
|
|||
* multivariate time series.
|
||||
* to our estimates of the pdfs.
|
||||
* This call should be made as opposed to addObservations(int states[][])
|
||||
* for computing active info for heterogeneous agents.
|
||||
* for computing conditional TE for heterogeneous agents.
|
||||
*
|
||||
* @param states multivariate time series, indexed first by time
|
||||
* then by variable number.
|
||||
|
|
@ -843,7 +843,7 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
|
|||
}
|
||||
|
||||
/**
|
||||
* Computes local active information storage for the given
|
||||
* Computes local conditional transfer entropy for the given
|
||||
* states, using pdfs built up from observations previously
|
||||
* sent in via the addObservations method.
|
||||
* This method is suitable for heterogeneous agents
|
||||
|
|
@ -939,6 +939,134 @@ public class ConditionalTransferEntropyCalculatorDiscrete extends InfoMeasureCal
|
|||
return localTE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes local conditional transfer entropy for the given
|
||||
* samples, using pdfs built up from observations previously
|
||||
* sent in via the addObservations method.
|
||||
*
|
||||
* <p>This method takes in a univariate conditionals time series - it is assumed
|
||||
* that either numOtherInfoContributors == 1 or the user has combined the
|
||||
* multivariate tuples into a single value for each observation, e.g. by calling
|
||||
* {@link MatrixUtils#computeCombinedValues(int[][], int)}. This cannot be
|
||||
* checked here however, so use at your own risk!
|
||||
* </p>
|
||||
*
|
||||
* @param source source time series samples
|
||||
* @param dest dest time series samples
|
||||
* @param conditional time series for conditionals
|
||||
* (indexed only by time step)
|
||||
* @return time-series of local conditional TE values
|
||||
*/
|
||||
private double[] computeLocalFromPreviousObservations
|
||||
(int source[], int dest[], int[] conditional){
|
||||
|
||||
int rows = source.length;
|
||||
|
||||
// Allocate for all rows even though we'll leave the first ones as zeros
|
||||
double[] localTE = new double[rows];
|
||||
average = 0;
|
||||
max = 0;
|
||||
min = 0;
|
||||
|
||||
// Initialise and store the current previous value for each column
|
||||
int pastVal = 0;
|
||||
pastVal = 0;
|
||||
for (int p = 0; p < k; p++) {
|
||||
pastVal *= base;
|
||||
pastVal += dest[p];
|
||||
}
|
||||
int destVal, sourceVal, conditionalVal;
|
||||
double logTerm;
|
||||
for (int r = startObservationTime; r < rows; r++) {
|
||||
destVal = dest[r];
|
||||
sourceVal = source[r-1];
|
||||
conditionalVal = conditional[r-1];
|
||||
// Now compute the local value
|
||||
logTerm = ((double) sourceDestPastOthersCount[sourceVal][destVal][pastVal][conditionalVal] / (double) sourcePastOthersCount[sourceVal][pastVal][conditionalVal]) /
|
||||
((double) destPastOthersCount[destVal][pastVal][conditionalVal] / (double) pastOthersCount[pastVal][conditionalVal]);
|
||||
localTE[r] = Math.log(logTerm) / log_2;
|
||||
average += localTE[r];
|
||||
if (localTE[r] > max) {
|
||||
max = localTE[r];
|
||||
} else if (localTE[r] < min) {
|
||||
min = localTE[r];
|
||||
}
|
||||
// Update the previous value:
|
||||
if (k > 0) {
|
||||
pastVal -= maxShiftedValue[dest[r-k]];
|
||||
pastVal *= base;
|
||||
pastVal += dest[r];
|
||||
}
|
||||
}
|
||||
|
||||
average = average/(double) (rows - startObservationTime);
|
||||
|
||||
return localTE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes local conditional transfer entropy for the given
|
||||
* samples, using pdfs built up from observations previously
|
||||
* sent in via the addObservations method.
|
||||
*
|
||||
* @param source source time series samples
|
||||
* @param dest dest time series samples
|
||||
* @param conditionals multivariate time series for conditionals
|
||||
* (indexed first by time step then by variable number)
|
||||
* @return time-series of local conditional TE values
|
||||
*/
|
||||
private double[] computeLocalFromPreviousObservations
|
||||
(int source[], int dest[], int[][] conditionals){
|
||||
|
||||
int rows = source.length;
|
||||
|
||||
// Allocate for all rows even though we'll leave the first ones as zeros
|
||||
double[] localTE = new double[rows];
|
||||
average = 0;
|
||||
max = 0;
|
||||
min = 0;
|
||||
|
||||
// Initialise and store the current previous value for each column
|
||||
int pastVal = 0;
|
||||
pastVal = 0;
|
||||
for (int p = 0; p < k; p++) {
|
||||
pastVal *= base;
|
||||
pastVal += dest[p];
|
||||
}
|
||||
int destVal, sourceVal, conditionalsVal;
|
||||
double logTerm;
|
||||
for (int r = startObservationTime; r < rows; r++) {
|
||||
destVal = dest[r];
|
||||
sourceVal = source[r-1];
|
||||
conditionalsVal = 0;
|
||||
for (int o = 0; o < conditionals[r-1].length; o++) {
|
||||
// Include this other contributor
|
||||
conditionalsVal *= base;
|
||||
conditionalsVal += conditionals[r-1][o];
|
||||
}
|
||||
// Now compute the local value
|
||||
logTerm = ((double) sourceDestPastOthersCount[sourceVal][destVal][pastVal][conditionalsVal] / (double) sourcePastOthersCount[sourceVal][pastVal][conditionalsVal]) /
|
||||
((double) destPastOthersCount[destVal][pastVal][conditionalsVal] / (double) pastOthersCount[pastVal][conditionalsVal]);
|
||||
localTE[r] = Math.log(logTerm) / log_2;
|
||||
average += localTE[r];
|
||||
if (localTE[r] > max) {
|
||||
max = localTE[r];
|
||||
} else if (localTE[r] < min) {
|
||||
min = localTE[r];
|
||||
}
|
||||
// Update the previous value:
|
||||
if (k > 0) {
|
||||
pastVal -= maxShiftedValue[dest[r-k]];
|
||||
pastVal *= base;
|
||||
pastVal += dest[r];
|
||||
}
|
||||
}
|
||||
|
||||
average = average/(double) (rows - startObservationTime);
|
||||
|
||||
return localTE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standalone routine to
|
||||
* compute local conditional transfer entropy across a 2D spatiotemporal
|
||||
|
|
|
|||
|
|
@ -91,8 +91,29 @@ public class MultiInformationCalculatorDiscrete extends InfoMeasureCalculatorDis
|
|||
MatrixUtils.fill(marginalCounts, 0);
|
||||
}
|
||||
|
||||
// TODO Define just a simple addObservations for
|
||||
// int[][] states where there are just numVars variables
|
||||
/**
|
||||
* Given multiple time samples of a homogeneous array of variables (states),
|
||||
* add the observations of all sets of numVars of these
|
||||
* Do this for every time point
|
||||
*
|
||||
* @param states 2D array of values of an array of variables
|
||||
* at many observations (first index is time, second is variable index)
|
||||
*/
|
||||
public void addObservations(int[][] states) {
|
||||
for (int t = 0; t < states.length; t++) {
|
||||
// Add the marginal observations in, and compute the joint state value
|
||||
// for this sample at time t
|
||||
int jointValue = 0;
|
||||
for (int i = 0; i < numVars; i++) {
|
||||
int thisValue = states[t][i];
|
||||
marginalCounts[i][thisValue]++;
|
||||
jointValue *= base;
|
||||
jointValue += thisValue;
|
||||
}
|
||||
jointCount[jointValue]++;
|
||||
observations++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given one time sample of a homogeneous array of variables (states),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -41,6 +41,20 @@ import infodynamics.utils.RandomGenerator;
|
|||
* {@link MutualInfoCalculatorMultiVariate}
|
||||
* </p>
|
||||
*
|
||||
* <p>This calculator effectively implements the nearest neighbour method
|
||||
* laid out <a href="http://journals.plos.org/plosone/article?id=10.1371%2Fjournal.pone.0087357">here</a>
|
||||
* (see Ross reference below),
|
||||
* but we have subtract out the sample point from the count in the discrete space,
|
||||
* following the original KSG method.
|
||||
* </p>
|
||||
*
|
||||
* <p><b>References:</b><br/>
|
||||
* <ul>
|
||||
* <li> B.C. Ross, "Mutual Information between Discrete and Continuous Data Sets",
|
||||
* PLoS ONE 9(2): e87357. doi:
|
||||
* <a href="http://journals.plos.org/plosone/article?id=10.1371%2Fjournal.pone.0087357">10.1371/journal.pone.0087357</a></li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Joseph Lizier (<a href="joseph.lizier at gmail.com">email</a>,
|
||||
* <a href="http://lizier.me/joseph/">www</a>)
|
||||
*/
|
||||
|
|
@ -571,8 +585,11 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
|
|||
continuousNewStates, means, stds);
|
||||
}
|
||||
|
||||
int N = continuousNewStates.length; // number of observations
|
||||
double[] locals = new double[N];
|
||||
// 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
|
||||
|
|
@ -580,16 +597,16 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
|
|||
// double fixedPartOfLocals = MathsUtils.digamma(k) - 1.0/(double)k +
|
||||
// MathsUtils.digamma(N);
|
||||
// Instead do:
|
||||
double fixedPartOfLocals = MathsUtils.digamma(k) + MathsUtils.digamma(N);
|
||||
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));
|
||||
MathsUtils.digamma(k), MathsUtils.digamma(N_samplesForPdfs));
|
||||
}
|
||||
double avNx = 0;
|
||||
double avNy = 0;
|
||||
|
||||
for (int t = 0; t < N; t++) {
|
||||
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.
|
||||
|
|
@ -629,8 +646,8 @@ public class MutualInfoCalculatorMultiVariateWithDiscreteKraskov implements Mutu
|
|||
}
|
||||
}
|
||||
if (debug) {
|
||||
avNx /= (double)N;
|
||||
avNy /= (double)N;
|
||||
avNx /= (double)N_newObservations;
|
||||
avNy /= (double)N_newObservations;
|
||||
System.out.printf("Average n_x=%.3f, Average n_y=%.3f\n", avNx, avNy);
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -137,6 +137,18 @@ public class MatrixUtils {
|
|||
return total;
|
||||
}
|
||||
|
||||
public static double sum(double[][][] input) {
|
||||
double total = 0;
|
||||
for (int i = 0; i < input.length; i++) {
|
||||
for (int j = 0; j < input[i].length; j++) {
|
||||
for (int k = 0; k < input[i][j].length; k++) {
|
||||
total += input[i][j][k];
|
||||
}
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public static int sum(int[] input) {
|
||||
int total = 0;
|
||||
for (int i = 0; i < input.length; i++) {
|
||||
|
|
@ -155,6 +167,18 @@ public class MatrixUtils {
|
|||
return total;
|
||||
}
|
||||
|
||||
public static int sum(int[][][] input) {
|
||||
int total = 0;
|
||||
for (int i = 0; i < input.length; i++) {
|
||||
for (int j = 0; j < input[i].length; j++) {
|
||||
for (int k = 0; k < input[i][j].length; k++) {
|
||||
total += input[i][j][k];
|
||||
}
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of the sums for each column in the 2D input
|
||||
*
|
||||
|
|
@ -979,6 +1003,23 @@ public class MatrixUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the given source array into the required column number of the destination
|
||||
* @param destination
|
||||
* @param index1
|
||||
* @param index2
|
||||
* @param source
|
||||
*/
|
||||
public static void copyIntoColumn3D(int[][][] destination, int index1, int index2, int[] source) throws Exception {
|
||||
if (source.length != destination.length) {
|
||||
throw new Exception("Destination column is not of the same length as the source (" +
|
||||
destination.length + " vs " + source.length + ")");
|
||||
}
|
||||
for (int r = 0; r < destination.length; r++) {
|
||||
destination[r][index1][index2] = source[r];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new matrix with the columns of matrix1 joined on the back of matrix2
|
||||
*
|
||||
|
|
@ -1258,6 +1299,14 @@ public class MatrixUtils {
|
|||
return returnData;
|
||||
}
|
||||
|
||||
public static boolean[] selectColumn(boolean matrix[][], int columnNo) {
|
||||
boolean[] column = new boolean[matrix.length];
|
||||
for (int r = 0; r < matrix.length; r++) {
|
||||
column[r] = matrix[r][columnNo];
|
||||
}
|
||||
return column;
|
||||
}
|
||||
|
||||
public static int[] selectColumn(int matrix[][], int columnNo) {
|
||||
int[] column = new int[matrix.length];
|
||||
for (int r = 0; r < matrix.length; r++) {
|
||||
|
|
@ -1301,6 +1350,25 @@ public class MatrixUtils {
|
|||
return column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the required columns from the matrix
|
||||
*
|
||||
* @param matrix
|
||||
* @param fromCol
|
||||
* @param cols
|
||||
* @return
|
||||
*/
|
||||
public static double[][] selectColumns(double matrix[][],
|
||||
int fromCol, int cols) {
|
||||
double[][] data = new double[matrix.length][cols];
|
||||
for (int r = 0; r < matrix.length; r++) {
|
||||
for (int cIndex = 0; cIndex < cols; cIndex++) {
|
||||
data[r][cIndex] = matrix[r][cIndex + fromCol];
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the required columns from the matrix
|
||||
*
|
||||
|
|
@ -1394,6 +1462,27 @@ public class MatrixUtils {
|
|||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the required rows and columns from the matrix
|
||||
*
|
||||
* @param matrix 2D data array
|
||||
* @param fromRow index of the first row to return
|
||||
* @param rows number of rows (including the first) to return
|
||||
* @param fromCol index of the first column to return
|
||||
* @param cols number of columns (including the first) to return
|
||||
* @return a 2D data array of the selected rows and columns
|
||||
*/
|
||||
public static double[][] selectRowsAndColumns(double matrix[][],
|
||||
int fromRow, int rows, int fromCol, int cols) {
|
||||
double[][] data = new double[rows][cols];
|
||||
for (int rIndex = 0; rIndex < rows; rIndex++) {
|
||||
for (int cIndex = 0; cIndex < cols; cIndex++) {
|
||||
data[rIndex][cIndex] = matrix[rIndex + fromRow][cIndex + fromCol];
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public static double[][] selectFirstTwoDimenions(double[][][][] matrix, int d2, int d3) {
|
||||
double[][] newMatrix = new double[matrix.length][];
|
||||
for (int i = 0; i < matrix.length; i++) {
|
||||
|
|
@ -1654,6 +1743,24 @@ public class MatrixUtils {
|
|||
return matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs all embedding vectors of size k for the data.
|
||||
* There will be (data.length - k + 1) of these vectors returned.
|
||||
*
|
||||
* @param data time series data
|
||||
* @param k embedding length
|
||||
* @return An array of k-length embedding vectors
|
||||
*/
|
||||
public static int[][] makeDelayEmbeddingVector(int[] data, int k) {
|
||||
try {
|
||||
return makeDelayEmbeddingVector(data, k, k - 1, data.length - k + 1);
|
||||
} catch (Exception e) {
|
||||
// The above call should not throw an Exception, handle here
|
||||
// in a RuntimeException so this method doesn't throw one
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs all embedding vectors of size k for the data.
|
||||
* There will be (data.length - k + 1) of these vectors returned.
|
||||
|
|
@ -1672,6 +1779,39 @@ public class MatrixUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs numEmbeddingVectors embedding vectors of size k for the data,
|
||||
* with the first embedding vector having it's last time point at t=startKthPoint
|
||||
*
|
||||
* @param data time series data
|
||||
* @param k embedding length
|
||||
* @param startKthPoint last time point of the first embedding vector
|
||||
* (i.e. use k-1 if you want to go from the start)
|
||||
* @param numEmbeddingVectors the number of embedding vectors to return
|
||||
* (i.e. use data.length-k+1 if you go from the start and want all
|
||||
* of them extracted)
|
||||
* @return a 2D array of k-length embedding vectors.
|
||||
*/
|
||||
public static int[][] makeDelayEmbeddingVector(int[] data, int k,
|
||||
int startKthPoint, int numEmbeddingVectors) throws Exception {
|
||||
if (startKthPoint < k - 1) {
|
||||
throw new Exception("Start point t=" + startKthPoint + " is too early for a " +
|
||||
k + " length embedding vector");
|
||||
}
|
||||
if (numEmbeddingVectors + startKthPoint > data.length) {
|
||||
throw new Exception("Too many embedding vectors " + numEmbeddingVectors +
|
||||
" requested for the given startPoint " + startKthPoint +
|
||||
" and time series length " + data.length);
|
||||
}
|
||||
int[][] embeddingVectors = new int[numEmbeddingVectors][k];
|
||||
for (int t = startKthPoint; t < numEmbeddingVectors + startKthPoint; t++) {
|
||||
for (int i = 0; i < k; i++) {
|
||||
embeddingVectors[t - startKthPoint][i] = data[t - i];
|
||||
}
|
||||
}
|
||||
return embeddingVectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs numEmbeddingVectors embedding vectors of size k for the data,
|
||||
* with the first embedding vector having it's last time point at t=startKthPoint
|
||||
|
|
@ -1705,6 +1845,42 @@ public class MatrixUtils {
|
|||
return embeddingVectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs numEmbeddingVectors embedding vectors of size k for the data,
|
||||
* with embedding delay tau between each time sample for the vectors,
|
||||
* with the first embedding vector having it's last time point at t=startKthPoint
|
||||
*
|
||||
* @param data time series data
|
||||
* @param k embedding length
|
||||
* @param tau embedding delay between each point in the original time series
|
||||
* selected into each embedding vector
|
||||
* @param startKthPoint last time point of the first embedding vector
|
||||
* (i.e. use (k-1)*tau if you want to go from the start)
|
||||
* @param numEmbeddingVectors the number of embedding vectors to return
|
||||
* (i.e. use data.length-(k-1)*tau if you go from the start and want all
|
||||
* of them extracted)
|
||||
* @return a 2D array of k-length embedding vectors.
|
||||
*/
|
||||
public static int[][] makeDelayEmbeddingVector(int[] data, int k, int tau,
|
||||
int startKthPoint, int numEmbeddingVectors) throws Exception {
|
||||
if (startKthPoint < (k - 1)*tau) {
|
||||
throw new Exception("Start point t=" + startKthPoint + " is too early for a " +
|
||||
k + " length embedding vector with delay " + tau);
|
||||
}
|
||||
if (numEmbeddingVectors + startKthPoint > data.length) {
|
||||
throw new Exception("Too many embedding vectors " + numEmbeddingVectors +
|
||||
" requested for the given startPoint " + startKthPoint +
|
||||
" and time series length " + data.length);
|
||||
}
|
||||
int[][] embeddingVectors = new int[numEmbeddingVectors][k];
|
||||
for (int t = startKthPoint; t < numEmbeddingVectors + startKthPoint; t++) {
|
||||
for (int i = 0; i < k; i++) {
|
||||
embeddingVectors[t - startKthPoint][i] = data[t - i*tau];
|
||||
}
|
||||
}
|
||||
return embeddingVectors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs numEmbeddingVectors embedding vectors of size k for the data,
|
||||
* with embedding delay tau between each time sample for the vectors,
|
||||
|
|
@ -2163,6 +2339,25 @@ public class MatrixUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalises the elements along each column of the matrix
|
||||
*
|
||||
* @param matrix 2D matrix of doubles
|
||||
* @param means means to normalise to for each column of data
|
||||
* @param stds std deviations to normalise to for each column of data
|
||||
*/
|
||||
public static void normalise(double[][] matrix, double[] means, double[] stds) {
|
||||
for (int r = 0; r < matrix.length; r++) {
|
||||
for (int c = 0; c < matrix[r].length; c++) {
|
||||
matrix[r][c] = matrix[r][c] - means[c];
|
||||
if (!Double.isInfinite(1.0 / stds[c])) {
|
||||
matrix[r][c] /= stds[c];
|
||||
} // else we just subtract off the mean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalises the elements along each column of the matrix
|
||||
*
|
||||
|
|
@ -3706,9 +3901,16 @@ public class MatrixUtils {
|
|||
for (int r = 0; r < array.length; r++) {
|
||||
out.print(array[r] + " ");
|
||||
}
|
||||
out.println();
|
||||
// out.println();
|
||||
}
|
||||
|
||||
public static void printArray(PrintStream out, double[] array, int numDp) {
|
||||
for (int r = 0; r < array.length; r++) {
|
||||
out.printf(String.format("%%.%df ", numDp), array[r]);
|
||||
}
|
||||
// out.println();
|
||||
}
|
||||
|
||||
public static String arrayToString(double[] array) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int r = 0; r < array.length; r++) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,10 @@ public abstract class NearestNeighbourSearcher {
|
|||
public static NearestNeighbourSearcher create(double[][] data)
|
||||
throws Exception {
|
||||
|
||||
if (data[0].length == 1) {
|
||||
if ((data == null) || (data[0].length == 0)) {
|
||||
// We have null data:
|
||||
return null;
|
||||
} else if (data[0].length == 1) {
|
||||
// We have univariate data:
|
||||
return new UnivariateNearestNeighbourSearcher(MatrixUtils.selectColumn(data, 0));
|
||||
} else {
|
||||
|
|
@ -271,6 +274,23 @@ public abstract class NearestNeighbourSearcher {
|
|||
public abstract int countPointsWithinR(int sampleIndex, double r,
|
||||
int dynCorrExclTime, boolean allowEqualToR);
|
||||
|
||||
/**
|
||||
* As per {@link #countPointsWithinR(int, double, boolean)}
|
||||
* however the search is to match a specified sample point (not a sample
|
||||
* point within the search space itself).
|
||||
*
|
||||
* @param sampleVectors sample vectors to find the neighbours within r
|
||||
* for
|
||||
* @param r radius within which to count points
|
||||
* @param dynCorrExclTime time window within which to exclude
|
||||
* points to be counted. Is >= 0. 0 means only exclude sampleIndex.
|
||||
* @param allowEqualToR if true, then count points at radius r also,
|
||||
* otherwise only those strictly within r
|
||||
* @return the count of points within r.
|
||||
*/
|
||||
public abstract int countPointsWithinR(double[][] sampleVectors, double r,
|
||||
boolean allowEqualToR);
|
||||
|
||||
/**
|
||||
* As per {@link #countPointsWithinR(int, double, boolean)}
|
||||
* however returns a collection rather than a count.
|
||||
|
|
@ -331,6 +351,60 @@ public abstract class NearestNeighbourSearcher {
|
|||
int sampleIndex, double r, int dynCorrExclTime,
|
||||
boolean allowEqualToR, boolean[] isWithinR, int[] indicesWithinR);
|
||||
|
||||
/**
|
||||
* As per {@link #findPointsWithinR(int, double, boolean, boolean[], int[])}
|
||||
* however incorporates dynamic correlation exclusion.
|
||||
* </p>
|
||||
*
|
||||
* @param sampleIndex sample index in the data to find a nearest neighbour
|
||||
* for
|
||||
* @param r radius within which to count points
|
||||
* @param dynCorrExclTime time window within which to exclude
|
||||
* points to be counted. Is >= 0. 0 means only exclude sampleIndex.
|
||||
* @param allowEqualToR if true, then count points at radius r also,
|
||||
* otherwise only those strictly within r
|
||||
* @param isWithinR the array MUST be passed in with all points set to
|
||||
* false initially, and is returned indicating whether each sample was
|
||||
* found to be within r of that at sampleIndex.
|
||||
* @param distancesWithinRInOrder the array must be passed in and is
|
||||
* returned with distances for each point found to be within r. Values
|
||||
* at other indices are not defined.
|
||||
* @param distancesAndIndicesWithinR is returned as
|
||||
* a list of distances (in column index 0)
|
||||
* and array indices (in column index 1)
|
||||
* for points marked as true in isWithinR, terminated with a -1 value on the index.
|
||||
* @return the point count
|
||||
*/
|
||||
public abstract int findPointsWithinR(
|
||||
int sampleIndex, double r, int dynCorrExclTime,
|
||||
boolean allowEqualToR, boolean[] isWithinR,
|
||||
double[] distancesWithinRInOrder,
|
||||
double[][] distancesAndIndicesWithinR);
|
||||
|
||||
/**
|
||||
* As per {@link #countPointsWithinR(int, double, boolean)}
|
||||
* however records the nearest neighbours for a sample data point
|
||||
* (which may not be in the search tree), within the isWithinR
|
||||
* and indicesWithinR arrays, which must be constructed before
|
||||
* calling this method, with length at or exceeding the total
|
||||
* number of data points. indicesWithinR is
|
||||
* </p>
|
||||
*
|
||||
* @param r radius within which to count points
|
||||
* @param sampleVectors sample vectors to find the neighbours within r
|
||||
* for
|
||||
* @param allowEqualToR if true, then count points at radius r also,
|
||||
* otherwise only those strictly within r
|
||||
* @param isWithinR the array MUST be passed in with all points set to
|
||||
* false initially, and is returned indicating whether each sample was
|
||||
* found to be within r of that at sampleIndex.
|
||||
* @param indicesWithinR a list of array indices
|
||||
* for points marked as true in isWithinR, terminated with a -1 value.
|
||||
*/
|
||||
public abstract void findPointsWithinR(
|
||||
double r, double[][] sampleVectors,
|
||||
boolean allowEqualToR, boolean[] isWithinR, int[] indicesWithinR);
|
||||
|
||||
/**
|
||||
* As per {@link #countPointsWithinR(int, double, int, boolean)}
|
||||
* with allowEqualToR == false
|
||||
|
|
@ -472,5 +546,20 @@ public abstract class NearestNeighbourSearcher {
|
|||
public abstract int countPointsWithinR(int sampleIndex, double r,
|
||||
boolean allowEqualToR, boolean[] additionalCriteria);
|
||||
|
||||
|
||||
/**
|
||||
* As per {@link #countPointsWithinR(double[][], double, boolean)}
|
||||
* however each point is subject to also meeting the additional
|
||||
* criteria of being true in additionalCriteria.
|
||||
*
|
||||
* @param sampleVectors sample vectors to find the neighbours within r
|
||||
* for
|
||||
* @param r radius within which to count points
|
||||
* @param allowEqualToR if true, then count points at radius r also,
|
||||
* otherwise only those strictly within r
|
||||
* @param additionalCriteria array of booleans. Only count a point if it
|
||||
* is within r and is true in additionalCrtieria.
|
||||
* @return the count of points within r.
|
||||
*/
|
||||
public abstract int countPointsWithinR(double[][] sampleVectors, double r,
|
||||
boolean allowEqualToR, boolean[] additionalCriteria);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package infodynamics.utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Vector;
|
||||
|
|
@ -43,6 +44,7 @@ public class UnivariateNearestNeighbourSearcher extends NearestNeighbourSearcher
|
|||
* Number of samples in the data
|
||||
*/
|
||||
protected int numObservations = 0;
|
||||
|
||||
/**
|
||||
* An array of indices to the data in originalDataSet,
|
||||
* sorted in order (min to max).
|
||||
|
|
@ -55,6 +57,11 @@ public class UnivariateNearestNeighbourSearcher extends NearestNeighbourSearcher
|
|||
*/
|
||||
protected int[] indicesInSortedArray = null;
|
||||
|
||||
/**
|
||||
* An array of the original values sorted into ascending order.
|
||||
*/
|
||||
protected double[] sortedValues = null;
|
||||
|
||||
public UnivariateNearestNeighbourSearcher(double[][] data) throws Exception {
|
||||
// Ideally we would not call the constructor until after the following check,
|
||||
// but the constructor must come first in Java.
|
||||
|
|
@ -88,6 +95,13 @@ public class UnivariateNearestNeighbourSearcher extends NearestNeighbourSearcher
|
|||
sortedArrayIndices[t] = (int) dataWithIndices[t][1];
|
||||
indicesInSortedArray[(int) dataWithIndices[t][1]] = t;
|
||||
}
|
||||
// Finally, create the sorted array of the values:
|
||||
// (these are only used by a few methods, but better to create the array now
|
||||
// rather than hit concurrency issues trying to create it when required
|
||||
sortedValues = new double[numObservations];
|
||||
for (int i = 0; i < numObservations; i++) {
|
||||
sortedValues[i] = originalDataSet[sortedArrayIndices[i]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -359,6 +373,47 @@ public class UnivariateNearestNeighbourSearcher extends NearestNeighbourSearcher
|
|||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of points within norms r_upper and r_lower for a given
|
||||
* sample index in the data set. The node itself is
|
||||
* excluded from the search.
|
||||
* Nearest neighbour function to compare to radii is the specified norm.
|
||||
* (If {@link EuclideanUtils#NORM_EUCLIDEAN} was selected, then the supplied
|
||||
* r should be the required Euclidean norm <b>squared</b>, since we switch it
|
||||
* to {@link EuclideanUtils#NORM_EUCLIDEAN_SQUARED} internally).
|
||||
*/
|
||||
public int countPointsWithinRs(int sampleIndex, double r_upper,
|
||||
double r_lower, boolean allowEqualToR) {
|
||||
int count = 0;
|
||||
// Find where this node sits in the sorted array:
|
||||
int indexInSortedArray = indicesInSortedArray[sampleIndex];
|
||||
// Check the points with smaller data values first:
|
||||
for (int i = indexInSortedArray - 1; i >= 0; i--) {
|
||||
double theNorm = norm(originalDataSet[sampleIndex],
|
||||
originalDataSet[sortedArrayIndices[i]], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r_lower)) ||
|
||||
(!allowEqualToR && (theNorm < r_lower))) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
// Next check the points with larger data values:
|
||||
for (int i = indexInSortedArray + 1; i < numObservations; i++) {
|
||||
double theNorm = norm(originalDataSet[sampleIndex],
|
||||
originalDataSet[sortedArrayIndices[i]], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r_upper)) ||
|
||||
(!allowEqualToR && (theNorm < r_upper))) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of points within norm r for a given
|
||||
* sample index in the data set. Nodes within dynCorrExclTime
|
||||
|
|
@ -408,6 +463,124 @@ public class UnivariateNearestNeighbourSearcher extends NearestNeighbourSearcher
|
|||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of points within norm r for a given
|
||||
* sample index in the data set, or larger than r. The node itself is
|
||||
* excluded from the search.
|
||||
* Nearest neighbour function to compare to r is the specified norm.
|
||||
* (If {@link EuclideanUtils#NORM_EUCLIDEAN} was selected, then the supplied
|
||||
* r should be the required Euclidean norm <b>squared</b>, since we switch it
|
||||
* to {@link EuclideanUtils#NORM_EUCLIDEAN_SQUARED} internally).
|
||||
*/
|
||||
public int countPointsWithinROrLarger(int sampleIndex, double r, boolean allowEqualToR) {
|
||||
int count = 0;
|
||||
// Find where this node sits in the sorted array:
|
||||
int indexInSortedArray = indicesInSortedArray[sampleIndex];
|
||||
// Check the points with smaller data values first:
|
||||
for (int i = indexInSortedArray - 1; i >= 0; i--) {
|
||||
double theNorm = norm(originalDataSet[sampleIndex],
|
||||
originalDataSet[sortedArrayIndices[i]], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r)) ||
|
||||
(!allowEqualToR && (theNorm < r))) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
count += numObservations - indexInSortedArray - 1;
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of points less than the sample at the given index,
|
||||
* and outside norm r. allowEqualToR means to be considered same as the sample.
|
||||
* The node itself is
|
||||
* excluded from the search.
|
||||
* Nearest neighbour function to compare to r is the specified norm.
|
||||
* (If {@link EuclideanUtils#NORM_EUCLIDEAN} was selected, then the supplied
|
||||
* r should be the required Euclidean norm <b>squared</b>, since we switch it
|
||||
* to {@link EuclideanUtils#NORM_EUCLIDEAN_SQUARED} internally).
|
||||
*/
|
||||
public int countPointsSmallerAndOutsideR(int sampleIndex, double r, boolean allowEqualToR) {
|
||||
// Find where this node sits in the sorted array:
|
||||
int indexInSortedArray = indicesInSortedArray[sampleIndex];
|
||||
// Check the points with smaller data values first:
|
||||
int i;
|
||||
for (i = indexInSortedArray - 1; i >= 0; i--) {
|
||||
double theNorm = norm(originalDataSet[sampleIndex],
|
||||
originalDataSet[sortedArrayIndices[i]], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r)) ||
|
||||
(!allowEqualToR && (theNorm < r))) {
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countPointsWithinR(double[][] sampleVectors, double r, boolean allowEqualToR) {
|
||||
if (sampleVectors.length > 1) {
|
||||
throw new RuntimeException("sampleVectors[][] argument must have length 1 on each dimension when calling a UnivariateNearstNeighbourSearcher");
|
||||
}
|
||||
if (sampleVectors[0].length > 1) {
|
||||
throw new RuntimeException("sampleVectors[][] argument must have length 1 on each dimension when calling a UnivariateNearstNeighbourSearcher");
|
||||
}
|
||||
|
||||
return countPointsWithinR(sampleVectors[0][0], r, allowEqualToR);
|
||||
}
|
||||
|
||||
/**
|
||||
* As per {@link #countPointsWithinR(double, double, boolean)} however
|
||||
* for a specific sample value (not one within the data set itself).
|
||||
*
|
||||
* @param sampleValue the specific value to search for
|
||||
*/
|
||||
public int countPointsWithinR(double sampleValue, double r, boolean allowEqualToR) {
|
||||
|
||||
int count = 0;
|
||||
|
||||
// Find where this value would sit in the sorted array.
|
||||
int potentialArrayIndex = Arrays.binarySearch(sortedValues, sampleValue);
|
||||
if (potentialArrayIndex < 0) {
|
||||
// The sampleValue was not found because it wasn't in the original set of values:
|
||||
// invert the return value of binarySearch to find where the sampleValue would be inserted into the array:
|
||||
potentialArrayIndex = -potentialArrayIndex - 1;
|
||||
// This means at potentialArrayIndex and higher are values >= sampleValue, while
|
||||
// at potentialArrayIndex-1 and lower are values <= sampleValue.
|
||||
// potentialArrayIndex will be array.length if all values are smaller than it.
|
||||
} // else potentialArrayIndex is pointing to one instance of this value in the sorted array
|
||||
|
||||
// Check the points with smaller data values first
|
||||
for (int i = potentialArrayIndex - 1; i >= 0; i--) {
|
||||
double theNorm = norm(sampleValue,
|
||||
sortedValues[i], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r)) ||
|
||||
(!allowEqualToR && (theNorm < r))) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
// Next check the points with larger data values: (and including potentialArrayIndex, since we're
|
||||
// not excluding it since it's not nominally part of the underlying data set)
|
||||
for (int i = potentialArrayIndex; i < numObservations; i++) {
|
||||
double theNorm = norm(sampleValue,
|
||||
sortedValues[i], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r)) ||
|
||||
(!allowEqualToR && (theNorm < r))) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the collection of points within norm r for a given
|
||||
* sample index in the data set. The node itself is
|
||||
|
|
@ -565,6 +738,214 @@ public class UnivariateNearestNeighbourSearcher extends NearestNeighbourSearcher
|
|||
indicesWithinR[indexInIndicesWithinR++] = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findPointsWithinR(int sampleIndex, double r,
|
||||
int dynCorrExclTime, boolean allowEqualToR, boolean[] isWithinR,
|
||||
double[] distancesWithinRInOrder,
|
||||
double[][] distancesAndIndicesWithinR) {
|
||||
int indexInIndicesWithinR = 0;
|
||||
// Find where this node sits in the sorted array:
|
||||
int indexInSortedArray = indicesInSortedArray[sampleIndex];
|
||||
// Check the points with smaller data values first:
|
||||
for (int i = indexInSortedArray - 1; i >= 0; i--) {
|
||||
if (Math.abs(sampleIndex - sortedArrayIndices[i]) <= dynCorrExclTime) {
|
||||
// Can't count this point, but keep checking:
|
||||
continue;
|
||||
}
|
||||
double theNorm = norm(originalDataSet[sampleIndex],
|
||||
originalDataSet[sortedArrayIndices[i]], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r)) ||
|
||||
(!allowEqualToR && (theNorm < r))) {
|
||||
isWithinR[sortedArrayIndices[i]] = true;
|
||||
distancesWithinRInOrder[sortedArrayIndices[i]] = theNorm;
|
||||
distancesAndIndicesWithinR[indexInIndicesWithinR][0] = theNorm;
|
||||
distancesAndIndicesWithinR[indexInIndicesWithinR++][1] = sortedArrayIndices[i];
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
// Next check the points with larger data values:
|
||||
for (int i = indexInSortedArray + 1; i < numObservations; i++) {
|
||||
if (Math.abs(sampleIndex - sortedArrayIndices[i]) <= dynCorrExclTime) {
|
||||
// Can't count this point, but keep checking:
|
||||
continue;
|
||||
}
|
||||
double theNorm = norm(originalDataSet[sampleIndex],
|
||||
originalDataSet[sortedArrayIndices[i]], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r)) ||
|
||||
(!allowEqualToR && (theNorm < r))) {
|
||||
isWithinR[sortedArrayIndices[i]] = true;
|
||||
distancesWithinRInOrder[sortedArrayIndices[i]] = theNorm;
|
||||
distancesAndIndicesWithinR[indexInIndicesWithinR][0] = theNorm;
|
||||
distancesAndIndicesWithinR[indexInIndicesWithinR++][1] = sortedArrayIndices[i];
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
// Write the terminating integer into the indicesWithinR array:
|
||||
distancesAndIndicesWithinR[indexInIndicesWithinR++][1] = -1;
|
||||
return indexInIndicesWithinR - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* As per {@link #findPointsWithinR(int, double, int, boolean, boolean[], double[], double[][])}
|
||||
* only we have additional criteria, and we report max values taking that additional
|
||||
* criteria into account.
|
||||
*
|
||||
* @param sampleIndex sample index in the data to find a nearest neighbour
|
||||
* for
|
||||
* @param r radius within which to count points
|
||||
* @param dynCorrExclTime time window within which to exclude
|
||||
* points to be counted. Is >= 0. 0 means only exclude sampleIndex.
|
||||
* @param allowEqualToR if true, then count points at radius r also,
|
||||
* otherwise only those strictly within r
|
||||
* @param additionalCriteria an array indicating whether each sample for
|
||||
* should be tested for this search point
|
||||
* @param reportDistancesAsMaxWith an array of distances for
|
||||
* points flagged in additionalCriteria: distancesAndIndicesWithinR should
|
||||
* return the max of this value or the distances within r computed here.
|
||||
* Values at indices where distances are not within r are not defined.
|
||||
* @param distancesAndIndicesWithinR a list of distances (in column index 0)
|
||||
* and array indices (in column index 1)
|
||||
* for points found to be within r, terminated with a -1 value on the index.
|
||||
* @return the next available index in distancesAndIndicesWithinR after the method is complete
|
||||
*/
|
||||
public int findPointsWithinRAndTakeRadiusMax(int sampleIndex, double r,
|
||||
int dynCorrExclTime, boolean allowEqualToR,
|
||||
boolean[] additionalCriteria,
|
||||
double[] reportDistancesAsMaxWith,
|
||||
double[][] distancesAndIndicesWithinR) {
|
||||
int indexInIndicesWithinR = 0;
|
||||
// Find where this node sits in the sorted array:
|
||||
int indexInSortedArray = indicesInSortedArray[sampleIndex];
|
||||
// Check the points with smaller data values first:
|
||||
for (int i = indexInSortedArray - 1; i >= 0; i--) {
|
||||
if (!additionalCriteria[sortedArrayIndices[i]] ||
|
||||
(Math.abs(sampleIndex - sortedArrayIndices[i]) <= dynCorrExclTime)) {
|
||||
// Can't count this point, but keep checking:
|
||||
continue;
|
||||
}
|
||||
double theNorm = norm(originalDataSet[sampleIndex],
|
||||
originalDataSet[sortedArrayIndices[i]], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r)) ||
|
||||
(!allowEqualToR && (theNorm < r))) {
|
||||
if (theNorm < reportDistancesAsMaxWith[sortedArrayIndices[i]]) {
|
||||
theNorm = reportDistancesAsMaxWith[sortedArrayIndices[i]];
|
||||
}
|
||||
distancesAndIndicesWithinR[indexInIndicesWithinR][0] = theNorm;
|
||||
distancesAndIndicesWithinR[indexInIndicesWithinR++][1] = sortedArrayIndices[i];
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
// Next check the points with larger data values:
|
||||
for (int i = indexInSortedArray + 1; i < numObservations; i++) {
|
||||
if (!additionalCriteria[sortedArrayIndices[i]] ||
|
||||
(Math.abs(sampleIndex - sortedArrayIndices[i]) <= dynCorrExclTime)) {
|
||||
// Can't count this point, but keep checking:
|
||||
continue;
|
||||
}
|
||||
double theNorm = norm(originalDataSet[sampleIndex],
|
||||
originalDataSet[sortedArrayIndices[i]], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r)) ||
|
||||
(!allowEqualToR && (theNorm < r))) {
|
||||
if (theNorm < reportDistancesAsMaxWith[sortedArrayIndices[i]]) {
|
||||
theNorm = reportDistancesAsMaxWith[sortedArrayIndices[i]];
|
||||
}
|
||||
distancesAndIndicesWithinR[indexInIndicesWithinR][0] = theNorm;
|
||||
distancesAndIndicesWithinR[indexInIndicesWithinR++][1] = sortedArrayIndices[i];
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
// Write the terminating integer into the indicesWithinR array:
|
||||
distancesAndIndicesWithinR[indexInIndicesWithinR++][1] = -1;
|
||||
return indexInIndicesWithinR - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* As per {@link #findPointsWithinR(int, double, int, boolean, boolean[], int[])}
|
||||
* only for a specific data point.
|
||||
*
|
||||
* Note that the method signature takes a 2D sample vector, however since this is class
|
||||
* only searches univariate spaces, then there must only be one value in #sampleVector
|
||||
* (i.e. length one on both array indices).
|
||||
*
|
||||
*
|
||||
* @see super{@link #findPointsWithinR(double, double[][], boolean, boolean[], int[])}
|
||||
*/
|
||||
@Override
|
||||
public void findPointsWithinR(double r, double[][] sampleVectors,
|
||||
boolean allowEqualToR, boolean[] isWithinR, int[] indicesWithinR) {
|
||||
|
||||
if (sampleVectors.length > 1) {
|
||||
throw new RuntimeException("sampleVectors[][] argument must have length 1 on each dimension when calling a UnivariateNearstNeighbourSearcher");
|
||||
}
|
||||
if (sampleVectors[0].length > 1) {
|
||||
throw new RuntimeException("sampleVectors[][] argument must have length 1 on each dimension when calling a UnivariateNearstNeighbourSearcher");
|
||||
}
|
||||
|
||||
findPointsWithinR(r, sampleVectors[0][0], allowEqualToR, isWithinR, indicesWithinR);
|
||||
}
|
||||
|
||||
/**
|
||||
* As per {@link #findPointsWithinR(int, double, int, boolean, boolean[], int[])}
|
||||
* only for a specific data point.
|
||||
*
|
||||
* @see super{@link #findPointsWithinR(double, double[][], boolean, boolean[], int[])}
|
||||
*
|
||||
* @param sampleValue the specific point being searched (not a data point in the search structure)
|
||||
*/
|
||||
public void findPointsWithinR(double r, double sampleValue,
|
||||
boolean allowEqualToR, boolean[] isWithinR, int[] indicesWithinR) {
|
||||
int indexInIndicesWithinR = 0;
|
||||
|
||||
// Find where this value would sit in the sorted array.
|
||||
int potentialArrayIndex = Arrays.binarySearch(sortedValues, sampleValue);
|
||||
if (potentialArrayIndex < 0) {
|
||||
// The sampleValue was not found because it wasn't in the original set of values:
|
||||
// invert the return value of binarySearch to find where the sampleValue would be inserted into the array:
|
||||
potentialArrayIndex = -potentialArrayIndex - 1;
|
||||
// This means at potentialArrayIndex and higher are values >= sampleValue, while
|
||||
// at potentialArrayIndex-1 and lower are values <= sampleValue.
|
||||
// potentialArrayIndex will be array.length if all values are smaller than it.
|
||||
} // else potentialArrayIndex is pointing to one instance of this value in the sorted array
|
||||
|
||||
// Check the points with smaller data values first:
|
||||
for (int i = potentialArrayIndex - 1; i >= 0; i--) {
|
||||
double theNorm = norm(sampleValue,
|
||||
sortedValues[i], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r)) ||
|
||||
(!allowEqualToR && (theNorm < r))) {
|
||||
isWithinR[sortedArrayIndices[i]] = true;
|
||||
indicesWithinR[indexInIndicesWithinR++] = sortedArrayIndices[i];
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
// Next check the points with larger data values: (and including potentialArrayIndex, since we're
|
||||
// not excluding it since it's not nominally part of the underlying data set)
|
||||
for (int i = potentialArrayIndex; i < numObservations; i++) {
|
||||
double theNorm = norm(sampleValue,
|
||||
sortedValues[i], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r)) ||
|
||||
(!allowEqualToR && (theNorm < r))) {
|
||||
isWithinR[sortedArrayIndices[i]] = true;
|
||||
indicesWithinR[indexInIndicesWithinR++] = sortedArrayIndices[i];
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
// Write the terminating integer into the indicesWithinR array:
|
||||
indicesWithinR[indexInIndicesWithinR++] = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countPointsWithinR(int sampleIndex, double r, boolean allowEqualToR,
|
||||
boolean[] additionalCriteria) {
|
||||
|
|
@ -618,4 +999,103 @@ public class UnivariateNearestNeighbourSearcher extends NearestNeighbourSearcher
|
|||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* As per {@link #countPointsWithinR(int, double, boolean, boolean[])}
|
||||
* only for a specific data point.
|
||||
*
|
||||
* Note that the method signature takes a 2D sample vector, however since this is class
|
||||
* only searches univariate spaces, then there must only be one value in #sampleVector
|
||||
* (i.e. length one on both array indices).
|
||||
*
|
||||
*
|
||||
* @see super{@link #findPointsWithinR(double, double[][], boolean, boolean[], int[])}
|
||||
*/
|
||||
@Override
|
||||
public int countPointsWithinR(double[][] sampleVectors, double r, boolean allowEqualToR,
|
||||
boolean[] additionalCriteria) {
|
||||
|
||||
if (sampleVectors.length > 1) {
|
||||
throw new RuntimeException("sampleVectors[][] argument must have length 1 on each dimension when calling a UnivariateNearstNeighbourSearcher");
|
||||
}
|
||||
if (sampleVectors[0].length > 1) {
|
||||
throw new RuntimeException("sampleVectors[][] argument must have length 1 on each dimension when calling a UnivariateNearstNeighbourSearcher");
|
||||
}
|
||||
|
||||
return countPointsWithinR(sampleVectors[0][0], r, allowEqualToR, additionalCriteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* As per {@link #countPointsWithinR(int, double, boolean, boolean[])}
|
||||
* only for a specific data point.
|
||||
*
|
||||
* @param sampleValue the specific point being searched (not a data point in the search structure)
|
||||
*/
|
||||
public int countPointsWithinR(double sampleValue, double r, boolean allowEqualToR,
|
||||
boolean[] additionalCriteria) {
|
||||
|
||||
int count = 0;
|
||||
|
||||
// Find where this value would sit in the sorted array.
|
||||
int potentialArrayIndex = Arrays.binarySearch(sortedValues, sampleValue);
|
||||
if (potentialArrayIndex < 0) {
|
||||
// The sampleValue was not found because it wasn't in the original set of values:
|
||||
// invert the return value of binarySearch to find where the sampleValue would be inserted into the array:
|
||||
potentialArrayIndex = -potentialArrayIndex - 1;
|
||||
// This means at potentialArrayIndex and higher are values >= sampleValue, while
|
||||
// at potentialArrayIndex-1 and lower are values <= sampleValue.
|
||||
// potentialArrayIndex will be array.length if all values are smaller than it.
|
||||
} // else potentialArrayIndex is pointing to one instance of this value in the sorted array
|
||||
|
||||
// Check the points with smaller data values first:
|
||||
for (int i = potentialArrayIndex - 1; i >= 0; i--) {
|
||||
if (!additionalCriteria[sortedArrayIndices[i]]) {
|
||||
// This point failed the additional criteria,
|
||||
// so skip checking it.
|
||||
// We check this first, even though it's the norm
|
||||
// that really determines whether we need to continue
|
||||
// checking or not. In some circumstances this may be
|
||||
// slower, but for our main application - KSG conditional MI -
|
||||
// this should be faster.
|
||||
continue;
|
||||
}
|
||||
double theNorm = norm(sampleValue,
|
||||
sortedValues[i], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r)) ||
|
||||
(!allowEqualToR && (theNorm < r))) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
// Next check the points with larger data values: (and including potentialArrayIndex, since we're
|
||||
// not excluding it since it's not nominally part of the underlying data set)
|
||||
for (int i = potentialArrayIndex; i < numObservations; i++) {
|
||||
if (!additionalCriteria[sortedArrayIndices[i]]) {
|
||||
// This point failed the additional criteria,
|
||||
// so skip checking it.
|
||||
// We check this first, even though it's the norm
|
||||
// that really determines whether we need to continue
|
||||
// checking or not. In some circumstances this may be
|
||||
// slower, but for our main application - KSG conditional MI -
|
||||
// this should be faster.
|
||||
continue;
|
||||
}
|
||||
double theNorm = norm(sampleValue,
|
||||
sortedValues[i], normTypeToUse);
|
||||
if ((allowEqualToR && (theNorm <= r)) ||
|
||||
(!allowEqualToR && (theNorm < r))) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
// Else no point checking further points
|
||||
break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public int getNumObservations() {
|
||||
return numObservations;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,4 +272,10 @@ public class ActiveInfoStorageCalculatorKernelDirect
|
|||
double[] newObservations) throws Exception {
|
||||
throw new Exception("Not yet implemented");
|
||||
}
|
||||
|
||||
public void addObservations(double[] observations, boolean[] valid)
|
||||
throws Exception {
|
||||
// Probably won't need to implement this, as we're only using this class for testing
|
||||
throw new Exception("Not implemented yet");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,13 @@
|
|||
|
||||
package infodynamics.measures.continuous.kraskov;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import infodynamics.utils.ArrayFileReader;
|
||||
import infodynamics.utils.EmpiricalMeasurementDistribution;
|
||||
import infodynamics.utils.MathsUtils;
|
||||
import infodynamics.utils.MatrixUtils;
|
||||
import infodynamics.utils.RandomGenerator;
|
||||
|
||||
public class ConditionalMutualInfoMultiVariateTester
|
||||
extends infodynamics.measures.continuous.ConditionalMutualInfoMultiVariateAbstractTester {
|
||||
|
|
@ -423,7 +428,109 @@ public class ConditionalMutualInfoMultiVariateTester
|
|||
1, 1,
|
||||
kNNs, expectedFromTRENTOOL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unit test for conditional MI on new observations.
|
||||
* We can test this against the calculator itself. If we send in the original data set as new observations,
|
||||
* we can recreate the neighbour counts (plus one) by setting K to 1 larger (to account for the data point itself), and
|
||||
* account for the change in bias.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void testMultivariateCondMIForNewObservations() throws Exception {
|
||||
|
||||
ArrayFileReader afr = new ArrayFileReader("demos/data/4ColsPairedOneStepNoisyDependence-1.txt");
|
||||
double[][] data = afr.getDouble2DMatrix();
|
||||
// RandomGenerator rg = new RandomGenerator();
|
||||
//double[][] data = rg.generateNormalData(50, 4, 0, 1);
|
||||
|
||||
// Use various Kraskov k nearest neighbours parameter
|
||||
int[] kNNs = {4};
|
||||
|
||||
System.out.println("Kraskov Cond MI testing new Observations:");
|
||||
|
||||
for (int ki = 0; ki < kNNs.length; ki++) {
|
||||
ConditionalMutualInfoCalculatorMultiVariateKraskov condMiCalc = getNewCalc(1);
|
||||
ConditionalMutualInfoCalculatorMultiVariateKraskov condMiCalcForNew = getNewCalc(1);
|
||||
// Let it normalise by default
|
||||
// And no noise addition to protect the integrity of our neighbour counts under both techniques here:
|
||||
condMiCalc.setProperty(ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_ADD_NOISE, "0");
|
||||
condMiCalcForNew.setProperty(ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_ADD_NOISE, "0");
|
||||
double[][] var1 = MatrixUtils.selectColumns(data, new int[] {0});
|
||||
double[][] var2 = MatrixUtils.selectColumns(data, new int[] {1});
|
||||
double[][] condVar = MatrixUtils.selectColumns(data, new int[] {2, 3});
|
||||
|
||||
// Compute MI(0;1|2,3) :
|
||||
condMiCalc.setProperty(
|
||||
ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_K,
|
||||
Integer.toString(kNNs[ki]));
|
||||
System.out.println("Main calc normalisation is " + condMiCalc.getProperty(condMiCalc.PROP_NORMALISE));
|
||||
condMiCalc.initialise(var1[0].length, var2[0].length, condVar[0].length);
|
||||
condMiCalc.setObservations(var1, var2, condVar);
|
||||
double miAverage = condMiCalc.computeAverageLocalOfObservations();
|
||||
// Now compute as new observations:
|
||||
condMiCalcForNew.setProperty(
|
||||
ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_K,
|
||||
Integer.toString(kNNs[ki] + 1)); // Using K = K + 1
|
||||
// condMiCalcForNew.setProperty(
|
||||
// ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NUM_THREADS,
|
||||
// "1");
|
||||
System.out.println("New obs calc normalisation is " + condMiCalcForNew.getProperty(condMiCalc.PROP_NORMALISE));
|
||||
condMiCalcForNew.initialise(var1[0].length, var2[0].length, condVar[0].length);
|
||||
condMiCalcForNew.setObservations(var1, var2, condVar);
|
||||
// condMiCalc.setDebug(true);
|
||||
//condMiCalcForNew.setDebug(true);
|
||||
double[] newLocals = condMiCalcForNew.computeLocalUsingPreviousObservations(var1, var2, condVar);
|
||||
//condMiCalcForNew.setDebug(false);
|
||||
double averageFromNewObservations = MatrixUtils.mean(newLocals);
|
||||
// We can't check this directly, so test each point individually:
|
||||
for (int t = 0; t < data.length; t++) {
|
||||
double[] originalNeighbourCounts = condMiCalc.partialComputeFromObservations(t, 1, false);
|
||||
// Need to normalise the data before passing it in here -- this is
|
||||
// what is happening inside computeLocalUsingPreviousObservations above
|
||||
double[] newObsNeighbourCounts = condMiCalcForNew.partialComputeFromNewObservations(
|
||||
t, 1,
|
||||
MatrixUtils.normaliseIntoNewArray(var1),
|
||||
MatrixUtils.normaliseIntoNewArray(var2),
|
||||
MatrixUtils.normaliseIntoNewArray(condVar), false);
|
||||
// Now check each return count in the array:
|
||||
if (originalNeighbourCounts[1] != newObsNeighbourCounts[1] - 1) {
|
||||
System.out.println("Assertion failure for t=" + t + ": expected " + originalNeighbourCounts[1] +
|
||||
" from original, plus 1, but got " + newObsNeighbourCounts[1]);
|
||||
System.out.print("Actual raw data was: ");
|
||||
MatrixUtils.printArray(System.out, data[0]);
|
||||
}
|
||||
assertEquals(originalNeighbourCounts[1], newObsNeighbourCounts[1] - 1); // Nxz should be 1 higher
|
||||
assertEquals(originalNeighbourCounts[2], newObsNeighbourCounts[2] - 1); // Nyz should be 1 higher
|
||||
assertEquals(originalNeighbourCounts[3], newObsNeighbourCounts[3] - 1); // Nz should be 1 higher
|
||||
// Now check the local value at each point using these verified counts:
|
||||
double newLocalValue = condMiCalcForNew.digammaK -
|
||||
MathsUtils.digamma((int) newObsNeighbourCounts[1] + 1) -
|
||||
MathsUtils.digamma((int) newObsNeighbourCounts[2] + 1) +
|
||||
MathsUtils.digamma((int) newObsNeighbourCounts[3] + 1);
|
||||
/* System.out.printf("n=%d, n_xz=%d, n_yz=%d, n_z=%d, local=%.4f," +
|
||||
" digamma(n_xz+1)=%.5f, digamma(n_yz+1)=%.5f, digamma(n_z+1)=%.5f, \n",
|
||||
t, (int) newObsNeighbourCounts[1], (int) newObsNeighbourCounts[2],
|
||||
(int) newObsNeighbourCounts[3], newLocalValue,
|
||||
MathsUtils.digamma((int) newObsNeighbourCounts[1] + 1),
|
||||
MathsUtils.digamma((int) newObsNeighbourCounts[2] + 1),
|
||||
MathsUtils.digamma((int) newObsNeighbourCounts[3] + 1)); */
|
||||
if (Math.abs(newLocalValue - newLocals[t]) > 0.00000001) {
|
||||
System.out.printf("t=%d: Assertion failed: computed local was %.5f, local from nn counts was %.5f\n",
|
||||
t, newLocals[t], newLocalValue);
|
||||
}
|
||||
assertEquals(newLocalValue, newLocals[t], 0.00000001);
|
||||
}
|
||||
|
||||
// And check that the two methods are equivalent, after correcting for the different K biases:
|
||||
// CAN'T EXPECT THIS TO WORK though, because different neighbour counts (by one in each case) puts
|
||||
// different inputs to the digamma functions, and we have no way to check that for the average here;
|
||||
// Our above checks on the counts for each point suffice though.
|
||||
// assertEquals(miAverage, averageFromNewObservations - MathsUtils.digamma(kNNs[ki]+1) + MathsUtils.digamma(kNNs[ki]),
|
||||
// 0.00000001);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the computed univariate TE as a conditional MI
|
||||
* using various numbers of threads.
|
||||
|
|
@ -502,4 +609,133 @@ public class ConditionalMutualInfoMultiVariateTester
|
|||
kNNs, expectedValue);
|
||||
NUM_THREADS_TO_USE = NUM_THREADS_TO_USE_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the conditional MI without any conditionals
|
||||
* against the MI calculated by Kraskov's own MILCA
|
||||
* tool on the same data.
|
||||
*
|
||||
* To run Kraskov's tool (http://www.klab.caltech.edu/~kraskov/MILCA/) for this
|
||||
* data, run:
|
||||
* ./MIxnyn <dataFile> 1 1 3000 <kNearestNeighbours> 0
|
||||
*
|
||||
* @throws Exception if file not found
|
||||
*
|
||||
*/
|
||||
public void testNoConditionalsIsMIforRandomVariablesFromFile() throws Exception {
|
||||
|
||||
// Test set 1:
|
||||
|
||||
ArrayFileReader afr = new ArrayFileReader("demos/data/2randomCols-1.txt");
|
||||
double[][] data = afr.getDouble2DMatrix();
|
||||
|
||||
// Use various Kraskov k nearest neighbours parameter
|
||||
int[] kNNs = {1, 2, 3, 4, 5, 6, 10, 15};
|
||||
// Expected values from Kraskov's MILCA toolkit:
|
||||
double[] expectedFromMILCA = {-0.05294175, -0.03944338, -0.02190217,
|
||||
0.00120807, -0.00924771, -0.00316402, -0.00778205, -0.00565778};
|
||||
|
||||
System.out.println("Kraskov comparison 1 - univariate random data 1 --");
|
||||
// Check for algorithm 1 first (no expected results from MILCA included):
|
||||
System.out.println("Algorithm 1:");
|
||||
checkMIForGivenData(MatrixUtils.selectColumns(data, new int[] {0}),
|
||||
MatrixUtils.selectColumns(data, new int[] {1}),
|
||||
kNNs, null);
|
||||
// then check for algorithm 2 with expected results from MILCA
|
||||
System.out.println("Algorithm 2:");
|
||||
checkMIForGivenData(MatrixUtils.selectColumns(data, new int[] {0}),
|
||||
MatrixUtils.selectColumns(data, new int[] {1}),
|
||||
kNNs, expectedFromMILCA);
|
||||
|
||||
//------------------
|
||||
// Test set 2:
|
||||
|
||||
// We'll just take the first two columns from this data set
|
||||
afr = new ArrayFileReader("demos/data/4randomCols-1.txt");
|
||||
data = afr.getDouble2DMatrix();
|
||||
|
||||
// Expected values from Kraskov's MILCA toolkit:
|
||||
double[] expectedFromMILCA_2 = {-0.04614525, -0.00861460, -0.00164540,
|
||||
-0.01130354, -0.01339670, -0.00964035, -0.00237072, -0.00096891};
|
||||
|
||||
System.out.println("Kraskov comparison 2 - univariate random data 2 --");
|
||||
// Check for algorithm 1 first (no expected results from MILCA included):
|
||||
System.out.println("Algorithm 1:");
|
||||
checkMIForGivenData(MatrixUtils.selectColumns(data, new int[] {0}),
|
||||
MatrixUtils.selectColumns(data, new int[] {1}),
|
||||
kNNs, null);
|
||||
// then check for algorithm 2 with expected results from MILCA
|
||||
System.out.println("Algorithm 2:");
|
||||
checkMIForGivenData(MatrixUtils.selectColumns(data, new int[] {0}),
|
||||
MatrixUtils.selectColumns(data, new int[] {1}),
|
||||
kNNs, expectedFromMILCA_2);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to run Kraskov MI for data with known results
|
||||
*
|
||||
* @param var1
|
||||
* @param var2
|
||||
* @param kNNs array of Kraskov k nearest neighbours parameter to check
|
||||
* @param expectedResults array of expected results for each k
|
||||
*/
|
||||
protected void checkMIForGivenData(double[][] var1, double[][] var2,
|
||||
int[] kNNs, double[] expectedResults) throws Exception {
|
||||
|
||||
// The Kraskov MILCA toolkit MIhigherdim executable
|
||||
// uses algorithm 2 by default (this is what it means by rectangular);
|
||||
// These are the only ones we have expected results for
|
||||
ConditionalMutualInfoCalculatorMultiVariateKraskov condMiCalc = getNewCalc(expectedResults == null ? 1 : 2);
|
||||
|
||||
for (int kIndex = 0; kIndex < kNNs.length; kIndex++) {
|
||||
int k = kNNs[kIndex];
|
||||
condMiCalc.setProperty(
|
||||
MutualInfoCalculatorMultiVariateKraskov.PROP_K,
|
||||
Integer.toString(k));
|
||||
condMiCalc.setProperty(
|
||||
MutualInfoCalculatorMultiVariateKraskov.PROP_NUM_THREADS,
|
||||
NUM_THREADS_TO_USE);
|
||||
// No longer need to set this property as it's set by default:
|
||||
//miCalc.setProperty(MutualInfoCalculatorMultiVariateKraskov.PROP_NORM_TYPE,
|
||||
// EuclideanUtils.NORM_MAX_NORM_STRING);
|
||||
condMiCalc.setProperty(MutualInfoCalculatorMultiVariateKraskov.PROP_ADD_NOISE, "0"); // Need consistency for unit tests
|
||||
condMiCalc.initialise(var1[0].length, var2[0].length, 0);
|
||||
condMiCalc.setObservations(var1, var2, null);
|
||||
// condMiCalc.setDebug(true);
|
||||
double mi = condMiCalc.computeAverageLocalOfObservations();
|
||||
// condMiCalc.setDebug(false);
|
||||
|
||||
// And compute also passing in an array of vectors of length 0
|
||||
condMiCalc.initialise(var1[0].length, var2[0].length, 0);
|
||||
condMiCalc.setObservations(var1, var2, new double[var1.length][]);
|
||||
// condMiCalc.setDebug(true);
|
||||
double miFromEmptyVectors = condMiCalc.computeAverageLocalOfObservations();
|
||||
|
||||
double expectedMi = 0.0;
|
||||
if (expectedResults == null) {
|
||||
// We don't have expected results from MILCA toolkit for algorithm 1.
|
||||
// In this case, we'll generate our own expectations:
|
||||
MutualInfoCalculatorMultiVariateKraskov1 miCalc = new MutualInfoCalculatorMultiVariateKraskov1();
|
||||
miCalc.setProperty(
|
||||
MutualInfoCalculatorMultiVariateKraskov.PROP_K,
|
||||
Integer.toString(k));
|
||||
miCalc.setProperty(
|
||||
MutualInfoCalculatorMultiVariateKraskov.PROP_NUM_THREADS,
|
||||
NUM_THREADS_TO_USE);
|
||||
miCalc.setProperty(MutualInfoCalculatorMultiVariateKraskov.PROP_ADD_NOISE, "0"); // Need consistency for unit tests
|
||||
miCalc.initialise(var1[0].length, var2[0].length);
|
||||
miCalc.setObservations(var1, var2);
|
||||
expectedMi = miCalc.computeAverageLocalOfObservations();
|
||||
} else {
|
||||
expectedMi = expectedResults[kIndex];
|
||||
}
|
||||
System.out.printf("k=%d: Average MI %.8f (expected %.8f)\n",
|
||||
k, mi, expectedMi);
|
||||
// Dropping required accuracy by one order of magnitude, due
|
||||
// to faster but slightly less accurate digamma estimator change
|
||||
assertEquals(expectedMi, mi, 0.0000001);
|
||||
assertEquals(expectedMi, miFromEmptyVectors, 0.0000001);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ package infodynamics.measures.continuous.kraskov;
|
|||
|
||||
import infodynamics.utils.ArrayFileReader;
|
||||
import infodynamics.utils.MatrixUtils;
|
||||
import infodynamics.utils.RandomGenerator;
|
||||
|
||||
public class TransferEntropyMultiVariateTester
|
||||
extends infodynamics.measures.continuous.TransferEntropyMultiVariateAbstractTester {
|
||||
|
|
@ -441,4 +442,40 @@ public class TransferEntropyMultiVariateTester
|
|||
kNNs, expectedValue);
|
||||
NUM_THREADS_TO_USE = NUM_THREADS_TO_USE_DEFAULT;
|
||||
}
|
||||
|
||||
public void testMulitvariateAddObservations() throws Exception {
|
||||
// Just make sure the code runs first (we had an execution error earlier)
|
||||
TransferEntropyCalculatorMultiVariateKraskov teCalc =
|
||||
new TransferEntropyCalculatorMultiVariateKraskov();
|
||||
teCalc.setProperty("k", "4");
|
||||
teCalc.initialise(1,3,3);
|
||||
teCalc.startAddObservations();
|
||||
RandomGenerator rg = new RandomGenerator();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
double[][] source = rg.generateNormalData(100, 3, 0, 1);
|
||||
double[][] target = rg.generateNormalData(100, 3, 0, 1);
|
||||
teCalc.addObservations(source, target);
|
||||
}
|
||||
teCalc.finaliseAddObservations();
|
||||
teCalc.computeAverageLocalOfObservations();
|
||||
|
||||
// Now make sure the ensemble method returns the same value as for
|
||||
// a single time series
|
||||
teCalc.initialise(1,3,3);
|
||||
double[][] source = rg.generateNormalData(100, 3, 0, 1);
|
||||
double[][] target = rg.generateNormalData(100, 3, 0, 1);
|
||||
teCalc.setObservations(source, target);
|
||||
int numObervationsSingle = teCalc.getNumObservations();
|
||||
double resultSingle = teCalc.computeAverageLocalOfObservations();
|
||||
teCalc.initialise(1,3,3);
|
||||
teCalc.startAddObservations();
|
||||
teCalc.addObservations(source, target, 0, 50); // Give first 50 time steps
|
||||
teCalc.addObservations(source, target, 49, 51); // Give next 50 with
|
||||
// one previous as the embedded history
|
||||
teCalc.finaliseAddObservations();
|
||||
int numObervationsEnsemble = teCalc.getNumObservations();
|
||||
double resultEnsemble = teCalc.computeAverageLocalOfObservations();
|
||||
assertEquals(numObervationsSingle, numObervationsEnsemble);
|
||||
assertEquals(resultSingle, resultEnsemble, 0.00001);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -537,4 +537,70 @@ public class TransferEntropyTester
|
|||
assertEquals(teOptimisedSingleThread, teOptimisedWithValidity, 0.00000001);
|
||||
System.out.println("Answer unchanged by setting validity");
|
||||
}
|
||||
|
||||
public void testGetSeparateNumObservations() throws Exception {
|
||||
ArrayFileReader afr = new ArrayFileReader("demos/data/SFI-heartRate_breathVol_bloodOx.txt");
|
||||
double[][] data = afr.getDouble2DMatrix();
|
||||
|
||||
TransferEntropyCalculatorKraskov teCalc = new TransferEntropyCalculatorKraskov();
|
||||
|
||||
teCalc.initialise();
|
||||
teCalc.startAddObservations();
|
||||
int timeStepsPerCall = 100;
|
||||
int calls = 10;
|
||||
for (int i = 0; i < calls; i++) {
|
||||
// Add more samples
|
||||
teCalc.addObservations(MatrixUtils.selectColumn(data, 0, i*timeStepsPerCall, timeStepsPerCall),
|
||||
MatrixUtils.selectColumn(data, 1, i*timeStepsPerCall, timeStepsPerCall));
|
||||
}
|
||||
teCalc.finaliseAddObservations();
|
||||
@SuppressWarnings("unused")
|
||||
double result = teCalc.computeAverageLocalOfObservations();
|
||||
|
||||
// Now we want to check how many observations were added at each call:
|
||||
int[] samplesPerCall = teCalc.getSeparateNumObservations();
|
||||
assertEquals(calls, samplesPerCall.length);
|
||||
for (int i = 0; i < calls; i++) {
|
||||
// For k = l = 1, we should have timeStepsPerCall - 1 samples per addObservations() call:
|
||||
assertEquals(timeStepsPerCall - 1, samplesPerCall[i]);
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Now run it again with different k and l and embedding lags, etc:
|
||||
teCalc.initialise();
|
||||
teCalc.startAddObservations();
|
||||
// auto embed destination only
|
||||
teCalc.setProperty(TransferEntropyCalculatorKraskov.PROP_AUTO_EMBED_METHOD,
|
||||
TransferEntropyCalculatorKraskov.AUTO_EMBED_METHOD_RAGWITZ_DEST_ONLY);
|
||||
teCalc.setProperty(TransferEntropyCalculatorKraskov.PROP_K_SEARCH_MAX, "5");
|
||||
teCalc.setProperty(TransferEntropyCalculatorKraskov.PROP_TAU_SEARCH_MAX, "5");
|
||||
// Explicitly set the source embedding params
|
||||
teCalc.setProperty(TransferEntropyCalculatorKraskov.L_PROP_NAME, "1");
|
||||
teCalc.setProperty(TransferEntropyCalculatorKraskov.L_TAU_PROP_NAME, "1");
|
||||
int[] timeStepsPerCallArray = new int[] {100, 200, 150, 300, 99, 54};
|
||||
int startTime = 0;
|
||||
for (int i = 0; i < timeStepsPerCallArray.length; i++) {
|
||||
// Add more samples
|
||||
teCalc.addObservations(MatrixUtils.selectColumn(data, 0, startTime, timeStepsPerCallArray[i]),
|
||||
MatrixUtils.selectColumn(data, 1, startTime, timeStepsPerCallArray[i]));
|
||||
startTime += timeStepsPerCallArray[i];
|
||||
}
|
||||
teCalc.finaliseAddObservations();
|
||||
result = teCalc.computeAverageLocalOfObservations();
|
||||
int optimisedK = Integer.parseInt(teCalc.getProperty(TransferEntropyCalculatorKraskov.K_PROP_NAME));
|
||||
int optimisedKTau = Integer.parseInt(teCalc.getProperty(TransferEntropyCalculatorKraskov.K_TAU_PROP_NAME));
|
||||
int timeOfFirstObservationPerSet = (optimisedK - 1)*optimisedKTau + 1;
|
||||
System.out.printf("In testing tracking of observations per addObservations() call" +
|
||||
" we have auto-embedding dimension %d and lag %d, timeOfFirstObservationPerSet %d\n",
|
||||
optimisedK, optimisedKTau, timeOfFirstObservationPerSet);
|
||||
|
||||
// Now we want to check how many observations were added at each call:
|
||||
samplesPerCall = teCalc.getSeparateNumObservations();
|
||||
assertEquals(timeStepsPerCallArray.length, samplesPerCall.length);
|
||||
for (int i = 0; i < timeStepsPerCallArray.length; i++) {
|
||||
// For timeOfFirstObservationPerSet, we should have timeStepsPerCall - timeOfFirstObservationPerSet
|
||||
// samples per addObservations() call:
|
||||
assertEquals(timeStepsPerCallArray[i] - timeOfFirstObservationPerSet, samplesPerCall[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,21 @@ public class MutualInformationTester extends TestCase {
|
|||
assertEquals(0.0, miRand, 0.000001);
|
||||
}
|
||||
|
||||
public void testAnd() throws Exception {
|
||||
MutualInformationCalculatorDiscrete miCalc = new MutualInformationCalculatorDiscrete(2, 0);
|
||||
|
||||
int[] X1 = new int[] {0, 0, 1, 1};
|
||||
int[] X2 = new int[] {0, 1, 0, 1};
|
||||
int[] Y = new int[] {0, 0, 0, 1};
|
||||
|
||||
// Y is dependent on X1 - MI should be 0.311 bits
|
||||
miCalc.initialise();
|
||||
miCalc.addObservations(X1, Y);
|
||||
double miX1Y = miCalc.computeAverageLocalOfObservations();
|
||||
assertEquals(0.311, miX1Y, 0.001);
|
||||
assertEquals(4, miCalc.getNumObservations());
|
||||
}
|
||||
|
||||
public void testXor() throws Exception {
|
||||
MutualInformationCalculatorDiscrete miCalc = new MutualInformationCalculatorDiscrete(2, 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@
|
|||
|
||||
package infodynamics.measures.discrete;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import infodynamics.utils.AnalyticMeasurementDistribution;
|
||||
import infodynamics.utils.EmpiricalMeasurementDistribution;
|
||||
import infodynamics.utils.MatrixUtils;
|
||||
import infodynamics.utils.RandomGenerator;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
|
|
@ -84,4 +90,477 @@ public class TransferEntropyTester extends TestCase {
|
|||
result = teCalc.computeAverageLocalOfObservations();
|
||||
assertEquals(1, result, 0.0000001);
|
||||
}
|
||||
|
||||
public void testEmbeddings() throws Exception {
|
||||
// Use a short length so that if anything is mis-embedded we'll
|
||||
// definitely see a change in the result
|
||||
int length = 50;
|
||||
for (int type = 0; type < NUM_ORDINARY_TESTS; type++) {
|
||||
System.out.println("Testing operation " + type + " for TE discrete embeddings...");
|
||||
for (int delay = 1; delay < 4; delay++) {
|
||||
for (int selfLag = 1; selfLag < 4; selfLag++) {
|
||||
for (int k = 1; k < 4; k++) {
|
||||
for (int l = 1; l < 4; l++) {
|
||||
for (int k_tau = 1; k_tau < 4; k_tau++) {
|
||||
for (int l_tau = 1; l_tau < 4; l_tau++) {
|
||||
for (int cDelay = 1; cDelay < 4; cDelay++) {
|
||||
for (int startOffset = 0; startOffset < 10; startOffset += 3) {
|
||||
int[][] data = generateData(length, type, delay, selfLag);
|
||||
int[] source = data[0];
|
||||
int[] dest = data[1];
|
||||
TransferEntropyCalculatorDiscrete teCalc =
|
||||
new TransferEntropyCalculatorDiscrete(2, k, k_tau, l, l_tau, cDelay);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest, startOffset, dest.length - 1);
|
||||
double result = teCalc.computeAverageLocalOfObservations();
|
||||
// System.out.printf("Result = %.4f\n", result);
|
||||
// Now check that against our own embeddings with conditional MI:
|
||||
int startTimeBasedOnDestPast = (k-1)*k_tau + 1;
|
||||
int startTimeBasedOnSourcePast = (l-1)*l_tau + cDelay;
|
||||
int startObservationTime = Math.max(startTimeBasedOnDestPast, startTimeBasedOnSourcePast) + startOffset;
|
||||
int[][] destPastVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(dest, k, k_tau,
|
||||
startObservationTime-1,
|
||||
dest.length - startObservationTime);
|
||||
int[] destPastCombined = MatrixUtils.computeCombinedValues(destPastVectors, 2);
|
||||
int[][] sourcePastVectors =
|
||||
MatrixUtils.makeDelayEmbeddingVector(source, l, l_tau,
|
||||
startObservationTime - cDelay,
|
||||
source.length - startObservationTime);
|
||||
int[] sourcePastCombined = MatrixUtils.computeCombinedValues(sourcePastVectors, 2);
|
||||
int[] destNext = MatrixUtils.select(dest, startObservationTime, dest.length - startObservationTime);
|
||||
ConditionalMutualInformationCalculatorDiscrete cmiCalc =
|
||||
new ConditionalMutualInformationCalculatorDiscrete((int) Math.pow(2, l), 2, (int) Math.pow(2, k));
|
||||
cmiCalc.initialise();
|
||||
cmiCalc.addObservations(sourcePastCombined, destNext, destPastCombined);
|
||||
double resultCmi = cmiCalc.computeAverageLocalOfObservations();
|
||||
assertEquals(resultCmi, result, 0.000001);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final static int TYPE_COPY = 0;
|
||||
public final static int TYPE_OR = 1;
|
||||
public final static int TYPE_AND = 2;
|
||||
public final static int TYPE_XOR = 3;
|
||||
public final static int TYPE_NOISY_XOR = 4;
|
||||
public final static int NUM_ORDINARY_TESTS = 5;
|
||||
|
||||
public int[][] generateData(int length, int type, int delay, int selfLag) {
|
||||
int[] source = rand.generateRandomInts(length, 2);
|
||||
int[] dest = new int[length];
|
||||
int startTime = Math.max(delay, selfLag);
|
||||
Random rng = new Random();
|
||||
for (int t = 0; t < startTime; t++) {
|
||||
dest[t] = rng.nextInt(2);
|
||||
}
|
||||
for (int t = startTime; t < length; t++) {
|
||||
switch(type){
|
||||
case TYPE_COPY:
|
||||
dest[t] = source[t-delay];
|
||||
break;
|
||||
case TYPE_OR:
|
||||
dest[t] = source[t-delay] | dest[t-selfLag];
|
||||
break;
|
||||
case TYPE_AND:
|
||||
dest[t] = source[t-delay] & dest[t-selfLag];
|
||||
break;
|
||||
case TYPE_XOR:
|
||||
dest[t] = (source[t-delay] ^ dest[t-selfLag]) & 1; // Mask out the upper bits
|
||||
break;
|
||||
case TYPE_NOISY_XOR:
|
||||
dest[t] = (source[t-delay] ^ dest[t-selfLag]) & 1; // Mask out the upper bits
|
||||
if (rng.nextInt(4) == 1) {
|
||||
// 1 in 4 chance we flip the output:
|
||||
dest[t] = (dest[t] ^ 1) & 1; // Mask out the upper bits
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
int[][] retVal = new int[2][];
|
||||
retVal[0] = source;
|
||||
retVal[1] = dest;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public void testValidObservations() {
|
||||
int length = 100;
|
||||
int[][] data = generateData(100, 1, 1, 1);
|
||||
int[] source = data[0];
|
||||
int[] dest = data[1];
|
||||
TransferEntropyCalculatorDiscrete teCalc =
|
||||
new TransferEntropyCalculatorDiscrete(2, 1, 1, 1, 1, 1);
|
||||
teCalc.initialise();
|
||||
boolean[] validity = new boolean[length];
|
||||
boolean[] allValid = new boolean[length];
|
||||
|
||||
// 1. check that all samples are counted if validity is always true
|
||||
Arrays.fill(allValid, true);
|
||||
teCalc.addObservations(source, dest, allValid);
|
||||
assertEquals(length - 1, teCalc.getNumObservations());
|
||||
|
||||
// 2. check that only one samples is left out if first or last entry is invalid:
|
||||
Arrays.fill(validity, true);
|
||||
validity[0] = false;
|
||||
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 1, 1, 1, 1);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest, validity);
|
||||
assertEquals(length - 2, teCalc.getNumObservations());
|
||||
validity[0] = true;
|
||||
validity[length - 1] = false;
|
||||
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 1, 1, 1, 1);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest, validity);
|
||||
assertEquals(length - 2, teCalc.getNumObservations());
|
||||
|
||||
// 3. check that only two samples are left out if an entry in middle invalid:
|
||||
Arrays.fill(validity, true);
|
||||
validity[10] = false;
|
||||
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 1, 1, 1, 1);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest, validity);
|
||||
assertEquals(length - 3, teCalc.getNumObservations());
|
||||
|
||||
// 4. check that only three samples are left out if two consecutive entries in middle invalid:
|
||||
Arrays.fill(validity, true);
|
||||
validity[10] = false;
|
||||
validity[11] = false;
|
||||
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 1, 1, 1, 1);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest, validity);
|
||||
assertEquals(length - 4, teCalc.getNumObservations());
|
||||
|
||||
// 5. check that four samples are left out if two non-consecutive entries in middle invalid:
|
||||
Arrays.fill(validity, true);
|
||||
validity[10] = false;
|
||||
validity[12] = false;
|
||||
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 1, 1, 1, 1);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest, validity);
|
||||
assertEquals(length - 5, teCalc.getNumObservations());
|
||||
|
||||
// 6. check that only k+1 extra samples are left out if an entry in middle invalid:
|
||||
Arrays.fill(validity, true);
|
||||
validity[50] = false;
|
||||
for (int k = 1; k < 5; k++) {
|
||||
for (int k_tau = 1; k_tau < 5; k_tau++) {
|
||||
int startTimeBasedOnDestPast = (k-1)*k_tau + 1;
|
||||
// First check the number of observations when all valid:
|
||||
System.out.printf("Testing addObservations(valid) for k=%d, k_tau=%d\n", k, k_tau);
|
||||
teCalc = new TransferEntropyCalculatorDiscrete(2, k, k_tau, 1, 1, 1);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest, allValid);
|
||||
assertEquals(length - startTimeBasedOnDestPast, teCalc.getNumObservations());
|
||||
// Now check with an entry left out in the middle:
|
||||
teCalc = new TransferEntropyCalculatorDiscrete(2, k, k_tau, 1, 1, 1);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest, validity);
|
||||
assertEquals(length - startTimeBasedOnDestPast - ((k-1)*k_tau+2), teCalc.getNumObservations());
|
||||
}
|
||||
}
|
||||
// 7. check correct number of extra samples left out
|
||||
// for two nearby entries in middle being invalid:
|
||||
Arrays.fill(validity, true);
|
||||
int invalidtime = 50;
|
||||
validity[invalidtime] = false;
|
||||
for (int k = 1; k < 5; k++) {
|
||||
for (int k_tau = 1; k_tau < 5; k_tau++) {
|
||||
int startTimeBasedOnDestPast = (k-1)*k_tau + 1;
|
||||
for (int t = 1; t < 20; t++) {
|
||||
validity[invalidtime+t] = false;
|
||||
teCalc = new TransferEntropyCalculatorDiscrete(2, k, k_tau, 1, 1, 1);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest, validity);
|
||||
int expectedInvalidTimes = 0;
|
||||
if (t > startTimeBasedOnDestPast) {
|
||||
// Both invalid entries cause independent invalid periods
|
||||
expectedInvalidTimes = (startTimeBasedOnDestPast + 1)*2;
|
||||
} else {
|
||||
// Their invalid times overlap
|
||||
expectedInvalidTimes = (startTimeBasedOnDestPast + 1) + t;
|
||||
}
|
||||
assertEquals(length - startTimeBasedOnDestPast - expectedInvalidTimes,
|
||||
teCalc.getNumObservations());
|
||||
validity[invalidtime+t] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 8. check correct number of extra samples left out
|
||||
// for two nearby entries in middle being invalid,
|
||||
// but using a long source embedding
|
||||
Arrays.fill(validity, true);
|
||||
validity[invalidtime] = false;
|
||||
for (int l = 1; l < 5; l++) {
|
||||
for (int l_tau = 1; l_tau < 5; l_tau++) {
|
||||
int startTimeBasedOnDestPast = (l-1)*l_tau + 1;
|
||||
for (int t = 1; t < 20; t++) {
|
||||
validity[invalidtime+t] = false;
|
||||
teCalc = new TransferEntropyCalculatorDiscrete(2, 1, 1, l, l_tau, 1);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest, validity);
|
||||
int expectedInvalidTimes = 0;
|
||||
if (t > startTimeBasedOnDestPast) {
|
||||
// Both invalid entries cause independent invalid periods
|
||||
// Need the +1 here because when the invalid is next state
|
||||
// it still adds 1 in addition to the source
|
||||
// invalidities!
|
||||
expectedInvalidTimes = (startTimeBasedOnDestPast + 1)*2;
|
||||
} else {
|
||||
// Their invalid times overlap
|
||||
expectedInvalidTimes = (startTimeBasedOnDestPast + 1) + t;
|
||||
}
|
||||
assertEquals(length - startTimeBasedOnDestPast - expectedInvalidTimes,
|
||||
teCalc.getNumObservations());
|
||||
validity[invalidtime+t] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 9. check correct number of extra samples left out
|
||||
// for two nearby entries in middle being invalid,
|
||||
// but using a long source embedding AND a source-dest delay
|
||||
for (int l = 1; l < 5; l++) {
|
||||
for (int l_tau = 1; l_tau < 5; l_tau++) {
|
||||
for (int k = 1; k < 5; k++) {
|
||||
for (int k_tau = 1; k_tau < 5; k_tau++) {
|
||||
for (int delay = 1; delay < 5; delay++) {
|
||||
int startTimeBasedOnDestPast = (k-1)*k_tau + 1;
|
||||
int startTimeBasedOnSourcePast = (l-1)*l_tau + delay;
|
||||
int startObservationTime = Math.max(startTimeBasedOnDestPast, startTimeBasedOnSourcePast);
|
||||
int numPointsRemoved = 0;
|
||||
Arrays.fill(validity, true);
|
||||
validity[startObservationTime+2] = false;
|
||||
boolean overlap = false;
|
||||
if (startTimeBasedOnDestPast >= delay) {
|
||||
// The embeddings overlap, so number of points removed
|
||||
// is solely based on startObservationTime
|
||||
numPointsRemoved = (startObservationTime + 1);
|
||||
overlap = true;
|
||||
} else {
|
||||
// The embeddings do not overlap, so will contribute
|
||||
// to point removal separately
|
||||
numPointsRemoved = (l-1)*l_tau + 1 + startTimeBasedOnDestPast + 1;
|
||||
overlap = false;
|
||||
}
|
||||
System.out.printf("k=%d, k_tau=%d, l=%d, l_tau=%d, delay=%d, overlap=%b; expecting removal of %d points\n",
|
||||
k, k_tau, l, l_tau, delay, overlap, numPointsRemoved);
|
||||
teCalc = new TransferEntropyCalculatorDiscrete(2, k, k_tau, l, l_tau, delay);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest, validity);
|
||||
assertEquals(length - startObservationTime - numPointsRemoved,
|
||||
teCalc.getNumObservations());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testSignificance() throws Exception {
|
||||
int length = 1000;
|
||||
int[][] data = generateData(length, 2, 2, 3);
|
||||
int[] source = data[0];
|
||||
int[] dest = data[1];
|
||||
TransferEntropyCalculatorDiscrete teCalc =
|
||||
new TransferEntropyCalculatorDiscrete(2, 2, 2, 2, 2, 2);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest);
|
||||
double originalResult = teCalc.computeAverageLocalOfObservations();
|
||||
// Just make sure the significance check returns the right number of calculations, etc
|
||||
int numSurrogates = 151;
|
||||
EmpiricalMeasurementDistribution emd = teCalc.computeSignificance(numSurrogates);
|
||||
assertEquals(numSurrogates, emd.distribution.length);
|
||||
// And now make an analytic calculation:
|
||||
@SuppressWarnings("unused")
|
||||
AnalyticMeasurementDistribution amd = teCalc.computeSignificance();
|
||||
// Not really anything I can check here, just want to make sure it runs ok
|
||||
double resultAfterSigTest = teCalc.computeAverageLocalOfObservations();
|
||||
assertEquals(originalResult, resultAfterSigTest, 0.000001);
|
||||
}
|
||||
|
||||
public void testLocals() throws Exception {
|
||||
int length = 500;
|
||||
int[][] data2D = new int[length][2];
|
||||
int[][][] data3D = new int[length][2][2];
|
||||
for (int type = 0; type < NUM_ORDINARY_TESTS; type++) {
|
||||
System.out.println("Testing operation " + type + " for TE discrete embeddings with locals...");
|
||||
for (int delay = 1; delay < 4; delay++) {
|
||||
for (int selfLag = 1; selfLag < 4; selfLag++) {
|
||||
for (int k = 1; k < 4; k++) {
|
||||
for (int l = 1; l < 4; l++) {
|
||||
for (int k_tau = 1; k_tau < 4; k_tau++) {
|
||||
for (int l_tau = 1; l_tau < 4; l_tau++) {
|
||||
for (int cDelay = 1; cDelay < 4; cDelay++) {
|
||||
int[][] data = generateData(length, type, delay, selfLag);
|
||||
int[] source = data[0];
|
||||
int[] dest = data[1];
|
||||
TransferEntropyCalculatorDiscrete teCalc =
|
||||
new TransferEntropyCalculatorDiscrete(2, k, k_tau, l, l_tau, cDelay);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest);
|
||||
double result = teCalc.computeAverageLocalOfObservations();
|
||||
double[] locals = teCalc.computeLocalFromPreviousObservations(source, dest);
|
||||
// Now check that the average of the locals gives us the correct value
|
||||
int startTimeBasedOnDestPast = (k-1)*k_tau + 1;
|
||||
int startTimeBasedOnSourcePast = (l-1)*l_tau + cDelay;
|
||||
int startObservationTime = Math.max(startTimeBasedOnDestPast, startTimeBasedOnSourcePast);
|
||||
double sumLocals = MatrixUtils.sum(locals);
|
||||
double avLocals = sumLocals / (double) (length - startObservationTime);
|
||||
// System.out.printf("Testing locals for k=%d, k_tau=%d, l=%d, l_tau=%d, delay=%d, expecting no locals for first %d points\n",
|
||||
// k, k_tau, l, l_tau, delay, startObservationTime);
|
||||
assertEquals(length - startObservationTime, teCalc.getNumObservations());
|
||||
for (int t = 0; t < startObservationTime; t++) {
|
||||
// First startObservationTime time steps should have
|
||||
// local value of 0
|
||||
assertEquals(0, locals[t], 0.000001);
|
||||
}
|
||||
assertEquals(result, avLocals, 0.000001);
|
||||
|
||||
// Now test for the 2D methods:
|
||||
teCalc.initialise();
|
||||
MatrixUtils.copyIntoColumn(data2D, 0, source);
|
||||
MatrixUtils.copyIntoColumn(data2D, 1, dest);
|
||||
// Make sure we only calculate source -> dest:
|
||||
teCalc.setPeriodicBoundaryConditions(false);
|
||||
teCalc.addObservations(data2D, 1);
|
||||
double result2D = teCalc.computeAverageLocalOfObservations();
|
||||
assertEquals(result, result2D, 0.00001);
|
||||
int numObs = teCalc.getNumObservations();
|
||||
// Now compute across both columns:
|
||||
teCalc.initialise();
|
||||
teCalc.setPeriodicBoundaryConditions(true);
|
||||
teCalc.addObservations(data2D, 1);
|
||||
assertEquals(numObs*2, teCalc.getNumObservations());
|
||||
double result2DBoth = teCalc.computeAverageLocalOfObservations();
|
||||
double[][] locals2D = teCalc.computeLocalFromPreviousObservations(data2D, 1);
|
||||
sumLocals = MatrixUtils.sum(locals2D);
|
||||
avLocals = sumLocals / (double) (teCalc.getNumObservations());
|
||||
assertEquals(result2DBoth, avLocals, 0.000001);
|
||||
// Now compute for one source and dest only:
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(data2D, 0, 1);
|
||||
double resultAcrossCols = teCalc.computeAverageLocalOfObservations();
|
||||
assertEquals(result, resultAcrossCols, 0.0000001);
|
||||
|
||||
// Now test the 3D methods
|
||||
teCalc.initialise();
|
||||
MatrixUtils.copyIntoColumn3D(data3D, 0, 0, source);
|
||||
MatrixUtils.copyIntoColumn3D(data3D, 1, 1, dest);
|
||||
// Make sure we only calculate source -> dest:
|
||||
teCalc.setPeriodicBoundaryConditions(false);
|
||||
teCalc.addObservations(data3D, 1, 1);
|
||||
double result3D = teCalc.computeAverageLocalOfObservations();
|
||||
assertEquals(result, result3D, 0.00001);
|
||||
numObs = teCalc.getNumObservations();
|
||||
// Now compute across all columns:
|
||||
teCalc.initialise();
|
||||
teCalc.setPeriodicBoundaryConditions(true);
|
||||
teCalc.addObservations(data3D, 1, 1);
|
||||
assertEquals(numObs*4, teCalc.getNumObservations());
|
||||
double result3DAll = teCalc.computeAverageLocalOfObservations();
|
||||
double[][][] locals3D = teCalc.computeLocalFromPreviousObservations(data3D, 1, 1);
|
||||
sumLocals = MatrixUtils.sum(locals3D);
|
||||
avLocals = sumLocals / (double) (teCalc.getNumObservations());
|
||||
assertEquals(result3DAll, avLocals, 0.000001);
|
||||
// Now compute for one source and dest only:
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(data3D, 0, 0, 1, 1);
|
||||
resultAcrossCols = teCalc.computeAverageLocalOfObservations();
|
||||
assertEquals(result, resultAcrossCols, 0.0000001);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int[][] generateParityData(int length, int delay, int k, int l) {
|
||||
int[][] data = new int[length][2];
|
||||
int k_tau = 1;
|
||||
int l_tau = 1;
|
||||
int startTimeBasedOnDestPast = (k-1)*k_tau + 1;
|
||||
int startTimeBasedOnSourcePast = (l-1)*l_tau + delay;
|
||||
int startObservationTime = Math.max(startTimeBasedOnDestPast, startTimeBasedOnSourcePast);
|
||||
Random random = new Random();
|
||||
for (int t = 0; t < startObservationTime; t++) {
|
||||
data[t][0] = random.nextInt(2);
|
||||
data[t][1] = random.nextInt(2);
|
||||
}
|
||||
for (int t = startObservationTime; t < length; t++) {
|
||||
data[t][1] = 0;
|
||||
for (int ki = 0; ki < k; ki++) {
|
||||
data[t][1] ^= data[t-1-ki][1];
|
||||
}
|
||||
for (int li = 0; li < l; li++) {
|
||||
data[t][1] ^= data[t-delay-li][0];
|
||||
}
|
||||
data[t][0] = random.nextInt(2);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public void testComplexParity() throws Exception {
|
||||
int length = 10000;
|
||||
for (int delay = 1; delay < 4; delay++) {
|
||||
for (int k_dest = 1; k_dest < 4; k_dest++) {
|
||||
for (int l_source = 1; l_source < 4; l_source++) {
|
||||
for (int k = 1; k < 4; k++) {
|
||||
for (int l = 1; l < 4; l++) {
|
||||
for (int k_tau = 1; k_tau < 4; k_tau++) {
|
||||
for (int l_tau = 1; l_tau < 4; l_tau++) {
|
||||
for (int cDelay = 1; cDelay < 4; cDelay++) {
|
||||
int[][] data = generateParityData(length, delay, k_dest, l_source);
|
||||
int[] source = MatrixUtils.selectColumn(data, 0);
|
||||
int[] dest = MatrixUtils.selectColumn(data, 1);
|
||||
TransferEntropyCalculatorDiscrete teCalc =
|
||||
new TransferEntropyCalculatorDiscrete(2, k, k_tau, l, l_tau, cDelay);
|
||||
teCalc.initialise();
|
||||
teCalc.addObservations(source, dest);
|
||||
double result = teCalc.computeAverageLocalOfObservations();
|
||||
if ((k >= k_dest) && (cDelay <= delay) &&
|
||||
(l - delay + cDelay >= l_source) &&
|
||||
((k_dest==1) || (k_tau == 1)) &&
|
||||
(((l_source==1) && (cDelay==delay)) || (l_tau == 1))) {
|
||||
// Our embedding parameters will be satisfactory
|
||||
// to detect the parity operation
|
||||
if (result <= 0.9) {
|
||||
System.out.printf("(k=%d >= k_dest=%d) && (cDelay=%d <= delay=%d) && " +
|
||||
"(l=%d - delay=%d + cDelay=%d >= l_source=%d) && ((k_dest=%d==1) || (k_tau=%d == 1)) && " +
|
||||
"(((l_source=%d==1)&&(cDelay=%d==delay=%d)) || (l_tau=%d == 1)) was true but result=%.3f too low\n",
|
||||
k, k_dest, cDelay, delay, l, delay, cDelay, l_source, k_dest, k_tau, l_source, cDelay, delay, l_tau, result);
|
||||
}
|
||||
assertTrue(result > 0.9);
|
||||
} else {
|
||||
// It's still quite possible that the parity operation is detected, because
|
||||
// we can have some strange behaviour emerge here, e.g. initial conditions lock in
|
||||
// dest behaviour as either a copy or inversion of source (in which case you always detect
|
||||
// the one bit dependence with k=1).
|
||||
// So instead, let's look for conditions where we know it *cannot* be detected!
|
||||
if (cDelay > delay) {
|
||||
if (result > 0.1) {
|
||||
System.out.printf("(cDelay=%d > delay=%d) was false but result=%.3f too high\n",
|
||||
cDelay, delay, result);
|
||||
}
|
||||
assertTrue(result < 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -502,6 +502,48 @@ public class KdTreeTest extends TestCase {
|
|||
long endTimeValidate = Calendar.getInstance().getTimeInMillis();
|
||||
System.out.printf("All %d nearest neighbours found in: %.3f sec\n",
|
||||
K, ((double) (endTimeValidate - startTime)/1000.0));
|
||||
|
||||
// And also test the K nearest neighbours for a few points that
|
||||
// weren't in the original data set:
|
||||
for (int n = 0; n < 100; n++) {
|
||||
// Generate a sample vector
|
||||
double[][] sampleVectors = new double[variables][];
|
||||
for (int v = 0; v < variables; v++) {
|
||||
int vLength = data[v][0].length;
|
||||
sampleVectors[v] = rg.generateNormalData(vLength, 0, 1);
|
||||
}
|
||||
// Now pull the kNNs for it:
|
||||
PriorityQueue<NeighbourNodeData> nnPQ =
|
||||
kdTree.findKNearestNeighbours(K, sampleVectors);
|
||||
assertTrue(nnPQ.size() == K);
|
||||
// Now find the K nearest neighbours with a naive all-pairs comparison
|
||||
double[][] distancesAndIndices = new double[samples][2];
|
||||
for (int t2 = 0; t2 < samples; t2++) {
|
||||
double maxNorm = 0;
|
||||
for (int v = 0; v < variables; v++) {
|
||||
double normForThisVariable = normCalculator.norm(
|
||||
sampleVectors[v], data[v][t2]);
|
||||
if (maxNorm < normForThisVariable) {
|
||||
maxNorm = normForThisVariable;
|
||||
}
|
||||
}
|
||||
distancesAndIndices[t2][0] = maxNorm;
|
||||
distancesAndIndices[t2][1] = t2;
|
||||
}
|
||||
int[] timeStepsOfKthMins =
|
||||
MatrixUtils.kMinIndices(distancesAndIndices, 0, K);
|
||||
for (int i = 0; i < K; i++) {
|
||||
// Check that the ith nearest neighbour matches
|
||||
NeighbourNodeData nnData = nnPQ.poll();
|
||||
if (timeStepsOfKthMins[K - 1 - i] != nnData.sampleIndex) {
|
||||
// We have an error:
|
||||
System.out.printf("Erroneous match between indices %d (expected) " +
|
||||
" and %d\n", timeStepsOfKthMins[K - 1 - i], nnData.sampleIndex);
|
||||
}
|
||||
assertEquals(timeStepsOfKthMins[K - 1 - i], nnData.sampleIndex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void testCountNeighboursWithinRSeparateArrays() {
|
||||
|
|
@ -534,6 +576,16 @@ public class KdTreeTest extends TestCase {
|
|||
data, samples, kdTree, rs[i], true);
|
||||
verifyCountNeighboursWithinRForSeparateArraysArrayArgs(EuclideanUtils.NORM_MAX_NORM,
|
||||
data, samples, kdTree, rs[i], false);
|
||||
// And test the array return variant for radii returned:
|
||||
verifyCountNeighboursWithinRForSeparateArraysArrayArgsPlusRadiusOut(EuclideanUtils.NORM_MAX_NORM,
|
||||
data, samples, kdTree, rs[i], true);
|
||||
verifyCountNeighboursWithinRForSeparateArraysArrayArgsPlusRadiusOut(EuclideanUtils.NORM_MAX_NORM,
|
||||
data, samples, kdTree, rs[i], false);
|
||||
// And test the array return variant for new data points too:
|
||||
verifyCountNeighboursWithinRForSeparateArraysArrayArgsNewPoints(EuclideanUtils.NORM_MAX_NORM,
|
||||
data, samples, kdTree, rs[i], true);
|
||||
verifyCountNeighboursWithinRForSeparateArraysArrayArgsNewPoints(EuclideanUtils.NORM_MAX_NORM,
|
||||
data, samples, kdTree, rs[i], false);
|
||||
// Must test with EuclideanUtils.NORM_EUCLIDEAN_SQUARED instead
|
||||
// of EuclideanUtils.NORM_EUCLIDEAN if we want the min distances
|
||||
// to match when tested inside verifyNearestNeighbourForSeparateArrays()
|
||||
|
|
@ -545,6 +597,14 @@ public class KdTreeTest extends TestCase {
|
|||
data, samples, kdTree, rs[i], true);
|
||||
verifyCountNeighboursWithinRForSeparateArraysArrayArgs(EuclideanUtils.NORM_EUCLIDEAN_SQUARED,
|
||||
data, samples, kdTree, rs[i], false);
|
||||
verifyCountNeighboursWithinRForSeparateArraysArrayArgsPlusRadiusOut(EuclideanUtils.NORM_EUCLIDEAN_SQUARED,
|
||||
data, samples, kdTree, rs[i], true);
|
||||
verifyCountNeighboursWithinRForSeparateArraysArrayArgsPlusRadiusOut(EuclideanUtils.NORM_EUCLIDEAN_SQUARED,
|
||||
data, samples, kdTree, rs[i], false);
|
||||
verifyCountNeighboursWithinRForSeparateArraysArrayArgsNewPoints(EuclideanUtils.NORM_EUCLIDEAN_SQUARED,
|
||||
data, samples, kdTree, rs[i], true);
|
||||
verifyCountNeighboursWithinRForSeparateArraysArrayArgsNewPoints(EuclideanUtils.NORM_EUCLIDEAN_SQUARED,
|
||||
data, samples, kdTree, rs[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -694,6 +754,198 @@ public class KdTreeTest extends TestCase {
|
|||
((double) (endTimeValidate - endTimeCount)/1000.0));
|
||||
}
|
||||
|
||||
private void verifyCountNeighboursWithinRForSeparateArraysArrayArgsPlusRadiusOut(int normType,
|
||||
double[][][] data, int samples, KdTree kdTree, double r,
|
||||
boolean allowEqualToR) {
|
||||
|
||||
int variables = data.length;
|
||||
|
||||
// Set up the given norm type:
|
||||
EuclideanUtils normCalculator = new EuclideanUtils(normType);
|
||||
kdTree.setNormType(normType);
|
||||
|
||||
long startTime = Calendar.getInstance().getTimeInMillis();
|
||||
int totalCount = 0;
|
||||
int[] counts = new int[samples];
|
||||
boolean[] withinR = new boolean[samples];
|
||||
double[] distancesWithinRInOrder = new double[samples];
|
||||
double[][] distancesAndIndicesWithinR = new double[samples][2];
|
||||
for (int t = 0; t < samples; t++) {
|
||||
// int count = kdTree.countPointsWithinR(t, r, allowEqualToR);
|
||||
kdTree.findPointsWithinR(t, r, 0, allowEqualToR, withinR,
|
||||
distancesWithinRInOrder, distancesAndIndicesWithinR);
|
||||
int count = 0;
|
||||
// Run through the list of points returned as within r and check them
|
||||
for (int t2 = 0; distancesAndIndicesWithinR[t2][1] >= 0; t2++) {
|
||||
count++;
|
||||
assertTrue(withinR[(int) distancesAndIndicesWithinR[t2][1]]);
|
||||
// Reset this marker
|
||||
withinR[(int) distancesAndIndicesWithinR[t2][1]] = false;
|
||||
// Check that it's really within r
|
||||
double maxNorm = 0;
|
||||
for (int v = 0; v < variables; v++) {
|
||||
double normForThisVariable = normCalculator.norm(
|
||||
data[v][t], data[v][(int) distancesAndIndicesWithinR[t2][1]]);
|
||||
if (maxNorm < normForThisVariable) {
|
||||
maxNorm = normForThisVariable;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue((!allowEqualToR && (maxNorm < r)) ||
|
||||
( allowEqualToR && (maxNorm <= r)));
|
||||
// Check that its distance was properly returned in the other arrays:
|
||||
assertEquals(maxNorm, distancesAndIndicesWithinR[t2][0], 0.000001);
|
||||
assertEquals(distancesAndIndicesWithinR[t2][0],
|
||||
distancesWithinRInOrder[(int) distancesAndIndicesWithinR[t2][1]],
|
||||
0.00000001);
|
||||
}
|
||||
// Check that there were no other points marked as within r:
|
||||
int count2 = 0;
|
||||
for (int t2 = 0; t2 < samples; t2++) {
|
||||
if (withinR[t2]) {
|
||||
count2++;
|
||||
}
|
||||
}
|
||||
assertEquals(0, count2); // We set all the ones that should have been true to false already
|
||||
// Now reset the boolean array
|
||||
counts[t] = count;
|
||||
totalCount += count;
|
||||
}
|
||||
long endTimeCount = Calendar.getInstance().getTimeInMillis();
|
||||
System.out.printf("All neighbours within %.3f (average of %.3f) found in: %.3f sec\n",
|
||||
r, (double) totalCount / (double) samples,
|
||||
((double) (endTimeCount - startTime)/1000.0));
|
||||
// Now find the neighbour count with a naive all-pairs comparison
|
||||
for (int t = 0; t < samples; t++) {
|
||||
int naiveCount = 0;
|
||||
for (int t2 = 0; t2 < samples; t2++) {
|
||||
double norm = Double.POSITIVE_INFINITY;
|
||||
if (t2 != t) {
|
||||
double maxNorm = 0;
|
||||
for (int v = 0; v < variables; v++) {
|
||||
double normForThisVariable = normCalculator.norm(
|
||||
data[v][t], data[v][t2]);
|
||||
if (maxNorm < normForThisVariable) {
|
||||
maxNorm = normForThisVariable;
|
||||
}
|
||||
}
|
||||
norm = maxNorm;
|
||||
}
|
||||
if ((!allowEqualToR && (norm < r)) ||
|
||||
( allowEqualToR && (norm <= r))) {
|
||||
naiveCount++;
|
||||
}
|
||||
}
|
||||
if (naiveCount != counts[t]) {
|
||||
System.out.printf("All pairs : count %d\n", naiveCount);
|
||||
System.out.printf("kdTree search: count %d\n", counts[t]);
|
||||
}
|
||||
assertEquals(naiveCount, counts[t]);
|
||||
}
|
||||
long endTimeValidate = Calendar.getInstance().getTimeInMillis();
|
||||
System.out.printf("All neighbours within %.3f (average of %.3f) validated in: %.3f sec\n",
|
||||
r, (double) totalCount / (double) samples,
|
||||
((double) (endTimeValidate - endTimeCount)/1000.0));
|
||||
}
|
||||
|
||||
private void verifyCountNeighboursWithinRForSeparateArraysArrayArgsNewPoints(int normType,
|
||||
double[][][] data, int samples, KdTree kdTree, double r,
|
||||
boolean allowEqualToR) {
|
||||
|
||||
int variables = data.length;
|
||||
|
||||
int numNewSamplesToCheck = 100;
|
||||
double[][][] allSampleVectors = new double[numNewSamplesToCheck][][];
|
||||
|
||||
// Set up the given norm type:
|
||||
EuclideanUtils normCalculator = new EuclideanUtils(normType);
|
||||
kdTree.setNormType(normType);
|
||||
|
||||
long startTime = Calendar.getInstance().getTimeInMillis();
|
||||
int totalCount = 0;
|
||||
int[] counts = new int[samples];
|
||||
boolean[] withinR = new boolean[samples];
|
||||
int[] indicesWithinR = new int[samples];
|
||||
|
||||
// Test the range search for a few points that
|
||||
// weren't in the original data set:
|
||||
for (int n = 0; n < numNewSamplesToCheck; n++) {
|
||||
// Generate a sample vector
|
||||
double[][] sampleVectors = new double[variables][];
|
||||
for (int v = 0; v < variables; v++) {
|
||||
int vLength = data[v][0].length;
|
||||
sampleVectors[v] = rg.generateNormalData(vLength, 0, 1);
|
||||
}
|
||||
allSampleVectors[n] = sampleVectors;
|
||||
|
||||
kdTree.findPointsWithinR(r, sampleVectors, allowEqualToR, withinR, indicesWithinR);
|
||||
int count = 0;
|
||||
// Run through the list of points returned as within r and check them
|
||||
for (int t2 = 0; indicesWithinR[t2] != -1; t2++) {
|
||||
count++;
|
||||
assertTrue(withinR[indicesWithinR[t2]]);
|
||||
// Reset this marker
|
||||
withinR[indicesWithinR[t2]] = false;
|
||||
// Check that it's really within r
|
||||
double maxNorm = 0;
|
||||
for (int v = 0; v < variables; v++) {
|
||||
double normForThisVariable = normCalculator.norm(
|
||||
sampleVectors[v], data[v][indicesWithinR[t2]]);
|
||||
if (maxNorm < normForThisVariable) {
|
||||
maxNorm = normForThisVariable;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue((!allowEqualToR && (maxNorm < r)) ||
|
||||
( allowEqualToR && (maxNorm <= r)));
|
||||
}
|
||||
// Check that there were no other points marked as within r:
|
||||
int count2 = 0;
|
||||
for (int t2 = 0; t2 < samples; t2++) {
|
||||
if (withinR[t2]) {
|
||||
count2++;
|
||||
}
|
||||
}
|
||||
assertEquals(0, count2); // We set all the ones that should have been true to false already
|
||||
// Now reset the boolean array
|
||||
counts[n] = count;
|
||||
totalCount += count;
|
||||
}
|
||||
long endTimeCount = Calendar.getInstance().getTimeInMillis();
|
||||
System.out.printf("All neighbours within %.3f (average of %.3f) found in: %.3f sec\n",
|
||||
r, (double) totalCount / (double) samples,
|
||||
((double) (endTimeCount - startTime)/1000.0));
|
||||
// Now find the neighbour count with a naive all-pairs comparison
|
||||
for (int n = 0; n < numNewSamplesToCheck; n++) {
|
||||
int naiveCount = 0;
|
||||
for (int t2 = 0; t2 < samples; t2++) {
|
||||
double norm = Double.POSITIVE_INFINITY;
|
||||
double maxNorm = 0;
|
||||
for (int v = 0; v < variables; v++) {
|
||||
double normForThisVariable = normCalculator.norm(
|
||||
allSampleVectors[n][v], data[v][t2]);
|
||||
if (maxNorm < normForThisVariable) {
|
||||
maxNorm = normForThisVariable;
|
||||
}
|
||||
}
|
||||
norm = maxNorm;
|
||||
if ((!allowEqualToR && (norm < r)) ||
|
||||
( allowEqualToR && (norm <= r))) {
|
||||
naiveCount++;
|
||||
}
|
||||
}
|
||||
if (naiveCount != counts[n]) {
|
||||
System.out.printf("All pairs : count %d\n", naiveCount);
|
||||
System.out.printf("kdTree search: count %d\n", counts[n]);
|
||||
}
|
||||
assertEquals(naiveCount, counts[n]);
|
||||
}
|
||||
long endTimeValidate = Calendar.getInstance().getTimeInMillis();
|
||||
System.out.printf("All neighbours within %.3f (average of %.3f) validated in: %.3f sec\n",
|
||||
r, (double) totalCount / (double) samples,
|
||||
((double) (endTimeValidate - endTimeCount)/1000.0));
|
||||
}
|
||||
|
||||
public void testCountNeighboursWithinRExclusionWindow() {
|
||||
int dimensionsPerVariable = 3;
|
||||
int samples = 1000;
|
||||
|
|
@ -1106,6 +1358,42 @@ public class KdTreeTest extends TestCase {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// And now test some fresh samples:
|
||||
int numNewSamples = 1000;
|
||||
for (int i = 0; i < r1.length; i++) {
|
||||
|
||||
for (int n = 0; n < numNewSamples; n++) {
|
||||
double[][] newSample = rg.generateNormalData(variables, dimensionsPerVariable, 0, 1);
|
||||
double[] variable1 = newSample[0];
|
||||
double[][] newSampleSansVariable1 = new double[variables - 1][];
|
||||
for (int v = 1; v < variables; v++) {
|
||||
newSampleSansVariable1[v-1] = newSample[v];
|
||||
}
|
||||
|
||||
// Establish our ground truth by searching all variables:
|
||||
int count = kdTree.countPointsWithinR(newSample, r1[i], allowEqualsR);
|
||||
|
||||
// Now search in variable 1 first, then use that
|
||||
// to help the others:
|
||||
nnSearcherVar1.findPointsWithinR(r1[i], new double[][] {variable1}, allowEqualsR,
|
||||
isWithinR, indicesWithinR);
|
||||
int countUsingVar1 = kdTree.countPointsWithinR(r1[i], newSample,
|
||||
allowEqualsR, 0, isWithinR);
|
||||
assertEquals(count, countUsingVar1);
|
||||
|
||||
// And then search the kdTree which does not have variable 1:
|
||||
int countAdditionalCriteria =
|
||||
nnSearcherExceptVar1.countPointsWithinR(newSampleSansVariable1, r1[i],
|
||||
allowEqualsR, isWithinR);
|
||||
assertEquals(count, countAdditionalCriteria);
|
||||
|
||||
// Finally, reset our boolean array:
|
||||
for (int t2 = 0; indicesWithinR[t2] != -1; t2++) {
|
||||
isWithinR[indicesWithinR[t2]] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testPreviouslyTestedVariableMethodMultipleRs() throws Exception {
|
||||
|
|
|
|||
|
|
@ -117,6 +117,21 @@ public class UnivariateNearestNeighbourTest extends TestCase {
|
|||
checkRangeFinderArrayCalls(false, 100);
|
||||
}
|
||||
|
||||
public void testRangeFinderArrayCallsRaidusOut() throws Exception {
|
||||
checkRangeFinderArrayCallsPlusRadiusOut(true, 100);
|
||||
checkRangeFinderArrayCallsPlusRadiusOut(false, 100);
|
||||
}
|
||||
|
||||
public void testRangeFinderArrayCallsMaxRadiusOut() throws Exception {
|
||||
checkRangeFinderArrayCallsPlusMaxRadiusOut(true, 100);
|
||||
checkRangeFinderArrayCallsPlusMaxRadiusOut(false, 100);
|
||||
}
|
||||
|
||||
public void testRangeFinderArrayCallsNewData() throws Exception {
|
||||
checkRangeFinderArrayCallsNewData(true);
|
||||
checkRangeFinderArrayCallsNewData(false);
|
||||
}
|
||||
|
||||
public void checkRangeFinder(boolean strict, int exclWindow) throws Exception {
|
||||
int numTimeStepsInitial = 10000;
|
||||
int duplicateSteps = 100;
|
||||
|
|
@ -242,6 +257,238 @@ public class UnivariateNearestNeighbourTest extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void checkRangeFinderArrayCallsPlusRadiusOut(boolean strict, int exclWindow) throws Exception {
|
||||
int numTimeStepsInitial = 10000;
|
||||
int duplicateSteps = 100;
|
||||
int numTimeSteps = numTimeStepsInitial+duplicateSteps;
|
||||
double[] dataRaw = rg.generateNormalData(numTimeStepsInitial, 0, 1);
|
||||
double[] data = new double[numTimeSteps];
|
||||
// Now duplicate some of the time steps as a test:
|
||||
System.arraycopy(dataRaw, 0, data, 0, numTimeStepsInitial);
|
||||
System.arraycopy(dataRaw, 0, data, numTimeStepsInitial, duplicateSteps);
|
||||
|
||||
UnivariateNearestNeighbourSearcher searcher =
|
||||
new UnivariateNearestNeighbourSearcher(data);
|
||||
int[] counts = new int[data.length];
|
||||
boolean[] withinR = new boolean[data.length];
|
||||
double[] distancesWithinRInOrder = new double[data.length];
|
||||
double[][] distancesAndIndicesWithinR = new double[data.length][2];
|
||||
double r = 0.2;
|
||||
long startTime = Calendar.getInstance().getTimeInMillis();
|
||||
for (int t = 0; t < data.length; t++) {
|
||||
searcher.findPointsWithinR(t, r, exclWindow, !strict,
|
||||
withinR, distancesWithinRInOrder, distancesAndIndicesWithinR);
|
||||
int count = 0;
|
||||
// Run through the list of points returned as within r and check them
|
||||
for (int t2 = 0; distancesAndIndicesWithinR[t2][1] >= 0; t2++) {
|
||||
count++;
|
||||
assertTrue(withinR[(int) distancesAndIndicesWithinR[t2][1]]);
|
||||
// Reset this marker
|
||||
withinR[(int) distancesAndIndicesWithinR[t2][1]] = false;
|
||||
// Check that it's really within r
|
||||
double maxNorm = Math.abs(data[t] - data[(int) distancesAndIndicesWithinR[t2][1]]);
|
||||
assertTrue((strict && (maxNorm < r)) ||
|
||||
( !strict && (maxNorm <= r)));
|
||||
// Check that its distance was properly returned in the other arrays:
|
||||
assertEquals(maxNorm, distancesAndIndicesWithinR[t2][0], 0.000001);
|
||||
assertEquals(distancesAndIndicesWithinR[t2][0],
|
||||
distancesWithinRInOrder[(int) distancesAndIndicesWithinR[t2][1]],
|
||||
0.00000001);
|
||||
}
|
||||
// Check that there were no other points marked as within r in withinR:
|
||||
int count2 = 0;
|
||||
for (int t2 = 0; t2 < data.length; t2++) {
|
||||
if (withinR[t2]) {
|
||||
count2++;
|
||||
}
|
||||
}
|
||||
assertEquals(0, count2); // We set all the ones that should have been true to false already
|
||||
// Now reset the boolean array
|
||||
counts[t] = count;
|
||||
}
|
||||
long endTimeNNs = Calendar.getInstance().getTimeInMillis();
|
||||
System.out.printf("Found all neighbours within %.3f (mean = %.3f) in: %.3f sec\n",
|
||||
r, MatrixUtils.mean(counts),
|
||||
((double) (endTimeNNs - startTime)/1000.0));
|
||||
|
||||
// Now do brute force:
|
||||
int[] bruteForceCounts = new int[data.length];
|
||||
for (int t = 0; t < data.length; t++) {
|
||||
int count = 0;
|
||||
for (int t2 = 0; t2 < data.length; t2++) {
|
||||
if (Math.abs(t2 - t) <= exclWindow) {
|
||||
continue;
|
||||
}
|
||||
double norm = Math.abs(data[t] - data[t2]);
|
||||
if ((strict && (norm < r) ) ||
|
||||
(!strict && (norm <= r))) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
bruteForceCounts[t] = count;
|
||||
}
|
||||
long endTimeBruteForce = Calendar.getInstance().getTimeInMillis();
|
||||
System.out.printf("Found all neighbours within %.3f (mean = %.3f) by brute force in: %.3f sec\n",
|
||||
r, MatrixUtils.mean(bruteForceCounts),
|
||||
((double) (endTimeBruteForce - endTimeNNs)/1000.0));
|
||||
|
||||
// Now validate the nearest neighbour counts:
|
||||
for (int t = 0; t < data.length; t++) {
|
||||
assertEquals(bruteForceCounts[t], counts[t]);
|
||||
}
|
||||
}
|
||||
|
||||
public void checkRangeFinderArrayCallsPlusMaxRadiusOut(boolean strict, int exclWindow) throws Exception {
|
||||
int numTimeStepsInitial = 10000;
|
||||
int duplicateSteps = 100;
|
||||
int numTimeSteps = numTimeStepsInitial+duplicateSteps;
|
||||
double[] dataRaw = rg.generateNormalData(numTimeStepsInitial, 0, 1);
|
||||
double[] data2 = new double[numTimeSteps];
|
||||
// Now duplicate some of the time steps as a test:
|
||||
System.arraycopy(dataRaw, 0, data2, 0, numTimeStepsInitial);
|
||||
System.arraycopy(dataRaw, 0, data2, numTimeStepsInitial, duplicateSteps);
|
||||
// And generate a first set of data:
|
||||
dataRaw = rg.generateNormalData(numTimeStepsInitial, 0, 1);
|
||||
double[] data1 = new double[numTimeSteps];
|
||||
// Now duplicate some of the time steps as a test:
|
||||
System.arraycopy(dataRaw, 0, data1, 0, numTimeStepsInitial);
|
||||
System.arraycopy(dataRaw, 0, data1, numTimeStepsInitial, duplicateSteps);
|
||||
|
||||
UnivariateNearestNeighbourSearcher searcher =
|
||||
new UnivariateNearestNeighbourSearcher(data2);
|
||||
boolean[] withinR = new boolean[data2.length];
|
||||
double[] distancesForFirstVariable = new double[data1.length];
|
||||
double[][] distancesAndIndicesWithinR = new double[data1.length][2];
|
||||
double r = 0.2;
|
||||
for (int t = 0; t < data2.length; t++) {
|
||||
// Compute the distances for the first variable:
|
||||
for (int t2 = 0; t2 < data1.length; t2++) {
|
||||
if (Math.abs(t2 - t) <= exclWindow) {
|
||||
// Within the exclusion window (always includes the point itself for exclWindow==0)
|
||||
withinR[t2] = false;
|
||||
distancesForFirstVariable[t2] = Double.POSITIVE_INFINITY;
|
||||
continue;
|
||||
}
|
||||
distancesForFirstVariable[t2] = Math.abs(data1[t] - data1[t2]);
|
||||
if ((strict && (distancesForFirstVariable[t2] < r)) ||
|
||||
(!strict && (distancesForFirstVariable[t2] <= r))) {
|
||||
withinR[t2] = true;
|
||||
} else {
|
||||
withinR[t2] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Now compute the max distances across both variables:
|
||||
searcher.findPointsWithinRAndTakeRadiusMax(t, r, exclWindow, !strict,
|
||||
withinR, distancesForFirstVariable, distancesAndIndicesWithinR);
|
||||
|
||||
// Now check all of the results -- first those said to be within r
|
||||
for (int t2 = 0; distancesAndIndicesWithinR[t2][1] >= 0; t2++) {
|
||||
// Check that the first variable was within r
|
||||
assertTrue(withinR[(int) distancesAndIndicesWithinR[t2][1]]);
|
||||
// Reset this marker
|
||||
withinR[(int) distancesAndIndicesWithinR[t2][1]] = false;
|
||||
// And check that the max norm was reported
|
||||
double maxNorm = (Math.abs(data2[t] - data2[(int) distancesAndIndicesWithinR[t2][1]]));
|
||||
if (maxNorm < distancesForFirstVariable[(int) distancesAndIndicesWithinR[t2][1]]) {
|
||||
maxNorm = distancesForFirstVariable[(int) distancesAndIndicesWithinR[t2][1]];
|
||||
}
|
||||
assertTrue((strict && (maxNorm < r)) ||
|
||||
( !strict && (maxNorm <= r)));
|
||||
// Check that its distance was properly returned in the array:
|
||||
assertEquals(maxNorm, distancesAndIndicesWithinR[t2][0], 0.000001);
|
||||
}
|
||||
|
||||
// Check that there were no other points that should have been returned:
|
||||
int count2 = 0;
|
||||
for (int t2 = 0; t2 < data2.length; t2++) {
|
||||
if (withinR[t2]) {
|
||||
// This won't count points that came through above
|
||||
// since we already disabled withinR for them,
|
||||
// nor the points within the exclusion window
|
||||
double maxNorm = Math.abs(data2[t] - data2[t2]);
|
||||
if ((strict && (maxNorm < r)) ||
|
||||
(!strict && (maxNorm <= r))) {
|
||||
count2++;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(0, count2); // We set all the ones that should have been true to false already
|
||||
}
|
||||
}
|
||||
|
||||
public void checkRangeFinderArrayCallsNewData(boolean strict) throws Exception {
|
||||
int numTimeSteps = 10000;
|
||||
double[] data = rg.generateNormalData(numTimeSteps, 0, 1);
|
||||
|
||||
int newSamplesToCheck = 1000;
|
||||
double[] samplePoints = rg.generateNormalData(newSamplesToCheck, 0, 1);
|
||||
|
||||
UnivariateNearestNeighbourSearcher searcher =
|
||||
new UnivariateNearestNeighbourSearcher(data);
|
||||
int[] counts = new int[newSamplesToCheck];
|
||||
boolean[] withinR = new boolean[data.length];
|
||||
int[] indicesWithinR = new int[data.length];
|
||||
double r = 0.2;
|
||||
long startTime = Calendar.getInstance().getTimeInMillis();
|
||||
for (int n = 0; n < newSamplesToCheck; n++) {
|
||||
int simpleCount = searcher.countPointsWithinR(samplePoints[n], r, !strict);
|
||||
|
||||
searcher.findPointsWithinR(r, samplePoints[n], !strict,
|
||||
withinR, indicesWithinR);
|
||||
int count = 0;
|
||||
// Run through the list of points returned as within r and check them
|
||||
for (int t2 = 0; indicesWithinR[t2] != -1; t2++) {
|
||||
count++;
|
||||
assertTrue(withinR[indicesWithinR[t2]]);
|
||||
// Reset this marker
|
||||
withinR[indicesWithinR[t2]] = false;
|
||||
// Check that it's really within r
|
||||
double maxNorm = Math.abs(samplePoints[n] - data[indicesWithinR[t2]]);
|
||||
assertTrue((strict && (maxNorm < r)) ||
|
||||
( !strict && (maxNorm <= r)));
|
||||
}
|
||||
// Check that there were no other points marked as within r:
|
||||
int count2 = 0;
|
||||
for (int t2 = 0; t2 < data.length; t2++) {
|
||||
if (withinR[t2]) {
|
||||
count2++;
|
||||
}
|
||||
}
|
||||
assertEquals(0, count2); // We set all the ones that should have been true to false already
|
||||
assertEquals(simpleCount, count);
|
||||
// Now reset the boolean array
|
||||
counts[n] = count;
|
||||
}
|
||||
long endTimeNNs = Calendar.getInstance().getTimeInMillis();
|
||||
System.out.printf("Found all neighbours within %.3f (mean = %.3f) in: %.3f sec\n",
|
||||
r, MatrixUtils.mean(counts),
|
||||
((double) (endTimeNNs - startTime)/1000.0));
|
||||
|
||||
// Now do brute force:
|
||||
int[] bruteForceCounts = new int[newSamplesToCheck];
|
||||
for (int n = 0; n < newSamplesToCheck; n++) {
|
||||
int count = 0;
|
||||
for (int t2 = 0; t2 < data.length; t2++) {
|
||||
double norm = Math.abs(samplePoints[n] - data[t2]);
|
||||
if ((strict && (norm < r) ) ||
|
||||
(!strict && (norm <= r))) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
bruteForceCounts[n] = count;
|
||||
}
|
||||
long endTimeBruteForce = Calendar.getInstance().getTimeInMillis();
|
||||
System.out.printf("Found all neighbours within %.3f (mean = %.3f) by brute force in: %.3f sec\n",
|
||||
r, MatrixUtils.mean(bruteForceCounts),
|
||||
((double) (endTimeBruteForce - endTimeNNs)/1000.0));
|
||||
|
||||
// Now validate the nearest neighbour counts:
|
||||
for (int n = 0; n < newSamplesToCheck; n++) {
|
||||
assertEquals(bruteForceCounts[n], counts[n]);
|
||||
}
|
||||
}
|
||||
|
||||
public void testFindKNearestNeighbours() throws Exception {
|
||||
for (int K = 1; K < 5; K++) {
|
||||
double[] data = rg.generateNormalData(1000, 0, 1);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Java Information Dynamics Toolkit (JIDT)
|
||||
Copyright (C) 2012-2014 Joseph T. Lizier
|
||||
Copyright (C) 2014-2015 Joseph T. Lizier and Ipek Özdemir
|
||||
Copyright (C) 2014-2016 Joseph T. Lizier and Ipek Özdemir
|
||||
|
||||
Version @VERSION@ (see release notes below)
|
||||
|
||||
|
|
@ -31,22 +31,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
Website
|
||||
=============
|
||||
|
||||
Full information on the JIDT (usage, etc) is provided at the project page and wiki on google code:
|
||||
Full information on the JIDT (usage, etc) is provided at the project page and wiki on github:
|
||||
|
||||
http://code.google.com/p/information-dynamics-toolkit/
|
||||
https://github.com/jlizier/jidt/
|
||||
https://github.com/jlizier/jidt/wiki
|
||||
|
||||
=============
|
||||
Installation
|
||||
=============
|
||||
|
||||
"Full" description of any required installation is at: http://code.google.com/p/information-dynamics-toolkit/wiki/Installation
|
||||
"Full" description of any required installation is at: https://github.com/jlizier/jidt/wiki/Installation
|
||||
|
||||
However, if you are reading this file, you've downloaded a distribution and you're halfway there!
|
||||
|
||||
There are no dependencies to download; unless:
|
||||
a. You don't have java installed - download it from http://www.java.com/
|
||||
b. You wish to build the project using the build.xml script - this requires ant: http://ant.apache.org/
|
||||
c. You wish to run the JUnit test cases - this requires JUnit: http://www.junit.org/ - for how to run JUnit with our ant script see http://code.google.com/p/information-dynamics-toolkit/wiki/JUnitTestCases
|
||||
c. You wish to run the JUnit test cases - this requires JUnit: http://www.junit.org/ - for how to run JUnit with our ant script see https://github.com/jlizier/jidt/wiki/JUnitTestCases
|
||||
|
||||
Then just put the jar in a relevant location in your file structure.
|
||||
|
||||
|
|
@ -61,10 +62,10 @@ A research paper describing the toolkit is included in the top level directory -
|
|||
A tutorial, providing background to the information-theoretic measures, various estimators, and then to the JIDT toolkit itself is included in the tutorial folder (see "JIDT-TutorialSlides.pdf" for the tutorial slides, and "README-TutorialAndExercise.pdf" for further description of the tutorial exercises).
|
||||
|
||||
Javadocs for the toolkit are included in the full distribution at javadocs.
|
||||
They can also be generated using "ant javadocs" (useful if you are on an SVN view).
|
||||
Further, they will soon be posted on the web.
|
||||
They can also be generated using "ant javadocs" (useful if you are on a git clone).
|
||||
Further, they will are posted on the web via links at https://github.com/jlizier/jidt/wiki/Documentation
|
||||
|
||||
The project wiki also contains further information on various aspects; see http://code.google.com/p/information-dynamics-toolkit/ to start.
|
||||
The project wiki also contains further information on various aspects; see https://github.com/jlizier/jidt/wiki to start.
|
||||
|
||||
Further documentation is provided by the Usage demo examples below.
|
||||
|
||||
|
|
@ -76,33 +77,33 @@ You can also join our email discussion group jidt-discuss at http://groups.googl
|
|||
|
||||
Several sets of demonstration code are distributed with the toolkit:
|
||||
|
||||
a. demos/AutoAnalyser -- a GUI tool to compute transfer entropy on a chosen data set with the toolkit, and also automatically generate code in Java, Python and Matlab to show how to do this calculation with the toolkit. See description at https://code.google.com/p/information-dynamics-toolkit/wiki/AutoAnalyser
|
||||
a. demos/AutoAnalyser -- a GUI tool to compute transfer entropy on a chosen data set with the toolkit, and also automatically generate code in Java, Python and Matlab to show how to do this calculation with the toolkit. See description at https://github.com/jlizier/jidt/wiki/AutoAnalyser
|
||||
|
||||
b. demos/java -- basic examples on easily using the Java toolkit -- run these from the shell scripts in this directory -- see description at http://code.google.com/p/information-dynamics-toolkit/wiki/SimpleJavaExamples
|
||||
b. demos/java -- basic examples on easily using the Java toolkit -- run these from the shell scripts in this directory -- see description at https://github.com/jlizier/jidt/wiki/SimpleJavaExamples
|
||||
|
||||
c. Several demo sets mirror the SimpleJavaExamples to demonstrate the use of the toolkit in non-Java environments:
|
||||
|
||||
i. demos/octave -- basic examples on easily using the Java toolkit from Octave or Matlab environments -- see description at http://code.google.com/p/information-dynamics-toolkit/wiki/OctaveMatlabExamples
|
||||
i. demos/octave -- basic examples on easily using the Java toolkit from Octave or Matlab environments -- see description at https://github.com/jlizier/jidt/wiki/OctaveMatlabExamples
|
||||
|
||||
ii. demos/python -- basic examples on easily using the Java toolkit from Python -- see description at http://code.google.com/p/information-dynamics-toolkit/wiki/PythonExamples
|
||||
ii. demos/python -- basic examples on easily using the Java toolkit from Python -- see description at https://github.com/jlizier/jidt/wiki/PythonExamples
|
||||
|
||||
iii. demos/r -- basic examples on easily using the Java toolkit from R -- see description at http://code.google.com/p/information-dynamics-toolkit/wiki/R_Examples
|
||||
iii. demos/r -- basic examples on easily using the Java toolkit from R -- see description at https://github.com/jlizier/jidt/wiki/R_Examples
|
||||
|
||||
iv. demos/julia -- basic examples on easily using the Java toolkit from Julia -- see description at http://code.google.com/p/information-dynamics-toolkit/wiki/JuliaExamples
|
||||
iv. demos/julia -- basic examples on easily using the Java toolkit from Julia -- see description at https://github.com/jlizier/jidt/wiki/JuliaExamples
|
||||
|
||||
v. demos/clojure -- basic examples on easily using the Java toolkit from Clojure -- see description at http://code.google.com/p/information-dynamics-toolkit/wiki/Clojure_Examples
|
||||
v. demos/clojure -- basic examples on easily using the Java toolkit from Clojure -- see description at https://github.com/jlizier/jidt/wiki/Clojure_Examples
|
||||
|
||||
d. demos/octave/CellularAutomata -- using the Java toolkit to plot local information dynamics profiles in cellular automata; the toolkit is run under Octave or Matlab -- see description at http://code.google.com/p/information-dynamics-toolkit/wiki/CellularAutomataDemos
|
||||
d. demos/octave/CellularAutomata -- using the Java toolkit to plot local information dynamics profiles in cellular automata; the toolkit is run under Octave or Matlab -- see description at https://github.com/jlizier/jidt/wiki/CellularAutomataDemos
|
||||
|
||||
e. demos/octave/SchreiberTransferEntropyExamples -- recreates the transfer entropy examples in Schreiber's original paper presenting this measure; shows the correct parameter settings to reproduce these results -- see description at http://code.google.com/p/information-dynamics-toolkit/wiki/SchreiberTeDemos
|
||||
e. demos/octave/SchreiberTransferEntropyExamples -- recreates the transfer entropy examples in Schreiber's original paper presenting this measure; shows the correct parameter settings to reproduce these results -- see description at https://github.com/jlizier/jidt/wiki/SchreiberTeDemos
|
||||
|
||||
f. demos/octave/DetectingInteractionLags -- demonstration of using the transfer entropy with source-destination lags; the demo is run under Octave or Matlab -- see description at http://code.google.com/p/information-dynamics-toolkit/wiki/DetectingInteractionLags
|
||||
f. demos/octave/DetectingInteractionLags -- demonstration of using the transfer entropy with source-destination lags; the demo is run under Octave or Matlab -- see description at https://github.com/jlizier/jidt/wiki/DetectingInteractionLags
|
||||
|
||||
g. demos/java/InterregionalTransfer -- higher level example using collective transfer entropy to infer effective connections between "regions" of data -- see description at http://code.google.com/p/information-dynamics-toolkit/wiki/InterregionalTransfer
|
||||
g. demos/java/InterregionalTransfer -- higher level example using collective transfer entropy to infer effective connections between "regions" of data -- see description at https://github.com/jlizier/jidt/wiki/InterregionalTransfer
|
||||
|
||||
h. demos/octave/NullDistributions -- investigating the correspondence between analytic and bootstrapped distributions for TE and MI under null hypotheses of no relationship; the demo is run under Octave or Matlab -- see description at https://code.google.com/p/information-dynamics-toolkit/wiki/NullDistributions
|
||||
h. demos/octave/NullDistributions -- investigating the correspondence between analytic and bootstrapped distributions for TE and MI under null hypotheses of no relationship; the demo is run under Octave or Matlab -- see description at https://github.com/jlizier/jidt/wiki/NullDistributions
|
||||
|
||||
i. java/unittests -- the JUnit test cases for the Java toolkit are included in the distribution -- these case also be browsed to see simple use cases for the various calculators in the toolkit -- see description at http://code.google.com/p/information-dynamics-toolkit/wiki/JUnitTestCases
|
||||
i. java/unittests -- the JUnit test cases for the Java toolkit are included in the distribution -- these case also be browsed to see simple use cases for the various calculators in the toolkit -- see description at https://github.com/jlizier/jidt/wiki/JUnitTestCases
|
||||
|
||||
=============
|
||||
Citation
|
||||
|
|
@ -112,7 +113,7 @@ Please cite your use of this toolkit as:
|
|||
|
||||
Joseph T. Lizier, "JIDT: An information-theoretic toolkit for studying the dynamics of complex systems", Frontiers in Robotics and AI 1:11, 2014; doi:10.3389/frobt.2014.00011
|
||||
|
||||
A pre-print of this paper is distributed with this toolkit (InfoDynamicsToolkit.pdf) and is available at arXiv:1408.3270
|
||||
A pre-print of this paper is distributed with this toolkit (InfoDynamicsToolkit.pdf) and is available at arXiv:1408.3270 (https://arxiv.org/abs/1408.3270)
|
||||
|
||||
=============
|
||||
Notices
|
||||
|
|
@ -132,6 +133,24 @@ Notices for this software are found in the notices/JAMA directory.
|
|||
Release notes
|
||||
===============
|
||||
|
||||
v1.3.1 21/10/2016
|
||||
-----------------
|
||||
(after 385 commits recorded by github, repository as at https://github.com/jlizier/jidt/tree/269e263a84998807c5c02f36397b585a19205938 save for this file update)
|
||||
Major update to TransferEntropyCalculatorDiscrete so as to implement arbirtray source and dest embeddings and source-dest delay;
|
||||
Conditional TE calculators (continuous) handle empty conditional variables;
|
||||
Added auto-embedding method for AIS and TE which maximises bias corrected AIS;
|
||||
Added getNumSeparateObservations() method to TE calculators to make reconstructing/separating local values easier after multiple addObservations() calls;
|
||||
Fixed kernel estimator classes to return proper densities, not probabilities;
|
||||
Bug fix in mixed discrete-continuous MI (Kraskov) implementation;
|
||||
Added simple interface for adding joint observations for MultiInfoCalculatorDiscrete
|
||||
Including compiled class files for the AutoAnalyser demo in distribution;
|
||||
Updated Python demo 1 to show use of numpy arrays with ints;
|
||||
Added Python demo 7 and 9 for TE Kraskov with ensemble method and auto-embedding respectively;
|
||||
Added Matlab/Octave example 10 for conditional TE via Kraskov (KSG) algorithm;
|
||||
Added utilities to prepare for enhancing surrogate calculations with fast nearest neighbour search;
|
||||
Minor bug patch to Python readFloatsFile utility;
|
||||
|
||||
|
||||
v1.3 10/7/2015 at r691
|
||||
----------------------
|
||||
Added AutoAnalyser (Code Generator) GUI demo for MI and TE;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Java Information Dynamics Toolkit
|
||||
Copyright (C) 2012-2014 Joseph T. Lizier
|
||||
Copyright (C) 2014 Joseph T. Lizier and Ipek Özdemir
|
||||
Copyright (C) 2014-2016 Joseph T. Lizier and Ipek Özdemir
|
||||
|
||||
Version @VERSION@
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue