Optimise bicubic polynomial

Remove computation of products and sums where one factor is zero.

Avoid computation of products where one factor is one.

Use static functions where applicable.
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/interpolation/BicubicInterpolatingFunction.java b/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/interpolation/BicubicInterpolatingFunction.java
index 2543f0b..11a02a2 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/interpolation/BicubicInterpolatingFunction.java
+++ b/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/analysis/interpolation/BicubicInterpolatingFunction.java
@@ -295,7 +295,7 @@
      * @throws OutOfRangeException if {@code c} is out of the
      * range defined by the boundary values of {@code val}.
      */
-    private int searchIndex(double c, double[] val) {
+    private static int searchIndex(double c, double[] val) {
         final int r = Arrays.binarySearch(val, c);
 
         if (r == -1 ||
@@ -348,7 +348,7 @@
      * values.
      * @return the spline coefficients.
      */
-    private double[] computeSplineCoefficients(double[] beta) {
+    private static double[] computeSplineCoefficients(double[] beta) {
         final double[] a = new double[NUM_COEFF];
 
         for (int i = 0; i < NUM_COEFF; i++) {
@@ -433,7 +433,7 @@
                 final double y3 = y2 * y;
                 final double[] pY = {1, y, y2, y3};
 
-                return apply(pX, pY, aX) / xR;
+                return apply(pX, 1, pY, 0, aX) / xR;
             };
             partialDerivativeY = (double x, double y) -> {
                 final double x2 = x * x;
@@ -443,7 +443,7 @@
                 final double y2 = y * y;
                 final double[] pY = {0, 1, y, y2};
 
-                return apply(pX, pY, aY) / yR;
+                return apply(pX, 0, pY, 1, aY) / yR;
             };
             partialDerivativeXX = (double x, double y) -> {
                 final double[] pX = {0, 0, 1, x};
@@ -452,7 +452,7 @@
                 final double y3 = y2 * y;
                 final double[] pY = {1, y, y2, y3};
 
-                return apply(pX, pY, aXX) / (xR * xR);
+                return apply(pX, 2, pY, 0, aXX) / (xR * xR);
             };
             partialDerivativeYY = (double x, double y) -> {
                 final double x2 = x * x;
@@ -461,7 +461,7 @@
 
                 final double[] pY = {0, 0, 1, y};
 
-                return apply(pX, pY, aYY) / (yR * yR);
+                return apply(pX, 0, pY, 2, aYY) / (yR * yR);
             };
             partialDerivativeXY = (double x, double y) -> {
                 final double x2 = x * x;
@@ -470,7 +470,7 @@
                 final double y2 = y * y;
                 final double[] pY = {0, 1, y, y2};
 
-                return apply(pX, pY, aXY) / (xR * yR);
+                return apply(pX, 1, pY, 1, aXY) / (xR * yR);
             };
         } else {
             partialDerivativeX = null;
@@ -501,28 +501,52 @@
         final double y3 = y2 * y;
         final double[] pY = {1, y, y2, y3};
 
-        return apply(pX, pY, a);
+        return apply(pX, 0, pY, 0, a);
     }
 
     /**
      * Compute the value of the bicubic polynomial.
      *
+     * <p>Assumes the powers are zero below the provided index, and 1 at the provided
+     * index. This allows skipping some zero products and optimising multiplication
+     * by one.
+     *
      * @param pX Powers of the x-coordinate.
+     * @param i Index of pX[i] == 1
      * @param pY Powers of the y-coordinate.
+     * @param j Index of pX[j] == 1
      * @param coeff Spline coefficients.
      * @return the interpolated value.
      */
-    private double apply(double[] pX, double[] pY, double[][] coeff) {
-        double result = 0;
-        for (int i = 0; i < N; i++) {
-            final double r = Sum.ofProducts(coeff[i], pY).getAsDouble();
+    private static double apply(double[] pX, int i, double[] pY, int j, double[][] coeff) {
+        // assert pX[i] == 1
+        double result = sumOfProducts(coeff[i], pY, j);
+        while (++i < N) {
+            final double r = sumOfProducts(coeff[i], pY, j);
             result += r * pX[i];
         }
-
         return result;
     }
 
     /**
+     * Compute the sum of products starting from the provided index.
+     * Assumes that factor {@code b[j] == 1}.
+     *
+     * @param a Factors.
+     * @param b Factors.
+     * @param j Index to initialise the sum.
+     * @return the double
+     */
+    private static double sumOfProducts(double[] a, double[] b, int j) {
+        // assert b[j] == 1
+        final Sum sum = Sum.of(a[j]);
+        while (++j < N) {
+            sum.addProduct(a[j], b[j]);
+        }
+        return sum.getAsDouble();
+    }
+
+    /**
      * @return the partial derivative wrt {@code x}.
      */
     BivariateFunction partialDerivativeX() {