FieldEphemeris.java

/* Copyright 2022-2026 Romain Serra
 * 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.List;

import org.hipparchus.CalculusFieldElement;
import org.hipparchus.exception.MathIllegalArgumentException;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.attitudes.FieldAttitude;
import org.orekit.attitudes.FrameAlignedProvider;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitIllegalArgumentException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.orbits.FieldOrbit;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.propagation.FieldSpacecraftStateInterpolator;
import org.orekit.time.AbstractFieldTimeInterpolator;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.time.FieldTimeInterpolator;
import org.orekit.utils.FieldBoundedPVCoordinatesProvider;
import org.orekit.utils.FieldDataDictionary;
import org.orekit.utils.ImmutableFieldTimeStampedCache;
import org.orekit.utils.ParameterDriver;

/**
 * This class is designed to accept and handle tabulated orbital entries. Tabulated entries are classified and then
 * extrapolated in way to obtain continuous output, with accuracy and computation methods configured by the user.
 *
 * @see Ephemeris
 * @author Luc Maisonobe
 * @author Vincent Cucchietti
 * @author Romain Serra
 * @since 14.0
 */
public class FieldEphemeris<T extends CalculusFieldElement<T>> extends FieldAbstractAnalyticalPropagator<T>
        implements FieldBoundedPVCoordinatesProvider<T> {

    /** First date in range. */
    private final FieldAbsoluteDate<T> minDate;

    /** Last date in range. */
    private final FieldAbsoluteDate<T> maxDate;

    /** Reference frame. */
    private final Frame frame;

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

    /** List of spacecraft states. */
    private final ImmutableFieldTimeStampedCache<FieldSpacecraftState<T>, T> statesCache;

    /** Spacecraft state interpolator. */
    private final FieldTimeInterpolator<FieldSpacecraftState<T>, T> stateInterpolator;

    /**
     * Constructor with tabulated states and default Hermite interpolation.
     *
     * @param states list of spacecraft states
     * @param interpolationPoints number of points to use in interpolation
     *
     * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
     *                                        interpolation
     * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
     *                                        position-velocity-acceleration)
     * @see FieldSpacecraftStateInterpolator
     */
    public FieldEphemeris(final List<FieldSpacecraftState<T>> states, final int interpolationPoints)
            throws MathIllegalArgumentException {
        this(states, new FieldSpacecraftStateInterpolator<>(interpolationPoints, states.get(0).getFrame(), states.get(0).getFrame()));
    }

    /**
     * Constructor with tabulated states.
     *
     * @param states list of spacecraft states
     * @param stateInterpolator spacecraft state interpolator
     *
     * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
     *                                        interpolation
     * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
     *                                        position-velocity-acceleration)
     */
    public FieldEphemeris(final List<FieldSpacecraftState<T>> states,
                          final FieldTimeInterpolator<FieldSpacecraftState<T>, T> stateInterpolator)
            throws MathIllegalArgumentException {
        this(states, stateInterpolator, new FrameAlignedProvider(states.get(0).getFrame()));
    }

    /**
     * Constructor with tabulated states and attitude provider.
     *
     * @param states list of spacecraft states
     * @param stateInterpolator spacecraft state interpolator
     * @param attitudeProvider attitude law to use
     *
     * @throws MathIllegalArgumentException   if the number of states is smaller than the number of points to use in
     *                                        interpolation
     * @throws OrekitIllegalArgumentException if states are not defined the same way (orbit or absolute
     *                                        position-velocity-acceleration)
     */
    public FieldEphemeris(final List<FieldSpacecraftState<T>> states,
                          final FieldTimeInterpolator<FieldSpacecraftState<T>, T> stateInterpolator,
                          final AttitudeProvider attitudeProvider)
            throws MathIllegalArgumentException {
        super(states.get(0).getDate().getField(), attitudeProvider);

        // Check input consistency
        checkInputConsistency(states, stateInterpolator);

        // Initialize variables
        final FieldSpacecraftState<T> s0 = states.get(0);
        minDate = s0.getDate();
        maxDate = states.get(states.size() - 1).getDate();
        frame   = s0.getFrame();

        final List<FieldDataDictionary<T>.Entry> as = s0.getAdditionalDataValues().getData();
        additional = new String[as.size()];
        for (int i = 0; i < additional.length; ++i) {
            additional[i] = as.get(i).getKey();
        }

        this.statesCache       = new ImmutableFieldTimeStampedCache<>(stateInterpolator.getNbInterpolationPoints(), states);
        this.stateInterpolator = stateInterpolator;

        // Initialize initial state
        super.resetInitialState(getInitialState());
    }

    /**
     * Check input consistency between states and the interpolator.
     *
     * @param states spacecraft states sample
     * @param interpolator spacecraft state interpolator
     */
    public void checkInputConsistency(final List<FieldSpacecraftState<T>> states,
                                      final FieldTimeInterpolator<FieldSpacecraftState<T>, T> interpolator) {
        checkStatesDefinitionsConsistency(states);

        // Check that every interpolator used in the state interpolator are compatible with the sample size
        AbstractFieldTimeInterpolator.checkInterpolatorCompatibilityWithSampleSize(interpolator, states.size());
    }

    /**
     * Check that all state are either orbit defined or based on absolute position-velocity-acceleration.
     *
     * @param states spacecraft state sample
     */
    public void checkStatesDefinitionsConsistency(final List<FieldSpacecraftState<T>> states) {
        // Check all states handle the same additional states and are defined the same way (orbit or absolute PVA)
        final FieldSpacecraftState<T> s0               = states.get(0);
        final boolean         s0IsOrbitDefined = s0.isOrbitDefined();
        for (final FieldSpacecraftState<T> state : states) {
            s0.ensureCompatibleAdditionalStates(state);
            if (s0IsOrbitDefined != state.isOrbitDefined()) {
                throw new OrekitIllegalArgumentException(OrekitMessages.DIFFERENT_STATE_DEFINITION);
            }
        }
    }

    /**
     * Get the first date of the range.
     *
     * @return the first date of the range
     */
    public FieldAbsoluteDate<T> getMinDate() {
        return minDate;
    }

    /**
     * Get the last date of the range.
     *
     * @return the last date of the range
     */
    public FieldAbsoluteDate<T> getMaxDate() {
        return maxDate;
    }

    /** {@inheritDoc} */
    @Override
    public Frame getFrame() {
        return frame;
    }

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

        final FieldAbsoluteDate<T> centralDate =
                AbstractFieldTimeInterpolator.getCentralDate(date, statesCache, stateInterpolator.getExtrapolationThreshold());
        final FieldSpacecraftState<T>  evaluatedState   = stateInterpolator.interpolate(date, statesCache.getNeighbors(centralDate));
        final AttitudeProvider attitudeProvider = getAttitudeProvider();
        final FieldAttitude<T> calculatedAttitude;
        // Verify if orbit is defined
        if (evaluatedState.isOrbitDefined()) {
            calculatedAttitude =
                    attitudeProvider.getAttitude(evaluatedState.getOrbit(), date, evaluatedState.getFrame());
            return new FieldSpacecraftState<>(evaluatedState.getOrbit(), calculatedAttitude, evaluatedState.getMass(),
                                       evaluatedState.getMassRate(), evaluatedState.getAdditionalDataValues(),
                                       evaluatedState.getAdditionalStatesDerivatives());
        }
        else {
            calculatedAttitude =
                    attitudeProvider.getAttitude(evaluatedState.getAbsPVA(), date, evaluatedState.getFrame());
            return new FieldSpacecraftState<>(evaluatedState.getAbsPVA(), calculatedAttitude, evaluatedState.getMass(),
                                       evaluatedState.getMassRate(), evaluatedState.getAdditionalDataValues(),
                                       evaluatedState.getAdditionalStatesDerivatives());
        }
    }

    /** {@inheritDoc} */
    public FieldOrbit<T> propagateOrbit(final FieldAbsoluteDate<T> date, final T[] parameters) {
        return basicPropagate(date).getOrbit();
    }

    /** {@inheritDoc} */
    protected T getMass(final FieldAbsoluteDate<T> date) {
        return basicPropagate(date).getMass();
    }

    /**
     * Try (and fail) to reset the initial state.
     * <p>
     * This method always throws an exception, as ephemerides cannot be reset.
     * </p>
     *
     * @param state new initial state to consider
     */
    @Override
    public void resetInitialState(final FieldSpacecraftState<T> state) {
        resetIntermediateState(state, true);
    }

    /** {@inheritDoc} */
    protected void resetIntermediateState(final FieldSpacecraftState<T> state, final boolean forward) {
        throw new OrekitException(OrekitMessages.NON_RESETABLE_STATE);
    }

    /** {@inheritDoc} */
    @Override
    public FieldSpacecraftState<T> getInitialState() {
        return basicPropagate(getMinDate());
    }

    /** {@inheritDoc} */
    @Override
    public boolean isAdditionalDataManaged(final String name) {

        // the additional state may be managed by a specific provider in the base class
        if (super.isAdditionalDataManaged(name)) {
            return true;
        }

        // the additional state may be managed in the states sample
        for (final String a : additional) {
            if (a.equals(name)) {
                return true;
            }
        }

        return false;

    }

    /** {@inheritDoc} */
    @Override
    public String[] getManagedAdditionalData() {
        final String[] upperManaged = super.getManagedAdditionalData();
        final String[] managed      = new String[upperManaged.length + additional.length];
        System.arraycopy(upperManaged, 0, managed, 0, upperManaged.length);
        System.arraycopy(additional, 0, managed, upperManaged.length, additional.length);
        return managed;
    }

    /** Get state interpolator.
     * @return state interpolator
     */
    public FieldTimeInterpolator<FieldSpacecraftState<T>, T> getStateInterpolator() {
        return stateInterpolator;
    }

    @Override
    public List<ParameterDriver> getParametersDrivers() {
        return new ArrayList<>();
    }

}