View Javadoc
1   /*
2    * Copyright (C) 2023 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.numerical.interpolation;
17  
18  /**
19   * Abstract base class used by all interpolation implementations.
20   */
21  public abstract class BaseInterpolator {
22      /**
23       * Length of x and y values to be interpolated.
24       */
25      protected final int n;
26  
27      /**
28       * Length of data to be taken into account on x's and y's.
29       */
30      protected final int mm;
31  
32      protected int cor = 0;
33  
34      private int jsav = 0;
35  
36      private final int dj;
37  
38      /**
39       * X values to be used for interpolation estimation. Must be monotonic (either increasing or
40       * decreasing).
41       */
42      protected final double[] xx;
43  
44      /**
45       * Y values to be used for interpolation estimation.
46       */
47      protected final double[] yy;
48  
49      /**
50       * Constructor.
51       *
52       * @param x x values to interpolate to. Values in x must be monotonic (either increasing or
53       *          decreasing)
54       * @param y y values to interpolate to.
55       * @param m length of x's and y's to take into account. Must be less or equal than x or y
56       *          length.
57       * @param check true to make validations, false otherwise.
58       * @throws IllegalArgumentException if x or y have invalid length or m exceeds length of x or y.
59       */
60      BaseInterpolator(final double[] x, final double[] y, final int m, final boolean check) {
61          n = x.length;
62          mm = m;
63  
64          if (check) {
65              if (n != y.length) {
66                  throw new IllegalArgumentException("mismatched x and y length");
67              }
68              if (n < 2 || mm < 2) {
69                  throw new IllegalArgumentException("x length is too small");
70              }
71              if (mm > n) {
72                  throw new IllegalArgumentException("m exceeds length of x or y");
73              }
74          }
75  
76          xx = x;
77          yy = y;
78  
79          dj = Math.min(1, (int) Math.pow(n, 0.25));
80      }
81  
82      /**
83       * Constructor.
84       *
85       * @param x x values to interpolate to. Values in x must be monotonic (either increasing or
86       *          decreasing)
87       * @param y y values to interpolate to.
88       * @param m length of x's and y's to take into account. Must be less or equal than x or y
89       *          length.
90       * @throws IllegalArgumentException if x or y have invalid length or m exceeds length of x or y.
91       */
92      BaseInterpolator(final double[] x, final double[] y, final int m) {
93          this(x, y, m, true);
94      }
95  
96      /**
97       * Given a value x, returns an interpolated value, using data pointed to by {@link #xx} and
98       * {@link #yy}.
99       *
100      * @param x value to obtain interpolation for.
101      * @return interpolated value.
102      * @throws InterpolationException if interpolation fails.
103      */
104     public double interpolate(final double x) throws InterpolationException {
105         final var jlo = cor != 0 ? hunt(x) : locate(x);
106         return rawinterp(jlo, x);
107     }
108 
109     /**
110      * Actual interpolation method to be implemented by subclasses.
111      *
112      * @param jlo index where value x to be interpolated in located in the array of xx.
113      * @param x value to obtain interpolation for.
114      * @return interpolated value.
115      * @throws InterpolationException if interpolation fails.
116      */
117     public abstract double rawinterp(final int jlo, final double x) throws InterpolationException;
118 
119     /**
120      * Given a value x, returns a value j such that x is (insofar as possible) centered in the
121      * subrange xx[j..j+mm-1], where xx is the stored array. The value in xx must be monotonic,
122      * either increasing or decreasing. The returned value is not less than 0, nor greater than
123      * n - 1.
124      * @param x value to obtain interpolation for.
125      * @return position where value to obtain interpolation lies in the array of {@link #xx}.
126      */
127     @SuppressWarnings("Duplicates")
128     protected int locate(final double x) {
129         int ju;
130         int jm;
131         int jl;
132         // True if ascending order of table, false otherwise.
133         final var ascend = (xx[n - 1] >= xx[0]);
134 
135         // Initialize lower and upper limits.
136         jl = 0;
137         ju = n - 1;
138 
139         while (ju - jl > 1) {
140             // If we are not yet done, compute a midpoint
141             jm = (ju + jl) >> 1;
142             if (x >= xx[jm] == ascend) {
143                 // and replace either the lower limit
144                 jl = jm;
145             } else {
146                 // or the upper limit, as appropriate
147                 ju = jm;
148             }
149 
150             // Repeat until the test condition is satisfied
151         }
152 
153         // Decide whether to use hunt or locate next time
154         cor = Math.abs(jl - jsav) > dj ? 0 : 1;
155         jsav = jl;
156         return Math.max(0, Math.min(n - mm, jl - ((mm - 2) >> 1)));
157     }
158 
159     /**
160      * Given a value x, returns a value j such that x is (insofar as possible) centered in the
161      * subrange xx[j..j+mm-1], where xx is the stored array. The value in xx must be monotonic,
162      * either increasing or decreasing. The returned value is not less than 0, nor greater than
163      * n - 1.
164      * This method starts with a guessed position in the table. It first "hunts" either up or own,
165      * in increments of 1, then 2, then 3, etc. until the desired value is bracketed. It then
166      * bisects in the bracketed interval. At worst, this routine is about a factor of 2 slower than
167      * {@link #locate(double)} (if the hunt phase expands to include the whole table). At best, it
168      * can be a factor of log(n)/log(2) faster than {@link #locate(double)} if the desired point is
169      * usually quite close to the input guess.
170      *
171      * @param x value to obtain interpolation for.
172      * @return position where value to obtain interpolation lies in the array of {@link #xx}.
173      */
174     @SuppressWarnings("Duplicates")
175     protected int hunt(final double x) {
176         var jl = jsav;
177         int jm;
178         int ju;
179         var inc = 1;
180 
181         // Ture if ascending order of table, false otherwise
182         final var ascnd = (xx[n - 1] >= xx[0]);
183         if (jl < 0 || jl > n - 1) {
184             // Input guess not useful. Go immediately to bisection
185             jl = 0;
186             ju = n - 1;
187         } else {
188             if (x >= xx[jl] == ascnd) {
189                 // Hunt up
190                 for (; ; ) {
191                     ju = jl + inc;
192                     if (ju >= n - 1) {
193                         // Off end of table
194                         ju = n - 1;
195                         break;
196                     } else if (x < xx[ju] == ascnd) {
197                         // Found bracket
198                         break;
199                     } else {
200                         // Not done, so double the increment and try again
201                         jl = ju;
202                         inc += inc;
203                     }
204                 }
205             } else {
206                 // Hunt down
207                 ju = jl;
208                 for (; ; ) {
209                     jl = jl - inc;
210                     if (jl <= 0) {
211                         // Off end of table
212                         jl = 0;
213                         break;
214                     } else if (x >= xx[jl] == ascnd) {
215                         // Found bracket
216                         break;
217                     } else {
218                         // Not done, so double the increment and try again
219                         ju = jl;
220                         inc += inc;
221                     }
222                 }
223             }
224         }
225 
226         // Hunt is done, so begin the final bisection phase
227         while (ju - jl > 1) {
228             jm = (ju + jl) >> 1;
229             if (x >= xx[jm] == ascnd) {
230                 jl = jm;
231             } else {
232                 ju = jm;
233             }
234         }
235 
236         // Decide whether to use hunt or locate next time
237         cor = Math.abs(jl - jsav) > dj ? 0 : 1;
238         jsav = jl;
239         return Math.max(0, Math.min(n - mm, jl - ((mm - 2) >> 1)));
240     }
241 }