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.propagation.analytical;
18  
19  import org.hipparchus.exception.LocalizedCoreFormats;
20  import org.hipparchus.exception.MathIllegalArgumentException;
21  import org.hipparchus.linear.RealMatrix;
22  import org.orekit.attitudes.Attitude;
23  import org.orekit.attitudes.AttitudeProvider;
24  import org.orekit.attitudes.FrameAlignedProvider;
25  import org.orekit.errors.OrekitException;
26  import org.orekit.errors.OrekitIllegalArgumentException;
27  import org.orekit.errors.OrekitIllegalStateException;
28  import org.orekit.errors.OrekitMessages;
29  import org.orekit.frames.Frame;
30  import org.orekit.orbits.Orbit;
31  import org.orekit.propagation.AbstractMatricesHarvester;
32  import org.orekit.propagation.BoundedPropagator;
33  import org.orekit.propagation.SpacecraftState;
34  import org.orekit.propagation.SpacecraftStateInterpolator;
35  import org.orekit.propagation.StateCovariance;
36  import org.orekit.time.AbsoluteDate;
37  import org.orekit.time.AbstractTimeInterpolator;
38  import org.orekit.time.TimeInterpolator;
39  import org.orekit.time.TimeStampedPair;
40  import org.orekit.utils.DoubleArrayDictionary;
41  import org.orekit.utils.ImmutableTimeStampedCache;
42  
43  import java.util.ArrayList;
44  import java.util.List;
45  import java.util.Optional;
46  import org.orekit.utils.DataDictionary;
47  
48  /**
49   * This class is designed to accept and handle tabulated orbital entries. Tabulated entries are classified and then
50   * extrapolated in way to obtain continuous output, with accuracy and computation methods configured by the user.
51   *
52   * @author Fabien Maussion
53   * @author Véronique Pommier-Maurussane
54   * @author Luc Maisonobe
55   * @author Vincent Cucchietti
56   */
57  public class Ephemeris extends AbstractAnalyticalPropagator implements BoundedPropagator {
58  
59      /** First date in range. */
60      private final AbsoluteDate minDate;
61  
62      /** Last date in range. */
63      private final AbsoluteDate maxDate;
64  
65      /** Reference frame. */
66      private final Frame frame;
67  
68      /** Names of the additional states. */
69      private final String[] additional;
70  
71      /** List of spacecraft states. */
72      private final transient ImmutableTimeStampedCache<SpacecraftState> statesCache;
73  
74      /** List of covariances. **/
75      private final transient ImmutableTimeStampedCache<StateCovariance> covariancesCache;
76  
77      /** Spacecraft state interpolator. */
78      private final transient TimeInterpolator<SpacecraftState> stateInterpolator;
79  
80      /** State covariance interpolator. */
81      private final transient TimeInterpolator<TimeStampedPair<Orbit, StateCovariance>> covarianceInterpolator;
82  
83      /** Flag defining if states are defined using an orbit or an absolute position-velocity-acceleration. */
84      private final transient boolean statesAreOrbitDefined;
85  
86      /**
87       * Legacy constructor with tabulated states and default Hermite interpolation.
88       * <p>
89       * As this implementation of interpolation is polynomial, it should be used only with small samples (about 10-20 points)
90       * in order to avoid <a href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a> and numerical
91       * problems (including NaN appearing).
92       *
93       * @param states list of spacecraft states
94       * @param interpolationPoints number of points to use in interpolation
95       *
96       * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
97       *                                        interpolation
98       * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
99       *                                        position-velocity-acceleration)
100      * @see #Ephemeris(List, TimeInterpolator, List, TimeInterpolator)
101      * @see SpacecraftStateInterpolator
102      */
103     public Ephemeris(final List<SpacecraftState> states, final int interpolationPoints)
104             throws MathIllegalArgumentException {
105         // If states is empty an exception will be thrown in the other constructor
106         this(states, new SpacecraftStateInterpolator(interpolationPoints,
107                                                      states.get(0).getFrame(),
108                                                      states.get(0).getFrame()),
109              new ArrayList<>(), null);
110     }
111 
112     /**
113      * Constructor with tabulated states.
114      *
115      * @param states list of spacecraft states
116      * @param stateInterpolator spacecraft state interpolator
117      *
118      * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
119      *                                        interpolation
120      * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
121      *                                        position-velocity-acceleration)
122      * @see #Ephemeris(List, TimeInterpolator, List, TimeInterpolator)
123      */
124     public Ephemeris(final List<SpacecraftState> states, final TimeInterpolator<SpacecraftState> stateInterpolator)
125             throws MathIllegalArgumentException {
126         this(states, stateInterpolator, new ArrayList<>(), null);
127     }
128 
129     /**
130      * Constructor with tabulated states.
131      *
132      * @param states list of spacecraft states
133      * @param stateInterpolator spacecraft state interpolator
134      * @param attitudeProvider attitude law to use, null by default
135      *
136      * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
137      *                                        interpolation
138      * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
139      *                                        position-velocity-acceleration)
140      * @see #Ephemeris(List, TimeInterpolator, List, TimeInterpolator)
141      */
142     public Ephemeris(final List<SpacecraftState> states, final TimeInterpolator<SpacecraftState> stateInterpolator,
143                      final AttitudeProvider attitudeProvider)
144             throws MathIllegalArgumentException {
145         this(states, stateInterpolator, new ArrayList<>(), null, attitudeProvider);
146     }
147 
148     /**
149      * Constructor with tabulated states and associated covariances.
150      *
151      * @param states list of spacecraft states
152      * @param stateInterpolator spacecraft state interpolator
153      * @param covariances tabulated covariances associated to tabulated states ephemeris bounds to be doing extrapolation
154      * @param covarianceInterpolator covariance interpolator
155      *
156      * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
157      *                                        interpolation
158      * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
159      *                                        position-velocity-acceleration)
160      * @throws OrekitIllegalArgumentException if number of states is different from the number of covariances
161      * @throws OrekitIllegalStateException    if dates between states and associated covariances are different
162      * @see #Ephemeris(List, TimeInterpolator, List, TimeInterpolator, AttitudeProvider)
163      * @since 9.0
164      */
165     public Ephemeris(final List<SpacecraftState> states,
166                      final TimeInterpolator<SpacecraftState> stateInterpolator,
167                      final List<StateCovariance> covariances,
168                      final TimeInterpolator<TimeStampedPair<Orbit, StateCovariance>> covarianceInterpolator)
169             throws MathIllegalArgumentException {
170         this(states, stateInterpolator, covariances, covarianceInterpolator,
171              // if states is empty an exception will be thrown in the other constructor
172              states.isEmpty() ? null : FrameAlignedProvider.of(states.get(0).getFrame()));
173     }
174 
175     /**
176      * Constructor with tabulated states and associated covariances.
177      * <p>
178      * The user is expected to explicitly define an attitude provider if they want to use one. Otherwise, it is null by
179      * default
180      *
181      * @param states list of spacecraft states
182      * @param stateInterpolator spacecraft state interpolator
183      * @param covariances tabulated covariances associated to tabulated states
184      * @param covarianceInterpolator covariance interpolator
185      * @param attitudeProvider attitude law to use, null by default
186      *
187      * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
188      *                                        interpolation
189      * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
190      *                                        position-velocity-acceleration)
191      * @throws OrekitIllegalArgumentException if number of states is different from the number of covariances
192      * @throws OrekitIllegalStateException    if dates between states and associated covariances are different
193      * @since 10.1
194      */
195     public Ephemeris(final List<SpacecraftState> states,
196                      final TimeInterpolator<SpacecraftState> stateInterpolator,
197                      final List<StateCovariance> covariances,
198                      final TimeInterpolator<TimeStampedPair<Orbit, StateCovariance>> covarianceInterpolator,
199                      final AttitudeProvider attitudeProvider)
200             throws MathIllegalArgumentException {
201 
202         super(attitudeProvider);
203 
204         // Check input consistency
205         checkInputConsistency(states, stateInterpolator, covariances, covarianceInterpolator);
206 
207         // Initialize variables
208         final SpacecraftState s0 = states.get(0);
209         minDate = s0.getDate();
210         maxDate = states.get(states.size() - 1).getDate();
211         frame   = s0.getFrame();
212 
213         final List<DataDictionary.Entry> as = s0.getAdditionalDataValues().getData();
214         additional = new String[as.size()];
215         for (int i = 0; i < additional.length; ++i) {
216             additional[i] = as.get(i).getKey();
217         }
218 
219         this.statesCache       = new ImmutableTimeStampedCache<>(stateInterpolator.getNbInterpolationPoints(), states);
220         this.stateInterpolator = stateInterpolator;
221 
222         this.covarianceInterpolator = covarianceInterpolator;
223         if (covarianceInterpolator != null) {
224             this.covariancesCache = new ImmutableTimeStampedCache<>(covarianceInterpolator.getNbInterpolationPoints(),
225                                                                     covariances);
226         } else {
227             this.covariancesCache = null;
228         }
229         this.statesAreOrbitDefined = s0.isOrbitDefined();
230 
231         // Initialize initial state
232         super.resetInitialState(getInitialState());
233     }
234 
235     /**
236      * Check input consistency between states, covariances and their associated interpolators.
237      *
238      * @param states spacecraft states sample
239      * @param stateInterpolator spacecraft state interpolator
240      * @param covariances covariances sample
241      * @param covarianceInterpolator covariance interpolator
242      */
243     public static void checkInputConsistency(final List<SpacecraftState> states,
244                                              final TimeInterpolator<SpacecraftState> stateInterpolator,
245                                              final List<StateCovariance> covariances,
246                                              final TimeInterpolator<TimeStampedPair<Orbit, StateCovariance>> covarianceInterpolator) {
247         // Checks to perform is states are provided
248         if (!states.isEmpty()) {
249             // Check given that given states definition are consistent
250             // (all defined by either orbits or absolute position-velocity-acceleration coordinates)
251             SpacecraftStateInterpolator.checkStatesDefinitionsConsistency(states);
252 
253             // Check that every interpolator used in the state interpolator are compatible with the sample size
254             AbstractTimeInterpolator.checkInterpolatorCompatibilityWithSampleSize(stateInterpolator, states.size());
255 
256             // Additional checks if covariances are provided
257             if (!covariances.isEmpty()) {
258                 // Check that every interpolator used in the state covariance interpolator are compatible with the sample size
259                 AbstractTimeInterpolator.checkInterpolatorCompatibilityWithSampleSize(covarianceInterpolator,
260                                                                                       covariances.size());
261                 // Check states and covariances consistency
262                 checkStatesAndCovariancesConsistency(states, covariances);
263             }
264         }
265         else {
266             throw new OrekitIllegalArgumentException(OrekitMessages.NOT_ENOUGH_DATA, 0);
267         }
268     }
269 
270     /**
271      * Check that given states and covariances are consistent.
272      *
273      * @param states tabulates states to check
274      * @param covariances tabulated covariances associated to tabulated states to check
275      */
276     public static void checkStatesAndCovariancesConsistency(final List<SpacecraftState> states,
277                                                             final List<StateCovariance> covariances) {
278         final int nbStates = states.size();
279 
280         // Check that we have an equal number of states and covariances
281         if (nbStates != covariances.size()) {
282             throw new OrekitIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
283                                                      states.size(),
284                                                      covariances.size());
285         }
286 
287         // Check that states and covariance are defined at the same date
288         for (int i = 0; i < nbStates; i++) {
289             if (!states.get(i).getDate().isCloseTo(covariances.get(i).getDate(),
290                                                    TimeStampedPair.DEFAULT_DATE_EQUALITY_THRESHOLD)) {
291                 throw new OrekitIllegalStateException(OrekitMessages.STATE_AND_COVARIANCE_DATES_MISMATCH,
292                                                       states.get(i).getDate(), covariances.get(i).getDate());
293             }
294         }
295     }
296 
297     /**
298      * Get the first date of the range.
299      *
300      * @return the first date of the range
301      */
302     public AbsoluteDate getMinDate() {
303         return minDate;
304     }
305 
306     /**
307      * Get the last date of the range.
308      *
309      * @return the last date of the range
310      */
311     public AbsoluteDate getMaxDate() {
312         return maxDate;
313     }
314 
315     /** {@inheritDoc} */
316     @Override
317     public Frame getFrame() {
318         return frame;
319     }
320 
321     /**
322      * Get the covariance at given date.
323      * <p>
324      * BEWARE : If this instance has been created without sample of covariances and/or with spacecraft states defined with
325      * absolute position-velocity-acceleration, it will return an empty {@link Optional}.
326      *
327      * @param date date at which the covariance is desired
328      *
329      * @return covariance at given date
330      *
331      * @see Optional
332      */
333     public Optional<StateCovariance> getCovariance(final AbsoluteDate date) {
334         if (covarianceInterpolator != null && covariancesCache != null && statesAreOrbitDefined) {
335 
336             // Build list of time stamped pair of orbits and their associated covariances
337             final List<TimeStampedPair<Orbit, StateCovariance>> sample = buildOrbitAndCovarianceSample();
338 
339             // Interpolate
340             final TimeStampedPair<Orbit, StateCovariance> interpolatedOrbitAndCovariance =
341                     covarianceInterpolator.interpolate(date, sample);
342 
343             return Optional.of(interpolatedOrbitAndCovariance.getSecond());
344         }
345         else {
346             return Optional.empty();
347         }
348 
349     }
350 
351     /** @return sample of orbits and their associated covariances */
352     private List<TimeStampedPair<Orbit, StateCovariance>> buildOrbitAndCovarianceSample() {
353         final List<TimeStampedPair<Orbit, StateCovariance>> sample      = new ArrayList<>();
354         final List<SpacecraftState>                         states      = statesCache.getAll();
355         final List<StateCovariance>                         covariances = covariancesCache.getAll();
356         for (int i = 0; i < states.size(); i++) {
357             sample.add(new TimeStampedPair<>(states.get(i).getOrbit(), covariances.get(i)));
358         }
359 
360         return sample;
361     }
362 
363     /** {@inheritDoc} */
364     @Override
365     public SpacecraftState basicPropagate(final AbsoluteDate date) {
366 
367         final AbsoluteDate centralDate =
368                 AbstractTimeInterpolator.getCentralDate(date, statesCache, stateInterpolator.getExtrapolationThreshold());
369         final SpacecraftState  evaluatedState   = stateInterpolator.interpolate(date, statesCache.getNeighbors(centralDate));
370         final AttitudeProvider attitudeProvider = getAttitudeProvider();
371         if (attitudeProvider == null) {
372             return evaluatedState;
373         }
374         else {
375             final Attitude calculatedAttitude;
376             // Verify if orbit is defined
377             if (evaluatedState.isOrbitDefined()) {
378                 calculatedAttitude =
379                         attitudeProvider.getAttitude(evaluatedState.getOrbit(), date, evaluatedState.getFrame());
380                 return new SpacecraftState(evaluatedState.getOrbit(), calculatedAttitude, evaluatedState.getMass(),
381                                            evaluatedState.getAdditionalDataValues(),
382                                            evaluatedState.getAdditionalStatesDerivatives());
383             }
384             else {
385                 calculatedAttitude =
386                         attitudeProvider.getAttitude(evaluatedState.getAbsPVA(), date, evaluatedState.getFrame());
387                 return new SpacecraftState(evaluatedState.getAbsPVA(), calculatedAttitude, evaluatedState.getMass(),
388                                            evaluatedState.getAdditionalDataValues(),
389                                            evaluatedState.getAdditionalStatesDerivatives());
390             }
391 
392         }
393     }
394 
395     /** {@inheritDoc} */
396     public Orbit propagateOrbit(final AbsoluteDate date) {
397         return basicPropagate(date).getOrbit();
398     }
399 
400     /** {@inheritDoc} */
401     protected double getMass(final AbsoluteDate date) {
402         return basicPropagate(date).getMass();
403     }
404 
405     /**
406      * Try (and fail) to reset the initial state.
407      * <p>
408      * This method always throws an exception, as ephemerides cannot be reset.
409      * </p>
410      *
411      * @param state new initial state to consider
412      */
413     public void resetInitialState(final SpacecraftState state) {
414         throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
415     }
416 
417     /** {@inheritDoc} */
418     protected void resetIntermediateState(final SpacecraftState state, final boolean forward) {
419         throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
420     }
421 
422     /** {@inheritDoc} */
423     public SpacecraftState getInitialState() {
424         return basicPropagate(getMinDate());
425     }
426 
427     /** {@inheritDoc} */
428     @Override
429     public boolean isAdditionalDataManaged(final String name) {
430 
431         // the additional state may be managed by a specific provider in the base class
432         if (super.isAdditionalDataManaged(name)) {
433             return true;
434         }
435 
436         // the additional state may be managed in the states sample
437         for (final String a : additional) {
438             if (a.equals(name)) {
439                 return true;
440             }
441         }
442 
443         return false;
444 
445     }
446 
447     /** {@inheritDoc} */
448     @Override
449     public String[] getManagedAdditionalData() {
450         final String[] upperManaged = super.getManagedAdditionalData();
451         final String[] managed      = new String[upperManaged.length + additional.length];
452         System.arraycopy(upperManaged, 0, managed, 0, upperManaged.length);
453         System.arraycopy(additional, 0, managed, upperManaged.length, additional.length);
454         return managed;
455     }
456 
457     /** {@inheritDoc} */
458     @Override
459     protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
460                                                         final DoubleArrayDictionary initialJacobianColumns) {
461         // In order to not throw an Orekit exception during ephemeris based orbit determination
462         // The default behavior of the method is overridden to return a null parameter
463         return null;
464     }
465 
466     /** Get state interpolator.
467      * @return state interpolator
468      */
469     public TimeInterpolator<SpacecraftState> getStateInterpolator() {
470         return stateInterpolator;
471     }
472 
473     /** Get covariance interpolator.
474      * @return optional covariance interpolator
475      * @see Optional
476      */
477     public Optional<TimeInterpolator<TimeStampedPair<Orbit, StateCovariance>>> getCovarianceInterpolator() {
478         return Optional.ofNullable(covarianceInterpolator);
479     }
480 
481 }