diff --git a/demos/AutoAnalyser/runCMIAutoAnalyser.bat b/demos/AutoAnalyser/runCMIAutoAnalyser.bat new file mode 100755 index 0000000..07dbda8 --- /dev/null +++ b/demos/AutoAnalyser/runCMIAutoAnalyser.bat @@ -0,0 +1,8 @@ +@ECHO OFF + +REM Make sure the latest example source file is compiled. +javac -classpath "..\java;..\..\infodynamics.jar" "..\java\infodynamics\demos\autoanalysis\AutoAnalyserCMI.java" + +REM Run the example: +java -classpath "..\java;..\..\infodynamics.jar" infodynamics.demos.autoanalysis.AutoAnalyserCMI + diff --git a/demos/AutoAnalyser/runCMIAutoAnalyser.sh b/demos/AutoAnalyser/runCMIAutoAnalyser.sh new file mode 100755 index 0000000..5c508a4 --- /dev/null +++ b/demos/AutoAnalyser/runCMIAutoAnalyser.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Make sure the latest example source file is compiled. +javac -classpath "../java:../../infodynamics.jar" "../java/infodynamics/demos/autoanalysis/AutoAnalyserCMI.java" + +# Run the example: +java -classpath "../java:../../infodynamics.jar" infodynamics.demos.autoanalysis.AutoAnalyserCMI + diff --git a/demos/java/infodynamics/demos/autoanalysis/AutoAnalyser.java b/demos/java/infodynamics/demos/autoanalysis/AutoAnalyser.java index 44ea6d5..8157e16 100644 --- a/demos/java/infodynamics/demos/autoanalysis/AutoAnalyser.java +++ b/demos/java/infodynamics/demos/autoanalysis/AutoAnalyser.java @@ -121,6 +121,9 @@ public abstract class AutoAnalyser extends JFrame // Should be overridden by the child classes. protected String[] variableColNumLabels = null; + protected boolean[] disableVariableColTextFieldsForAllCombos = null; + protected int indentsForAllCombos = 1; + // Store which calculator type we're using: @SuppressWarnings("rawtypes") protected Class calcClass = null; @@ -688,22 +691,30 @@ public abstract class AutoAnalyser extends JFrame int[] singleCalcColumns = new int[numVariables]; Vector variableCombinations = new Vector(); - if (allCombosCheckBox.isSelected()) { - // We're doing all combinations - fillOutAllCombinations(variableCombinations); - } else { - // we're doing a single combination - for (int i = 0; i < numVariables; i++) { - singleCalcColumns[i] = Integer.parseInt(variableColTextFields[i].getText()); - if ((singleCalcColumns[i] < 0) || (singleCalcColumns[i] >= dataColumns)) { - JOptionPane.showMessageDialog(this, - String.format("%s column must be between 0 and %d for this data set", - variableColNumLabels[i], dataColumns-1)); - resultsLabel.setText(" "); - return; + try { + if (allCombosCheckBox.isSelected()) { + // We're doing all combinations + fillOutAllCombinations(variableCombinations); + } else { + // we're doing a single combination + for (int i = 0; i < numVariables; i++) { + singleCalcColumns[i] = Integer.parseInt(variableColTextFields[i].getText()); + if ((singleCalcColumns[i] < 0) || (singleCalcColumns[i] >= dataColumns)) { + JOptionPane.showMessageDialog(this, + String.format("%s column must be between 0 and %d for this data set", + variableColNumLabels[i], dataColumns-1)); + resultsLabel.setText(" "); + return; + } } + variableCombinations.add(singleCalcColumns); } - variableCombinations.add(singleCalcColumns); + } catch (Exception e) { + // Catches number format exception, and column number out of bounds + JOptionPane.showMessageDialog(this, + e.getMessage()); + resultsLabel.setText("Cannot parse a column number from input: " + e.getMessage()); + return; } // Generate headers: @@ -1013,10 +1024,13 @@ public abstract class AutoAnalyser extends JFrame // Get the indents right from here on, // and prepare a string of the column labels for // each variable, to be used in later formatting. - for (int i = 0; i < numVariables; i++) { + for (int i = 0; i < indentsForAllCombos; i++) { javaPrefix += " "; pythonPrefix += "\t"; matlabPrefix += "\t"; + } + + for (int i = 0; i < numVariables; i++) { extraFormatTerms.append(columnVariables[i] + ", "); } @@ -1324,7 +1338,7 @@ public abstract class AutoAnalyser extends JFrame * * @param variableCombinations */ - protected abstract void fillOutAllCombinations(Vector variableCombinations); + protected abstract void fillOutAllCombinations(Vector variableCombinations) throws Exception; /** * Method to allow child classes to set up the loops over all combinations of @@ -1748,7 +1762,9 @@ public abstract class AutoAnalyser extends JFrame // "All pairs" checkbox -- update in case changed: if (allCombosCheckBox.isSelected()) { for (int i = 0; i < numVariables; i++) { - variableColTextFields[i].setEnabled(false); + if (disableVariableColTextFieldsForAllCombos[i]) { + variableColTextFields[i].setEnabled(false); + } } } else { for (int i = 0; i < numVariables; i++) { diff --git a/demos/java/infodynamics/demos/autoanalysis/AutoAnalyserCMI.java b/demos/java/infodynamics/demos/autoanalysis/AutoAnalyserCMI.java new file mode 100644 index 0000000..526c9ff --- /dev/null +++ b/demos/java/infodynamics/demos/autoanalysis/AutoAnalyserCMI.java @@ -0,0 +1,366 @@ +/* + * Java Information Dynamics Toolkit (JIDT) + * Copyright (C) 2015, Joseph T. Lizier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package infodynamics.demos.autoanalysis; + +import infodynamics.measures.continuous.ConditionalMutualInfoCalculatorMultiVariate; +import infodynamics.measures.continuous.ConditionalMutualInfoMultiVariateCommon; +import infodynamics.measures.continuous.InfoMeasureCalculatorContinuous; +import infodynamics.measures.continuous.gaussian.ConditionalMutualInfoCalculatorMultiVariateGaussian; +import infodynamics.measures.continuous.kraskov.ConditionalMutualInfoCalculatorMultiVariateKraskov; +import infodynamics.measures.continuous.kraskov.ConditionalMutualInfoCalculatorMultiVariateKraskov1; +import infodynamics.measures.continuous.kraskov.ConditionalMutualInfoCalculatorMultiVariateKraskov2; +import infodynamics.measures.discrete.ConditionalMutualInformationCalculatorDiscrete; +import infodynamics.measures.discrete.InfoMeasureCalculatorDiscrete; +import infodynamics.utils.MatrixUtils; + +import javax.swing.JOptionPane; +import javax.swing.event.DocumentListener; + +import java.awt.event.ActionListener; +import java.awt.event.MouseListener; +import java.util.Vector; + +/** + * This class provides a GUI to build a simple conditional mutual information calculation, + * and supply the code to execute it. + * + * + * @author Joseph Lizier + * + */ +public class AutoAnalyserCMI extends AutoAnalyser + implements ActionListener, DocumentListener, MouseListener { + + /** + * Need serialVersionUID to be serializable + */ + private static final long serialVersionUID = 1L; + + // Property names for specific continuous calculators: + protected String[] gaussianProperties; + protected String[] gaussianPropertiesFieldNames; + protected String[] gaussianPropertyDescriptions; + protected String[] kraskovProperties; + protected String[] kraskovPropertiesFieldNames; + protected String[] kraskovPropertyDescriptions; + + protected static final String DISCRETE_PROPNAME_TIME_DIFF = "time difference"; + + protected static final String CALC_TYPE_KRASKOV_ALG1 = CALC_TYPE_KRASKOV + " alg. 1"; + protected static final String CALC_TYPE_KRASKOV_ALG2 = CALC_TYPE_KRASKOV + " alg. 2"; + + /** + * Constructor to initialise the GUI for MI + */ + protected void makeSpecificInitialisations() { + + numVariables = 3; + variableColNumLabels = new String[] {"Source", "Destination", "Conditional"}; + useAllCombosCheckBox = true; + useStatSigCheckBox = true; + wordForCombinations = "pairs"; + variableRelationshipFormatString = "col_%d -> col_%d | col_%d"; + disableVariableColTextFieldsForAllCombos = new boolean[] {true, true, false}; + indentsForAllCombos = 2; + + // Set up the properties for CMI: + measureAcronym = "CMI"; + appletTitle = "JIDT Conditional MI Auto-Analyser"; + + calcTypes = new String[] { + CALC_TYPE_DISCRETE, CALC_TYPE_BINNED, CALC_TYPE_GAUSSIAN, + CALC_TYPE_KRASKOV_ALG1, CALC_TYPE_KRASKOV_ALG2}; + // No kernel calculator defined for CMI (yet, and unlikely to happen) + unitsForEachCalc = new String[] {"bits", "bits", "nats", "nats", "nats"}; + + // Discrete: + discreteClass = ConditionalMutualInformationCalculatorDiscrete.class; + discreteProperties = new String[] { + DISCRETE_PROPNAME_BASE + }; + discretePropertyDefaultValues = new String[] { + "2" + }; + discretePropertyDescriptions = new String[] { + "Number of discrete states available for each variable (i.e. 2 for binary).
" + + "Can be set individually for each variable -- see code." + }; + + // Continuous: + abstractContinuousClass = ConditionalMutualInfoCalculatorMultiVariate.class; + // Common properties for all continuous calcs: + commonContPropertyNames = new String[] { + // None + }; + commonContPropertiesFieldNames = new String[] { + // None + }; + commonContPropertyDescriptions = new String[] { + // None + }; + // Gaussian properties: + gaussianProperties = new String[] { + }; + gaussianPropertiesFieldNames = new String[] { + }; + gaussianPropertyDescriptions = new String[] { + }; + // KSG (Kraskov): + kraskovProperties = new String[] { + ConditionalMutualInfoMultiVariateCommon.PROP_NORMALISE, + ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_K, + ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_ADD_NOISE, + ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_DYN_CORR_EXCL_TIME, + ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NORM_TYPE, + ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NUM_THREADS, + }; + kraskovPropertiesFieldNames = new String[] { + "ConditionalMutualInfoMultiVariateCommon.PROP_NORMALISE", + "ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_K", + "ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_ADD_NOISE", + "ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_DYN_CORR_EXCL_TIME", + "ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NORM_TYPE", + "ConditionalMutualInfoCalculatorMultiVariateKraskov.PROP_NUM_THREADS", + }; + kraskovPropertyDescriptions = new String[] { + "(boolean) whether to normalise
each incoming time-series to mean 0, standard deviation 1, or not (recommended)", + "Number of k nearest neighbours to use
in the full joint kernel space in the KSG algorithm", + "Standard deviation for an amount
of random Gaussian noise to add to each variable, " + + "to avoid having neighbourhoods with artificially large counts.
" + + "(\"false\" may be used to indicate \"0\".). The amount is added in after any normalisation.", + "Dynamic correlation exclusion time or
Theiler window (see Kantz and Schreiber); " + + "0 (default) means no dynamic exclusion window", + "
Norm type to use in KSG algorithm between the points in each marginal space.
Options are: " + + "\"MAX_NORM\" (default), otherwise \"EUCLIDEAN\" or \"EUCLIDEAN_SQUARED\" (both equivalent here)", + "Number of parallel threads to use
in computation: an integer > 0 or \"USE_ALL\" " + + "(default, to indicate to use all available processors)", + }; + + } + + @Override + protected void fillOutAllCombinations(Vector variableCombinations) throws Exception { + // All combinations here means all pairs of sources and destinations, + // with the conditional fixed. + int conditional = Integer.parseInt(variableColTextFields[2].getText()); + if (conditional >= dataColumns) { + throw new Exception(String.format("%s column must be between 0 and %d for this data set", + variableColNumLabels[2], dataColumns-1)); + } + for (int s = 0; s < dataColumns; s++) { + for (int d = 0; d < dataColumns; d++) { + variableCombinations.add(new int[] {s, d, conditional}); + } + } + } + + @Override + protected String[] setUpLoopsForAllCombos(StringBuffer javaCode, + StringBuffer pythonCode, StringBuffer matlabCode) { + // Set up loops in the code: + int conditional = Integer.parseInt(variableColTextFields[2].getText()); + // 1. Java code + javaCode.append(" \n"); + javaCode.append(" int c = " + conditional + ";\n"); + javaCode.append(" // Compute for all source-destination pairs:\n"); + javaCode.append(" for (int s = 0; s < " + dataColumns + + "; s++) {\n"); + javaCode.append(" for (int d = 0; d < " + dataColumns + + "; d++) {\n"); + String javaPrefix = " "; + javaCode.append(javaPrefix + "// For each source-dest pair (given conditional):\n"); + javaCode.append(javaPrefix + "if ((s == d) || (s == c) || (d == c)) {\n"); + javaCode.append(javaPrefix + " continue;\n"); + javaCode.append(javaPrefix + "}\n"); + // 2. Python code + pythonCode.append("\n"); + pythonCode.append("c = " + conditional + "\n"); + pythonCode.append("# Compute for all pairs:\n"); + pythonCode.append("for s in range(" + dataColumns + "):\n"); + pythonCode.append("\tfor d in range(" + dataColumns + "):\n"); + String pythonPrefix = "\t\t"; + pythonCode.append(pythonPrefix+ "# For each source-dest pair (given conditional):\n"); + pythonCode.append(pythonPrefix + "if ((s == d) or (s == c) or (d == c)):\n"); + pythonCode.append(pythonPrefix + "\tcontinue\n"); + // 3. Matlab code + matlabCode.append("\n"); + matlabCode.append("c = " + (conditional+1) + ";\n"); + matlabCode.append("% Compute for all pairs:\n"); + matlabCode.append("for s = 1:" + dataColumns + "\n"); + matlabCode.append("\tfor d = 1:" + dataColumns + "\n"); + String matlabPrefix = "\t\t"; + matlabCode.append(matlabPrefix + "% For each source-dest pair (given conditional):\n"); + matlabCode.append(matlabPrefix + "if ((s == d) || (s == c) || (d == c))\n"); + matlabCode.append(matlabPrefix + "\tcontinue;\n"); + matlabCode.append(matlabPrefix + "end\n"); + + // Return the variables to index each column: + return new String[] {"s", "d", "c"}; + } + + @Override + protected void finaliseLoopsForAllCombos(StringBuffer javaCode, + StringBuffer pythonCode, StringBuffer matlabCode) { + + // 1. Java code + javaCode.append(" }\n"); + javaCode.append(" }\n"); + // 2. Python code + // Nothing to do + // 3. Matlab code + matlabCode.append("\tend\n"); + matlabCode.append("end\n"); + } + + @Override + protected String formatStringWithColumnNumbers(String formatStr, int[] columnNumbers) { + // We format the source and target variable numbers into the + // return string here: + return String.format(formatStr, + columnNumbers[0], columnNumbers[1], columnNumbers[2]); + } + + @Override + protected boolean skipColumnCombo(int[] columnCombo) { + if ((columnCombo[0] == columnCombo[1]) || + (columnCombo[0] == columnCombo[2]) || + (columnCombo[1] == columnCombo[2])) { + // Two columns are the same here, + // so don't compute the conditional MI + return true; + } + return false; + } + + @Override + protected void setObservations(InfoMeasureCalculatorDiscrete calcDiscrete, + InfoMeasureCalculatorContinuous calcContinuous, + int[] columnCombo) throws Exception { + + String selectedCalcType = (String) + calcTypeComboBox.getSelectedItem(); + + int sourceColumn = columnCombo[0]; + int destColumn = columnCombo[1]; + int condColumn = columnCombo[2]; + + // Set observations + if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_DISCRETE)) { + ConditionalMutualInformationCalculatorDiscrete cmiCalc = + (ConditionalMutualInformationCalculatorDiscrete) calcDiscrete; + cmiCalc.addObservations( + MatrixUtils.selectColumn(dataDiscrete, sourceColumn), + MatrixUtils.selectColumn(dataDiscrete, destColumn), + MatrixUtils.selectColumn(dataDiscrete, condColumn)); + } else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_BINNED)) { + ConditionalMutualInformationCalculatorDiscrete cmiCalc = + (ConditionalMutualInformationCalculatorDiscrete) calcDiscrete; + cmiCalc.addObservations( + MatrixUtils.discretise( + MatrixUtils.selectColumn(data, sourceColumn), + Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE))), + MatrixUtils.discretise( + MatrixUtils.selectColumn(data, destColumn), + Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE))), + MatrixUtils.discretise( + MatrixUtils.selectColumn(data, condColumn), + Integer.parseInt(propertyValues.get(DISCRETE_PROPNAME_BASE)))); + } else { + ConditionalMutualInfoCalculatorMultiVariate cmiCalc = + (ConditionalMutualInfoCalculatorMultiVariate) calcContinuous; + cmiCalc.setObservations( + MatrixUtils.selectColumn(data, sourceColumn), + MatrixUtils.selectColumn(data, destColumn), + MatrixUtils.selectColumn(data, condColumn)); + } + } + + protected CalcProperties assignCalcProperties(String selectedCalcType) + throws Exception { + // Let the super class handle discrete calculators + CalcProperties calcProperties = super.assignCalcProperties(selectedCalcType); + if (calcProperties == null) { + // We need to assign properties for a continuous calculator + calcProperties = new CalcProperties(); + calcProperties.calc = assignCalcObjectContinuous(selectedCalcType); + calcProperties.calcClass = calcProperties.calc.getClass(); + if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) { + calcProperties.classSpecificPropertyNames = gaussianProperties; + calcProperties.classSpecificPropertiesFieldNames = gaussianPropertiesFieldNames; + calcProperties.classSpecificPropertyDescriptions = gaussianPropertyDescriptions; + } else if (selectedCalcType.startsWith(CALC_TYPE_KRASKOV)) { + // The if statement will work for both MI Kraskov calculators + calcProperties.classSpecificPropertyNames = kraskovProperties; + calcProperties.classSpecificPropertiesFieldNames = kraskovPropertiesFieldNames; + calcProperties.classSpecificPropertyDescriptions = kraskovPropertyDescriptions; + } else { + calcProperties = null; + throw new Exception("No recognised calculator selected: " + + selectedCalcType); + } + } + return calcProperties; + } + + /** + * Method to assign and initialise our continuous calculator class + */ + @Override + protected ConditionalMutualInfoCalculatorMultiVariate assignCalcObjectContinuous(String selectedCalcType) throws Exception { + if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_GAUSSIAN)) { + return new ConditionalMutualInfoCalculatorMultiVariateGaussian(); + } else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KRASKOV_ALG1)) { + return new ConditionalMutualInfoCalculatorMultiVariateKraskov1(); + } else if (selectedCalcType.equalsIgnoreCase(CALC_TYPE_KRASKOV_ALG2)) { + return new ConditionalMutualInfoCalculatorMultiVariateKraskov2(); + } else { + throw new Exception("No recognised continuous calculator selected: " + + selectedCalcType); + } + + } + + /** + * Method to assign and initialise our discrete calculator class + */ + protected DiscreteCalcAndArguments assignCalcObjectDiscrete() throws Exception { + int base; + try { + String basePropValueStr = propertyValues.get(DISCRETE_PROPNAME_BASE); + base = Integer.parseInt(basePropValueStr); + } catch (Exception ex) { + JOptionPane.showMessageDialog(this, + ex.getMessage()); + resultsLabel.setText("Cannot find a value for property " + DISCRETE_PROPNAME_BASE); + return null; + } + + return new DiscreteCalcAndArguments( + new ConditionalMutualInformationCalculatorDiscrete(base, base, base), + base, + base + ", " + base + ", " + base); + } + + /** + * @param args + */ + public static void main(String[] args) { + new AutoAnalyserCMI(); + } +} diff --git a/demos/java/infodynamics/demos/autoanalysis/AutoAnalyserChannelCalculator.java b/demos/java/infodynamics/demos/autoanalysis/AutoAnalyserChannelCalculator.java index 93b8438..d365b90 100644 --- a/demos/java/infodynamics/demos/autoanalysis/AutoAnalyserChannelCalculator.java +++ b/demos/java/infodynamics/demos/autoanalysis/AutoAnalyserChannelCalculator.java @@ -64,6 +64,9 @@ public abstract class AutoAnalyserChannelCalculator extends AutoAnalyser { useStatSigCheckBox = true; wordForCombinations = "pairs"; variableRelationshipFormatString = "col_%d -> col_%d"; + + disableVariableColTextFieldsForAllCombos = new boolean[] {true, true}; + indentsForAllCombos = 2; } @Override diff --git a/demos/java/infodynamics/demos/autoanalysis/AutoAnalyserEntropy.java b/demos/java/infodynamics/demos/autoanalysis/AutoAnalyserEntropy.java index b9ceb52..3131cae 100644 --- a/demos/java/infodynamics/demos/autoanalysis/AutoAnalyserEntropy.java +++ b/demos/java/infodynamics/demos/autoanalysis/AutoAnalyserEntropy.java @@ -69,7 +69,9 @@ public class AutoAnalyserEntropy extends AutoAnalyser { useStatSigCheckBox = false; wordForCombinations = "variables"; variableRelationshipFormatString = "col_%d"; - + disableVariableColTextFieldsForAllCombos = new boolean[] {true}; + indentsForAllCombos = 1; + // Set up the properties for Entropy: measureAcronym = "H"; appletTitle = "JIDT Entropy Auto-Analyser";