package org.opensha2.data;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.opensha2.data.Data.areMonotonic;
import static org.opensha2.internal.TextUtils.NEWLINE;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.primitives.Doubles;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.RandomAccess;
/**
* Sequence of xy-value pairs that is iterable ascending in x. Once created, the
* x-values of a sequence are immutable. This class provides static operations
* to create instances of sequences that have both mutable and immutable
* y-values. All data supplied to these operations is defensively copied unless
* it is not necessary to do so. For instance, {@code *copyOf()} variants should
* be used where possible as x-values will never be replicated in memory.
*
* <p>Although this class is not final, it can not be subclassed. Mutable
* instances of this class are not thread-safe for operations that alter
* y-values.
*
* @author Peter Powers
*/
public abstract class XySequence implements Iterable<XyPoint> {
/**
* Create a new sequence with mutable y-values from the supplied value arrays.
* If the supplied y-value array is null, all y-values are initialized to 0.
*
* @param xs x-values to initialize sequence with
* @param ys y-values to initialize sequence with; may be null
* @return a mutable, {@code double[]}-backed sequence
* @throws NullPointerException if {@code xs} are null
* @throws IllegalArgumentException if {@code xs} and {@code ys} are not the
* same size
* @throws IllegalArgumentException if {@code xs} does not increase
* monotonically or contains repeated values
*/
public static XySequence create(double[] xs, double[] ys) {
return create(xs, ys, true);
}
/**
* Create a new, immutable sequence from the supplied value arrays. Unlike
* {@link #create(double[], double[])}, the supplied y-value array may not be
* null.
*
* @param xs x-values to initialize sequence with
* @param ys y-values to initialize sequence with
* @return an immutable, {@code double[]}-backed sequence
* @throws NullPointerException if {@code xs} or {@code ys} are null
* @throws IllegalArgumentException if {@code xs} and {@code ys} are not the
* same size
* @throws IllegalArgumentException if {@code xs} does not increase
* monotonically or contains repeated values
*/
public static XySequence createImmutable(double[] xs, double[] ys) {
return create(xs, checkNotNull(ys), false);
}
private static XySequence create(double[] xs, double[] ys, boolean mutable) {
return construct(
Arrays.copyOf(xs, xs.length),
(ys == null) ? new double[xs.length] : Arrays.copyOf(ys, ys.length),
mutable);
}
/**
* Create a new sequence with mutable y-values from the supplied value
* collections. If the y-value collection is null, all y-values are
* initialized to 0.
*
* @param xs x-values to initialize sequence with
* @param ys y-values to initialize sequence with; may be null
* @return a mutable, {@code double[]}-backed sequence
* @throws NullPointerException if {@code xs} are null
* @throws IllegalArgumentException if {@code xs} and {@code ys} are not the
* same size
* @throws IllegalArgumentException if {@code xs} does not increase
* monotonically or contains repeated values
*/
public static XySequence create(
Collection<? extends Number> xs,
Collection<? extends Number> ys) {
return create(xs, ys, true);
}
/**
* Create a new, immutable sequence from the supplied value collections.
* Unlike {@link #create(Collection, Collection)} , the supplied y-value
* collection may not be null.
*
* @param xs x-values to initialize sequence with
* @param ys y-values to initialize sequence with
* @return an immutable, {@code double[]}-backed sequence
* @throws NullPointerException if {@code xs} or {@code ys} are null
* @throws IllegalArgumentException if {@code xs} and {@code ys} are not the
* same size
* @throws IllegalArgumentException if {@code xs} does not increase
* monotonically or contains repeated values
*/
public static XySequence createImmutable(
Collection<? extends Number> xs,
Collection<? extends Number> ys) {
return create(xs, checkNotNull(ys), false);
}
private static XySequence create(
Collection<? extends Number> xs,
Collection<? extends Number> ys,
boolean mutable) {
return construct(
Doubles.toArray(xs),
(ys == null) ? new double[xs.size()] : Doubles.toArray(ys),
mutable);
}
private static XySequence construct(double[] xs, double[] ys, boolean mutable) {
checkArgument(xs.length > 0, "x-values may not be empty");
checkArgument(xs.length == ys.length, "x- and y-values are different sizes");
if (xs.length > 1) {
checkArgument(areMonotonic(true, true, xs), "x-values do not increase monotonically");
}
return mutable ? new MutableXySequence(xs, ys) : new ImmutableXySequence(xs, ys);
}
/**
* Create a mutable copy of the supplied {@code sequence}.
*
* @param sequence to copy
* @return a mutable copy of the supplied {@code sequence}
* @throws NullPointerException if the supplied {@code sequence} is null
*/
public static XySequence copyOf(XySequence sequence) {
return new MutableXySequence(checkNotNull(sequence), false);
}
/**
* Create a mutable copy of the supplied {@code sequence} with all y-values
* reset to zero.
*
* @param sequence to copy
* @return a mutable copy of the supplied {@code sequence}
* @throws NullPointerException if the supplied {@code sequence} is null
*/
public static XySequence emptyCopyOf(XySequence sequence) {
return new MutableXySequence(checkNotNull(sequence), true);
}
/**
* Create an immutable copy of the supplied {@code sequence}.
*
* @param sequence to copy
* @return an immutable copy of the supplied {@code sequence}
* @throws NullPointerException if the supplied {@code sequence} is null
*/
public static XySequence immutableCopyOf(XySequence sequence) {
return (sequence.getClass().equals(ImmutableXySequence.class)) ? sequence
: new ImmutableXySequence(sequence, false);
}
/**
* Create a resampled version of the supplied {@code sequence}. Method
* resamples via linear interpolation and does not extrapolate beyond the
* domain of the source {@code sequence}; y-values with x-values outside the
* domain of the source sequence are set to 0.
*
* @param sequence to resample
* @param xs resample values
* @return a resampled sequence
*/
@Deprecated
public static XySequence resampleTo(XySequence sequence, double[] xs) {
// NOTE TODO this will support mfd combining
checkNotNull(sequence);
checkArgument(checkNotNull(xs).length > 0);
double[] yResample = Interpolate.findY(sequence.xValues(), sequence.yValues(), xs);
// TODO disable extrapolation
if (true) {
throw new UnsupportedOperationException();
}
return XySequence.create(xs, yResample);
}
XySequence() {}
/**
* Returns the x-value at {@code index}.
* @param index to retrieve
* @return the x-value at {@code index}
* @throws IndexOutOfBoundsException if the index is out of range (
* {@code index < 0 || index >= size()})
*/
public abstract double x(int index);
abstract double xUnchecked(int index);
/**
* Returns the y-value at {@code index}.
* @param index to retrieve
* @return the y-value at {@code index}
* @throws IndexOutOfBoundsException if the index is out of range (
* {@code index < 0 || index >= size()})
*/
public abstract double y(int index);
abstract double yUnchecked(int index);
/**
* Sets the y-{@code value} at {@code index}.
* @param index of y-{@code value} to set.
* @param value to set
* @throws IndexOutOfBoundsException if the index is out of range (
* {@code index < 0 || index >= size()})
*/
@SuppressWarnings("unused")
public void set(int index, double value) {
throw new UnsupportedOperationException();
}
/**
* Returns the number or points in this sequence.
* @return the sequence size
*/
public abstract int size();
/**
* Returns an immutable {@code List} of the sequence x-values.
* @return the {@code List} of x-values
*/
public List<Double> xValues() {
return new X_List();
}
private final class X_List extends AbstractList<Double> implements RandomAccess {
@Override
public Double get(int index) {
return x(index);
}
@Override
public int size() {
return XySequence.this.size();
}
@Override
public Iterator<Double> iterator() {
return new Iterator<Double>() {
private final int size = size();
private int caret = 0;
@Override
public boolean hasNext() {
return caret < size;
}
@Override
public Double next() {
return xUnchecked(caret++);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Returns an immutable {@code List} of the sequence y-values.
* @return the {@code List} of y-values
*/
public List<Double> yValues() {
return new Y_List();
}
private final class Y_List extends AbstractList<Double> implements RandomAccess {
@Override
public Double get(int index) {
return y(index);
}
@Override
public int size() {
return XySequence.this.size();
}
@Override
public Iterator<Double> iterator() {
return new Iterator<Double>() {
private int caret = 0;
private final int size = size();
@Override
public boolean hasNext() {
return caret < size;
}
@Override
public Double next() {
return yUnchecked(caret++);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Returns an iterator over the {@link XyPoint}s in this sequence. For
* immutable implementations, the {@link XyPoint#set(double)} method of a
* returned point throws an {@code UnsupportedOperationException}.
*/
@Override
public Iterator<XyPoint> iterator() {
return new XyIterator(false);
}
class XyIterator implements Iterator<XyPoint> {
private final boolean mutable;
private final int size = size();
private int caret = 0;
XyIterator(boolean mutable) {
this.mutable = mutable;
}
@Override
public boolean hasNext() {
return caret < size;
}
@Override
public XyPoint next() {
return new Point(caret++, mutable);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* The first {@link XyPoint} in this sequence.
*/
public XyPoint min() {
return new Point(0, false);
}
/**
* The last {@link XyPoint} in this sequence.
*/
public XyPoint max() {
return new Point(size() - 1, false);
}
class Point implements XyPoint {
final int index;
final boolean mutable;
Point(int index, boolean mutable) {
this.index = index;
this.mutable = mutable;
}
@Override
public double x() {
return XySequence.this.xUnchecked(index);
}
@Override
public double y() {
return XySequence.this.yUnchecked(index);
}
@Override
public void set(double y) {
if (mutable) {
XySequence.this.set(index, y);
return;
}
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return "XyPoint: [" + x() + ", " + y() + "]";
}
}
/**
* Returns a readable string representation of this sequence.
*/
@Override
public String toString() {
return new StringBuilder(getClass().getSimpleName())
.append(":")
.append(NEWLINE)
.append(Joiner.on(NEWLINE).join(this))
.toString();
}
/**
* Add a {@code term} to the y-values of this sequence in place.
*
* @param term to add
* @return {@code this} sequence, for use inline
*/
@SuppressWarnings("unused")
public XySequence add(double term) {
throw new UnsupportedOperationException();
}
/**
* Add the supplied y-values to the y-values of this sequence in place.
*
* @param ys y-values to add
* @return {@code this} sequence, for use inline
*/
@SuppressWarnings("unused")
public XySequence add(double[] ys) {
throw new UnsupportedOperationException();
}
/**
* Add the y-values of a sequence to the y-values of this sequence in place.
*
* @param sequence to add
* @return {@code this} sequence, for use inline
* @throws IllegalArgumentException if
* {@code sequence.xValues() != this.xValues()}
*/
@SuppressWarnings("unused")
public XySequence add(XySequence sequence) {
throw new UnsupportedOperationException();
}
/**
* Multiply ({@code scale}) the y-values of this sequence in place.
*
* @param scale factor
* @return {@code this} sequence, for use inline
*/
@SuppressWarnings("unused")
public XySequence multiply(double scale) {
throw new UnsupportedOperationException();
}
/**
* Multiply the y-values of this sequence by the y-values of another sequence
* in place.
*
* @param sequence to multiply {@code this} sequence by
* @return {@code this} sequence, for use inline
* @throws IllegalArgumentException if
* {@code sequence.xValues() != this.xValues()}
*/
@SuppressWarnings("unused")
public XySequence multiply(XySequence sequence) {
throw new UnsupportedOperationException();
}
/**
* Sets the y-values of this sequence to their complement in place [
* {@code 1 - y}]. Assumes this is a probability function limited to the
* domain [0 1].
*
* @return {@code this} sequence, for use inline
*/
public XySequence complement() {
throw new UnsupportedOperationException();
}
/**
* Sets all y-values to 0.
*
* @return {@code this} sequence, for use inline
*/
public XySequence clear() {
throw new UnsupportedOperationException();
}
/**
* Returns {@code true} if all y-values are 0.0; {@code false} otherwise. a
*/
public abstract boolean isClear();
/**
* Returns a new, immutable sequence that has had all leading and trailing
* zero-valued points ({@code y = 0}) removed. Any zero-valued points in the
* middle of this sequence are ignored.
*
* @throws IllegalStateException if {@link #isClear() this.isClear()} as empty
* sequences are not permitted
*/
public abstract XySequence trim();
/**
* Transforms all y-values in place using the supplied {@link Function}.
*
* @param function for transform
* @return {@code this} sequence, for use inline
*/
@SuppressWarnings("unused")
public XySequence transform(Function<Double, Double> function) {
throw new UnsupportedOperationException();
}
/**
* Adds {@code this} sequence to any exisiting sequence for {@code key} in the
* supplied {@code map}. If {@code key} does not exist in the {@code map},
* method puts a mutable copy of {@code this} in the map.
*
* @param key for sequence to add
* @param map of sequences to add to
* @throws IllegalArgumentException if the x-values of added sequences to not
* match those of existing sequences
*/
public final <E extends Enum<E>> void addToMap(E key, Map<E, XySequence> map) {
if (map.containsKey(key)) {
map.get(key).add(this);
} else {
map.put(key, XySequence.copyOf(this));
}
}
}