BrouwerLyddanePropagator.java

  1. /* Copyright 2002-2024 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.analytical;

  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.List;

  21. import org.hipparchus.analysis.differentiation.UnivariateDerivative2;
  22. import org.hipparchus.linear.RealMatrix;
  23. import org.hipparchus.util.CombinatoricsUtils;
  24. import org.hipparchus.util.FastMath;
  25. import org.hipparchus.util.MathUtils;
  26. import org.hipparchus.util.Precision;
  27. import org.hipparchus.util.SinCos;
  28. import org.orekit.attitudes.AttitudeProvider;
  29. import org.orekit.attitudes.FrameAlignedProvider;
  30. import org.orekit.errors.OrekitException;
  31. import org.orekit.errors.OrekitMessages;
  32. import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider;
  33. import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider.UnnormalizedSphericalHarmonics;
  34. import org.orekit.orbits.KeplerianOrbit;
  35. import org.orekit.orbits.Orbit;
  36. import org.orekit.orbits.OrbitType;
  37. import org.orekit.orbits.PositionAngleType;
  38. import org.orekit.propagation.AbstractMatricesHarvester;
  39. import org.orekit.propagation.MatricesHarvester;
  40. import org.orekit.propagation.PropagationType;
  41. import org.orekit.propagation.SpacecraftState;
  42. import org.orekit.propagation.analytical.tle.TLE;
  43. import org.orekit.time.AbsoluteDate;
  44. import org.orekit.utils.DoubleArrayDictionary;
  45. import org.orekit.utils.ParameterDriver;
  46. import org.orekit.utils.ParameterDriversProvider;
  47. import org.orekit.utils.TimeSpanMap;
  48. import org.orekit.utils.TimeSpanMap.Span;

  49. /**
  50.  * This class propagates a {@link org.orekit.propagation.SpacecraftState}
  51.  *  using the analytical Brouwer-Lyddane model (from J2 to J5 zonal harmonics).
  52.  * <p>
  53.  * At the opposite of the {@link EcksteinHechlerPropagator}, the Brouwer-Lyddane model is
  54.  * suited for elliptical orbits, there is no problem having a rather small eccentricity or inclination
  55.  * (Lyddane helped to solve this issue with the Brouwer model). Singularity for the critical
  56.  * inclination i = 63.4° is avoided using the method developed in Warren Phipps' 1992 thesis.
  57.  * <p>
  58.  * By default, Brouwer-Lyddane model considers only the perturbations due to zonal harmonics.
  59.  * However, for low Earth orbits, the magnitude of the perturbative acceleration due to
  60.  * atmospheric drag can be significant. Warren Phipps' 1992 thesis considered the atmospheric
  61.  * drag by time derivatives of the <i>mean</i> mean anomaly using the catch-all coefficient
  62.  * {@link #M2Driver}. Beware that M2Driver must have only 1 span on its TimeSpanMap value.
  63.  *
  64.  * Usually, M2 is adjusted during an orbit determination process and it represents the
  65.  * combination of all unmodeled secular along-track effects (i.e. not just the atmospheric drag).
  66.  * The behavior of M2 is close to the {@link TLE#getBStar()} parameter for the TLE.
  67.  *
  68.  * If the value of M2 is equal to {@link #M2 0.0}, the along-track  secular effects are not
  69.  * considered in the dynamical model. Typical values for M2 are not known. It depends on the
  70.  * orbit type. However, the value of M2 must be very small (e.g. between 1.0e-14 and 1.0e-15).
  71.  * The unit of M2 is rad/s².
  72.  *
  73.  * The along-track effects, represented by the secular rates of the mean semi-major axis
  74.  * and eccentricity, are computed following Eq. 2.38, 2.41, and 2.45 of Warren Phipps' thesis.
  75.  *
  76.  * @see "Brouwer, Dirk. Solution of the problem of artificial satellite theory without drag.
  77.  *       YALE UNIV NEW HAVEN CT NEW HAVEN United States, 1959."
  78.  *
  79.  * @see "Lyddane, R. H. Small eccentricities or inclinations in the Brouwer theory of the
  80.  *       artificial satellite. The Astronomical Journal 68 (1963): 555."
  81.  *
  82.  * @see "Phipps Jr, Warren E. Parallelization of the Navy Space Surveillance Center
  83.  *       (NAVSPASUR) Satellite Model. NAVAL POSTGRADUATE SCHOOL MONTEREY CA, 1992."
  84.  *
  85.  * @author Melina Vanel
  86.  * @author Bryan Cazabonne
  87.  * @since 11.1
  88.  */
  89. public class BrouwerLyddanePropagator extends AbstractAnalyticalPropagator implements ParameterDriversProvider {

  90.     /** Parameter name for M2 coefficient. */
  91.     public static final String M2_NAME = "M2";

  92.     /** Default value for M2 coefficient. */
  93.     public static final double M2 = 0.0;

  94.     /** Default convergence threshold for mean parameters conversion. */
  95.     private static final double EPSILON_DEFAULT = 1.0e-13;

  96.     /** Default value for maxIterations. */
  97.     private static final int MAX_ITERATIONS_DEFAULT = 200;

  98.     /** Parameters scaling factor.
  99.      * <p>
  100.      * We use a power of 2 to avoid numeric noise introduction
  101.      * in the multiplications/divisions sequences.
  102.      * </p>
  103.      */
  104.     private static final double SCALE = FastMath.scalb(1.0, -32);

  105.     /** Beta constant used by T2 function. */
  106.     private static final double BETA = FastMath.scalb(100, -11);

  107.     /** Initial Brouwer-Lyddane model. */
  108.     private BLModel initialModel;

  109.     /** All models. */
  110.     private transient TimeSpanMap<BLModel> models;

  111.     /** Reference radius of the central body attraction model (m). */
  112.     private double referenceRadius;

  113.     /** Central attraction coefficient (m³/s²). */
  114.     private double mu;

  115.     /** Un-normalized zonal coefficients. */
  116.     private double[] ck0;

  117.     /** Empirical coefficient used in the drag modeling. */
  118.     private final ParameterDriver M2Driver;

  119.     /** Build a propagator from orbit and potential provider.
  120.      * <p>Mass and attitude provider are set to unspecified non-null arbitrary values.</p>
  121.      *
  122.      * <p>Using this constructor, an initial osculating orbit is considered.</p>
  123.      *
  124.      * @param initialOrbit initial orbit
  125.      * @param provider for un-normalized zonal coefficients
  126.      * @param M2 value of empirical drag coefficient in rad/s².
  127.      *        If equal to {@link #M2} drag is not computed
  128.      * @see #BrouwerLyddanePropagator(Orbit, AttitudeProvider, UnnormalizedSphericalHarmonicsProvider, double)
  129.      * @see #BrouwerLyddanePropagator(Orbit, UnnormalizedSphericalHarmonicsProvider, PropagationType, double)
  130.      */
  131.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  132.                                     final UnnormalizedSphericalHarmonicsProvider provider,
  133.                                     final double M2) {
  134.         this(initialOrbit, FrameAlignedProvider.of(initialOrbit.getFrame()),
  135.              DEFAULT_MASS, provider, provider.onDate(initialOrbit.getDate()), M2);
  136.     }

  137.     /**
  138.      * Private helper constructor.
  139.      * <p>Using this constructor, an initial osculating orbit is considered.</p>
  140.      * @param initialOrbit initial orbit
  141.      * @param attitude attitude provider
  142.      * @param mass spacecraft mass
  143.      * @param provider for un-normalized zonal coefficients
  144.      * @param harmonics {@code provider.onDate(initialOrbit.getDate())}
  145.      * @param M2 value of empirical drag coefficient in rad/s².
  146.      *        If equal to {@link #M2} drag is not computed
  147.      * @see #BrouwerLyddanePropagator(Orbit, AttitudeProvider, double,
  148.      *                                 UnnormalizedSphericalHarmonicsProvider,
  149.      *                                 UnnormalizedSphericalHarmonicsProvider.UnnormalizedSphericalHarmonics,
  150.      *                                 PropagationType, double)
  151.      */
  152.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  153.                                     final AttitudeProvider attitude,
  154.                                     final double mass,
  155.                                     final UnnormalizedSphericalHarmonicsProvider provider,
  156.                                     final UnnormalizedSphericalHarmonics harmonics,
  157.                                     final double M2) {
  158.         this(initialOrbit, attitude, mass, provider.getAe(), provider.getMu(),
  159.              harmonics.getUnnormalizedCnm(2, 0),
  160.              harmonics.getUnnormalizedCnm(3, 0),
  161.              harmonics.getUnnormalizedCnm(4, 0),
  162.              harmonics.getUnnormalizedCnm(5, 0),
  163.              M2);
  164.     }

  165.     /** Build a propagator from orbit and potential.
  166.      * <p>Mass and attitude provider are set to unspecified non-null arbitrary values.</p>
  167.      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
  168.      * are related to both the normalized coefficients
  169.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  170.      *  and the J<sub>n</sub> one as follows:</p>
  171.      *
  172.      * <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
  173.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  174.      *
  175.      * <p> C<sub>n,0</sub> = -J<sub>n</sub>
  176.      *
  177.      * <p>Using this constructor, an initial osculating orbit is considered.</p>
  178.      *
  179.      * @param initialOrbit initial orbit
  180.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  181.      * @param mu central attraction coefficient (m³/s²)
  182.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  183.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  184.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  185.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  186.      * @param M2 value of empirical drag coefficient in rad/s².
  187.      *        If equal to {@link #M2} drag is not computed
  188.      * @see org.orekit.utils.Constants
  189.      * @see #BrouwerLyddanePropagator(Orbit, AttitudeProvider, double, double, double,
  190.      * double, double, double, double, double)
  191.      */
  192.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  193.                                     final double referenceRadius, final double mu,
  194.                                     final double c20, final double c30, final double c40,
  195.                                     final double c50, final double M2) {
  196.         this(initialOrbit, FrameAlignedProvider.of(initialOrbit.getFrame()),
  197.              DEFAULT_MASS, referenceRadius, mu, c20, c30, c40, c50, M2);
  198.     }

  199.     /** Build a propagator from orbit, mass and potential provider.
  200.      * <p>Attitude law is set to an unspecified non-null arbitrary value.</p>
  201.      *
  202.      * <p>Using this constructor, an initial osculating orbit is considered.</p>
  203.      *
  204.      * @param initialOrbit initial orbit
  205.      * @param mass spacecraft mass
  206.      * @param provider for un-normalized zonal coefficients
  207.      * @param M2 value of empirical drag coefficient in rad/s².
  208.      *        If equal to {@link #M2} drag is not computed
  209.      * @see #BrouwerLyddanePropagator(Orbit, AttitudeProvider, double, UnnormalizedSphericalHarmonicsProvider, double)
  210.      */
  211.     public BrouwerLyddanePropagator(final Orbit initialOrbit, final double mass,
  212.                                     final UnnormalizedSphericalHarmonicsProvider provider,
  213.                                     final double M2) {
  214.         this(initialOrbit, FrameAlignedProvider.of(initialOrbit.getFrame()),
  215.              mass, provider, provider.onDate(initialOrbit.getDate()), M2);
  216.     }

  217.     /** Build a propagator from orbit, mass and potential.
  218.      * <p>Attitude law is set to an unspecified non-null arbitrary value.</p>
  219.      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
  220.      * are related to both the normalized coefficients
  221.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  222.      *  and the J<sub>n</sub> one as follows:</p>
  223.      *
  224.      * <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
  225.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  226.      *
  227.      * <p> C<sub>n,0</sub> = -J<sub>n</sub>
  228.      *
  229.      * <p>Using this constructor, an initial osculating orbit is considered.</p>
  230.      *
  231.      * @param initialOrbit initial orbit
  232.      * @param mass spacecraft mass
  233.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  234.      * @param mu central attraction coefficient (m³/s²)
  235.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  236.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  237.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  238.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  239.      * @param M2 value of empirical drag coefficient in rad/s².
  240.      *        If equal to {@link #M2} drag is not computed
  241.      * @see #BrouwerLyddanePropagator(Orbit, AttitudeProvider, double, double, double,
  242.      * double, double, double, double, double)
  243.      */
  244.     public BrouwerLyddanePropagator(final Orbit initialOrbit, final double mass,
  245.                                     final double referenceRadius, final double mu,
  246.                                     final double c20, final double c30, final double c40,
  247.                                     final double c50, final double M2) {
  248.         this(initialOrbit, FrameAlignedProvider.of(initialOrbit.getFrame()),
  249.              mass, referenceRadius, mu, c20, c30, c40, c50, M2);
  250.     }

  251.     /** Build a propagator from orbit, attitude provider and potential provider.
  252.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  253.      * <p>Using this constructor, an initial osculating orbit is considered.</p>
  254.      * @param initialOrbit initial orbit
  255.      * @param attitudeProv attitude provider
  256.      * @param provider for un-normalized zonal coefficients
  257.      * @param M2 value of empirical drag coefficient in rad/s².
  258.      *        If equal to {@link #M2} drag is not computed
  259.      */
  260.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  261.                                     final AttitudeProvider attitudeProv,
  262.                                     final UnnormalizedSphericalHarmonicsProvider provider,
  263.                                     final double M2) {
  264.         this(initialOrbit, attitudeProv, DEFAULT_MASS, provider, provider.onDate(initialOrbit.getDate()), M2);
  265.     }

  266.     /** Build a propagator from orbit, attitude provider and potential.
  267.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  268.      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
  269.      * are related to both the normalized coefficients
  270.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  271.      *  and the J<sub>n</sub> one as follows:</p>
  272.      *
  273.      * <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
  274.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  275.      *
  276.      * <p> C<sub>n,0</sub> = -J<sub>n</sub>
  277.      *
  278.      * <p>Using this constructor, an initial osculating orbit is considered.</p>
  279.      *
  280.      * @param initialOrbit initial orbit
  281.      * @param attitudeProv attitude provider
  282.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  283.      * @param mu central attraction coefficient (m³/s²)
  284.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  285.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  286.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  287.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  288.      * @param M2 value of empirical drag coefficient in rad/s².
  289.      *        If equal to {@link #M2} drag is not computed
  290.      */
  291.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  292.                                     final AttitudeProvider attitudeProv,
  293.                                     final double referenceRadius, final double mu,
  294.                                     final double c20, final double c30, final double c40,
  295.                                     final double c50, final double M2) {
  296.         this(initialOrbit, attitudeProv, DEFAULT_MASS, referenceRadius, mu, c20, c30, c40, c50, M2);
  297.     }

  298.     /** Build a propagator from orbit, attitude provider, mass and potential provider.
  299.      * <p>Using this constructor, an initial osculating orbit is considered.</p>
  300.      * @param initialOrbit initial orbit
  301.      * @param attitudeProv attitude provider
  302.      * @param mass spacecraft mass
  303.      * @param provider for un-normalized zonal coefficients
  304.      * @param M2 value of empirical drag coefficient in rad/s².
  305.      *        If equal to {@link #M2} drag is not computed
  306.      * @see #BrouwerLyddanePropagator(Orbit, AttitudeProvider, double,
  307.      *                                UnnormalizedSphericalHarmonicsProvider, PropagationType, double)
  308.      */
  309.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  310.                                     final AttitudeProvider attitudeProv,
  311.                                     final double mass,
  312.                                     final UnnormalizedSphericalHarmonicsProvider provider,
  313.                                     final double M2) {
  314.         this(initialOrbit, attitudeProv, mass, provider, provider.onDate(initialOrbit.getDate()), M2);
  315.     }

  316.     /** Build a propagator from orbit, attitude provider, mass and potential.
  317.      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
  318.      * are related to both the normalized coefficients
  319.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  320.      *  and the J<sub>n</sub> one as follows:</p>
  321.      *
  322.      * <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
  323.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  324.      *
  325.      * <p> C<sub>n,0</sub> = -J<sub>n</sub>
  326.      *
  327.      * <p>Using this constructor, an initial osculating orbit is considered.</p>
  328.      *
  329.      * @param initialOrbit initial orbit
  330.      * @param attitudeProv attitude provider
  331.      * @param mass spacecraft mass
  332.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  333.      * @param mu central attraction coefficient (m³/s²)
  334.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  335.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  336.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  337.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  338.      * @param M2 value of empirical drag coefficient in rad/s².
  339.      *        If equal to {@link #M2} drag is not computed
  340.      * @see #BrouwerLyddanePropagator(Orbit, AttitudeProvider, double, double, double,
  341.      *                                 double, double, double, double, PropagationType, double)
  342.      */
  343.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  344.                                     final AttitudeProvider attitudeProv,
  345.                                     final double mass,
  346.                                     final double referenceRadius, final double mu,
  347.                                     final double c20, final double c30, final double c40,
  348.                                     final double c50, final double M2) {
  349.         this(initialOrbit, attitudeProv, mass, referenceRadius, mu, c20, c30, c40, c50,
  350.              PropagationType.OSCULATING, M2);
  351.     }


  352.     /** Build a propagator from orbit and potential provider.
  353.      * <p>Mass and attitude provider are set to unspecified non-null arbitrary values.</p>
  354.      *
  355.      * <p>Using this constructor, it is possible to define the initial orbit as
  356.      * a mean Brouwer-Lyddane orbit or an osculating one.</p>
  357.      *
  358.      * @param initialOrbit initial orbit
  359.      * @param provider for un-normalized zonal coefficients
  360.      * @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
  361.      * @param M2 value of empirical drag coefficient in rad/s².
  362.      *        If equal to {@link #M2} drag is not computed
  363.      */
  364.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  365.                                     final UnnormalizedSphericalHarmonicsProvider provider,
  366.                                     final PropagationType initialType, final double M2) {
  367.         this(initialOrbit, FrameAlignedProvider.of(initialOrbit.getFrame()),
  368.              DEFAULT_MASS, provider, provider.onDate(initialOrbit.getDate()), initialType, M2);
  369.     }

  370.     /** Build a propagator from orbit, attitude provider, mass and potential provider.
  371.      * <p>Using this constructor, it is possible to define the initial orbit as
  372.      * a mean Brouwer-Lyddane orbit or an osculating one.</p>
  373.      * @param initialOrbit initial orbit
  374.      * @param attitudeProv attitude provider
  375.      * @param mass spacecraft mass
  376.      * @param provider for un-normalized zonal coefficients
  377.      * @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
  378.      * @param M2 value of empirical drag coefficient in rad/s².
  379.      *        If equal to {@link #M2} drag is not computed
  380.      */
  381.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  382.                                     final AttitudeProvider attitudeProv,
  383.                                     final double mass,
  384.                                     final UnnormalizedSphericalHarmonicsProvider provider,
  385.                                     final PropagationType initialType, final double M2) {
  386.         this(initialOrbit, attitudeProv, mass, provider, provider.onDate(initialOrbit.getDate()), initialType, M2);
  387.     }

  388.     /**
  389.      * Private helper constructor.
  390.      * <p>Using this constructor, it is possible to define the initial orbit as
  391.      * a mean Brouwer-Lyddane orbit or an osculating one.</p>
  392.      * @param initialOrbit initial orbit
  393.      * @param attitude attitude provider
  394.      * @param mass spacecraft mass
  395.      * @param provider for un-normalized zonal coefficients
  396.      * @param harmonics {@code provider.onDate(initialOrbit.getDate())}
  397.      * @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
  398.      * @param M2 value of empirical drag coefficient in rad/s².
  399.      *        If equal to {@link #M2} drag is not computed
  400.      */
  401.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  402.                                     final AttitudeProvider attitude,
  403.                                     final double mass,
  404.                                     final UnnormalizedSphericalHarmonicsProvider provider,
  405.                                     final UnnormalizedSphericalHarmonics harmonics,
  406.                                     final PropagationType initialType, final double M2) {
  407.         this(initialOrbit, attitude, mass, provider.getAe(), provider.getMu(),
  408.              harmonics.getUnnormalizedCnm(2, 0),
  409.              harmonics.getUnnormalizedCnm(3, 0),
  410.              harmonics.getUnnormalizedCnm(4, 0),
  411.              harmonics.getUnnormalizedCnm(5, 0),
  412.              initialType, M2);
  413.     }

  414.     /** Build a propagator from orbit, attitude provider, mass and potential.
  415.      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
  416.      * are related to both the normalized coefficients
  417.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  418.      *  and the J<sub>n</sub> one as follows:</p>
  419.      *
  420.      * <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
  421.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  422.      *
  423.      * <p> C<sub>n,0</sub> = -J<sub>n</sub>
  424.      *
  425.      * <p>Using this constructor, it is possible to define the initial orbit as
  426.      * a mean Brouwer-Lyddane orbit or an osculating one.</p>
  427.      *
  428.      * @param initialOrbit initial orbit
  429.      * @param attitudeProv attitude provider
  430.      * @param mass spacecraft mass
  431.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  432.      * @param mu central attraction coefficient (m³/s²)
  433.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  434.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  435.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  436.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  437.      * @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
  438.      * @param M2 value of empirical drag coefficient in rad/s².
  439.      *        If equal to {@link #M2} drag is not computed
  440.      */
  441.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  442.                                     final AttitudeProvider attitudeProv,
  443.                                     final double mass,
  444.                                     final double referenceRadius, final double mu,
  445.                                     final double c20, final double c30, final double c40,
  446.                                     final double c50,
  447.                                     final PropagationType initialType, final double M2) {
  448.         this(initialOrbit, attitudeProv, mass, referenceRadius, mu,
  449.              c20, c30, c40, c50, initialType, M2, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
  450.     }

  451.     /** Build a propagator from orbit, attitude provider, mass and potential.
  452.      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
  453.      * are related to both the normalized coefficients
  454.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  455.      *  and the J<sub>n</sub> one as follows:</p>
  456.      *
  457.      * <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
  458.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  459.      *
  460.      * <p> C<sub>n,0</sub> = -J<sub>n</sub>
  461.      *
  462.      * <p>Using this constructor, it is possible to define the initial orbit as
  463.      * a mean Brouwer-Lyddane orbit or an osculating one.</p>
  464.      *
  465.      * @param initialOrbit initial orbit
  466.      * @param attitudeProv attitude provider
  467.      * @param mass spacecraft mass
  468.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  469.      * @param mu central attraction coefficient (m³/s²)
  470.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  471.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  472.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  473.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  474.      * @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
  475.      * @param M2 value of empirical drag coefficient in rad/s².
  476.      *        If equal to {@link #M2} drag is not computed
  477.      * @param epsilon convergence threshold for mean parameters conversion
  478.      * @param maxIterations maximum iterations for mean parameters conversion
  479.      * @since 11.2
  480.      */
  481.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  482.                                     final AttitudeProvider attitudeProv,
  483.                                     final double mass,
  484.                                     final double referenceRadius, final double mu,
  485.                                     final double c20, final double c30, final double c40,
  486.                                     final double c50,
  487.                                     final PropagationType initialType, final double M2,
  488.                                     final double epsilon, final int maxIterations) {

  489.         super(attitudeProv);

  490.         // store model coefficients
  491.         this.referenceRadius = referenceRadius;
  492.         this.mu  = mu;
  493.         this.ck0 = new double[] {0.0, 0.0, c20, c30, c40, c50};

  494.         // initialize M2 driver
  495.         this.M2Driver = new ParameterDriver(M2_NAME, M2, SCALE,
  496.                                             Double.NEGATIVE_INFINITY,
  497.                                             Double.POSITIVE_INFINITY);

  498.         // compute mean parameters if needed
  499.         resetInitialState(new SpacecraftState(initialOrbit,
  500.                                               attitudeProv.getAttitude(initialOrbit,
  501.                                                                        initialOrbit.getDate(),
  502.                                                                        initialOrbit.getFrame()),
  503.                                               mass),
  504.                           initialType, epsilon, maxIterations);

  505.     }

  506.     /** Conversion from osculating to mean orbit.
  507.      * <p>
  508.      * Compute mean orbit <b>in a Brouwer-Lyddane sense</b>, corresponding to the
  509.      * osculating SpacecraftState in input.
  510.      * </p>
  511.      * <p>
  512.      * Since the osculating orbit is obtained with the computation of
  513.      * short-periodic variation, the resulting output will depend on
  514.      * both the gravity field parameterized in input and the
  515.      * atmospheric drag represented by the {@code m2} parameter.
  516.      * </p>
  517.      * <p>
  518.      * The computation is done through a fixed-point iteration process.
  519.      * </p>
  520.      * @param osculating osculating orbit to convert
  521.      * @param provider for un-normalized zonal coefficients
  522.      * @param harmonics {@code provider.onDate(osculating.getDate())}
  523.      * @param M2Value value of empirical drag coefficient in rad/s².
  524.      *        If equal to {@code BrouwerLyddanePropagator.M2} drag is not considered
  525.      * @return mean orbit in a Brouwer-Lyddane sense
  526.      * @since 11.2
  527.      */
  528.     public static KeplerianOrbit computeMeanOrbit(final Orbit osculating,
  529.                                                   final UnnormalizedSphericalHarmonicsProvider provider,
  530.                                                   final UnnormalizedSphericalHarmonics harmonics,
  531.                                                   final double M2Value) {
  532.         return computeMeanOrbit(osculating, provider, harmonics, M2Value, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
  533.     }

  534.     /** Conversion from osculating to mean orbit.
  535.      * <p>
  536.      * Compute mean orbit <b>in a Brouwer-Lyddane sense</b>, corresponding to the
  537.      * osculating SpacecraftState in input.
  538.      * </p>
  539.      * <p>
  540.      * Since the osculating orbit is obtained with the computation of
  541.      * short-periodic variation, the resulting output will depend on
  542.      * both the gravity field parameterized in input and the
  543.      * atmospheric drag represented by the {@code m2} parameter.
  544.      * </p>
  545.      * <p>
  546.      * The computation is done through a fixed-point iteration process.
  547.      * </p>
  548.      * @param osculating osculating orbit to convert
  549.      * @param provider for un-normalized zonal coefficients
  550.      * @param harmonics {@code provider.onDate(osculating.getDate())}
  551.      * @param M2Value value of empirical drag coefficient in rad/s².
  552.      *        If equal to {@code BrouwerLyddanePropagator.M2} drag is not considered
  553.      * @param epsilon convergence threshold for mean parameters conversion
  554.      * @param maxIterations maximum iterations for mean parameters conversion
  555.      * @return mean orbit in a Brouwer-Lyddane sense
  556.      * @since 11.2
  557.      */
  558.     public static KeplerianOrbit computeMeanOrbit(final Orbit osculating,
  559.                                                   final UnnormalizedSphericalHarmonicsProvider provider,
  560.                                                   final UnnormalizedSphericalHarmonics harmonics,
  561.                                                   final double M2Value,
  562.                                                   final double epsilon, final int maxIterations) {
  563.         return computeMeanOrbit(osculating,
  564.                                 provider.getAe(), provider.getMu(),
  565.                                 harmonics.getUnnormalizedCnm(2, 0),
  566.                                 harmonics.getUnnormalizedCnm(3, 0),
  567.                                 harmonics.getUnnormalizedCnm(4, 0),
  568.                                 harmonics.getUnnormalizedCnm(5, 0),
  569.                                 M2Value, epsilon, maxIterations);
  570.     }

  571.     /** Conversion from osculating to mean orbit.
  572.      * <p>
  573.      * Compute mean orbit <b>in a Brouwer-Lyddane sense</b>, corresponding to the
  574.      * osculating SpacecraftState in input.
  575.      * </p>
  576.      * <p>
  577.      * Since the osculating orbit is obtained with the computation of
  578.      * short-periodic variation, the resulting output will depend on
  579.      * both the gravity field parameterized in input and the
  580.      * atmospheric drag represented by the {@code m2} parameter.
  581.      * </p>
  582.      * <p>
  583.      * The computation is done through a fixed-point iteration process.
  584.      * </p>
  585.      * @param osculating osculating orbit to convert
  586.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  587.      * @param mu central attraction coefficient (m³/s²)
  588.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  589.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  590.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  591.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  592.      * @param M2Value value of empirical drag coefficient in rad/s².
  593.      *        If equal to {@code BrouwerLyddanePropagator.M2} drag is not considered
  594.      * @param epsilon convergence threshold for mean parameters conversion
  595.      * @param maxIterations maximum iterations for mean parameters conversion
  596.      * @return mean orbit in a Brouwer-Lyddane sense
  597.      * @since 11.2
  598.      */
  599.     public static KeplerianOrbit computeMeanOrbit(final Orbit osculating,
  600.                                                   final double referenceRadius, final double mu,
  601.                                                   final double c20, final double c30, final double c40,
  602.                                                   final double c50, final double M2Value,
  603.                                                   final double epsilon, final int maxIterations) {
  604.         final BrouwerLyddanePropagator propagator =
  605.                         new BrouwerLyddanePropagator(osculating,
  606.                                                      FrameAlignedProvider.of(osculating.getFrame()),
  607.                                                      DEFAULT_MASS,
  608.                                                      referenceRadius, mu, c20, c30, c40, c50,
  609.                                                      PropagationType.OSCULATING, M2Value,
  610.                                                      epsilon, maxIterations);
  611.         return propagator.initialModel.mean;
  612.     }

  613.     /** {@inheritDoc}
  614.      * <p>The new initial state to consider
  615.      * must be defined with an osculating orbit.</p>
  616.      * @see #resetInitialState(SpacecraftState, PropagationType)
  617.      */
  618.     public void resetInitialState(final SpacecraftState state) {
  619.         resetInitialState(state, PropagationType.OSCULATING);
  620.     }

  621.     /** Reset the propagator initial state.
  622.      * @param state new initial state to consider
  623.      * @param stateType mean Brouwer-Lyddane orbit or osculating orbit
  624.      */
  625.     public void resetInitialState(final SpacecraftState state, final PropagationType stateType) {
  626.         resetInitialState(state, stateType, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
  627.     }

  628.     /** Reset the propagator initial state.
  629.      * @param state new initial state to consider
  630.      * @param stateType mean Brouwer-Lyddane orbit or osculating orbit
  631.      * @param epsilon convergence threshold for mean parameters conversion
  632.      * @param maxIterations maximum iterations for mean parameters conversion
  633.      * @since 11.2
  634.      */
  635.     public void resetInitialState(final SpacecraftState state, final PropagationType stateType,
  636.                                   final double epsilon, final int maxIterations) {
  637.         super.resetInitialState(state);
  638.         final KeplerianOrbit keplerian = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(state.getOrbit());
  639.         this.initialModel = (stateType == PropagationType.MEAN) ?
  640.                              new BLModel(keplerian, state.getMass(), referenceRadius, mu, ck0) :
  641.                              computeMeanParameters(keplerian, state.getMass(), epsilon, maxIterations);
  642.         this.models = new TimeSpanMap<BLModel>(initialModel);
  643.     }

  644.     /** {@inheritDoc} */
  645.     protected void resetIntermediateState(final SpacecraftState state, final boolean forward) {
  646.         resetIntermediateState(state, forward, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
  647.     }

  648.     /** Reset an intermediate state.
  649.      * @param state new intermediate state to consider
  650.      * @param forward if true, the intermediate state is valid for
  651.      * propagations after itself
  652.      * @param epsilon convergence threshold for mean parameters conversion
  653.      * @param maxIterations maximum iterations for mean parameters conversion
  654.      * @since 11.2
  655.      */
  656.     protected void resetIntermediateState(final SpacecraftState state, final boolean forward,
  657.                                           final double epsilon, final int maxIterations) {
  658.         final BLModel newModel = computeMeanParameters((KeplerianOrbit) OrbitType.KEPLERIAN.convertType(state.getOrbit()),
  659.                                                        state.getMass(), epsilon, maxIterations);
  660.         if (forward) {
  661.             models.addValidAfter(newModel, state.getDate(), false);
  662.         } else {
  663.             models.addValidBefore(newModel, state.getDate(), false);
  664.         }
  665.         stateChanged(state);
  666.     }

  667.     /** Compute mean parameters according to the Brouwer-Lyddane analytical model computation
  668.      * in order to do the propagation.
  669.      * @param osculating osculating orbit
  670.      * @param mass constant mass
  671.      * @param epsilon convergence threshold for mean parameters conversion
  672.      * @param maxIterations maximum iterations for mean parameters conversion
  673.      * @return Brouwer-Lyddane mean model
  674.      */
  675.     private BLModel computeMeanParameters(final KeplerianOrbit osculating, final double mass,
  676.                                           final double epsilon, final int maxIterations) {

  677.         // sanity check
  678.         if (osculating.getA() < referenceRadius) {
  679.             throw new OrekitException(OrekitMessages.TRAJECTORY_INSIDE_BRILLOUIN_SPHERE,
  680.                                            osculating.getA());
  681.         }

  682.         // rough initialization of the mean parameters
  683.         BLModel current = new BLModel(osculating, mass, referenceRadius, mu, ck0);

  684.         // threshold for each parameter
  685.         final double thresholdA      = epsilon * (1 + FastMath.abs(current.mean.getA()));
  686.         final double thresholdE      = epsilon * (1 + current.mean.getE());
  687.         final double thresholdAngles = epsilon * FastMath.PI;

  688.         int i = 0;
  689.         while (i++ < maxIterations) {

  690.             // recompute the osculating parameters from the current mean parameters
  691.             final KeplerianOrbit parameters = current.propagateParameters(current.mean.getDate());

  692.             // adapted parameters residuals
  693.             final double deltaA     = osculating.getA() - parameters.getA();
  694.             final double deltaE     = osculating.getE() - parameters.getE();
  695.             final double deltaI     = osculating.getI() - parameters.getI();
  696.             final double deltaOmega = MathUtils.normalizeAngle(osculating.getPerigeeArgument() -
  697.                                                                parameters.getPerigeeArgument(),
  698.                                                                0.0);
  699.             final double deltaRAAN  = MathUtils.normalizeAngle(osculating.getRightAscensionOfAscendingNode() -
  700.                                                                parameters.getRightAscensionOfAscendingNode(),
  701.                                                                0.0);
  702.             final double deltaAnom = MathUtils.normalizeAngle(osculating.getMeanAnomaly() -
  703.                                                               parameters.getMeanAnomaly(),
  704.                                                               0.0);


  705.             // update mean parameters
  706.             current = new BLModel(new KeplerianOrbit(current.mean.getA() + deltaA,
  707.                                                      FastMath.max(current.mean.getE() + deltaE, 0.0),
  708.                                                      current.mean.getI() + deltaI,
  709.                                                      current.mean.getPerigeeArgument() + deltaOmega,
  710.                                                      current.mean.getRightAscensionOfAscendingNode() + deltaRAAN,
  711.                                                      current.mean.getMeanAnomaly() + deltaAnom,
  712.                                                      PositionAngleType.MEAN,
  713.                                                      current.mean.getFrame(),
  714.                                                      current.mean.getDate(), mu),
  715.                                   mass, referenceRadius, mu, ck0);
  716.             // check convergence
  717.             if (FastMath.abs(deltaA)     < thresholdA &&
  718.                 FastMath.abs(deltaE)     < thresholdE &&
  719.                 FastMath.abs(deltaI)     < thresholdAngles &&
  720.                 FastMath.abs(deltaOmega) < thresholdAngles &&
  721.                 FastMath.abs(deltaRAAN)  < thresholdAngles &&
  722.                 FastMath.abs(deltaAnom)  < thresholdAngles) {
  723.                 return current;
  724.             }
  725.         }
  726.         throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_BROUWER_LYDDANE_MEAN_PARAMETERS, i);
  727.     }


  728.     /** {@inheritDoc} */
  729.     public KeplerianOrbit propagateOrbit(final AbsoluteDate date) {
  730.         // compute keplerian parameters, taking derivatives into account
  731.         final BLModel current = models.get(date);
  732.         return current.propagateParameters(date);
  733.     }

  734.     /**
  735.      * Get the value of the M2 drag parameter. Beware that M2Driver
  736.      * must have only 1 span on its TimeSpanMap value (that is
  737.      * to say setPeriod method should not be called)
  738.      * @return the value of the M2 drag parameter
  739.      */
  740.     public double getM2() {
  741.         // As Brouwer Lyddane is an analytical propagator, for now it is not possible for
  742.         // M2Driver to have several values estimated
  743.         return M2Driver.getValue();
  744.     }

  745.     /**
  746.      * Get the central attraction coefficient μ.
  747.      * @return mu central attraction coefficient (m³/s²)
  748.      */
  749.     public double getMu() {
  750.         return mu;
  751.     }

  752.     /**
  753.      * Get the un-normalized zonal coefficients.
  754.      * @return the un-normalized zonal coefficients
  755.      */
  756.     public double[] getCk0() {
  757.         return ck0.clone();
  758.     }

  759.     /**
  760.      * Get the reference radius of the central body attraction model.
  761.      * @return the reference radius in meters
  762.      */
  763.     public double getReferenceRadius() {
  764.         return referenceRadius;
  765.     }

  766.     /**
  767.      * Get the parameters driver for propagation model.
  768.      * @return drivers for propagation model
  769.      */
  770.     public List<ParameterDriver> getParametersDrivers() {
  771.         return Collections.singletonList(M2Driver);
  772.     }

  773.     /** {@inheritDoc} */
  774.     @Override
  775.     protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
  776.                                                         final DoubleArrayDictionary initialJacobianColumns) {
  777.         // Create the harvester
  778.         final BrouwerLyddaneHarvester harvester = new BrouwerLyddaneHarvester(this, stmName, initialStm, initialJacobianColumns);
  779.         // Update the list of additional state provider
  780.         addAdditionalStateProvider(harvester);
  781.         // Return the configured harvester
  782.         return harvester;
  783.     }

  784.     /**
  785.      * Get the names of the parameters in the matrix returned by {@link MatricesHarvester#getParametersJacobian}.
  786.      * @return names of the parameters (i.e. columns) of the Jacobian matrix
  787.      */
  788.     protected List<String> getJacobiansColumnsNames() {
  789.         final List<String> columnsNames = new ArrayList<>();
  790.         for (final ParameterDriver driver : getParametersDrivers()) {
  791.             if (driver.isSelected() && !columnsNames.contains(driver.getNamesSpanMap().getFirstSpan().getData())) {
  792.                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  793.                     columnsNames.add(span.getData());
  794.                 }
  795.             }
  796.         }
  797.         Collections.sort(columnsNames);
  798.         return columnsNames;
  799.     }

  800.     /** Local class for Brouwer-Lyddane model. */
  801.     private class BLModel {

  802.         /** Mean orbit. */
  803.         private final KeplerianOrbit mean;

  804.         /** Constant mass. */
  805.         private final double mass;

  806.         // CHECKSTYLE: stop JavadocVariable check

  807.         // preprocessed values

  808.         // Constant for secular terms l'', g'', h''
  809.         // l standing for true anomaly, g for perigee argument and h for raan
  810.         private final double xnotDot;
  811.         private final double n;
  812.         private final double lt;
  813.         private final double gt;
  814.         private final double ht;


  815.         // Long period terms
  816.         private final double dei3sg;
  817.         private final double de2sg;
  818.         private final double deisg;
  819.         private final double de;


  820.         private final double dlgs2g;
  821.         private final double dlgc3g;
  822.         private final double dlgcg;


  823.         private final double dh2sgcg;
  824.         private final double dhsgcg;
  825.         private final double dhcg;


  826.         // Short period terms
  827.         private final double aC;
  828.         private final double aCbis;
  829.         private final double ac2g2f;


  830.         private final double eC;
  831.         private final double ecf;
  832.         private final double e2cf;
  833.         private final double e3cf;
  834.         private final double ec2f2g;
  835.         private final double ecfc2f2g;
  836.         private final double e2cfc2f2g;
  837.         private final double e3cfc2f2g;
  838.         private final double ec2gf;
  839.         private final double ec2g3f;


  840.         private final double ide;
  841.         private final double isfs2f2g;
  842.         private final double icfc2f2g;
  843.         private final double ic2f2g;


  844.         private final double glf;
  845.         private final double gll;
  846.         private final double glsf;
  847.         private final double glosf;
  848.         private final double gls2f2g;
  849.         private final double gls2gf;
  850.         private final double glos2gf;
  851.         private final double gls2g3f;
  852.         private final double glos2g3f;


  853.         private final double hf;
  854.         private final double hl;
  855.         private final double hsf;
  856.         private final double hcfs2g2f;
  857.         private final double hs2g2f;
  858.         private final double hsfc2g2f;


  859.         private final double edls2g;
  860.         private final double edlcg;
  861.         private final double edlc3g;
  862.         private final double edlsf;
  863.         private final double edls2gf;
  864.         private final double edls2g3f;

  865.         // Drag terms
  866.         private final double aRate;
  867.         private final double eRate;

  868.         // CHECKSTYLE: resume JavadocVariable check

  869.         /** Create a model for specified mean orbit.
  870.          * @param mean mean orbit
  871.          * @param mass constant mass
  872.          * @param referenceRadius reference radius of the central body attraction model (m)
  873.          * @param mu central attraction coefficient (m³/s²)
  874.          * @param ck0 un-normalized zonal coefficients
  875.          */
  876.         BLModel(final KeplerianOrbit mean, final double mass,
  877.                 final double referenceRadius, final double mu, final double[] ck0) {

  878.             this.mean = mean;
  879.             this.mass = mass;

  880.             final double app = mean.getA();
  881.             xnotDot = FastMath.sqrt(mu / app) / app;

  882.             // preliminary processing
  883.             final double q = referenceRadius / app;
  884.             double ql = q * q;
  885.             final double y2 = -0.5 * ck0[2] * ql;

  886.             n = FastMath.sqrt(1 - mean.getE() * mean.getE());
  887.             final double n2 = n * n;
  888.             final double n3 = n2 * n;
  889.             final double n4 = n2 * n2;
  890.             final double n6 = n4 * n2;
  891.             final double n8 = n4 * n4;
  892.             final double n10 = n8 * n2;

  893.             final double yp2 = y2 / n4;
  894.             ql *= q;
  895.             final double yp3 = ck0[3] * ql / n6;
  896.             ql *= q;
  897.             final double yp4 = 0.375 * ck0[4] * ql / n8;
  898.             ql *= q;
  899.             final double yp5 = ck0[5] * ql / n10;


  900.             final SinCos sc    = FastMath.sinCos(mean.getI());
  901.             final double sinI1 = sc.sin();
  902.             final double sinI2 = sinI1 * sinI1;
  903.             final double cosI1 = sc.cos();
  904.             final double cosI2 = cosI1 * cosI1;
  905.             final double cosI3 = cosI2 * cosI1;
  906.             final double cosI4 = cosI2 * cosI2;
  907.             final double cosI6 = cosI4 * cosI2;
  908.             final double C5c2 = 1.0 / T2(cosI1);
  909.             final double C3c2 = 3.0 * cosI2 - 1.0;

  910.             final double epp = mean.getE();
  911.             final double epp2 = epp * epp;
  912.             final double epp3 = epp2 * epp;
  913.             final double epp4 = epp2 * epp2;

  914.             if (epp >= 1) {
  915.                 // Only for elliptical (e < 1) orbits
  916.                 throw new OrekitException(OrekitMessages.TOO_LARGE_ECCENTRICITY_FOR_PROPAGATION_MODEL,
  917.                                           mean.getE());
  918.             }

  919.             // secular multiplicative
  920.             lt = 1 +
  921.                     1.5 * yp2 * n * C3c2 +
  922.                     0.09375 * yp2 * yp2 * n * (-15.0 + 16.0 * n + 25.0 * n2 + (30.0 - 96.0 * n - 90.0 * n2) * cosI2 + (105.0 + 144.0 * n + 25.0 * n2) * cosI4) +
  923.                     0.9375 * yp4 * n * epp2 * (3.0 - 30.0 * cosI2 + 35.0 * cosI4);

  924.             gt = -1.5 * yp2 * C5c2 +
  925.                     0.09375 * yp2 * yp2 * (-35.0 + 24.0 * n + 25.0 * n2 + (90.0 - 192.0 * n - 126.0 * n2) * cosI2 + (385.0 + 360.0 * n + 45.0 * n2) * cosI4) +
  926.                     0.3125 * yp4 * (21.0 - 9.0 * n2 + (-270.0 + 126.0 * n2) * cosI2 + (385.0 - 189.0 * n2) * cosI4 );

  927.             ht = -3.0 * yp2 * cosI1 +
  928.                     0.375 * yp2 * yp2 * ((-5.0 + 12.0 * n + 9.0 * n2) * cosI1 + (-35.0 - 36.0 * n - 5.0 * n2) * cosI3) +
  929.                     1.25 * yp4 * (5.0 - 3.0 * n2) * cosI1 * (3.0 - 7.0 * cosI2);

  930.             final double cA = 1.0 - 11.0 * cosI2 - 40.0 * cosI4 / C5c2;
  931.             final double cB = 1.0 - 3.0 * cosI2 - 8.0 * cosI4 / C5c2;
  932.             final double cC = 1.0 - 9.0 * cosI2 - 24.0 * cosI4 / C5c2;
  933.             final double cD = 1.0 - 5.0 * cosI2 - 16.0 * cosI4 / C5c2;

  934.             final double qyp2_4 = 3.0 * yp2 * yp2 * cA -
  935.                                   10.0 * yp4 * cB;
  936.             final double qyp52 = epp3 * cosI1 * (0.5 * cD / sinI1 +
  937.                                  sinI1 * (5.0 + 32.0 * cosI2 / C5c2 + 80.0 * cosI4 / C5c2 / C5c2));
  938.             final double qyp22 = 2.0 + epp2 - 11.0 * (2.0 + 3.0 * epp2) * cosI2 -
  939.                                  40.0 * (2.0 + 5.0 * epp2) * cosI4 / C5c2 -
  940.                                  400.0 * epp2 * cosI6 / C5c2 / C5c2;
  941.             final double qyp42 = ( qyp22 + 4.0 * (2.0 + epp2 - (2.0 + 3.0 * epp2) * cosI2) ) / 5.0;
  942.             final double qyp52bis = epp * cosI1 * sinI1 *
  943.                                     (4.0 + 3.0 * epp2) *
  944.                                     (3.0 + 16.0 * cosI2 / C5c2 + 40.0 * cosI4 / C5c2 / C5c2);

  945.             // long periodic multiplicative
  946.             dei3sg =  35.0 / 96.0 * yp5 / yp2 * epp2 * n2 * cD * sinI1;
  947.             de2sg = -1.0 / 12.0 * epp * n2 / yp2 * qyp2_4;
  948.             deisg = ( -35.0 / 128.0 * yp5 / yp2 * epp2 * n2 * cD +
  949.                     1.0 / 4.0 * n2 / yp2 * (yp3 + 5.0 / 16.0 * yp5 * (4.0 + 3.0 * epp2) * cC)) * sinI1;
  950.             de = epp2 * n2 / 24.0 / yp2 * qyp2_4;

  951.             final double qyp52quotient = epp * (-32.0 + 81.0 * epp4) / (4.0 + 3.0 * epp2 + n * (4.0 + 9.0 * epp2));
  952.             dlgs2g = 1.0 / 48.0 / yp2 * (-3.0 * yp2 * yp2 * qyp22 +
  953.                      10.0 * yp4 * qyp42 ) +
  954.                      n3 / yp2 * qyp2_4 / 24.0;
  955.             dlgc3g = 35.0 / 384.0 * yp5 / yp2 * n3 * epp * cD * sinI1 +
  956.                      35.0 / 1152.0 * yp5 / yp2 * (2.0 * qyp52 * cosI1 - epp * cD * sinI1 * (3.0 + 2.0 * epp2));
  957.             dlgcg = -yp3 * epp * cosI2 / ( 4.0 * yp2 * sinI1) +
  958.                     0.078125 * yp5 / yp2 * (-epp * cosI2 / sinI1 * (4.0 + 3.0 * epp2) + epp2 * sinI1 * (26.0 + 9.0 * epp2)) * cC -
  959.                     0.46875 * yp5 / yp2 * qyp52bis * cosI1 +
  960.                     0.25 * yp3 / yp2 * sinI1 * epp / (1.0 + n3) * (3.0 - epp2 * (3.0 - epp2)) +
  961.                     0.078125 * yp5 / yp2 * n2 * cC * qyp52quotient * sinI1;


  962.             final double qyp24 = 3.0 * yp2 * yp2 * (11.0 + 80.0 * cosI2 / sinI1 + 200.0 * cosI4 / sinI2) -
  963.                                  10.0 * yp4 * (3.0 + 16.0 * cosI2 / sinI1 + 40.0 * cosI4 / sinI2);
  964.             dh2sgcg = 35.0 / 144.0 * yp5 / yp2 * qyp52;
  965.             dhsgcg = -epp2 * cosI1 / (12.0 * yp2) * qyp24;
  966.             dhcg = -35.0 / 576.0 * yp5 / yp2 * qyp52 +
  967.                    epp * cosI1 / (4.0 * yp2 * sinI1) * (yp3 + 0.3125 * yp5 * (4.0 + 3.0 * epp2) * cC) +
  968.                    1.875 / (4.0 * yp2) * yp5 * qyp52bis;

  969.             // short periodic multiplicative
  970.             aC = -yp2 * C3c2 * app / n3;
  971.             aCbis = y2 * app * C3c2;
  972.             ac2g2f = y2 * app * 3.0 * sinI2;

  973.             double qe = 0.5 * n2 * y2 * C3c2 / n6;
  974.             eC = qe * epp / (1.0 + n3) * (3.0 - epp2 * (3.0 - epp2));
  975.             ecf = 3.0 * qe;
  976.             e2cf = 3.0 * epp * qe;
  977.             e3cf = epp2 * qe;
  978.             qe = 0.5 * n2 * y2 * 3.0 * (1.0 - cosI2) / n6;
  979.             ec2f2g = qe * epp;
  980.             ecfc2f2g = 3.0 * qe;
  981.             e2cfc2f2g = 3.0 * epp * qe;
  982.             e3cfc2f2g = epp2 * qe;
  983.             qe = -0.5 * yp2 * n2 * (1.0 - cosI2);
  984.             ec2gf = 3.0 * qe;
  985.             ec2g3f = qe;

  986.             double qi = epp * yp2 * cosI1 * sinI1;
  987.             ide = -epp * cosI1 / (n2 * sinI1);
  988.             isfs2f2g = qi;
  989.             icfc2f2g = 2.0 * qi;
  990.             qi = yp2 * cosI1 * sinI1;
  991.             ic2f2g = 1.5 * qi;

  992.             double qgl1 = 0.25 * yp2;
  993.             double qgl2 = 0.25 * yp2 * epp * n2 / (1.0 + n);
  994.             glf = qgl1 * -6.0 * C5c2;
  995.             gll = qgl1 * 6.0 * C5c2;
  996.             glsf = qgl1 * -6.0 * C5c2 * epp +
  997.                    qgl2 * 2.0 * C3c2;
  998.             glosf = qgl2 * 2.0 * C3c2;
  999.             qgl1 = qgl1 * (3.0 - 5.0 * cosI2);
  1000.             qgl2 = qgl2 * 3.0 * (1.0 - cosI2);
  1001.             gls2f2g = 3.0 * qgl1;
  1002.             gls2gf = 3.0 * epp * qgl1 +
  1003.                      qgl2;
  1004.             glos2gf = -1.0 * qgl2;
  1005.             gls2g3f = qgl1 * epp +
  1006.                       1.0 / 3.0 * qgl2;
  1007.             glos2g3f = qgl2;

  1008.             final double qh = 3.0 * yp2 * cosI1;
  1009.             hf = -qh;
  1010.             hl = qh;
  1011.             hsf = -epp * qh;
  1012.             hcfs2g2f = 2.0 * epp * yp2 * cosI1;
  1013.             hs2g2f = 1.5 * yp2 * cosI1;
  1014.             hsfc2g2f = -epp * yp2 * cosI1;

  1015.             final double qedl = -0.25 * yp2 * n3;
  1016.             edls2g = 1.0 / 24.0 * epp * n3 / yp2 * qyp2_4;
  1017.             edlcg = -0.25 * yp3 / yp2 * n3 * sinI1 -
  1018.                     0.078125 * yp5 / yp2 * n3 * sinI1 * (4.0 + 9.0 * epp2) * cC;
  1019.             edlc3g = 35.0 / 384.0 * yp5 / yp2 * n3 * epp2 * cD * sinI1;
  1020.             edlsf = 2.0 * qedl * C3c2;
  1021.             edls2gf = 3.0 * qedl * (1.0 - cosI2);
  1022.             edls2g3f = 1.0 / 3.0 * qedl;

  1023.             // secular rates of the mean semi-major axis and eccentricity
  1024.             // Eq. 2.41 and Eq. 2.45 of Phipps' 1992 thesis
  1025.             aRate = -4.0 * app / (3.0 * xnotDot);
  1026.             eRate = -4.0 * epp * n * n / (3.0 * xnotDot);

  1027.         }

  1028.         /**
  1029.          * Accurate computation of E - e sin(E).
  1030.          *
  1031.          * @param E eccentric anomaly
  1032.          * @return E - e sin(E)
  1033.          */
  1034.         private UnivariateDerivative2 eMeSinE(final UnivariateDerivative2 E) {
  1035.             UnivariateDerivative2 x = E.sin().multiply(1 - mean.getE());
  1036.             final UnivariateDerivative2 mE2 = E.negate().multiply(E);
  1037.             UnivariateDerivative2 term = E;
  1038.             UnivariateDerivative2 d    = E.getField().getZero();
  1039.             // the inequality test below IS intentional and should NOT be replaced by a check with a small tolerance
  1040.             for (UnivariateDerivative2 x0 = d.add(Double.NaN); !Double.valueOf(x.getValue()).equals(Double.valueOf(x0.getValue()));) {
  1041.                 d = d.add(2);
  1042.                 term = term.multiply(mE2.divide(d.multiply(d.add(1))));
  1043.                 x0 = x;
  1044.                 x = x.subtract(term);
  1045.             }
  1046.             return x;
  1047.         }

  1048.         /**
  1049.          * Gets eccentric anomaly from mean anomaly.
  1050.          * <p>The algorithm used to solve the Kepler equation has been published in:
  1051.          * "Procedures for  solving Kepler's Equation", A. W. Odell and R. H. Gooding,
  1052.          * Celestial Mechanics 38 (1986) 307-334</p>
  1053.          * <p>It has been copied from the OREKIT library (KeplerianOrbit class).</p>
  1054.          *
  1055.          * @param mk the mean anomaly (rad)
  1056.          * @return the eccentric anomaly (rad)
  1057.          */
  1058.         private UnivariateDerivative2 getEccentricAnomaly(final UnivariateDerivative2 mk) {
  1059.             final double k1 = 3 * FastMath.PI + 2;
  1060.             final double k2 = FastMath.PI - 1;
  1061.             final double k3 = 6 * FastMath.PI - 1;
  1062.             final double A  = 3.0 * k2 * k2 / k1;
  1063.             final double B  = k3 * k3 / (6.0 * k1);
  1064.             // reduce M to [-PI PI] interval
  1065.             final UnivariateDerivative2 reducedM = new UnivariateDerivative2(MathUtils.normalizeAngle(mk.getValue(), 0.0),
  1066.                                                                              mk.getFirstDerivative(),
  1067.                                                                              mk.getSecondDerivative());

  1068.             // compute start value according to A. W. Odell and R. H. Gooding S12 starter
  1069.             UnivariateDerivative2 ek;
  1070.             if (FastMath.abs(reducedM.getValue()) < 1.0 / 6.0) {
  1071.                 if (FastMath.abs(reducedM.getValue()) < Precision.SAFE_MIN) {
  1072.                     // this is an Orekit change to the S12 starter.
  1073.                     // If reducedM is 0.0, the derivative of cbrt is infinite which induces NaN appearing later in
  1074.                     // the computation. As in this case E and M are almost equal, we initialize ek with reducedM
  1075.                     ek = reducedM;
  1076.                 } else {
  1077.                     // this is the standard S12 starter
  1078.                     ek = reducedM.add(reducedM.multiply(6).cbrt().subtract(reducedM).multiply(mean.getE()));
  1079.                 }
  1080.             } else {
  1081.                 if (reducedM.getValue() < 0) {
  1082.                     final UnivariateDerivative2 w = reducedM.add(FastMath.PI);
  1083.                     ek = reducedM.add(w.multiply(-A).divide(w.subtract(B)).subtract(FastMath.PI).subtract(reducedM).multiply(mean.getE()));
  1084.                 } else {
  1085.                     final UnivariateDerivative2 minusW = reducedM.subtract(FastMath.PI);
  1086.                     ek = reducedM.add(minusW.multiply(A).divide(minusW.add(B)).add(FastMath.PI).subtract(reducedM).multiply(mean.getE()));
  1087.                 }
  1088.             }

  1089.             final double e1 = 1 - mean.getE();
  1090.             final boolean noCancellationRisk = (e1 + ek.getValue() * ek.getValue() / 6) >= 0.1;

  1091.             // perform two iterations, each consisting of one Halley step and one Newton-Raphson step
  1092.             for (int j = 0; j < 2; ++j) {
  1093.                 final UnivariateDerivative2 f;
  1094.                 UnivariateDerivative2 fd;
  1095.                 final UnivariateDerivative2 fdd  = ek.sin().multiply(mean.getE());
  1096.                 final UnivariateDerivative2 fddd = ek.cos().multiply(mean.getE());
  1097.                 if (noCancellationRisk) {
  1098.                     f  = ek.subtract(fdd).subtract(reducedM);
  1099.                     fd = fddd.subtract(1).negate();
  1100.                 } else {
  1101.                     f  = eMeSinE(ek).subtract(reducedM);
  1102.                     final UnivariateDerivative2 s = ek.multiply(0.5).sin();
  1103.                     fd = s.multiply(s).multiply(2 * mean.getE()).add(e1);
  1104.                 }
  1105.                 final UnivariateDerivative2 dee = f.multiply(fd).divide(f.multiply(0.5).multiply(fdd).subtract(fd.multiply(fd)));

  1106.                 // update eccentric anomaly, using expressions that limit underflow problems
  1107.                 final UnivariateDerivative2 w = fd.add(dee.multiply(0.5).multiply(fdd.add(dee.multiply(fdd).divide(3))));
  1108.                 fd = fd.add(dee.multiply(fdd.add(dee.multiply(0.5).multiply(fdd))));
  1109.                 ek = ek.subtract(f.subtract(dee.multiply(fd.subtract(w))).divide(fd));
  1110.             }

  1111.             // expand the result back to original range
  1112.             ek = ek.add(mk.getValue() - reducedM.getValue());

  1113.             // Returns the eccentric anomaly
  1114.             return ek;
  1115.         }

  1116.         /**
  1117.          * This method is used in Brouwer-Lyddane model to avoid singularity at the
  1118.          * critical inclination (i = 63.4°).
  1119.          * <p>
  1120.          * This method, based on Warren Phipps's 1992 thesis (Eq. 2.47 and 2.48),
  1121.          * approximate the factor (1.0 - 5.0 * cos²(inc))^-1 (causing the singularity)
  1122.          * by a function, named T2 in the thesis.
  1123.          * </p>
  1124.          * @param cosInc cosine of the mean inclination
  1125.          * @return an approximation of (1.0 - 5.0 * cos²(inc))^-1 term
  1126.          */
  1127.         private double T2(final double cosInc) {

  1128.             // X = (1.0 - 5.0 * cos²(inc))
  1129.             final double x  = 1.0 - 5.0 * cosInc * cosInc;
  1130.             final double x2 = x * x;

  1131.             // Eq. 2.48
  1132.             double sum = 0.0;
  1133.             for (int i = 0; i <= 12; i++) {
  1134.                 final double sign = i % 2 == 0 ? +1.0 : -1.0;
  1135.                 sum += sign * FastMath.pow(BETA, i) * FastMath.pow(x2, i) / CombinatoricsUtils.factorialDouble(i + 1);
  1136.             }

  1137.             // Right term of equation 2.47
  1138.             double product = 1.0;
  1139.             for (int i = 0; i <= 10; i++) {
  1140.                 product *= 1 + FastMath.exp(FastMath.scalb(-1.0, i) * BETA * x2);
  1141.             }

  1142.             // Return (Eq. 2.47)
  1143.             return BETA * x * sum * product;

  1144.         }

  1145.         /** Extrapolate an orbit up to a specific target date.
  1146.          * @param date target date for the orbit
  1147.          * @return propagated parameters
  1148.          */
  1149.         public KeplerianOrbit propagateParameters(final AbsoluteDate date) {

  1150.             // Empirical drag coefficient M2
  1151.             final double m2 = M2Driver.getValue();

  1152.             // Keplerian evolution
  1153.             final UnivariateDerivative2 dt = new UnivariateDerivative2(date.durationFrom(mean.getDate()), 1.0, 0.0);
  1154.             final UnivariateDerivative2 xnot = dt.multiply(xnotDot);

  1155.             //____________________________________
  1156.             // secular effects

  1157.             // mean mean anomaly (with drag Eq. 2.38 of Phipps' 1992 thesis)
  1158.             final UnivariateDerivative2 dtM2  = dt.multiply(m2);
  1159.             final UnivariateDerivative2 dt2M2 = dt.multiply(dtM2);
  1160.             final UnivariateDerivative2 lpp = new UnivariateDerivative2(MathUtils.normalizeAngle(mean.getMeanAnomaly() + lt * xnot.getValue() + dt2M2.getValue(), 0),
  1161.                                                                       lt * xnotDot + 2.0 * dtM2.getValue(),
  1162.                                                                       2.0 * m2);
  1163.             // mean argument of perigee
  1164.             final UnivariateDerivative2 gpp = new UnivariateDerivative2(MathUtils.normalizeAngle(mean.getPerigeeArgument() + gt * xnot.getValue(), 0),
  1165.                                                                       gt * xnotDot,
  1166.                                                                       0.0);
  1167.             // mean longitude of ascending node
  1168.             final UnivariateDerivative2 hpp = new UnivariateDerivative2(MathUtils.normalizeAngle(mean.getRightAscensionOfAscendingNode() + ht * xnot.getValue(), 0),
  1169.                                                                       ht * xnotDot,
  1170.                                                                       0.0);

  1171.             // ________________________________________________
  1172.             // secular rates of the mean semi-major axis and eccentricity

  1173.             // semi-major axis
  1174.             final UnivariateDerivative2 appDrag = dt.multiply(aRate * m2);

  1175.             // eccentricity
  1176.             final UnivariateDerivative2 eppDrag = dt.multiply(eRate * m2);

  1177.             //____________________________________
  1178.             // Long periodical terms
  1179.             final UnivariateDerivative2 cg1 = gpp.cos();
  1180.             final UnivariateDerivative2 sg1 = gpp.sin();
  1181.             final UnivariateDerivative2 c2g = cg1.multiply(cg1).subtract(sg1.multiply(sg1));
  1182.             final UnivariateDerivative2 s2g = cg1.multiply(sg1).add(sg1.multiply(cg1));
  1183.             final UnivariateDerivative2 c3g = c2g.multiply(cg1).subtract(s2g.multiply(sg1));
  1184.             final UnivariateDerivative2 sg2 = sg1.multiply(sg1);
  1185.             final UnivariateDerivative2 sg3 = sg1.multiply(sg2);


  1186.             // de eccentricity
  1187.             final UnivariateDerivative2 d1e = sg3.multiply(dei3sg).
  1188.                                               add(sg1.multiply(deisg)).
  1189.                                               add(sg2.multiply(de2sg)).
  1190.                                               add(de);

  1191.             // l' + g'
  1192.             final UnivariateDerivative2 lp_p_gp = s2g.multiply(dlgs2g).
  1193.                                                add(c3g.multiply(dlgc3g)).
  1194.                                                add(cg1.multiply(dlgcg)).
  1195.                                                add(lpp).
  1196.                                                add(gpp);

  1197.             // h'
  1198.             final UnivariateDerivative2 hp = sg2.multiply(cg1).multiply(dh2sgcg).
  1199.                                                add(sg1.multiply(cg1).multiply(dhsgcg)).
  1200.                                                add(cg1.multiply(dhcg)).
  1201.                                                add(hpp);

  1202.             // Short periodical terms
  1203.             // eccentric anomaly
  1204.             final UnivariateDerivative2 Ep = getEccentricAnomaly(lpp);
  1205.             final UnivariateDerivative2 cf1 = (Ep.cos().subtract(mean.getE())).divide(Ep.cos().multiply(-mean.getE()).add(1.0));
  1206.             final UnivariateDerivative2 sf1 = (Ep.sin().multiply(n)).divide(Ep.cos().multiply(-mean.getE()).add(1.0));
  1207.             final UnivariateDerivative2 f = FastMath.atan2(sf1, cf1);

  1208.             final UnivariateDerivative2 c2f = cf1.multiply(cf1).subtract(sf1.multiply(sf1));
  1209.             final UnivariateDerivative2 s2f = cf1.multiply(sf1).add(sf1.multiply(cf1));
  1210.             final UnivariateDerivative2 c3f = c2f.multiply(cf1).subtract(s2f.multiply(sf1));
  1211.             final UnivariateDerivative2 s3f = c2f.multiply(sf1).add(s2f.multiply(cf1));
  1212.             final UnivariateDerivative2 cf2 = cf1.multiply(cf1);
  1213.             final UnivariateDerivative2 cf3 = cf1.multiply(cf2);

  1214.             final UnivariateDerivative2 c2g1f = cf1.multiply(c2g).subtract(sf1.multiply(s2g));
  1215.             final UnivariateDerivative2 c2g2f = c2f.multiply(c2g).subtract(s2f.multiply(s2g));
  1216.             final UnivariateDerivative2 c2g3f = c3f.multiply(c2g).subtract(s3f.multiply(s2g));
  1217.             final UnivariateDerivative2 s2g1f = cf1.multiply(s2g).add(c2g.multiply(sf1));
  1218.             final UnivariateDerivative2 s2g2f = c2f.multiply(s2g).add(c2g.multiply(s2f));
  1219.             final UnivariateDerivative2 s2g3f = c3f.multiply(s2g).add(c2g.multiply(s3f));

  1220.             final UnivariateDerivative2 eE = (Ep.cos().multiply(-mean.getE()).add(1.0)).reciprocal();
  1221.             final UnivariateDerivative2 eE3 = eE.multiply(eE).multiply(eE);
  1222.             final UnivariateDerivative2 sigma = eE.multiply(n * n).multiply(eE).add(eE);

  1223.             // Semi-major axis (with drag Eq. 2.41 of Phipps' 1992 thesis)
  1224.             final UnivariateDerivative2 a = eE3.multiply(aCbis).add(appDrag.add(mean.getA())).
  1225.                                             add(aC).
  1226.                                             add(eE3.multiply(c2g2f).multiply(ac2g2f));

  1227.             // Eccentricity (with drag Eq. 2.45 of Phipps' 1992 thesis)
  1228.             final UnivariateDerivative2 e = d1e.add(eppDrag.add(mean.getE())).
  1229.                                             add(eC).
  1230.                                             add(cf1.multiply(ecf)).
  1231.                                             add(cf2.multiply(e2cf)).
  1232.                                             add(cf3.multiply(e3cf)).
  1233.                                             add(c2g2f.multiply(ec2f2g)).
  1234.                                             add(c2g2f.multiply(cf1).multiply(ecfc2f2g)).
  1235.                                             add(c2g2f.multiply(cf2).multiply(e2cfc2f2g)).
  1236.                                             add(c2g2f.multiply(cf3).multiply(e3cfc2f2g)).
  1237.                                             add(c2g1f.multiply(ec2gf)).
  1238.                                             add(c2g3f.multiply(ec2g3f));

  1239.             // Inclination
  1240.             final UnivariateDerivative2 i = d1e.multiply(ide).
  1241.                                             add(mean.getI()).
  1242.                                             add(sf1.multiply(s2g2f.multiply(isfs2f2g))).
  1243.                                             add(cf1.multiply(c2g2f.multiply(icfc2f2g))).
  1244.                                             add(c2g2f.multiply(ic2f2g));

  1245.             // Argument of perigee + True anomaly
  1246.             final UnivariateDerivative2 g_p_l = lp_p_gp.add(f.multiply(glf)).
  1247.                                                 add(lpp.multiply(gll)).
  1248.                                                 add(sf1.multiply(glsf)).
  1249.                                                 add(sigma.multiply(sf1).multiply(glosf)).
  1250.                                                 add(s2g2f.multiply(gls2f2g)).
  1251.                                                 add(s2g1f.multiply(gls2gf)).
  1252.                                                 add(sigma.multiply(s2g1f).multiply(glos2gf)).
  1253.                                                 add(s2g3f.multiply(gls2g3f)).
  1254.                                                 add(sigma.multiply(s2g3f).multiply(glos2g3f));


  1255.             // Longitude of ascending node
  1256.             final UnivariateDerivative2 h = hp.add(f.multiply(hf)).
  1257.                                             add(lpp.multiply(hl)).
  1258.                                             add(sf1.multiply(hsf)).
  1259.                                             add(cf1.multiply(s2g2f).multiply(hcfs2g2f)).
  1260.                                             add(s2g2f.multiply(hs2g2f)).
  1261.                                             add(c2g2f.multiply(sf1).multiply(hsfc2g2f));

  1262.             final UnivariateDerivative2 edl = s2g.multiply(edls2g).
  1263.                                             add(cg1.multiply(edlcg)).
  1264.                                             add(c3g.multiply(edlc3g)).
  1265.                                             add(sf1.multiply(edlsf)).
  1266.                                             add(s2g1f.multiply(edls2gf)).
  1267.                                             add(s2g3f.multiply(edls2g3f)).
  1268.                                             add(sf1.multiply(sigma).multiply(edlsf)).
  1269.                                             add(s2g1f.multiply(sigma).multiply(-edls2gf)).
  1270.                                             add(s2g3f.multiply(sigma).multiply(3.0 * edls2g3f));

  1271.             final UnivariateDerivative2 A = e.multiply(lpp.cos()).subtract(edl.multiply(lpp.sin()));
  1272.             final UnivariateDerivative2 B = e.multiply(lpp.sin()).add(edl.multiply(lpp.cos()));

  1273.             // True anomaly
  1274.             final UnivariateDerivative2 l = FastMath.atan2(B, A);

  1275.             // Argument of perigee
  1276.             final UnivariateDerivative2 g = g_p_l.subtract(l);

  1277.             // Return a Keplerian orbit
  1278.             return new KeplerianOrbit(a.getValue(), e.getValue(), i.getValue(),
  1279.                                       g.getValue(), h.getValue(), l.getValue(),
  1280.                                       a.getFirstDerivative(), e.getFirstDerivative(), i.getFirstDerivative(),
  1281.                                       g.getFirstDerivative(), h.getFirstDerivative(), l.getFirstDerivative(),
  1282.                                       PositionAngleType.MEAN, mean.getFrame(), date, mu);

  1283.         }

  1284.     }

  1285.     /** {@inheritDoc} */
  1286.     protected double getMass(final AbsoluteDate date) {
  1287.         return models.get(date).mass;
  1288.     }

  1289. }