FieldDSSTPropagator.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.semianalytical.dsst;

  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.HashSet;
  23. import java.util.List;
  24. import java.util.Set;

  25. import org.hipparchus.CalculusFieldElement;
  26. import org.hipparchus.Field;
  27. import org.hipparchus.ode.FieldODEIntegrator;
  28. import org.hipparchus.ode.FieldODEStateAndDerivative;
  29. import org.hipparchus.ode.sampling.FieldODEStateInterpolator;
  30. import org.hipparchus.ode.sampling.FieldODEStepHandler;
  31. import org.hipparchus.util.MathArrays;
  32. import org.orekit.annotation.DefaultDataContext;
  33. import org.orekit.attitudes.AttitudeProvider;
  34. import org.orekit.attitudes.FieldAttitude;
  35. import org.orekit.data.DataContext;
  36. import org.orekit.errors.OrekitException;
  37. import org.orekit.errors.OrekitInternalError;
  38. import org.orekit.errors.OrekitMessages;
  39. import org.orekit.frames.Frame;
  40. import org.orekit.orbits.FieldEquinoctialOrbit;
  41. import org.orekit.orbits.FieldOrbit;
  42. import org.orekit.orbits.OrbitType;
  43. import org.orekit.orbits.PositionAngleType;
  44. import org.orekit.propagation.CartesianToleranceProvider;
  45. import org.orekit.propagation.FieldSpacecraftState;
  46. import org.orekit.propagation.PropagationType;
  47. import org.orekit.propagation.Propagator;
  48. import org.orekit.propagation.SpacecraftState;
  49. import org.orekit.propagation.ToleranceProvider;
  50. import org.orekit.propagation.conversion.osc2mean.DSSTTheory;
  51. import org.orekit.propagation.conversion.osc2mean.FixedPointConverter;
  52. import org.orekit.propagation.conversion.osc2mean.MeanTheory;
  53. import org.orekit.propagation.conversion.osc2mean.OsculatingToMeanConverter;
  54. import org.orekit.propagation.integration.FieldAbstractIntegratedPropagator;
  55. import org.orekit.propagation.integration.FieldStateMapper;
  56. import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
  57. import org.orekit.propagation.semianalytical.dsst.forces.DSSTNewtonianAttraction;
  58. import org.orekit.propagation.semianalytical.dsst.forces.FieldShortPeriodTerms;
  59. import org.orekit.propagation.semianalytical.dsst.utilities.FieldAuxiliaryElements;
  60. import org.orekit.propagation.semianalytical.dsst.utilities.FieldFixedNumberInterpolationGrid;
  61. import org.orekit.propagation.semianalytical.dsst.utilities.FieldInterpolationGrid;
  62. import org.orekit.propagation.semianalytical.dsst.utilities.FieldMaxGapInterpolationGrid;
  63. import org.orekit.time.AbsoluteDate;
  64. import org.orekit.time.FieldAbsoluteDate;
  65. import org.orekit.utils.FieldDataDictionary;
  66. import org.orekit.utils.FieldPVCoordinates;
  67. import org.orekit.utils.ParameterDriver;
  68. import org.orekit.utils.ParameterObserver;
  69. import org.orekit.utils.TimeSpanMap;

  70. /**
  71.  * This class propagates {@link org.orekit.orbits.FieldOrbit orbits} using the DSST theory.
  72.  * <p>
  73.  * Whereas analytical propagators are configured only thanks to their various
  74.  * constructors and can be used immediately after construction, such a semianalytical
  75.  * propagator configuration involves setting several parameters between construction
  76.  * time and propagation time, just as numerical propagators.
  77.  * </p>
  78.  * <p>
  79.  * The configuration parameters that can be set are:
  80.  * </p>
  81.  * <ul>
  82.  * <li>the initial spacecraft state ({@link #setInitialState(FieldSpacecraftState)})</li>
  83.  * <li>the various force models ({@link #addForceModel(DSSTForceModel)},
  84.  * {@link #removeForceModels()})</li>
  85.  * <li>the discrete events that should be triggered during propagation (
  86.  * {@link #addEventDetector(org.orekit.propagation.events.FieldEventDetector)},
  87.  * {@link #clearEventsDetectors()})</li>
  88.  * <li>the binding logic with the rest of the application ({@link #getMultiplexer()})</li>
  89.  * </ul>
  90.  * <p>
  91.  * From these configuration parameters, only the initial state is mandatory.
  92.  * The default propagation settings are in {@link OrbitType#EQUINOCTIAL equinoctial}
  93.  * parameters with {@link PositionAngleType#TRUE true} longitude argument.
  94.  * The central attraction coefficient used to define the initial orbit will be used.
  95.  * However, specifying only the initial state would mean the propagator would use
  96.  * only Keplerian forces. In this case, the simpler
  97.  * {@link org.orekit.propagation.analytical.KeplerianPropagator KeplerianPropagator}
  98.  * class would be more effective.
  99.  * </p>
  100.  * <p>
  101.  * The underlying numerical integrator set up in the constructor may also have
  102.  * its own configuration parameters. Typical configuration parameters for adaptive
  103.  * stepsize integrators are the min, max and perhaps start step size as well as
  104.  * the absolute and/or relative errors thresholds.
  105.  * </p>
  106.  * <p>
  107.  * The state that is seen by the integrator is a simple six elements double array.
  108.  * These six elements are:
  109.  * <ul>
  110.  * <li>the {@link org.orekit.orbits.FieldEquinoctialOrbit equinoctial orbit parameters}
  111.  * (a, e<sub>x</sub>, e<sub>y</sub>, h<sub>x</sub>, h<sub>y</sub>, λ<sub>m</sub>)
  112.  * in meters and radians,</li>
  113.  * </ul>
  114.  *
  115.  * <p>By default, at the end of the propagation, the propagator resets the initial state to the final state,
  116.  * thus allowing a new propagation to be started from there without recomputing the part already performed.
  117.  * This behaviour can be chenged by calling {@link #setResetAtEnd(boolean)}.
  118.  * </p>
  119.  * <p>Beware the same instance cannot be used simultaneously by different threads, the class is <em>not</em>
  120.  * thread-safe.</p>
  121.  *
  122.  * @see FieldSpacecraftState
  123.  * @see DSSTForceModel
  124.  * @author Romain Di Costanzo
  125.  * @author Pascal Parraud
  126.  * @since 10.0
  127.  * @param <T> type of the field elements
  128.  */
  129. public class FieldDSSTPropagator<T extends CalculusFieldElement<T>> extends FieldAbstractIntegratedPropagator<T>  {

  130.     /** Retrograde factor I.
  131.      *  <p>
  132.      *  DSST model needs equinoctial orbit as internal representation.
  133.      *  Classical equinoctial elements have discontinuities when inclination
  134.      *  is close to zero. In this representation, I = +1. <br>
  135.      *  To avoid this discontinuity, another representation exists and equinoctial
  136.      *  elements can be expressed in a different way, called "retrograde" orbit.
  137.      *  This implies I = -1. <br>
  138.      *  As Orekit doesn't implement the retrograde orbit, I is always set to +1.
  139.      *  But for the sake of consistency with the theory, the retrograde factor
  140.      *  has been kept in the formulas.
  141.      *  </p>
  142.      */
  143.     private static final int I = 1;

  144.     /** Number of grid points per integration step to be used in interpolation of short periodics coefficients.*/
  145.     private static final int INTERPOLATION_POINTS_PER_STEP = 3;

  146.     /** Default value for epsilon. */
  147.     private static final double EPSILON_DEFAULT = 1.0e-13;

  148.     /** Default value for maxIterations. */
  149.     private static final int MAX_ITERATIONS_DEFAULT = 200;

  150.     /** Flag specifying whether the initial orbital state is given with osculating elements. */
  151.     private boolean initialIsOsculating;

  152.     /** Field used by this class.*/
  153.     private final Field<T> field;

  154.     /** Force models used to compute short periodic terms. */
  155.     private final List<DSSTForceModel> forceModels;

  156.     /** State mapper holding the force models. */
  157.     private FieldMeanPlusShortPeriodicMapper mapper;

  158.     /** Generator for the interpolation grid. */
  159.     private FieldInterpolationGrid<T> interpolationgrid;

  160.     /** Create a new instance of DSSTPropagator.
  161.      *  <p>
  162.      *  After creation, there are no perturbing forces at all.
  163.      *  This means that if {@link #addForceModel addForceModel}
  164.      *  is not called after creation, the integrated orbit will
  165.      *  follow a Keplerian evolution only.
  166.      *  </p>
  167.      *
  168.      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
  169.      *
  170.      *  @param field field used by default
  171.      *  @param integrator numerical integrator to use for propagation.
  172.      *  @param propagationType type of orbit to output (mean or osculating).
  173.      * @see #FieldDSSTPropagator(Field, FieldODEIntegrator, PropagationType,
  174.      * AttitudeProvider)
  175.      */
  176.     @DefaultDataContext
  177.     public FieldDSSTPropagator(final Field<T> field, final FieldODEIntegrator<T> integrator, final PropagationType propagationType) {
  178.         this(field, integrator, propagationType,
  179.                 Propagator.getDefaultLaw(DataContext.getDefault().getFrames()));
  180.     }

  181.     /** Create a new instance of DSSTPropagator.
  182.      *  <p>
  183.      *  After creation, there are no perturbing forces at all.
  184.      *  This means that if {@link #addForceModel addForceModel}
  185.      *  is not called after creation, the integrated orbit will
  186.      *  follow a Keplerian evolution only.
  187.      *  </p>
  188.      * @param field field used by default
  189.      *  @param integrator numerical integrator to use for propagation.
  190.      * @param propagationType type of orbit to output (mean or osculating).
  191.      * @param attitudeProvider attitude law to use.
  192.      * @since 10.1
  193.      */
  194.     public FieldDSSTPropagator(final Field<T> field,
  195.                                final FieldODEIntegrator<T> integrator,
  196.                                final PropagationType propagationType,
  197.                                final AttitudeProvider attitudeProvider) {
  198.         super(field, integrator, propagationType);
  199.         this.field  = field;
  200.         forceModels = new ArrayList<>();
  201.         initMapper(field);
  202.         // DSST uses only equinoctial orbits and mean longitude argument
  203.         setOrbitType(OrbitType.EQUINOCTIAL);
  204.         setPositionAngleType(PositionAngleType.MEAN);
  205.         setAttitudeProvider(attitudeProvider);
  206.         setInterpolationGridToFixedNumberOfPoints(INTERPOLATION_POINTS_PER_STEP);
  207.     }

  208.     /** Create a new instance of DSSTPropagator.
  209.      *  <p>
  210.      *  After creation, there are no perturbing forces at all.
  211.      *  This means that if {@link #addForceModel addForceModel}
  212.      *  is not called after creation, the integrated orbit will
  213.      *  follow a Keplerian evolution only. Only the mean orbits
  214.      *  will be generated.
  215.      *  </p>
  216.      *
  217.      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
  218.      *
  219.      *  @param field fied used by default
  220.      *  @param integrator numerical integrator to use for propagation.
  221.      * @see #FieldDSSTPropagator(Field, FieldODEIntegrator, AttitudeProvider)
  222.      */
  223.     @DefaultDataContext
  224.     public FieldDSSTPropagator(final Field<T> field, final FieldODEIntegrator<T> integrator) {
  225.         this(field, integrator,
  226.                 Propagator.getDefaultLaw(DataContext.getDefault().getFrames()));
  227.     }

  228.     /** Create a new instance of DSSTPropagator.
  229.      *  <p>
  230.      *  After creation, there are no perturbing forces at all.
  231.      *  This means that if {@link #addForceModel addForceModel}
  232.      *  is not called after creation, the integrated orbit will
  233.      *  follow a Keplerian evolution only. Only the mean orbits
  234.      *  will be generated.
  235.      *  </p>
  236.      * @param field fied used by default
  237.      *  @param integrator numerical integrator to use for propagation.
  238.      * @param attitudeProvider attitude law to use.
  239.      * @since 10.1
  240.      */
  241.     public FieldDSSTPropagator(final Field<T> field,
  242.                                final FieldODEIntegrator<T> integrator,
  243.                                final AttitudeProvider attitudeProvider) {
  244.         super(field, integrator, PropagationType.MEAN);
  245.         this.field  = field;
  246.         forceModels = new ArrayList<>();
  247.         initMapper(field);
  248.         // DSST uses only equinoctial orbits and mean longitude argument
  249.         setOrbitType(OrbitType.EQUINOCTIAL);
  250.         setPositionAngleType(PositionAngleType.MEAN);
  251.         setAttitudeProvider(attitudeProvider);
  252.         setInterpolationGridToFixedNumberOfPoints(INTERPOLATION_POINTS_PER_STEP);
  253.     }

  254.     /** Set the central attraction coefficient μ.
  255.      * <p>
  256.      * Setting the central attraction coefficient is
  257.      * equivalent to {@link #addForceModel(DSSTForceModel) add}
  258.      * a {@link DSSTNewtonianAttraction} force model.
  259.      * </p>
  260.     * @param mu central attraction coefficient (m³/s²)
  261.     * @see #addForceModel(DSSTForceModel)
  262.     * @see #getAllForceModels()
  263.     */
  264.     @Override
  265.     public void setMu(final T mu) {
  266.         addForceModel(new DSSTNewtonianAttraction(mu.getReal()));
  267.     }

  268.     /** Set the central attraction coefficient μ only in upper class.
  269.      * @param mu central attraction coefficient (m³/s²)
  270.      */
  271.     private void superSetMu(final T mu) {
  272.         super.setMu(mu);
  273.     }

  274.     /** Check if Newtonian attraction force model is available.
  275.      * <p>
  276.      * Newtonian attraction is always the last force model in the list.
  277.      * </p>
  278.      * @return true if Newtonian attraction force model is available
  279.      */
  280.     private boolean hasNewtonianAttraction() {
  281.         final int last = forceModels.size() - 1;
  282.         return last >= 0 && forceModels.get(last) instanceof DSSTNewtonianAttraction;
  283.     }

  284.     /** Set the initial state with osculating orbital elements.
  285.      *  @param initialState initial state (defined with osculating elements)
  286.      */
  287.     public void setInitialState(final FieldSpacecraftState<T> initialState) {
  288.         setInitialState(initialState, PropagationType.OSCULATING);
  289.     }

  290.     /** Set the initial state.
  291.      *  @param initialState initial state
  292.      *  @param stateType defined if the orbital state is defined with osculating or mean elements
  293.      */
  294.     public void setInitialState(final FieldSpacecraftState<T> initialState,
  295.                                 final PropagationType stateType) {
  296.         resetInitialState(initialState, stateType);
  297.     }

  298.     /** Reset the initial state.
  299.      *
  300.      *  @param state new initial state
  301.      */
  302.     @Override
  303.     public void resetInitialState(final FieldSpacecraftState<T> state) {
  304.         super.resetInitialState(state);
  305.         if (!hasNewtonianAttraction()) {
  306.             // use the state to define central attraction
  307.             setMu(state.getOrbit().getMu());
  308.         }
  309.         super.setStartDate(state.getDate());
  310.     }

  311.     /** {@inheritDoc}.
  312.      *
  313.      * <p>Change parameter {@link #initialIsOsculating()} accordingly
  314.      * @since 12.1.3
  315.      */
  316.     @Override
  317.     public void resetInitialState(final FieldSpacecraftState<T> state, final PropagationType stateType) {
  318.         // Reset initial state
  319.         resetInitialState(state);

  320.         // Change state of initial osculating, if needed
  321.         initialIsOsculating = stateType == PropagationType.OSCULATING;
  322.     }

  323.     /** Set the selected short periodic coefficients that must be stored as additional states.
  324.      * @param selectedCoefficients short periodic coefficients that must be stored as additional states
  325.      * (null means no coefficients are selected, empty set means all coefficients are selected)
  326.      */
  327.     public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
  328.         mapper.setSelectedCoefficients(selectedCoefficients == null ?
  329.                                        null : new HashSet<>(selectedCoefficients));
  330.     }

  331.     /** Get the selected short periodic coefficients that must be stored as additional states.
  332.      * @return short periodic coefficients that must be stored as additional states
  333.      * (null means no coefficients are selected, empty set means all coefficients are selected)
  334.      */
  335.     public Set<String> getSelectedCoefficients() {
  336.         final Set<String> set = mapper.getSelectedCoefficients();
  337.         return set == null ? null : Collections.unmodifiableSet(set);
  338.     }

  339.     /** Check if the initial state is provided in osculating elements.
  340.      * @return true if initial state is provided in osculating elements
  341.      */
  342.     public boolean initialIsOsculating() {
  343.         return initialIsOsculating;
  344.     }

  345.     /** Set the interpolation grid generator.
  346.      * <p>
  347.      * The generator will create an interpolation grid with a fixed
  348.      * number of points for each mean element integration step.
  349.      * </p>
  350.      * <p>
  351.      * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
  352.      * nor {@link #setInterpolationGridToMaxTimeGap(CalculusFieldElement)} has been called,
  353.      * by default the propagator is set as to 3 interpolations points per step.
  354.      * </p>
  355.      * @param interpolationPoints number of interpolation points at
  356.      * each integration step
  357.      * @see #setInterpolationGridToMaxTimeGap(CalculusFieldElement)
  358.      * @since 7.1
  359.      */
  360.     public void setInterpolationGridToFixedNumberOfPoints(final int interpolationPoints) {
  361.         interpolationgrid = new FieldFixedNumberInterpolationGrid<>(field, interpolationPoints);
  362.     }

  363.     /** Set the interpolation grid generator.
  364.      * <p>
  365.      * The generator will create an interpolation grid with a maximum
  366.      * time gap between interpolation points.
  367.      * </p>
  368.      * <p>
  369.      * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
  370.      * nor {@link #setInterpolationGridToMaxTimeGap(CalculusFieldElement)} has been called,
  371.      * by default the propagator is set as to 3 interpolations points per step.
  372.      * </p>
  373.      * @param maxGap maximum time gap between interpolation points (seconds)
  374.      * @see #setInterpolationGridToFixedNumberOfPoints(int)
  375.      * @since 7.1
  376.      */
  377.     public void setInterpolationGridToMaxTimeGap(final T maxGap) {
  378.         interpolationgrid = new FieldMaxGapInterpolationGrid<>(field, maxGap);
  379.     }

  380.     /** Add a force model to the global perturbation model.
  381.      *  <p>
  382.      *  If this method is not called at all,
  383.      *  the integrated orbit will follow a Keplerian evolution only.
  384.      *  </p>
  385.      *  @param force perturbing {@link DSSTForceModel force} to add
  386.      *  @see #removeForceModels()
  387.      *  @see #setMu(CalculusFieldElement)
  388.      */
  389.     public void addForceModel(final DSSTForceModel force) {

  390.         if (force instanceof DSSTNewtonianAttraction) {
  391.             // we want to add the central attraction force model

  392.             try {
  393.                 // ensure we are notified of any mu change
  394.                 force.getParametersDrivers().get(0).addObserver(new ParameterObserver() {
  395.                     /** {@inheritDoc} */
  396.                     @Override
  397.                     public void valueChanged(final double previousValue, final ParameterDriver driver, final AbsoluteDate date) {
  398.                         // mu PDriver should have only 1 span
  399.                         superSetMu(field.getZero().newInstance(driver.getValue()));
  400.                     }
  401.                     /** {@inheritDoc} */
  402.                     @Override
  403.                     public void valueSpanMapChanged(final TimeSpanMap<Double> previousValue, final ParameterDriver driver) {
  404.                         // mu PDriver should have only 1 span
  405.                         superSetMu(field.getZero().newInstance(driver.getValue()));
  406.                     }
  407.                 });
  408.             } catch (OrekitException oe) {
  409.                 // this should never happen
  410.                 throw new OrekitInternalError(oe);
  411.             }

  412.             if (hasNewtonianAttraction()) {
  413.                 // there is already a central attraction model, replace it
  414.                 forceModels.set(forceModels.size() - 1, force);
  415.             } else {
  416.                 // there are no central attraction model yet, add it at the end of the list
  417.                 forceModels.add(force);
  418.             }
  419.         } else {
  420.             // we want to add a perturbing force model
  421.             if (hasNewtonianAttraction()) {
  422.                 // insert the new force model before Newtonian attraction,
  423.                 // which should always be the last one in the list
  424.                 forceModels.add(forceModels.size() - 1, force);
  425.             } else {
  426.                 // we only have perturbing force models up to now, just append at the end of the list
  427.                 forceModels.add(force);
  428.             }
  429.         }

  430.         force.registerAttitudeProvider(getAttitudeProvider());

  431.     }

  432.     /** Remove all perturbing force models from the global perturbation model
  433.      *  (except central attraction).
  434.      *  <p>
  435.      *  Once all perturbing forces have been removed (and as long as no new force model is added),
  436.      *  the integrated orbit will follow a Keplerian evolution only.
  437.      *  </p>
  438.      *  @see #addForceModel(DSSTForceModel)
  439.      */
  440.     public void removeForceModels() {
  441.         final int last = forceModels.size() - 1;
  442.         if (hasNewtonianAttraction()) {
  443.             // preserve the Newtonian attraction model at the end
  444.             final DSSTForceModel newton = forceModels.get(last);
  445.             forceModels.clear();
  446.             forceModels.add(newton);
  447.         } else {
  448.             forceModels.clear();
  449.         }
  450.     }

  451.     /** Get all the force models, perturbing forces and Newtonian attraction included.
  452.      * @return list of perturbing force models, with Newtonian attraction being the
  453.      * last one
  454.      * @see #addForceModel(DSSTForceModel)
  455.      * @see #setMu(CalculusFieldElement)
  456.      */
  457.     public List<DSSTForceModel> getAllForceModels() {
  458.         return Collections.unmodifiableList(forceModels);
  459.     }

  460.     /** Get propagation parameter type.
  461.      * @return orbit type used for propagation
  462.      */
  463.     @Override
  464.     public OrbitType getOrbitType() {
  465.         return super.getOrbitType();
  466.     }

  467.     /** Get propagation parameter type.
  468.      * @return angle type to use for propagation
  469.      */
  470.     @Override
  471.     public PositionAngleType getPositionAngleType() {
  472.         return super.getPositionAngleType();
  473.     }

  474.     /** Conversion from mean to osculating orbit.
  475.      * <p>
  476.      * Compute osculating state <b>in a DSST sense</b>, corresponding to the
  477.      * mean SpacecraftState in input, and according to the Force models taken
  478.      * into account.
  479.      * </p><p>
  480.      * Since the osculating state is obtained by adding short-periodic variation
  481.      * of each force model, the resulting output will depend on the
  482.      * force models parameterized in input.
  483.      * </p>
  484.      * @param mean Mean state to convert
  485.      * @param forces Forces to take into account
  486.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  487.      * like atmospheric drag, radiation pressure or specific user-defined models)
  488.      * @param <T> type of the elements
  489.      * @return osculating state in a DSST sense
  490.      */
  491.     @SuppressWarnings("unchecked")
  492.     public static <T extends CalculusFieldElement<T>> FieldSpacecraftState<T> computeOsculatingState(final FieldSpacecraftState<T> mean,
  493.                                                                                                      final AttitudeProvider attitudeProvider,
  494.                                                                                                      final Collection<DSSTForceModel> forces) {

  495.         //Create the auxiliary object
  496.         final FieldAuxiliaryElements<T> aux = new FieldAuxiliaryElements<>(mean.getOrbit(), I);

  497.         // Set the force models
  498.         final List<FieldShortPeriodTerms<T>> shortPeriodTerms = new ArrayList<>();
  499.         for (final DSSTForceModel force : forces) {
  500.             force.registerAttitudeProvider(attitudeProvider);
  501.             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, PropagationType.OSCULATING, force.getParameters(mean.getDate().getField(), mean.getDate())));
  502.             force.updateShortPeriodTerms(force.getParametersAllValues(mean.getDate().getField()), mean);
  503.         }

  504.         final FieldEquinoctialOrbit<T> osculatingOrbit = computeOsculatingOrbit(mean, shortPeriodTerms);

  505.         return new FieldSpacecraftState<>(osculatingOrbit, mean.getAttitude(), mean.getMass(),
  506.                                           mean.getAdditionalDataValues(), null);

  507.     }

  508.     /** Conversion from osculating to mean orbit.
  509.      * <p>
  510.      * Compute mean state <b>in a DSST sense</b>, corresponding to the
  511.      * osculating SpacecraftState in input, and according to the Force models
  512.      * taken into account.
  513.      * </p><p>
  514.      * Since the osculating state is obtained with the computation of
  515.      * short-periodic variation of each force model, the resulting output will
  516.      * depend on the force models parameterized in input.
  517.      * </p><p>
  518.      * The computation is done through a fixed-point iteration process.
  519.      * </p>
  520.      * @param osculating Osculating state to convert
  521.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  522.      * like atmospheric drag, radiation pressure or specific user-defined models)
  523.      * @param forceModel Forces to take into account
  524.      * @param <T> type of the elements
  525.      * @return mean state in a DSST sense
  526.      */
  527.     public static <T extends CalculusFieldElement<T>> FieldSpacecraftState<T> computeMeanState(final FieldSpacecraftState<T> osculating,
  528.                                                                                                final AttitudeProvider attitudeProvider,
  529.                                                                                                final Collection<DSSTForceModel> forceModel) {
  530.         final OsculatingToMeanConverter converter = new FixedPointConverter(EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT,
  531.                                                                             FixedPointConverter.DEFAULT_DAMPING);
  532.         return computeMeanState(osculating, attitudeProvider, forceModel, converter);
  533.     }

  534.     /** Conversion from osculating to mean orbit.
  535.      * <p>
  536.      * Compute mean state <b>in a DSST sense</b>, corresponding to the
  537.      * osculating SpacecraftState in input, and according to the Force models
  538.      * taken into account.
  539.      * </p><p>
  540.      * Since the osculating state is obtained with the computation of
  541.      * short-periodic variation of each force model, the resulting output will
  542.      * depend on the force models parameterized in input.
  543.      * </p><p>
  544.      * The computation is done through a fixed-point iteration process.
  545.      * </p>
  546.      * @param osculating Osculating state to convert
  547.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  548.      * like atmospheric drag, radiation pressure or specific user-defined models)
  549.      * @param forceModel Forces to take into account
  550.      * @param epsilon convergence threshold for mean parameters conversion
  551.      * @param maxIterations maximum iterations for mean parameters conversion
  552.      * @return mean state in a DSST sense
  553.      * @param <T> type of the elements
  554.      * @since 10.1
  555.      */
  556.     public static <T extends CalculusFieldElement<T>> FieldSpacecraftState<T> computeMeanState(final FieldSpacecraftState<T> osculating,
  557.                                                                                                final AttitudeProvider attitudeProvider,
  558.                                                                                                final Collection<DSSTForceModel> forceModel,
  559.                                                                                                final double epsilon,
  560.                                                                                                final int maxIterations) {
  561.         final OsculatingToMeanConverter converter = new FixedPointConverter(epsilon, maxIterations,
  562.                                                                             FixedPointConverter.DEFAULT_DAMPING);
  563.         return computeMeanState(osculating, attitudeProvider, forceModel, converter);
  564.     }

  565.     /** Conversion from osculating to mean orbit.
  566.      * <p>
  567.      * Compute mean state <b>in a DSST sense</b>, corresponding to the
  568.      * osculating SpacecraftState in input, and according to the Force models
  569.      * taken into account.
  570.      * </p><p>
  571.      * Since the osculating state is obtained with the computation of
  572.      * short-periodic variation of each force model, the resulting output will
  573.      * depend on the force models parameterized in input.
  574.      * </p><p>
  575.      * The computation is done using the given osculating to mean orbit converter.
  576.      * </p>
  577.      * @param <T> type of the elements
  578.      * @param osculating       osculating state to convert
  579.      * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
  580.      *                         like atmospheric drag, radiation pressure or specific user-defined models)
  581.      * @param forceModel       forces to take into account
  582.      * @param converter        osculating to mean orbit converter
  583.      * @return mean state in a DSST sense
  584.      * @since 13.0
  585.      */
  586.     public static <T extends CalculusFieldElement<T>> FieldSpacecraftState<T>  computeMeanState(final FieldSpacecraftState<T> osculating,
  587.                                                                                                 final AttitudeProvider attitudeProvider,
  588.                                                                                                 final Collection<DSSTForceModel> forceModel,
  589.                                                                                                 final OsculatingToMeanConverter converter) {

  590.         final MeanTheory theory = new DSSTTheory(forceModel, attitudeProvider, osculating.getMass().getReal());
  591.         converter.setMeanTheory(theory);
  592.         final FieldOrbit<T> meanOrbit = converter.convertToMean(osculating.getOrbit());
  593.         return new FieldSpacecraftState<>(meanOrbit, osculating.getAttitude(), osculating.getMass(),
  594.                                           osculating.getAdditionalDataValues(), null);
  595.     }

  596.     /** Override the default value of the parameter.
  597.     *  <p>
  598.     *  By default, if the initial orbit is defined as osculating,
  599.     *  it will be averaged over 2 satellite revolutions.
  600.     *  This can be changed by using this method.
  601.     *  </p>
  602.     *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
  603.     *                             elements
  604.     */
  605.     public void setSatelliteRevolution(final int satelliteRevolution) {
  606.         mapper.setSatelliteRevolution(satelliteRevolution);
  607.     }

  608.    /** Get the number of satellite revolutions to use for converting osculating to mean elements.
  609.     *  @return number of satellite revolutions to use for converting osculating to mean elements
  610.     */
  611.     public int getSatelliteRevolution() {
  612.         return mapper.getSatelliteRevolution();
  613.     }

  614.    /** {@inheritDoc} */
  615.     @Override
  616.     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
  617.         super.setAttitudeProvider(attitudeProvider);

  618.         //Register the attitude provider for each force model
  619.         for (final DSSTForceModel force : forceModels) {
  620.             force.registerAttitudeProvider(attitudeProvider);
  621.         }
  622.     }

  623.     /** Method called just before integration.
  624.      * <p>
  625.      * The default implementation does nothing, it may be specialized in subclasses.
  626.      * </p>
  627.      * @param initialState initial state
  628.      * @param tEnd target date at which state should be propagated
  629.      */
  630.     @SuppressWarnings("unchecked")
  631.     @Override
  632.     protected void beforeIntegration(final FieldSpacecraftState<T> initialState,
  633.                                      final FieldAbsoluteDate<T> tEnd) {

  634.         // check if only mean elements must be used
  635.         final PropagationType type = isMeanOrbit();

  636.         // compute common auxiliary elements
  637.         final FieldAuxiliaryElements<T> aux = new FieldAuxiliaryElements<>(initialState.getOrbit(), I);

  638.         // initialize all perturbing forces
  639.         final List<FieldShortPeriodTerms<T>> shortPeriodTerms = new ArrayList<>();
  640.         for (final DSSTForceModel force : forceModels) {
  641.             shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, type, force.getParameters(field, initialState.getDate())));
  642.         }
  643.         mapper.setShortPeriodTerms(shortPeriodTerms);

  644.         // if required, insert the special short periodics step handler
  645.         if (type == PropagationType.OSCULATING) {
  646.             final FieldShortPeriodicsHandler spHandler = new FieldShortPeriodicsHandler(forceModels);
  647.             // Compute short periodic coefficients for this point
  648.             for (DSSTForceModel forceModel : forceModels) {
  649.                 forceModel.updateShortPeriodTerms(forceModel.getParametersAllValues(field), initialState);

  650.             }
  651.             final Collection<FieldODEStepHandler<T>> stepHandlers = new ArrayList<>();
  652.             stepHandlers.add(spHandler);
  653.             final FieldODEIntegrator<T> integrator = getIntegrator();
  654.             final Collection<FieldODEStepHandler<T>> existing = integrator.getStepHandlers();
  655.             stepHandlers.addAll(existing);

  656.             integrator.clearStepHandlers();

  657.             // add back the existing handlers after the short periodics one
  658.             for (final FieldODEStepHandler<T> sp : stepHandlers) {
  659.                 integrator.addStepHandler(sp);
  660.             }
  661.         }
  662.     }

  663.     /** {@inheritDoc} */
  664.     @Override
  665.     protected void afterIntegration() {
  666.         // remove the special short periodics step handler if added before
  667.         if (isMeanOrbit() ==  PropagationType.OSCULATING) {
  668.             final List<FieldODEStepHandler<T>> preserved = new ArrayList<>();
  669.             final FieldODEIntegrator<T> integrator = getIntegrator();

  670.             // clear the list
  671.             integrator.clearStepHandlers();

  672.             // add back the step handlers that were important for the user
  673.             for (final FieldODEStepHandler<T> sp : preserved) {
  674.                 integrator.addStepHandler(sp);
  675.             }
  676.         }
  677.     }

  678.     /** Compute osculating state from mean state.
  679.      * <p>
  680.      * Compute and add the short periodic variation to the mean {@link SpacecraftState}.
  681.      * </p>
  682.      * @param meanState initial mean state
  683.      * @param shortPeriodTerms short period terms
  684.      * @param <T> type of the elements
  685.      * @return osculating state
  686.      */
  687.     private static <T extends CalculusFieldElement<T>> FieldEquinoctialOrbit<T> computeOsculatingOrbit(final FieldSpacecraftState<T> meanState,
  688.                                                                                                        final List<FieldShortPeriodTerms<T>> shortPeriodTerms) {

  689.         final T[] mean = MathArrays.buildArray(meanState.getDate().getField(), 6);
  690.         final T[] meanDot = MathArrays.buildArray(meanState.getDate().getField(), 6);
  691.         OrbitType.EQUINOCTIAL.mapOrbitToArray(meanState.getOrbit(), PositionAngleType.MEAN, mean, meanDot);
  692.         final T[] y = mean.clone();
  693.         for (final FieldShortPeriodTerms<T> spt : shortPeriodTerms) {
  694.             final T[] shortPeriodic = spt.value(meanState.getOrbit());
  695.             for (int i = 0; i < shortPeriodic.length; i++) {
  696.                 y[i] = y[i].add(shortPeriodic[i]);
  697.             }
  698.         }
  699.         return (FieldEquinoctialOrbit<T>) OrbitType.EQUINOCTIAL.mapArrayToOrbit(y, meanDot,
  700.                                                                                 PositionAngleType.MEAN, meanState.getDate(),
  701.                                                                                 meanState.getOrbit().getMu(), meanState.getFrame());
  702.     }

  703.     /** {@inheritDoc} */
  704.     @Override
  705.     protected FieldSpacecraftState<T> getInitialIntegrationState() {
  706.         if (initialIsOsculating) {
  707.             // the initial state is an osculating state,
  708.             // it must be converted to mean state
  709.             return computeMeanState(getInitialState(), getAttitudeProvider(), forceModels);
  710.         } else {
  711.             // the initial state is already a mean state
  712.             return getInitialState();
  713.         }
  714.     }

  715.     /** {@inheritDoc}
  716.      * <p>
  717.      * Note that for DSST, orbit type is hardcoded to {@link OrbitType#EQUINOCTIAL}
  718.      * and position angle type is hardcoded to {@link PositionAngleType#MEAN}, so
  719.      * the corresponding parameters are ignored.
  720.      * </p>
  721.      */
  722.     @Override
  723.     protected FieldStateMapper<T> createMapper(final FieldAbsoluteDate<T> referenceDate, final T mu,
  724.                                                final OrbitType ignoredOrbitType, final PositionAngleType ignoredPositionAngleType,
  725.                                                final AttitudeProvider attitudeProvider, final Frame frame) {

  726.         // create a mapper with the common settings provided as arguments
  727.         final FieldMeanPlusShortPeriodicMapper newMapper =
  728.                 new FieldMeanPlusShortPeriodicMapper(referenceDate, mu, attitudeProvider, frame);

  729.         // copy the specific settings from the existing mapper
  730.         if (mapper != null) {
  731.             newMapper.setSatelliteRevolution(mapper.getSatelliteRevolution());
  732.             newMapper.setSelectedCoefficients(mapper.getSelectedCoefficients());
  733.             newMapper.setShortPeriodTerms(mapper.getShortPeriodTerms());
  734.         }

  735.         mapper = newMapper;
  736.         return mapper;

  737.     }

  738.     /** Internal mapper using mean parameters plus short periodic terms. */
  739.     private class FieldMeanPlusShortPeriodicMapper extends FieldStateMapper<T> {

  740.         /** Short periodic coefficients that must be stored as additional states. */
  741.         private Set<String>                selectedCoefficients;

  742.         /** Number of satellite revolutions in the averaging interval. */
  743.         private int                        satelliteRevolution;

  744.         /** Short period terms. */
  745.         private List<FieldShortPeriodTerms<T>>     shortPeriodTerms;

  746.         /** Simple constructor.
  747.          * @param referenceDate reference date
  748.          * @param mu central attraction coefficient (m³/s²)
  749.          * @param attitudeProvider attitude provider
  750.          * @param frame inertial frame
  751.          */
  752.         FieldMeanPlusShortPeriodicMapper(final FieldAbsoluteDate<T> referenceDate, final T mu,
  753.                                          final AttitudeProvider attitudeProvider, final Frame frame) {

  754.             super(referenceDate, mu, OrbitType.EQUINOCTIAL, PositionAngleType.MEAN, attitudeProvider, frame);

  755.             this.selectedCoefficients = null;

  756.             // Default averaging period for conversion from osculating to mean elements
  757.             this.satelliteRevolution = 2;

  758.             this.shortPeriodTerms    = Collections.emptyList();

  759.         }

  760.         /** {@inheritDoc} */
  761.         @Override
  762.         public FieldSpacecraftState<T> mapArrayToState(final FieldAbsoluteDate<T> date,
  763.                                                        final T[] y, final T[] yDot,
  764.                                                        final PropagationType type) {

  765.             // add short periodic variations to mean elements to get osculating elements
  766.             // (the loop may not be performed if there are no force models and in the
  767.             //  case we want to remain in mean parameters only)
  768.             final T[] elements = y.clone();
  769.             final FieldDataDictionary<T> coefficients;
  770.             switch (type) {
  771.                 case MEAN:
  772.                     coefficients = null;
  773.                     break;
  774.                 case OSCULATING:
  775.                     final FieldOrbit<T> meanOrbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngleType.MEAN, date, getMu(), getFrame());
  776.                     coefficients = selectedCoefficients == null ? null : new FieldDataDictionary<>(date.getField());
  777.                     for (final FieldShortPeriodTerms<T> spt : shortPeriodTerms) {
  778.                         final T[] shortPeriodic = spt.value(meanOrbit);
  779.                         for (int i = 0; i < shortPeriodic.length; i++) {
  780.                             elements[i] = elements[i].add(shortPeriodic[i]);
  781.                         }
  782.                         if (selectedCoefficients != null) {
  783.                             coefficients.putAllFields(spt.getCoefficients(date, selectedCoefficients));
  784.                         }
  785.                     }
  786.                     break;
  787.                 default:
  788.                     throw new OrekitInternalError(null);
  789.             }

  790.             final T mass = elements[6];
  791.             if (mass.getReal() <= 0.0) {
  792.                 throw new OrekitException(OrekitMessages.NOT_POSITIVE_SPACECRAFT_MASS, mass);
  793.             }

  794.             final FieldOrbit<T> orbit       = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngleType.MEAN, date, getMu(), getFrame());
  795.             final FieldAttitude<T> attitude = getAttitudeProvider().getAttitude(orbit, date, getFrame());

  796.             return new FieldSpacecraftState<>(orbit, attitude, mass, coefficients, null);

  797.         }

  798.         /** {@inheritDoc} */
  799.         @Override
  800.         public void mapStateToArray(final FieldSpacecraftState<T> state, final T[] y, final T[] yDot) {

  801.             OrbitType.EQUINOCTIAL.mapOrbitToArray(state.getOrbit(), PositionAngleType.MEAN, y, yDot);
  802.             y[6] = state.getMass();

  803.         }

  804.         /** Set the number of satellite revolutions to use for converting osculating to mean elements.
  805.          *  <p>
  806.          *  By default, if the initial orbit is defined as osculating,
  807.          *  it will be averaged over 2 satellite revolutions.
  808.          *  This can be changed by using this method.
  809.          *  </p>
  810.          *  @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
  811.          *                             elements
  812.          */
  813.         public void setSatelliteRevolution(final int satelliteRevolution) {
  814.             this.satelliteRevolution = satelliteRevolution;
  815.         }

  816.         /** Get the number of satellite revolutions to use for converting osculating to mean elements.
  817.          *  @return number of satellite revolutions to use for converting osculating to mean elements
  818.          */
  819.         public int getSatelliteRevolution() {
  820.             return satelliteRevolution;
  821.         }

  822.         /** Set the selected short periodic coefficients that must be stored as additional states.
  823.          * @param selectedCoefficients short periodic coefficients that must be stored as additional states
  824.          * (null means no coefficients are selected, empty set means all coefficients are selected)
  825.          */
  826.         public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
  827.             this.selectedCoefficients = selectedCoefficients;
  828.         }

  829.         /** Get the selected short periodic coefficients that must be stored as additional states.
  830.          * @return short periodic coefficients that must be stored as additional states
  831.          * (null means no coefficients are selected, empty set means all coefficients are selected)
  832.          */
  833.         public Set<String> getSelectedCoefficients() {
  834.             return selectedCoefficients;
  835.         }

  836.         /** Set the short period terms.
  837.          * @param shortPeriodTerms short period terms
  838.          * @since 7.1
  839.          */
  840.         public void setShortPeriodTerms(final List<FieldShortPeriodTerms<T>> shortPeriodTerms) {
  841.             this.shortPeriodTerms = shortPeriodTerms;
  842.         }

  843.         /** Get the short period terms.
  844.          * @return shortPeriodTerms short period terms
  845.          * @since 7.1
  846.          */
  847.         public List<FieldShortPeriodTerms<T>> getShortPeriodTerms() {
  848.             return shortPeriodTerms;
  849.         }

  850.     }

  851.     /** {@inheritDoc} */
  852.     @Override
  853.     protected MainStateEquations<T> getMainStateEquations(final FieldODEIntegrator<T> integrator) {
  854.         return new Main(integrator);
  855.     }

  856.     /** Internal class for mean parameters integration. */
  857.     private class Main implements MainStateEquations<T> {

  858.         /** Derivatives array. */
  859.         private final T[] yDot;

  860.         /** Simple constructor.
  861.          * @param integrator numerical integrator to use for propagation.
  862.          */
  863.         Main(final FieldODEIntegrator<T> integrator) {
  864.             yDot = MathArrays.buildArray(field, 7);

  865.             // Setup event detectors for each force model
  866.             forceModels.forEach(dsstForceModel -> dsstForceModel.getFieldEventDetectors(field).
  867.                                 forEach(eventDetector -> setUpEventDetector(integrator, eventDetector)));
  868.         }

  869.         /** {@inheritDoc} */
  870.         @Override
  871.         public void init(final FieldSpacecraftState<T> initialState, final FieldAbsoluteDate<T> target) {
  872.             forceModels.forEach(fm -> fm.init(initialState, target));
  873.         }

  874.         /** {@inheritDoc} */
  875.         @Override
  876.         public T[] computeDerivatives(final FieldSpacecraftState<T> state) {

  877.             final T zero = state.getDate().getField().getZero();
  878.             Arrays.fill(yDot, zero);

  879.             // compute common auxiliary elements
  880.             final FieldAuxiliaryElements<T> auxiliaryElements = new FieldAuxiliaryElements<>(state.getOrbit(), I);

  881.             // compute the contributions of all perturbing forces
  882.             for (final DSSTForceModel forceModel : forceModels) {
  883.                 final T[] daidt = elementRates(forceModel, state, auxiliaryElements, forceModel.getParametersAllValues(field));
  884.                 for (int i = 0; i < daidt.length; i++) {
  885.                     yDot[i] = yDot[i].add(daidt[i]);
  886.                 }
  887.             }

  888.             return yDot.clone();
  889.         }

  890.         /** This method allows to compute the mean equinoctial elements rates da<sub>i</sub> / dt
  891.          *  for a specific force model.
  892.          *  @param forceModel force to take into account
  893.          *  @param state current state
  894.          *  @param auxiliaryElements auxiliary elements related to the current orbit
  895.          *  @param parameters force model parameters
  896.          *  @return the mean equinoctial elements rates da<sub>i</sub> / dt
  897.          */
  898.         private T[] elementRates(final DSSTForceModel forceModel,
  899.                                  final FieldSpacecraftState<T> state,
  900.                                  final FieldAuxiliaryElements<T> auxiliaryElements,
  901.                                  final T[] parameters) {
  902.             return forceModel.getMeanElementRate(state, auxiliaryElements, parameters);
  903.         }

  904.     }

  905.     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
  906.      *  <p>
  907.      *  The errors are estimated from partial derivatives properties of orbits,
  908.      *  starting from a scalar position error specified by the user.
  909.      *  Considering the energy conservation equation V = sqrt(mu (2/r - 1/a)),
  910.      *  we get at constant energy (i.e. on a Keplerian trajectory):
  911.      *
  912.      *  <pre>
  913.      *  V r² |dV| = mu |dr|
  914.      *  </pre>
  915.      *
  916.      *  <p> So we deduce a scalar velocity error consistent with the position error. From here, we apply
  917.      *  orbits Jacobians matrices to get consistent errors on orbital parameters.
  918.      *
  919.      *  <p>
  920.      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
  921.      *  local estimates, not global ones. So some care must be taken when using these tolerances.
  922.      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
  923.      *  position after several orbits integration.
  924.      *  </p>
  925.      * @param <T> elements type
  926.      * @param dP user specified position error (m)
  927.      * @param orbit reference orbit
  928.      * @return a two rows array, row 0 being the absolute tolerance error
  929.      *                       and row 1 being the relative tolerance error
  930.      * @deprecated since 13.0. Use {@link ToleranceProvider} for default and custom tolerances.
  931.      */
  932.     @Deprecated
  933.     public static <T extends CalculusFieldElement<T>> double[][] tolerances(final T dP, final FieldOrbit<T> orbit) {
  934.         // estimate the scalar velocity error
  935.         final FieldPVCoordinates<T> pv = orbit.getPVCoordinates();
  936.         final T r2 = pv.getPosition().getNormSq();
  937.         final T v  = pv.getVelocity().getNorm();
  938.         final T dV = orbit.getMu().multiply(dP).divide(v.multiply(r2));

  939.         return tolerances(dP, dV, orbit);
  940.     }

  941.     /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
  942.      *  <p>
  943.      *  The errors are estimated from partial derivatives properties of orbits,
  944.      *  starting from scalar position and velocity errors specified by the user.
  945.      *  <p>
  946.      *  The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
  947.      *  local estimates, not global ones. So some care must be taken when using these tolerances.
  948.      *  Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
  949.      *  position after several orbits integration.
  950.      *  </p>
  951.      *
  952.      * @param <T> elements type
  953.      * @param dP user specified position error (m)
  954.      * @param dV user specified velocity error (m/s)
  955.      * @param orbit reference orbit
  956.      * @return a two rows array, row 0 being the absolute tolerance error
  957.      *                       and row 1 being the relative tolerance error
  958.      * @since 10.3
  959.      * @deprecated since 13.0. Use {@link ToleranceProvider} for default and custom tolerances.
  960.      */
  961.     @Deprecated
  962.     public static <T extends CalculusFieldElement<T>> double[][] tolerances(final T dP, final T dV,
  963.                                                                             final FieldOrbit<T> orbit) {
  964.         return ToleranceProvider.of(CartesianToleranceProvider.of(dP.getReal(), dV.getReal(),
  965.                         CartesianToleranceProvider.DEFAULT_ABSOLUTE_MASS_TOLERANCE))
  966.                 .getTolerances(orbit, OrbitType.EQUINOCTIAL, PositionAngleType.MEAN);
  967.     }

  968.     /** Step handler used to compute the parameters for the short periodic contributions.
  969.      * @author Lucian Barbulescu
  970.      */
  971.     private class FieldShortPeriodicsHandler implements FieldODEStepHandler<T> {

  972.         /** Force models used to compute short periodic terms. */
  973.         private final List<DSSTForceModel> forceModels;

  974.         /** Constructor.
  975.          * @param forceModels force models
  976.          */
  977.         FieldShortPeriodicsHandler(final List<DSSTForceModel> forceModels) {
  978.             this.forceModels = forceModels;
  979.         }

  980.         /** {@inheritDoc} */
  981.         @SuppressWarnings("unchecked")
  982.         @Override
  983.         public void handleStep(final FieldODEStateInterpolator<T> interpolator) {

  984.             // Get the grid points to compute
  985.             final T[] interpolationPoints =
  986.                             interpolationgrid.getGridPoints(interpolator.getPreviousState().getTime(),
  987.                                                             interpolator.getCurrentState().getTime());

  988.             final FieldSpacecraftState<T>[] meanStates = new FieldSpacecraftState[interpolationPoints.length];
  989.             for (int i = 0; i < interpolationPoints.length; ++i) {

  990.                 // Build the mean state interpolated at grid point
  991.                 final T time = interpolationPoints[i];
  992.                 final FieldODEStateAndDerivative<T> sd = interpolator.getInterpolatedState(time);
  993.                 meanStates[i] = mapper.mapArrayToState(time,
  994.                                                        sd.getPrimaryState(),
  995.                                                        sd.getPrimaryDerivative(),
  996.                                                        PropagationType.MEAN);

  997.             }

  998.             // Compute short periodic coefficients for this step
  999.             for (DSSTForceModel forceModel : forceModels) {
  1000.                 forceModel.updateShortPeriodTerms(forceModel.getParametersAllValues(field), meanStates);
  1001.             }

  1002.         }
  1003.     }

  1004. }