View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.math4.legacy.analysis.interpolation;
18  
19  import java.util.Arrays;
20  
21  import org.apache.commons.math4.legacy.analysis.BivariateFunction;
22  import org.apache.commons.math4.legacy.analysis.polynomials.PolynomialSplineFunction;
23  import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
24  import org.apache.commons.math4.legacy.exception.InsufficientDataException;
25  import org.apache.commons.math4.legacy.exception.NoDataException;
26  import org.apache.commons.math4.legacy.exception.NonMonotonicSequenceException;
27  import org.apache.commons.math4.legacy.exception.NullArgumentException;
28  import org.apache.commons.math4.legacy.exception.OutOfRangeException;
29  import org.apache.commons.math4.legacy.core.MathArrays;
30  
31  /**
32   * Function that implements the
33   * <a href="http://www.paulinternet.nl/?page=bicubic">bicubic spline</a>
34   * interpolation.
35   * This implementation currently uses {@link AkimaSplineInterpolator} as the
36   * underlying one-dimensional interpolator, which requires 5 sample points;
37   * insufficient data will raise an exception when the
38   * {@link #value(double,double) value} method is called.
39   *
40   * @since 3.4
41   */
42  public class PiecewiseBicubicSplineInterpolatingFunction
43      implements BivariateFunction {
44      /** The minimum number of points that are needed to compute the function. */
45      private static final int MIN_NUM_POINTS = 5;
46      /** Samples x-coordinates. */
47      private final double[] xval;
48      /** Samples y-coordinates. */
49      private final double[] yval;
50      /** Set of cubic splines patching the whole data grid. */
51      private final double[][] fval;
52  
53      /**
54       * @param x Sample values of the x-coordinate, in increasing order.
55       * @param y Sample values of the y-coordinate, in increasing order.
56       * @param f Values of the function on every grid point. the expected number
57       *        of elements.
58       * @throws NonMonotonicSequenceException if {@code x} or {@code y} are not
59       *         strictly increasing.
60       * @throws NullArgumentException if any of the arguments are null
61       * @throws NoDataException if any of the arrays has zero length.
62       * @throws DimensionMismatchException if the length of x and y don't match the row, column
63       *         height of f
64       */
65      public PiecewiseBicubicSplineInterpolatingFunction(double[] x,
66                                                         double[] y,
67                                                         double[][] f)
68          throws DimensionMismatchException,
69                 NullArgumentException,
70                 NoDataException,
71                 NonMonotonicSequenceException {
72          if (x == null ||
73              y == null ||
74              f == null ||
75              f[0] == null) {
76              throw new NullArgumentException();
77          }
78  
79          final int xLen = x.length;
80          final int yLen = y.length;
81  
82          if (xLen == 0 ||
83              yLen == 0 ||
84              f.length == 0 ||
85              f[0].length == 0) {
86              throw new NoDataException();
87          }
88  
89          if (xLen < MIN_NUM_POINTS ||
90              yLen < MIN_NUM_POINTS ||
91              f.length < MIN_NUM_POINTS ||
92              f[0].length < MIN_NUM_POINTS) {
93              throw new InsufficientDataException();
94          }
95  
96          if (xLen != f.length) {
97              throw new DimensionMismatchException(xLen, f.length);
98          }
99  
100         if (yLen != f[0].length) {
101             throw new DimensionMismatchException(yLen, f[0].length);
102         }
103 
104         MathArrays.checkOrder(x);
105         MathArrays.checkOrder(y);
106 
107         xval = x.clone();
108         yval = y.clone();
109         fval = f.clone();
110     }
111 
112     /**
113      * {@inheritDoc}
114      */
115     @Override
116     public double value(double x,
117                         double y)
118         throws OutOfRangeException {
119         final AkimaSplineInterpolator interpolator = new AkimaSplineInterpolator();
120         final int offset = 2;
121         final int count = offset + 3;
122         final int i = searchIndex(x, xval, offset, count);
123         final int j = searchIndex(y, yval, offset, count);
124 
125         final double[] xArray = new double[count];
126         final double[] yArray = new double[count];
127         final double[] zArray = new double[count];
128         final double[] interpArray = new double[count];
129 
130         for (int index = 0; index < count; index++) {
131             xArray[index] = xval[i + index];
132             yArray[index] = yval[j + index];
133         }
134 
135         for (int zIndex = 0; zIndex < count; zIndex++) {
136             for (int index = 0; index < count; index++) {
137                 zArray[index] = fval[i + index][j + zIndex];
138             }
139             final PolynomialSplineFunction spline = interpolator.interpolate(xArray, zArray);
140             interpArray[zIndex] = spline.value(x);
141         }
142 
143         final PolynomialSplineFunction spline = interpolator.interpolate(yArray, interpArray);
144 
145         return spline.value(y);
146     }
147 
148     /**
149      * Indicates whether a point is within the interpolation range.
150      *
151      * @param x First coordinate.
152      * @param y Second coordinate.
153      * @return {@code true} if (x, y) is a valid point.
154      * @since 3.3
155      */
156     public boolean isValidPoint(double x,
157                                 double y) {
158         return !(x < xval[0] ||
159             x > xval[xval.length - 1] ||
160             y < yval[0] ||
161             y > yval[yval.length - 1]);
162     }
163 
164     /**
165      * @param c Coordinate.
166      * @param val Coordinate samples.
167      * @param offset how far back from found value to offset for querying
168      * @param count total number of elements forward from beginning that will be
169      *        queried
170      * @return the index in {@code val} corresponding to the interval containing
171      *         {@code c}.
172      * @throws OutOfRangeException if {@code c} is out of the range defined by
173      *         the boundary values of {@code val}.
174      */
175     private int searchIndex(double c,
176                             double[] val,
177                             int offset,
178                             int count) {
179         int r = Arrays.binarySearch(val, c);
180 
181         if (r == -1 || r == -val.length - 1) {
182             throw new OutOfRangeException(c, val[0], val[val.length - 1]);
183         }
184 
185         if (r < 0) {
186             // "c" in within an interpolation sub-interval, which returns
187             // negative
188             // need to remove the negative sign for consistency
189             r = -r - offset - 1;
190         } else {
191             r -= offset;
192         }
193 
194         if (r < 0) {
195             r = 0;
196         }
197 
198         if ((r + count) >= val.length) {
199             // "c" is the last sample of the range: Return the index
200             // of the sample at the lower end of the last sub-interval.
201             r = val.length - count;
202         }
203 
204         return r;
205     }
206 }