Skip to content

Commit

Permalink
Fix MultiArray and DoubleGrid data set to use row major storage as do…
Browse files Browse the repository at this point in the history
…cumented (#329)

* fixed MultiArray to be consistent with row-major indexing

* fixed DoubleGridDataSet to be consistent with MultiArray changes - WIP
N.B. while MultiArray is row-major (ie. [rowIndex,colIndex]) plotting is de-factor column-major order (ie. abscissa==column, ordinate==row) order, and thus the dimensions/shape need to be transposed w.r.t. each other.

* fixed DoubleGridDataSet to use the fixed MultiArrayImplementation

Co-authored-by: rstein <[email protected]>
  • Loading branch information
wirew0rm and RalphSteinhagen authored Dec 16, 2020
1 parent c0ea477 commit 582a499
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,26 @@ public static class MultiArray2DProto extends MultiArrayProto { //// codegen: su

protected MultiArray2DProto(final double[] elements, final int[] dimensions, final int offset) { //// codegen: subst:U:double[]:U[]
super(elements, dimensions, offset);
stride = dimensions[0];
stride = dimensions[1];
}

public double get(final int column, final int row) {
/**
*
* @param row rowIndex
* @param column columnIndex
* @return value at M(rowIndex,columnIndex)
*/
public double get(final int row, final int column) {
return elements[offset + column + row * stride];
}

public void set(final int column, final int row, final double value) {
/**
*
* @param row rowIndex
* @param column columnIndex
* @param value new value: M(rowIndex,columnIndex) == value
*/
public void set(final int row, final int column, final double value) {
elements[offset + column + row * stride] = value;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
* Implementation of the GridDataSet. Allows data on n-dimensional Cartesian grids with m values per point.
* The dimension of the dataSet is n+m.
*
* The data is stored in a row-major container, but as the renderer interface expects column major, the data is transposed
* internally in the DoubleGridDataSet.
*
* @author Alexander Krimm
*/
@SuppressWarnings({ "java:S2160" }) // equals is still valid because of DataSet interface
Expand Down Expand Up @@ -48,14 +51,15 @@ public DoubleGridDataSet(final String name, int dims) {
/**
* @param name name for this DataSet
* @param nDims number of Dimensions
* @param shape Shape of the grid
* @param shape Shape of the grid, length cannot exceed number of dimensions: double[nGrid] {n_x, n_y, ...}
*/
public DoubleGridDataSet(String name, int nDims, int[] shape) {
super(name, nDims);
this.shape = shape;
if (shape.length > nDims) {
throw new IllegalArgumentException("nDims must be greater or equal to grid shape");
}
this.shape = shape.clone();
final int[] containerShape = reverseOrder(shape);

grid = new double[shape.length][];
values = new MultiArrayDouble[nDims - shape.length];
Expand All @@ -66,52 +70,53 @@ public DoubleGridDataSet(String name, int nDims, int[] shape) {
grid[i] = IntStream.range(0, shape[i]).asDoubleStream().toArray();
}
for (int i = shape.length; i < nDims; i++) {
values[i - shape.length] = MultiArrayDouble.wrap(new double[dataCount], 0, shape);
values[i - shape.length] = MultiArrayDouble.wrap(new double[dataCount], 0, containerShape);
}
}

/**
* @param name name for the dataSet
* @param shape shape of the grid
* @param copy whether to copy the values in vals
* @param vals values
* @param shape shape of the grid: double[nGrid] {n_x, n_y, ...}
* @param copy whether to copy the values in values
* @param values values in column-major order (2d case: double[n_x * n_y]{z(0,0), z(1,0) ... z(m-1,n), z(m,n)})
*/
public DoubleGridDataSet(String name, int[] shape, final boolean copy, double[]... vals) {
super(name, shape.length + vals.length);
final int nDims = shape.length + vals.length;
public DoubleGridDataSet(String name, int[] shape, final boolean copy, double[]... values) {
super(name, shape.length + values.length);
final int nDims = shape.length + values.length;
this.shape = shape.clone();
final int[] containerShape = reverseOrder(shape);

grid = new double[shape.length][];
values = new MultiArrayDouble[vals.length];
this.values = new MultiArrayDouble[values.length];

dataCount = 1;
for (int i = 0; i < shape.length; i++) {
dataCount *= shape[i];
grid[i] = IntStream.range(0, shape[i]).asDoubleStream().toArray();
}
for (int i = shape.length; i < nDims; i++) {
if (vals[i - shape.length].length != dataCount) {
if (values[i - shape.length].length != dataCount) {
throw new IllegalArgumentException("Dimension missmatch between grid and values");
}
values[i - shape.length] = MultiArrayDouble.wrap(copy ? vals[i - shape.length].clone() : vals[i - shape.length], 0, shape);
this.values[i - shape.length] = MultiArrayDouble.wrap(copy ? values[i - shape.length].clone() : values[i - shape.length], 0, containerShape);
}
}

/**
* @param name name for the dataSet
* @param copy whether to copy the values from grid and vals
* @param grid values for the grid
* @param vals values
* @param copy whether to copy the values from grid and values
* @param grid values for the grid double[nGrid][m/n/...] {{x_0 ... x_n}, {y_0 ... y_m}, ...}
* @param values values in column-major order (2d case: double[n_x * n_y]{z(0,0), z(1,0) ... z(m-1,n), z(m,n)})
*/
public DoubleGridDataSet(final String name, final boolean copy, final double[][] grid, final double[]... vals) {
super(name, grid.length + vals.length);
set(copy, grid, vals);
public DoubleGridDataSet(final String name, final boolean copy, final double[][] grid, final double[]... values) {
super(name, grid.length + values.length);
set(copy, grid, values);
}

@Override
public double get(int dimIndex, int index) {
if (dimIndex < shape.length) {
return grid[dimIndex][values[0].getIndices(index)[dimIndex]];
return grid[dimIndex][values[0].getIndices(index)[shape.length - 1 - dimIndex]];
}
return values[dimIndex - shape.length].getStrided(index);
}
Expand Down Expand Up @@ -152,7 +157,7 @@ public double get(final int dimIndex, final int... indices) {
if (dimIndex < shape.length) {
return grid[dimIndex][indices[dimIndex]];
}
return values[dimIndex - shape.length].get(indices);
return values[dimIndex - shape.length].get(reverseOrder(indices));
}

@Override
Expand All @@ -170,6 +175,7 @@ public void set(final boolean copy, final double[][] grid, final double[]... val
throw new IllegalArgumentException("grid + value dimensions must match dataset dimensions");
}
shape = Arrays.stream(grid).mapToInt(doubles -> doubles.length).toArray();
final int[] containerShape = reverseOrder(shape);
this.grid = copy ? new double[shape.length][] : grid;
dataCount = 1;
for (int i = 0; i < shape.length; i++) {
Expand All @@ -181,9 +187,9 @@ public void set(final boolean copy, final double[][] grid, final double[]... val
values = new MultiArrayDouble[vals.length];
for (int i = shape.length; i < nDims; i++) {
if (vals[i - shape.length].length != dataCount) {
throw new IllegalArgumentException("Dimension missmatch between grid and values");
throw new IllegalArgumentException("Dimension mismatch between grid and values");
}
values[i - shape.length] = MultiArrayDouble.wrap(copy ? vals[i - shape.length].clone() : vals[i - shape.length], 0, shape);
values[i - shape.length] = MultiArrayDouble.wrap(copy ? vals[i - shape.length].clone() : vals[i - shape.length], 0, containerShape);
}
});
fireInvalidated(new UpdatedDataEvent(this));
Expand All @@ -203,6 +209,7 @@ public GridDataSet set(final DataSet another, final boolean copy) {

// copy data
this.shape = anotherGridDataSet.getShape().clone();
final int[] containerShape = reverseOrder(shape);
this.grid = new double[shape.length][];
this.values = new MultiArrayDouble[nDims - shape.length];

Expand All @@ -212,7 +219,7 @@ public GridDataSet set(final DataSet another, final boolean copy) {
this.grid[i] = anotherGridDataSet.getGridValues(i).clone();
}
for (int i = shape.length; i < nDims; i++) {
values[i - shape.length] = MultiArrayDouble.wrap(another.getValues(i).clone(), 0, shape);
values[i - shape.length] = MultiArrayDouble.wrap(another.getValues(i).clone(), 0, containerShape);
}

// deep copy data point labels and styles
Expand Down Expand Up @@ -257,4 +264,12 @@ public GridDataSet set(int dimIndex, int[] indices, double value) {
public void clearData() {
set(false, new double[shape.length][0], new double[1][0]);
}

private static int[] reverseOrder(final int[] input) {
final int[] result = new int[input.length];
for (int i = 0; i < input.length; i++) {
result[i] = input[input.length - 1 - i];
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ protected MultiArray(final T elements, final int[] dimensions, final int offset)
this.elements = elements;
this.offset = offset;
strides = new int[dimensions.length];
strides[0] = 1;
for (int i = 1; i < dimensions.length; i++) {
strides[i] = strides[i - 1] * dimensions[i - 1];
strides[dimensions.length - 1] = 1;
for (int i = dimensions.length - 2; i >= 0; i--) {
strides[i] = strides[i + 1] * dimensions[i + 1];
}
this.elementCount = strides[dimensions.length - 1] * dimensions[dimensions.length - 1];
this.elementCount = strides[0] * dimensions[0];
}

/**
Expand Down Expand Up @@ -146,12 +146,16 @@ public int getElementsCount() {
*/
public int getIndex(final int[] indices) {
int index = offset;
for (int i = 0; i < dimensions.length; i++) {
int multiplier = 1;

for (int i = indices.length - 1; i >= 0; i--) {
if (indices[i] < 0 || indices[i] >= dimensions[i]) {
throw new IndexOutOfBoundsException("Index " + indices[i] + " for dimension " + i + " out of bounds " + dimensions[i]);
}
index += indices[i] * strides[i];
index += indices[i] * multiplier;
multiplier *= dimensions[i];
}

return index;
}

Expand All @@ -167,13 +171,10 @@ public int[] getIndices(final int index) {
return new int[dimensions.length];
}
final int[] indices = new int[dimensions.length];
int ind = index - offset;
for (int i = dimensions.length - 1; i >= 0; i--) {
if (dimensions[i] == 0) {
throw new IndexOutOfBoundsException();
}
indices[i] = ind / strides[i];
ind = ind % strides[i];
int lindex = index - offset;
for (int i = 0; i < dimensions.length; i++) {
indices[i] = lindex / strides[i];
lindex -= indices[i] * strides[i];
}
return indices;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
*/
class DoubleGridDataSetTests {
@Test
public void testEmptyGridConstructor() {
void testEmptyGridConstructor() {
DoubleGridDataSet dataset = new DoubleGridDataSet("testGridDataSet", 3);
assertEquals("testGridDataSet", dataset.getName());
assertArrayEquals(new int[] { 0, 0 }, dataset.getShape());
Expand All @@ -34,7 +34,8 @@ public void testEmptyGridConstructor() {
}

@Test
public void testZeroInitializedConstructor() {
void testZeroInitializedConstructor() {
// create a grid data set with 3rows x 4columns x 2slices
DoubleGridDataSet dataset = new DoubleGridDataSet("testGridDataSet", 5, new int[] { 3, 4, 2 });
assertEquals("testGridDataSet", dataset.getName());
assertArrayEquals(new int[] { 3, 4, 2 }, dataset.getShape());
Expand All @@ -52,8 +53,48 @@ public void testZeroInitializedConstructor() {
}

@Test
public void testEquidistantFullDataConstructor() {
double[] data = new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
void nonPermutableShape() {
double[] data = new double[] {
// first slice
1, 2, 3, 4, //first row
5, 6, 7, 8, //
9, 10, 11, 12, //
// second slice
13, 14, 15, 16, // first row
17, 18, 19, 20, //
21, 22, 23, 24 //
};
DoubleGridDataSet dataset = new DoubleGridDataSet("testGridDataSet", new int[] { 4, 3, 2 }, false, data);
assertSame(data, dataset.getValues(3));
assertEquals("testGridDataSet", dataset.getName());
assertArrayEquals(new int[] { 4, 3, 2 }, dataset.getShape());
assertEquals(4, dataset.getDimension());
assertEquals(4 * 3 * 2, dataset.getDataCount());
assertArrayEquals(new double[] { 0, 1, 2, 3 }, dataset.getGridValues(DIM_X));

// test grid values
assertEquals(3, dataset.get(DIM_X, 19));
assertEquals(2, dataset.get(DIM_Y, 21));
assertEquals(1, dataset.get(DIM_Z, 17));

// test data values
assertEquals(2, dataset.get(3, 1, 0, 0));
assertEquals(5, dataset.get(3, 0, 1, 0));
assertEquals(13, dataset.get(3, 0, 0, 1));
}

@Test
void testEquidistantFullDataConstructor() {
double[] data = new double[] {
// first slice
1, 2, // first row
3, 4, // second row
5, 6, // third row
// second slice
7, 8, // first row
9, 10, // second row
11, 12 // third row
};
DoubleGridDataSet dataset = new DoubleGridDataSet("testGridDataSet", new int[] { 2, 3, 2 }, false, data);

assertSame(data, dataset.getValues(3));
Expand All @@ -78,8 +119,17 @@ public void testEquidistantFullDataConstructor() {
}

@Test
public void testFullDataConstructor() {
double[] data = new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
void testFullDataConstructor() {
double[] data = new double[] {
// first slice
1, 2, // first row
3, 4, // second row
5, 6, // third row
// second slice
7, 8, // first row
9, 10, // second row
11, 12 // third row
};
DoubleGridDataSet dataset = new DoubleGridDataSet("testGridDataSet", false, new double[][] { { 0.1, 0.2 }, { 1.1, 2.2, 3.3 }, { -0.5, 0.5 } }, data);

assertEquals("testGridDataSet", dataset.getName());
Expand All @@ -93,6 +143,8 @@ public void testFullDataConstructor() {
assertEquals(6.0, dataset.get(3, 5));
assertEquals(0.1, dataset.get(DIM_X, 2));
assertEquals(2.0, dataset.get(3, 1, 0, 0));
assertEquals(3.0, dataset.get(3, 0, 1, 0));
assertEquals(11.0, dataset.get(3, 0, 2, 1));
assertEquals(12.0, dataset.get(3, 1, 2, 1));
assertEquals(0.2, dataset.get(DIM_X, 1, 2, 1));
assertEquals(-0.5, dataset.get(DIM_Z, 0, 2, 0));
Expand All @@ -114,7 +166,7 @@ public void testFullDataConstructor() {
}

@Test
public void testCopyConstructor() {
void testCopyConstructor() {
double[] data = new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
DoubleGridDataSet dataset = new DoubleGridDataSet("testGridDataSet", false, new double[][] { { 0.1, 0.2 }, { 1.1, 2.2, 3.3 }, { -0.5, 0.5 } }, data);
dataset.addDataLabel(10, "test");
Expand All @@ -128,7 +180,7 @@ public void testCopyConstructor() {
}

@Test
public void testSettersAndListeners() {
void testSettersAndListeners() {
double[] data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
DoubleGridDataSet dataset = new DoubleGridDataSet("testGridDataSet", false, new double[][] { { 0.1, 0.2 }, { 1.1, 2.2, 3.3 }, { -0.5, 0.5 } }, data);

Expand Down
Loading

0 comments on commit 582a499

Please sign in to comment.