MI common estimators utilising underlying nearest neighbour searchers holding different data sets separately (for dynamic correlation exclusion)

This commit is contained in:
Joseph Lizier 2023-10-09 13:14:29 +11:00
parent 938dd7c4fb
commit 5f87e2018b
3 changed files with 224 additions and 24 deletions

View File

@ -92,6 +92,25 @@ public interface MutualInfoCalculatorMultiVariate
*/
public static final String PROP_ADD_NOISE = "NOISE_LEVEL_TO_ADD";
/**
* <p>As per {@link #addObservations(double[][], double[][])};
* but also includes parameters to track which observation set
* the samples came from. Intended to only be used by other
* estimator classes here and not by users directly.</p>
*
* @param source multivariate observations for variable 1
* (first index is time or observation index, second is variable number)
* @param destination multivariate observations for variable 2
* (first index is time or observation index, second is variable number)
* Length must match <code>source</code>, and their indices must correspond.
* @param observationSetIndexToUse which set of observations these came fmor
* @param startTimeIndex which was the first time index of these
* samples within that observation set.
* @throws Exception
*/
public void addObservationsTrackObservationIDs(double[][] source, double[][] destination,
int observationSetIndexToUse, int startTimeIndex) throws Exception;
/**
* Compute the mutual information if the observations of the
* first variable (source)
@ -130,4 +149,18 @@ public interface MutualInfoCalculatorMultiVariate
* @throws Exception for invalid property values
*/
public String getProperty(String propertyName) throws Exception;
/**
* Retrieve an array indicating which observation set each sample came from
*
* @return array of integers
*/
public int[] getObservationSetIndices();
/**
* Retrieve an array indicating which time index within its observation set that sample came from
*
* @return array of integers
*/
public int[] getObservationTimePoints();
}

View File

@ -23,6 +23,7 @@ import infodynamics.utils.EmpiricalMeasurementDistribution;
import infodynamics.utils.MatrixUtils;
import infodynamics.utils.RandomGenerator;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Random;
import java.util.Vector;
@ -86,6 +87,16 @@ public abstract class MutualInfoMultiVariateCommon implements
*/
protected double[] destStdsBeforeNorm;
/**
* Track which observation set each sample came from
*/
protected int[] observationSetIndices;
/**
* Track which sample index within an observation set that each sample came from
*/
protected int[] observationTimePoints;
/**
* Total number of observations supplied.
* Only valid after {@link #finaliseAddObservations()} is called.
@ -127,6 +138,22 @@ public abstract class MutualInfoMultiVariateCommon implements
* type calls
*/
protected Vector<double[][]> vectorOfDestinationObservations;
/**
* Tracks separate (time-series) observation sets
* we are taking samples from
*/
protected int observationSetIndex = 0;
/**
* Storage for which observation set each
* block of samples comes from
*/
protected Vector<Integer> vectorOfObservationSetIndices;
/**
* Storage for start time point for the observation
* set within its block of samples
*/
protected Vector<Integer> vectorOfObservationStartTimePoints;
/**
* Whether the user has supplied more than one (disjoint) set of samples
*/
@ -163,6 +190,11 @@ public abstract class MutualInfoMultiVariateCommon implements
sourceStdsBeforeNorm = null;
destMeansBeforeNorm = null;
destStdsBeforeNorm = null;
observationSetIndices = null;
observationTimePoints = null;
observationSetIndex = 0;
vectorOfObservationSetIndices = null;
vectorOfObservationStartTimePoints = null;
addedMoreThanOneObservationSet = false;
}
@ -296,14 +328,54 @@ public abstract class MutualInfoMultiVariateCommon implements
finaliseAddObservations();
}
@Override
public void setObservations(double[][] source, double[][] destination,
boolean[] sourceValid, boolean[] destValid) throws Exception {
startAddObservations();
addObservations(source, destination, sourceValid, destValid);
finaliseAddObservations();
}
@Override
public void setObservations(double[] source, double[] destination,
boolean[] sourceValid, boolean[] destValid) throws Exception {
if ((dimensionsDest != 1) || (dimensionsSource != 1)) {
throw new Exception("The number of source and dest dimensions (having been initialised to " +
dimensionsSource + " and " + dimensionsDest + ") can only be 1 when " +
"the univariate addObservations(double[],double[]) and " +
"setObservations(double[],double[]) methods are called");
}
setObservations(MatrixUtils.reshape(source, source.length, 1),
MatrixUtils.reshape(destination, destination.length, 1),
sourceValid, destValid);
}
@Override
public void setObservations(double[][] source, double[][] destination,
boolean[][] sourceValid, boolean[][] destValid) throws Exception {
boolean[] allSourceValid = MatrixUtils.andRows(sourceValid);
boolean[] allDestValid = MatrixUtils.andRows(destValid);
setObservations(source, destination, allSourceValid, allDestValid);
}
@Override
public void startAddObservations() {
vectorOfSourceObservations = new Vector<double[][]>();
vectorOfDestinationObservations = new Vector<double[][]>();
vectorOfObservationSetIndices = new Vector<Integer>();
vectorOfObservationStartTimePoints = new Vector<Integer>();
}
@Override
public void addObservations(double[][] source, double[][] destination) throws Exception {
// Use the current observationSetIndex and increment for next use:
addObservationsTrackObservationIDs(source, destination, observationSetIndex++, 0);
}
public void addObservationsTrackObservationIDs(double[][] source, double[][] destination,
int observationSetIndexToUse, int startTimeIndex) throws Exception {
if (vectorOfSourceObservations == null) {
// startAddObservations was not called first
throw new RuntimeException("User did not call startAddObservations before addObservations");
@ -326,6 +398,8 @@ public abstract class MutualInfoMultiVariateCommon implements
}
vectorOfSourceObservations.add(source);
vectorOfDestinationObservations.add(destination);
vectorOfObservationSetIndices.add(observationSetIndexToUse);
vectorOfObservationStartTimePoints.add(startTimeIndex);
}
/**
@ -391,8 +465,14 @@ public abstract class MutualInfoMultiVariateCommon implements
MatrixUtils.reshape(destination, destination.length, 1));
}
@Override
public void addObservations(double[][] source, double[][] destination,
int startTime, int numTimeSteps) throws Exception {
addObservations(source, destination, startTime, numTimeSteps, observationSetIndex++);
}
protected void addObservations(double[][] source, double[][] destination,
int startTime, int numTimeSteps, int observationSetIndexToUse) throws Exception {
if (vectorOfSourceObservations == null) {
// startAddObservations was not called first
throw new RuntimeException("User did not call startAddObservations before addObservations");
@ -403,12 +483,12 @@ public abstract class MutualInfoMultiVariateCommon implements
}
double[][] sourceToAdd = new double[numTimeSteps][];
System.arraycopy(source, startTime, sourceToAdd, 0, numTimeSteps);
vectorOfSourceObservations.add(sourceToAdd);
double[][] destToAdd = new double[numTimeSteps][];
System.arraycopy(destination, startTime, destToAdd, 0, numTimeSteps);
vectorOfDestinationObservations.add(destToAdd);
addObservationsTrackObservationIDs(sourceToAdd, destToAdd, observationSetIndexToUse, startTime);
}
@Override
public void addObservations(double[] source, double[] destination,
int startTime, int numTimeSteps) throws Exception {
@ -423,8 +503,14 @@ public abstract class MutualInfoMultiVariateCommon implements
startTime, numTimeSteps);
}
public void setObservations(double[][] source, double[][] destination,
public void addObservations(double[] source, double[] destination,
boolean[] sourceValid, boolean[] destValid) throws Exception {
addObservations(MatrixUtils.reshape(source, source.length, 1),
MatrixUtils.reshape(destination, destination.length, 1),
sourceValid, destValid);
}
public void addObservations(double[][] source, double[][] destination,
boolean[] sourceValid, boolean[] destValid) throws Exception {
Vector<int[]> startAndEndTimePairs = computeStartAndEndTimePairs(sourceValid, destValid);
@ -434,32 +520,18 @@ public abstract class MutualInfoMultiVariateCommon implements
for (int[] timePair : startAndEndTimePairs) {
int startTime = timePair[0];
int endTime = timePair[1];
addObservations(source, destination, startTime, endTime - startTime + 1);
addObservations(source, destination, startTime, endTime - startTime + 1, observationSetIndex);
}
observationSetIndex++;
finaliseAddObservations();
}
@Override
public void setObservations(double[] source, double[] destination,
boolean[] sourceValid, boolean[] destValid) throws Exception {
if ((dimensionsDest != 1) || (dimensionsSource != 1)) {
throw new Exception("The number of source and dest dimensions (having been initialised to " +
dimensionsSource + " and " + dimensionsDest + ") can only be 1 when " +
"the univariate addObservations(double[],double[]) and " +
"setObservations(double[],double[]) methods are called");
}
setObservations(MatrixUtils.reshape(source, source.length, 1),
MatrixUtils.reshape(destination, destination.length, 1),
sourceValid, destValid);
}
public void setObservations(double[][] source, double[][] destination,
public void addObservations(double[][] source, double[][] destination,
boolean[][] sourceValid, boolean[][] destValid) throws Exception {
boolean[] allSourceValid = MatrixUtils.andRows(sourceValid);
boolean[] allDestValid = MatrixUtils.andRows(destValid);
setObservations(source, destination, allSourceValid, allDestValid);
addObservations(source, destination, allSourceValid, allDestValid);
}
/**
@ -482,11 +554,15 @@ public abstract class MutualInfoMultiVariateCommon implements
}
destObservations = new double[totalObservations][dimensionsDest];
sourceObservations = new double[totalObservations][dimensionsSource];
observationSetIndices = new int[totalObservations];
observationTimePoints = new int[totalObservations];
// Construct the joint vectors from the given observations
// (removing redundant data which is outside any timeDiff)
int startObservation = 0;
Iterator<double[][]> iterator = vectorOfDestinationObservations.iterator();
Iterator<Integer> iteratorObsSetIndices = vectorOfObservationSetIndices.iterator();
Iterator<Integer> iteratorObsStartTimePoints = vectorOfObservationStartTimePoints.iterator();
for (double[][] source : vectorOfSourceObservations) {
double[][] destination = iterator.next();
// Copy the data from these given observations into our master
@ -497,7 +573,14 @@ public abstract class MutualInfoMultiVariateCommon implements
MatrixUtils.arrayCopy(destination, timeDiff, 0,
destObservations, startObservation, 0,
destination.length - timeDiff, dimensionsDest);
startObservation += destination.length - timeDiff;
int numNewObservations = destination.length - timeDiff;
// And update which observation set and time index each sample came from:
Arrays.fill(observationSetIndices, startObservation, startObservation + numNewObservations, iteratorObsSetIndices.next());
int firstTimeSampleId = iteratorObsStartTimePoints.next(); // This is the first sample of the source; storing the time index for destination below.
for (int i = 0; i < numNewObservations; i++) {
observationTimePoints[startObservation + i] = firstTimeSampleId + timeDiff + i;
}
startObservation += numNewObservations;
}
if (vectorOfSourceObservations.size() > 1) {
addedMoreThanOneObservationSet = true;
@ -834,4 +917,14 @@ public abstract class MutualInfoMultiVariateCommon implements
}
return startAndEndTimePairs;
}
@Override
public int[] getObservationSetIndices() {
return observationSetIndices;
}
@Override
public int[] getObservationTimePoints() {
return observationTimePoints;
}
}

View File

@ -21,6 +21,7 @@ package infodynamics.measures.continuous.kraskov;
import infodynamics.utils.ArrayFileReader;
import infodynamics.utils.MathsUtils;
import infodynamics.utils.MatrixUtils;
import infodynamics.utils.RandomGenerator;
public class MutualInfoMultiVariateTester
extends infodynamics.measures.continuous.MutualInfoMultiVariateAbstractTester {
@ -652,5 +653,78 @@ public class MutualInfoMultiVariateTester
assertEquals(expected_H_X_given_Y, conditionalEnt, 0.02);
}
/**
* Test that observationSetIndices and observationStartTimePoints are written properly
*
* @throws Exception
*/
public void testObservationSetIndices() throws Exception {
int dimensions = 1;
int timeSteps = 100;
MutualInfoCalculatorMultiVariateKraskov miCalc = getNewCalc(1);
miCalc.initialise(dimensions, dimensions);
// generate some random data
RandomGenerator rg = new RandomGenerator();
double[][] sourceData = rg.generateNormalData(timeSteps, dimensions,
0, 1);
double[][] destData = rg.generateNormalData(timeSteps, dimensions,
0, 1);
// First check that for a simple single observation set everything works:
miCalc.setObservations(sourceData, destData);
int[] observationSetIds = miCalc.getObservationSetIndices();
int[] timeSeriesIndices = miCalc.getObservationTimePoints();
assert(observationSetIds.length == timeSteps);
for (int t = 0; t < timeSteps; t++) {
assertEquals(observationSetIds[t], 0);
assertEquals(timeSeriesIndices[t], t);
}
// Now add the same one twice:
miCalc.initialise(dimensions, dimensions);
miCalc.startAddObservations();
miCalc.addObservations(sourceData, destData);
miCalc.addObservations(sourceData, destData);
miCalc.finaliseAddObservations();
observationSetIds = miCalc.getObservationSetIndices();
timeSeriesIndices = miCalc.getObservationTimePoints();
assert(observationSetIds.length == 2*timeSteps);
for (int t = 0; t < timeSteps; t++) {
assertEquals(observationSetIds[t], 0);
assertEquals(timeSeriesIndices[t], t);
}
for (int t = 0; t < timeSteps; t++) {
assertEquals(observationSetIds[timeSteps + t], 1);
assertEquals(timeSeriesIndices[timeSteps + t], t);
}
// Now add NUM_SEGMENTS randomly chosen segments:
int NUM_SEGMENTS = 10;
int maxLength = 10;
int[] startPoints = rg.generateRandomInts(NUM_SEGMENTS, timeSteps - maxLength);
int[] lengthsMinus1 = rg.generateRandomInts(NUM_SEGMENTS, maxLength - 1); // ensures we don't add segments of length 0
miCalc.initialise(dimensions, dimensions);
miCalc.startAddObservations();
for (int r = 0; r < NUM_SEGMENTS; r++) {
miCalc.addObservations(sourceData, destData, startPoints[r], lengthsMinus1[r]+1);
}
miCalc.finaliseAddObservations();
observationSetIds = miCalc.getObservationSetIndices();
timeSeriesIndices = miCalc.getObservationTimePoints();
assert(observationSetIds.length == MatrixUtils.sum(lengthsMinus1) + NUM_SEGMENTS);
int t = 0;
for (int r = 0; r < NUM_SEGMENTS; r++) {
for (int i = 0; i < lengthsMinus1[r]+1; i++) {
assertEquals(observationSetIds[t], r);
assertEquals(timeSeriesIndices[t], startPoints[r] + i);
t++;
}
}
}
}