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.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      public 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     /** {@inheritDoc} */
184     public double getExtrapolationThreshold() {
185         return extrapolationThreshold;
186     }
187 
188     /**
189      * Add all lowest level sub interpolators to the sub interpolator list.
190      *
191      * @param subInterpolator optional sub interpolator to add
192      * @param subInterpolators list of sub interpolators
193      * @param <S> type of the field element
194      */
195     protected <S extends CalculusFieldElement<S>> void addOptionalSubInterpolatorIfDefined(
196             final FieldTimeInterpolator<? extends FieldTimeStamped<S>, S> subInterpolator,
197             final List<FieldTimeInterpolator<? extends FieldTimeStamped<S>, S>> subInterpolators) {
198         // Add all lowest level sub interpolators
199         if (subInterpolator != null) {
200             subInterpolators.addAll(subInterpolator.getSubInterpolators());
201         }
202     }
203 
204     /**
205      * Interpolate instance from given interpolation data.
206      *
207      * @param interpolationData interpolation data
208      *
209      * @return interpolated instance from given interpolation data.
210      */
211     protected abstract T interpolate(InterpolationData interpolationData);
212 
213     /**
214      * Get the time parameter which lies between [0:1] by normalizing the difference between interpolating time and previous
215      * date by the Δt between tabulated values.
216      *
217      * @param interpolatingTime time at which we want to interpolate a value (between previous and next tabulated dates)
218      * @param previousDate previous tabulated value date
219      * @param nextDate next tabulated value date
220      *
221      * @return time parameter which lies between [0:1]
222      */
223     protected KK getTimeParameter(final FieldAbsoluteDate<KK> interpolatingTime,
224                                   final FieldAbsoluteDate<KK> previousDate,
225                                   final FieldAbsoluteDate<KK> nextDate) {
226 
227         return interpolatingTime.durationFrom(previousDate).divide(nextDate.getDate().durationFrom(previousDate));
228     }
229 
230     /**
231      * Nested class used to store interpolation data.
232      * <p>
233      * It makes the interpolator thread safe.
234      */
235     public class InterpolationData {
236 
237         /** Interpolation date. */
238         private final FieldAbsoluteDate<KK> interpolationDate;
239 
240         /** Unmodifiable list of neighbors. */
241         private final List<T> neighborList;
242 
243         /** Field of the element. */
244         private final Field<KK> field;
245 
246         /** Fielded zero. */
247         private final KK zero;
248 
249         /** Fielded one. */
250         private final KK one;
251 
252         /**
253          * Constructor.
254          *
255          * @param interpolationDate interpolation date
256          * @param sample time stamped sample
257          */
258         protected InterpolationData(final FieldAbsoluteDate<KK> interpolationDate, final Collection<T> sample) {
259             // Handle specific case that is not handled by the immutable time stamped cache constructor
260             if (sample.isEmpty()) {
261                 throw new OrekitIllegalArgumentException(OrekitMessages.NOT_ENOUGH_DATA, 0);
262             }
263 
264             // TODO performance: create neighborsList without copying sample.
265             if (sample.size() == interpolationPoints) {
266                 // shortcut for simple case
267                 // copy list to make neighborList immutable
268                 this.neighborList = Collections.unmodifiableList(new ArrayList<>(sample));
269             } else {
270                 // else, select sample.
271 
272                 // Create immutable time stamped cache
273                 final ImmutableFieldTimeStampedCache<T, KK> cachedSamples =
274                         new ImmutableFieldTimeStampedCache<>(interpolationPoints, sample);
275 
276                 // Find neighbors
277                 final FieldAbsoluteDate<KK> central =
278                         AbstractFieldTimeInterpolator.getCentralDate(
279                                 interpolationDate,
280                                 cachedSamples,
281                                 extrapolationThreshold);
282                 final Stream<T> neighborsStream = cachedSamples.getNeighbors(central);
283 
284                 // Convert to unmodifiable list
285                 neighborList = Collections.unmodifiableList(neighborsStream.collect(Collectors.toList()));
286             }
287 
288             // Extract field and useful terms
289             this.field = interpolationDate.getField();
290             this.zero = field.getZero();
291             this.one = field.getOne();
292 
293             // Store interpolation date
294             this.interpolationDate = interpolationDate;
295         }
296 
297         /** Get interpolation date.
298          * @return interpolation date
299          */
300         public FieldAbsoluteDate<KK> getInterpolationDate() {
301             return interpolationDate;
302         }
303 
304         /** Get neighbor list.
305          * @return neighbor list
306          */
307         public List<T> getNeighborList() {
308             return neighborList;
309         }
310 
311         /** Get field.
312          * @return field
313          */
314         public Field<KK> getField() {
315             return field;
316         }
317 
318         /** Get zero.
319          * @return zero
320          */
321         public KK getZero() {
322             return zero;
323         }
324 
325         /** Get one.
326          * @return one
327          */
328         public KK getOne() {
329             return one;
330         }
331     }
332 }