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.util.FastMath;
20  import org.orekit.errors.OrekitIllegalArgumentException;
21  import org.orekit.errors.OrekitInternalError;
22  import org.orekit.errors.OrekitMessages;
23  import org.orekit.utils.ImmutableTimeStampedCache;
24  import org.orekit.utils.SortedListTrimmer;
25  
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.List;
30  import java.util.Optional;
31  import java.util.stream.Collectors;
32  import java.util.stream.Stream;
33  
34  /**
35   * Abstract class for time interpolator.
36   *
37   * @param <T> interpolated time stamped type
38   *
39   * @author Vincent Cucchietti
40   */
41  public abstract class AbstractTimeInterpolator<T extends TimeStamped> implements TimeInterpolator<T> {
42  
43      /** Default extrapolation time threshold: 1ms. */
44      public static final double DEFAULT_EXTRAPOLATION_THRESHOLD_SEC = 1e-3;
45  
46      /** Default number of interpolation points. */
47      public static final int DEFAULT_INTERPOLATION_POINTS = 2;
48  
49      /** The extrapolation threshold beyond which the propagation will fail. */
50      private final double extrapolationThreshold;
51  
52      /** Neighbor size. */
53      private final int interpolationPoints;
54  
55      /**
56       * Constructor.
57       *
58       * @param interpolationPoints number of interpolation points
59       * @param extrapolationThreshold extrapolation threshold beyond which the propagation will fail
60       */
61      protected AbstractTimeInterpolator(final int interpolationPoints, final double extrapolationThreshold) {
62          this.interpolationPoints    = interpolationPoints;
63          this.extrapolationThreshold = extrapolationThreshold;
64      }
65  
66      /**
67       * Method checking if given interpolator is compatible with given sample size.
68       *
69       * @param interpolator interpolator
70       * @param sampleSize sample size
71       */
72      public static void checkInterpolatorCompatibilityWithSampleSize(
73              final TimeInterpolator<? extends TimeStamped> interpolator,
74              final int sampleSize) {
75  
76          // Retrieve all sub-interpolators (or a singleton list with given interpolator if there are no sub-interpolators)
77          final List<TimeInterpolator<? extends TimeStamped>> subInterpolators = interpolator.getSubInterpolators();
78          for (final TimeInterpolator<? extends TimeStamped> subInterpolator : subInterpolators) {
79              if (sampleSize < subInterpolator.getNbInterpolationPoints()) {
80                  throw new OrekitIllegalArgumentException(OrekitMessages.NOT_ENOUGH_DATA, sampleSize);
81              }
82          }
83      }
84  
85      /**
86       * {@inheritDoc}
87       * <p>
88       * The stream must hold elements in chronological order.
89       */
90      @Override
91      public T interpolate(final AbsoluteDate interpolationDate, final Stream<T> sample) {
92          return interpolate(interpolationDate, sample.collect(Collectors.toList()));
93      }
94  
95      /**
96       * {@inheritDoc}
97       * <p>
98       * <strong>Precondition:</strong> {@code sample} must be sorted in chronological order. Passing an unsorted
99       * sample yields undefined neighbors and may throw
100      * {@link org.orekit.errors.TimeStampedCacheException}.
101      */
102     @Override
103     public T interpolate(final AbsoluteDate interpolationDate, final Collection<T> sample) {
104         final InterpolationData interpolationData = new InterpolationData(interpolationDate, sample);
105         return interpolate(interpolationData);
106     }
107 
108     /**
109      * Get the central date to use to find neighbors while taking into account extrapolation threshold.
110      *
111      * @param date interpolation date
112      * @param cachedSamples cached samples
113      * @param threshold extrapolation threshold
114      * @param <T> type of element
115      *
116      * @return central date to use to find neighbors
117      * @since 12.0.1
118      */
119     public static <T extends TimeStamped> AbsoluteDate getCentralDate(final AbsoluteDate date,
120                                                                       final ImmutableTimeStampedCache<T> cachedSamples,
121                                                                       final double threshold) {
122         final AbsoluteDate minDate = cachedSamples.getEarliest().getDate();
123         final AbsoluteDate maxDate = cachedSamples.getLatest().getDate();
124         return getCentralDate(date, minDate, maxDate, threshold);
125     }
126 
127     /**
128      * Get the central date to use to find neighbors while taking into account an extrapolation threshold.
129      *
130      * @param date interpolation date
131      * @param minDate earliest date in the sample.
132      * @param maxDate latest date in the sample.
133      * @param threshold extrapolation threshold
134      *
135      * @return central date to use to find neighbors
136      * @since 12.0.1
137      */
138     public static AbsoluteDate getCentralDate(final AbsoluteDate date,
139                                               final AbsoluteDate minDate,
140                                               final AbsoluteDate maxDate,
141                                               final double threshold) {
142         final AbsoluteDate central;
143 
144         if (date.compareTo(minDate) < 0 && FastMath.abs(date.durationFrom(minDate)) <= threshold) {
145             // avoid TimeStampedCacheException as we are still within the tolerance before minDate
146             central = minDate;
147         } else if (date.compareTo(maxDate) > 0 && FastMath.abs(date.durationFrom(maxDate)) <= threshold) {
148             // avoid TimeStampedCacheException as we are still within the tolerance after maxDate
149             central = maxDate;
150         } else {
151             central = date;
152         }
153 
154         return central;
155     }
156 
157     /** {@inheritDoc} */
158     public List<TimeInterpolator<? extends TimeStamped>> getSubInterpolators() {
159         return Collections.singletonList(this);
160     }
161 
162     /** {@inheritDoc} */
163     public int getNbInterpolationPoints() {
164         final List<TimeInterpolator<? extends TimeStamped>> subInterpolators = getSubInterpolators();
165         // In case the interpolator does not have sub interpolators
166         if (subInterpolators.size() == 1 && subInterpolators.getFirst() == this) {
167             return interpolationPoints;
168         }
169         // Otherwise find maximum number of interpolation points among sub interpolators
170         else {
171             final Optional<Integer> optionalMaxNbInterpolationPoints =
172                     subInterpolators.stream().map(TimeInterpolator::getNbInterpolationPoints).max(Integer::compareTo);
173             if (optionalMaxNbInterpolationPoints.isPresent()) {
174                 return optionalMaxNbInterpolationPoints.get();
175             } else {
176                 // This should never happen
177                 throw new OrekitInternalError(null);
178             }
179         }
180     }
181 
182     /**
183      * Get the number of interpolation points for this instance only i.e., not taking into account sub-interpolators.
184      *
185      * @return required the number of interpolation points for this instance only i.e., not taking into account
186      * sub-interpolators.
187      */
188     public int getInternalNbInterpolationPoints() {
189         return interpolationPoints;
190     }
191 
192     /** {@inheritDoc} */
193     public double getExtrapolationThreshold() {
194         return extrapolationThreshold;
195     }
196 
197     /**
198      * Add all lowest level sub interpolators to the sub interpolator list.
199      *
200      * @param subInterpolator optional sub interpolator to add
201      * @param subInterpolators list of sub interpolators
202      */
203     protected void addOptionalSubInterpolatorIfDefined(final TimeInterpolator<? extends TimeStamped> subInterpolator,
204                                                        final List<TimeInterpolator<? extends TimeStamped>> subInterpolators) {
205         // Add all lowest level sub interpolators
206         if (subInterpolator != null) {
207             subInterpolators.addAll(subInterpolator.getSubInterpolators());
208         }
209     }
210 
211     /**
212      * Interpolate instance from given interpolation data.
213      *
214      * @param interpolationData interpolation data
215      *
216      * @return interpolated instance from given interpolation data.
217      */
218     protected abstract T interpolate(InterpolationData interpolationData);
219 
220     /**
221      * Get the time parameter which lies between [0:1] by normalizing the difference between interpolating time and previous
222      * date by the Δt between tabulated values.
223      *
224      * @param interpolatingTime time at which we want to interpolate a value (between previous and next tabulated dates)
225      * @param previousDate previous tabulated value date
226      * @param nextDate next tabulated value date
227      *
228      * @return time parameter which lies between [0:1]
229      */
230     protected double getTimeParameter(final AbsoluteDate interpolatingTime,
231                                       final AbsoluteDate previousDate,
232                                       final AbsoluteDate nextDate) {
233         return interpolatingTime.durationFrom(previousDate) / nextDate.getDate().durationFrom(previousDate);
234     }
235 
236     /**
237      * Nested class used to store interpolation data.
238      * <p>
239      * It makes the interpolator thread safe.
240      */
241     public class InterpolationData {
242 
243         /** Interpolation date. */
244         private final AbsoluteDate interpolationDate;
245 
246         /** Neighbor list around interpolation date. */
247         private final List<T> neighborList;
248 
249         /**
250          * Constructor (Collection variant).
251          * <p>
252          * If {@code sample} is already a {@link List}, it is used directly; otherwise it is copied into a new
253          * {@link ArrayList}. Forwards to {@link #InterpolationData(AbsoluteDate, List)} — see that constructor for
254          * the sorted-sample precondition.
255          *
256          * @param interpolationDate interpolation date
257          * @param sample            time stamped sample (chronologically sorted)
258          */
259         protected InterpolationData(final AbsoluteDate interpolationDate, final Collection<T> sample) {
260             this(interpolationDate, (sample instanceof List) ? (List<T>) sample : new ArrayList<>(sample));
261         }
262 
263         /**
264          * Constructor.
265          * <p>
266          * <strong>Precondition:</strong> {@code sample} must be sorted in chronological order. Passing an unsorted
267          * sample yields undefined neighbors and may throw
268          * {@link org.orekit.errors.TimeStampedCacheException}. Prior implementations silently sorted the input;
269          * this is no longer the case.
270          *
271          * @param interpolationDate interpolation date
272          * @param sample            time stamped sample (chronologically sorted)
273          */
274         protected InterpolationData(final AbsoluteDate interpolationDate, final List<T> sample) {
275 
276             // Check if there is enough sample points
277             final int nbInterpolationPoints = getNbInterpolationPoints();
278             if (sample.size() < nbInterpolationPoints) {
279                 throw new OrekitIllegalArgumentException(OrekitMessages.NOT_ENOUGH_CACHED_NEIGHBORS,
280                                                          sample.size(), nbInterpolationPoints);
281             }
282 
283             // Shortcut to see if sample size is equal to number of interpolation points
284             if (sample.size() == nbInterpolationPoints) {
285                 this.neighborList = Collections.unmodifiableList(sample);
286             } else {
287                 final AbsoluteDate central = getCentralDate(interpolationDate,
288                                                             sample.get(0).getDate(),
289                                                             sample.get(sample.size() - 1).getDate(),
290                                                             extrapolationThreshold);
291 
292                 // Trimmer returns a sublist view, so wrap (don't copy) for immutability.
293                 final SortedListTrimmer trimmer = new SortedListTrimmer(nbInterpolationPoints);
294                 this.neighborList = Collections.unmodifiableList(trimmer.getNeighborsSubList(central, sample));
295             }
296 
297             this.interpolationDate = interpolationDate;
298         }
299 
300         /** Get interpolation date.
301          * @return interpolation date
302          */
303         public AbsoluteDate getInterpolationDate() {
304             return interpolationDate;
305         }
306 
307         /** Get neighbor list.
308          * @return neighbor list
309          */
310         public List<T> getNeighborList() {
311             return neighborList;
312         }
313 
314     }
315 }