package org.opensha2.data; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.opensha2.internal.TextUtils.NEWLINE; import org.opensha2.util.Maths; import com.google.common.base.Function; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableSortedSet.Builder; import com.google.common.collect.Lists; import com.google.common.collect.Range; import com.google.common.math.DoubleMath; import com.google.common.primitives.Doubles; import java.math.BigDecimal; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; /** * Utilities for operating on {@code double}-valued data. * * <p>Unless otherwise noted, the methods of this class: * * <ul><li>Operate on data in place, returning a reference to the supplied * data.</li> * * <li>Are not synchronized.</li> * * <li>Throw a {@code NullPointerException} when supplied with {@code null} * data.</li> * * <li>Return an empty array when attempting to transform each element of a * dataset for which no varargs elements have been supplied(e.g. * {@link #add(double, double...)}). * * <li>Throw an {@code IllegalArgumentException} if they operate on all elements * of a dataset to yield a singular numeric or boolean value, but the supplied * dataset is empty, with some documented exceptions. * * <li>Throw an {@code IllegalArguementException} if they operate on multiple * datasets and the datasets are not the same size.</li> * * <li>Do not check for finiteness (see {@link Doubles#isFinite(double)}). For * example, any method that operates on data containing {@code Double.NaN} or * infinite values will likely return {@code NaN}s or infinite values as a * result. See the Java {@link Math} class for details on the behavior of * individual functions referenced herein.</li> * * <li>Do not check for over/under flow.</li></ul> * * <p>Buyer beware. * * <p>Many methods in this class are overloaded with {@code Collection<Double>} * arguments. Those overloaded methods that return a single result or * information about a supplied data set typically require a * {@code Collection<Double>} as an argument, whereas methods that transform * data in place require a {@code List<Double>} subtype. * * <p>For other useful {@code Double} utilities, see the Google Guava * {@link Doubles} class. * * @author Peter Powers */ public final class Data { /* * Developer notes: * ------------------------------------------------------------------------- * Transform Functions vs Pure Iteration * * The original implementation of this class used the built-in transform() * methods and math Functions to operate on data arrays. Tests showed the * Function approach to be only marginally slower, but much more processor * intensive suggesting there would be a performance penalty in multi-threaded * applications. * * Unchecked delegate methods for primitive arrays are supplied for package * level use where data integrity is assured. * ------------------------------------------------------------------------- */ private Data() {} /* * * * * * * * * * * * * OPERATORS * * * * * * * * * * * * */ /** * Add a {@code term} to the elements of {@code data} in place. * * @param data to operate on * @param term to add * @return a reference to the supplied {@code data} */ public static List<Double> add(double term, List<Double> data) { for (int i = 0; i < data.size(); i++) { data.set(i, data.get(i) + term); } return data; } /** * Add a {@code term} to the elements of {@code data} in place. * * @param data to operate on * @param term to add * @return a reference to the supplied {@code data} */ public static double[] add(double term, double... data) { for (int i = 0; i < data.length; i++) { data[i] += term; } return data; } /** * Add a {@code term} to the elements of {@code data} in place. * * @param data to operate on * @param term to add * @return a reference to the supplied {@code data} */ public static double[][] add(double term, double[][] data) { for (int i = 0; i < data.length; i++) { add(term, data[i]); } return data; } /** * Add a {@code term} to the elements of {@code data} in place. * * @param data to operate on * @param term to add * @return a reference to the supplied {@code data} */ public static double[][][] add(double term, double[][][] data) { for (int i = 0; i < data.length; i++) { add(term, data[i]); } return data; } /** * Add the values of {@code data2} to {@code data1} in place. * * @param data1 * @param data2 * @return a reference to {@code data1} */ public static List<Double> add(List<Double> data1, List<Double> data2) { checkSizes(data1, data2); for (int i = 0; i < data1.size(); i++) { data1.set(i, data1.get(i) + data2.get(i)); } return data1; } /** * Add the values of {@code data2} to {@code data1} in place. * * @param data1 * @param data2 * @return a reference to {@code data1} */ public static double[] add(double[] data1, double[] data2) { checkSizes(data1, data2); return uncheckedAdd(data1, data2); } static double[] uncheckedAdd(double[] data1, double[] data2) { for (int i = 0; i < data1.length; i++) { data1[i] += data2[i]; } return data1; } /** * Add the values of {@code data2} to {@code data1} in place. * * @param data1 * @param data2 * @return a reference to {@code data1} */ public static double[][] add(double[][] data1, double[][] data2) { checkSizes(data1, data2); for (int i = 0; i < data1.length; i++) { add(data1[i], data2[i]); } return data1; } static double[][] uncheckedAdd(double[][] data1, double[][] data2) { for (int i = 0; i < data1.length; i++) { uncheckedAdd(data1[i], data2[i]); } return data1; } /** * Add the values of {@code data2} to {@code data1} in place. * * @param data1 * @param data2 * @return a reference to {@code data1} */ public static double[][][] add(double[][][] data1, double[][][] data2) { checkSizes(data1, data2); for (int i = 0; i < data1.length; i++) { add(data1[i], data2[i]); } return data1; } static double[][][] uncheckedAdd(double[][][] data1, double[][][] data2) { for (int i = 0; i < data1.length; i++) { uncheckedAdd(data1[i], data2[i]); } return data1; } /** * Adds the entries of {@code map2} to {@code map1} in place. If a key from * {@code map2} exists in {@code map1}, then the value for that key is added * to the corresponding value in {@code map1}. If no such key exists in map 1, * then the key and value from map2 are transferred as is. Note that this * method is <i>not</i> synchronized. * * @param map1 * @param map2 * @return a reference to {@code map1} */ public static <T> Map<T, Double> add(Map<T, Double> map1, Map<T, Double> map2) { for (T key : map2.keySet()) { Double v2 = map2.get(key); Double v1 = (map1.containsKey(key)) ? map1.get(key) + v2 : v2; map1.put(key, v1); } return map1; } /** * Subtract the values of {@code data2} from {@code data1} in place. To * subtract a term from every value of a dataset, use * {@link #add(double, List)} with a negative addend. * * @param data1 * @param data2 * @return a reference to {@code data1} */ public static List<Double> subtract(List<Double> data1, List<Double> data2) { checkSizes(data1, data2); for (int i = 0; i < data1.size(); i++) { data1.set(i, data1.get(i) - data2.get(i)); } return data1; } /** * Subtract the values of {@code data2} from {@code data1} in place. To * subtract a term from every value of a dataset, use * {@link #add(double, double...)} with a negative addend. * * @param data1 * @param data2 * @return a reference to {@code data1} */ public static double[] subtract(double[] data1, double[] data2) { checkSizes(data1, data2); return uncheckedSubtract(data1, data2); } static double[] uncheckedSubtract(double[] data1, double[] data2) { for (int i = 0; i < data1.length; i++) { data1[i] -= data2[i]; } return data1; } /** * Multiply ({@code scale}) the elements of {@code data} in place. * * @param data to operate on * @param scale factor * @return a reference to the supplied {@code data} */ public static List<Double> multiply(double scale, List<Double> data) { for (int i = 0; i < data.size(); i++) { data.set(i, data.get(i) * scale); } return data; } /** * Multiply ({@code scale}) the elements of {@code data} in place. * * @param data to operate on * @param scale factor * @return a reference to the supplied {@code data} */ public static double[] multiply(double scale, double... data) { for (int i = 0; i < data.length; i++) { data[i] *= scale; } return data; } /** * Multiply ({@code scale}) the elements of {@code data} in place. * * @param data to operate on * @param scale factor * @return a reference to the supplied {@code data} */ public static double[][] multiply(double scale, double[][] data) { for (int i = 0; i < data.length; i++) { multiply(scale, data[i]); } return data; } /** * Multiply ({@code scale}) the elements of {@code data} in place. * * @param data to operate on * @param scale factor * @return a reference to the supplied {@code data} */ public static double[][][] multiply(double scale, double[][][] data) { for (int i = 0; i < data.length; i++) { multiply(scale, data[i]); } return data; } /** * Multiply the elements of {@code data1} by the elements of {@code data2} in * place. * * @param data1 * @param data2 * @return a reference to {@code data1} */ public static List<Double> multiply(List<Double> data1, List<Double> data2) { checkSizes(data1, data2); for (int i = 0; i < data1.size(); i++) { data1.set(i, data1.get(i) * data2.get(i)); } return data1; } /** * Multiply the elements of {@code data1} by the elements of {@code data2} in * place. * * @param data1 * @param data2 * @return a reference to {@code data1} */ public static double[] multiply(double[] data1, double[] data2) { checkSizes(data1, data2); return uncheckedMultiply(data1, data2); } static double[] uncheckedMultiply(double[] data1, double[] data2) { for (int i = 0; i < data1.length; i++) { data1[i] *= data2[i]; } return data1; } /** * Divide the elements of {@code data1} by the elements of {@code data2} in * place. To divide every value of a dataset by some term, use * {@link #multiply(double, double...)} with 1/divisor. * * @param data1 * @param data2 * @return a reference to {@code data1} */ public static double[] divide(double[] data1, double[] data2) { checkSizes(data1, data2); return uncheckedDivide(data1, data2); } static double[] uncheckedDivide(double[] data1, double[] data2) { for (int i = 0; i < data1.length; i++) { data1[i] /= data2[i]; } return data1; } /** * Divide the elements of {@code data1} by the elements of {@code data2} in * place. To divide every value of a dataset by some term, use * {@link #multiply(double, List)} with 1/divisor. * * @param data1 * @param data2 * @return a reference to {@code data1} */ public static List<Double> divide(List<Double> data1, List<Double> data2) { checkSizes(data1, data2); for (int i = 0; i < data1.size(); i++) { data1.set(i, data1.get(i) / data2.get(i)); } return data1; } /** * Set the elements of {@code data} to their absolute value in place. * * @param data to operate on * @return a reference to the supplied {@code data} * @see Math#abs(double) */ public static List<Double> abs(List<Double> data) { for (int i = 0; i < data.size(); i++) { data.set(i, Math.abs(data.get(i))); } return data; } /** * Set the elements of {@code data} to their absolute value in place. * * @param data to operate on * @return a reference to the supplied {@code data} * @see Math#abs(double) */ public static double[] abs(double... data) { for (int i = 0; i < data.length; i++) { data[i] = Math.abs(data[i]); } return data; } /** * Raise Euler's number {@code e} to each of the elements of {@code data} in * place. * * @param data to operate on * @return a reference to the supplied {@code data} * @see Math#exp(double) */ public static List<Double> exp(List<Double> data) { for (int i = 0; i < data.size(); i++) { data.set(i, Math.exp(data.get(i))); } return data; } /** * Raise Euler's number {@code e} to each of the elements of {@code data} in * place. * * @param data to operate on * @return a reference to the supplied {@code data} * @see Math#exp(double) */ public static double[] exp(double... data) { for (int i = 0; i < data.length; i++) { data[i] = Math.exp(data[i]); } return data; } /** * Take the natural logarithm of the elements of {@code data} in place. * * @param data to operate on * @return a reference to the supplied {@code data} * @see Math#log(double) */ public static List<Double> ln(List<Double> data) { for (int i = 0; i < data.size(); i++) { data.set(i, Math.log(data.get(i))); } return data; } /** * Take the natural logarithm of the elements of {@code data} in place. * * @param data to operate on * @return a reference to the supplied {@code data} * @see Math#log(double) */ public static double[] ln(double[] data) { for (int i = 0; i < data.length; i++) { data[i] = Math.log(data[i]); } return data; } /** * Raise the elements of {@code data} to the power of 10 in place. * * @param data to operate on * @return a reference to the supplied {@code data} * @see Math#pow(double, double) */ public static List<Double> pow10(List<Double> data) { for (int i = 0; i < data.size(); i++) { data.set(i, Math.pow(10, data.get(i))); } return data; } /** * Raise the elements of {@code data} to the power of 10 in place. * * @param data to operate on * @return a reference to the supplied {@code data} * @see Math#pow(double, double) */ public static double[] pow10(double... data) { for (int i = 0; i < data.length; i++) { data[i] = Math.pow(10, data[i]); } return data; } /** * Take the base-10 logarithm of the elements of {@code data} in place. * * @param data to operate on * @return a reference to the supplied {@code data} * @see Math#log10(double) */ public static List<Double> log(List<Double> data) { for (int i = 0; i < data.size(); i++) { data.set(i, Math.log10(data.get(i))); } return data; } /** * Take the base-10 logarithm of the elements of {@code data} in place. * * @param data to operate on * @return a reference to the supplied {@code data} * @see Math#log10(double) */ public static double[] log(double... data) { for (int i = 0; i < data.length; i++) { data[i] = Math.log10(data[i]); } return data; } /** * Flip the sign of the elements of {@code data} in place. * * @param data to operate on * @return a reference to the supplied {@code data} */ public static List<Double> flip(List<Double> data) { return multiply(-1, data); } /** * Flip the sign of the elements of {@code data} in place. * * @param data to operate on * @return a reference to the supplied {@code data} */ public static double[] flip(double... data) { return multiply(-1, data); } /** * Sum the elements of {@code data}. Method returns zero for an empty * {@code data} argument. * * @param data to sum * @return the sum of the supplied values */ public static double sum(Collection<Double> data) { double sum = 0; for (double d : data) { sum += d; } return sum; } /** * Sum the elements of {@code data}. Method returns zero for empty * {@code data} argument or no varargs. * * @param data to sum * @return the sum of the supplied values */ public static double sum(double... data) { double sum = 0; for (double d : data) { sum += d; } return sum; } /** * Sum the arrays in the 2nd dimension of {@code data} into a new 1-D array. * * @param data to collapse * @return a new array with the sums of the second dimension of {@code data} */ public static double[] collapse(double[][] data) { double[] collapsed = new double[data.length]; for (int i = 0; i < data.length; i++) { collapsed[i] = sum(data[i]); } return collapsed; } /** * Sum the arrays in the 3rd dimension of {@code data} into the 2nd dimension * of a new 2-D array. * * @param data to collapse * @return a new 2-D array with the sums of the third dimension of * {@code data} */ public static double[][] collapse(double[][][] data) { double[][] collapsed = new double[data.length][]; for (int i = 0; i < data.length; i++) { collapsed[i] = collapse(data[i]); } return collapsed; } /** * Transform {@code data} by a {@code function} in place. * * @param function to apply * @param data to operate on * @return a reference to the supplied {@code data} */ public static List<Double> transform(Function<Double, Double> function, List<Double> data) { checkNotNull(function); for (int i = 0; i < data.size(); i++) { data.set(i, function.apply(data.get(i))); } return data; } /** * Transform {@code data} by a {@code function} in place. * * @param function to apply * @param data to operate on * @return a reference to the supplied {@code data} */ public static double[] transform(Function<Double, Double> function, double... data) { checkNotNull(function); for (int i = 0; i < data.length; i++) { data[i] = function.apply(data[i]); } return data; } private static final String NORM_DATA_ERROR = "Normalize: Data outside range [0..+Inf)"; private static final String NORM_SUM_ERROR = "Normalize: Sum outside range (0..+Inf)"; /** * Normalize the elements of {@code data} in place such that they sum to 1. * * @param data to normalize * @return a reference to the supplied {@code data} * @throws IllegalArgumentException if {@code data} is empty or no varargs are * supplied, contains values outside the range {@code [0..+Inf)}, or * sums to a value outside the range {@code (0..+Inf)} */ public static List<Double> normalize(List<Double> data) { checkArgument(arePositiveAndRealOrZero(data), NORM_DATA_ERROR); double sum = sum(data); checkArgument(isPositiveAndReal(sum), NORM_SUM_ERROR); double scale = 1.0 / sum; return multiply(scale, data); } /** * Normalize the elements of {@code data} in place such that they sum to 1. * * @param data to normalize * @return a reference to the supplied {@code data} * @throws IllegalArgumentException if {@code data} is empty or no varargs are * supplied, contains values outside the range {@code [0..+Inf)}, or * sums to a value outside the range {@code (0..+Inf)} */ public static double[] normalize(double... data) { checkArgument(arePositiveAndRealOrZero(data), NORM_DATA_ERROR); double sum = sum(data); checkArgument(isPositiveAndReal(sum), NORM_SUM_ERROR); double scale = 1.0 / sum; return multiply(scale, data); } /** * Round the elements of {@code data} in place to double values of a specified * scale (precision). Internally, this method uses the rounding and precision * functionality of {@link BigDecimal}. * * @param data to operate on * @param scale decimal precision * @return a reference to the supplied {@code data} * @see Maths#round(double, int) */ public static List<Double> round(int scale, List<Double> data) { for (int i = 0; i < data.size(); i++) { data.set(i, Maths.round(data.get(i), scale)); } return data; } /** * Round the elements of {@code data} in place to double values of a specified * scale (precision). Internally, this method uses the rounding and precision * functionality of {@link BigDecimal}. * * @param data to operate on * @param scale decimal precision * @return a reference to the supplied {@code data} * @see Maths#round(double, int) */ public static double[] round(int scale, double... data) { for (int i = 0; i < data.length; i++) { data[i] = Maths.round(data[i], scale); } return data; } /** * Ensures positivity of values by adding {@code Math.abs(min(data))} in place * if {@code min < 0}. * * @param data to operate on * @return a reference to the supplied data, positivized if necessary */ public static double[] positivize(double... data) { if (data.length == 0) { return data; } double min = Doubles.min(data); if (min >= 0) { return data; } min = Math.abs(min); return add(min, data); } /** * Build an array of the differences between the adjacent elements of * {@code data}. Method returns results in a new array that has * {@code data.length - 1} where differences are computed per * {@code data[i+1] - data[i]}. * * @param data to difference * @return the differences between adjacent values * @throws IllegalArgumentException if {@code data.length < 2} */ public static double[] diff(double... data) { int size = checkSize(2, data).length - 1; double[] diff = new double[size]; for (int i = 0; i < size; i++) { diff[i] = data[i + 1] - data[i]; } return diff; } /** * Compute the difference between {@code test} and {@code target}, relative to * {@code target}, as a percent. If {@code target == 0}, method returns * {@code 0} if {@code test == 0}, otherwise {@code Double.POSITIVE_INFINITY}. * * @param test value * @param target value * @return {@code 100 * abs(test - target) / target} * @throws IllegalArgumentException if {@code test} or {@code target} are not * finite. */ public static double percentDiff(double test, double target) { checkFinite("test", test); checkFinite("target", target); if (target == 0 && test == 0) { return 0; } return Math.abs(test - target) / target * 100.0; } /* * * * * * * * * * * * * * STATE * * * * * * * * * * * * * */ /** * Determine whether {@code value} is a positive, real number in the range * {@code (0..+Inf)}. * * @param value to validate */ static boolean isPositiveAndReal(double value) { return value > 0.0 && value < Double.POSITIVE_INFINITY; } /** * Determine whether the elements of {@code data} are all positive, real * numbers. * * @param data to validate * @return {@code true} if all data are in the range {@code (0..+Inf)}; {@code * false} otherwise * @throws IllegalArgumentException if {@code data} is empty or no varargs are * supplied */ static boolean arePositiveAndReal(double... data) { checkSize(1, data); for (double d : data) { if (!isPositiveAndReal(d)) { return false; } } return true; } /** * Determine whether the elements of {@code data} are all positive, real * numbers. * * @param data to validate * @return {@code true} if all data are in the range {@code (0..+Inf)}; {@code * false} otherwise * @throws IllegalArgumentException if {@code data} is empty */ static boolean arePositiveAndReal(Collection<Double> data) { checkSize(1, data); for (double d : data) { if (!isPositiveAndReal(d)) { return false; } } return true; } /** * Determine whether {@code value} is a positive, real number in the range * {@code [0..+Inf)}. * * @param value to validate */ static boolean isPositiveAndRealOrZero(double value) { return value >= 0.0 && value < Double.POSITIVE_INFINITY; } /** * Determine whether the elements of {@code data} are all positive, real * numbers, or 0. * * @param data to validate * @return {@code true} if all data are in the range {@code [0..+Inf)}; {@code * false} otherwise * @throws IllegalArgumentException if {@code data} is empty or no varargs are * supplied */ static boolean arePositiveAndRealOrZero(double... data) { checkSize(1, data); for (double d : data) { if (!isPositiveAndRealOrZero(d)) { return false; } } return true; } /** * Determine whether the elements of {@code data} are all positive, real * numbers, or 0. * * @param data to validate * @return {@code true} if all data are in the range {@code [0..+Inf)}; {@code * false} otherwise * @throws IllegalArgumentException if {@code data} is empty */ static boolean arePositiveAndRealOrZero(Collection<Double> data) { checkSize(1, data); for (double d : data) { if (!isPositiveAndRealOrZero(d)) { return false; } } return true; } /** * Determine whether the elements of {@code data} are all equal to 0. * * @param data to validate * @return {@code true} if all values = 0; {@code false} otherwise * @throws IllegalArgumentException if {@code data} is empty or no varargs are * supplied */ static boolean areZeroValued(double... data) { checkSize(1, data); for (double d : data) { if (d != 0.0) { return false; } } return true; } /** * Determine whether all the elements of {@code data} are equal to 0. * * @param data to validate * @return {@code true} if all values = 0; {@code false} otherwise * @throws IllegalArgumentException if {@code data} is empty */ static boolean areZeroValued(Collection<Double> data) { checkSize(1, data); for (double d : data) { if (d != 0.0) { return false; } } return true; } /** * Determine whether the elements of {@code data} increase or decrease * monotonically.The {@code strict} flag indicates if identical adjacent * elements are forbidden. The {@code strict} flag could be {@code true} if * checking the x-values of a sequence for any steps, or {@code false} if * checking the y-values of a cumulative distribution, which are commonly * constant. * * @param increasing if {@code true}, descending if {@code false} * @param strict {@code true} if data must always increase or decrease, {@code * false} if identical adjacent values are permitted * @param data to validate * @return {@code true} if monotonic, {@code false} otherwise * @throws IllegalArgumentException if fewer than two data elements are * supplied */ static boolean areMonotonic(boolean increasing, boolean strict, double... data) { double[] diff = diff(data); if (!increasing) { flip(diff); } double min = Doubles.min(diff); return (strict) ? min > 0 : min >= 0; } /* * * * * * * * * * * * PRECONDITIONS * * * * * * * * * * * */ private static final String DATA_ERROR = "Data value"; /** * Ensure validity of sequence discretization parameters. Confirms that for a * specified range {@code [min, max]} and {@code Δ} that: * * <ul><li>{@code min}, {@code max}, and {@code Δ} are finite</li> * * <li>{@code max > min}</li> * * <li>{@code Δ ≥ 0}</li> * * <li>{@code Δ > 0} for {@code max > min}</li> * * <li>{@code Δ ≤ max - min}</li></ul> * * @param min value * @param max value * @param Δ discretization delta * @return the supplied {@code Δ} for use inline */ public static double checkDelta(double min, double max, double Δ) { checkFinite("Sequence minimum", min); checkFinite("Sequence maximum", max); checkFinite("Sequence Δ", Δ); checkArgument(max >= min, "min [%s] >= max [%s]", min, max); checkArgument(Δ >= 0.0, "Invalid Δ [%s]", Δ); if (max > min) { checkArgument(Δ > 0.0, "Invalid Δ [%s] for max > min", Δ); } checkArgument(Δ <= max - min, "Δ [%s] > max - min [%s]", Δ, max - min); return Δ; } /** * Ensure {@code value} is finite. * * @param label for value if check fails * @param value to check * @return the validated value * @throws IllegalArgumentException if {@code value} is outside the range * {@code (-Inf..+Inf)} * @see Doubles#isFinite(double) */ public static double checkFinite(String label, double value) { checkArgument(Doubles.isFinite(value), "%s [%s] not finite", label, value); return value; } /** * Ensure the elements of {@code data} are finite. * * @param data to validate * @return a reference to the supplied {@code data} * @throws IllegalArgumentException if {@code data} is empty, or any elements * of {@code data} are outside the range {@code (-Inf..+Inf)} * @see Doubles#isFinite(double) */ public static Collection<Double> checkFinite(Collection<Double> data) { checkSize(1, data); for (double d : data) { checkFinite(DATA_ERROR, d); } return data; } /** * Ensure the elements of {@code data} are finite. * * @param data to validate * @return a reference to the supplied {@code data} * @throws IllegalArgumentException if {@code data} is empty or no varargs are * supplied, or any elements of {@code data} are outside the range * {@code (-Inf..+Inf)} * @see Doubles#isFinite(double) */ public static double[] checkFinite(double... data) { checkSize(1, data); for (double d : data) { checkFinite(DATA_ERROR, d); } return data; } /** * Ensure {@code value} falls within the specified {@link Range}. * * @param range of allowable values * @param label for value if check fails * @param value to validate * @return the supplied {@code value} * @throws IllegalArgumentException if either range endpoint is {@code NaN}. A * range where both enpoints are {@code NaN} and at least one enpoint * is closed is permitted; only when <em>both</em> endpoints are * closed is the value {@code NaN} permitted to the exclusion of all * other values. */ public static double checkInRange(Range<Double> range, String label, double value) { checkArgument(range.contains(value), "%s [%s] not in range %s", label, value, range); return value; } /** * Ensure the elements of {@code data} fall within the specified {@link Range} * . * * @param range of allowable values * @param data to validate * @return a reference to the supplied {@code data} * @see #checkInRange(Range, String, double) for exception notes */ public static Collection<Double> checkInRange(Range<Double> range, Collection<Double> data) { for (double d : data) { checkInRange(range, DATA_ERROR, d); } return data; } /** * Ensure the elements of {@code data} fall within the specified {@link Range} * . * * @param range of allowable values * @param data to validate * @return a reference to the supplied {@code data} * @see #checkInRange(Range, String, double) for exception notes */ public static double[] checkInRange(Range<Double> range, double... data) { for (int i = 0; i < data.length; i++) { checkInRange(range, DATA_ERROR, data[i]); } return data; } /** * Ensure {@code data.size() ≥ min}. * * @return a reference to the supplied {@code data} */ public static Collection<Double> checkSize(int min, Collection<Double> data) { checkSize(min, data.size()); return data; } /** * Ensure {@code data.length ≥ min}. * * @return a reference to the supplied {@code data} */ public static double[] checkSize(int min, double[] data) { checkSize(min, data.length); return data; } private static void checkSize(int min, int size) { checkArgument(size >= min, "Data size[%s] < minimum[%s]", size, min); } /** * Ensure the supplied datasets are the same size. */ public static void checkSizes(Collection<Double> data1, Collection<Double> data2) { checkSizes(data1.size(), data2.size()); } /** * Ensure the supplied datasets are the same size. */ public static void checkSizes(double[] data1, double[] data2) { checkSizes(data1.length, data2.length); } /** * Ensure the 1<sup>st</sup> dimensions of the supplied datasets are the same * size. */ public static void checkSizes(double[][] data1, double[][] data2) { checkSizes(data1.length, data2.length); } /** * Ensure the 1<sup>st</sup> dimensions of the supplied datasets are the same * size. */ public static void checkSizes(double[][][] data1, double[][][] data2) { checkSizes(data1.length, data2.length); } private static void checkSizes(int s1, int s2) { checkArgument(s1 == s2, "Data1.size[%s] ≠ Data2.size[%s]", s1, s2); } private static final Range<Double> WEIGHT_RANGE = Range.openClosed(0.0, 1.0); private static final Range<Double> WEIGHT_RANGE_0 = Range.closed(0.0, 1.0); private static final double WEIGHT_TOLERANCE = 1e-4; private static double checkWeight(double weight, boolean allowZero) { checkInRange(allowZero ? WEIGHT_RANGE_0 : WEIGHT_RANGE, "Weight", weight); return weight; } /** * Ensure {@code 0.0 < weight ≤ 1.0}. * * @return the supplied {@code weight} */ public static double checkWeight(double weight) { return checkWeight(weight, false); } /** * Ensure each {@code 0.0 ≤ weight ≤ 1.0} and * {@code sum(weights) = 1.0 ± 0.0001}. * * @param weights to validate * @return a reference to the supplied {@code weights} */ public static Collection<Double> checkWeights(Collection<Double> weights) { for (double weight : weights) { checkWeight(weight, true); } double sum = sum(weights); checkArgument(DoubleMath.fuzzyEquals(sum, 1.0, WEIGHT_TOLERANCE), "Weights Σ %s = %s ≠ 1.0", weights, sum); return weights; } /* * * * * * * * 2D & 3D ARRAYS EXTENSIONS * * * * * * * * */ /** * Create a deep copy of a two-dimensional data array. * * @param data to copy * @return a new two-dimensional array populated with the values of * {@code data} */ public static double[][] copyOf(double[][] data) { double[][] out = new double[data.length][]; for (int i = 0; i < data.length; i++) { out[i] = Arrays.copyOf(data[i], data[i].length); } return out; } /** * Create a deep copy of a three-dimensional data array. * * @param data to copy * @return a new three-dimensional array populated with the values of * {@code data} */ public static double[][][] copyOf(double[][][] data) { double[][][] out = new double[data.length][][]; for (int i = 0; i < data.length; i++) { out[i] = copyOf(data[i]); } return out; } /** * Format a two-dimensional data array for printing. * * @param data to format * @return a string representation of the supplied {@code data} */ public static String toString(double[][] data) { return toString(data, 1); } /* To support indenting of multidimensional arrays */ private static String toString(double[][] data, int indent) { StringBuilder sb = new StringBuilder("["); for (int i = 0; i < data.length; i++) { if (i > 0) { sb.append(",").append(NEWLINE); sb.append(Strings.repeat(" ", indent)); } sb.append(Arrays.toString(data[i])); } sb.append("]"); return sb.toString(); } /** * Format a three-dimensional data array for printing * * @param data to format * @return a string representation of the supplied {@code data} */ public static String toString(double[][][] data) { return toString(data, 1); } /* To support indenting of multidimensional arrays */ private static String toString(double[][][] data, int indent) { StringBuilder sb = new StringBuilder("["); for (int i = 0; i < data.length; i++) { if (i > 0) { sb.append(",").append(NEWLINE); sb.append(Strings.repeat(" ", indent)); } sb.append(toString(data[i], indent + 1)); } sb.append("]"); return sb.toString(); } /* * * * * * * * * * * * Everything below needs review */ /** * Creates a sequence of evenly spaced values starting at {@code min} and * ending at {@code max}. If {@code (max - min) / step} is not equivalent to * an integer, the last step in the sequence will be {@code <step}. Unlike * {@link #buildSequence(double, double, double, boolean)}, this method * returns a sequence where any 'odd' values due to rounding errors have been * removed, at least within the range of the specified {@code scale} * (precision or number of decimal places). * @param min sequence value * @param max sequence value * @param step sequence spacing * @param ascending if {@code true}, descending if {@code false} * @param scale the number of decimal places to preserve * @return a monotonically increasing or decreasing sequence of values * @throws IllegalArgumentException if {@code min >= max}, {@code step <= 0} , * or any arguments are {@code Double.NaN}, * {@code Double.POSITIVE_INFINITY}, or * {@code Double.NEGATIVE_INFINITY} */ public static double[] buildCleanSequence(double min, double max, double step, boolean ascending, int scale) { double[] seq = buildSequence(min, max, step, ascending); return round(scale, seq); } /** * Creates a sequence of evenly spaced values starting at {@code min} and * ending at {@code max}. If {@code (max - min) / step} is not integer valued, * the last step in the sequence will be {@code <step}. If {@code min == max}, * then an array containing a single value is returned. * @param min sequence value * @param max sequence value * @param step sequence spacing * @param ascending if {@code true}, descending if {@code false} * @return a monotonically increasing or decreasing sequence of values * @throws IllegalArgumentException if {@code min >= max}, {@code step <= 0} , * or any arguments are {@code Double.NaN}, * {@code Double.POSITIVE_INFINITY}, or * {@code Double.NEGATIVE_INFINITY} */ public static double[] buildSequence(double min, double max, double step, boolean ascending) { // if passed in arguments are NaN, +Inf, or -Inf, and step <= 0, // then capacity [c] will end up 0 because (int) NaN = 0, or outside the // range 1:10000 checkArgument(min <= max, "min-max reversed"); if (min == max) { return new double[] { min }; } int c = (int) ((max - min) / step); checkArgument(c > 0 && c < MAX_SEQ_LEN, "sequence size"); if (ascending) { return buildSequence(min, max, step, c + 2); } double[] descSeq = buildSequence(-max, -min, step, c + 2); return flip(descSeq); // TODO // double[] mags = DataUtils.buildSequence(5.05, 7.85, 0.1, true); // System.out.println(Arrays.toString(mags)); // produces crummy values 2.449999999999999999 etc... } private static final int MAX_SEQ_LEN = 10001; private static final double SEQ_MAX_VAL_TOL = 0.000000000001; private static double[] buildSequence(double min, double max, double step, int capacity) { List<Double> seq = Lists.newArrayListWithCapacity(capacity); for (double val = min; val < max; val += step) { seq.add(val); } // do not add max if current max is equal to max wihthin tolerance if (!DoubleMath.fuzzyEquals(seq.get(seq.size() - 1), max, SEQ_MAX_VAL_TOL)) { seq.add(max); } return Doubles.toArray(seq); } /** * Combine the supplied {@code sequences}. The y-values returned are the set * of all supplied y-values. The x-values returned are the sum of the supplied * x-values. When summing, x-values for points outside the original domain of * a sequence are set to 0, while those inside the original domain are sampled * via linear interpolation. * * * @param sequences to combine * @return a combined sequence */ @Deprecated public static XySequence combine(Iterable<XySequence> sequences) { // TODO I think we want to have interpolating and non-interpolating // flavors. Interpolating for visual presentation, non-interpolating // for re-use as MFD // create master x-value sequence Builder<Double> builder = ImmutableSortedSet.naturalOrder(); for (XySequence sequence : sequences) { builder.addAll(sequence.xValues()); } double[] xMaster = Doubles.toArray(builder.build()); // resample and combine sequences XySequence combined = XySequence.create(xMaster, null); for (XySequence sequence : sequences) { // TODO need to disable extrapolation in Interpolation if (true) { throw new UnsupportedOperationException(); } XySequence resampled = XySequence.resampleTo(sequence, xMaster); combined.add(resampled); } return combined; } // TODO clean // /** // * Validates the domain of a {@code double} data set. Method verifies // * that data values all fall between {@code min} and {@code max} range // * (inclusive). Empty arrays are ignored. If {@code min} is // * {@code Double.NaN}, no lower limit is imposed; the same holds true // * for {@code max}. {@code Double.NaN} values in {@code array} // * will validate. // * // * @param min minimum range value // * @param max maximum range value // * @param array to validate // * @throws IllegalArgumentException if {@code min > max} // * @throws IllegalArgumentException if any {@code array} value is out of // * range // * @deprecated Ranges should be used instead with NaNs throwing an // exception // */ // @Deprecated // public final static void validate(double min, double max, double... // array) { // checkNotNull(array, "array"); // for (int i = 0; i < array.length; i++) { // validate(min, max, array[i]); // } // } // // /** // * Verifies that a {@code double} data value falls within a specified // * minimum and maximum range (inclusive). If {@code min} is // * {@code Double.NaN}, no lower limit is imposed; the same holds true // * for {@code max}. A value of {@code Double.NaN} will always // * validate. // * // * @param min minimum range value // * @param max minimum range value // * @param value to check // * @throws IllegalArgumentException if {@code min > max} // * @throws IllegalArgumentException if value is out of range // * @deprecated Ranges should be used instead with NaNs throwing an // exception // */ // @Deprecated // public final static void validate(double min, double max, double value) { // boolean valNaN = isNaN(value); // boolean minNaN = isNaN(min); // boolean maxNaN = isNaN(max); // boolean both = minNaN && maxNaN; // boolean neither = !(minNaN || maxNaN); // if (neither) checkArgument(min <= max, "min-max reversed"); // boolean expression = valNaN || both ? true : minNaN // ? value <= max : maxNaN ? value >= min : value >= min && // value <= max; // checkArgument(expression, "value"); // } static int firstNonZeroIndex(double... data) { for (int i=0; i<data.length; i++) { if (data[i] != 0.0) { return i; } } return -1; } static int lastNonZeroIndex(double... data) { for (int i=data.length-1; i>=0; i--) { if (data[i] != 0.0) { return i; } } return -1; } }