NumericalPropagator.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.numerical;

  18. import org.hipparchus.exception.LocalizedCoreFormats;
  19. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  20. import org.hipparchus.linear.MatrixUtils;
  21. import org.hipparchus.linear.QRDecomposition;
  22. import org.hipparchus.linear.RealMatrix;
  23. import org.hipparchus.ode.ODEIntegrator;
  24. import org.hipparchus.util.FastMath;
  25. import org.hipparchus.util.Precision;
  26. import org.orekit.annotation.DefaultDataContext;
  27. import org.orekit.attitudes.Attitude;
  28. import org.orekit.attitudes.AttitudeProvider;
  29. import org.orekit.attitudes.AttitudeProviderModifier;
  30. import org.orekit.data.DataContext;
  31. import org.orekit.errors.OrekitException;
  32. import org.orekit.errors.OrekitIllegalArgumentException;
  33. import org.orekit.errors.OrekitInternalError;
  34. import org.orekit.errors.OrekitMessages;
  35. import org.orekit.forces.ForceModel;
  36. import org.orekit.forces.gravity.NewtonianAttraction;
  37. import org.orekit.forces.inertia.InertialForces;
  38. import org.orekit.forces.maneuvers.Maneuver;
  39. import org.orekit.forces.maneuvers.jacobians.Duration;
  40. import org.orekit.forces.maneuvers.jacobians.MedianDate;
  41. import org.orekit.forces.maneuvers.jacobians.TriggerDate;
  42. import org.orekit.forces.maneuvers.trigger.ManeuverTriggerDetector;
  43. import org.orekit.forces.maneuvers.trigger.ResettableManeuverTriggers;
  44. import org.orekit.frames.Frame;
  45. import org.orekit.orbits.Orbit;
  46. import org.orekit.orbits.OrbitType;
  47. import org.orekit.orbits.PositionAngleType;
  48. import org.orekit.propagation.AbstractMatricesHarvester;
  49. import org.orekit.propagation.AdditionalDataProvider;
  50. import org.orekit.propagation.CartesianToleranceProvider;
  51. import org.orekit.propagation.MatricesHarvester;
  52. import org.orekit.propagation.PropagationType;
  53. import org.orekit.propagation.Propagator;
  54. import org.orekit.propagation.SpacecraftState;
  55. import org.orekit.propagation.ToleranceProvider;
  56. import org.orekit.propagation.events.EventDetector;
  57. import org.orekit.propagation.events.ParameterDrivenDateIntervalDetector;
  58. import org.orekit.propagation.integration.AbstractIntegratedPropagator;
  59. import org.orekit.propagation.integration.AdditionalDerivativesProvider;
  60. import org.orekit.propagation.integration.StateMapper;
  61. import org.orekit.time.AbsoluteDate;
  62. import org.orekit.utils.AbsolutePVCoordinates;
  63. import org.orekit.utils.DoubleArrayDictionary;
  64. import org.orekit.utils.ParameterDriver;
  65. import org.orekit.utils.ParameterDriversList;
  66. import org.orekit.utils.ParameterDriversList.DelegatingDriver;
  67. import org.orekit.utils.ParameterObserver;
  68. import org.orekit.utils.TimeSpanMap;
  69. import org.orekit.utils.TimeSpanMap.Span;
  70. import org.orekit.utils.TimeStampedPVCoordinates;

  71. import java.util.ArrayList;
  72. import java.util.Arrays;
  73. import java.util.Collection;
  74. import java.util.Collections;
  75. import java.util.List;
  76. import java.util.stream.Collectors;

  77. /** This class propagates {@link org.orekit.orbits.Orbit orbits} using
  78.  * numerical integration.
  79.  * <p>Numerical propagation is much more accurate than analytical propagation
  80.  * like for example {@link org.orekit.propagation.analytical.KeplerianPropagator
  81.  * Keplerian} or {@link org.orekit.propagation.analytical.EcksteinHechlerPropagator
  82.  * Eckstein-Hechler}, but requires a few more steps to set up to be used properly.
  83.  * Whereas analytical propagators are configured only thanks to their various
  84.  * constructors and can be used immediately after construction, numerical propagators
  85.  * configuration involve setting several parameters between construction time
  86.  * and propagation time.</p>
  87.  * <p>The configuration parameters that can be set are:</p>
  88.  * <ul>
  89.  *   <li>the initial spacecraft state ({@link #setInitialState(SpacecraftState)})</li>
  90.  *   <li>the central attraction coefficient ({@link #setMu(double)})</li>
  91.  *   <li>the various force models ({@link #addForceModel(ForceModel)},
  92.  *   {@link #removeForceModels()})</li>
  93.  *   <li>the {@link OrbitType type} of orbital parameters to be used for propagation
  94.  *   ({@link #setOrbitType(OrbitType)}),</li>
  95.  *   <li>the {@link PositionAngleType type} of position angle to be used in orbital parameters
  96.  *   to be used for propagation where it is relevant ({@link
  97.  *   #setPositionAngleType(PositionAngleType)}),</li>
  98.  *   <li>whether {@link MatricesHarvester state transition matrices and Jacobians matrices}
  99.  *   should be propagated along with orbital state ({@link
  100.  *   #setupMatricesComputation(String, RealMatrix, DoubleArrayDictionary)}),</li>
  101.  *   <li>whether {@link org.orekit.propagation.integration.AdditionalDerivativesProvider additional derivatives}
  102.  *   should be propagated along with orbital state ({@link
  103.  *   #addAdditionalDerivativesProvider(AdditionalDerivativesProvider)}),</li>
  104.  *   <li>the discrete events that should be triggered during propagation
  105.  *   ({@link #addEventDetector(EventDetector)},
  106.  *   {@link #clearEventsDetectors()})</li>
  107.  *   <li>the binding logic with the rest of the application ({@link #getMultiplexer()})</li>
  108.  * </ul>
  109.  * <p>From these configuration parameters, only the initial state is mandatory. The default
  110.  * propagation settings are in {@link OrbitType#EQUINOCTIAL equinoctial} parameters with
  111.  * {@link PositionAngleType#ECCENTRIC} longitude argument. If the central attraction coefficient
  112.  * is not explicitly specified, the one used to define the initial orbit will be used.
  113.  * However, specifying only the initial state and perhaps the central attraction coefficient
  114.  * would mean the propagator would use only Keplerian forces. In this case, the simpler {@link
  115.  * org.orekit.propagation.analytical.KeplerianPropagator KeplerianPropagator} class would
  116.  * perhaps be more effective.</p>
  117.  * <p>The underlying numerical integrator set up in the constructor may also have its own
  118.  * configuration parameters. Typical configuration parameters for adaptive stepsize integrators
  119.  * are the min, max and perhaps start step size as well as the absolute and/or relative errors
  120.  * thresholds.</p>
  121.  * <p>The state that is seen by the integrator is a simple seven elements double array.
  122.  * The six first elements are either:
  123.  * <ul>
  124.  *   <li>the {@link org.orekit.orbits.EquinoctialOrbit equinoctial orbit parameters} (a, e<sub>x</sub>,
  125.  *   e<sub>y</sub>, h<sub>x</sub>, h<sub>y</sub>, λ<sub>M</sub> or λ<sub>E</sub>
  126.  *   or λ<sub>v</sub>) in meters and radians,</li>
  127.  *   <li>the {@link org.orekit.orbits.KeplerianOrbit Keplerian orbit parameters} (a, e, i, ω, Ω,
  128.  *   M or E or v) in meters and radians,</li>
  129.  *   <li>the {@link org.orekit.orbits.CircularOrbit circular orbit parameters} (a, e<sub>x</sub>, e<sub>y</sub>, i,
  130.  *   Ω, α<sub>M</sub> or α<sub>E</sub> or α<sub>v</sub>) in meters
  131.  *   and radians,</li>
  132.  *   <li>the {@link org.orekit.orbits.CartesianOrbit Cartesian orbit parameters} (x, y, z, v<sub>x</sub>,
  133.  *   v<sub>y</sub>, v<sub>z</sub>) in meters and meters per seconds.
  134.  * </ul>
  135.  * <p> The last element is the mass in kilograms and changes only during thrusters firings
  136.  *
  137.  * <p>The following code snippet shows a typical setting for Low Earth Orbit propagation in
  138.  * equinoctial parameters and true longitude argument:</p>
  139.  * <pre>
  140.  * final double dP       = 0.001;
  141.  * final double minStep  = 0.001;
  142.  * final double maxStep  = 500;
  143.  * final double initStep = 60;
  144.  * final double[][] tolerance = ToleranceProvider.getDefaultToleranceProvider(dP).getTolerances(orbit, OrbitType.EQUINOCTIAL);
  145.  * AdaptiveStepsizeIntegrator integrator = new DormandPrince853Integrator(minStep, maxStep, tolerance[0], tolerance[1]);
  146.  * integrator.setInitialStepSize(initStep);
  147.  * propagator = new NumericalPropagator(integrator);
  148.  * </pre>
  149.  * <p>By default, at the end of the propagation, the propagator resets the initial state to the final state,
  150.  * thus allowing a new propagation to be started from there without recomputing the part already performed.
  151.  * This behaviour can be changed by calling {@link #setResetAtEnd(boolean)}.
  152.  * </p>
  153.  * <p>Beware the same instance cannot be used simultaneously by different threads, the class is <em>not</em>
  154.  * thread-safe.</p>
  155.  *
  156.  * @see SpacecraftState
  157.  * @see ForceModel
  158.  * @see org.orekit.propagation.sampling.OrekitStepHandler
  159.  * @see org.orekit.propagation.sampling.OrekitFixedStepHandler
  160.  * @see org.orekit.propagation.integration.IntegratedEphemeris
  161.  * @see TimeDerivativesEquations
  162.  *
  163.  * @author Mathieu Rom&eacute;ro
  164.  * @author Luc Maisonobe
  165.  * @author Guylaine Prat
  166.  * @author Fabien Maussion
  167.  * @author V&eacute;ronique Pommier-Maurussane
  168.  */
  169. public class NumericalPropagator extends AbstractIntegratedPropagator {

  170.     /** Default orbit type. */
  171.     public static final OrbitType DEFAULT_ORBIT_TYPE = OrbitType.EQUINOCTIAL;

  172.     /** Default position angle type. */
  173.     public static final PositionAngleType DEFAULT_POSITION_ANGLE_TYPE = PositionAngleType.ECCENTRIC;

  174.     /** Space dimension. */
  175.     private static final int SPACE_DIMENSION = 3;

  176.     /** State dimension. */
  177.     private static final int STATE_DIMENSION = 2 * SPACE_DIMENSION;

  178.     /** Threshold for matrix solving. */
  179.     private static final double THRESHOLD = Precision.SAFE_MIN;

  180.     /** Force models used during the extrapolation of the orbit. */
  181.     private final List<ForceModel> forceModels;

  182.     /** boolean to ignore or not the creation of a NewtonianAttraction. */
  183.     private boolean ignoreCentralAttraction;

  184.     /**
  185.      * boolean to know if a full attitude (with rates) is needed when computing derivatives for the ODE.
  186.      * since 12.1
  187.      */
  188.     private boolean needFullAttitudeForDerivatives = true;

  189.     /** Create a new instance of NumericalPropagator, based on orbit definition mu.
  190.      * After creation, the instance is empty, i.e. the attitude provider is set to an
  191.      * unspecified default law and there are no perturbing forces at all.
  192.      * This means that if {@link #addForceModel addForceModel} is not
  193.      * called after creation, the integrated orbit will follow a Keplerian
  194.      * evolution only. The defaults are {@link OrbitType#EQUINOCTIAL}
  195.      * for {@link #setOrbitType(OrbitType) propagation
  196.      * orbit type} and {@link PositionAngleType#ECCENTRIC} for {@link
  197.      * #setPositionAngleType(PositionAngleType) position angle type}.
  198.      *
  199.      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
  200.      *
  201.      * @param integrator numerical integrator to use for propagation.
  202.      * @see #NumericalPropagator(ODEIntegrator, AttitudeProvider)
  203.      */
  204.     @DefaultDataContext
  205.     public NumericalPropagator(final ODEIntegrator integrator) {
  206.         this(integrator,
  207.                 Propagator.getDefaultLaw(DataContext.getDefault().getFrames()));
  208.     }

  209.     /** Create a new instance of NumericalPropagator, based on orbit definition mu.
  210.      * After creation, the instance is empty, i.e. the attitude provider is set to an
  211.      * unspecified default law and there are no perturbing forces at all.
  212.      * This means that if {@link #addForceModel addForceModel} is not
  213.      * called after creation, the integrated orbit will follow a Keplerian
  214.      * evolution only. The defaults are {@link OrbitType#EQUINOCTIAL}
  215.      * for {@link #setOrbitType(OrbitType) propagation
  216.      * orbit type} and {@link PositionAngleType#ECCENTRIC} for {@link
  217.      * #setPositionAngleType(PositionAngleType) position angle type}.
  218.      * @param integrator numerical integrator to use for propagation.
  219.      * @param attitudeProvider the attitude law.
  220.      * @since 10.1
  221.      */
  222.     public NumericalPropagator(final ODEIntegrator integrator,
  223.                                final AttitudeProvider attitudeProvider) {
  224.         super(integrator, PropagationType.OSCULATING);
  225.         forceModels             = new ArrayList<>();
  226.         ignoreCentralAttraction = false;
  227.         initMapper();
  228.         setAttitudeProvider(attitudeProvider);
  229.         clearStepHandlers();
  230.         setOrbitType(DEFAULT_ORBIT_TYPE);
  231.         setPositionAngleType(DEFAULT_POSITION_ANGLE_TYPE);
  232.     }

  233.     /** Set the flag to ignore or not the creation of a {@link NewtonianAttraction}.
  234.      * @param ignoreCentralAttraction if true, {@link NewtonianAttraction} is <em>not</em>
  235.      * added automatically if missing
  236.      */
  237.     public void setIgnoreCentralAttraction(final boolean ignoreCentralAttraction) {
  238.         this.ignoreCentralAttraction = ignoreCentralAttraction;
  239.     }

  240.      /** Set the central attraction coefficient μ.
  241.       * <p>
  242.       * Setting the central attraction coefficient is
  243.       * equivalent to {@link #addForceModel(ForceModel) add}
  244.       * a {@link NewtonianAttraction} force model.
  245.       * * </p>
  246.       * @param mu central attraction coefficient (m³/s²)
  247.       * @see #addForceModel(ForceModel)
  248.       * @see #getAllForceModels()
  249.       */
  250.     @Override
  251.     public void setMu(final double mu) {
  252.         if (ignoreCentralAttraction) {
  253.             superSetMu(mu);
  254.         } else {
  255.             addForceModel(new NewtonianAttraction(mu));
  256.             superSetMu(mu);
  257.         }
  258.     }

  259.     /** Set the central attraction coefficient μ only in upper class.
  260.      * @param mu central attraction coefficient (m³/s²)
  261.      */
  262.     private void superSetMu(final double mu) {
  263.         super.setMu(mu);
  264.     }

  265.     /** Check if Newtonian attraction force model is available.
  266.      * <p>
  267.      * Newtonian attraction is always the last force model in the list.
  268.      * </p>
  269.      * @return true if Newtonian attraction force model is available
  270.      */
  271.     private boolean hasNewtonianAttraction() {
  272.         final int last = forceModels.size() - 1;
  273.         return last >= 0 && forceModels.get(last) instanceof NewtonianAttraction;
  274.     }

  275.     /** Add a force model.
  276.      * <p>If this method is not called at all, the integrated orbit will follow
  277.      * a Keplerian evolution only.</p>
  278.      * @param model {@link ForceModel} to add (it can be either a perturbing force
  279.      * model or an instance of {@link NewtonianAttraction})
  280.      * @see #removeForceModels()
  281.      * @see #setMu(double)
  282.      */
  283.     public void addForceModel(final ForceModel model) {

  284.         if (model instanceof NewtonianAttraction) {
  285.             // we want to add the central attraction force model

  286.             try {
  287.                 // ensure we are notified of any mu change
  288.                 model.getParametersDrivers().get(0).addObserver(new ParameterObserver() {
  289.                     /** {@inheritDoc} */
  290.                     @Override
  291.                     public void valueSpanMapChanged(final TimeSpanMap<Double> previousValue, final ParameterDriver driver) {
  292.                         superSetMu(driver.getValue());
  293.                     }
  294.                     /** {@inheritDoc} */
  295.                     @Override
  296.                     public void valueChanged(final double previousValue, final ParameterDriver driver, final AbsoluteDate date) {
  297.                         superSetMu(driver.getValue());
  298.                     }
  299.                 });
  300.             } catch (OrekitException oe) {
  301.                 // this should never happen
  302.                 throw new OrekitInternalError(oe);
  303.             }

  304.             if (hasNewtonianAttraction()) {
  305.                 // there is already a central attraction model, replace it
  306.                 forceModels.set(forceModels.size() - 1, model);
  307.             } else {
  308.                 // there are no central attraction model yet, add it at the end of the list
  309.                 forceModels.add(model);
  310.             }
  311.         } else {
  312.             // we want to add a perturbing force model
  313.             if (hasNewtonianAttraction()) {
  314.                 // insert the new force model before Newtonian attraction,
  315.                 // which should always be the last one in the list
  316.                 forceModels.add(forceModels.size() - 1, model);
  317.             } else {
  318.                 // we only have perturbing force models up to now, just append at the end of the list
  319.                 forceModels.add(model);
  320.             }
  321.         }

  322.     }

  323.     /** Remove all force models (except central attraction).
  324.      * <p>Once all perturbing forces have been removed (and as long as no new force
  325.      * model is added), the integrated orbit will follow a Keplerian evolution
  326.      * only.</p>
  327.      * @see #addForceModel(ForceModel)
  328.      */
  329.     public void removeForceModels() {
  330.         final int last = forceModels.size() - 1;
  331.         if (hasNewtonianAttraction()) {
  332.             // preserve the Newtonian attraction model at the end
  333.             final ForceModel newton = forceModels.get(last);
  334.             forceModels.clear();
  335.             forceModels.add(newton);
  336.         } else {
  337.             forceModels.clear();
  338.         }
  339.     }

  340.     /** Get all the force models, perturbing forces and Newtonian attraction included.
  341.      * @return list of perturbing force models, with Newtonian attraction being the
  342.      * last one
  343.      * @see #addForceModel(ForceModel)
  344.      * @see #setMu(double)
  345.      */
  346.     public List<ForceModel> getAllForceModels() {
  347.         return Collections.unmodifiableList(forceModels);
  348.     }

  349.     /** Set propagation orbit type.
  350.      * @param orbitType orbit type to use for propagation, null for
  351.      * propagating using {@link org.orekit.utils.AbsolutePVCoordinates} rather than {@link Orbit}
  352.      */
  353.     @Override
  354.     public void setOrbitType(final OrbitType orbitType) {
  355.         super.setOrbitType(orbitType);
  356.     }

  357.     /** Get propagation parameter type.
  358.      * @return orbit type used for propagation, null for
  359.      * propagating using {@link org.orekit.utils.AbsolutePVCoordinates} rather than {@link Orbit}
  360.      */
  361.     @Override
  362.     public OrbitType getOrbitType() {
  363.         return super.getOrbitType();
  364.     }

  365.     /** Set position angle type.
  366.      * <p>
  367.      * The position parameter type is meaningful only if {@link
  368.      * #getOrbitType() propagation orbit type}
  369.      * support it. As an example, it is not meaningful for propagation
  370.      * in {@link OrbitType#CARTESIAN Cartesian} parameters.
  371.      * </p>
  372.      * @param positionAngleType angle type to use for propagation
  373.      */
  374.     @Override
  375.     public void setPositionAngleType(final PositionAngleType positionAngleType) {
  376.         super.setPositionAngleType(positionAngleType);
  377.     }

  378.     /** Get propagation parameter type.
  379.      * @return angle type to use for propagation
  380.      */
  381.     @Override
  382.     public PositionAngleType getPositionAngleType() {
  383.         return super.getPositionAngleType();
  384.     }

  385.     /** Set the initial state.
  386.      * @param initialState initial state
  387.      */
  388.     public void setInitialState(final SpacecraftState initialState) {
  389.         resetInitialState(initialState);
  390.     }

  391.     /** {@inheritDoc} */
  392.     @Override
  393.     public void resetInitialState(final SpacecraftState state) {
  394.         super.resetInitialState(state);
  395.         if (!hasNewtonianAttraction()) {
  396.             // use the state to define central attraction
  397.             setMu(state.isOrbitDefined() ? state.getOrbit().getMu() : Double.NaN);
  398.         }
  399.         setStartDate(state.getDate());
  400.     }

  401.     /** Get the names of the parameters in the matrix returned by {@link MatricesHarvester#getParametersJacobian}.
  402.      * @return names of the parameters (i.e. columns) of the Jacobian matrix
  403.      */
  404.     List<String> getJacobiansColumnsNames() {
  405.         final List<String> columnsNames = new ArrayList<>();
  406.         for (final ForceModel forceModel : getAllForceModels()) {
  407.             for (final ParameterDriver driver : forceModel.getParametersDrivers()) {
  408.                 if (driver.isSelected() && !columnsNames.contains(driver.getNamesSpanMap().getFirstSpan().getData())) {
  409.                     // As driver with same name should have same NamesSpanMap we only check if the first span is present,
  410.                     // if not we add all span names to columnsNames
  411.                     for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  412.                         columnsNames.add(span.getData());
  413.                     }
  414.                 }
  415.             }
  416.         }
  417.         Collections.sort(columnsNames);
  418.         return columnsNames;
  419.     }

  420.     /** {@inheritDoc} */
  421.     @Override
  422.     protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
  423.                                                         final DoubleArrayDictionary initialJacobianColumns) {
  424.         return new NumericalPropagationHarvester(this, stmName, initialStm, initialJacobianColumns);
  425.     }

  426.     /** {@inheritDoc} */
  427.     @Override
  428.     protected void setUpStmAndJacobianGenerators() {

  429.         final AbstractMatricesHarvester harvester = getHarvester();
  430.         if (harvester != null) {

  431.             // set up the additional equations and additional state providers
  432.             final StateTransitionMatrixGenerator stmGenerator = setUpStmGenerator();
  433.             final List<String> triggersDates = setUpTriggerDatesJacobiansColumns(stmGenerator.getName());
  434.             setUpRegularParametersJacobiansColumns(stmGenerator, triggersDates);

  435.             // as we are now starting the propagation, everything is configured
  436.             // we can freeze the names in the harvester
  437.             harvester.freezeColumnsNames();

  438.         }

  439.     }

  440.     /** Set up the State Transition Matrix Generator.
  441.      * @return State Transition Matrix Generator
  442.      * @since 11.1
  443.      */
  444.     private StateTransitionMatrixGenerator setUpStmGenerator() {

  445.         final AbstractMatricesHarvester harvester = getHarvester();

  446.         // add the STM generator corresponding to the current settings, and setup state accordingly
  447.         StateTransitionMatrixGenerator stmGenerator = null;
  448.         for (final AdditionalDerivativesProvider equations : getAdditionalDerivativesProviders()) {
  449.             if (equations instanceof StateTransitionMatrixGenerator &&
  450.                 equations.getName().equals(harvester.getStmName())) {
  451.                 // the STM generator has already been set up in a previous propagation
  452.                 stmGenerator = (StateTransitionMatrixGenerator) equations;
  453.                 break;
  454.             }
  455.         }
  456.         if (stmGenerator == null) {
  457.             // this is the first time we need the STM generate, create it
  458.             stmGenerator = new StateTransitionMatrixGenerator(harvester.getStmName(), getAllForceModels(), getAttitudeProvider());
  459.             addAdditionalDerivativesProvider(stmGenerator);
  460.         }

  461.         if (!getInitialIntegrationState().hasAdditionalData(harvester.getStmName())) {
  462.             // add the initial State Transition Matrix if it is not already there
  463.             // (perhaps due to a previous propagation)
  464.             setInitialState(stmGenerator.setInitialStateTransitionMatrix(getInitialState(),
  465.                                                                          harvester.getInitialStateTransitionMatrix(),
  466.                                                                          getOrbitType(),
  467.                                                                          getPositionAngleType()));
  468.         }

  469.         return stmGenerator;

  470.     }

  471.     /** Set up the Jacobians columns generator dedicated to trigger dates.
  472.      * @param stmName name of the State Transition Matrix state
  473.      * @return names of the columns corresponding to trigger dates
  474.      * @since 11.1
  475.      */
  476.     private List<String> setUpTriggerDatesJacobiansColumns(final String stmName) {

  477.         final List<String> names = new ArrayList<>();
  478.         for (final ForceModel forceModel : getAllForceModels()) {
  479.             if (forceModel instanceof Maneuver && ((Maneuver) forceModel).getManeuverTriggers() instanceof ResettableManeuverTriggers) {
  480.                 final Maneuver maneuver = (Maneuver) forceModel;
  481.                 final ResettableManeuverTriggers maneuverTriggers = (ResettableManeuverTriggers) maneuver.getManeuverTriggers();

  482.                 final Collection<EventDetector> selectedDetectors = maneuverTriggers.getEventDetectors().
  483.                         filter(ManeuverTriggerDetector.class::isInstance).
  484.                         map(triggerDetector -> ((ManeuverTriggerDetector<?>) triggerDetector).getDetector())
  485.                         .collect(Collectors.toList());
  486.                 for (final EventDetector detector: selectedDetectors) {
  487.                     if (detector instanceof ParameterDrivenDateIntervalDetector) {
  488.                         final ParameterDrivenDateIntervalDetector d = (ParameterDrivenDateIntervalDetector) detector;
  489.                         TriggerDate start;
  490.                         TriggerDate stop;

  491.                         if (d.getStartDriver().isSelected() || d.getMedianDriver().isSelected() || d.getDurationDriver().isSelected()) {
  492.                             // normally datedriver should have only 1 span but just in case the user defines several span, there will
  493.                             // be no problem here
  494.                             for (Span<String> span = d.getStartDriver().getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  495.                                 start = manageTriggerDate(stmName, maneuver, maneuverTriggers, span.getData(), true, d.getThreshold());
  496.                                 names.add(start.getName());
  497.                                 start = null;
  498.                             }
  499.                         }
  500.                         if (d.getStopDriver().isSelected() || d.getMedianDriver().isSelected() || d.getDurationDriver().isSelected()) {
  501.                             // normally datedriver should have only 1 span but just in case the user defines several span, there will
  502.                             // be no problem here
  503.                             for (Span<String> span = d.getStopDriver().getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  504.                                 stop = manageTriggerDate(stmName, maneuver, maneuverTriggers, span.getData(), false, d.getThreshold());
  505.                                 names.add(stop.getName());
  506.                                 stop = null;
  507.                             }
  508.                         }
  509.                         if (d.getMedianDriver().isSelected()) {
  510.                             // for first span
  511.                             Span<String> currentMedianNameSpan = d.getMedianDriver().getNamesSpanMap().getFirstSpan();
  512.                             MedianDate median =
  513.                                     manageMedianDate(d.getStartDriver().getNamesSpanMap().getFirstSpan().getData(),
  514.                                             d.getStopDriver().getNamesSpanMap().getFirstSpan().getData(), currentMedianNameSpan.getData());
  515.                             names.add(median.getName());
  516.                             // for all span
  517.                             // normally datedriver should have only 1 span but just in case the user defines several span, there will
  518.                             // be no problem here. /!\ medianDate driver, startDate driver and stopDate driver must have same span number
  519.                             for (int spanNumber = 1; spanNumber < d.getMedianDriver().getNamesSpanMap().getSpansNumber(); ++spanNumber) {
  520.                                 currentMedianNameSpan = d.getMedianDriver().getNamesSpanMap().getSpan(currentMedianNameSpan.getEnd());
  521.                                 median =
  522.                                         manageMedianDate(d.getStartDriver().getNamesSpanMap().getSpan(currentMedianNameSpan.getStart()).getData(),
  523.                                                 d.getStopDriver().getNamesSpanMap().getSpan(currentMedianNameSpan.getStart()).getData(),
  524.                                                 currentMedianNameSpan.getData());
  525.                                 names.add(median.getName());

  526.                             }

  527.                         }
  528.                         if (d.getDurationDriver().isSelected()) {
  529.                             // for first span
  530.                             Span<String> currentDurationNameSpan = d.getDurationDriver().getNamesSpanMap().getFirstSpan();
  531.                             Duration duration =
  532.                                     manageManeuverDuration(d.getStartDriver().getNamesSpanMap().getFirstSpan().getData(),
  533.                                             d.getStopDriver().getNamesSpanMap().getFirstSpan().getData(), currentDurationNameSpan.getData());
  534.                             names.add(duration.getName());
  535.                             // for all span
  536.                             for (int spanNumber = 1; spanNumber < d.getDurationDriver().getNamesSpanMap().getSpansNumber(); ++spanNumber) {
  537.                                 currentDurationNameSpan = d.getDurationDriver().getNamesSpanMap().getSpan(currentDurationNameSpan.getEnd());
  538.                                 duration =
  539.                                         manageManeuverDuration(d.getStartDriver().getNamesSpanMap().getSpan(currentDurationNameSpan.getStart()).getData(),
  540.                                                 d.getStopDriver().getNamesSpanMap().getSpan(currentDurationNameSpan.getStart()).getData(),
  541.                                                 currentDurationNameSpan.getData());
  542.                                 names.add(duration.getName());

  543.                             }
  544.                         }
  545.                     }
  546.                 }
  547.             }
  548.         }

  549.         return names;

  550.     }

  551.     /** Manage a maneuver trigger date.
  552.      * @param stmName name of the State Transition Matrix state
  553.      * @param maneuver maneuver force model
  554.      * @param mt trigger to which the driver is bound
  555.      * @param driverName name of the date driver
  556.      * @param start if true, the driver is a maneuver start
  557.      * @param threshold event detector threshold
  558.      * @return generator for the date driver
  559.      * @since 11.1
  560.      */
  561.     private TriggerDate manageTriggerDate(final String stmName,
  562.                                           final Maneuver maneuver,
  563.                                           final ResettableManeuverTriggers mt,
  564.                                           final String driverName,
  565.                                           final boolean start,
  566.                                           final double threshold) {

  567.         TriggerDate triggerGenerator = null;

  568.         // check if we already have set up the provider
  569.         for (final AdditionalDataProvider<?> provider : getAdditionalDataProviders()) {
  570.             if (provider instanceof TriggerDate &&
  571.                 provider.getName().equals(driverName)) {
  572.                 // the Jacobian column generator has already been set up in a previous propagation
  573.                 triggerGenerator = (TriggerDate) provider;
  574.                 break;
  575.             }
  576.         }

  577.         if (triggerGenerator == null) {
  578.             // this is the first time we need the Jacobian column generator, create it
  579.             triggerGenerator = new TriggerDate(stmName, driverName, start, maneuver, threshold);
  580.             mt.addResetter(triggerGenerator);
  581.             addAdditionalDerivativesProvider(triggerGenerator.getMassDepletionDelay());
  582.             addAdditionalDataProvider(triggerGenerator);
  583.         }

  584.         if (!getInitialIntegrationState().hasAdditionalData(driverName)) {
  585.             // add the initial Jacobian column if it is not already there
  586.             // (perhaps due to a previous propagation)
  587.             setInitialColumn(triggerGenerator.getMassDepletionDelay().getName(), new double[6]);
  588.             setInitialColumn(driverName, getHarvester().getInitialJacobianColumn(driverName));
  589.         }

  590.         return triggerGenerator;

  591.     }

  592.     /** Manage a maneuver median date.
  593.      * @param startName name of the start driver
  594.      * @param stopName name of the stop driver
  595.      * @param medianName name of the median driver
  596.      * @return generator for the median driver
  597.      * @since 11.1
  598.      */
  599.     private MedianDate manageMedianDate(final String startName, final String stopName, final String medianName) {

  600.         MedianDate medianGenerator = null;

  601.         // check if we already have set up the provider
  602.         for (final AdditionalDataProvider<?> provider : getAdditionalDataProviders()) {
  603.             if (provider instanceof MedianDate &&
  604.                 provider.getName().equals(medianName)) {
  605.                 // the Jacobian column generator has already been set up in a previous propagation
  606.                 medianGenerator = (MedianDate) provider;
  607.                 break;
  608.             }
  609.         }

  610.         if (medianGenerator == null) {
  611.             // this is the first time we need the Jacobian column generator, create it
  612.             medianGenerator = new MedianDate(startName, stopName, medianName);
  613.             addAdditionalDataProvider(medianGenerator);
  614.         }

  615.         if (!getInitialIntegrationState().hasAdditionalData(medianName)) {
  616.             // add the initial Jacobian column if it is not already there
  617.             // (perhaps due to a previous propagation)
  618.             setInitialColumn(medianName, getHarvester().getInitialJacobianColumn(medianName));
  619.         }

  620.         return medianGenerator;

  621.     }

  622.     /** Manage a maneuver duration.
  623.      * @param startName name of the start driver
  624.      * @param stopName name of the stop driver
  625.      * @param durationName name of the duration driver
  626.      * @return generator for the median driver
  627.      * @since 11.1
  628.      */
  629.     private Duration manageManeuverDuration(final String startName, final String stopName, final String durationName) {

  630.         Duration durationGenerator = null;

  631.         // check if we already have set up the provider
  632.         for (final AdditionalDataProvider<?> provider : getAdditionalDataProviders()) {
  633.             if (provider instanceof Duration &&
  634.                 provider.getName().equals(durationName)) {
  635.                 // the Jacobian column generator has already been set up in a previous propagation
  636.                 durationGenerator = (Duration) provider;
  637.                 break;
  638.             }
  639.         }

  640.         if (durationGenerator == null) {
  641.             // this is the first time we need the Jacobian column generator, create it
  642.             durationGenerator = new Duration(startName, stopName, durationName);
  643.             addAdditionalDataProvider(durationGenerator);
  644.         }

  645.         if (!getInitialIntegrationState().hasAdditionalData(durationName)) {
  646.             // add the initial Jacobian column if it is not already there
  647.             // (perhaps due to a previous propagation)
  648.             setInitialColumn(durationName, getHarvester().getInitialJacobianColumn(durationName));
  649.         }

  650.         return durationGenerator;

  651.     }

  652.     /** Set up the Jacobians columns generator for regular parameters.
  653.      * @param stmGenerator generator for the State Transition Matrix
  654.      * @param triggerDates names of the columns already managed as trigger dates
  655.      * @since 11.1
  656.      */
  657.     private void setUpRegularParametersJacobiansColumns(final StateTransitionMatrixGenerator stmGenerator,
  658.                                                         final List<String> triggerDates) {

  659.         // first pass: gather all parameters (excluding trigger dates), binding similar names together
  660.         final ParameterDriversList selected = new ParameterDriversList();
  661.         for (final ForceModel forceModel : getAllForceModels()) {
  662.             for (final ParameterDriver driver : forceModel.getParametersDrivers()) {
  663.                 if (!triggerDates.contains(driver.getNamesSpanMap().getFirstSpan().getData())) {
  664.                     // if the first span is not in triggerDates,
  665.                     // it means that the driver is not a trigger date and can be selected here
  666.                     selected.add(driver);
  667.                 }
  668.             }
  669.         }

  670.         // second pass: now that shared parameter names are bound together,
  671.         // their selections status have been synchronized, we can filter them
  672.         selected.filter(true);

  673.         // third pass: sort parameters lexicographically
  674.         selected.sort();

  675.         // add the Jacobians column generators corresponding to parameters, and setup state accordingly
  676.         // a new column is needed for each value estimated so for each span of the parameterDriver
  677.         for (final DelegatingDriver driver : selected.getDrivers()) {

  678.             for (Span<String> currentNameSpan = driver.getNamesSpanMap().getFirstSpan(); currentNameSpan != null; currentNameSpan = currentNameSpan.next()) {

  679.                 IntegrableJacobianColumnGenerator generator = null;
  680.                 // check if we already have set up the providers
  681.                 for (final AdditionalDerivativesProvider provider : getAdditionalDerivativesProviders()) {
  682.                     if (provider instanceof IntegrableJacobianColumnGenerator &&
  683.                         provider.getName().equals(currentNameSpan.getData())) {
  684.                         // the Jacobian column generator has already been set up in a previous propagation
  685.                         generator = (IntegrableJacobianColumnGenerator) provider;
  686.                         break;
  687.                     }

  688.                 }

  689.                 if (generator == null) {
  690.                     // this is the first time we need the Jacobian column generator, create it
  691.                     generator = new IntegrableJacobianColumnGenerator(stmGenerator, currentNameSpan.getData());
  692.                     addAdditionalDerivativesProvider(generator);
  693.                 }

  694.                 if (!getInitialIntegrationState().hasAdditionalData(currentNameSpan.getData())) {
  695.                     // add the initial Jacobian column if it is not already there
  696.                     // (perhaps due to a previous propagation)
  697.                     setInitialColumn(currentNameSpan.getData(), getHarvester().getInitialJacobianColumn(currentNameSpan.getData()));
  698.                 }

  699.             }

  700.         }

  701.     }

  702.     /** Add the initial value of the column to the initial state.
  703.      * <p>
  704.      * The initial state must already contain the Cartesian State Transition Matrix.
  705.      * </p>
  706.      * @param columnName name of the column
  707.      * @param dYdQ column of the Jacobian ∂Y/∂qₘ with respect to propagation type,
  708.      * if null (which is the most frequent case), assumed to be 0
  709.      * @since 11.1
  710.      */
  711.     private void setInitialColumn(final String columnName, final double[] dYdQ) {

  712.         final SpacecraftState state = getInitialState();

  713.         if (dYdQ.length != STATE_DIMENSION) {
  714.             throw new OrekitException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  715.                                       dYdQ.length, STATE_DIMENSION);
  716.         }

  717.         // convert to Cartesian Jacobian
  718.         final double[][] dYdC = new double[STATE_DIMENSION][STATE_DIMENSION];
  719.         getOrbitType().convertType(state.getOrbit()).getJacobianWrtCartesian(getPositionAngleType(), dYdC);
  720.         final double[] column = new QRDecomposition(MatrixUtils.createRealMatrix(dYdC), THRESHOLD).
  721.                         getSolver().
  722.                         solve(MatrixUtils.createRealVector(dYdQ)).
  723.                         toArray();

  724.         // set additional state
  725.         setInitialState(state.addAdditionalData(columnName, column));

  726.     }

  727.     /** {@inheritDoc} */
  728.     @Override
  729.     protected AttitudeProvider initializeAttitudeProviderForDerivatives() {
  730.         final AttitudeProvider attitudeProvider = getAttitudeProvider();
  731.         return needFullAttitudeForDerivatives ? attitudeProvider :
  732.                 AttitudeProviderModifier.getFrozenAttitudeProvider(attitudeProvider);
  733.     }

  734.     /** {@inheritDoc} */
  735.     @Override
  736.     protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
  737.                                        final OrbitType orbitType, final PositionAngleType positionAngleType,
  738.                                        final AttitudeProvider attitudeProvider, final Frame frame) {
  739.         return new OsculatingMapper(referenceDate, mu, orbitType, positionAngleType, attitudeProvider, frame);
  740.     }

  741.     /** Internal mapper using directly osculating parameters. */
  742.     private static class OsculatingMapper extends StateMapper {

  743.         /** Simple constructor.
  744.          * <p>
  745.          * The position parameter type is meaningful only if {@link
  746.          * #getOrbitType() propagation orbit type}
  747.          * support it. As an example, it is not meaningful for propagation
  748.          * in {@link OrbitType#CARTESIAN Cartesian} parameters.
  749.          * </p>
  750.          * @param referenceDate reference date
  751.          * @param mu central attraction coefficient (m³/s²)
  752.          * @param orbitType orbit type to use for mapping (can be null for {@link AbsolutePVCoordinates})
  753.          * @param positionAngleType angle type to use for propagation
  754.          * @param attitudeProvider attitude provider
  755.          * @param frame inertial frame
  756.          */
  757.         OsculatingMapper(final AbsoluteDate referenceDate, final double mu,
  758.                          final OrbitType orbitType, final PositionAngleType positionAngleType,
  759.                          final AttitudeProvider attitudeProvider, final Frame frame) {
  760.             super(referenceDate, mu, orbitType, positionAngleType, attitudeProvider, frame);
  761.         }

  762.         /** {@inheritDoc} */
  763.         public SpacecraftState mapArrayToState(final AbsoluteDate date, final double[] y, final double[] yDot,
  764.                                                final PropagationType type) {
  765.             // the parameter type is ignored for the Numerical Propagator

  766.             final double mass = y[6];
  767.             if (mass <= 0.0) {
  768.                 throw new OrekitException(OrekitMessages.NOT_POSITIVE_SPACECRAFT_MASS, mass);
  769.             }

  770.             if (getOrbitType() == null) {
  771.                 // propagation uses absolute position-velocity-acceleration
  772.                 final Vector3D p = new Vector3D(y[0],    y[1],    y[2]);
  773.                 final Vector3D v = new Vector3D(y[3],    y[4],    y[5]);
  774.                 final Vector3D a;
  775.                 final AbsolutePVCoordinates absPva;
  776.                 if (yDot == null) {
  777.                     absPva = new AbsolutePVCoordinates(getFrame(), new TimeStampedPVCoordinates(date, p, v));
  778.                 } else {
  779.                     a = new Vector3D(yDot[3], yDot[4], yDot[5]);
  780.                     absPva = new AbsolutePVCoordinates(getFrame(), new TimeStampedPVCoordinates(date, p, v, a));
  781.                 }

  782.                 final Attitude attitude = getAttitudeProvider().getAttitude(absPva, date, getFrame());
  783.                 return new SpacecraftState(absPva, attitude).withMass(mass);
  784.             } else {
  785.                 // propagation uses regular orbits
  786.                 final Orbit orbit       = getOrbitType().mapArrayToOrbit(y, yDot, getPositionAngleType(), date, getMu(), getFrame());
  787.                 final Attitude attitude = getAttitudeProvider().getAttitude(orbit, date, getFrame());

  788.                 return new SpacecraftState(orbit, attitude).withMass(mass);
  789.             }

  790.         }

  791.         /** {@inheritDoc} */
  792.         public void mapStateToArray(final SpacecraftState state, final double[] y, final double[] yDot) {
  793.             if (getOrbitType() == null) {
  794.                 // propagation uses absolute position-velocity-acceleration
  795.                 final Vector3D p = state.getAbsPVA().getPosition();
  796.                 final Vector3D v = state.getAbsPVA().getVelocity();
  797.                 y[0] = p.getX();
  798.                 y[1] = p.getY();
  799.                 y[2] = p.getZ();
  800.                 y[3] = v.getX();
  801.                 y[4] = v.getY();
  802.                 y[5] = v.getZ();
  803.                 y[6] = state.getMass();
  804.             }
  805.             else {
  806.                 getOrbitType().mapOrbitToArray(state.getOrbit(), getPositionAngleType(), y, yDot);
  807.                 y[6] = state.getMass();
  808.             }
  809.         }

  810.     }

  811.     /** {@inheritDoc} */
  812.     protected MainStateEquations getMainStateEquations(final ODEIntegrator integrator) {
  813.         return new Main(integrator);
  814.     }

  815.     /** Internal class for osculating parameters integration. */
  816.     private class Main implements MainStateEquations, TimeDerivativesEquations {

  817.         /** Derivatives array. */
  818.         private final double[] yDot;

  819.         /** Current state. */
  820.         private SpacecraftState currentState;

  821.         /** Jacobian of the orbital parameters with respect to the Cartesian parameters. */
  822.         private final double[][] jacobian;

  823.         /** Flag keeping track whether Jacobian matrix needs to be recomputed or not. */
  824.         private boolean recomputingJacobian;

  825.         /** Simple constructor.
  826.          * @param integrator numerical integrator to use for propagation.
  827.          */
  828.         Main(final ODEIntegrator integrator) {

  829.             this.yDot     = new double[7];
  830.             this.jacobian = new double[6][6];
  831.             this.recomputingJacobian = true;

  832.             // feed internal event detectors
  833.             for (final ForceModel forceModel : forceModels) {
  834.                 forceModel.getEventDetectors().forEach(detector -> setUpEventDetector(integrator, detector));
  835.             }
  836.             getAttitudeProvider().getEventDetectors().forEach(detector -> setUpEventDetector(integrator, detector));

  837.             // default value for Jacobian is identity
  838.             for (int i = 0; i < jacobian.length; ++i) {
  839.                 Arrays.fill(jacobian[i], 0.0);
  840.                 jacobian[i][i] = 1.0;
  841.             }

  842.         }

  843.         /** {@inheritDoc} */
  844.         @Override
  845.         public void init(final SpacecraftState initialState, final AbsoluteDate target) {
  846.             needFullAttitudeForDerivatives = forceModels.stream().anyMatch(ForceModel::dependsOnAttitudeRate);

  847.             forceModels.forEach(fm -> fm.init(initialState, target));

  848.             final int numberOfForces = forceModels.size();
  849.             final OrbitType orbitType = getOrbitType();
  850.             if (orbitType != null && orbitType != OrbitType.CARTESIAN && numberOfForces > 0) {
  851.                 if (numberOfForces > 1) {
  852.                     recomputingJacobian = true;
  853.                 } else {
  854.                     recomputingJacobian = !(forceModels.get(0) instanceof NewtonianAttraction);
  855.                 }
  856.             } else {
  857.                 recomputingJacobian = false;
  858.             }
  859.         }

  860.         /** {@inheritDoc} */
  861.         @Override
  862.         public double[] computeDerivatives(final SpacecraftState state) {

  863.             currentState = state;
  864.             Arrays.fill(yDot, 0.0);
  865.             if (recomputingJacobian) {
  866.                 // propagation uses Jacobian matrix of orbital parameters w.r.t. Cartesian ones
  867.                 currentState.getOrbit().getJacobianWrtCartesian(getPositionAngleType(), jacobian);
  868.             }

  869.             // compute the contributions of all perturbing forces,
  870.             // using the Kepler contribution at the end since
  871.             // NewtonianAttraction is always the last instance in the list
  872.             for (final ForceModel forceModel : forceModels) {
  873.                 forceModel.addContribution(state, this);
  874.             }

  875.             if (getOrbitType() == null) {
  876.                 // position derivative is velocity, and was not added above in the force models
  877.                 // (it is added when orbit type is non-null because NewtonianAttraction considers it)
  878.                 final Vector3D velocity = currentState.getPVCoordinates().getVelocity();
  879.                 yDot[0] += velocity.getX();
  880.                 yDot[1] += velocity.getY();
  881.                 yDot[2] += velocity.getZ();
  882.             }


  883.             return yDot.clone();

  884.         }

  885.         /** {@inheritDoc} */
  886.         @Override
  887.         public void addKeplerContribution(final double mu) {
  888.             if (getOrbitType() == null) {
  889.                 // if mu is neither 0 nor NaN, we want to include Newtonian acceleration
  890.                 if (mu > 0) {
  891.                     // velocity derivative is Newtonian acceleration
  892.                     final Vector3D position = currentState.getPosition();
  893.                     final double r2         = position.getNormSq();
  894.                     final double coeff      = -mu / (r2 * FastMath.sqrt(r2));
  895.                     yDot[3] += coeff * position.getX();
  896.                     yDot[4] += coeff * position.getY();
  897.                     yDot[5] += coeff * position.getZ();
  898.                 }

  899.             } else {
  900.                 // propagation uses regular orbits
  901.                 currentState.getOrbit().addKeplerContribution(getPositionAngleType(), mu, yDot);
  902.             }
  903.         }

  904.         /** {@inheritDoc} */
  905.         public void addNonKeplerianAcceleration(final Vector3D gamma) {
  906.             for (int i = 0; i < 6; ++i) {
  907.                 final double[] jRow = jacobian[i];
  908.                 yDot[i] += jRow[3] * gamma.getX() + jRow[4] * gamma.getY() + jRow[5] * gamma.getZ();
  909.             }
  910.         }

  911.         /** {@inheritDoc} */
  912.         @Override
  913.         public void addMassDerivative(final double q) {
  914.             if (q > 0) {
  915.                 throw new OrekitIllegalArgumentException(OrekitMessages.POSITIVE_FLOW_RATE, q);
  916.             }
  917.             yDot[6] += q;
  918.         }

  919.     }

  920.     /** Estimate tolerance vectors for integrators when propagating in absolute position-velocity-acceleration.
  921.      * @param dP user specified position error
  922.      * @param absPva reference absolute position-velocity-acceleration
  923.      * @return a two rows array, row 0 being the absolute tolerance error and row 1
  924.      * being the relative tolerance error
  925.      * @deprecated since 13.0. Use {@link ToleranceProvider} for default and custom tolerances.
  926.      */
  927.     @Deprecated
  928.     public static double[][] tolerances(final double dP, final AbsolutePVCoordinates absPva) {
  929.         return ToleranceProvider.of(CartesianToleranceProvider.of(dP)).getTolerances(absPva);
  930.     }

  931.     /** Estimate tolerance vectors for integrators when propagating in orbits.
  932.      * <p>
  933.      * The errors are estimated from partial derivatives properties of orbits,
  934.      * starting from a scalar position error specified by the user.
  935.      * Considering the energy conservation equation V = sqrt(mu (2/r - 1/a)),
  936.      * we get at constant energy (i.e. on a Keplerian trajectory):
  937.      * <pre>
  938.      * V r² |dV| = mu |dr|
  939.      * </pre>
  940.      * <p> So we deduce a scalar velocity error consistent with the position error.
  941.      * From here, we apply orbits Jacobians matrices to get consistent errors
  942.      * on orbital parameters.
  943.      *
  944.      * <p>
  945.      * The tolerances are only <em>orders of magnitude</em>, and integrator tolerances
  946.      * are only local estimates, not global ones. So some care must be taken when using
  947.      * these tolerances. Setting 1mm as a position error does NOT mean the tolerances
  948.      * will guarantee a 1mm error position after several orbits integration.
  949.      * </p>
  950.      * @param dP user specified position error
  951.      * @param orbit reference orbit
  952.      * @param type propagation type for the meaning of the tolerance vectors elements
  953.      * (it may be different from {@code orbit.getType()})
  954.      * @return a two rows array, row 0 being the absolute tolerance error and row 1
  955.      * being the relative tolerance error
  956.      * @deprecated since 13.0. Use {@link ToleranceProvider} for default and custom tolerances.
  957.      */
  958.     @Deprecated
  959.     public static double[][] tolerances(final double dP, final Orbit orbit, final OrbitType type) {
  960.         return ToleranceProvider.getDefaultToleranceProvider(dP).getTolerances(orbit, type, PositionAngleType.TRUE);
  961.     }

  962.     /** Estimate tolerance vectors for integrators when propagating in orbits.
  963.      * <p>
  964.      * The errors are estimated from partial derivatives properties of orbits,
  965.      * starting from scalar position and velocity errors specified by the user.
  966.      * <p>
  967.      * The tolerances are only <em>orders of magnitude</em>, and integrator tolerances
  968.      * are only local estimates, not global ones. So some care must be taken when using
  969.      * these tolerances. Setting 1mm as a position error does NOT mean the tolerances
  970.      * will guarantee a 1mm error position after several orbits integration.
  971.      * </p>
  972.      * @param dP user specified position error
  973.      * @param dV user specified velocity error
  974.      * @param orbit reference orbit
  975.      * @param type propagation type for the meaning of the tolerance vectors elements
  976.      * (it may be different from {@code orbit.getType()})
  977.      * @return a two rows array, row 0 being the absolute tolerance error and row 1
  978.      * being the relative tolerance error
  979.      * @since 10.3
  980.      * @deprecated since 13.0. Use {@link ToleranceProvider} for default and custom tolerances.
  981.      */
  982.     @Deprecated
  983.     public static double[][] tolerances(final double dP, final double dV,
  984.                                         final Orbit orbit, final OrbitType type) {

  985.         return ToleranceProvider.of(CartesianToleranceProvider.of(dP, dV, CartesianToleranceProvider.DEFAULT_ABSOLUTE_MASS_TOLERANCE))
  986.                 .getTolerances(orbit, type, PositionAngleType.TRUE);
  987.     }

  988.     /** {@inheritDoc} */
  989.     @Override
  990.     protected void beforeIntegration(final SpacecraftState initialState, final AbsoluteDate tEnd) {

  991.         if (!getFrame().isPseudoInertial()) {

  992.             // inspect all force models to find InertialForces
  993.             for (ForceModel force : forceModels) {
  994.                 if (force instanceof InertialForces) {
  995.                     return;
  996.                 }
  997.             }

  998.             // throw exception if no inertial forces found
  999.             throw new OrekitException(OrekitMessages.INERTIAL_FORCE_MODEL_MISSING, getFrame().getName());

  1000.         }

  1001.     }

  1002. }