1   /* Copyright 2002-2026 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.time;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.util.FastMath;
22  import org.orekit.errors.OrekitIllegalArgumentException;
23  import org.orekit.errors.OrekitInternalError;
24  import org.orekit.errors.OrekitMessages;
25  import org.orekit.utils.ImmutableFieldTimeStampedCache;
26  
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.List;
31  import java.util.Optional;
32  import java.util.stream.Collectors;
33  import java.util.stream.Stream;
34  
35  /**
36   * Abstract class for time interpolator.
37   *
38   * @param <T> interpolated time stamped type
39   * @param <KK> type of the field element
40   *
41   * @author Vincent Cucchietti
42   */
43  public abstract class AbstractFieldTimeInterpolator<T extends FieldTimeStamped<KK>, KK extends CalculusFieldElement<KK>>
44          implements FieldTimeInterpolator<T, KK> {
45  
46      /** Default extrapolation time threshold: 1ms. */
47      public static final double DEFAULT_EXTRAPOLATION_THRESHOLD_SEC = 1e-3;
48  
49      /** Default number of interpolation points. */
50      public static final int DEFAULT_INTERPOLATION_POINTS = 2;
51  
52      /** The extrapolation threshold beyond which the propagation will fail. */
53      private final double extrapolationThreshold;
54  
55      /** Neighbor size. */
56      private final int interpolationPoints;
57  
58      /**
59       * Constructor.
60       *
61       * @param interpolationPoints number of interpolation points
62       * @param extrapolationThreshold extrapolation threshold beyond which the propagation will fail
63       */
64      protected AbstractFieldTimeInterpolator(final int interpolationPoints, final double extrapolationThreshold) {
65          this.interpolationPoints    = interpolationPoints;
66          this.extrapolationThreshold = extrapolationThreshold;
67      }
68  
69      /**
70       * Method checking if given interpolator is compatible with given sample size.
71       *
72       * @param interpolator interpolator
73       * @param sampleSize sample size
74       * @param <T> type of the field elements
75       */
76      public static <T extends CalculusFieldElement<T>> void checkInterpolatorCompatibilityWithSampleSize(
77              final FieldTimeInterpolator<? extends FieldTimeStamped<T>, T> interpolator,
78              final int sampleSize) {
79  
80          // Retrieve all sub-interpolators (or a singleton list with given interpolator if there are no sub-interpolators)
81          final List<FieldTimeInterpolator<? extends FieldTimeStamped<T>, T>> subInterpolators =
82                  interpolator.getSubInterpolators();
83          for (final FieldTimeInterpolator<? extends FieldTimeStamped<T>, T> subInterpolator : subInterpolators) {
84              if (sampleSize < subInterpolator.getNbInterpolationPoints()) {
85                  throw new OrekitIllegalArgumentException(OrekitMessages.NOT_ENOUGH_DATA, sampleSize);
86              }
87          }
88      }
89  
90      /** {@inheritDoc} */
91      @Override
92      public T interpolate(final FieldAbsoluteDate<KK> interpolationDate, final Stream<T> sample) {
93          return interpolate(interpolationDate, sample.collect(Collectors.toList()));
94      }
95  
96      /** {@inheritDoc}. */
97      @Override
98      public T interpolate(final FieldAbsoluteDate<KK> interpolationDate, final Collection<T> sample) {
99          final InterpolationData interpolationData = new InterpolationData(interpolationDate, sample);
100         return interpolate(interpolationData);
101     }
102 
103     /**
104      * Get the central date to use to find neighbors while taking into account extrapolation threshold.
105      *
106      * @param date interpolation date
107      * @param cachedSamples cached samples
108      * @param threshold extrapolation threshold
109      * @param <T> type of time stamped element
110      * @param <KK> type of calculus field element
111      *
112      * @return central date to use to find neighbors
113      * @since 12.0.1
114      */
115     public static <T extends FieldTimeStamped<KK>, KK extends CalculusFieldElement<KK>> FieldAbsoluteDate<KK> getCentralDate(
116             final FieldAbsoluteDate<KK> date,
117             final ImmutableFieldTimeStampedCache<T, KK> cachedSamples,
118             final double threshold) {
119         return getCentralDate(
120                 date,
121                 cachedSamples.getEarliest().getDate(),
122                 cachedSamples.getLatest().getDate(),
123                 threshold);
124     }
125 
126     /**
127      * Get the central date to use to find neighbors while taking into account extrapolation threshold.
128      *
129      * @param date interpolation date
130      * @param minDate earliest date in the sample.
131      * @param maxDate latest date in the sample.
132      * @param threshold extrapolation threshold
133      * @param <KK> type of calculus field element
134      *
135      * @return central date to use to find neighbors
136      * @since 12.0.1
137      */
138     public static <KK extends CalculusFieldElement<KK>> FieldAbsoluteDate<KK> getCentralDate(
139             final FieldAbsoluteDate<KK> date,
140             final FieldAbsoluteDate<KK> minDate,
141             final FieldAbsoluteDate<KK> maxDate,
142             final double threshold) {
143         final FieldAbsoluteDate<KK> central;
144 
145         if (date.compareTo(minDate) < 0 && FastMath.abs(date.durationFrom(minDate)).getReal() <= threshold) {
146             // avoid TimeStampedCacheException as we are still within the tolerance before minDate
147             central = minDate;
148         } else if (date.compareTo(maxDate) > 0 && FastMath.abs(date.durationFrom(maxDate)).getReal() <= threshold) {
149             // avoid TimeStampedCacheException as we are still within the tolerance after maxDate
150             central = maxDate;
151         } else {
152             central = date;
153         }
154 
155         return central;
156     }
157 
158     /** {@inheritDoc} */
159     public List<FieldTimeInterpolator<? extends FieldTimeStamped<KK>, KK>> getSubInterpolators() {
160         return Collections.singletonList(this);
161     }
162 
163     /** {@inheritDoc} */
164     public int getNbInterpolationPoints() {
165         final List<FieldTimeInterpolator<? extends FieldTimeStamped<KK>, KK>> subInterpolators = getSubInterpolators();
166         // In case the interpolator does not have sub interpolators
167         if (subInterpolators.size() == 1) {
168             return interpolationPoints;
169         }
170         // Otherwise find maximum number of interpolation points among sub interpolators
171         else {
172             final Optional<Integer> optionalMaxNbInterpolationPoints =
173                     subInterpolators.stream().map(FieldTimeInterpolator::getNbInterpolationPoints).max(Integer::compareTo);
174             if (optionalMaxNbInterpolationPoints.isPresent()) {
175                 return optionalMaxNbInterpolationPoints.get();
176             } else {
177                 // This should never happen
178                 throw new OrekitInternalError(null);
179             }
180         }
181     }
182 
183     /**
184      * Get the number of interpolation points for this instance only i.e., not taking into account sub-interpolators.
185      *
186      * @return required the number of interpolation points for this instance only i.e., not taking into account
187      * sub-interpolators.
188      */
189     public int getInternalNbInterpolationPoints() {
190         return interpolationPoints;
191     }
192 
193     /** {@inheritDoc} */
194     public double getExtrapolationThreshold() {
195         return extrapolationThreshold;
196     }
197 
198     /**
199      * Add all lowest level sub interpolators to the sub interpolator list.
200      *
201      * @param subInterpolator optional sub interpolator to add
202      * @param subInterpolators list of sub interpolators
203      * @param <S> type of the field element
204      */
205     protected <S extends CalculusFieldElement<S>> void addOptionalSubInterpolatorIfDefined(
206             final FieldTimeInterpolator<? extends FieldTimeStamped<S>, S> subInterpolator,
207             final List<FieldTimeInterpolator<? extends FieldTimeStamped<S>, S>> subInterpolators) {
208         // Add all lowest level sub interpolators
209         if (subInterpolator != null) {
210             subInterpolators.addAll(subInterpolator.getSubInterpolators());
211         }
212     }
213 
214     /**
215      * Interpolate instance from given interpolation data.
216      *
217      * @param interpolationData interpolation data
218      *
219      * @return interpolated instance from given interpolation data.
220      */
221     protected abstract T interpolate(InterpolationData interpolationData);
222 
223     /**
224      * Get the time parameter which lies between [0:1] by normalizing the difference between interpolating time and previous
225      * date by the Δt between tabulated values.
226      *
227      * @param interpolatingTime time at which we want to interpolate a value (between previous and next tabulated dates)
228      * @param previousDate previous tabulated value date
229      * @param nextDate next tabulated value date
230      *
231      * @return time parameter which lies between [0:1]
232      */
233     protected KK getTimeParameter(final FieldAbsoluteDate<KK> interpolatingTime,
234                                   final FieldAbsoluteDate<KK> previousDate,
235                                   final FieldAbsoluteDate<KK> nextDate) {
236 
237         return interpolatingTime.durationFrom(previousDate).divide(nextDate.getDate().durationFrom(previousDate));
238     }
239 
240     /**
241      * Nested class used to store interpolation data.
242      * <p>
243      * It makes the interpolator thread safe.
244      */
245     public class InterpolationData {
246 
247         /** Interpolation date. */
248         private final FieldAbsoluteDate<KK> interpolationDate;
249 
250         /** Unmodifiable list of neighbors. */
251         private final List<T> neighborList;
252 
253         /** Field of the element. */
254         private final Field<KK> field;
255 
256         /** Fielded zero. */
257         private final KK zero;
258 
259         /** Fielded one. */
260         private final KK one;
261 
262         /**
263          * Constructor.
264          *
265          * @param interpolationDate interpolation date
266          * @param sample time stamped sample
267          */
268         protected InterpolationData(final FieldAbsoluteDate<KK> interpolationDate, final Collection<T> sample) {
269             // Handle specific case that is not handled by the immutable time stamped cache constructor
270             if (sample.isEmpty()) {
271                 throw new OrekitIllegalArgumentException(OrekitMessages.NOT_ENOUGH_DATA, 0);
272             }
273 
274             // TODO performance: create neighborsList without copying sample.
275             final int nbInterpolationPoints = getNbInterpolationPoints();
276             if (sample.size() == nbInterpolationPoints) {
277                 // shortcut for simple case
278                 // copy list to make neighborList immutable
279                 this.neighborList = Collections.unmodifiableList(new ArrayList<>(sample));
280             } else {
281                 // else, select sample.
282 
283                 // Create immutable time stamped cache
284                 final ImmutableFieldTimeStampedCache<T, KK> cachedSamples =
285                         new ImmutableFieldTimeStampedCache<>(nbInterpolationPoints, sample);
286 
287                 // Find neighbors
288                 final FieldAbsoluteDate<KK> central =
289                         AbstractFieldTimeInterpolator.getCentralDate(
290                                 interpolationDate,
291                                 cachedSamples,
292                                 extrapolationThreshold);
293                 final Stream<T> neighborsStream = cachedSamples.getNeighbors(central);
294 
295                 // Convert to unmodifiable list
296                 neighborList = Collections.unmodifiableList(neighborsStream.collect(Collectors.toList()));
297             }
298 
299             // Extract field and useful terms
300             this.field = interpolationDate.getField();
301             this.zero = field.getZero();
302             this.one = field.getOne();
303 
304             // Store interpolation date
305             this.interpolationDate = interpolationDate;
306         }
307 
308         /** Get interpolation date.
309          * @return interpolation date
310          */
311         public FieldAbsoluteDate<KK> getInterpolationDate() {
312             return interpolationDate;
313         }
314 
315         /** Get neighbor list.
316          * @return neighbor list
317          */
318         public List<T> getNeighborList() {
319             return neighborList;
320         }
321 
322         /** Get field.
323          * @return field
324          */
325         public Field<KK> getField() {
326             return field;
327         }
328 
329         /** Get zero.
330          * @return zero
331          */
332         public KK getZero() {
333             return zero;
334         }
335 
336         /** Get one.
337          * @return one
338          */
339         public KK getOne() {
340             return one;
341         }
342     }
343 }