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 }