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