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