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 }