AbstractAnalyticalPropagator.java

  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. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.Comparator;
  22. import java.util.List;
  23. import java.util.PriorityQueue;
  24. import java.util.Queue;
  25. import java.util.stream.Collectors;

  26. import org.hipparchus.exception.MathRuntimeException;
  27. import org.hipparchus.ode.events.Action;
  28. import org.orekit.attitudes.Attitude;
  29. import org.orekit.attitudes.AttitudeProvider;
  30. import org.orekit.errors.OrekitException;
  31. import org.orekit.errors.OrekitIllegalArgumentException;
  32. import org.orekit.errors.OrekitInternalError;
  33. import org.orekit.errors.OrekitMessages;
  34. import org.orekit.frames.Frame;
  35. import org.orekit.orbits.Orbit;
  36. import org.orekit.propagation.AbstractPropagator;
  37. import org.orekit.propagation.AdditionalDataProvider;
  38. import org.orekit.propagation.BoundedPropagator;
  39. import org.orekit.propagation.EphemerisGenerator;
  40. import org.orekit.propagation.MatricesHarvester;
  41. import org.orekit.propagation.SpacecraftState;
  42. import org.orekit.propagation.events.EventDetector;
  43. import org.orekit.propagation.events.EventState;
  44. import org.orekit.propagation.events.EventState.EventOccurrence;
  45. import org.orekit.propagation.sampling.OrekitStepInterpolator;
  46. import org.orekit.time.AbsoluteDate;
  47. import org.orekit.utils.PVCoordinatesProvider;
  48. import org.orekit.utils.TimeStampedPVCoordinates;

  49. /** Common handling of {@link org.orekit.propagation.Propagator} methods for analytical propagators.
  50.  * <p>
  51.  * This abstract class allows to provide easily the full set of {@link
  52.  * org.orekit.propagation.Propagator Propagator} methods, including all propagation
  53.  * modes support and discrete events support for any simple propagation method. Only
  54.  * two methods must be implemented by derived classes: {@link #propagateOrbit(AbsoluteDate)}
  55.  * and {@link #getMass(AbsoluteDate)}. The first method should perform straightforward
  56.  * propagation starting from some internally stored initial state up to the specified target date.
  57.  * </p>
  58.  * @author Luc Maisonobe
  59.  */
  60. public abstract class AbstractAnalyticalPropagator extends AbstractPropagator {

  61.     /** Provider for attitude computation. */
  62.     private final PVCoordinatesProvider pvProvider;

  63.     /** Start date of last propagation. */
  64.     private AbsoluteDate lastPropagationStart;

  65.     /** End date of last propagation. */
  66.     private AbsoluteDate lastPropagationEnd;

  67.     /** Initialization indicator of events states. */
  68.     private boolean statesInitialized;

  69.     /** Indicator for last step. */
  70.     private boolean isLastStep;

  71.     /** User-defined event states. */
  72.     private final Collection<EventState<?>> userEventStates;

  73.     /** All event states, including internal ones. */
  74.     private Collection<EventState<?>> eventStates;

  75.     /** Build a new instance.
  76.      * @param attitudeProvider provider for attitude computation
  77.      */
  78.     protected AbstractAnalyticalPropagator(final AttitudeProvider attitudeProvider) {
  79.         setAttitudeProvider(attitudeProvider);
  80.         pvProvider           = new LocalPVProvider();
  81.         lastPropagationStart = AbsoluteDate.PAST_INFINITY;
  82.         lastPropagationEnd   = AbsoluteDate.FUTURE_INFINITY;
  83.         statesInitialized    = false;
  84.         userEventStates = new ArrayList<>();
  85.     }

  86.     /** {@inheritDoc} */
  87.     @Override
  88.     public EphemerisGenerator getEphemerisGenerator() {
  89.         return () -> new BoundedPropagatorView(lastPropagationStart, lastPropagationEnd);
  90.     }

  91.     /** {@inheritDoc} */
  92.     public <T extends EventDetector> void addEventDetector(final T detector) {
  93.         userEventStates.add(new EventState<>(detector));
  94.     }

  95.     /** {@inheritDoc} */
  96.     public Collection<EventDetector> getEventDetectors() {
  97.         final List<EventDetector> list = new ArrayList<>();
  98.         for (final EventState<?> state : userEventStates) {
  99.             list.add(state.getEventDetector());
  100.         }
  101.         return Collections.unmodifiableCollection(list);
  102.     }

  103.     /** {@inheritDoc} */
  104.     public void clearEventsDetectors() {
  105.         userEventStates.clear();
  106.     }

  107.     /** {@inheritDoc} */
  108.     public SpacecraftState propagate(final AbsoluteDate start, final AbsoluteDate target) {
  109.         checkStartDateIsNotInfinity(start);
  110.         try {
  111.             initializePropagation();

  112.             lastPropagationStart = start;

  113.             // Initialize additional data
  114.             initializeAdditionalData(target);

  115.             final boolean isForward = target.compareTo(start) >= 0;
  116.             SpacecraftState state   = updateAdditionalData(basicPropagate(start));

  117.             // initialize event detectors
  118.             eventStates = getAttitudeProvider().getEventDetectors().map(EventState::new).collect(Collectors.toList());
  119.             eventStates.addAll(userEventStates);
  120.             for (final EventState<?> es : eventStates) {
  121.                 es.init(state, target);
  122.             }

  123.             // initialize step handlers
  124.             getMultiplexer().init(state, target);

  125.             // iterate over the propagation range, need loop due to reset events
  126.             statesInitialized = false;
  127.             isLastStep = false;
  128.             do {

  129.                 // attempt to advance to the target date
  130.                 final SpacecraftState previous = state;
  131.                 final SpacecraftState current = updateAdditionalData(basicPropagate(target));
  132.                 final OrekitStepInterpolator interpolator =
  133.                         new BasicStepInterpolator(isForward, previous, current);

  134.                 // accept the step, trigger events and step handlers
  135.                 state = acceptStep(interpolator, target);

  136.                 // Update the potential changes in the spacecraft state due to the events
  137.                 // especially the potential attitude transition
  138.                 state = updateAdditionalData(basicPropagate(state.getDate()));

  139.             } while (!isLastStep);

  140.             // Finalize event detectors
  141.             for (final EventState<?> es : eventStates) {
  142.                 es.finish(state);
  143.             }

  144.             // return the last computed state
  145.             lastPropagationEnd = state.getDate();
  146.             setStartDate(state.getDate());
  147.             return state;

  148.         } catch (MathRuntimeException mrte) {
  149.             throw OrekitException.unwrap(mrte);
  150.         }
  151.     }

  152.     /**
  153.      * Check the starting date is not {@code AbsoluteDate.PAST_INFINITY} or {@code AbsoluteDate.FUTURE_INFINITY}.
  154.      * @param start propagation starting date
  155.      */
  156.     private void checkStartDateIsNotInfinity(final AbsoluteDate start) {
  157.         if (start.isEqualTo(AbsoluteDate.PAST_INFINITY) || start.isEqualTo(AbsoluteDate.FUTURE_INFINITY)) {
  158.             throw new OrekitIllegalArgumentException(OrekitMessages.CANNOT_START_PROPAGATION_FROM_INFINITY);
  159.         }
  160.     }

  161.     /** Accept a step, triggering events and step handlers.
  162.      * @param interpolator interpolator for the current step
  163.      * @param target final propagation time
  164.      * @return state at the end of the step
  165.      * @exception MathRuntimeException if an event cannot be located
  166.      */
  167.     protected SpacecraftState acceptStep(final OrekitStepInterpolator interpolator,
  168.                                          final AbsoluteDate target)
  169.         throws MathRuntimeException {

  170.         SpacecraftState        previous   = interpolator.getPreviousState();
  171.         final SpacecraftState  current    = interpolator.getCurrentState();
  172.         OrekitStepInterpolator restricted = interpolator;


  173.         // initialize the events states if needed
  174.         if (!statesInitialized) {

  175.             if (!eventStates.isEmpty()) {
  176.                 // initialize the events states
  177.                 for (final EventState<?> state : eventStates) {
  178.                     state.reinitializeBegin(interpolator);
  179.                 }
  180.             }

  181.             statesInitialized = true;

  182.         }

  183.         // search for next events that may occur during the step
  184.         final int orderingSign = interpolator.isForward() ? +1 : -1;
  185.         final Queue<EventState<?>> occurringEvents = new PriorityQueue<>(new Comparator<EventState<?>>() {
  186.             /** {@inheritDoc} */
  187.             @Override
  188.             public int compare(final EventState<?> es0, final EventState<?> es1) {
  189.                 return orderingSign * es0.getEventDate().compareTo(es1.getEventDate());
  190.             }
  191.         });

  192.         boolean doneWithStep = false;
  193.         resetEvents:
  194.         do {

  195.             // Evaluate all event detectors for events
  196.             occurringEvents.clear();
  197.             for (final EventState<?> state : eventStates) {
  198.                 if (state.evaluateStep(interpolator)) {
  199.                     // the event occurs during the current step
  200.                     occurringEvents.add(state);
  201.                 }
  202.             }

  203.             do {

  204.                 eventLoop:
  205.                 while (!occurringEvents.isEmpty()) {

  206.                     // handle the chronologically first event
  207.                     final EventState<?> currentEvent = occurringEvents.poll();

  208.                     // get state at event time
  209.                     SpacecraftState eventState = restricted.getInterpolatedState(currentEvent.getEventDate());

  210.                     // restrict the interpolator to the first part of the step, up to the event
  211.                     restricted = restricted.restrictStep(previous, eventState);

  212.                     // try to advance all event states to current time
  213.                     for (final EventState<?> state : eventStates) {
  214.                         if (state != currentEvent && state.tryAdvance(eventState, interpolator)) {
  215.                             // we need to handle another event first
  216.                             // remove event we just updated to prevent heap corruption
  217.                             occurringEvents.remove(state);
  218.                             // add it back to update its position in the heap
  219.                             occurringEvents.add(state);
  220.                             // re-queue the event we were processing
  221.                             occurringEvents.add(currentEvent);
  222.                             continue eventLoop;
  223.                         }
  224.                     }
  225.                     // all event detectors agree we can advance to the current event time

  226.                     // handle the first part of the step, up to the event
  227.                     getMultiplexer().handleStep(restricted);

  228.                     // acknowledge event occurrence
  229.                     final EventOccurrence occurrence = currentEvent.doEvent(eventState);
  230.                     final Action action = occurrence.getAction();
  231.                     isLastStep = action == Action.STOP;

  232.                     if (isLastStep) {

  233.                         // ensure the event is after the root if it is returned STOP
  234.                         // this lets the user integrate to a STOP event and then restart
  235.                         // integration from the same time.
  236.                         final SpacecraftState savedState = eventState;
  237.                         eventState = interpolator.getInterpolatedState(occurrence.getStopDate());
  238.                         restricted = restricted.restrictStep(savedState, eventState);

  239.                         // handle the almost zero size last part of the final step, at event time
  240.                         getMultiplexer().handleStep(restricted);
  241.                         getMultiplexer().finish(restricted.getCurrentState());

  242.                     }

  243.                     if (isLastStep) {
  244.                         // the event asked to stop integration
  245.                         return eventState;
  246.                     }

  247.                     if (action == Action.RESET_DERIVATIVES || action == Action.RESET_STATE) {
  248.                         // some event handler has triggered changes that
  249.                         // invalidate the derivatives, we need to recompute them
  250.                         final SpacecraftState resetState = occurrence.getNewState();
  251.                         resetIntermediateState(resetState, interpolator.isForward());
  252.                         eventStates.forEach(es -> es.getEventDetector().reset(resetState, target));
  253.                         return resetState;
  254.                     }
  255.                     // at this point action == Action.CONTINUE or Action.RESET_EVENTS

  256.                     // prepare handling of the remaining part of the step
  257.                     previous = eventState;
  258.                     restricted = new BasicStepInterpolator(restricted.isForward(), eventState, current);

  259.                     if (action == Action.RESET_EVENTS) {
  260.                         continue resetEvents;
  261.                     }

  262.                     // at this point action == Action.CONTINUE
  263.                     // check if the same event occurs again in the remaining part of the step
  264.                     if (currentEvent.evaluateStep(restricted)) {
  265.                         // the event occurs during the current step
  266.                         occurringEvents.add(currentEvent);
  267.                     }

  268.                 }

  269.                 // last part of the step, after the last event. Advance all detectors to
  270.                 // the end of the step. Should only detect a new event here if an event
  271.                 // modified the g function of another detector. Detecting such events here
  272.                 // is unreliable and RESET_EVENTS should be used instead. Might as well
  273.                 // re-check here because we have to loop through all the detectors anyway
  274.                 // and the alternative is to throw an exception.
  275.                 for (final EventState<?> state : eventStates) {
  276.                     if (state.tryAdvance(current, interpolator)) {
  277.                         occurringEvents.add(state);
  278.                     }
  279.                 }

  280.             } while (!occurringEvents.isEmpty());

  281.             doneWithStep = true;
  282.         } while (!doneWithStep);

  283.         isLastStep = target.equals(current.getDate());

  284.         // handle the remaining part of the step, after all events if any
  285.         getMultiplexer().handleStep(restricted);
  286.         if (isLastStep) {
  287.             getMultiplexer().finish(restricted.getCurrentState());
  288.         }

  289.         return current;

  290.     }

  291.     /** Get the mass.
  292.      * @param date target date for the orbit
  293.      * @return mass mass
  294.      */
  295.     protected abstract double getMass(AbsoluteDate date);

  296.     /** Get PV coordinates provider.
  297.      * @return PV coordinates provider
  298.      */
  299.     public PVCoordinatesProvider getPvProvider() {
  300.         return pvProvider;
  301.     }

  302.     /** Reset an intermediate state.
  303.      * @param state new intermediate state to consider
  304.      * @param forward if true, the intermediate state is valid for
  305.      * propagations after itself
  306.      */
  307.     protected abstract void resetIntermediateState(SpacecraftState state, boolean forward);

  308.     /** Extrapolate an orbit up to a specific target date.
  309.      * @param date target date for the orbit
  310.      * @return extrapolated parameters
  311.      */
  312.     public abstract Orbit propagateOrbit(AbsoluteDate date);

  313.     /** Propagate an orbit without any fancy features.
  314.      * <p>This method is similar in spirit to the {@link #propagate} method,
  315.      * except that it does <strong>not</strong> call any handler during
  316.      * propagation, nor any discrete events, not additional states. It always
  317.      * stops exactly at the specified date.</p>
  318.      * @param date target date for propagation
  319.      * @return state at specified date
  320.      */
  321.     public SpacecraftState basicPropagate(final AbsoluteDate date) {
  322.         try {

  323.             // evaluate orbit
  324.             final Orbit orbit = propagateOrbit(date);

  325.             // evaluate attitude
  326.             final Attitude attitude =
  327.                 getAttitudeProvider().getAttitude(pvProvider, date, orbit.getFrame());

  328.             // build raw state
  329.             return new SpacecraftState(orbit, attitude).withMass(getMass(date));

  330.         } catch (OrekitException oe) {
  331.             throw new OrekitException(oe);
  332.         }
  333.     }

  334.     /**
  335.      * Get the names of the parameters in the matrix returned by {@link MatricesHarvester#getParametersJacobian}.
  336.      * @return names of the parameters (i.e. columns) of the Jacobian matrix
  337.      * @since 11.1
  338.      */
  339.     protected List<String> getJacobiansColumnsNames() {
  340.         return Collections.emptyList();
  341.     }

  342.     /** Internal PVCoordinatesProvider for attitude computation. */
  343.     private class LocalPVProvider implements PVCoordinatesProvider {

  344.         /** {@inheritDoc} */
  345.         public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame) {
  346.             return propagateOrbit(date).getPVCoordinates(frame);
  347.         }

  348.     }

  349.     /** {@link BoundedPropagator} view of the instance. */
  350.     private class BoundedPropagatorView extends AbstractAnalyticalPropagator implements BoundedPropagator {

  351.         /** Min date. */
  352.         private final AbsoluteDate minDate;

  353.         /** Max date. */
  354.         private final AbsoluteDate maxDate;

  355.         /** Simple constructor.
  356.          * @param startDate start date of the propagation
  357.          * @param endDate end date of the propagation
  358.          */
  359.         BoundedPropagatorView(final AbsoluteDate startDate, final AbsoluteDate endDate) {
  360.             super(AbstractAnalyticalPropagator.this.getAttitudeProvider());
  361.             super.resetInitialState(AbstractAnalyticalPropagator.this.getInitialState());
  362.             if (startDate.compareTo(endDate) <= 0) {
  363.                 minDate = startDate;
  364.                 maxDate = endDate;
  365.             } else {
  366.                 minDate = endDate;
  367.                 maxDate = startDate;
  368.             }

  369.             try {
  370.                 // copy the same additional state providers as the original propagator
  371.                 for (AdditionalDataProvider<?> provider : AbstractAnalyticalPropagator.this.getAdditionalDataProviders()) {
  372.                     addAdditionalDataProvider(provider);
  373.                 }
  374.             } catch (OrekitException oe) {
  375.                 // as the generators are already compatible with each other,
  376.                 // this should never happen
  377.                 throw new OrekitInternalError(null);
  378.             }

  379.         }

  380.         /** {@inheritDoc} */
  381.         public AbsoluteDate getMinDate() {
  382.             return minDate;
  383.         }

  384.         /** {@inheritDoc} */
  385.         public AbsoluteDate getMaxDate() {
  386.             return maxDate;
  387.         }

  388.         /** {@inheritDoc} */
  389.         public Orbit propagateOrbit(final AbsoluteDate target) {
  390.             return AbstractAnalyticalPropagator.this.propagateOrbit(target);
  391.         }

  392.         /** {@inheritDoc} */
  393.         public double getMass(final AbsoluteDate date) {
  394.             return AbstractAnalyticalPropagator.this.getMass(date);
  395.         }

  396.         /** {@inheritDoc} */
  397.         @Override
  398.         public void resetInitialState(final SpacecraftState state) {
  399.             super.resetInitialState(state);
  400.             AbstractAnalyticalPropagator.this.resetInitialState(state);
  401.         }

  402.         /** {@inheritDoc} */
  403.         protected void resetIntermediateState(final SpacecraftState state, final boolean forward) {
  404.             AbstractAnalyticalPropagator.this.resetIntermediateState(state, forward);
  405.         }

  406.         /** {@inheritDoc} */
  407.         @Override
  408.         public SpacecraftState getInitialState() {
  409.             return AbstractAnalyticalPropagator.this.getInitialState();
  410.         }

  411.         /** {@inheritDoc} */
  412.         @Override
  413.         public Frame getFrame() {
  414.             return AbstractAnalyticalPropagator.this.getFrame();
  415.         }

  416.     }

  417.     /** Internal class for local propagation. */
  418.     private class BasicStepInterpolator implements OrekitStepInterpolator {

  419.         /** Previous state. */
  420.         private final SpacecraftState previousState;

  421.         /** Current state. */
  422.         private final SpacecraftState currentState;

  423.         /** Forward propagation indicator. */
  424.         private final boolean forward;

  425.         /** Simple constructor.
  426.          * @param isForward integration direction indicator
  427.          * @param previousState start of the step
  428.          * @param currentState end of the step
  429.          */
  430.         BasicStepInterpolator(final boolean isForward,
  431.                               final SpacecraftState previousState,
  432.                               final SpacecraftState currentState) {
  433.             this.forward         = isForward;
  434.             this.previousState   = previousState;
  435.             this.currentState    = currentState;
  436.         }

  437.         /** {@inheritDoc} */
  438.         @Override
  439.         public SpacecraftState getPreviousState() {
  440.             return previousState;
  441.         }

  442.         /** {@inheritDoc} */
  443.         @Override
  444.         public boolean isPreviousStateInterpolated() {
  445.             // no difference in analytical propagators
  446.             return false;
  447.         }

  448.         /** {@inheritDoc} */
  449.         @Override
  450.         public SpacecraftState getCurrentState() {
  451.             return currentState;
  452.         }

  453.         /** {@inheritDoc} */
  454.         @Override
  455.         public boolean isCurrentStateInterpolated() {
  456.             // no difference in analytical propagators
  457.             return false;
  458.         }

  459.         /** {@inheritDoc} */
  460.         @Override
  461.         public SpacecraftState getInterpolatedState(final AbsoluteDate date) {

  462.             // compute the basic spacecraft state
  463.             final SpacecraftState basicState = basicPropagate(date);

  464.             // add the additional states
  465.             return updateAdditionalData(basicState);

  466.         }

  467.         /** {@inheritDoc} */
  468.         @Override
  469.         public boolean isForward() {
  470.             return forward;
  471.         }

  472.         /** {@inheritDoc} */
  473.         @Override
  474.         public BasicStepInterpolator restrictStep(final SpacecraftState newPreviousState,
  475.                                                   final SpacecraftState newCurrentState) {
  476.             return new BasicStepInterpolator(forward, newPreviousState, newCurrentState);
  477.         }

  478.     }

  479. }