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