IntegratedEphemeris.java

/* Copyright 2002-2013 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.integration;

import java.io.NotSerializableException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.math3.ode.ContinuousOutputModel;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitExceptionWrapper;
import org.orekit.errors.OrekitMessages;
import org.orekit.errors.PropagationException;
import org.orekit.frames.Frame;
import org.orekit.orbits.Orbit;
import org.orekit.propagation.AdditionalStateProvider;
import org.orekit.propagation.BoundedPropagator;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.analytical.AbstractAnalyticalPropagator;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.PVCoordinates;

/** This class stores sequentially generated orbital parameters for
 * later retrieval.
 *
 * <p>
 * Instances of this class are built and then must be fed with the results
 * provided by {@link org.orekit.propagation.Propagator Propagator} objects
 * configured in {@link org.orekit.propagation.Propagator#setEphemerisMode()
 * ephemeris generation mode}. Once propagation is o, random access to any
 * intermediate state of the orbit throughout the propagation range is possible.
 * </p>
 * <p>
 * A typical use case is for numerically integrated orbits, which can be used by
 * algorithms that need to wander around according to their own algorithm without
 * cumbersome tight links with the integrator.
 * </p>
 * <p>
 * Another use case is persistence, as this class is one of the few propagators
 * to be serializable.
 * </p>
 * <p>
 * As this class implements the {@link org.orekit.propagation.Propagator Propagator}
 * interface, it can itself be used in batch mode to build another instance of the
 * same type. This is however not recommended since it would be a waste of resources.
 * </p>
 * <p>
 * Note that this class stores all intermediate states along with interpolation
 * models, so it may be memory intensive.
 * </p>
 *
 * @see org.orekit.propagation.numerical.NumericalPropagator
 * @author Mathieu Rom&eacute;ro
 * @author Luc Maisonobe
 * @author V&eacute;ronique Pommier-Maurussane
 */
public class IntegratedEphemeris
    extends AbstractAnalyticalPropagator implements BoundedPropagator, Serializable  {

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

    /** Mapper between raw double components and spacecraft state. */
    private final StateMapper mapper;

    /** Start date of the integration (can be min or max). */
    private final AbsoluteDate startDate;

    /** First date of the range. */
    private final AbsoluteDate minDate;

    /** Last date of the range. */
    private final AbsoluteDate maxDate;

    /** Underlying raw mathematical model. */
    private ContinuousOutputModel model;

    /** Unmanaged additional states that must be simply copied. */
    private final Map<String, double[]> unmanaged;

    /** Creates a new instance of IntegratedEphemeris.
     * @param startDate Start date of the integration (can be minDate or maxDate)
     * @param minDate first date of the range
     * @param maxDate last date of the range
     * @param mapper mapper between raw double components and spacecraft state
     * @param model underlying raw mathematical model
     * @param unmanaged unmanaged additional states that must be simply copied
     * @param providers providers for pre-integrated states
     * @param equations names of additional equations
     * @exception OrekitException if several providers have the same name
     */
    public IntegratedEphemeris(final AbsoluteDate startDate,
                               final AbsoluteDate minDate, final AbsoluteDate maxDate,
                               final StateMapper mapper, final ContinuousOutputModel model,
                               final Map<String, double[]> unmanaged,
                               final List<AdditionalStateProvider> providers,
                               final String[] equations)
        throws OrekitException {

        super(mapper.getAttitudeProvider());

        this.startDate = startDate;
        this.minDate   = minDate;
        this.maxDate   = maxDate;
        this.mapper    = mapper;
        this.model     = model;
        this.unmanaged = unmanaged;

        // set up the pre-integrated providers
        for (final AdditionalStateProvider provider : providers) {
            addAdditionalStateProvider(provider);
        }

        // set up providers to map the final elements of the model array to additional states
        for (int i = 0; i < equations.length; ++i) {
            addAdditionalStateProvider(new LocalProvider(equations[i], i));
        }

    }

    /** Set up the model at some interpolation date.
     * @param date desired interpolation date
     * @exception PropagationException if specified date is outside
     * of supported range
     */
    private void setInterpolationDate(final AbsoluteDate date)
        throws PropagationException {

        if (date.equals(startDate.shiftedBy(model.getInterpolatedTime()))) {
            // the current model date is already the desired one
            return;
        }

        if ((date.compareTo(minDate) < 0) || (date.compareTo(maxDate) > 0)) {
            // date is outside of supported range
            throw new PropagationException(OrekitMessages.OUT_OF_RANGE_EPHEMERIDES_DATE,
                                           date, minDate, maxDate);
        }

        // reset interpolation model to the desired date
        model.setInterpolatedTime(date.durationFrom(startDate));

    }

    /** {@inheritDoc} */
    @Override
    protected SpacecraftState basicPropagate(final AbsoluteDate date)
        throws PropagationException {
        try {
            setInterpolationDate(date);
            SpacecraftState state = mapper.mapArrayToState(model.getInterpolatedTime(),
                                                           model.getInterpolatedState());
            for (Map.Entry<String, double[]> initial : unmanaged.entrySet()) {
                state = state.addAdditionalState(initial.getKey(), initial.getValue());
            }
            return state;
        } catch (OrekitExceptionWrapper oew) {
            if (oew.getException() instanceof PropagationException) {
                throw (PropagationException) oew.getException();
            } else {
                throw new PropagationException(oew.getException());
            }
        } catch (OrekitException oe) {
            if (oe instanceof PropagationException) {
                throw (PropagationException) oe;
            } else {
                throw new PropagationException(oe);
            }
        }
    }

    /** {@inheritDoc} */
    protected Orbit propagateOrbit(final AbsoluteDate date)
        throws PropagationException {
        return basicPropagate(date).getOrbit();
    }

    /** {@inheritDoc} */
    protected double getMass(final AbsoluteDate date) throws PropagationException {
        return basicPropagate(date).getMass();
    }

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

    /** Get the first date of the range.
     * @return the first date of the range
     */
    public AbsoluteDate getMinDate() {
        return minDate;
    }

    /** Get the last date of the range.
     * @return the last date of the range
     */
    public AbsoluteDate getMaxDate() {
        return maxDate;
    }

    @Override
    public Frame getFrame() {
        return this.mapper.getFrame();
    }

    /** {@inheritDoc} */
    public void resetInitialState(final SpacecraftState state)
        throws PropagationException {
        throw new PropagationException(OrekitMessages.NON_RESETABLE_STATE);
    }

    /** {@inheritDoc} */
    public SpacecraftState getInitialState() throws PropagationException {
        return updateAdditionalStates(basicPropagate(getMinDate()));
    }

    /** Replace the instance with a data transfer object for serialization.
     * @return data transfer object that will be serialized
     * @exception NotSerializableException if the state mapper cannot be serialized (typically for DSST propagator)
     */
    private Object writeReplace() throws NotSerializableException {

        // unmanaged additional states
        final String[]   unmanagedNames  = new String[unmanaged.size()];
        final double[][] unmanagedValues = new double[unmanaged.size()][];
        int i = 0;
        for (Map.Entry<String, double[]> entry : unmanaged.entrySet()) {
            unmanagedNames[i]  = entry.getKey();
            unmanagedValues[i] = entry.getValue();
            ++i;
        }

        // managed states providers
        final List<AdditionalStateProvider> serializableProviders = new ArrayList<AdditionalStateProvider>();
        final List<String> equationNames = new ArrayList<String>();
        for (final AdditionalStateProvider provider : getAdditionalStateProviders()) {
            if (provider instanceof LocalProvider) {
                equationNames.add(((LocalProvider) provider).getName());
            } else if (provider instanceof Serializable) {
                serializableProviders.add(provider);
            }
        }

        return new DataTransferObject(startDate, minDate, maxDate, mapper, model,
                                      unmanagedNames, unmanagedValues,
                                      serializableProviders.toArray(new AdditionalStateProvider[serializableProviders.size()]),
                                      equationNames.toArray(new String[equationNames.size()]));

    }

    /** Local provider for additional state data. */
    private class LocalProvider implements AdditionalStateProvider {

        /** Name of the additional state. */
        private final String name;

        /** Index of the additional state. */
        private final int index;

        /** Simple constructor.
         * @param name name of the additional state
         * @param index index of the additional state
         */
        public LocalProvider(final String name, final int index) {
            this.name  = name;
            this.index = index;
        }

        /** {@inheritDoc} */
        public String getName() {
            return name;
        }

        /** {@inheritDoc} */
        public double[] getAdditionalState(final SpacecraftState state)
            throws PropagationException {

            // set the model date
            setInterpolationDate(state.getDate());

            // extract the part of the interpolated array corresponding to the additional state
            return model.getInterpolatedSecondaryState(index);

        }

    }

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

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

        /** Mapper between raw double components and spacecraft state. */
        private final StateMapper mapper;

        /** Start date of the integration (can be min or max). */
        private final AbsoluteDate startDate;

        /** First date of the range. */
        private final AbsoluteDate minDate;

        /** Last date of the range. */
        private final AbsoluteDate maxDate;

        /** Underlying raw mathematical model. */
        private final ContinuousOutputModel model;

        /** Names of unmanaged additional states that must be simply copied. */
        private final String[] unmanagedNames;

        /** Values of unmanaged additional states that must be simply copied. */
        private final double[][] unmanagedValues;

        /** Names of additional equations. */
        private final String[] equations;

        /** Providers for pre-integrated states. */
        private final AdditionalStateProvider[] providers;

        /** Simple constructor.
         * @param startDate Start date of the integration (can be minDate or maxDate)
         * @param minDate first date of the range
         * @param maxDate last date of the range
         * @param mapper mapper between raw double components and spacecraft state
         * @param model underlying raw mathematical model
         * @param unmanagedNames names of unmanaged additional states that must be simply copied
         * @param unmanagedValues values of unmanaged additional states that must be simply copied
         * @param providers providers for pre-integrated states
         * @param equations names of additional equations
         */
        public DataTransferObject(final AbsoluteDate startDate,
                                  final AbsoluteDate minDate, final AbsoluteDate maxDate,
                                  final StateMapper mapper, final ContinuousOutputModel model,
                                  final String[] unmanagedNames, final double[][] unmanagedValues,
                                  final AdditionalStateProvider[] providers,
                                  final String[] equations) {
            this.startDate       = startDate;
            this.minDate         = minDate;
            this.maxDate         = maxDate;
            this.mapper          = mapper;
            this.model           = model;
            this.unmanagedNames  = unmanagedNames;
            this.unmanagedValues = unmanagedValues;
            this.providers       = providers;
            this.equations       = equations;
        }

        /** Replace the deserialized data transfer object with a {@link IntegratedEphemeris}.
         * @return replacement {@link IntegratedEphemeris}
         */
        private Object readResolve() {
            try {
                final Map<String, double[]> unmanaged = new HashMap<String, double[]>(unmanagedNames.length);
                for (int i = 0; i < unmanagedNames.length; ++i) {
                    unmanaged.put(unmanagedNames[i], unmanagedValues[i]);
                }
                return new IntegratedEphemeris(startDate, minDate, maxDate, mapper, model,
                                               unmanaged, Arrays.asList(providers), equations);
            } catch (OrekitException oe) {
                throw OrekitException.createInternalError(oe);
            }
        }

    }

}