FieldAbstractAnalyticalPropagator.java

/* Copyright 2002-2024 CS GROUP
 * Licensed to CS GROUP (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.orekit.propagation.analytical;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;

import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.hipparchus.exception.MathRuntimeException;
import org.hipparchus.ode.events.Action;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.attitudes.FieldAttitude;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitInternalError;
import org.orekit.frames.Frame;
import org.orekit.orbits.FieldOrbit;
import org.orekit.propagation.BoundedPropagator;
import org.orekit.propagation.FieldAbstractPropagator;
import org.orekit.propagation.FieldAdditionalStateProvider;
import org.orekit.propagation.FieldBoundedPropagator;
import org.orekit.propagation.FieldEphemerisGenerator;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.propagation.events.FieldEventDetector;
import org.orekit.propagation.events.FieldEventState;
import org.orekit.propagation.events.FieldEventState.EventOccurrence;
import org.orekit.propagation.sampling.FieldOrekitStepInterpolator;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.utils.FieldPVCoordinatesProvider;
import org.orekit.utils.ParameterDriver;
import org.orekit.utils.ParameterDriversProvider;
import org.orekit.utils.TimeStampedFieldPVCoordinates;

/** Common handling of {@link org.orekit.propagation.FieldPropagator} methods for analytical propagators.
 * <p>
 * This abstract class allows to provide easily the full set of {@link
 * org.orekit.propagation.FieldPropagator FieldPropagator} methods, including all propagation
 * modes support and discrete events support for any simple propagation method. Only
 * two methods must be implemented by derived classes: {@link #propagateOrbit(FieldAbsoluteDate, CalculusFieldElement[])}
 * and {@link #getMass(FieldAbsoluteDate)}. The first method should perform straightforward
 * propagation starting from some internally stored initial state up to the specified target date.
 * </p>
 * @author Luc Maisonobe
 * @param <T> type of the field elements
 */

public abstract class FieldAbstractAnalyticalPropagator<T extends CalculusFieldElement<T>> extends FieldAbstractPropagator<T>
                                                                                           implements ParameterDriversProvider {

    /** Provider for attitude computation. */
    private FieldPVCoordinatesProvider<T> pvProvider;

    /** Start date of last propagation. */
    private FieldAbsoluteDate<T> lastPropagationStart;

    /** End date of last propagation. */
    private FieldAbsoluteDate<T> lastPropagationEnd;

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

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

    /** Event steps. */
    private final Collection<FieldEventState<?, T>> eventsStates;

    /** Build a new instance.
     * @param attitudeProvider provider for attitude computation
     * @param field field used as default
     */
    protected FieldAbstractAnalyticalPropagator(final Field<T> field, final AttitudeProvider attitudeProvider) {
        super(field);
        setAttitudeProvider(attitudeProvider);
        pvProvider           = new FieldLocalPVProvider();
        lastPropagationStart = FieldAbsoluteDate.getPastInfinity(field);
        lastPropagationEnd   = FieldAbsoluteDate.getFutureInfinity(field);
        statesInitialized    = false;
        eventsStates         = new ArrayList<>();
    }

    /** {@inheritDoc} */
    @Override
    public FieldEphemerisGenerator<T> getEphemerisGenerator() {
        return () -> new FieldBoundedPropagatorView(lastPropagationStart, lastPropagationEnd);
    }

    /** {@inheritDoc} */
    public <D extends FieldEventDetector<T>> void addEventDetector(final D detector) {
        eventsStates.add(new FieldEventState<>(detector));
    }

    /** {@inheritDoc} */
    @Override
    public Collection<FieldEventDetector<T>> getEventsDetectors() {
        final List<FieldEventDetector<T>> list = new ArrayList<>();
        for (final FieldEventState<?, T> state : eventsStates) {
            list.add(state.getEventDetector());
        }
        return Collections.unmodifiableCollection(list);
    }

    /** {@inheritDoc} */
    @Override
    public void clearEventsDetectors() {
        eventsStates.clear();
    }
    /** {@inheritDoc} */
    @Override
    public FieldSpacecraftState<T> propagate(final FieldAbsoluteDate<T> start, final FieldAbsoluteDate<T> target) {
        try {

            initializePropagation();

            lastPropagationStart = start;

            // Initialize additional states
            initializeAdditionalStates(target);

            final boolean           isForward = target.compareTo(start) >= 0;
            FieldSpacecraftState<T> state   = updateAdditionalStates(basicPropagate(start));

            // initialize event detectors
            for (final FieldEventState<?, T> es : eventsStates) {
                es.init(state, target);
            }

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

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

                // attempt to advance to the target date
                final FieldSpacecraftState<T> previous = state;
                final FieldSpacecraftState<T> current = updateAdditionalStates(basicPropagate(target));
                final FieldBasicStepInterpolator interpolator =
                        new FieldBasicStepInterpolator(isForward, previous, current);

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

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

            } while (!isLastStep);

            // return the last computed state
            lastPropagationEnd = state.getDate();
            setStartDate(state.getDate());
            return state;

        } catch (MathRuntimeException mrte) {
            throw OrekitException.unwrap(mrte);
        }
    }

    /** Accept a step, triggering events and step handlers.
     * @param interpolator interpolator for the current step
     * @param target final propagation time
     * @return state at the end of the step
     * @exception MathRuntimeException if an event cannot be located
     */
    protected FieldSpacecraftState<T> acceptStep(final FieldBasicStepInterpolator interpolator,
                                                 final FieldAbsoluteDate<T> target)
        throws MathRuntimeException {

        FieldSpacecraftState<T>       previous   = interpolator.getPreviousState();
        final FieldSpacecraftState<T> current    = interpolator.getCurrentState();
        FieldBasicStepInterpolator    restricted = interpolator;

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

            if (!eventsStates.isEmpty()) {
                // initialize the events states
                for (final FieldEventState<?, T> state : eventsStates) {
                    state.reinitializeBegin(interpolator);
                }
            }

            statesInitialized = true;

        }

        // search for next events that may occur during the step
        final int orderingSign = interpolator.isForward() ? +1 : -1;
        final Queue<FieldEventState<?, T>> occurringEvents = new PriorityQueue<>(new Comparator<FieldEventState<?, T>>() {
            /** {@inheritDoc} */
            @Override
            public int compare(final FieldEventState<?, T> es0, final FieldEventState<?, T> es1) {
                return orderingSign * es0.getEventDate().compareTo(es1.getEventDate());
            }
        });

        boolean doneWithStep = false;
        resetEvents:
        do {

            // Evaluate all event detectors for events
            occurringEvents.clear();
            for (final FieldEventState<?, T> state : eventsStates) {
                if (state.evaluateStep(interpolator)) {
                    // the event occurs during the current step
                    occurringEvents.add(state);
                }
            }


            do {

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

                    // handle the chronologically first event
                    final FieldEventState<?, T> currentEvent = occurringEvents.poll();

                    // get state at event time
                    FieldSpacecraftState<T> eventState = restricted.getInterpolatedState(currentEvent.getEventDate());

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

                    // try to advance all event states to current time
                    for (final FieldEventState<?, T> state : eventsStates) {
                        if (state != currentEvent && state.tryAdvance(eventState, interpolator)) {
                            // we need to handle another event first
                            // remove event we just updated to prevent heap corruption
                            occurringEvents.remove(state);
                            // add it back to update its position in the heap
                            occurringEvents.add(state);
                            // re-queue the event we were processing
                            occurringEvents.add(currentEvent);
                            continue eventLoop;
                        }
                    }
                    // all event detectors agree we can advance to the current event time

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

                    // acknowledge event occurrence
                    final EventOccurrence<T> occurrence = currentEvent.doEvent(eventState);
                    final Action action = occurrence.getAction();
                    isLastStep = action == Action.STOP;
                    if (isLastStep) {

                        // ensure the event is after the root if it is returned STOP
                        // this lets the user integrate to a STOP event and then restart
                        // integration from the same time.
                        final FieldSpacecraftState<T> savedState = eventState;
                        eventState = interpolator.getInterpolatedState(occurrence.getStopDate());
                        restricted = restricted.restrictStep(savedState, eventState);

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

                    }

                    if (isLastStep) {
                        // the event asked to stop integration
                        return eventState;
                    }

                    if (action == Action.RESET_DERIVATIVES || action == Action.RESET_STATE) {
                        // some event handler has triggered changes that
                        // invalidate the derivatives, we need to recompute them
                        final FieldSpacecraftState<T> resetState = occurrence.getNewState();
                        resetIntermediateState(resetState, interpolator.isForward());
                        return resetState;
                    }
                    // at this point action == Action.CONTINUE or Action.RESET_EVENTS

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

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

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

                }

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

            } while (!occurringEvents.isEmpty());

            doneWithStep = true;
        } while (!doneWithStep);

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

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

        return current;

    }

    /** Get the mass.
     * @param date target date for the orbit
     * @return mass mass
     */
    protected abstract T getMass(FieldAbsoluteDate<T> date);

    /** Get PV coordinates provider.
     * @return PV coordinates provider
     */
    public FieldPVCoordinatesProvider<T> getPvProvider() {
        return pvProvider;
    }

    /** Reset an intermediate state.
     * @param state new intermediate state to consider
     * @param forward if true, the intermediate state is valid for
     * propagations after itself
     */
    protected abstract void resetIntermediateState(FieldSpacecraftState<T> state, boolean forward);

    /** Extrapolate an orbit up to a specific target date.
     * @param date target date for the orbit
     * @param parameters model parameters
     * @return extrapolated parameters
     */
    protected abstract FieldOrbit<T> propagateOrbit(FieldAbsoluteDate<T> date, T[] parameters);

    /** Propagate an orbit without any fancy features.
     * <p>This method is similar in spirit to the {@link #propagate} method,
     * except that it does <strong>not</strong> call any handler during
     * propagation, nor any discrete events, not additional states. It always
     * stop exactly at the specified date.</p>
     * @param date target date for propagation
     * @return state at specified date
     */
    protected FieldSpacecraftState<T> basicPropagate(final FieldAbsoluteDate<T> date) {
        try {

            // evaluate orbit
            final FieldOrbit<T> orbit = propagateOrbit(date, getParameters(date.getField(), date.getDate()));

            // evaluate attitude
            final FieldAttitude<T> attitude =
                getAttitudeProvider().getAttitude(pvProvider, date, orbit.getFrame());

            // build raw state
            return new FieldSpacecraftState<>(orbit, attitude, getMass(date));

        } catch (OrekitException oe) {
            throw new OrekitException(oe);
        }
    }

    /** Internal FieldPVCoordinatesProvider<T> for attitude computation. */
    private class FieldLocalPVProvider implements FieldPVCoordinatesProvider<T> {

        /** {@inheritDoc} */
        @Override
        public TimeStampedFieldPVCoordinates<T> getPVCoordinates(final FieldAbsoluteDate<T> date, final Frame frame) {
            return propagateOrbit(date, getParameters(date.getField(), date)).getPVCoordinates(frame);
        }

    }

    /** {@link BoundedPropagator} view of the instance. */
    private class FieldBoundedPropagatorView extends FieldAbstractAnalyticalPropagator<T>
        implements FieldBoundedPropagator<T> {

        /** Min date. */
        private final FieldAbsoluteDate<T> minDate;

        /** Max date. */
        private final FieldAbsoluteDate<T> maxDate;

        /** Simple constructor.
         * @param startDate start date of the propagation
         * @param endDate end date of the propagation
         */
        FieldBoundedPropagatorView(final FieldAbsoluteDate<T> startDate, final FieldAbsoluteDate<T> endDate) {
            super(startDate.durationFrom(endDate).getField(), FieldAbstractAnalyticalPropagator.this.getAttitudeProvider());
            super.resetInitialState(FieldAbstractAnalyticalPropagator.this.getInitialState());
            if (startDate.compareTo(endDate) <= 0) {
                minDate = startDate;
                maxDate = endDate;
            } else {
                minDate = endDate;
                maxDate = startDate;
            }

            try {
                // copy the same additional state providers as the original propagator
                for (FieldAdditionalStateProvider<T> provider : FieldAbstractAnalyticalPropagator.this.getAdditionalStateProviders()) {
                    addAdditionalStateProvider(provider);
                }
            } catch (OrekitException oe) {
                // as the providers are already compatible with each other,
                // this should never happen
                throw new OrekitInternalError(null);
            }

        }

        /** {@inheritDoc} */
        @Override
        public FieldAbsoluteDate<T> getMinDate() {
            return minDate;
        }

        /** {@inheritDoc} */
        @Override
        public FieldAbsoluteDate<T> getMaxDate() {
            return maxDate;
        }

        /** {@inheritDoc} */
        @Override
        protected FieldOrbit<T> propagateOrbit(final FieldAbsoluteDate<T> target, final T[] parameters) {
            return FieldAbstractAnalyticalPropagator.this.propagateOrbit(target, parameters);
        }

        /** {@inheritDoc} */
        @Override
        public T getMass(final FieldAbsoluteDate<T> date) {
            return FieldAbstractAnalyticalPropagator.this.getMass(date);
        }

        /** {@inheritDoc} */
        @Override
        public TimeStampedFieldPVCoordinates<T> getPVCoordinates(final FieldAbsoluteDate<T> date, final Frame frame) {
            return propagate(date).getPVCoordinates(frame);
        }

        /** {@inheritDoc} */
        @Override
        public void resetInitialState(final FieldSpacecraftState<T> state) {
            super.resetInitialState(state);
            FieldAbstractAnalyticalPropagator.this.resetInitialState(state);
        }

        /** {@inheritDoc} */
        @Override
        protected void resetIntermediateState(final FieldSpacecraftState<T> state, final boolean forward) {
            FieldAbstractAnalyticalPropagator.this.resetIntermediateState(state, forward);
        }

        /** {@inheritDoc} */
        @Override
        public FieldSpacecraftState<T> getInitialState() {
            return FieldAbstractAnalyticalPropagator.this.getInitialState();
        }

        /** {@inheritDoc} */
        @Override
        public Frame getFrame() {
            return FieldAbstractAnalyticalPropagator.this.getFrame();
        }

        /** {@inheritDoc} */
        @Override
        public List<ParameterDriver> getParametersDrivers() {
            return FieldAbstractAnalyticalPropagator.this.getParametersDrivers();
        }
    }

    /** Internal class for local propagation. */
    private class FieldBasicStepInterpolator implements FieldOrekitStepInterpolator<T> {

        /** Previous state. */
        private final FieldSpacecraftState<T> previousState;

        /** Current state. */
        private final FieldSpacecraftState<T> currentState;

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

        /** Simple constructor.
         * @param isForward integration direction indicator
         * @param previousState start of the step
         * @param currentState end of the step
         */
        FieldBasicStepInterpolator(final boolean isForward,
                                   final FieldSpacecraftState<T> previousState,
                                   final FieldSpacecraftState<T> currentState) {
            this.forward             = isForward;
            this.previousState   = previousState;
            this.currentState    = currentState;
        }

        /** {@inheritDoc} */
        @Override
        public FieldSpacecraftState<T> getPreviousState() {
            return previousState;
        }

        /** {@inheritDoc} */
        @Override
        public FieldSpacecraftState<T> getCurrentState() {
            return currentState;
        }

        /** {@inheritDoc} */
        @Override
        public FieldSpacecraftState<T> getInterpolatedState(final FieldAbsoluteDate<T> date) {

            // compute the basic spacecraft state
            final FieldSpacecraftState<T> basicState = basicPropagate(date);

            // add the additional states
            return updateAdditionalStates(basicState);

        }

        /** {@inheritDoc} */
        @Override
        public boolean isForward() {
            return forward;
        }

        /** {@inheritDoc} */
        @Override
        public FieldBasicStepInterpolator restrictStep(final FieldSpacecraftState<T> newPreviousState,
                                                       final FieldSpacecraftState<T> newCurrentState) {
            return new FieldBasicStepInterpolator(forward, newPreviousState, newCurrentState);
        }

    }

}