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 java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.Comparator;
23  import java.util.List;
24  import java.util.PriorityQueue;
25  import java.util.Queue;
26  import java.util.stream.Collectors;
27  
28  import org.hipparchus.exception.MathRuntimeException;
29  import org.hipparchus.ode.events.Action;
30  import org.orekit.attitudes.Attitude;
31  import org.orekit.attitudes.AttitudeProvider;
32  import org.orekit.errors.OrekitException;
33  import org.orekit.errors.OrekitIllegalArgumentException;
34  import org.orekit.errors.OrekitInternalError;
35  import org.orekit.errors.OrekitMessages;
36  import org.orekit.frames.Frame;
37  import org.orekit.orbits.Orbit;
38  import org.orekit.propagation.AbstractPropagator;
39  import org.orekit.propagation.AdditionalDataProvider;
40  import org.orekit.propagation.BoundedPropagator;
41  import org.orekit.propagation.EphemerisGenerator;
42  import org.orekit.propagation.MatricesHarvester;
43  import org.orekit.propagation.SpacecraftState;
44  import org.orekit.propagation.events.EventDetector;
45  import org.orekit.propagation.events.EventState;
46  import org.orekit.propagation.events.EventState.EventOccurrence;
47  import org.orekit.propagation.sampling.OrekitStepInterpolator;
48  import org.orekit.time.AbsoluteDate;
49  import org.orekit.utils.PVCoordinatesProvider;
50  import org.orekit.utils.TimeStampedPVCoordinates;
51  
52  /** Common handling of {@link org.orekit.propagation.Propagator} methods for analytical propagators.
53   * <p>
54   * This abstract class allows to provide easily the full set of {@link
55   * org.orekit.propagation.Propagator Propagator} methods, including all propagation
56   * modes support and discrete events support for any simple propagation method. Only
57   * two methods must be implemented by derived classes: {@link #propagateOrbit(AbsoluteDate)}
58   * and {@link #getMass(AbsoluteDate)}. The first method should perform straightforward
59   * propagation starting from some internally stored initial state up to the specified target date.
60   * </p>
61   * @author Luc Maisonobe
62   */
63  public abstract class AbstractAnalyticalPropagator extends AbstractPropagator {
64  
65      /** Provider for attitude computation. */
66      private final PVCoordinatesProvider pvProvider;
67  
68      /** Start date of last propagation. */
69      private AbsoluteDate lastPropagationStart;
70  
71      /** End date of last propagation. */
72      private AbsoluteDate lastPropagationEnd;
73  
74      /** Initialization indicator of events states. */
75      private boolean statesInitialized;
76  
77      /** Indicator for last step. */
78      private boolean isLastStep;
79  
80      /** User-defined event states. */
81      private final Collection<EventState<?>> userEventStates;
82  
83      /** All event states, including internal ones. */
84      private Collection<EventState<?>> eventStates;
85  
86      /** Build a new instance.
87       * @param attitudeProvider provider for attitude computation
88       */
89      protected AbstractAnalyticalPropagator(final AttitudeProvider attitudeProvider) {
90          setAttitudeProvider(attitudeProvider);
91          pvProvider           = new LocalPVProvider();
92          lastPropagationStart = AbsoluteDate.PAST_INFINITY;
93          lastPropagationEnd   = AbsoluteDate.FUTURE_INFINITY;
94          statesInitialized    = false;
95          userEventStates = new ArrayList<>();
96      }
97  
98      /** {@inheritDoc} */
99      @Override
100     public EphemerisGenerator getEphemerisGenerator() {
101         return () -> new BoundedPropagatorView(lastPropagationStart, lastPropagationEnd);
102     }
103 
104     /** {@inheritDoc} */
105     public <T extends EventDetector> void addEventDetector(final T detector) {
106         userEventStates.add(new EventState<>(detector));
107     }
108 
109     /** {@inheritDoc} */
110     public Collection<EventDetector> getEventDetectors() {
111         final List<EventDetector> list = new ArrayList<>();
112         for (final EventState<?> state : userEventStates) {
113             list.add(state.getEventDetector());
114         }
115         return Collections.unmodifiableCollection(list);
116     }
117 
118     /** {@inheritDoc} */
119     public void clearEventsDetectors() {
120         userEventStates.clear();
121     }
122 
123     /** {@inheritDoc} */
124     public SpacecraftState propagate(final AbsoluteDate start, final AbsoluteDate target) {
125         checkStartDateIsNotInfinity(start);
126         try {
127             initializePropagation();
128 
129             lastPropagationStart = start;
130 
131             // Initialize additional data
132             initializeAdditionalData(target);
133 
134             final boolean isForward = target.compareTo(start) >= 0;
135             SpacecraftState state   = updateAdditionalData(basicPropagate(start));
136 
137             // initialize event detectors
138             eventStates = getAttitudeProvider().getEventDetectors().map(EventState::new).collect(Collectors.toList());
139             eventStates.addAll(userEventStates);
140             for (final EventState<?> es : eventStates) {
141                 es.init(state, target);
142             }
143 
144             // initialize step handlers
145             getMultiplexer().init(state, target);
146 
147             // iterate over the propagation range, need loop due to reset events
148             statesInitialized = false;
149             isLastStep = false;
150             do {
151 
152                 // attempt to advance to the target date
153                 final SpacecraftState previous = state;
154                 final SpacecraftState current = updateAdditionalData(basicPropagate(target));
155                 final OrekitStepInterpolator interpolator =
156                         new BasicStepInterpolator(isForward, previous, current);
157 
158                 // accept the step, trigger events and step handlers
159                 state = acceptStep(interpolator, target);
160 
161                 // Update the potential changes in the spacecraft state due to the events
162                 // especially the potential attitude transition
163                 state = updateAdditionalData(basicPropagate(state.getDate()));
164 
165             } while (!isLastStep);
166 
167             // Finalize event detectors
168             for (final EventState<?> es : eventStates) {
169                 es.finish(state);
170             }
171 
172             // return the last computed state
173             lastPropagationEnd = state.getDate();
174             setStartDate(state.getDate());
175             return state;
176 
177         } catch (MathRuntimeException mrte) {
178             throw OrekitException.unwrap(mrte);
179         }
180     }
181 
182     /**
183      * Check the starting date is not {@code AbsoluteDate.PAST_INFINITY} or {@code AbsoluteDate.FUTURE_INFINITY}.
184      * @param start propagation starting date
185      */
186     private void checkStartDateIsNotInfinity(final AbsoluteDate start) {
187         if (start.isEqualTo(AbsoluteDate.PAST_INFINITY) || start.isEqualTo(AbsoluteDate.FUTURE_INFINITY)) {
188             throw new OrekitIllegalArgumentException(OrekitMessages.CANNOT_START_PROPAGATION_FROM_INFINITY);
189         }
190     }
191 
192     /** Accept a step, triggering events and step handlers.
193      * @param interpolator interpolator for the current step
194      * @param target final propagation time
195      * @return state at the end of the step
196      * @exception MathRuntimeException if an event cannot be located
197      */
198     protected SpacecraftState acceptStep(final OrekitStepInterpolator interpolator,
199                                          final AbsoluteDate target)
200         throws MathRuntimeException {
201 
202         SpacecraftState        previous   = interpolator.getPreviousState();
203         final SpacecraftState  current    = interpolator.getCurrentState();
204         OrekitStepInterpolator restricted = interpolator;
205 
206 
207         // initialize the events states if needed
208         if (!statesInitialized) {
209 
210             if (!eventStates.isEmpty()) {
211                 // initialize the events states
212                 for (final EventState<?> state : eventStates) {
213                     state.reinitializeBegin(interpolator);
214                 }
215             }
216 
217             statesInitialized = true;
218 
219         }
220 
221         // search for next events that may occur during the step
222         final int orderingSign = interpolator.isForward() ? +1 : -1;
223         final Queue<EventState<?>> occurringEvents = new PriorityQueue<>(new Comparator<EventState<?>>() {
224             /** {@inheritDoc} */
225             @Override
226             public int compare(final EventState<?> es0, final EventState<?> es1) {
227                 return orderingSign * es0.getEventDate().compareTo(es1.getEventDate());
228             }
229         });
230 
231         boolean doneWithStep = false;
232         resetEvents:
233         do {
234 
235             // Evaluate all event detectors for events
236             occurringEvents.clear();
237             for (final EventState<?> state : eventStates) {
238                 if (state.evaluateStep(interpolator)) {
239                     // the event occurs during the current step
240                     occurringEvents.add(state);
241                 }
242             }
243 
244             do {
245 
246                 eventLoop:
247                 while (!occurringEvents.isEmpty()) {
248 
249                     // handle the chronologically first event
250                     final EventState<?> currentEvent = occurringEvents.poll();
251 
252                     // get state at event time
253                     SpacecraftState eventState = restricted.getInterpolatedState(currentEvent.getEventDate());
254 
255                     // restrict the interpolator to the first part of the step, up to the event
256                     restricted = restricted.restrictStep(previous, eventState);
257 
258                     // try to advance all event states to current time
259                     for (final EventState<?> state : eventStates) {
260                         if (state != currentEvent && state.tryAdvance(eventState, interpolator)) {
261                             // we need to handle another event first
262                             // remove event we just updated to prevent heap corruption
263                             occurringEvents.remove(state);
264                             // add it back to update its position in the heap
265                             occurringEvents.add(state);
266                             // re-queue the event we were processing
267                             occurringEvents.add(currentEvent);
268                             continue eventLoop;
269                         }
270                     }
271                     // all event detectors agree we can advance to the current event time
272 
273                     // handle the first part of the step, up to the event
274                     getMultiplexer().handleStep(restricted);
275 
276                     // acknowledge event occurrence
277                     final EventOccurrence occurrence = currentEvent.doEvent(eventState);
278                     final Action action = occurrence.getAction();
279                     isLastStep = action == Action.STOP;
280 
281                     if (isLastStep) {
282 
283                         // ensure the event is after the root if it is returned STOP
284                         // this lets the user integrate to a STOP event and then restart
285                         // integration from the same time.
286                         final SpacecraftState savedState = eventState;
287                         eventState = interpolator.getInterpolatedState(occurrence.getStopDate());
288                         restricted = restricted.restrictStep(savedState, eventState);
289 
290                         // handle the almost zero size last part of the final step, at event time
291                         getMultiplexer().handleStep(restricted);
292                         getMultiplexer().finish(restricted.getCurrentState());
293 
294                     }
295 
296                     if (isLastStep) {
297                         // the event asked to stop integration
298                         return eventState;
299                     }
300 
301                     if (action == Action.RESET_DERIVATIVES || action == Action.RESET_STATE) {
302                         // some event handler has triggered changes that
303                         // invalidate the derivatives, we need to recompute them
304                         final SpacecraftState resetState = occurrence.getNewState();
305                         resetIntermediateState(resetState, interpolator.isForward());
306                         eventStates.forEach(es -> es.getEventDetector().reset(resetState, target));
307                         return resetState;
308                     }
309                     // at this point action == Action.CONTINUE or Action.RESET_EVENTS
310 
311                     // prepare handling of the remaining part of the step
312                     previous = eventState;
313                     restricted = new BasicStepInterpolator(restricted.isForward(), eventState, current);
314 
315                     if (action == Action.RESET_EVENTS) {
316                         continue resetEvents;
317                     }
318 
319                     // at this point action == Action.CONTINUE
320                     // check if the same event occurs again in the remaining part of the step
321                     if (currentEvent.evaluateStep(restricted)) {
322                         // the event occurs during the current step
323                         occurringEvents.add(currentEvent);
324                     }
325 
326                 }
327 
328                 // last part of the step, after the last event. Advance all detectors to
329                 // the end of the step. Should only detect a new event here if an event
330                 // modified the g function of another detector. Detecting such events here
331                 // is unreliable and RESET_EVENTS should be used instead. Might as well
332                 // re-check here because we have to loop through all the detectors anyway
333                 // and the alternative is to throw an exception.
334                 for (final EventState<?> state : eventStates) {
335                     if (state.tryAdvance(current, interpolator)) {
336                         occurringEvents.add(state);
337                     }
338                 }
339 
340             } while (!occurringEvents.isEmpty());
341 
342             doneWithStep = true;
343         } while (!doneWithStep);
344 
345         isLastStep = target.equals(current.getDate());
346 
347         // handle the remaining part of the step, after all events if any
348         getMultiplexer().handleStep(restricted);
349         if (isLastStep) {
350             getMultiplexer().finish(restricted.getCurrentState());
351         }
352 
353         return current;
354 
355     }
356 
357     /** Get the mass.
358      * @param date target date for the orbit
359      * @return mass mass
360      */
361     protected abstract double getMass(AbsoluteDate date);
362 
363     /** Get PV coordinates provider.
364      * @return PV coordinates provider
365      */
366     public PVCoordinatesProvider getPvProvider() {
367         return pvProvider;
368     }
369 
370     /** Reset an intermediate state.
371      * @param state new intermediate state to consider
372      * @param forward if true, the intermediate state is valid for
373      * propagations after itself
374      */
375     protected abstract void resetIntermediateState(SpacecraftState state, boolean forward);
376 
377     /** Extrapolate an orbit up to a specific target date.
378      * @param date target date for the orbit
379      * @return extrapolated parameters
380      */
381     public abstract Orbit propagateOrbit(AbsoluteDate date);
382 
383     /** Propagate an orbit without any fancy features.
384      * <p>This method is similar in spirit to the {@link #propagate} method,
385      * except that it does <strong>not</strong> call any handler during
386      * propagation, nor any discrete events, not additional states. It always
387      * stops exactly at the specified date.</p>
388      * @param date target date for propagation
389      * @return state at specified date
390      */
391     public SpacecraftState basicPropagate(final AbsoluteDate date) {
392         try {
393 
394             // evaluate orbit
395             final Orbit orbit = propagateOrbit(date);
396 
397             // evaluate attitude
398             final Attitude attitude =
399                 getAttitudeProvider().getAttitude(pvProvider, date, orbit.getFrame());
400 
401             // build raw state
402             return new SpacecraftState(orbit, attitude).withMass(getMass(date));
403 
404         } catch (OrekitException oe) {
405             throw new OrekitException(oe);
406         }
407     }
408 
409     @Override
410     public void clearMatricesComputation() {
411         final List<AdditionalDataProvider<?>> copiedProviders = new ArrayList<>(getAdditionalDataProviders());
412         copiedProviders.stream().filter(AbstractAnalyticalMatricesHarvester.class::isInstance)
413                 .forEach(provider -> removeAdditionalDataProvider(provider.getName()));
414         super.clearMatricesComputation();
415     }
416 
417     /**
418      * Get the names of the parameters in the matrix returned by {@link MatricesHarvester#getParametersJacobian}.
419      * @return names of the parameters (i.e. columns) of the Jacobian matrix
420      * @since 11.1
421      */
422     protected List<String> getJacobiansColumnsNames() {
423         return Collections.emptyList();
424     }
425 
426     /** Internal PVCoordinatesProvider for attitude computation. */
427     private class LocalPVProvider implements PVCoordinatesProvider {
428 
429         /** {@inheritDoc} */
430         public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
431             return propagateOrbit(date).getPVCoordinates(frame);
432         }
433 
434     }
435 
436     /** {@link BoundedPropagator} view of the instance. */
437     private class BoundedPropagatorView extends AbstractAnalyticalPropagator implements BoundedPropagator {
438 
439         /** Min date. */
440         private final AbsoluteDate minDate;
441 
442         /** Max date. */
443         private final AbsoluteDate maxDate;
444 
445         /** Simple constructor.
446          * @param startDate start date of the propagation
447          * @param endDate end date of the propagation
448          */
449         BoundedPropagatorView(final AbsoluteDate startDate, final AbsoluteDate endDate) {
450             super(AbstractAnalyticalPropagator.this.getAttitudeProvider());
451             super.resetInitialState(AbstractAnalyticalPropagator.this.getInitialState());
452             if (startDate.compareTo(endDate) <= 0) {
453                 minDate = startDate;
454                 maxDate = endDate;
455             } else {
456                 minDate = endDate;
457                 maxDate = startDate;
458             }
459 
460             try {
461                 // copy the same additional state providers as the original propagator
462                 for (AdditionalDataProvider<?> provider : AbstractAnalyticalPropagator.this.getAdditionalDataProviders()) {
463                     addAdditionalDataProvider(provider);
464                 }
465             } catch (OrekitException oe) {
466                 // as the generators are already compatible with each other,
467                 // this should never happen
468                 throw new OrekitInternalError(null);
469             }
470 
471         }
472 
473         /** {@inheritDoc} */
474         public AbsoluteDate getMinDate() {
475             return minDate;
476         }
477 
478         /** {@inheritDoc} */
479         public AbsoluteDate getMaxDate() {
480             return maxDate;
481         }
482 
483         /** {@inheritDoc} */
484         public Orbit propagateOrbit(final AbsoluteDate target) {
485             return AbstractAnalyticalPropagator.this.propagateOrbit(target);
486         }
487 
488         /** {@inheritDoc} */
489         public double getMass(final AbsoluteDate date) {
490             return AbstractAnalyticalPropagator.this.getMass(date);
491         }
492 
493         /** {@inheritDoc} */
494         @Override
495         public void resetInitialState(final SpacecraftState state) {
496             super.resetInitialState(state);
497             AbstractAnalyticalPropagator.this.resetInitialState(state);
498         }
499 
500         /** {@inheritDoc} */
501         protected void resetIntermediateState(final SpacecraftState state, final boolean forward) {
502             AbstractAnalyticalPropagator.this.resetIntermediateState(state, forward);
503         }
504 
505         /** {@inheritDoc} */
506         @Override
507         public SpacecraftState getInitialState() {
508             return AbstractAnalyticalPropagator.this.getInitialState();
509         }
510 
511         /** {@inheritDoc} */
512         @Override
513         public Frame getFrame() {
514             return AbstractAnalyticalPropagator.this.getFrame();
515         }
516 
517     }
518 
519     /** Internal class for local propagation. */
520     private class BasicStepInterpolator implements OrekitStepInterpolator {
521 
522         /** Previous state. */
523         private final SpacecraftState previousState;
524 
525         /** Current state. */
526         private final SpacecraftState currentState;
527 
528         /** Forward propagation indicator. */
529         private final boolean forward;
530 
531         /** Simple constructor.
532          * @param isForward integration direction indicator
533          * @param previousState start of the step
534          * @param currentState end of the step
535          */
536         BasicStepInterpolator(final boolean isForward,
537                               final SpacecraftState previousState,
538                               final SpacecraftState currentState) {
539             this.forward         = isForward;
540             this.previousState   = previousState;
541             this.currentState    = currentState;
542         }
543 
544         /** {@inheritDoc} */
545         @Override
546         public SpacecraftState getPreviousState() {
547             return previousState;
548         }
549 
550         /** {@inheritDoc} */
551         @Override
552         public boolean isPreviousStateInterpolated() {
553             // no difference in analytical propagators
554             return false;
555         }
556 
557         /** {@inheritDoc} */
558         @Override
559         public SpacecraftState getCurrentState() {
560             return currentState;
561         }
562 
563         /** {@inheritDoc} */
564         @Override
565         public boolean isCurrentStateInterpolated() {
566             // no difference in analytical propagators
567             return false;
568         }
569 
570         /** {@inheritDoc} */
571         @Override
572         public SpacecraftState getInterpolatedState(final AbsoluteDate date) {
573 
574             // compute the basic spacecraft state
575             final SpacecraftState basicState = basicPropagate(date);
576 
577             // add the additional states
578             return updateAdditionalData(basicState);
579 
580         }
581 
582         /** {@inheritDoc} */
583         @Override
584         public boolean isForward() {
585             return forward;
586         }
587 
588         /** {@inheritDoc} */
589         @Override
590         public BasicStepInterpolator restrictStep(final SpacecraftState newPreviousState,
591                                                   final SpacecraftState newCurrentState) {
592             return new BasicStepInterpolator(forward, newPreviousState, newCurrentState);
593         }
594 
595     }
596 
597 }