1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS 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.orekit.utils;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.analysis.interpolation.FieldHermiteInterpolator;
21  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
22  import org.orekit.errors.OrekitInternalError;
23  import org.orekit.time.AbstractFieldTimeInterpolator;
24  import org.orekit.time.FieldAbsoluteDate;
25  
26  import java.util.stream.Stream;
27  
28  /**
29   * Class using a Hermite interpolator to interpolate time stamped position-velocity-acceleration coordinates.
30   * <p>
31   * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation points
32   * (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
33   * and numerical problems (including NaN appearing).
34   *
35   * @param <KK> type of the field element
36   *
37   * @author Luc Maisonobe
38   * @author Vincent Cucchietti
39   * @see FieldHermiteInterpolator
40   * @see TimeStampedFieldPVCoordinates
41   */
42  public class TimeStampedFieldPVCoordinatesHermiteInterpolator<KK extends CalculusFieldElement<KK>>
43          extends AbstractFieldTimeInterpolator<TimeStampedFieldPVCoordinates<KK>, KK> {
44  
45      /** Filter for derivatives from the sample to use in interpolation. */
46      private final CartesianDerivativesFilter filter;
47  
48      /**
49       * Constructor with :
50       * <ul>
51       *     <li>Default number of interpolation points of {@code DEFAULT_INTERPOLATION_POINTS}</li>
52       *     <li>Default extrapolation threshold value ({@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s)</li>
53       *     <li>Use of angular and first time derivative for attitude interpolation</li>
54       * </ul>
55       * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
56       * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
57       * phenomenon</a> and numerical problems (including NaN appearing).
58       */
59      public TimeStampedFieldPVCoordinatesHermiteInterpolator() {
60          this(DEFAULT_INTERPOLATION_POINTS);
61      }
62  
63      /**
64       * Constructor with :
65       * <ul>
66       *     <li>Default extrapolation threshold value ({@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s)</li>
67       *     <li>Use of position and both time derivatives for attitude interpolation</li>
68       * </ul>
69       * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
70       * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
71       * phenomenon</a> and numerical problems (including NaN appearing).
72       *
73       * @param interpolationPoints number of interpolation points
74       */
75      public TimeStampedFieldPVCoordinatesHermiteInterpolator(final int interpolationPoints) {
76  
77          this(interpolationPoints, CartesianDerivativesFilter.USE_PVA);
78      }
79  
80      /**
81       * Constructor with :
82       * <ul>
83       *     <li>Default extrapolation threshold value ({@code DEFAULT_EXTRAPOLATION_THRESHOLD_SEC} s)</li>
84       * </ul>
85       * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
86       * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
87       * phenomenon</a> and numerical problems (including NaN appearing).
88       *
89       * @param interpolationPoints number of interpolation points
90       * @param filter filter for derivatives from the sample to use in interpolation
91       */
92      public TimeStampedFieldPVCoordinatesHermiteInterpolator(final int interpolationPoints,
93                                                              final CartesianDerivativesFilter filter) {
94          this(interpolationPoints, DEFAULT_EXTRAPOLATION_THRESHOLD_SEC, filter);
95      }
96  
97      /**
98       * Constructor.
99       * <p>
100      * As this implementation of interpolation is polynomial, it should be used only with small number of interpolation
101      * points (about 10-20 points) in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's
102      * phenomenon</a> and numerical problems (including NaN appearing).
103      *
104      * @param interpolationPoints number of interpolation points
105      * @param extrapolationThreshold extrapolation threshold beyond which the propagation will fail
106      * @param filter filter for derivatives from the sample to use in interpolation
107      */
108     public TimeStampedFieldPVCoordinatesHermiteInterpolator(final int interpolationPoints,
109                                                             final double extrapolationThreshold,
110                                                             final CartesianDerivativesFilter filter) {
111         super(interpolationPoints, extrapolationThreshold);
112         this.filter = filter;
113     }
114 
115     /** filter for derivatives from the sample to use in interpolation.
116      * @return filter for derivatives from the sample to use in interpolation
117      */
118     public CartesianDerivativesFilter getFilter() {
119         return filter;
120     }
121 
122     /**
123      * {@inheritDoc}
124      * <p>
125      * The interpolated instance is created by polynomial Hermite interpolation ensuring velocity remains the exact
126      * derivative of position.
127      * <p>
128      * Note that even if first time derivatives (velocities) from sample can be ignored, the interpolated instance always
129      * includes interpolated derivatives. This feature can be used explicitly to compute these derivatives when it would be
130      * too complex to compute them from an analytical formula: just compute a few sample points from the explicit formula and
131      * set the derivatives to zero in these sample points, then use interpolation to add derivatives consistent with the
132      * positions.
133      */
134     @Override
135     protected TimeStampedFieldPVCoordinates<KK> interpolate(final InterpolationData interpolationData) {
136 
137         // Get interpolation date
138         final FieldAbsoluteDate<KK> interpolationDate = interpolationData.getInterpolationDate();
139 
140         // Convert sample to stream
141         final Stream<TimeStampedFieldPVCoordinates<KK>> sample = interpolationData.getNeighborList().stream();
142 
143         // Set up an interpolator taking derivatives into account
144         final FieldHermiteInterpolator<KK> interpolator = new FieldHermiteInterpolator<>();
145 
146         // Add sample points
147         switch (filter) {
148             case USE_P:
149                 // populate sample with position data, ignoring velocity
150                 sample.forEach(pv -> {
151                     final FieldVector3D<KK> position = pv.getPosition();
152                     interpolator.addSamplePoint(pv.getDate().durationFrom(interpolationDate),
153                                                 position.toArray());
154                 });
155                 break;
156             case USE_PV:
157                 // populate sample with position and velocity data
158                 sample.forEach(pv -> {
159                     final FieldVector3D<KK> position = pv.getPosition();
160                     final FieldVector3D<KK> velocity = pv.getVelocity();
161                     interpolator.addSamplePoint(pv.getDate().durationFrom(interpolationDate),
162                                                 position.toArray(), velocity.toArray());
163                 });
164                 break;
165             case USE_PVA:
166                 // populate sample with position, velocity and acceleration data
167                 sample.forEach(pv -> {
168                     final FieldVector3D<KK> position     = pv.getPosition();
169                     final FieldVector3D<KK> velocity     = pv.getVelocity();
170                     final FieldVector3D<KK> acceleration = pv.getAcceleration();
171                     interpolator.addSamplePoint(pv.getDate().durationFrom(interpolationDate),
172                                                 position.toArray(), velocity.toArray(), acceleration.toArray());
173                 });
174                 break;
175             default:
176                 // this should never happen
177                 throw new OrekitInternalError(null);
178         }
179 
180         // Interpolate
181         final KK[][] pva = interpolator.derivatives(interpolationDate.getField().getZero(), 2);
182 
183         // Build a new interpolated instance
184         return new TimeStampedFieldPVCoordinates<>(interpolationDate, new FieldVector3D<>(pva[0]), new FieldVector3D<>(pva[1]),
185                                                    new FieldVector3D<>(pva[2]));
186     }
187 }