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 }