AbstractAnalyticalPropagator.java

/* Copyright 2002-2016 CS Systèmes d'Information
 * Licensed to CS Systèmes d'Information (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.io.NotSerializableException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.math3.exception.NoBracketingException;
import org.apache.commons.math3.exception.TooManyEvaluationsException;
import org.apache.commons.math3.util.FastMath;
import org.orekit.attitudes.Attitude;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitInternalError;
import org.orekit.errors.PropagationException;
import org.orekit.frames.Frame;
import org.orekit.orbits.Orbit;
import org.orekit.propagation.AbstractPropagator;
import org.orekit.propagation.AdditionalStateProvider;
import org.orekit.propagation.BoundedPropagator;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.EventDetector;
import org.orekit.propagation.events.EventState;
import org.orekit.propagation.sampling.OrekitStepInterpolator;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.PVCoordinatesProvider;
import org.orekit.utils.TimeStampedPVCoordinates;

/** Common handling of {@link org.orekit.propagation.Propagator} methods for analytical propagators.
 * <p>
 * This abstract class allows to provide easily the full set of {@link
 * org.orekit.propagation.Propagator Propagator} 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(AbsoluteDate)}
 * and {@link #getMass(AbsoluteDate)}. The first method should perform straightforward
 * propagation starting from some internally stored initial state up to the specified target date.
 * </p>
 * @author Luc Maisonobe
 */
public abstract class AbstractAnalyticalPropagator extends AbstractPropagator {

    /** Internal steps interpolator. */
    private final BasicStepInterpolator interpolator;

    /** Provider for attitude computation. */
    private PVCoordinatesProvider pvProvider;

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

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

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

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

    /** Event steps. */
    private final Collection<EventState<?>> eventsStates;

    /** Build a new instance.
     * @param attitudeProvider provider for attitude computation
     */
    protected AbstractAnalyticalPropagator(final AttitudeProvider attitudeProvider) {
        setAttitudeProvider(attitudeProvider);
        interpolator             = new BasicStepInterpolator();
        pvProvider               = new LocalPVProvider();
        lastPropagationStart     = AbsoluteDate.PAST_INFINITY;
        lastPropagationEnd       = AbsoluteDate.FUTURE_INFINITY;
        statesInitialized        = false;
        eventsStates             = new ArrayList<EventState<?>>();
    }

    /** {@inheritDoc} */
    public BoundedPropagator getGeneratedEphemeris() {
        return new BoundedPropagatorView(lastPropagationStart, lastPropagationEnd);
    }

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

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

    /** {@inheritDoc} */
    public void clearEventsDetectors() {
        eventsStates.clear();
    }

    /** {@inheritDoc} */
    public SpacecraftState propagate(final AbsoluteDate start, final AbsoluteDate target)
        throws PropagationException {
        try {

            lastPropagationStart = start;

            final double dt      = target.durationFrom(start);
            final double epsilon = FastMath.ulp(dt);
            interpolator.storeDate(start);
            SpacecraftState state = interpolator.getInterpolatedState();

            // evaluate step size
            final double stepSize;
            if (getMode() == MASTER_MODE) {
                if (Double.isNaN(getFixedStepSize())) {
                    stepSize = FastMath.copySign(state.getKeplerianPeriod() / 100, dt);
                } else {
                    stepSize = FastMath.copySign(getFixedStepSize(), dt);
                }
            } else {
                stepSize = dt;
            }

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

            // initialize step handler
            if (getStepHandler() != null) {
                getStepHandler().init(state, target);
            }

            // iterate over the propagation range
            statesInitialized = false;
            isLastStep = false;
            do {

                // go ahead one step size
                interpolator.shift();
                final AbsoluteDate t = interpolator.getCurrentDate().shiftedBy(stepSize);
                if ((dt == 0) || ((dt > 0) ^ (t.compareTo(target) <= 0))) {
                    // current step exceeds target
                    interpolator.storeDate(target);
                } else {
                    // current step is within range
                    interpolator.storeDate(t);
                }

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

            } while (!isLastStep);

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

        } catch (PropagationException pe) {
            throw pe;
        } catch (OrekitException oe) {
            throw PropagationException.unwrap(oe);
        } catch (TooManyEvaluationsException tmee) {
            throw PropagationException.unwrap(tmee);
        } catch (NoBracketingException nbe) {
            throw PropagationException.unwrap(nbe);
        }
    }

    /** Accept a step, triggering events and step handlers.
     * @param target final propagation time
     * @param epsilon threshold for end date detection
     * @return state at the end of the step
     * @exception OrekitException if the switching function cannot be evaluated
     * @exception TooManyEvaluationsException if an event cannot be located
     * @exception NoBracketingException if bracketing cannot be performed
     */
    protected SpacecraftState acceptStep(final AbsoluteDate target, final double epsilon)
        throws OrekitException, TooManyEvaluationsException, NoBracketingException {

        AbsoluteDate previousT = interpolator.getGlobalPreviousDate();
        AbsoluteDate currentT  = interpolator.getGlobalCurrentDate();

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

            if (!eventsStates.isEmpty()) {
                // initialize the events states
                final AbsoluteDate t0 = interpolator.getPreviousDate();
                interpolator.setInterpolatedDate(t0);
                final SpacecraftState y = interpolator.getInterpolatedState();
                for (final EventState<?> state : eventsStates) {
                    state.reinitializeBegin(y, interpolator.isForward());
                }
            }

            statesInitialized = true;

        }

        // search for next events that may occur during the step
        final List<EventState<?>> occurringEvents = new ArrayList<EventState<?>>();
        for (final EventState<?> state : eventsStates) {
            if (state.evaluateStep(interpolator)) {
                // the event occurs during the current step
                occurringEvents.add(state);
            }
        }

        // chronological or reverse chronological sorter, according to propagation direction
        final int orderingSign = interpolator.isForward() ? +1 : -1;
        final Comparator<EventState<?>> sorter = new Comparator<EventState<?>>() {

            /** {@inheritDoc} */
            public int compare(final EventState<?> es0, final EventState<?> es1) {
                return orderingSign * es0.getEventTime().compareTo(es1.getEventTime());
            }

        };

        while (!occurringEvents.isEmpty()) {

            // handle the chronologically first event
            Collections.sort(occurringEvents, sorter);
            final Iterator<EventState<?>> iterator = occurringEvents.iterator();
            final EventState<?> currentEvent = iterator.next();
            iterator.remove();

            // restrict the interpolator to the first part of the step, up to the event
            final AbsoluteDate eventT = currentEvent.getEventTime();
            interpolator.setSoftPreviousDate(previousT);
            interpolator.setSoftCurrentDate(eventT);

            // trigger the event
            interpolator.setInterpolatedDate(eventT);
            final SpacecraftState eventY = interpolator.getInterpolatedState();
            currentEvent.stepAccepted(eventY);
            isLastStep = currentEvent.stop();

            // handle the first part of the step, up to the event
            if (getStepHandler() != null) {
                getStepHandler().handleStep(interpolator, isLastStep);
            }

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

            final SpacecraftState resetState = currentEvent.reset(eventY);
            if (resetState != null) {
                resetIntermediateState(resetState, interpolator.isForward());
                return resetState;
            }

            // prepare handling of the remaining part of the step
            previousT = eventT;
            interpolator.setSoftPreviousDate(eventT);
            interpolator.setSoftCurrentDate(currentT);

            // check if the same event occurs again in the remaining part of the step
            if (currentEvent.evaluateStep(interpolator)) {
                // the event occurs during the current step
                occurringEvents.add(currentEvent);
            }

        }

        final double remaining = target.durationFrom(currentT);
        if (interpolator.isForward()) {
            isLastStep = remaining <  epsilon;
        } else {
            isLastStep = remaining > -epsilon;
        }
        if (isLastStep) {
            currentT = target;
        }

        interpolator.setInterpolatedDate(currentT);
        final SpacecraftState currentY = interpolator.getInterpolatedState();
        for (final EventState<?> state : eventsStates) {
            state.stepAccepted(currentY);
            isLastStep = isLastStep || state.stop();
        }

        // handle the remaining part of the step, after all events if any
        if (getStepHandler() != null) {
            getStepHandler().handleStep(interpolator, isLastStep);
        }

        return currentY;

    }

    /** Get the mass.
     * @param date target date for the orbit
     * @return mass mass
     * @exception PropagationException if some parameters are out of bounds
     */
    protected abstract double getMass(final AbsoluteDate date)
        throws PropagationException;

    /** Get PV coordinates provider.
     * @return PV coordinates provider
     */
    public PVCoordinatesProvider getPvProvider() {
        return pvProvider;
    }

    /** {@inheritDoc} */
    public void resetInitialState(final SpacecraftState state)
        throws PropagationException {
        super.resetInitialState(state);
        interpolator.globalCurrentDate = state.getDate();
        interpolator.softCurrentDate   = interpolator.globalCurrentDate;
    }

    /** Reset an intermediate state.
     * @param state new intermediate state to consider
     * @param forward if true, the intermediate state is valid for
     * propagations after itself
     * @exception PropagationException if initial state cannot be reset
     */
    protected void resetIntermediateState(final SpacecraftState state, final boolean forward)
        throws PropagationException {
        interpolator.globalCurrentDate = state.getDate();
        interpolator.softCurrentDate   = interpolator.globalCurrentDate;
    }

    /** Extrapolate an orbit up to a specific target date.
     * @param date target date for the orbit
     * @return extrapolated parameters
     * @exception PropagationException if some parameters are out of bounds
     */
    protected abstract Orbit propagateOrbit(final AbsoluteDate date)
        throws PropagationException;

    /** 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
     * @exception PropagationException if propagation cannot reach specified date
     */
    protected SpacecraftState basicPropagate(final AbsoluteDate date) throws PropagationException {
        try {

            // evaluate orbit
            final Orbit orbit = propagateOrbit(date);

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

            // build raw state
            return new SpacecraftState(orbit, attitude, getMass(date));

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

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

        /** {@inheritDoc} */
        public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame)
            throws OrekitException {
            return propagateOrbit(date).getPVCoordinates(frame);
        }

    }

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

        /** Serializable UID. */
        private static final long serialVersionUID = 20151117L;

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

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

        /** Simple constructor.
         * @param startDate start date of the propagation
         * @param endDate end date of the propagation
         */
        BoundedPropagatorView(final AbsoluteDate startDate, final AbsoluteDate endDate) {
            super(AbstractAnalyticalPropagator.this.getAttitudeProvider());
            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 (AdditionalStateProvider provider : AbstractAnalyticalPropagator.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} */
        public AbsoluteDate getMinDate() {
            return minDate;
        }

        /** {@inheritDoc} */
        public AbsoluteDate getMaxDate() {
            return maxDate;
        }

        /** {@inheritDoc} */
        protected Orbit propagateOrbit(final AbsoluteDate target)
            throws PropagationException {
            return AbstractAnalyticalPropagator.this.propagateOrbit(target);
        }

        /** {@inheritDoc} */
        public double getMass(final AbsoluteDate date) throws PropagationException {
            return AbstractAnalyticalPropagator.this.getMass(date);
        }

        /** {@inheritDoc} */
        public TimeStampedPVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame)
            throws OrekitException {
            return propagate(date).getPVCoordinates(frame);
        }

        /** {@inheritDoc} */
        public void resetInitialState(final SpacecraftState state) throws PropagationException {
            AbstractAnalyticalPropagator.this.resetInitialState(state);
        }

        /** {@inheritDoc} */
        protected void resetIntermediateState(final SpacecraftState state, final boolean forward)
            throws PropagationException {
            AbstractAnalyticalPropagator.this.resetIntermediateState(state, forward);
        }

        /** {@inheritDoc} */
        public SpacecraftState getInitialState() throws PropagationException {
            return AbstractAnalyticalPropagator.this.getInitialState();
        }

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

        /** Replace the instance with a data transfer object for serialization.
         * @return data transfer object that will be serialized
         * @exception NotSerializableException if attitude provider or additional
         * state provider is not serializable
         */
        private Object writeReplace() throws NotSerializableException {
            return new DataTransferObject(minDate, maxDate, AbstractAnalyticalPropagator.this);
        }

    }

    /** Internal class used only for serialization. */
    private static class DataTransferObject implements Serializable {

        /** Serializable UID. */
        private static final long serialVersionUID = 20151117L;

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

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

        /** Underlying propagator. */
        private final AbstractAnalyticalPropagator propagator;

        /** Simple constructor.
         * @param minDate min date
         * @param maxDate max date
         * @param propagator underlying propagator
         */
        DataTransferObject(final AbsoluteDate minDate, final AbsoluteDate maxDate,
                           final AbstractAnalyticalPropagator propagator) {
            this.minDate    = minDate;
            this.maxDate    = maxDate;
            this.propagator = propagator;
        }

        /** Replace the deserialized data transfer object with an {@link BoundedPropagatorView}.
         * @return replacement {@link BoundedPropagatorView}
         */
        private Object readResolve() {
            propagator.lastPropagationStart = minDate;
            propagator.lastPropagationEnd   = maxDate;
            return propagator.getGeneratedEphemeris();
        }

    }

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

        /** Global previous date. */
        private AbsoluteDate globalPreviousDate;

        /** Global current date. */
        private AbsoluteDate globalCurrentDate;

        /** Soft previous date. */
        private AbsoluteDate softPreviousDate;

        /** Soft current date. */
        private AbsoluteDate softCurrentDate;

        /** Interpolated state. */
        private SpacecraftState interpolatedState;

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

        /** Build a new instance from a basic propagator.
         */
        BasicStepInterpolator() {
            globalPreviousDate = AbsoluteDate.PAST_INFINITY;
            globalCurrentDate  = AbsoluteDate.PAST_INFINITY;
            softPreviousDate   = AbsoluteDate.PAST_INFINITY;
            softCurrentDate    = AbsoluteDate.PAST_INFINITY;
        }

        /** Restrict step range to a limited part of the global step.
         * <p>
         * This method can be used to restrict a step and make it appear
         * as if the original step was smaller. Calling this method
         * <em>only</em> changes the value returned by {@link #getPreviousDate()},
         * it does not change any other property
         * </p>
         * @param softPreviousDate start of the restricted step
         */
        public void setSoftPreviousDate(final AbsoluteDate softPreviousDate) {
            this.softPreviousDate = softPreviousDate;
        }

        /** Restrict step range to a limited part of the global step.
         * <p>
         * This method can be used to restrict a step and make it appear
         * as if the original step was smaller. Calling this method
         * <em>only</em> changes the value returned by {@link #getCurrentDate()},
         * it does not change any other property
         * </p>
         * @param softCurrentDate end of the restricted step
         */
        public void setSoftCurrentDate(final AbsoluteDate softCurrentDate) {
            this.softCurrentDate  = softCurrentDate;
        }

        /**
         * Get the previous global grid point time.
         * @return previous global grid point time
         */
        public AbsoluteDate getGlobalPreviousDate() {
            return globalPreviousDate;
        }

        /**
         * Get the current global grid point time.
         * @return current global grid point time
         */
        public AbsoluteDate getGlobalCurrentDate() {
            return globalCurrentDate;
        }

        /** {@inheritDoc} */
        public AbsoluteDate getCurrentDate() {
            return softCurrentDate;
        }

        /** {@inheritDoc} */
        public AbsoluteDate getInterpolatedDate() {
            return interpolatedState.getDate();
        }

        /** {@inheritDoc} */
        public SpacecraftState getInterpolatedState() throws OrekitException {
            return interpolatedState;
        }

        /** {@inheritDoc} */
        public AbsoluteDate getPreviousDate() {
            return softPreviousDate;
        }

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

        /** {@inheritDoc} */
        public void setInterpolatedDate(final AbsoluteDate date) throws PropagationException {

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

            // add the additional states
            interpolatedState = updateAdditionalStates(basicState);

        }

        /** Shift one step forward.
         * Copy the current date into the previous date, hence preparing the
         * interpolator for future calls to {@link #storeDate storeDate}
         */
        public void shift() {
            globalPreviousDate = globalCurrentDate;
            softPreviousDate   = globalPreviousDate;
            softCurrentDate    = globalCurrentDate;
        }

        /** Store the current step date.
         * @param date current date
         * @exception PropagationException if the state cannot be propagated at specified date
         */
        public void storeDate(final AbsoluteDate date)
            throws PropagationException {
            globalCurrentDate = date;
            softCurrentDate   = globalCurrentDate;
            forward           = globalCurrentDate.compareTo(globalPreviousDate) >= 0;
            setInterpolatedDate(globalCurrentDate);
        }

    }

}