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.integration;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.ode.DenseOutputModel;
22  import org.hipparchus.ode.ODEStateAndDerivative;
23  import org.orekit.attitudes.AttitudeProvider;
24  import org.orekit.attitudes.AttitudeProviderModifier;
25  import org.orekit.errors.OrekitException;
26  import org.orekit.errors.OrekitMessages;
27  import org.orekit.frames.Frame;
28  import org.orekit.orbits.Orbit;
29  import org.orekit.propagation.AdditionalDataProvider;
30  import org.orekit.propagation.BoundedPropagator;
31  import org.orekit.propagation.PropagationType;
32  import org.orekit.propagation.SpacecraftState;
33  import org.orekit.propagation.analytical.AbstractAnalyticalPropagator;
34  import org.orekit.propagation.events.EventDetector;
35  import org.orekit.propagation.events.FieldEventDetector;
36  import org.orekit.time.AbsoluteDate;
37  import org.orekit.utils.DataDictionary;
38  
39  import java.util.Arrays;
40  import java.util.List;
41  import java.util.stream.Stream;
42  
43  /** This class stores sequentially generated orbital parameters for
44   * later retrieval.
45   *
46   * <p>
47   * Instances of this class are built automatically when the {@link
48   * org.orekit.propagation.Propagator#getEphemerisGenerator()
49   * getEphemerisGenerator} method has been called. They are created when propagation is over.
50   * Random access to any intermediate state of the orbit throughout the propagation range is
51   * possible afterwards through this object.
52   * </p>
53   * <p>
54   * A typical use case is for numerically integrated orbits, which can be used by
55   * algorithms that need to wander around according to their own algorithm without
56   * cumbersome tight links with the integrator.
57   * </p>
58   * <p>
59   * As this class implements the {@link org.orekit.propagation.Propagator Propagator}
60   * interface, it can itself be used in batch mode to build another instance of the
61   * same type. This is however not recommended since it would be a waste of resources.
62   * </p>
63   * <p>
64   * Note that this class stores all intermediate states along with interpolation
65   * models, so it may be memory intensive.
66   * </p>
67   *
68   * @see org.orekit.propagation.numerical.NumericalPropagator
69   * @author Mathieu Rom&eacute;ro
70   * @author Luc Maisonobe
71   * @author V&eacute;ronique Pommier-Maurussane
72   */
73  public class IntegratedEphemeris
74      extends AbstractAnalyticalPropagator implements BoundedPropagator {
75  
76      /** Event detection requires evaluating the state slightly before / past an event. */
77      private static final double EXTRAPOLATION_TOLERANCE = 1.0;
78  
79      /** Mapper between raw double components and spacecraft state. */
80      private final StateMapper mapper;
81  
82      /** Type of orbit to output (mean or osculating).
83       * <p>
84       * This is used only in the case of semianalitical propagators where there is a clear separation between
85       * mean and short periodic elements. It is ignored by the Numerical propagator.
86       * </p>
87       */
88      private final PropagationType type;
89  
90      /** Start date of the integration (can be min or max). */
91      private final AbsoluteDate startDate;
92  
93      /** First date of the range. */
94      private final AbsoluteDate minDate;
95  
96      /** Last date of the range. */
97      private final AbsoluteDate maxDate;
98  
99      /** Underlying raw mathematical model. */
100     private final DenseOutputModel model;
101 
102     /** Unmanaged additional states that must be simply copied. */
103     private final DataDictionary unmanaged;
104 
105     /** Names of additional equations.
106      * @since 11.2
107      */
108     private final String[] equations;
109 
110     /** Dimensions of additional equations.
111      * @since 11.2
112      */
113     private final int[] dimensions;
114 
115     /** Creates a new instance of IntegratedEphemeris.
116      * @param startDate Start date of the integration (can be minDate or maxDate)
117      * @param minDate first date of the range
118      * @param maxDate last date of the range
119      * @param mapper mapper between raw double components and spacecraft state
120      * @param attitudeProvider attitude provider
121      * @param type type of orbit to output (mean or osculating)
122      * @param model underlying raw mathematical model
123      * @param unmanaged unmanaged additional states that must be simply copied
124      * @param providers providers for pre-integrated states
125      * @param equations names of additional equations
126      * @param dimensions dimensions of additional equations
127      * @since 13.0
128      */
129     public IntegratedEphemeris(final AbsoluteDate startDate,
130                                final AbsoluteDate minDate, final AbsoluteDate maxDate,
131                                final StateMapper mapper, final AttitudeProvider attitudeProvider,
132                                final PropagationType type, final DenseOutputModel model,
133                                final DataDictionary unmanaged,
134                                final List<AdditionalDataProvider<?>> providers,
135                                final String[] equations, final int[] dimensions) {
136 
137         super(attitudeProvider);
138 
139         this.startDate = startDate;
140         this.minDate   = minDate;
141         this.maxDate   = maxDate;
142         this.mapper    = mapper;
143         this.type      = type;
144         this.model     = model;
145         this.unmanaged = unmanaged;
146 
147         // set up the pre-integrated providers
148         for (final AdditionalDataProvider<?> provider : providers) {
149             addAdditionalDataProvider(provider);
150         }
151 
152         this.equations  = equations.clone();
153         this.dimensions = dimensions.clone();
154 
155         // set up initial state
156         super.resetInitialState(getInitialState());
157 
158         // remove event detectors in attitude provider
159         setAttitudeProvider(new AttitudeProviderModifier() {
160             @Override
161             public AttitudeProvider getUnderlyingAttitudeProvider() {
162                 return attitudeProvider;
163             }
164 
165             @Override
166             public Stream<EventDetector> getEventDetectors() {
167                 return Stream.of();
168             }
169 
170             @Override
171             public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(final Field<T> field) {
172                 return Stream.of();
173             }
174         });
175     }
176 
177     /** Interpolate the model at some date.
178      * @param date desired interpolation date
179      * @return state interpolated at date
180      */
181     private ODEStateAndDerivative getInterpolatedState(final AbsoluteDate date) {
182 
183         // compare using double precision instead of AbsoluteDate.compareTo(...)
184         // because time is expressed as a double when searching for events
185         if (date.compareTo(minDate.shiftedBy(-EXTRAPOLATION_TOLERANCE)) < 0) {
186             // date is outside of supported range
187             throw new OrekitException(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_BEFORE,
188                     date, minDate, maxDate, minDate.durationFrom(date));
189         }
190         if (date.compareTo(maxDate.shiftedBy(EXTRAPOLATION_TOLERANCE)) > 0) {
191             // date is outside of supported range
192             throw new OrekitException(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE_AFTER,
193                     date, minDate, maxDate, date.durationFrom(maxDate));
194         }
195 
196         return model.getInterpolatedState(date.durationFrom(startDate));
197 
198     }
199 
200     /** {@inheritDoc} */
201     @Override
202     public SpacecraftState basicPropagate(final AbsoluteDate date) {
203         final ODEStateAndDerivative os = getInterpolatedState(date);
204         SpacecraftState state = mapper.mapArrayToState(mapper.mapDoubleToDate(os.getTime(), date),
205                                                        os.getPrimaryState(), os.getPrimaryDerivative(),
206                                                        type);
207         for (DataDictionary.Entry initial : unmanaged.getData()) {
208             state = state.addAdditionalData(initial.getKey(), initial.getValue());
209         }
210         return state;
211     }
212 
213     /** {@inheritDoc} */
214     public Orbit propagateOrbit(final AbsoluteDate date) {
215         return basicPropagate(date).getOrbit();
216     }
217 
218     /** {@inheritDoc} */
219     protected double getMass(final AbsoluteDate date) {
220         return basicPropagate(date).getMass();
221     }
222 
223     /** Get the first date of the range.
224      * @return the first date of the range
225      */
226     public AbsoluteDate getMinDate() {
227         return minDate;
228     }
229 
230     /** Get the last date of the range.
231      * @return the last date of the range
232      */
233     public AbsoluteDate getMaxDate() {
234         return maxDate;
235     }
236 
237     @Override
238     public Frame getFrame() {
239         return this.mapper.getFrame();
240     }
241 
242     /** {@inheritDoc} */
243     @Override
244     public void resetInitialState(final SpacecraftState state) {
245         throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
246     }
247 
248     /** {@inheritDoc} */
249     protected void resetIntermediateState(final SpacecraftState state, final boolean forward) {
250         throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
251     }
252 
253     /** {@inheritDoc} */
254     @Override
255     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
256         super.setAttitudeProvider(attitudeProvider);
257         if (mapper != null) {
258             // At the construction, the mapper is not set yet
259             // However, if the attitude provider is changed afterwards, it must be changed in the mapper too
260             mapper.setAttitudeProvider(attitudeProvider);
261         }
262     }
263 
264     /** {@inheritDoc} */
265     @Override
266     public SpacecraftState getInitialState() {
267         return updateAdditionalData(basicPropagate(getMinDate()));
268     }
269 
270     /** {@inheritDoc} */
271     @Override
272     public SpacecraftState updateAdditionalData(final SpacecraftState original) {
273 
274         SpacecraftState updated = super.updateAdditionalData(original);
275 
276         if (equations.length > 0) {
277             final ODEStateAndDerivative osd                = getInterpolatedState(updated.getDate());
278             final double[]              combinedState      = osd.getSecondaryState(1);
279             final double[]              combinedDerivative = osd.getSecondaryDerivative(1);
280             int index = 0;
281             for (int i = 0; i < equations.length; ++i) {
282                 final double[] state      = Arrays.copyOfRange(combinedState,      index, index + dimensions[i]);
283                 final double[] derivative = Arrays.copyOfRange(combinedDerivative, index, index + dimensions[i]);
284                 updated = updated.
285                           addAdditionalData(equations[i], state).
286                           addAdditionalStateDerivative(equations[i], derivative);
287                 index += dimensions[i];
288             }
289         }
290 
291         return updated;
292 
293     }
294 
295 }