package edu.stanford.nlp.loglinear.model;
import java.util.Iterator;
/**
* Created on 9/12/15.
* @author keenon
* <p>
* Holds and provides access to an N-dimensional array.
* <p>
* Yes, generics will lead to unfortunate boxing and unboxing in the TableFactor case, we'll handle that if it becomes a
* problem.
*/
public class NDArrayDoubles implements Iterable<int[]> {
// public data
protected int[] dimensions;
// OPTIMIZATION:
// in normal NDArray this is private, but to allow for optimizations we actually leave it as protected
protected double[] values;
/**
* Constructor takes a list of neighbor variables to use for this factor. This must not change after construction,
* and the number of states of those variables must also not change.
*
* @param dimensions list of neighbor variables assignment range sizes
*/
public NDArrayDoubles(int[] dimensions) {
for (int size : dimensions) {
assert (size > 0);
}
this.dimensions = dimensions;
values = new double[combinatorialNeighborStatesCount()];
}
/**
* This is to enable the partially observed constructor for TableFactor. It's an ugly break of modularity, but seems
* to be necessary if we want to keep the constructor for TableFactor with partial observations relatively simple.
*/
protected NDArrayDoubles() {
}
/**
* Set a single value in the factor table.
*
* @param assignment a list of variable settings, in the same order as the neighbors array of the factor
* @param value the value to put into the factor table
*/
public void setAssignmentValue(int[] assignment, double value) {
values[getTableAccessOffset(assignment)] = value;
}
/**
* Retrieve a single value for an assignment.
*
* @param assignment a list of variable settings, in the same order as the neighbors array of the factor
* @return the value for the given assignment. Can be null if not been set yet.
*/
public double getAssignmentValue(int[] assignment) {
return values[getTableAccessOffset(assignment)];
}
/**
* @return the size array of the neighbors of the feature factor, passed by value to ensure immutability.
*/
public int[] getDimensions() {
return dimensions.clone();
}
/**
* WARNING: This is pass by reference to avoid massive GC overload during heavy iterations, and because the standard
* use case is to use the assignments array as an accessor. Please, clone if you save a copy, otherwise the array
* will mutate underneath you.
*
* @return an iterator over all possible assignments to this factor
*/
@Override
public Iterator<int[]> iterator() {
return new Iterator<int[]>() {
Iterator<int[]> unsafe = fastPassByReferenceIterator();
@Override
public boolean hasNext() {
return unsafe.hasNext();
}
@Override
public int[] next() {
return unsafe.next().clone();
}
};
}
/**
* This is its own function because people will inevitably attempt this optimization of not cloning the array we
* hand to the iterator, to save on GC, and it should not be default behavior. If you know what you're doing, then
* this may be the iterator for you.
*
* @return an iterator that will mutate the value it returns to you, so you must clone if you want to keep a copy
*/
public Iterator<int[]> fastPassByReferenceIterator() {
final int[] assignments = new int[dimensions.length];
if (dimensions.length > 0) assignments[0] = -1;
return new Iterator<int[]>() {
@Override
public boolean hasNext() {
for (int i = 0; i < assignments.length; i++) {
if (assignments[i] < dimensions[i] - 1) return true;
}
return false;
}
@Override
public int[] next() {
// Add one to the first position
assignments[0]++;
// Carry any resulting overflow all the way to the end.
for (int i = 0; i < assignments.length; i++) {
if (assignments[i] >= dimensions[i]) {
assignments[i] = 0;
if (i < assignments.length - 1) {
assignments[i + 1]++;
}
} else {
break;
}
}
return assignments;
}
};
}
/**
* @return the total number of states this factor must represent to include all neighbors.
*/
public int combinatorialNeighborStatesCount() {
int c = 1;
for (int n : dimensions) {
c *= n;
}
return c;
}
////////////////////////////////////////////////////////////////////////////
// PRIVATE IMPLEMENTATION
////////////////////////////////////////////////////////////////////////////
/**
* Compute the distance into the one dimensional factorTable array that corresponds to a setting of all the
* neighbors of the factor.
*
* @param assignment assignment indices, in same order as neighbors array
* @return the offset index
*/
private int getTableAccessOffset(int[] assignment) {
assert (assignment.length == dimensions.length);
int offset = 0;
for (int i = 0; i < assignment.length; i++) {
assert (assignment[i] < dimensions[i]);
offset = (offset * dimensions[i]) + assignment[i];
}
return offset;
}
}