package org.opensha2.data; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static org.opensha2.data.IntervalData.checkDataState; import static org.opensha2.data.IntervalData.indexOf; import static org.opensha2.data.IntervalData.keys; import org.opensha2.data.IntervalData.AbstractVolume; import org.opensha2.data.IntervalData.DefaultVolume; import java.util.Arrays; import java.util.List; /** * A 3-dimensional volume of immutable, double-valued data that is arranged * according to increasing and uniformly spaced double-valued keys. Data volumes * are used to represent binned data, and so while row, column, and level keys * are bin centers, indexing is managed internally using bin edges. This * simplifies issues related to rounding/precision errors that occur when * indexing according to explicit double values. * * <p>To create an instance of an {@code IntervalVolume}, use a {@link Builder}. * * <p>Internally, an {@code IntervalVolume} is backed by a {@code double[][][]} * where 'row' maps to the 1st dimension, 'column' the 2nd dimension, and * 'level' the 3rd. * * <p>Note that interval volumes are not intended for use with very high * precision data and keys are currently limited to a precision of 4 decimal * places. This may change in the future. * * @author Peter Powers * @see IntervalData * @see IntervalArray * @see IntervalTable */ public interface IntervalVolume { /** * Return the value of the bin that maps to the supplied row, column, and * level values. Do not confuse this method with {@link #get(int, int, int)} * by row index. * * @param rowValue of bin to retrieve * @param columnValue of bin to retrieve * @param levelValue of bin to retrieve * @throws IndexOutOfBoundsException if any value is out of range */ double get(double rowValue, double columnValue, double levelValue); /** * Return the value of the bin that maps to the supplied row, column, and * level indices. Do not confuse this method with * {@link #get(double, double, double)} by row value. * * @param rowIndex of bin to retrieve * @param columnIndex of bin to retrieve * @param levelIndex of bin to retrieve * @throws IndexOutOfBoundsException if any index is out of range */ double get(int rowIndex, int columnIndex, int levelIndex); /** * Return an immutable view of the values that map to the supplied row and * column values. Do not confuse with {@link #column(int, int)} retrieval by * index. * * @param rowValue of bin to retrieve * @param columnValue of bin to retrieve */ XySequence column(double rowValue, double columnValue); /** * Return an immutable view of the values that map to the supplied row and * column values. Do not confuse with {@link #column(double, double)} * retrieval by index. * * @param rowIndex of bin to retrieve * @param columnIndex of bin to retrieve */ XySequence column(int rowIndex, int columnIndex); /** * Return the lower edge of the lowermost row bin. */ double rowMin(); /** * Return the upper edge of the uppermost row bin. */ double rowMax(); /** * Return the row bin discretization. */ double rowΔ(); /** * Return an immutable list <i>view</i> of the row keys (bin centers). */ List<Double> rows(); /** * Return the lower edge of the lowermost column bin. */ double columnMin(); /** * Return the upper edge of the uppermost column bin. */ double columnMax(); /** * Return the column bin discretization. */ double columnΔ(); /** * Return an immutable list <i>view</i> of the column keys (bin centers). */ List<Double> columns(); /** * Return the lower edge of the lowermost level bin. */ double levelMin(); /** * Return the upper edge of the uppermost level bin. */ double levelMax(); /** * Return the level bin discretization. */ double levelΔ(); /** * Return an immutable list <i>view</i> of the level keys (bin centers). */ List<Double> levels(); /** * Return a new {@code IntervalTable} created by summing the levels of this * volume. */ IntervalTable collapse(); /** * Return the indices of the bin with smallest value in the form * {@code [rowIndex, columnIndex, levelIndex]}. */ int[] minIndex(); /** * Return the indices of the bin with largest value in the form * {@code [rowIndex, columnIndex, levelIndex]}. */ int[] maxIndex(); /** * A supplier of values with which to fill a {@code IntervalVolume}. */ interface Loader { /** * Compute the value corresponding to the supplied row, column, and level * keys. * * @param row value * @param column value * @param level value */ public double compute(double row, double column, double level); } /** * A builder of immutable {@code IntervalVolume}s. * * <p>Rows, columns, and levels must be specified before any data can be * added. Note that any supplied {@code max} values may not correspond to the * final upper edge of the uppermost bins if {@code max - min} is not evenly * divisible by {@code Δ}. */ public static final class Builder { private double[][][] data; private double rowMin; private double rowMax; private double rowΔ; private double[] rows; private double columnMin; private double columnMax; private double columnΔ; private double[] columns; private double levelMin; private double levelMax; private double levelΔ; private double[] levels; private boolean built = false; private boolean initialized = false; /** * Create a new builder. */ public Builder() {} /** * Create a new builder with the structure and content identical to that of * the supplied volume. * * @param volume to copy */ public static Builder copyOf(IntervalVolume volume) { /* Safe covariant cast. */ DefaultVolume defaultVolume = (DefaultVolume) volume; Builder builder = copyStructure(defaultVolume); builder.data = Data.copyOf(defaultVolume.data); builder.init(); return builder; } /** * Create a new builder with a structure identical to that of the supplied * model. * * @param model data volume */ public static Builder fromModel(IntervalVolume model) { /* Safe covariant cast. */ Builder builder = copyStructure((AbstractVolume) model); builder.init(); return builder; } private static Builder copyStructure(AbstractVolume from) { Builder to = new Builder(); to.rowMin = from.rowMin; to.rowMax = from.rowMax; to.rowΔ = from.rowΔ; to.rows = from.rows; to.columnMin = from.columnMin; to.columnMax = from.columnMax; to.columnΔ = from.columnΔ; to.columns = from.columns; to.levelMin = from.levelMin; to.levelMax = from.levelMax; to.levelΔ = from.levelΔ; to.levels = from.levels; return to; } /** * Define the data volume rows. * * @param min lower edge of lowermost row bin * @param max upper edge of uppermost row bin * @param Δ bin discretization */ public Builder rows(double min, double max, double Δ) { rowMin = min; rowMax = max; rowΔ = Δ; rows = keys(min, max, Δ); init(); return this; } /** * Define the data volume columns. * * @param min lower edge of lowermost column bin * @param max upper edge of uppermost column bin * @param Δ bin discretization */ public Builder columns(double min, double max, double Δ) { columnMin = min; columnMax = max; columnΔ = Δ; columns = keys(min, max, Δ); init(); return this; } /** * Define the data volume levels. * * @param min lower edge of lowermost column bin * @param max upper edge of uppermost column bin * @param Δ bin discretization */ public Builder levels(double min, double max, double Δ) { levelMin = min; levelMax = max; levelΔ = Δ; levels = keys(min, max, Δ); init(); return this; } private void init() { checkState(!initialized, "Builder has already been initialized"); if (rows != null && columns != null && levels != null) { if (data == null) { data = new double[rows.length][columns.length][levels.length]; } initialized = true; } } /** * Return the index of the row that would contain the supplied value. * @param row value */ public int rowIndex(double row) { return indexOf(rowMin, rowΔ, row, rows.length); } /** * Return the index of the column that would contain the supplied value. * @param column value */ public int columnIndex(double column) { return indexOf(columnMin, columnΔ, column, columns.length); } /** * Return the index of the level that would contain the supplied value. * @param level value */ public int levelIndex(double level) { return indexOf(levelMin, levelΔ, level, levels.length); } /** * Set the value at the specified row, column, and level. Be careful not to * confuse this with {@link #set(int, int, int, double)}. * * @param row key * @param column key * @param level key * @param value to set */ public Builder set(double row, double column, double level, double value) { return set(rowIndex(row), columnIndex(column), levelIndex(level), value); } /** * Set the value at the specified row, column, and level indices. Be careful * not to confuse this with {@link #set(double, double, double, double)}. * * @param row index * @param column index * @param level index * @param value to set */ public Builder set(int row, int column, int level, double value) { data[row][column][level] = value; return this; } /** * Add to the existing value at the specified row, column, and level. Be * careful not to confuse this with {@link #add(int, int, int, double)}. * * @param row key * @param column key * @param level key * @param value to add */ public Builder add(double row, double column, double level, double value) { return add(rowIndex(row), columnIndex(column), levelIndex(level), value); } /** * Add to the existing value at the specified row, column, and level * indices. Be careful not to confuse this with * {@link #add(double, double, double, double)}. * * @param row index * @param column index * @param level index * @param value */ public Builder add(int row, int column, int level, double value) { data[row][column][level] += value; return this; } /** * Add the values in the supplied volume to this builder. This operation is * very efficient if this builder and the supplied volume are sourced from * the same model. * * @param volume to add * @throws IllegalArgumentException if the rows, columns, and levels of the * supplied volume do not match those of this volume * @see #fromModel(IntervalVolume) */ public Builder add(IntervalVolume volume) { // safe covariant cast validateVolume((AbstractVolume) volume); // safe covariant cast until other concrete implementations exist Data.uncheckedAdd(data, ((DefaultVolume) volume).data); return this; } /** * Multiply ({@code scale}) all values in this builder. * @param scale factor */ public Builder multiply(double scale) { Data.multiply(scale, data); return this; } /* * Check hash codes of row, column, and level arrays in case fromModel or * copyOf has been used, otherwise check array equality. */ AbstractVolume validateVolume(AbstractVolume that) { checkArgument((this.rows.hashCode() == that.rows.hashCode() && this.columns.hashCode() == that.columns.hashCode() && this.levels.hashCode() == that.levels.hashCode()) || (Arrays.equals(this.rows, that.rows) && Arrays.equals(this.columns, that.columns) && Arrays.equals(this.levels, that.levels))); return that; } /* * Data is not copied on build() so we dereference data arrays to prevent * lingering builders from further modifying data. */ private void dereference() { data = null; rows = null; columns = null; levels = null; } /** * Return a newly-created, immutable, 3-dimensional interval data container * populated with values computed by the supplied loader. Calling this * method will overwrite any values already supplied via {@code set*} or * {@code add*} methods. * * @param loader that will compute values */ public IntervalVolume build(Loader loader) { checkNotNull(loader); for (int i = 0; i < rows.length; i++) { double row = rows[i]; for (int j = 0; j < columns.length; j++) { double column = columns[j]; for (int k = 0; k < levels.length; k++) { data[i][j][k] = loader.compute(row, column, levels[k]); } } } return build(); } /** * Return a newly-created, immutable 3-dimensional interval data container * populated with the contents of this {@code Builder}. */ public IntervalVolume build() { checkState(built != true, "This builder has already been used"); checkDataState(rows, columns, levels); IntervalVolume volume = new DefaultVolume( rowMin, rowMax, rowΔ, rows, columnMin, columnMax, columnΔ, columns, levelMin, levelMax, levelΔ, levels, data); dereference(); return volume; } } }