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   * Computes cubic spline interpolation.
20   * Accuracy of this interpolator worsens as the polynomial degree to interpolate increases.
21   * Typically, up to degree 3 results are reasonable.
22   */
23  public class CubicSplineInterpolator extends BaseInterpolator {
24  
25      /**
26       * Length of x's and y's to take into account.
27       */
28      private static final int M = 2;
29  
30      private static final double YP1 = 1.e99;
31  
32      private static final double YPN = 1.e99;
33  
34      private final double[] y2;
35  
36      /**
37       * Constructor with x and y vectors, and values of the first derivative at the endpoints.
38       *
39       * @param x x values to interpolate to. Values in x must be monotonic (either increasing or
40       *          decreasing)
41       * @param y y values to interpolate to.
42       * @param yp1 1st derivative at lowest endpoint.
43       * @param ypn 1st derivative at highest endpoint
44       */
45      public CubicSplineInterpolator(final double[] x, final double[] y, final double yp1, final double ypn) {
46          super(x, y, M);
47          y2 = new double[x.length];
48          setY2(x, y, yp1, ypn);
49      }
50  
51      /**
52       * Constructor with x and y vectors and default values for first derivative at the endpoints.
53       *
54       * @param x x values to interpolate to. Values in x must be monotonic (either increasing or
55       *          decreasing)
56       * @param y y values to interpolate to.
57       */
58      public CubicSplineInterpolator(final double[] x, final double[] y) {
59          this(x, y, YP1, YPN);
60      }
61  
62      /**
63       * Actual interpolation method.
64       *
65       * @param jl index where value x to be interpolated in located in the array of xx.
66       * @param x  value to obtain interpolation for.
67       * @return interpolated value.
68       * @throws InterpolationException if interpolation fails.
69       */
70      @Override
71      public double rawinterp(int jl, double x) throws InterpolationException {
72          // Given a value x, and using pointers to data xx and yy, and the stored vector of second
73          // derivatives y2, this routine this cubic spline interpolated value y
74          final var khi = jl + 1;
75  
76          final var h = xx[khi] - xx[jl];
77          if (h == 0.0) {
78              // The xa's must be distinct
79              throw new InterpolationException();
80          }
81  
82          // Cubic spline polynomial is now evaluated
83          final var a = (xx[khi] - x) / h;
84          final var b = (x - xx[jl]) / h;
85          return a * yy[jl] + b * yy[khi] + ((a * a * a - a) * y2[jl] + (b * b * b - b) * y2[khi]) * (h * h) / 6.0;
86      }
87  
88      /**
89       * This method stores an array y2[0..n-1] with second derivatives of the interpolating function
90       * at the tabulated points pointed to by xv, using function values pointed to by yv. If yp1
91       * and/or ypn are equal to 1e99 or larger, the routine is signaled to set the corresponding
92       * boundary condition for a natural spline, with zero second derivative on that boundary;
93       * otherwise, they are the values of the first derivatives at the endpoints.
94       *
95       * @param xv x values to interpolate to. Values in x must be monotonic (either increasing or
96       *           decreasing)
97       * @param yv y values to interpolate to.
98       * @param yp1 1st derivative at lowest endpoint.
99       * @param ypn 1st derivative at highest endpoint
100      */
101     private void setY2(final double[] xv, final double[] yv, final double yp1, final double ypn) {
102         int i;
103         int k;
104         double p;
105         final double qn;
106         double sig;
107         final double un;
108         final var n = y2.length;
109         final var u = new double[n - 1];
110 
111         if (yp1 > 0.99e99) {
112             // The lower boundary condition is set either to be "natural"
113             y2[0] = u[0] = 0.0;
114         } else {
115             // or else to have a specified first derivative
116             y2[0] = -0.5;
117             u[0] = (3.0 / (xv[1] - xv[0])) * ((yv[1] - yv[0]) / (xv[1] - xv[0]) - yp1);
118         }
119 
120         for (i = 1; i < n - 1; i++) {
121             // This is the decomposition loop of the tri-diagonal algorithm. y2 and "u" are used for
122             // temporary storage of the decomposed factors
123             sig = (xv[i] - xv[i - 1]) / (xv[i + 1] - xv[i - 1]);
124             p = sig * y2[i - 1] + 2.0;
125             y2[i] = (sig - 1.0) / p;
126             u[i] = (yv[i + 1] - yv[i]) / (xv[i + 1] - xv[i]) - (yv[i] - yv[i - 1]) / (xv[i] - xv[i - 1]);
127             u[i] = (6.0 * u[i] / (xv[i + 1] - xv[i - 1]) - sig * u[i - 1]) / p;
128         }
129 
130         if (ypn > 0.99e99) {
131             // The upper boundary condition is set either to be "natural"
132             qn = un = 0.0;
133         } else {
134             // or else to have a specified first derivative
135             qn = 0.5;
136             un = (3.0 / (xv[n - 1] - xv[n - 2])) * (ypn - (yv[n - 1] - yv[n - 2]) / (xv[n - 1] - xv[n - 2]));
137         }
138         y2[n - 1] = (un - qn * u[n - 2]) / (qn * y2[n - 2] + 1.0);
139         for (k = n - 2; k >= 0; k--) {
140             // This is the back-substitution loop of the tri-diagonal algorithm
141             y2[k] = y2[k] * y2[k + 1] + u[k];
142         }
143     }
144 }