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