BrouwerLyddanePropagator.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.analytical;

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

  21. import org.hipparchus.analysis.differentiation.UnivariateDerivative1;
  22. import org.hipparchus.linear.RealMatrix;
  23. import org.hipparchus.util.CombinatoricsUtils;
  24. import org.hipparchus.util.FastMath;
  25. import org.hipparchus.util.FieldSinCos;
  26. import org.hipparchus.util.MathUtils;
  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.FieldKeplerianAnomalyUtility;
  35. import org.orekit.orbits.KeplerianOrbit;
  36. import org.orekit.orbits.Orbit;
  37. import org.orekit.orbits.OrbitType;
  38. import org.orekit.orbits.PositionAngleType;
  39. import org.orekit.propagation.AbstractMatricesHarvester;
  40. import org.orekit.propagation.MatricesHarvester;
  41. import org.orekit.propagation.PropagationType;
  42. import org.orekit.propagation.SpacecraftState;
  43. import org.orekit.propagation.analytical.tle.TLE;
  44. import org.orekit.propagation.conversion.osc2mean.BrouwerLyddaneTheory;
  45. import org.orekit.propagation.conversion.osc2mean.FixedPointConverter;
  46. import org.orekit.propagation.conversion.osc2mean.MeanTheory;
  47. import org.orekit.propagation.conversion.osc2mean.OsculatingToMeanConverter;
  48. import org.orekit.time.AbsoluteDate;
  49. import org.orekit.utils.DoubleArrayDictionary;
  50. import org.orekit.utils.ParameterDriver;
  51. import org.orekit.utils.ParameterDriversProvider;
  52. import org.orekit.utils.TimeSpanMap;
  53. import org.orekit.utils.TimeSpanMap.Span;

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

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

  101.     /** Default value for M2 coefficient. */
  102.     public static final double M2 = 0.0;

  103.     /** Default convergence threshold for mean parameters conversion. */
  104.     public static final double EPSILON_DEFAULT = 1.0e-13;

  105.     /** Default value for maxIterations. */
  106.     public static final int MAX_ITERATIONS_DEFAULT = 200;

  107.     /** Parameters scaling factor.
  108.      * <p>
  109.      * We use a power of 2 to avoid numeric noise introduction
  110.      * in the multiplications/divisions sequences.
  111.      * </p>
  112.      */
  113.     private static final double SCALE = FastMath.scalb(1.0, -32);

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

  116.     /** Max value for the eccentricity. */
  117.     private static final double MAX_ECC = 0.999999;

  118.     /** Initial Brouwer-Lyddane model. */
  119.     private BLModel initialModel;

  120.     /** All models. */
  121.     private transient TimeSpanMap<BLModel> models;

  122.     /** Reference radius of the central body attraction model (m). */
  123.     private final double referenceRadius;

  124.     /** Central attraction coefficient (m³/s²). */
  125.     private final double mu;

  126.     /** Un-normalized zonal coefficients. */
  127.     private final double[] ck0;

  128.     /** Empirical coefficient used in the drag modeling. */
  129.     private final ParameterDriver M2Driver;

  130.     /** Build a propagator from orbit and potential provider.
  131.      * <p>Mass and attitude provider are set to unspecified non-null arbitrary values.</p>
  132.      *
  133.      * <p>Using this constructor, an initial osculating orbit is considered.</p>
  134.      *
  135.      * @param initialOrbit initial orbit
  136.      * @param provider for un-normalized zonal coefficients
  137.      * @param m2Value value of empirical drag coefficient in rad/s².
  138.      *        If equal to {@link #M2} drag is not computed
  139.      * @see #BrouwerLyddanePropagator(Orbit, AttitudeProvider, UnnormalizedSphericalHarmonicsProvider, double)
  140.      * @see #BrouwerLyddanePropagator(Orbit, UnnormalizedSphericalHarmonicsProvider, PropagationType, double)
  141.      */
  142.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  143.                                     final UnnormalizedSphericalHarmonicsProvider provider,
  144.                                     final double m2Value) {
  145.         this(initialOrbit, FrameAlignedProvider.of(initialOrbit.getFrame()),
  146.              DEFAULT_MASS, provider, provider.onDate(initialOrbit.getDate()), m2Value);
  147.     }

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

  186.     /** Build a propagator from orbit, mass and potential provider.
  187.      * <p>Attitude law is set to an unspecified non-null arbitrary value.</p>
  188.      *
  189.      * <p>Using this constructor, an initial osculating orbit is considered.</p>
  190.      *
  191.      * @param initialOrbit initial orbit
  192.      * @param mass spacecraft mass
  193.      * @param provider for un-normalized zonal coefficients
  194.      * @param m2Value value of empirical drag coefficient in rad/s².
  195.      *        If equal to {@link #M2} drag is not computed
  196.      * @see #BrouwerLyddanePropagator(Orbit, AttitudeProvider, double, UnnormalizedSphericalHarmonicsProvider, double)
  197.      */
  198.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  199.                                     final double mass,
  200.                                     final UnnormalizedSphericalHarmonicsProvider provider,
  201.                                     final double m2Value) {
  202.         this(initialOrbit, FrameAlignedProvider.of(initialOrbit.getFrame()),
  203.              mass, provider, provider.onDate(initialOrbit.getDate()), m2Value);
  204.     }

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

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

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

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

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


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

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

  393.     /** Build a propagator from orbit, attitude provider, mass and potential.
  394.      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
  395.      * are related to both the normalized coefficients
  396.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  397.      *  and the J<sub>n</sub> one as follows:</p>
  398.      *
  399.      * <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
  400.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  401.      *
  402.      * <p> C<sub>n,0</sub> = -J<sub>n</sub>
  403.      *
  404.      * <p>Using this constructor, it is possible to define the initial orbit as
  405.      * a mean Brouwer-Lyddane orbit or an osculating one.</p>
  406.      *
  407.      * @param initialOrbit initial orbit
  408.      * @param attitudeProv attitude provider
  409.      * @param mass spacecraft mass
  410.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  411.      * @param mu central attraction coefficient (m³/s²)
  412.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  413.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  414.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  415.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  416.      * @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
  417.      * @param m2Value value of empirical drag coefficient in rad/s².
  418.      *        If equal to {@link #M2} drag is not computed
  419.      */
  420.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  421.                                     final AttitudeProvider attitudeProv,
  422.                                     final double mass,
  423.                                     final double referenceRadius,
  424.                                     final double mu,
  425.                                     final double c20,
  426.                                     final double c30,
  427.                                     final double c40,
  428.                                     final double c50,
  429.                                     final PropagationType initialType,
  430.                                     final double m2Value) {
  431.         this(initialOrbit, attitudeProv, mass, referenceRadius, mu,
  432.              c20, c30, c40, c50, initialType, m2Value, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
  433.     }

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

  481.     /** Build a propagator from orbit, attitude provider, mass and potential.
  482.      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
  483.      * are related to both the normalized coefficients
  484.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  485.      *  and the J<sub>n</sub> one as follows:</p>
  486.      *
  487.      * <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
  488.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  489.      *
  490.      * <p> C<sub>n,0</sub> = -J<sub>n</sub>
  491.      *
  492.      * <p>Using this constructor, it is possible to define the initial orbit as
  493.      * a mean Brouwer-Lyddane orbit or an osculating one.</p>
  494.      *
  495.      * @param initialOrbit initial orbit
  496.      * @param attitudeProv attitude provider
  497.      * @param mass spacecraft mass
  498.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  499.      * @param mu central attraction coefficient (m³/s²)
  500.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  501.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  502.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  503.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  504.      * @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
  505.      * @param m2Value value of empirical drag coefficient in rad/s².
  506.      *        If equal to {@link #M2} drag is not computed
  507.      * @param converter osculating to mean orbit converter
  508.      * @since 13.0
  509.      */
  510.     public BrouwerLyddanePropagator(final Orbit initialOrbit,
  511.                                     final AttitudeProvider attitudeProv,
  512.                                     final double mass,
  513.                                     final double referenceRadius,
  514.                                     final double mu,
  515.                                     final double c20,
  516.                                     final double c30,
  517.                                     final double c40,
  518.                                     final double c50,
  519.                                     final PropagationType initialType,
  520.                                     final double m2Value,
  521.                                     final OsculatingToMeanConverter converter) {

  522.         super(attitudeProv);

  523.         // store model coefficients
  524.         this.referenceRadius = referenceRadius;
  525.         this.mu  = mu;
  526.         this.ck0 = new double[] {0.0, 0.0, c20, c30, c40, c50};

  527.         // initialize M2 driver
  528.         this.M2Driver = new ParameterDriver(M2_NAME, m2Value, SCALE,
  529.                                             Double.NEGATIVE_INFINITY,
  530.                                             Double.POSITIVE_INFINITY);

  531.         // compute mean parameters if needed
  532.         resetInitialState(new SpacecraftState(initialOrbit,
  533.                                               attitudeProv.getAttitude(initialOrbit,
  534.                                                                        initialOrbit.getDate(),
  535.                                                                        initialOrbit.getFrame())).withMass(mass),
  536.                           initialType, converter);

  537.     }

  538.     /**
  539.      * Private helper constructor.
  540.      * <p>Using this constructor, an initial osculating orbit is considered.</p>
  541.      * @param initialOrbit initial orbit
  542.      * @param attitude attitude provider
  543.      * @param mass spacecraft mass
  544.      * @param provider for un-normalized zonal coefficients
  545.      * @param harmonics {@code provider.onDate(initialOrbit.getDate())}
  546.      * @param m2Value value of empirical drag coefficient in rad/s².
  547.      *        If equal to {@link #M2} drag is not computed
  548.      * @see #BrouwerLyddanePropagator(Orbit, AttitudeProvider, double,
  549.      *                                UnnormalizedSphericalHarmonicsProvider,
  550.      *                                UnnormalizedSphericalHarmonics,
  551.      *                                PropagationType, double)
  552.      */
  553.     private BrouwerLyddanePropagator(final Orbit initialOrbit,
  554.                                      final AttitudeProvider attitude,
  555.                                      final double mass,
  556.                                      final UnnormalizedSphericalHarmonicsProvider provider,
  557.                                      final UnnormalizedSphericalHarmonics harmonics,
  558.                                      final double m2Value) {
  559.         this(initialOrbit, attitude, mass, provider.getAe(), provider.getMu(),
  560.              harmonics.getUnnormalizedCnm(2, 0),
  561.              harmonics.getUnnormalizedCnm(3, 0),
  562.              harmonics.getUnnormalizedCnm(4, 0),
  563.              harmonics.getUnnormalizedCnm(5, 0),
  564.              m2Value);
  565.     }

  566.     /**
  567.      * Private helper constructor.
  568.      * <p>Using this constructor, it is possible to define the initial orbit as
  569.      * a mean Brouwer-Lyddane orbit or an osculating one.</p>
  570.      * @param initialOrbit initial orbit
  571.      * @param attitude attitude provider
  572.      * @param mass spacecraft mass
  573.      * @param provider for un-normalized zonal coefficients
  574.      * @param harmonics {@code provider.onDate(initialOrbit.getDate())}
  575.      * @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
  576.      * @param m2Value value of empirical drag coefficient in rad/s².
  577.      *        If equal to {@link #M2} drag is not computed
  578.      */
  579.     private BrouwerLyddanePropagator(final Orbit initialOrbit,
  580.                                      final AttitudeProvider attitude,
  581.                                      final double mass,
  582.                                      final UnnormalizedSphericalHarmonicsProvider provider,
  583.                                      final UnnormalizedSphericalHarmonics harmonics,
  584.                                      final PropagationType initialType,
  585.                                      final double m2Value) {
  586.         this(initialOrbit, attitude, mass, provider.getAe(), provider.getMu(),
  587.              harmonics.getUnnormalizedCnm(2, 0),
  588.              harmonics.getUnnormalizedCnm(3, 0),
  589.              harmonics.getUnnormalizedCnm(4, 0),
  590.              harmonics.getUnnormalizedCnm(5, 0),
  591.              initialType, m2Value);
  592.     }

  593.     /** Conversion from osculating to mean orbit.
  594.      * <p>
  595.      * Compute mean orbit <b>in a Brouwer-Lyddane sense</b>, corresponding to the
  596.      * osculating SpacecraftState in input.
  597.      * </p>
  598.      * <p>
  599.      * Since the osculating orbit is obtained with the computation of
  600.      * short-periodic variation, the resulting output will depend on
  601.      * both the gravity field parameterized in input and the
  602.      * atmospheric drag represented by the {@code m2Value} parameter.
  603.      * </p>
  604.      * <p>
  605.      * The computation is done through a fixed-point iteration process.
  606.      * </p>
  607.      * @param osculating osculating orbit to convert
  608.      * @param provider for un-normalized zonal coefficients
  609.      * @param harmonics {@code provider.onDate(osculating.getDate())}
  610.      * @param m2Value value of empirical drag coefficient in rad/s².
  611.      *        If equal to {@link #M2} drag is not considered
  612.      * @return mean orbit in a Brouwer-Lyddane sense
  613.      * @since 11.2
  614.      */
  615.     public static KeplerianOrbit computeMeanOrbit(final Orbit osculating,
  616.                                                   final UnnormalizedSphericalHarmonicsProvider provider,
  617.                                                   final UnnormalizedSphericalHarmonics harmonics,
  618.                                                   final double m2Value) {
  619.         return computeMeanOrbit(osculating, provider, harmonics, m2Value,
  620.                                 EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
  621.     }

  622.     /** Conversion from osculating to mean orbit.
  623.      * <p>
  624.      * Compute mean orbit <b>in a Brouwer-Lyddane sense</b>, corresponding to the
  625.      * osculating SpacecraftState in input.
  626.      * </p>
  627.      * <p>
  628.      * Since the osculating orbit is obtained with the computation of
  629.      * short-periodic variation, the resulting output will depend on
  630.      * both the gravity field parameterized in input and the
  631.      * atmospheric drag represented by the {@code m2Value} parameter.
  632.      * </p>
  633.      * <p>
  634.      * The computation is done through a fixed-point iteration process.
  635.      * </p>
  636.      * @param osculating osculating orbit to convert
  637.      * @param provider for un-normalized zonal coefficients
  638.      * @param harmonics {@code provider.onDate(osculating.getDate())}
  639.      * @param m2Value value of empirical drag coefficient in rad/s².
  640.      *        If equal to {@link #M2} drag is not considered
  641.      * @param epsilon convergence threshold for mean parameters conversion
  642.      * @param maxIterations maximum iterations for mean parameters conversion
  643.      * @return mean orbit in a Brouwer-Lyddane sense
  644.      * @since 11.2
  645.      */
  646.     public static KeplerianOrbit computeMeanOrbit(final Orbit osculating,
  647.                                                   final UnnormalizedSphericalHarmonicsProvider provider,
  648.                                                   final UnnormalizedSphericalHarmonics harmonics,
  649.                                                   final double m2Value,
  650.                                                   final double epsilon,
  651.                                                   final int maxIterations) {
  652.         return computeMeanOrbit(osculating,
  653.                                 provider.getAe(), provider.getMu(),
  654.                                 harmonics.getUnnormalizedCnm(2, 0),
  655.                                 harmonics.getUnnormalizedCnm(3, 0),
  656.                                 harmonics.getUnnormalizedCnm(4, 0),
  657.                                 harmonics.getUnnormalizedCnm(5, 0),
  658.                                 m2Value, epsilon, maxIterations);
  659.     }

  660.     /** Conversion from osculating to mean orbit.
  661.      * <p>
  662.      * Compute mean orbit <b>in a Brouwer-Lyddane sense</b>, corresponding to the
  663.      * osculating SpacecraftState in input.
  664.      * </p>
  665.      * <p>
  666.      * Since the osculating orbit is obtained with the computation of
  667.      * short-periodic variation, the resulting output will depend on
  668.      * both the gravity field parameterized in input and the
  669.      * atmospheric drag represented by the {@code m2Value} parameter.
  670.      * </p>
  671.      * <p>
  672.      * The computation is done through a fixed-point iteration process.
  673.      * </p>
  674.      * @param osculating osculating orbit to convert
  675.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  676.      * @param mu central attraction coefficient (m³/s²)
  677.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  678.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  679.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  680.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  681.      * @param m2Value value of empirical drag coefficient in rad/s².
  682.      *        If equal to {@link #M2} drag is not considered
  683.      * @param epsilon convergence threshold for mean parameters conversion
  684.      * @param maxIterations maximum iterations for mean parameters conversion
  685.      * @return mean orbit in a Brouwer-Lyddane sense
  686.      * @since 11.2
  687.      */
  688.     public static KeplerianOrbit computeMeanOrbit(final Orbit osculating,
  689.                                                   final double referenceRadius,
  690.                                                   final double mu,
  691.                                                   final double c20,
  692.                                                   final double c30,
  693.                                                   final double c40,
  694.                                                   final double c50,
  695.                                                   final double m2Value,
  696.                                                   final double epsilon,
  697.                                                   final int maxIterations) {
  698.         // Build a fixed-point converter
  699.         final OsculatingToMeanConverter converter = new FixedPointConverter(epsilon, maxIterations,
  700.                                                                             FixedPointConverter.DEFAULT_DAMPING);
  701.         return computeMeanOrbit(osculating, referenceRadius, mu, c20, c30, c40, c50, m2Value, converter);
  702.     }

  703.     /** Conversion from osculating to mean orbit.
  704.      * <p>
  705.      * Compute mean orbit <b>in a Brouwer-Lyddane sense</b>, corresponding to the
  706.      * osculating SpacecraftState in input.
  707.      * </p>
  708.      * <p>
  709.      * Since the osculating orbit is obtained with the computation of
  710.      * short-periodic variation, the resulting output will depend on
  711.      * both the gravity field parameterized in input and the
  712.      * atmospheric drag represented by the {@code m2Value} parameter.
  713.      * </p>
  714.      * <p>
  715.      * The computation is done through the given osculating to mean orbit converter.
  716.      * </p>
  717.      * @param osculating osculating orbit to convert
  718.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  719.      * @param mu central attraction coefficient (m³/s²)
  720.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  721.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  722.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  723.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  724.      * @param m2Value value of empirical drag coefficient in rad/s².
  725.      *        If equal to {@link #M2} drag is not considered
  726.      * @param converter osculating to mean orbit converter
  727.      * @return mean orbit in a Brouwer-Lyddane sense
  728.      * @since 13.0
  729.      */
  730.     public static KeplerianOrbit computeMeanOrbit(final Orbit osculating,
  731.                                                   final double referenceRadius,
  732.                                                   final double mu,
  733.                                                   final double c20,
  734.                                                   final double c30,
  735.                                                   final double c40,
  736.                                                   final double c50,
  737.                                                   final double m2Value,
  738.                                                   final OsculatingToMeanConverter converter) {
  739.         // Set BL as the mean theory for converting
  740.         final MeanTheory theory = new BrouwerLyddaneTheory(referenceRadius, mu, c20, c30, c40, c50, m2Value);
  741.         converter.setMeanTheory(theory);
  742.         return (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(converter.convertToMean(osculating));
  743.     }

  744.     /** Conversion from osculating to mean orbit.
  745.      * <p>
  746.      * Compute mean orbit <b>in a Brouwer-Lyddane sense</b>, corresponding to the
  747.      * osculating SpacecraftState in input.
  748.      * </p>
  749.      * <p>
  750.      * Since the osculating orbit is obtained with the computation of
  751.      * short-periodic variation, the resulting output will depend on
  752.      * both the gravity field parameterized in input and the
  753.      * atmospheric drag represented by the {@code m2Value} parameter.
  754.      * </p>
  755.      * <p>
  756.      * The computation is done through the given osculating to mean orbit converter.
  757.      * </p>
  758.      * @param osculating osculating orbit to convert
  759.      * @param provider   for un-normalized zonal coefficients
  760.      * @param m2Value    value of empirical drag coefficient in rad/s².
  761.      *        If equal to {@link #M2} drag is not considered
  762.      * @param converter  osculating to mean orbit converter
  763.      * @return mean orbit in a Brouwer-Lyddane sense
  764.      * @since 13.0
  765.      */
  766.     public static KeplerianOrbit computeMeanOrbit(final Orbit osculating,
  767.                                                   final UnnormalizedSphericalHarmonicsProvider provider,
  768.                                                   final double m2Value,
  769.                                                   final OsculatingToMeanConverter converter) {
  770.         // Set BL as the mean theory for converting
  771.         final MeanTheory theory = new BrouwerLyddaneTheory(provider, m2Value);
  772.         converter.setMeanTheory(theory);
  773.         return (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(converter.convertToMean(osculating));
  774.     }

  775.     /** {@inheritDoc}
  776.      * <p>The new initial state to consider
  777.      * must be defined with an osculating orbit.</p>
  778.      * @see #resetInitialState(SpacecraftState, PropagationType)
  779.      */
  780.     @Override
  781.     public void resetInitialState(final SpacecraftState state) {
  782.         resetInitialState(state, PropagationType.OSCULATING);
  783.     }

  784.     /** Reset the propagator initial state.
  785.      * @param state new initial state to consider
  786.      * @param stateType mean Brouwer-Lyddane orbit or osculating orbit
  787.      */
  788.     public void resetInitialState(final SpacecraftState state, final PropagationType stateType) {
  789.         resetInitialState(state, stateType, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
  790.     }

  791.     /** Reset the propagator initial state.
  792.      * @param state new initial state to consider
  793.      * @param stateType mean Brouwer-Lyddane orbit or osculating orbit
  794.      * @param epsilon convergence threshold for mean parameters conversion
  795.      * @param maxIterations maximum iterations for mean parameters conversion
  796.      * @since 11.2
  797.      */
  798.     public void resetInitialState(final SpacecraftState state,
  799.                                   final PropagationType stateType,
  800.                                   final double epsilon,
  801.                                   final int maxIterations) {
  802.         final OsculatingToMeanConverter converter = new FixedPointConverter(epsilon, maxIterations,
  803.                                                                             FixedPointConverter.DEFAULT_DAMPING);
  804.         resetInitialState(state, stateType, converter);
  805.     }

  806.     /** Reset the propagator initial state.
  807.      * @param state     new initial state to consider
  808.      * @param stateType mean Brouwer-Lyddane orbit or osculating orbit
  809.      * @param converter osculating to mean orbit converter
  810.      * @since 13.0
  811.      */
  812.     public void resetInitialState(final SpacecraftState state,
  813.                                   final PropagationType stateType,
  814.                                   final OsculatingToMeanConverter converter) {
  815.         super.resetInitialState(state);
  816.         KeplerianOrbit keplerian = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(state.getOrbit());
  817.         if (stateType == PropagationType.OSCULATING) {
  818.             final MeanTheory theory = new BrouwerLyddaneTheory(referenceRadius, mu,
  819.                                                                ck0[2], ck0[3], ck0[4], ck0[5],
  820.                                                                getM2());
  821.             converter.setMeanTheory(theory);
  822.             keplerian = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(converter.convertToMean(keplerian));
  823.         }
  824.         this.initialModel = new BLModel(keplerian, state.getMass(), referenceRadius, mu, ck0);
  825.         this.models = new TimeSpanMap<>(initialModel);
  826.     }

  827.     /** {@inheritDoc} */
  828.     protected void resetIntermediateState(final SpacecraftState state, final boolean forward) {
  829.         resetIntermediateState(state, forward, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
  830.     }

  831.     /** Reset an intermediate state.
  832.      * @param state new intermediate state to consider
  833.      * @param forward if true, the intermediate state is valid for
  834.      * propagations after itself
  835.      * @param epsilon convergence threshold for mean parameters conversion
  836.      * @param maxIterations maximum iterations for mean parameters conversion
  837.      * @since 11.2
  838.      */
  839.     protected void resetIntermediateState(final SpacecraftState state,
  840.                                           final boolean forward,
  841.                                           final double epsilon,
  842.                                           final int maxIterations) {
  843.         final OsculatingToMeanConverter converter = new FixedPointConverter(epsilon, maxIterations,
  844.                                                                             FixedPointConverter.DEFAULT_DAMPING);
  845.         resetIntermediateState(state, forward, converter);
  846.     }

  847.     /** Reset an intermediate state.
  848.      * @param state     new intermediate state to consider
  849.      * @param forward   if true, the intermediate state is valid for
  850.      *                  propagations after itself
  851.      * @param converter osculating to mean orbit converter
  852.      * @since 13.0
  853.      */
  854.     protected void resetIntermediateState(final SpacecraftState state,
  855.                                           final boolean forward,
  856.                                           final OsculatingToMeanConverter converter) {
  857.         final MeanTheory theory = new BrouwerLyddaneTheory(referenceRadius, mu,
  858.                                                            ck0[2], ck0[3], ck0[4], ck0[5],
  859.                                                            getM2());
  860.         converter.setMeanTheory(theory);
  861.         final KeplerianOrbit mean = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(converter.convertToMean(state.getOrbit()));
  862.         final BLModel newModel = new BLModel(mean, state.getMass(), referenceRadius, mu, ck0);
  863.         if (forward) {
  864.             models.addValidAfter(newModel, state.getDate(), false);
  865.         } else {
  866.             models.addValidBefore(newModel, state.getDate(), false);
  867.         }
  868.         stateChanged(state);
  869.     }

  870.     /** {@inheritDoc} */
  871.     public KeplerianOrbit propagateOrbit(final AbsoluteDate date) {
  872.         // compute Keplerian parameters, taking derivatives into account
  873.         final BLModel current = models.get(date);
  874.         return current.propagateParameters(date);
  875.     }

  876.     /**
  877.      * Get the value of the M2 drag parameter. Beware that M2Driver
  878.      * must have only 1 span on its TimeSpanMap value (that is
  879.      * to say setPeriod method should not be called)
  880.      * @return the value of the M2 drag parameter
  881.      */
  882.     public double getM2() {
  883.         // As Brouwer Lyddane is an analytical propagator, for now it is not possible for
  884.         // M2Driver to have several values estimated
  885.         return M2Driver.getValue();
  886.     }

  887.     /**
  888.      * Get the central attraction coefficient μ.
  889.      * @return mu central attraction coefficient (m³/s²)
  890.      */
  891.     public double getMu() {
  892.         return mu;
  893.     }

  894.     /**
  895.      * Get the un-normalized zonal coefficients.
  896.      * @return the un-normalized zonal coefficients
  897.      */
  898.     public double[] getCk0() {
  899.         return ck0.clone();
  900.     }

  901.     /**
  902.      * Get the reference radius of the central body attraction model.
  903.      * @return the reference radius in meters
  904.      */
  905.     public double getReferenceRadius() {
  906.         return referenceRadius;
  907.     }

  908.     /**
  909.      * Get the parameters driver for propagation model.
  910.      * @return drivers for propagation model
  911.      */
  912.     public List<ParameterDriver> getParametersDrivers() {
  913.         return Collections.singletonList(M2Driver);
  914.     }

  915.     /** {@inheritDoc} */
  916.     @Override
  917.     protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
  918.                                                         final DoubleArrayDictionary initialJacobianColumns) {
  919.         // Create the harvester
  920.         final BrouwerLyddaneHarvester harvester = new BrouwerLyddaneHarvester(this, stmName, initialStm, initialJacobianColumns);
  921.         // Update the list of additional state provider
  922.         addAdditionalDataProvider(harvester);
  923.         // Return the configured harvester
  924.         return harvester;
  925.     }

  926.     /**
  927.      * Get the names of the parameters in the matrix returned by {@link MatricesHarvester#getParametersJacobian}.
  928.      * @return names of the parameters (i.e. columns) of the Jacobian matrix
  929.      */
  930.     @Override
  931.     protected List<String> getJacobiansColumnsNames() {
  932.         final List<String> columnsNames = new ArrayList<>();
  933.         for (final ParameterDriver driver : getParametersDrivers()) {
  934.             if (driver.isSelected() && !columnsNames.contains(driver.getNamesSpanMap().getFirstSpan().getData())) {
  935.                 for (Span<String> span = driver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
  936.                     columnsNames.add(span.getData());
  937.                 }
  938.             }
  939.         }
  940.         Collections.sort(columnsNames);
  941.         return columnsNames;
  942.     }

  943.     /** Local class for Brouwer-Lyddane model. */
  944.     private class BLModel {

  945.         /** Constant mass. */
  946.         private final double mass;

  947.         /** Brouwer-Lyddane mean orbit. */
  948.         private final KeplerianOrbit mean;

  949.         // Preprocessed values

  950.         /** Mean mean motion: n0 = √(μ/a")/a". */
  951.         private final double n0;

  952.         /** η = √(1 - e"²). */
  953.         private final double n;
  954.         /** η². */
  955.         private final double n2;
  956.         /** η³. */
  957.         private final double n3;
  958.         /** η + 1 / (1 + η). */
  959.         private final double t8;

  960.         /** Secular correction for mean anomaly l: &delta;<sub>s</sub>l. */
  961.         private final double dsl;
  962.         /** Secular correction for perigee argument g: &delta;<sub>s</sub>g. */
  963.         private final double dsg;
  964.         /** Secular correction for raan h: &delta;<sub>s</sub>h. */
  965.         private final double dsh;

  966.         /** Secular rate of change of semi-major axis due to drag. */
  967.         private final double aRate;
  968.         /** Secular rate of change of eccentricity due to drag. */
  969.         private final double eRate;

  970.         // CHECKSTYLE: stop JavadocVariable check

  971.         // Storage for speed-up
  972.         private final double yp2;
  973.         private final double ci;
  974.         private final double si;
  975.         private final double oneMci2;
  976.         private final double ci2X3M1;

  977.         // Long periodic corrections factors
  978.         private final double vle1;
  979.         private final double vle2;
  980.         private final double vle3;
  981.         private final double vli1;
  982.         private final double vli2;
  983.         private final double vli3;
  984.         private final double vll2;
  985.         private final double vlh1I;
  986.         private final double vlh2I;
  987.         private final double vlh3I;
  988.         private final double vls1;
  989.         private final double vls2;
  990.         private final double vls3;

  991.         // CHECKSTYLE: resume JavadocVariable check

  992.         /** Create a model for specified mean orbit.
  993.          * @param mean mean orbit
  994.          * @param mass constant mass
  995.          * @param referenceRadius reference radius of the central body attraction model (m)
  996.          * @param mu central attraction coefficient (m³/s²)
  997.          * @param ck0 un-normalized zonal coefficients
  998.          */
  999.         BLModel(final KeplerianOrbit mean, final double mass,
  1000.                 final double referenceRadius, final double mu, final double[] ck0) {

  1001.             this.mass = mass;

  1002.             // mean orbit
  1003.             this.mean = mean;

  1004.             // mean eccentricity e"
  1005.             final double epp = mean.getE();
  1006.             if (epp >= 1) {
  1007.                 // Only for elliptical (e < 1) orbits
  1008.                 throw new OrekitException(OrekitMessages.TOO_LARGE_ECCENTRICITY_FOR_PROPAGATION_MODEL,
  1009.                                           epp);
  1010.             }
  1011.             final double epp2 = epp * epp;

  1012.             // η
  1013.             n2 = 1. - epp2;
  1014.             n  = FastMath.sqrt(n2);
  1015.             n3 = n2 * n;
  1016.             t8 = n + 1. / (1. + n);

  1017.             // mean semi-major axis a"
  1018.             final double app = mean.getA();

  1019.             // mean mean motion
  1020.             n0 = FastMath.sqrt(mu / app) / app;

  1021.             // ae/a"
  1022.             final double q = referenceRadius / app;

  1023.             // γ2'
  1024.             double ql = q * q;
  1025.             double nl = n2 * n2;
  1026.             yp2 = -0.5 * ck0[2] * ql / nl;
  1027.             final double yp22 = yp2 * yp2;

  1028.             // γ3'
  1029.             ql *= q;
  1030.             nl *= n2;
  1031.             final double yp3 = ck0[3] * ql / nl;

  1032.             // γ4'
  1033.             ql *= q;
  1034.             nl *= n2;
  1035.             final double yp4 = 0.375 * ck0[4] * ql / nl;

  1036.             // γ5'
  1037.             ql *= q;
  1038.             nl *= n2;
  1039.             final double yp5 = ck0[5] * ql / nl;

  1040.             // mean inclination I" sin & cos
  1041.             final SinCos sci = FastMath.sinCos(mean.getI());
  1042.             si = sci.sin();
  1043.             ci = sci.cos();
  1044.             final double ci2 = ci * ci;
  1045.             oneMci2 = 1.0 - ci2;
  1046.             ci2X3M1 = 3.0 * ci2 - 1.0;
  1047.             final double ci2X5M1 = 5.0 * ci2 - 1.0;

  1048.             // secular corrections
  1049.             // true anomaly
  1050.             dsl = 1.5 * yp2 * n * (ci2X3M1 +
  1051.                                    0.0625 * yp2 * (-15.0 + n * (16.0 + 25.0 * n) +
  1052.                                                    ci2 * (30.0 - n * (96.0 + 90.0 * n) +
  1053.                                                           ci2 * (105.0 + n * (144.0 + 25.0 * n))))) +
  1054.                   0.9375 * yp4 * n * epp2 * (3.0 - ci2 * (30.0 - 35.0 * ci2));
  1055.             // perigee argument
  1056.             dsg = 1.5 * yp2 * ci2X5M1 +
  1057.                   0.09375 * yp22 * (-35.0 + n * (24.0 + 25.0 * n) +
  1058.                                     ci2 * (90.0 - n * (192.0 + 126.0 * n) +
  1059.                                            ci2 * (385.0 + n * (360.0 + 45.0 * n)))) +
  1060.                   0.3125 * yp4 * (21.0 - 9.0 * n2 + ci2 * (-270.0 + 126.0 * n2 +
  1061.                                                            ci2 * (385.0 - 189.0 * n2)));
  1062.             // right ascension of ascending node
  1063.             dsh = (-3.0 * yp2 +
  1064.                    0.375 * yp22 * (-5.0 + n * (12.0 + 9.0 * n) -
  1065.                                    ci2 * (35.0 + n * (36.0 + 5.0 * n))) +
  1066.                    1.25 * yp4 * (5.0 - 3.0 * n2) * (3.0 - 7.0 * ci2)) * ci;

  1067.             // secular rates of change due to drag
  1068.             // Eq. 2.41 and Eq. 2.45 of Phipps' 1992 thesis
  1069.             final double coef = -4.0 / (3.0 * n0 * (1 + dsl));
  1070.             aRate = coef * app;
  1071.             eRate = coef * epp * n2;

  1072.             // singular term 1/(1 - 5 * cos²(I")) replaced by T2 function
  1073.             final double t2 = T2(ci);

  1074.             // factors for long periodic corrections
  1075.             final double fs12 = yp3 / yp2;
  1076.             final double fs13 = 10. * yp4 / (3. * yp2);
  1077.             final double fs14 = yp5 / yp2;

  1078.             final double ci2Xt2 = ci2 * t2;
  1079.             final double cA = 1. - ci2 * (11. +  40. * ci2Xt2);
  1080.             final double cB = 1. - ci2 * ( 3. +   8. * ci2Xt2);
  1081.             final double cC = 1. - ci2 * ( 9. +  24. * ci2Xt2);
  1082.             final double cD = 1. - ci2 * ( 5. +  16. * ci2Xt2);
  1083.             final double cE = 1. - ci2 * (33. + 200. * ci2Xt2);
  1084.             final double cF = 1. - ci2 * ( 9. +  40. * ci2Xt2);

  1085.             final double p5p   = 1. + ci2Xt2 * (8. + 20 * ci2Xt2);
  1086.             final double p5p2  = 1. +  2. * p5p;
  1087.             final double p5p4  = 1. +  4. * p5p;
  1088.             final double p5p10 = 1. + 10. * p5p;

  1089.             final double e2X3P4  = 4. + 3. * epp2;
  1090.             final double ciO1Pci = ci / (1. + ci);

  1091.             final double q1 = 0.125 * (yp2 * cA - fs13 * cB);
  1092.             final double q2 = 0.125 * epp2 * ci * (yp2 * p5p10 - fs13 * p5p2);
  1093.             final double q5 = 0.25 * (fs12 + 0.3125 * e2X3P4 * fs14 * cC);
  1094.             final double p2 = 0.46875 * p5p2 * epp * ci * si * e2X3P4 * fs14;
  1095.             final double p3 = 0.15625 * epp * si * fs14 * cC;
  1096.             final double kf = 35. / 1152.;
  1097.             final double p4 = kf * epp * fs14 * cD;
  1098.             final double p5 = 2. * kf * epp * epp2 * ci * si * fs14 * p5p4;

  1099.             vle1 = epp * n2 * q1;
  1100.             vle2 = n2 * si * q5;
  1101.             vle3 = -3.0 * epp * n2 * si * p4;

  1102.             vli1 = -epp * q1 / si;
  1103.             vli2 = -epp * ci * q5;
  1104.             vli3 = -3.0 * epp2 * ci * p4;

  1105.             vll2 = vle2 + 3.0 * epp * n2 * p3;

  1106.             vlh1I = -si * q2;
  1107.             vlh2I =  epp * ci * q5 + si * p2;
  1108.             vlh3I = -epp2 * ci * p4 - si * p5;

  1109.             vls1 = (n3 - 1.0) * q1 -
  1110.                    q2 +
  1111.                    25.0 * epp2 * ci2 * ci2Xt2 * ci2Xt2 * (yp2 - 0.2 * fs13) -
  1112.                    0.0625 * epp2 * (yp2 * cE - fs13 * cF);

  1113.             vls2 = epp * si * (t8 + ciO1Pci) * q5 +
  1114.                    (11.0 + 3.0 * (epp2 - n3)) * p3 +
  1115.                    (1.0 - ci) * p2;

  1116.             vls3 = si * p4 * (3.0 * (n3 - 1.0) - epp2 * (2.0 + ciO1Pci)) -
  1117.                    (1.0 - ci) * p5;
  1118.         }

  1119.         /**
  1120.          * Get true anomaly from mean anomaly.
  1121.          * @param lM the mean anomaly (rad)
  1122.          * @param ecc the eccentricity
  1123.          * @return the true anomaly (rad)
  1124.          */
  1125.         private UnivariateDerivative1 getTrueAnomaly(final UnivariateDerivative1 lM,
  1126.                                                      final UnivariateDerivative1 ecc) {
  1127.             // reduce M to [-PI PI] interval
  1128.             final double reducedM = MathUtils.normalizeAngle(lM.getValue(), 0.);

  1129.             // compute the true anomaly
  1130.             UnivariateDerivative1 lV = FieldKeplerianAnomalyUtility.ellipticMeanToTrue(ecc, lM);

  1131.             // expand the result back to original range
  1132.             lV = lV.add(lM.getValue() - reducedM);

  1133.             // Returns the true anomaly
  1134.             return lV;
  1135.         }

  1136.         /**
  1137.          * This method is used in Brouwer-Lyddane model to avoid singularity at the
  1138.          * critical inclination (i = 63.4°).
  1139.          * <p>
  1140.          * This method, based on Warren Phipps's 1992 thesis (Eq. 2.47 and 2.48),
  1141.          * approximate the factor (1.0 - 5.0 * cos²(i))<sup>-1</sup> (causing the singularity)
  1142.          * by a function, named T2 in the thesis.
  1143.          * </p>
  1144.          * @param cosI cosine of the mean inclination
  1145.          * @return an approximation of (1.0 - 5.0 * cos²(i))<sup>-1</sup> term
  1146.          */
  1147.         private double T2(final double cosI) {

  1148.             // X = 1.0 - 5.0 * cos²(i)
  1149.             final double x  = 1.0 - 5.0 * cosI * cosI;
  1150.             final double x2 = x * x;

  1151.             // Eq. 2.48
  1152.             double sum = 0.0;
  1153.             for (int i = 0; i <= 12; i++) {
  1154.                 final double sign = i % 2 == 0 ? +1.0 : -1.0;
  1155.                 sum += sign * FastMath.pow(BETA, i) * FastMath.pow(x2, i) / CombinatoricsUtils.factorialDouble(i + 1);
  1156.             }

  1157.             // Right term of equation 2.47
  1158.             double product = 1.0;
  1159.             for (int i = 0; i <= 10; i++) {
  1160.                 product *= 1 + FastMath.exp(FastMath.scalb(-1.0, i) * BETA * x2);
  1161.             }

  1162.             // Return (Eq. 2.47)
  1163.             return BETA * x * sum * product;
  1164.         }

  1165.         /** Extrapolate an orbit up to a specific target date.
  1166.          * @param date target date for the orbit
  1167.          * @return propagated parameters
  1168.          */
  1169.         public KeplerianOrbit propagateParameters(final AbsoluteDate date) {

  1170.             // Empirical drag coefficient M2
  1171.             final double m2 = getM2();

  1172.             // Keplerian evolution
  1173.             final UnivariateDerivative1 dt  = new UnivariateDerivative1(date.durationFrom(mean.getDate()), 1.0);
  1174.             final UnivariateDerivative1 not = dt.multiply(n0);

  1175.             final UnivariateDerivative1 dtM2  = dt.multiply(m2);
  1176.             final UnivariateDerivative1 dt2M2 = dt.multiply(dtM2);

  1177.             // Secular corrections
  1178.             // -------------------

  1179.             // semi-major axis (with drag Eq. 2.41 of Phipps' 1992 thesis)
  1180.             final UnivariateDerivative1 app = dtM2.multiply(aRate).add(mean.getA());

  1181.             // eccentricity  (with drag Eq. 2.45 of Phipps' 1992 thesis) reduced to [0, 1[
  1182.             final UnivariateDerivative1 tmp = dtM2.multiply(eRate).add(mean.getE());
  1183.             final UnivariateDerivative1 epp = tmp.withValue(FastMath.max(0., FastMath.min(tmp.getValue(), MAX_ECC)));

  1184.             // argument of perigee
  1185.             final double gppVal = mean.getPerigeeArgument() + dsg * not.getValue();
  1186.             final UnivariateDerivative1 gpp = new UnivariateDerivative1(MathUtils.normalizeAngle(gppVal, 0.),
  1187.                                                                         dsg * n0);

  1188.             // longitude of ascending node
  1189.             final double hppVal = mean.getRightAscensionOfAscendingNode() + dsh * not.getValue();
  1190.             final UnivariateDerivative1 hpp = new UnivariateDerivative1(MathUtils.normalizeAngle(hppVal, 0.),
  1191.                                                                         dsh * n0);

  1192.             // mean anomaly (with drag Eq. 2.38 of Phipps' 1992 thesis)
  1193.             final double lppVal = mean.getMeanAnomaly() + (1. + dsl) * not.getValue() + dt2M2.getValue();
  1194.             final double dlppdt = (1. + dsl) * n0 + 2.0 * dtM2.getValue();
  1195.             final UnivariateDerivative1 lpp = new UnivariateDerivative1(MathUtils.normalizeAngle(lppVal, 0.),
  1196.                                                                         dlppdt);

  1197.             // Long period corrections
  1198.             //------------------------
  1199.             final FieldSinCos<UnivariateDerivative1> scgpp = gpp.sinCos();
  1200.             final UnivariateDerivative1 cgpp  = scgpp.cos();
  1201.             final UnivariateDerivative1 sgpp  = scgpp.sin();
  1202.             final FieldSinCos<UnivariateDerivative1> sc2gpp = gpp.multiply(2).sinCos();
  1203.             final UnivariateDerivative1 c2gpp  = sc2gpp.cos();
  1204.             final UnivariateDerivative1 s2gpp  = sc2gpp.sin();
  1205.             final FieldSinCos<UnivariateDerivative1> sc3gpp = gpp.multiply(3).sinCos();
  1206.             final UnivariateDerivative1 c3gpp  = sc3gpp.cos();
  1207.             final UnivariateDerivative1 s3gpp  = sc3gpp.sin();

  1208.             // δ1e
  1209.             final UnivariateDerivative1 d1e = c2gpp.multiply(vle1).
  1210.                                               add(sgpp.multiply(vle2)).
  1211.                                               add(s3gpp.multiply(vle3));

  1212.             // δ1I
  1213.             UnivariateDerivative1 d1I = sgpp.multiply(vli2).
  1214.                                         add(s3gpp.multiply(vli3));
  1215.             // Pseudo singular term, not to add if Ipp is zero
  1216.             if (Double.isFinite(vli1)) {
  1217.                 d1I = d1I.add(c2gpp.multiply(vli1));
  1218.             }

  1219.             // e"δ1l
  1220.             final UnivariateDerivative1 eppd1l = s2gpp.multiply(vle1).
  1221.                                                  subtract(cgpp.multiply(vll2)).
  1222.                                                  subtract(c3gpp.multiply(vle3)).
  1223.                                                  multiply(n);

  1224.             // δ1h
  1225.             final UnivariateDerivative1 sIppd1h = s2gpp.multiply(vlh1I).
  1226.                                                   add(cgpp.multiply(vlh2I)).
  1227.                                                   add(c3gpp.multiply(vlh3I));

  1228.             // δ1z = δ1l + δ1g + δ1h
  1229.             final UnivariateDerivative1 d1z = s2gpp.multiply(vls1).
  1230.                                               add(cgpp.multiply(vls2)).
  1231.                                               add(c3gpp.multiply(vls3));

  1232.             // Short period corrections
  1233.             // ------------------------

  1234.             // true anomaly
  1235.             final UnivariateDerivative1 fpp = getTrueAnomaly(lpp, epp);
  1236.             final FieldSinCos<UnivariateDerivative1> scfpp = fpp.sinCos();
  1237.             final UnivariateDerivative1 cfpp = scfpp.cos();
  1238.             final UnivariateDerivative1 sfpp = scfpp.sin();

  1239.             // e"sin(f')
  1240.             final UnivariateDerivative1 eppsfpp = epp.multiply(sfpp);
  1241.             // e"cos(f')
  1242.             final UnivariateDerivative1 eppcfpp = epp.multiply(cfpp);
  1243.             // 1 + e"cos(f')
  1244.             final UnivariateDerivative1 eppcfppP1 = eppcfpp.add(1.);
  1245.             // 2 + e"cos(f')
  1246.             final UnivariateDerivative1 eppcfppP2 = eppcfpp.add(2.);
  1247.             // 3 + e"cos(f')
  1248.             final UnivariateDerivative1 eppcfppP3 = eppcfpp.add(3.);
  1249.             // (1 + e"cos(f'))³
  1250.             final UnivariateDerivative1 eppcfppP1_3 = eppcfppP1.square().multiply(eppcfppP1);

  1251.             // 2g"
  1252.             final UnivariateDerivative1 g2 = gpp.multiply(2);

  1253.             // 2g" + f"
  1254.             final UnivariateDerivative1 g2f = g2.add(fpp);
  1255.             final FieldSinCos<UnivariateDerivative1> sc2gf = g2f.sinCos();
  1256.             final UnivariateDerivative1 c2gf = sc2gf.cos();
  1257.             final UnivariateDerivative1 s2gf = sc2gf.sin();
  1258.             final UnivariateDerivative1 eppc2gf = epp.multiply(c2gf);
  1259.             final UnivariateDerivative1 epps2gf = epp.multiply(s2gf);

  1260.             // 2g" + 2f"
  1261.             final UnivariateDerivative1 g2f2 = g2.add(fpp.multiply(2));
  1262.             final FieldSinCos<UnivariateDerivative1> sc2g2f = g2f2.sinCos();
  1263.             final UnivariateDerivative1 c2g2f = sc2g2f.cos();
  1264.             final UnivariateDerivative1 s2g2f = sc2g2f.sin();

  1265.             // 2g" + 3f"
  1266.             final UnivariateDerivative1 g2f3 = g2.add(fpp.multiply(3));
  1267.             final FieldSinCos<UnivariateDerivative1> sc2g3f = g2f3.sinCos();
  1268.             final UnivariateDerivative1 c2g3f = sc2g3f.cos();
  1269.             final UnivariateDerivative1 s2g3f = sc2g3f.sin();

  1270.             // e"cos(2g" + 3f")
  1271.             final UnivariateDerivative1 eppc2g3f = epp.multiply(c2g3f);
  1272.             // e"sin(2g" + 3f")
  1273.             final UnivariateDerivative1 epps2g3f = epp.multiply(s2g3f);

  1274.             // f" + e"sin(f") - l"
  1275.             final UnivariateDerivative1 w17 = fpp.add(eppsfpp).subtract(lpp);

  1276.             // ((e"cos(f") + 3)e"cos(f") + 3)cos(f")
  1277.             final UnivariateDerivative1 w20 = cfpp.multiply(eppcfppP3.multiply(eppcfpp).add(3.));

  1278.             // 3sin(2g" + 2f") + 3e"sin(2g" + f") + e"sin(2g" + f")
  1279.             final UnivariateDerivative1 w21 = s2g2f.add(epps2gf).multiply(3).add(epps2g3f);

  1280.             // (1 + e"cos(f"))(2 + e"cos(f"))/η²
  1281.             final UnivariateDerivative1 w22 = eppcfppP1.multiply(eppcfppP2).divide(n2);

  1282.             // sinCos(I"/2)
  1283.             final SinCos sci = FastMath.sinCos(0.5 * mean.getI());
  1284.             final double siO2 = sci.sin();
  1285.             final double ciO2 = sci.cos();

  1286.             // δ2a
  1287.             final UnivariateDerivative1 d2a = app.multiply(yp2 / n2).
  1288.                                                   multiply(eppcfppP1_3.subtract(n3).multiply(ci2X3M1).
  1289.                                                            add(eppcfppP1_3.multiply(c2g2f).multiply(3 * oneMci2)));

  1290.             // δ2e
  1291.             final UnivariateDerivative1 d2e = (w20.add(epp.multiply(t8))).multiply(ci2X3M1).
  1292.                                                add((w20.add(epp.multiply(c2g2f))).multiply(3 * oneMci2)).
  1293.                                                subtract((eppc2gf.multiply(3).add(eppc2g3f)).multiply(n2 * oneMci2)).
  1294.                                               multiply(0.5 * yp2);

  1295.             // δ2I
  1296.             final UnivariateDerivative1 d2I = ((c2g2f.add(eppc2gf)).multiply(3).add(eppc2g3f)).
  1297.                                               multiply(0.5 * yp2 * ci * si);

  1298.             // e"δ2l
  1299.             final UnivariateDerivative1 eppd2l = (w22.add(1).multiply(sfpp).multiply(2 * oneMci2).
  1300.                                                   add((w22.subtract(1).negate().multiply(s2gf)).
  1301.                                                        add(w22.add(1. / 3.).multiply(s2g3f)).
  1302.                                                       multiply(3 * oneMci2))).
  1303.                                                  multiply(0.25 * yp2 * n3).negate();

  1304.             // sinI"δ2h
  1305.             final UnivariateDerivative1 sIppd2h = (w21.subtract(w17.multiply(6))).
  1306.                                                   multiply(0.5 * yp2 * ci * si);

  1307.             // δ2z = δ2l + δ2g + δ2h
  1308.             final UnivariateDerivative1 d2z = (epp.multiply(eppd2l).multiply(t8 - 1.).divide(n3).
  1309.                                                add(w17.multiply(6. * (1. + ci * (2 - 5. * ci)))
  1310.                                                    .subtract(w21.multiply(3. + ci * (2 - 5. * ci))).multiply(0.25 * yp2))).
  1311.                                                negate();

  1312.             // Assembling elements
  1313.             // -------------------

  1314.             // e" + δe
  1315.             final UnivariateDerivative1 de = epp.add(d1e).add(d2e);

  1316.             // e"δl
  1317.             final UnivariateDerivative1 dl = eppd1l.add(eppd2l);

  1318.             // sin(I"/2)δh = sin(I")δh / cos(I"/2) (singular for I" = π, very unlikely)
  1319.             final UnivariateDerivative1 dh = sIppd1h.add(sIppd2h).divide(2. * ciO2);

  1320.             // δI
  1321.             final UnivariateDerivative1 di = d1I.add(d2I).multiply(0.5 * ciO2).add(siO2);

  1322.             // z = l" + g" + h" + δ1z + δ2z
  1323.             final UnivariateDerivative1 z = lpp.add(gpp).add(hpp).add(d1z).add(d2z);

  1324.             // Osculating elements
  1325.             // -------------------

  1326.             // Semi-major axis
  1327.             final UnivariateDerivative1 a = app.add(d2a);

  1328.             // Eccentricity
  1329.             final UnivariateDerivative1 e = FastMath.sqrt(de.square().add(dl.square()));

  1330.             // Mean anomaly
  1331.             final FieldSinCos<UnivariateDerivative1> sclpp = lpp.sinCos();
  1332.             final UnivariateDerivative1 clpp = sclpp.cos();
  1333.             final UnivariateDerivative1 slpp = sclpp.sin();
  1334.             final UnivariateDerivative1 l = FastMath.atan2(de.multiply(slpp).add(dl.multiply(clpp)),
  1335.                                                            de.multiply(clpp).subtract(dl.multiply(slpp)));

  1336.             // Inclination
  1337.             final UnivariateDerivative1 i = FastMath.acos(di.square().add(dh.square()).multiply(2).negate().add(1.));

  1338.             // Longitude of ascending node
  1339.             final FieldSinCos<UnivariateDerivative1> schpp = hpp.sinCos();
  1340.             final UnivariateDerivative1 chpp = schpp.cos();
  1341.             final UnivariateDerivative1 shpp = schpp.sin();
  1342.             final UnivariateDerivative1 h = FastMath.atan2(di.multiply(shpp).add(dh.multiply(chpp)),
  1343.                                                            di.multiply(chpp).subtract(dh.multiply(shpp)));

  1344.             // Argument of perigee
  1345.             final UnivariateDerivative1 g = z.subtract(l).subtract(h);

  1346.             // Return a Keplerian orbit
  1347.             return new KeplerianOrbit(a.getValue(), e.getValue(), i.getValue(),
  1348.                                       g.getValue(), h.getValue(), l.getValue(),
  1349.                                       a.getFirstDerivative(), e.getFirstDerivative(), i.getFirstDerivative(),
  1350.                                       g.getFirstDerivative(), h.getFirstDerivative(), l.getFirstDerivative(),
  1351.                                       PositionAngleType.MEAN, mean.getFrame(), date, mu);

  1352.         }

  1353.     }

  1354.     /** {@inheritDoc} */
  1355.     protected double getMass(final AbsoluteDate date) {
  1356.         return models.get(date).mass;
  1357.     }

  1358. }