From d30bd730c36c1f787d716c5b982860aaadb72a9c Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Thu, 23 Sep 2021 08:44:24 -0600
Subject: [PATCH] interpolator cleanup, tests, and docs

---
 .../earthquake/nshmp/calc/Disaggregation.java |   2 +-
 .../earthquake/nshmp/data/Interpolator.java   |  90 +++---
 .../nshmp/data/InterpolatorTest.java          | 185 -------------
 .../nshmp/data/InterpolatorTests.java         | 256 ++++++++++++++++--
 4 files changed, 285 insertions(+), 248 deletions(-)
 delete mode 100644 src/test/java/gov/usgs/earthquake/nshmp/data/InterpolatorTest.java

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/calc/Disaggregation.java b/src/main/java/gov/usgs/earthquake/nshmp/calc/Disaggregation.java
index 7fba75e1..72999c4a 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/calc/Disaggregation.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/calc/Disaggregation.java
@@ -226,7 +226,7 @@ public final class Disaggregation {
   /* Hazard curves are already in log-x space. */
   static final Interpolator IML_INTERPOLATER = Interpolator.builder()
       .logy()
-      .decreasingX()
+      .decreasingY()
       .build();
 
   /* Hazard curves are already in log-x space. */
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/data/Interpolator.java b/src/main/java/gov/usgs/earthquake/nshmp/data/Interpolator.java
index c4fd0b06..c6063740 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/data/Interpolator.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/data/Interpolator.java
@@ -9,19 +9,21 @@ import java.util.List;
 
 /**
  * Utility class to perform linear and log interpolations. The methods of this
- * class are designed to be fast and, as such, perform very little argument
- * checking for monotonicity and the like.
+ * class are designed to be fast and perform very little argument checking for
+ * monotonicity and the like. Results are undefined for x- and y-value arguments
+ * where {@code size < 2}.
  *
  * <p>Making some assumptions, interpolation is fairly straightforward. Most of
  * the methods implemented here are designed to support interpolation (or
- * derivation) of y-values keyed to monotonically increasing x-values. x-value
- * interpolation is somewhat thornier. Assumptions and behaviors:
+ * derivation) of y-values keyed to monotonically increasing x-values. X-value
+ * interpolation requires knowing if y-values are increasing or decreasing.
+ * Assumptions and behaviors:
  *
  * <ul><li>No error checking for null, empty, single-valued arrays; or arrays of
  * different lengths is performed. Buyer beware.</li>
  *
  * <li>X-value arrays are always assumed to be strictly monotonically ascending
- * (no repeated values)</li>
+ * with no repeated values.</li>
  *
  * <li>Internally, binary search is used for y-value interpolation; linear
  * search is used for x-value interpolation.</li>
@@ -29,7 +31,7 @@ import java.util.List;
  * <li>Y-value interpolation will always extrapolate off the ends a sequence;
  * this may change, or be configurable, in the future.</li>
  *
- * <li>X-value interpolation is predicated on x-values representing some form of
+ * <li>X-value interpolation is predicated on y-values representing some form of
  * cumulative distribution function, either increasing or decreasing
  * (complementary), and must be specified as such. X-values are assumed to be
  * increasing by default.</li>
@@ -46,20 +48,10 @@ import java.util.List;
  * all interpolation operations in this class. These two methods are point-order
  * agnostic.
  *
- * TODO example; explain array swapping techniques for x-interpolation
- *
  * @author U.S. Geological Survey
  */
 public abstract class Interpolator {
 
-  /*
-   * Developer notes:
-   *
-   * -------------------------------------------------------------------------
-   * Perhaps add extrapolation constraint (on/off) for y value interpolation
-   * -------------------------------------------------------------------------
-   */
-
   private Interpolator() {}
 
   /**
@@ -107,11 +99,11 @@ public abstract class Interpolator {
    * Return an interpolated x-value corresponding to the supplied y-value in the
    * supplied xy-sequence.
    *
-   * @param xys an xy-sequence
+   * @param xy an xy-sequence
    * @param y value at which to find x
    * @return an interpolated x-value
    */
-  public abstract double findX(XySequence xys, double y);
+  public abstract double findX(XySequence xy, double y);
 
   /**
    * Return an interpolated or extrapolated y-value corresponding to the
@@ -157,11 +149,11 @@ public abstract class Interpolator {
    * Return an interpolated or extrapolated y-value corresponding to the
    * supplied x-value in the supplied xy-sequence.
    *
-   * @param xys an xy-sequence
+   * @param xy an xy-sequence
    * @param x value at which to find y
    * @return an interpolated y-value
    */
-  public abstract double findY(XySequence xys, double x);
+  public abstract double findY(XySequence xy, double x);
 
   /**
    * Return interpolated or extrapolated y-values using the supplied x- and
@@ -189,26 +181,31 @@ public abstract class Interpolator {
    * Return interpolated or extrapolated y-values using the supplied x- and
    * y-value arrays.
    *
-   * @param xys an xy-sequence
+   * @param xy an xy-sequence
    * @param x values at which to find y-values
    * @return interpolated y-values
    */
-  public abstract double[] findY(XySequence xys, double[] x);
+  public abstract double[] findY(XySequence xy, double[] x);
 
+  /** Create a new builder instance. */
   public static Builder builder() {
     return new Builder();
   }
 
+  /**
+   * An interpolator builder.
+   */
   public static final class Builder {
 
     private Builder() {}
 
     boolean logx = false;
     boolean logy = false;
-    boolean xIncreasing = true;
+    boolean yIncreasing = true;
 
     /**
-     * Indicate that interpolation should be performed in y-value log space.
+     * Indicate that interpolation should be performed in {@code log(x)} value
+     * space.
      */
     public Builder logx() {
       this.logx = true;
@@ -216,7 +213,8 @@ public abstract class Interpolator {
     }
 
     /**
-     * Indicate that interpolation should be performed in y-value log space.
+     * Indicate that interpolation should be performed in {@code log(y)} value
+     * space.
      */
     public Builder logy() {
       this.logy = true;
@@ -224,12 +222,13 @@ public abstract class Interpolator {
     }
 
     /**
-     * Indicate if the x-values to be interpolated are decreasing. In the
-     * absence of calling this method, x-value are assumed to monotonically
-     * increasing. This setting has no effect on y-value interpolation.
+     * Indicate if the y-values to be interpolated decrease monotonically. In
+     * the absence of calling this method, both x- and y-values are assumed to
+     * monotonically increase. This setting has no effect on y-value
+     * interpolation.
      */
-    public Builder decreasingX() {
-      this.xIncreasing = false;
+    public Builder decreasingY() {
+      this.yIncreasing = false;
       return this;
     }
 
@@ -237,18 +236,17 @@ public abstract class Interpolator {
      * Return a newly created {@code Interpolator}.
      */
     public Interpolator build() {
-      return new RegularInterpolator(logx, logy, xIncreasing);
+      return new RegularInterpolator(logx, logy, yIncreasing);
     }
-
   }
 
   private static final class RegularInterpolator extends Interpolator {
 
     private final InterpolateFn yFunction;
     private final InterpolateFn xFunction;
-    private final boolean xIncreasing;
+    private final boolean yIncreasing;
 
-    private RegularInterpolator(boolean logx, boolean logy, boolean xIncreasing) {
+    private RegularInterpolator(boolean logx, boolean logy, boolean yIncreasing) {
       if (logx && logy) {
         xFunction = new XFn_LogX_LogY();
         yFunction = new YFn_LogX_LogY();
@@ -262,12 +260,12 @@ public abstract class Interpolator {
         xFunction = new XFn();
         yFunction = new YFn();
       }
-      this.xIncreasing = xIncreasing;
+      this.yIncreasing = yIncreasing;
     }
 
     @Override
     public double findX(double[] xs, double[] ys, double y) {
-      int i = linearIndex(ys, y, xIncreasing);
+      int i = linearIndex(ys, y, yIncreasing);
       if (i == -1) {
         return 0;
       }
@@ -276,7 +274,7 @@ public abstract class Interpolator {
 
     @Override
     public double findX(List<Double> xs, List<Double> ys, double y) {
-      int i = linearIndex(ys, y, xIncreasing);
+      int i = linearIndex(ys, y, yIncreasing);
       if (i == -1) {
         return 0;
       }
@@ -284,10 +282,10 @@ public abstract class Interpolator {
     }
 
     @Override
-    public double findX(XySequence xys, double y) {
+    public double findX(XySequence xy, double y) {
       // safe covariant cast
-      ArrayXySequence ixys = (ArrayXySequence) xys;
-      return findX(ixys.xs, ixys.ys, y);
+      ArrayXySequence ixy = (ArrayXySequence) xy;
+      return findX(ixy.xs, ixy.ys, y);
     }
 
     @Override
@@ -303,10 +301,10 @@ public abstract class Interpolator {
     }
 
     @Override
-    public double findY(XySequence xys, double x) {
+    public double findY(XySequence xy, double x) {
       // safe covariant cast
-      ArrayXySequence ixys = (ArrayXySequence) xys;
-      return findY(ixys.xs, ixys.ys, x);
+      ArrayXySequence ixy = (ArrayXySequence) xy;
+      return findY(ixy.xs, ixy.ys, x);
     }
 
     @Override
@@ -328,10 +326,10 @@ public abstract class Interpolator {
     }
 
     @Override
-    public double[] findY(XySequence xys, double[] x) {
+    public double[] findY(XySequence xy, double[] x) {
       // safe covariant cast
-      ArrayXySequence ixys = (ArrayXySequence) xys;
-      return findY(ixys.xs, ixys.ys, x);
+      ArrayXySequence ixy = (ArrayXySequence) xy;
+      return findY(ixy.xs, ixy.ys, x);
     }
   }
 
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/data/InterpolatorTest.java b/src/test/java/gov/usgs/earthquake/nshmp/data/InterpolatorTest.java
deleted file mode 100644
index d673b21c..00000000
--- a/src/test/java/gov/usgs/earthquake/nshmp/data/InterpolatorTest.java
+++ /dev/null
@@ -1,185 +0,0 @@
-package gov.usgs.earthquake.nshmp.data;
-
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.DoubleStream;
-
-import org.junit.jupiter.api.Test;
-
-class InterpolatorTest {
-
-  static final double[] X = { 0.01, 0.1, 0.3, 0.5, 0.7, 0.9, 1.0 };
-  static final double[] Y = { 1, 0.9, 0.7, 0.5, 0.3, 0.1, 1e-2 };
-
-  static final List<Double> X_LIST;
-  static final List<Double> Y_LIST;
-
-  static final XySequence XY_SEQUENCE;
-
-  static final double[] X_TARGET = { 0.01, 0.11, 0.4, 0.5, 0.8, 0.99, 1 };
-  static final int[] LOWER_BIN_INDEX = { 0, 1, 2, 3, 4, 5, 5 };
-
-  // Expected results calculated in Excel
-  static final double[] Y_TARGET_LINX_LINY = { 1.00, 0.89, 0.60, 0.50, 0.20, 0.019, 0.01 };
-  static final double[] Y_TARGET_LOGX_LINY = {
-      1.00, 0.882648987129, 0.587365841075, 0.50, 0.193733604125, 0.018585096809, 0.01 };
-  static final double[] Y_TARGET_LINX_LOGY = {
-      1.00, 0.888761607855, 0.591607978310, 0.50, 0.173205080757, 0.012589254118, 0.01 };
-  static final double[] Y_TARGET_LOGX_LOGY = {
-      1.00, 0.880589847272, 0.579165919197, 0.50, 0.167344511862, 0.012456325964, 0.01 };
-
-  static final Interpolator LINEAR_LINEAR_INTERPOLATOR = Interpolator.builder()
-      .build();
-  static final Interpolator LINEAR_LINEAR_DOWN_INTERPOLATOR = Interpolator.builder()
-      .decreasingX()
-      .build();
-  static final Interpolator LINEAR_LOG_INTERPOLATOR = Interpolator.builder()
-      .logy()
-      .build();
-  static final Interpolator LOG_LINEAR_INTERPOLATOR = Interpolator.builder()
-      .logx()
-      .build();
-  static final Interpolator LOG_LOG_INTERPOLATOR = Interpolator.builder()
-      .logx()
-      .logy()
-      .build();
-
-  static final double TOL = 1e-10;
-
-  static {
-    assert (X.length == Y.length);
-    assert (X_TARGET.length == LOWER_BIN_INDEX.length);
-    assert (X_TARGET.length == Y_TARGET_LINX_LINY.length);
-    assert (X_TARGET.length == Y_TARGET_LOGX_LINY.length);
-    assert (X_TARGET.length == Y_TARGET_LINX_LOGY.length);
-    assert (X_TARGET.length == Y_TARGET_LOGX_LOGY.length);
-
-    X_LIST = DoubleStream.of(X).boxed().collect(Collectors.toCollection(ArrayList::new));
-    Y_LIST = DoubleStream.of(Y).boxed().collect(Collectors.toCollection(ArrayList::new));
-    XY_SEQUENCE = XySequence.create(X, Y);
-  }
-
-  @Test
-  final void testFindX_DoubleDoubleDoubleDoubleDouble() {
-    for (int i = 0; i < X_TARGET.length; i++) {
-      int j = LOWER_BIN_INDEX[i];
-      int k = j + 1;
-      double actualX = Interpolator.findX(X[j], Y[j], X[k], Y[k], Y_TARGET_LINX_LINY[i]);
-      assertEquals(X_TARGET[i], actualX, TOL);
-    }
-  }
-
-  @Test
-  final void testFindY_DoubleDoubleDoubleDoubleDouble() {
-    for (int i = 0; i < X_TARGET.length; i++) {
-      int j = LOWER_BIN_INDEX[i];
-      int k = j + 1;
-      double actualY = Interpolator.findY(X[j], Y[j], X[k], Y[k], X_TARGET[i]);
-      assertEquals(Y_TARGET_LINX_LINY[i], actualY, TOL);
-    }
-  }
-
-  @Test
-  final void testFindX_DoubleArrayDoubleArrayDouble() {
-    for (int i = 0; i < X_TARGET.length; i++) {
-      // need to use an interpolator with decreasingX() since we're using the
-      // same data and swapping X & Y
-      double actualX = LINEAR_LINEAR_DOWN_INTERPOLATOR.findX(X, Y, Y_TARGET_LINX_LINY[i]);
-      assertEquals(X_TARGET[i], actualX, TOL);
-    }
-  }
-
-  @Test
-  final void testFindX_ListOfDoubleListOfDoubleDouble() {
-    for (int i = 0; i < X_TARGET.length; i++) {
-      // need to use an interpolator with decreasingX() since we're using the
-      // same data and swapping X & Y
-      double actualX = LINEAR_LINEAR_DOWN_INTERPOLATOR.findX(X_LIST, Y_LIST, Y_TARGET_LINX_LINY[i]);
-      assertEquals(X_TARGET[i], actualX, TOL);
-    }
-  }
-
-  @Test
-  final void testFindX_XySequenceDouble() {
-    for (int i = 0; i < X_TARGET.length; i++) {
-      // need to use an interpolator with decreasingX() since we're using the
-      // same data and swapping X & Y
-      double actualX = LINEAR_LINEAR_DOWN_INTERPOLATOR.findX(XY_SEQUENCE, Y_TARGET_LINX_LINY[i]);
-      assertEquals(X_TARGET[i], actualX, TOL);
-    }
-  }
-
-  @Test
-  final void testFindY_DoubleArrayDoubleArrayDouble() {
-    for (int i = 0; i < X_TARGET.length; i++) {
-      double actualY = LINEAR_LINEAR_INTERPOLATOR.findY(X, Y, X_TARGET[i]);
-      assertEquals(Y_TARGET_LINX_LINY[i], actualY, TOL);
-    }
-  }
-
-  @Test
-  final void testFindY_ListOfDoubleListOfDoubleDouble() {
-    for (int i = 0; i < X_TARGET.length; i++) {
-      double actualY = LINEAR_LINEAR_INTERPOLATOR.findY(X_LIST, Y_LIST, X_TARGET[i]);
-      assertEquals(Y_TARGET_LINX_LINY[i], actualY, TOL);
-    }
-  }
-
-  @Test
-  final void testFindY_XySequenceDouble() {
-    for (int i = 0; i < X_TARGET.length; i++) {
-      double actualY = LINEAR_LINEAR_INTERPOLATOR.findY(XY_SEQUENCE, X_TARGET[i]);
-      assertEquals(Y_TARGET_LINX_LINY[i], actualY, TOL);
-    }
-  }
-
-  @Test
-  final void testFindY_DoubleArrayDoubleArrayDoubleArray() {
-    double[] actual = LINEAR_LINEAR_INTERPOLATOR.findY(X, Y, X_TARGET);
-    assertArrayEquals(Y_TARGET_LINX_LINY, actual, TOL);
-
-    actual = LOG_LINEAR_INTERPOLATOR.findY(X, Y, X_TARGET);
-    assertArrayEquals(Y_TARGET_LOGX_LINY, actual, TOL);
-
-    actual = LINEAR_LOG_INTERPOLATOR.findY(X, Y, X_TARGET);
-    assertArrayEquals(Y_TARGET_LINX_LOGY, actual, TOL);
-
-    actual = LOG_LOG_INTERPOLATOR.findY(X, Y, X_TARGET);
-    assertArrayEquals(Y_TARGET_LOGX_LOGY, actual, TOL);
-  }
-
-  @Test
-  final void testFindY_ListOfDoubleListOfDoubleDoubleArray() {
-    double[] actual = LINEAR_LINEAR_INTERPOLATOR.findY(X_LIST, Y_LIST, X_TARGET);
-    assertArrayEquals(Y_TARGET_LINX_LINY, actual, TOL);
-
-    actual = LOG_LINEAR_INTERPOLATOR.findY(X_LIST, Y_LIST, X_TARGET);
-    assertArrayEquals(Y_TARGET_LOGX_LINY, actual, TOL);
-
-    actual = LINEAR_LOG_INTERPOLATOR.findY(X_LIST, Y_LIST, X_TARGET);
-    assertArrayEquals(Y_TARGET_LINX_LOGY, actual, TOL);
-
-    actual = LOG_LOG_INTERPOLATOR.findY(X_LIST, Y_LIST, X_TARGET);
-    assertArrayEquals(Y_TARGET_LOGX_LOGY, actual, TOL);
-  }
-
-  @Test
-  final void testFindY_XySequenceDoubleArray() {
-    double[] actual = LINEAR_LINEAR_INTERPOLATOR.findY(XY_SEQUENCE, X_TARGET);
-    assertArrayEquals(Y_TARGET_LINX_LINY, actual, TOL);
-
-    actual = LOG_LINEAR_INTERPOLATOR.findY(XY_SEQUENCE, X_TARGET);
-    assertArrayEquals(Y_TARGET_LOGX_LINY, actual, TOL);
-
-    actual = LINEAR_LOG_INTERPOLATOR.findY(XY_SEQUENCE, X_TARGET);
-    assertArrayEquals(Y_TARGET_LINX_LOGY, actual, TOL);
-
-    actual = LOG_LOG_INTERPOLATOR.findY(XY_SEQUENCE, X_TARGET);
-    assertArrayEquals(Y_TARGET_LOGX_LOGY, actual, TOL);
-  }
-
-}
diff --git a/src/test/java/gov/usgs/earthquake/nshmp/data/InterpolatorTests.java b/src/test/java/gov/usgs/earthquake/nshmp/data/InterpolatorTests.java
index 33c2fe0b..2df2af71 100644
--- a/src/test/java/gov/usgs/earthquake/nshmp/data/InterpolatorTests.java
+++ b/src/test/java/gov/usgs/earthquake/nshmp/data/InterpolatorTests.java
@@ -1,22 +1,246 @@
 package gov.usgs.earthquake.nshmp.data;
 
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.DoubleStream;
+
+import org.junit.jupiter.api.Test;
+
+import com.google.common.primitives.Doubles;
+
 class InterpolatorTests {
 
-  /*
-   * Developer notes:
-   *
-   * How are single valued XySequences handled; should empty XySequences be
-   * allowed, probably not; singletons should be however; so how would this
-   * behave in interpolator if extrapolation is allowed for y-interpolation;
-   * answer: singletons shouldn't be allowed as arguments; it's just simpler
-   *
-   * add checkArgument(xys.size() > 1), test with XySeq.size = 1; add to docs
-   */
-
-  // @Test
-  // public void test() {
-  //
-  // fail("Not yet implemented");
-  // }
+  static final double[] X = { 0.01, 0.1, 0.3, 0.5, 0.7, 0.9, 1.0 };
+  static final double[] Y = { 1, 0.9, 0.7, 0.5, 0.3, 0.1, 1e-2 };
+
+  static final List<Double> X_LIST;
+  static final List<Double> Y_LIST;
+
+  static final XySequence XY_SEQUENCE;
+
+  static final double[] X_TARGET = { 0.01, 0.11, 0.4, 0.5, 0.8, 0.99, 1 };
+  static final int[] LOWER_BIN_INDEX = { 0, 1, 2, 3, 4, 5, 5 };
+
+  // Expected results calculated in Excel
+  static final double[] Y_TARGET_LINX_LINY = { 1.00, 0.89, 0.60, 0.50, 0.20, 0.019, 0.01 };
+  static final double[] Y_TARGET_LOGX_LINY = {
+      1.00, 0.882648987129, 0.587365841075, 0.50, 0.193733604125, 0.018585096809, 0.01 };
+  static final double[] Y_TARGET_LINX_LOGY = {
+      1.00, 0.888761607855, 0.591607978310, 0.50, 0.173205080757, 0.012589254118, 0.01 };
+  static final double[] Y_TARGET_LOGX_LOGY = {
+      1.00, 0.880589847272, 0.579165919197, 0.50, 0.167344511862, 0.012456325964, 0.01 };
+
+  static final Interpolator LINEAR_LINEAR_INTERPOLATOR = Interpolator.builder()
+      .build();
+  static final Interpolator LINEAR_LINEAR_DOWN_INTERPOLATOR = Interpolator.builder()
+      .decreasingY()
+      .build();
+  static final Interpolator LINEAR_LOG_INTERPOLATOR = Interpolator.builder()
+      .logy()
+      .build();
+  static final Interpolator LOG_LINEAR_INTERPOLATOR = Interpolator.builder()
+      .logx()
+      .build();
+  static final Interpolator LOG_LOG_INTERPOLATOR = Interpolator.builder()
+      .logx()
+      .logy()
+      .build();
+
+  static final double TOL = 1e-10;
+
+  static {
+    assert (X.length == Y.length);
+    assert (X_TARGET.length == LOWER_BIN_INDEX.length);
+    assert (X_TARGET.length == Y_TARGET_LINX_LINY.length);
+    assert (X_TARGET.length == Y_TARGET_LOGX_LINY.length);
+    assert (X_TARGET.length == Y_TARGET_LINX_LOGY.length);
+    assert (X_TARGET.length == Y_TARGET_LOGX_LOGY.length);
+
+    X_LIST = DoubleStream.of(X).boxed().collect(Collectors.toCollection(ArrayList::new));
+    Y_LIST = DoubleStream.of(Y).boxed().collect(Collectors.toCollection(ArrayList::new));
+    XY_SEQUENCE = XySequence.create(X, Y);
+  }
+
+  @Test
+  final void testFindX_DoubleDoubleDoubleDoubleDouble() {
+    for (int i = 0; i < X_TARGET.length; i++) {
+      int j = LOWER_BIN_INDEX[i];
+      int k = j + 1;
+      double actualX = Interpolator.findX(X[j], Y[j], X[k], Y[k], Y_TARGET_LINX_LINY[i]);
+      assertEquals(X_TARGET[i], actualX, TOL);
+    }
+  }
+
+  @Test
+  final void testFindY_DoubleDoubleDoubleDoubleDouble() {
+    for (int i = 0; i < X_TARGET.length; i++) {
+      int j = LOWER_BIN_INDEX[i];
+      int k = j + 1;
+      double actualY = Interpolator.findY(X[j], Y[j], X[k], Y[k], X_TARGET[i]);
+      assertEquals(Y_TARGET_LINX_LINY[i], actualY, TOL);
+    }
+  }
+
+  @Test
+  final void testFindX_DoubleArrayDoubleArrayDouble() {
+    for (int i = 0; i < X_TARGET.length; i++) {
+      // need to use an interpolator with decreasingY() since we're using the
+      // same data and swapping X & Y
+      double actualX = LINEAR_LINEAR_DOWN_INTERPOLATOR.findX(X, Y, Y_TARGET_LINX_LINY[i]);
+      assertEquals(X_TARGET[i], actualX, TOL);
+    }
+  }
+
+  @Test
+  final void testFindX_ListOfDoubleListOfDoubleDouble() {
+    for (int i = 0; i < X_TARGET.length; i++) {
+      // need to use an interpolator with decreasingY() since we're using the
+      // same data and swapping X & Y
+      double actualX = LINEAR_LINEAR_DOWN_INTERPOLATOR.findX(X_LIST, Y_LIST, Y_TARGET_LINX_LINY[i]);
+      assertEquals(X_TARGET[i], actualX, TOL);
+    }
+  }
+
+  @Test
+  final void testFindX_XySequenceDouble() {
+    for (int i = 0; i < X_TARGET.length; i++) {
+      // need to use an interpolator with decreasingY() since we're using the
+      // same data and swapping X & Y
+      double actualX = LINEAR_LINEAR_DOWN_INTERPOLATOR.findX(XY_SEQUENCE, Y_TARGET_LINX_LINY[i]);
+      assertEquals(X_TARGET[i], actualX, TOL);
+    }
+  }
+
+  @Test
+  final void testFindY_DoubleArrayDoubleArrayDouble() {
+    for (int i = 0; i < X_TARGET.length; i++) {
+      double actualY = LINEAR_LINEAR_INTERPOLATOR.findY(X, Y, X_TARGET[i]);
+      assertEquals(Y_TARGET_LINX_LINY[i], actualY, TOL);
+    }
+  }
+
+  @Test
+  final void testFindY_ListOfDoubleListOfDoubleDouble() {
+    for (int i = 0; i < X_TARGET.length; i++) {
+      double actualY = LINEAR_LINEAR_INTERPOLATOR.findY(X_LIST, Y_LIST, X_TARGET[i]);
+      assertEquals(Y_TARGET_LINX_LINY[i], actualY, TOL);
+    }
+  }
+
+  @Test
+  final void testFindY_XySequenceDouble() {
+    for (int i = 0; i < X_TARGET.length; i++) {
+      double actualY = LINEAR_LINEAR_INTERPOLATOR.findY(XY_SEQUENCE, X_TARGET[i]);
+      assertEquals(Y_TARGET_LINX_LINY[i], actualY, TOL);
+    }
+  }
+
+  @Test
+  final void testFindXin_DoubleArrayDoubleArrayDoubleArray() {
+
+    double[] xs = { 1, 2, 3 };
+    double[] ys = { 0.2, 0.5, 0.8 };
+
+    /* Missing coverage for findX functions (XFn) */
+
+    double expected = 1.259921049894;
+    double actual = LOG_LINEAR_INTERPOLATOR.findX(xs, ys, 0.3);
+    assertEquals(expected, actual, TOL);
+
+    expected = 1.442507049349;
+    actual = LINEAR_LOG_INTERPOLATOR.findX(xs, ys, 0.3);
+    assertEquals(expected, actual, TOL);
+
+    expected = 1.358963821816;
+    actual = LOG_LOG_INTERPOLATOR.findX(xs, ys, 0.3);
+    assertEquals(expected, actual, TOL);
+  }
+
+  @Test
+  final void testFindY_DoubleArrayDoubleArrayDoubleArray() {
+    double[] actual = LINEAR_LINEAR_INTERPOLATOR.findY(X, Y, X_TARGET);
+    assertArrayEquals(Y_TARGET_LINX_LINY, actual, TOL);
+
+    actual = LOG_LINEAR_INTERPOLATOR.findY(X, Y, X_TARGET);
+    assertArrayEquals(Y_TARGET_LOGX_LINY, actual, TOL);
+
+    actual = LINEAR_LOG_INTERPOLATOR.findY(X, Y, X_TARGET);
+    assertArrayEquals(Y_TARGET_LINX_LOGY, actual, TOL);
+
+    actual = LOG_LOG_INTERPOLATOR.findY(X, Y, X_TARGET);
+    assertArrayEquals(Y_TARGET_LOGX_LOGY, actual, TOL);
+  }
+
+  @Test
+  final void testFindY_ListOfDoubleListOfDoubleDoubleArray() {
+    double[] actual = LINEAR_LINEAR_INTERPOLATOR.findY(X_LIST, Y_LIST, X_TARGET);
+    assertArrayEquals(Y_TARGET_LINX_LINY, actual, TOL);
+
+    actual = LOG_LINEAR_INTERPOLATOR.findY(X_LIST, Y_LIST, X_TARGET);
+    assertArrayEquals(Y_TARGET_LOGX_LINY, actual, TOL);
+
+    actual = LINEAR_LOG_INTERPOLATOR.findY(X_LIST, Y_LIST, X_TARGET);
+    assertArrayEquals(Y_TARGET_LINX_LOGY, actual, TOL);
+
+    actual = LOG_LOG_INTERPOLATOR.findY(X_LIST, Y_LIST, X_TARGET);
+    assertArrayEquals(Y_TARGET_LOGX_LOGY, actual, TOL);
+  }
+
+  @Test
+  final void testFindY_XySequenceDoubleArray() {
+    double[] actual = LINEAR_LINEAR_INTERPOLATOR.findY(XY_SEQUENCE, X_TARGET);
+    assertArrayEquals(Y_TARGET_LINX_LINY, actual, TOL);
+
+    actual = LOG_LINEAR_INTERPOLATOR.findY(XY_SEQUENCE, X_TARGET);
+    assertArrayEquals(Y_TARGET_LOGX_LINY, actual, TOL);
+
+    actual = LINEAR_LOG_INTERPOLATOR.findY(XY_SEQUENCE, X_TARGET);
+    assertArrayEquals(Y_TARGET_LINX_LOGY, actual, TOL);
+
+    actual = LOG_LOG_INTERPOLATOR.findY(XY_SEQUENCE, X_TARGET);
+    assertArrayEquals(Y_TARGET_LOGX_LOGY, actual, TOL);
+  }
+
+  /* Errors and Edge Cases */
+  @Test
+  final void testErrorsAndEdgeCases() {
+
+    double[] xs = { 2, 3, 4 };
+    double[] ys = { 0.2, 0.5, 0.8 };
+    Interpolator interp = Interpolator.builder().build();
+
+    /* linear index */
+
+    double yBelow = 0.1;
+    double yAbove = 0.9;
+
+    // out of range below
+    double actual = interp.findX(xs, ys, yBelow);
+    assertEquals(0.0, actual, 0.0);
+    actual = interp.findX(
+        Doubles.asList(xs),
+        Doubles.asList(ys), yBelow);
+    assertEquals(0.0, actual, 0.0);
+
+    // out of range above
+    actual = interp.findX(xs, ys, yAbove);
+    assertEquals(0.0, actual, 0.0);
+    actual = interp.findX(
+        Doubles.asList(xs),
+        Doubles.asList(ys), yAbove);
+    assertEquals(0.0, actual, 0.0);
+
+    /* binary index */
+
+    // out of range below
+    double xBelow = 1.0;
+    actual = interp.findY(xs, ys, xBelow);
+    assertEquals(-0.1, actual, TOL);
+
+  }
 
 }
-- 
GitLab