package org.opensha2.data; import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkState; import static com.google.common.primitives.Doubles.asList; import static java.util.Collections.unmodifiableList; import static org.opensha2.data.Data.checkDelta; import static org.opensha2.internal.TextUtils.NEWLINE; import org.opensha2.internal.Parsing; import java.util.Collection; import java.util.List; /** * Static utilities for working with and concrete implementations of 1-, 2- and * 3-dimensional interval data containers. * * @author Peter Powers * @see IntervalArray * @see IntervalTable * @see IntervalVolume */ public final class IntervalData { /** * Create key values for use in an {@link IntervalArray}, * {@link IntervalTable} or {@link IntervalVolume}. These classes call this * method directly when initializing their backing arrays. It is exposed for * convenience as there are circumstances where a reference to the row or * column keys is helpful to have when working with the builders for these * classes. Internally, this method calls * {@link Data#buildCleanSequence(double, double, double, boolean, int)} with * a precision value of 4 decimal places. This may change in the future. * * <p><b>Example:</b> {@code keys(5.0, 8.0, 1.0)} returns [5.5, 6.5, 7.5] * * @param min lower edge of lowermost bin * @param max upper edge of uppermost bin * @param Δ bin width */ public static double[] keys(double min, double max, double Δ) { return keyArray(min, max, checkDelta(min, max, Δ)); } /* * Create clean sequence of keys. Precision is curently set to 4 decimal * places. */ private static double[] keyArray(double min, double max, double Δ) { double Δby2 = Δ / 2.0; return Data.buildCleanSequence( min + Δby2, max - Δby2, Δ, true, 4); } /** * Compute an index from a minimum value, a value and an interval. * * @param min value * @param delta interval (i.e. bin width) * @param value for which to compute index * @param size of array or collection for which index is to be used * @throws IndexOutOfBoundsException if the index of {@code value} falls * outside the allowed index range of {@code [0, size-1]}. * @throws IllegalArgumentException if {@code size} is negative */ public static int indexOf(double min, double delta, double value, int size) { // casting to int floors value return checkElementIndex((int) ((value - min) / delta), size); } private static void checkDataState(double[] data, String label) { checkState(data != null, "%s data have not yet been fully specified", label); } /* * Ensure rows have been specified */ static void checkDataState(double[] rows) { checkDataState(rows, "Row"); } /* * Ensure rows and columns have been specified */ static void checkDataState(double[] rows, double[] columns) { checkDataState(rows); checkDataState(columns, "Column"); } /* * Ensure rows, columns, and levels have been specified */ static void checkDataState(double[] rows, double[] columns, double[] levels) { checkDataState(rows, columns); checkDataState(levels, "Level"); } static abstract class AbstractArray implements IntervalArray { final double rowMin; final double rowMax; final double rowΔ; final double[] rows; private AbstractArray(double rowMin, double rowMax, double rowΔ, double[] rows) { this.rowMin = rowMin; this.rowMax = rowMax; this.rowΔ = rowΔ; this.rows = rows; } @Override public double get(final double rowValue) { int iRow = indexOf(rowMin, rowΔ, rowValue, rows.length); return get(iRow); } @Override public List<Double> rows() { return unmodifiableList(asList(rows)); } @Override public double rowMin() { return rowMin; } @Override public double rowMax() { return rowMax; } @Override public double rowΔ() { return rowΔ; } @Override public String toString() { StringBuilder sb = new StringBuilder(); appendArrayKeys(sb, "", rows()); appendArrayValues(sb, values().yValues()); return sb.toString(); } } static final class DefaultArray extends AbstractArray { final double[] data; DefaultArray( double rowMin, double rowMax, double rowΔ, double[] rows, double[] data) { super(rowMin, rowMax, rowΔ, rows); this.data = data; } @Override public double get(final int rowIndex) { return data[rowIndex]; } @Override public XySequence values() { return new ImmutableXySequence(rows, data); } @Override public double sum() { return Data.sum(data); } @Override public int minIndex() { return Indexing.minIndex(data); } @Override public int maxIndex() { return Indexing.maxIndex(data); } } static abstract class AbstractTable implements IntervalTable { final double rowMin; final double rowMax; final double rowΔ; final double[] rows; final double columnMin; final double columnMax; final double columnΔ; final double[] columns; private AbstractTable( double rowMin, double rowMax, double rowΔ, double[] rows, double columnMin, double columnMax, double columnΔ, double[] columns) { this.rowMin = rowMin; this.rowMax = rowMax; this.rowΔ = rowΔ; this.rows = rows; this.columnMin = columnMin; this.columnMax = columnMax; this.columnΔ = columnΔ; this.columns = columns; } @Override public double get(final double rowValue, final double columnValue) { int iRow = indexOf(rowMin, rowΔ, rowValue, rows.length); int iColumn = indexOf(columnMin, columnΔ, columnValue, columns.length); return get(iRow, iColumn); } @Override public XySequence row(double rowValue) { int rowIndex = indexOf(rowMin, rowΔ, rowValue, rows.length); return row(rowIndex); } @Override public List<Double> rows() { return unmodifiableList(asList(rows)); } @Override public List<Double> columns() { return unmodifiableList(asList(columns)); } @Override public double rowMin() { return rowMin; } @Override public double rowMax() { return rowMax; } @Override public double rowΔ() { return rowΔ; } @Override public double columnMin() { return columnMin; } @Override public double columnMax() { return columnMax; } @Override public double columnΔ() { return columnΔ; } @Override public String toString() { StringBuilder sb = new StringBuilder(); List<Double> rows = rows(); appendArrayKeys(sb, " ", columns()); for (int i = 0; i < rows.size(); i++) { sb.append(String.format(KEY_WITH_BRACKETS, rows.get(i))); appendArrayValues(sb, row(i).yValues()); } return sb.toString(); } } static final class DefaultTable extends AbstractTable { final double[][] data; DefaultTable( double rowMin, double rowMax, double rowΔ, double[] rows, double columnMin, double columnMax, double columnΔ, double[] columns, double[][] data) { super( rowMin, rowMax, rowΔ, rows, columnMin, columnMax, columnΔ, columns); this.data = data; } @Override public double get(final int rowIndex, final int columnIndex) { return data[rowIndex][columnIndex]; } @Override public XySequence row(int rowIndex) { return new ImmutableXySequence(columns, data[rowIndex]); } @Override public IntervalArray collapse() { return new DefaultArray(rowMin, rowMax, rowΔ, rows, Data.collapse(data)); } @Override public int[] minIndex() { return Indexing.minIndex(data); } @Override public int[] maxIndex() { return Indexing.maxIndex(data); } } static abstract class AbstractVolume implements IntervalVolume { final double rowMin; final double rowMax; final double rowΔ; final double[] rows; final double columnMin; final double columnMax; final double columnΔ; final double[] columns; final double levelMin; final double levelMax; final double levelΔ; final double[] levels; private AbstractVolume( double rowMin, double rowMax, double rowΔ, double[] rows, double columnMin, double columnMax, double columnΔ, double[] columns, double levelMin, double levelMax, double levelΔ, double[] levels) { this.rowMin = rowMin; this.rowMax = rowMax; this.rowΔ = rowΔ; this.rows = rows; this.columnMin = columnMin; this.columnMax = columnMax; this.columnΔ = columnΔ; this.columns = columns; this.levelMin = levelMin; this.levelMax = levelMax; this.levelΔ = levelΔ; this.levels = levels; } @Override public double get(final double rowValue, final double columnValue, final double levelValue) { int iRow = indexOf(rowMin, rowΔ, rowValue, rows.length); int iColumn = indexOf(columnMin, columnΔ, columnValue, columns.length); int iLevel = indexOf(levelMin, levelΔ, levelValue, levels.length); return get(iRow, iColumn, iLevel); } @Override public XySequence column(double rowValue, double columnValue) { int iRow = indexOf(rowMin, rowΔ, rowValue, rows.length); int iColumn = indexOf(columnMin, columnΔ, columnValue, columns.length); return column(iRow, iColumn); } @Override public List<Double> rows() { return unmodifiableList(asList(rows)); } @Override public List<Double> columns() { return unmodifiableList(asList(columns)); } @Override public List<Double> levels() { return unmodifiableList(asList(levels)); } @Override public double rowMin() { return rowMin; } @Override public double rowMax() { return rowMax; } @Override public double rowΔ() { return rowΔ; } @Override public double columnMin() { return columnMin; } @Override public double columnMax() { return columnMax; } @Override public double columnΔ() { return columnΔ; } @Override public double levelMin() { return levelMin; } @Override public double levelMax() { return levelMax; } @Override public double levelΔ() { return levelΔ; } @Override public String toString() { StringBuilder sb = new StringBuilder(); List<Double> rows = rows(); List<Double> columns = columns(); appendArrayKeys(sb, " ", levels()); for (int i = 0; i < rows.size(); i++) { for (int j = 0; j < columns.size(); j++) { sb.append(String.format(KEY_WITH_BRACKETS, rows.get(i))); sb.append(String.format(KEY_WITH_BRACKETS, columns.get(j))); appendArrayValues(sb, column(i, j).yValues()); } } return sb.toString(); } } static final class DefaultVolume extends AbstractVolume { final double[][][] data; DefaultVolume( double rowMin, double rowMax, double rowΔ, double[] rows, double columnMin, double columnMax, double columnΔ, double[] columns, double levelMin, double levelMax, double levelΔ, double[] levels, double[][][] data) { super( rowMin, rowMax, rowΔ, rows, columnMin, columnMax, columnΔ, columns, levelMin, levelMax, levelΔ, levels); this.data = data; } @Override public double get(final int rowIndex, final int columnIndex, final int levelIndex) { return data[rowIndex][columnIndex][levelIndex]; } @Override public XySequence column(int rowIndex, int columnIndex) { return new ImmutableXySequence(levels, data[rowIndex][columnIndex]); } @Override public IntervalTable collapse() { return new DefaultTable( rowMin, rowMax, rowΔ, rows, columnMin, columnMax, columnΔ, columns, Data.collapse(data)); } @Override public int[] minIndex() { return Indexing.minIndex(data); } @Override public int[] maxIndex() { return Indexing.maxIndex(data); } } /* String utilities */ private static void appendArrayKeys(StringBuilder sb, String prefix, Collection<Double> values) { sb.append(prefix); sb.append(Parsing.toString(values, KEY_FORMAT, DELIMITER, true, true)); sb.append(NEWLINE); } private static void appendArrayValues(StringBuilder sb, Collection<Double> values) { String dataLine = Parsing.toString(values, DATA_FORMAT, DELIMITER, true, true); dataLine = dataLine.replace("0.0,", " 0.0,"); dataLine = dataLine.replace("0.0]", " 0.0]"); sb.append(dataLine); sb.append(NEWLINE); } private static final String KEY_FORMAT = "%8.2f"; private static final String KEY_WITH_BRACKETS = "[%7.2f] "; private static final String DATA_FORMAT = "%7.2e"; private static final String DELIMITER = ", "; }