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 org.hipparchus.analysis.differentiation.Gradient;
25  import org.hipparchus.analysis.differentiation.GradientField;
26  import org.hipparchus.analysis.interpolation.LinearInterpolator;
27  import org.hipparchus.exception.LocalizedCoreFormats;
28  import org.hipparchus.exception.MathIllegalArgumentException;
29  import org.hipparchus.exception.MathIllegalStateException;
30  import org.hipparchus.util.Binary64;
31  import org.junit.jupiter.api.BeforeEach;
32  import org.junit.jupiter.api.Test;
33  
34  import java.lang.reflect.Array;
35  import java.util.Arrays;
36  
37  import static org.junit.jupiter.api.Assertions.assertEquals;
38  import static org.junit.jupiter.api.Assertions.assertFalse;
39  import static org.junit.jupiter.api.Assertions.assertTrue;
40  import static org.junit.jupiter.api.Assertions.fail;
41  
42  /**
43   * Tests the FieldFieldPolynomialSplineFunction implementation.
44   *
45   */
46  class FieldPolynomialSplineFunctionTest {
47  
48      /** Error tolerance for tests */
49      private double tolerance;
50  
51      /**
52       * Quadratic polynomials used in tests:
53       *
54       * x^2 + x            [-1, 0)
55       * x^2 + x + 2        [0, 1)
56       * x^2 + x + 4        [1, 2)
57       *
58       * Defined so that evaluation using FieldPolynomialSplineFunction evaluation
59       * algorithm agrees at knot point boundaries.
60       */
61      private FieldPolynomialFunction<Binary64>[] polynomials;
62  
63      /** Knot points  */
64      private Binary64[] knots;
65  
66      /** Derivative of test polynomials -- 2x + 1  */
67      protected PolynomialFunction dp =
68          new PolynomialFunction(new double[] {1d, 2d});
69  
70  
71      @Test
72      void testConstructor() {
73          FieldPolynomialSplineFunction<Binary64> spline =
74              new FieldPolynomialSplineFunction<>(knots, polynomials);
75          assertTrue(Arrays.equals(knots, spline.getKnots()));
76          assertEquals(1d, spline.getPolynomials()[0].getCoefficients()[2].getReal(), 0);
77          assertEquals(3, spline.getN());
78  
79          try { // too few knots
80              new FieldPolynomialSplineFunction<>(new Binary64[] { new Binary64(0) }, polynomials);
81              fail("Expecting MathIllegalArgumentException");
82          } catch (MathIllegalArgumentException ex) {
83              // expected
84          }
85  
86          try { // too many knots
87              new FieldPolynomialSplineFunction<>(new Binary64[] {
88                  new Binary64(0), new Binary64(1), new Binary64(2),
89                  new Binary64(3), new Binary64(4)
90              }, polynomials);
91              fail("Expecting MathIllegalArgumentException");
92          } catch (MathIllegalArgumentException ex) {
93              // expected
94          }
95  
96          try { // knots not increasing
97              new FieldPolynomialSplineFunction<>(new Binary64[] {
98                  new Binary64(0), new Binary64(1), new Binary64(3), new Binary64(2)
99              }, polynomials);
100             fail("Expecting MathIllegalArgumentException");
101         } catch (MathIllegalArgumentException ex) {
102             // expected
103         }
104     }
105 
106     @Test
107     void testValues() {
108         FieldPolynomialSplineFunction<Binary64> spline =
109             new FieldPolynomialSplineFunction<>(knots, polynomials);
110         FieldPolynomialSplineFunction<Binary64> dSpline = spline.polynomialSplineDerivative();
111 
112         /**
113          * interior points -- spline value at x should equal p(x - knot)
114          * where knot is the largest knot point less than or equal to x and p
115          * is the polynomial defined over the knot segment to which x belongs.
116          */
117         double x = -1;
118         int index = 0;
119         for (int i = 0; i < 10; i++) {
120            x += 0.25;
121            index = findKnot(knots, x);
122            assertEquals(polynomials[index].value(new Binary64(x).subtract(knots[index])).getReal(),
123                                spline.value(x).getReal(),
124                                tolerance,
125                                "spline function evaluation failed for x=" + x);
126            assertEquals(dp.value(new Binary64(x).subtract(knots[index])).getReal(),
127                                dSpline.value(x).getReal(),
128                                tolerance,
129                                "spline derivative evaluation failed for x=" + x);
130         }
131 
132         // knot points -- centering should zero arguments
133         for (int i = 0; i < 3; i++) {
134             assertEquals(polynomials[i].value(0).getReal(),
135                                 spline.value(knots[i]).getReal(),
136                                 tolerance,
137                                 "spline function evaluation failed for knot=" + knots[i].getReal());
138             assertEquals(dp.value(0),
139                                 dSpline.value(knots[i]).getReal(),
140                                 tolerance,
141                                 "spline function evaluation failed for knot=" + knots[i]);
142         }
143 
144         try { //outside of domain -- under min
145             spline.value(-1.5);
146             fail("Expecting MathIllegalArgumentException");
147         } catch (MathIllegalArgumentException ex) {
148             // expected
149         }
150 
151         try { //outside of domain -- over max
152             spline.value(2.5);
153             fail("Expecting MathIllegalArgumentException");
154         } catch (MathIllegalArgumentException ex) {
155             // expected
156         }
157     }
158 
159     @Test
160     void testIsValidPoint() {
161         final FieldPolynomialSplineFunction<Binary64> spline =
162             new FieldPolynomialSplineFunction<>(knots, polynomials);
163         final Binary64 xMin = knots[0];
164         final Binary64 xMax = knots[knots.length - 1];
165 
166         Binary64 x;
167 
168         x = xMin;
169         assertTrue(spline.isValidPoint(x));
170         // Ensure that no exception is thrown.
171         spline.value(x);
172 
173         x = xMax;
174         assertTrue(spline.isValidPoint(x));
175         // Ensure that no exception is thrown.
176         spline.value(x);
177 
178         final Binary64 xRange = xMax.subtract(xMin);
179         x = xMin.add(xRange.divide(3.4));
180         assertTrue(spline.isValidPoint(x));
181         // Ensure that no exception is thrown.
182         spline.value(x);
183 
184         final Binary64 small = new Binary64(1e-8);
185         x = xMin.subtract(small);
186         assertFalse(spline.isValidPoint(x));
187         // Ensure that an exception would have been thrown.
188         try {
189             spline.value(x);
190             fail("MathIllegalArgumentException expected");
191         } catch (MathIllegalArgumentException expected) {}
192     }
193 
194     /**
195      *  Do linear search to find largest knot point less than or equal to x.
196      *  Implementation does binary search.
197      */
198      protected int findKnot(Binary64[] knots, double x) {
199          if (x < knots[0].getReal() || x >= knots[knots.length -1].getReal()) {
200              throw new MathIllegalArgumentException(LocalizedCoreFormats.OUT_OF_RANGE_SIMPLE,
201                                                     x, knots[0], knots[knots.length -1]);
202          }
203          for (int i = 0; i < knots.length; i++) {
204              if (knots[i].getReal() > x) {
205                  return i - 1;
206              }
207          }
208          throw new MathIllegalStateException(LocalizedCoreFormats.ILLEGAL_STATE);
209      }
210 
211      private FieldPolynomialFunction<Binary64> buildD64(double...c) {
212          Binary64[] array = new Binary64[c.length];
213          for (int i = 0; i < c.length; ++i) {
214              array[i] = new Binary64(c[i]);
215          }
216          return new FieldPolynomialFunction<>(array);
217      }
218 
219     @BeforeEach
220     @SuppressWarnings("unchecked")
221     void setUp() {
222          tolerance = 1.0e-12;
223          polynomials = (FieldPolynomialFunction<Binary64>[]) Array.newInstance(FieldPolynomialFunction.class, 3);
224          polynomials[0] = buildD64(0, 1, 1);
225          polynomials[1] = buildD64(2, 1, 1);
226          polynomials[2] = buildD64(4, 1, 1);
227          knots = new Binary64[] {
228              new Binary64(-1), new Binary64(0), new Binary64(1), new Binary64(2)
229          };
230      }
231 
232     @Test
233     void testValueGradient() {
234          // issue #257
235          // Given
236          final int freeParameters = 1;
237          final GradientField field = GradientField.getField(freeParameters);
238          final Gradient zero = field.getZero();
239          final Gradient time1 = zero.add(1.0);
240          final Gradient time2 = zero.add(2.0);
241          final Gradient time3 = zero.add(4.0);
242          final Gradient x1 = zero.add(4.0);
243          final Gradient x2 = Gradient.variable(freeParameters, 0, -2.0);
244          final Gradient x3 = zero.add(0.0);
245          final Gradient[] times = new Gradient[] {time1, time2, time3};
246          final Gradient[] xs = new Gradient[] {x1, x2, x3};
247          // When
248          final FieldPolynomialSplineFunction<Gradient> spline = new LinearInterpolator().interpolate(times, xs);
249          final Gradient actualEvaluation = spline.value(zero.add(3.));
250          // Then
251          final Gradient expectedEvaluation = Gradient.variable(freeParameters, 0, 0).multiply(0.5).add(-1.0);
252          assertEquals(expectedEvaluation, actualEvaluation);
253      }
254 
255 }
256