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