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    *      https://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  
18  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.analysis.polynomials;
23  
24  import java.util.Arrays;
25  
26  import org.hipparchus.CalculusFieldElement;
27  import org.hipparchus.analysis.FieldUnivariateFunction;
28  import org.hipparchus.analysis.differentiation.Derivative;
29  import org.hipparchus.analysis.differentiation.UnivariateDifferentiableFunction;
30  import org.hipparchus.exception.LocalizedCoreFormats;
31  import org.hipparchus.exception.MathIllegalArgumentException;
32  import org.hipparchus.exception.NullArgumentException;
33  import org.hipparchus.util.MathArrays;
34  import org.hipparchus.util.MathUtils;
35  
36  /**
37   * Represents a polynomial spline function.
38   * <p>
39   * A <strong>polynomial spline function</strong> consists of a set of
40   * <i>interpolating polynomials</i> and an ascending array of domain
41   * <i>knot points</i>, determining the intervals over which the spline function
42   * is defined by the constituent polynomials.  The polynomials are assumed to
43   * have been computed to match the values of another function at the knot
44   * points.  The value consistency constraints are not currently enforced by
45   * <code>PolynomialSplineFunction</code> itself, but are assumed to hold among
46   * the polynomials and knot points passed to the constructor.</p>
47   * <p>
48   * N.B.:  The polynomials in the <code>polynomials</code> property must be
49   * centered on the knot points to compute the spline function values.
50   * See below.</p>
51   * <p>
52   * The domain of the polynomial spline function is
53   * <code>[smallest knot, largest knot]</code>.  Attempts to evaluate the
54   * function at values outside of this range generate IllegalArgumentExceptions.
55   * </p>
56   * <p>
57   * The value of the polynomial spline function for an argument <code>x</code>
58   * is computed as follows:
59   * <ol>
60   * <li>The knot array is searched to find the segment to which <code>x</code>
61   * belongs.  If <code>x</code> is less than the smallest knot point or greater
62   * than the largest one, an <code>IllegalArgumentException</code>
63   * is thrown.</li>
64   * <li> Let <code>j</code> be the index of the largest knot point that is less
65   * than or equal to <code>x</code>.  The value returned is
66   * {@code polynomials[j](x - knot[j])}</li></ol>
67   *
68   */
69  public class PolynomialSplineFunction implements UnivariateDifferentiableFunction, FieldUnivariateFunction {
70      /**
71       * Spline segment interval delimiters (knots).
72       * Size is n + 1 for n segments.
73       */
74      private final double[] knots;
75      /**
76       * The polynomial functions that make up the spline.  The first element
77       * determines the value of the spline over the first subinterval, the
78       * second over the second, etc.   Spline function values are determined by
79       * evaluating these functions at {@code (x - knot[i])} where i is the
80       * knot segment to which x belongs.
81       */
82      private final PolynomialFunction[] polynomials;
83      /**
84       * Number of spline segments. It is equal to the number of polynomials and
85       * to the number of partition points - 1.
86       */
87      private final int n;
88  
89  
90      /**
91       * Construct a polynomial spline function with the given segment delimiters
92       * and interpolating polynomials.
93       * The constructor copies both arrays and assigns the copies to the knots
94       * and polynomials properties, respectively.
95       *
96       * @param knots Spline segment interval delimiters.
97       * @param polynomials Polynomial functions that make up the spline.
98       * @throws NullArgumentException if either of the input arrays is {@code null}.
99       * @throws MathIllegalArgumentException if knots has length less than 2.
100      * @throws MathIllegalArgumentException if {@code polynomials.length != knots.length - 1}.
101      * @throws MathIllegalArgumentException if the {@code knots} array is not strictly increasing.
102      *
103      */
104     public PolynomialSplineFunction(double[] knots, PolynomialFunction[] polynomials)
105         throws MathIllegalArgumentException, NullArgumentException {
106         if (knots == null ||
107             polynomials == null) {
108             throw new NullArgumentException();
109         }
110         if (knots.length < 2) {
111             throw new MathIllegalArgumentException(LocalizedCoreFormats.NOT_ENOUGH_POINTS_IN_SPLINE_PARTITION,
112                                                    2, knots.length, false);
113         }
114         MathUtils.checkDimension(polynomials.length, knots.length - 1);
115         MathArrays.checkOrder(knots);
116 
117         this.n = knots.length -1;
118         this.knots = new double[n + 1];
119         System.arraycopy(knots, 0, this.knots, 0, n + 1);
120         this.polynomials = new PolynomialFunction[n];
121         System.arraycopy(polynomials, 0, this.polynomials, 0, n);
122     }
123 
124     /**
125      * Compute the value for the function.
126      * See {@link PolynomialSplineFunction} for details on the algorithm for
127      * computing the value of the function.
128      *
129      * @param v Point for which the function value should be computed.
130      * @return the value.
131      * @throws MathIllegalArgumentException if {@code v} is outside of the domain of the
132      * spline function (smaller than the smallest knot point or larger than the
133      * largest knot point).
134      */
135     @Override
136     public double value(double v) {
137         MathUtils.checkRangeInclusive(v, knots[0], knots[n]);
138         int i = Arrays.binarySearch(knots, v);
139         if (i < 0) {
140             i = -i - 2;
141         }
142         // This will handle the case where v is the last knot value
143         // There are only n-1 polynomials, so if v is the last knot
144         // then we will use the last polynomial to calculate the value.
145         if ( i >= polynomials.length ) {
146             i--;
147         }
148         return polynomials[i].value(v - knots[i]);
149     }
150 
151     /**
152      * Get the derivative of the polynomial spline function.
153      *
154      * @return the derivative function.
155      */
156     public PolynomialSplineFunction polynomialSplineDerivative() {
157         PolynomialFunction[] derivativePolynomials = new PolynomialFunction[n];
158         for (int i = 0; i < n; i++) {
159             derivativePolynomials[i] = polynomials[i].polynomialDerivative();
160         }
161         return new PolynomialSplineFunction(knots, derivativePolynomials);
162     }
163 
164 
165     /** {@inheritDoc}
166      */
167     @Override
168     public <T extends Derivative<T>> T value(final T t) {
169         final double t0 = t.getReal();
170         MathUtils.checkRangeInclusive(t0, knots[0], knots[n]);
171         int i = Arrays.binarySearch(knots, t0);
172         if (i < 0) {
173             i = -i - 2;
174         }
175         // This will handle the case where t is the last knot value
176         // There are only n-1 polynomials, so if t is the last knot
177         // then we will use the last polynomial to calculate the value.
178         if ( i >= polynomials.length ) {
179             i--;
180         }
181         return polynomials[i].value(t.subtract(knots[i]));
182     }
183 
184     /**
185      * {@inheritDoc}
186      */
187     @Override
188     public <T extends CalculusFieldElement<T>> T value(final T t) {
189         final double t0 = t.getReal();
190         MathUtils.checkRangeInclusive(t0, knots[0], knots[n]);
191         int i = Arrays.binarySearch(knots, t0);
192         if (i < 0) {
193             i = -i - 2;
194         }
195         // This will handle the case where t is the last knot value
196         // There are only n-1 polynomials, so if t is the last knot
197         // then we will use the last polynomial to calculate the value.
198         if ( i >= polynomials.length ) {
199             i--;
200         }
201         return polynomials[i].value(t.subtract(knots[i]));
202     }
203 
204     /**
205      * Get the number of spline segments.
206      * It is also the number of polynomials and the number of knot points - 1.
207      *
208      * @return the number of spline segments.
209      */
210     public int getN() {
211         return n;
212     }
213 
214     /**
215      * Get a copy of the interpolating polynomials array.
216      * It returns a fresh copy of the array. Changes made to the copy will
217      * not affect the polynomials property.
218      *
219      * @return the interpolating polynomials.
220      */
221     public PolynomialFunction[] getPolynomials() {
222         PolynomialFunction[] p = new PolynomialFunction[n];
223         System.arraycopy(polynomials, 0, p, 0, n);
224         return p;
225     }
226 
227     /**
228      * Get an array copy of the knot points.
229      * It returns a fresh copy of the array. Changes made to the copy
230      * will not affect the knots property.
231      *
232      * @return the knot points.
233      */
234     public double[] getKnots() {
235         double[] out = new double[n + 1];
236         System.arraycopy(knots, 0, out, 0, n + 1);
237         return out;
238     }
239 
240     /**
241      * Indicates whether a point is within the interpolation range.
242      *
243      * @param x Point.
244      * @return {@code true} if {@code x} is a valid point.
245      */
246     public boolean isValidPoint(double x) {
247         return x >= knots[0] && x <= knots[n];
248     }
249 }