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 }