FieldGnssPropagator.java

  1. /* Copyright 2022-2025 Luc Maisonobe
  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.gnss;

  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.Field;
  20. import org.hipparchus.analysis.differentiation.FieldGradient;
  21. import org.hipparchus.analysis.differentiation.FieldUnivariateDerivative2;
  22. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  23. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  24. import org.hipparchus.linear.FieldMatrix;
  25. import org.hipparchus.linear.FieldQRDecomposition;
  26. import org.hipparchus.linear.FieldVector;
  27. import org.hipparchus.linear.MatrixUtils;
  28. import org.hipparchus.util.FastMath;
  29. import org.hipparchus.util.FieldSinCos;
  30. import org.hipparchus.util.MathArrays;
  31. import org.orekit.attitudes.AttitudeProvider;
  32. import org.orekit.attitudes.FieldAttitude;
  33. import org.orekit.frames.Frame;
  34. import org.orekit.orbits.FieldCartesianOrbit;
  35. import org.orekit.orbits.FieldKeplerianAnomalyUtility;
  36. import org.orekit.orbits.FieldKeplerianOrbit;
  37. import org.orekit.orbits.FieldOrbit;
  38. import org.orekit.orbits.PositionAngleType;
  39. import org.orekit.propagation.FieldSpacecraftState;
  40. import org.orekit.propagation.analytical.FieldAbstractAnalyticalPropagator;
  41. import org.orekit.propagation.analytical.gnss.data.FieldGnssOrbitalElements;
  42. import org.orekit.propagation.analytical.gnss.data.GNSSOrbitalElements;
  43. import org.orekit.time.FieldAbsoluteDate;
  44. import org.orekit.utils.FieldPVCoordinates;
  45. import org.orekit.utils.ParameterDriver;

  46. import java.util.List;

  47. /** Common handling of {@link FieldAbstractAnalyticalPropagator} methods for GNSS propagators.
  48.  * <p>
  49.  * This class allows to provide easily a subset of {@link FieldAbstractAnalyticalPropagator} methods
  50.  * for specific GNSS propagators.
  51.  * </p>
  52.  * @author Pascal Parraud
  53.  * @author Luc Maisonobe
  54.  * @param <T> type of the field elements
  55.  * @since 13.0
  56.  */
  57. public class FieldGnssPropagator<T extends CalculusFieldElement<T>> extends FieldAbstractAnalyticalPropagator<T> {

  58.     /** Maximum number of iterations for internal loops. */
  59.     private static final int MAX_ITER = 100;

  60.     /** Tolerance on position for rebuilding orbital elements from initial state. */
  61.     private static final double TOL_P = 1.0e-6;

  62.     /** Tolerance on velocity for rebuilding orbital elements from initial state. */
  63.     private static final double TOL_V = 1.0e-9;

  64.     /** Number of free parameters for orbital elements. */
  65.     private static final int FREE_PARAMETERS = 6;

  66.     /** Convergence parameter. */
  67.     private static final double EPS = 1.0e-12;

  68.     /** The GNSS propagation model used. */
  69.     private FieldGnssOrbitalElements<T, ?> orbitalElements;

  70.     /** The ECI frame used for GNSS propagation. */
  71.     private final Frame eci;

  72.     /** The ECEF frame used for GNSS propagation. */
  73.     private final Frame ecef;

  74.     /**
  75.      * Build a new instance.
  76.      * @param orbitalElements GNSS orbital elements
  77.      * @param eci Earth Centered Inertial frame
  78.      * @param ecef Earth Centered Earth Fixed frame
  79.      * @param provider Attitude provider
  80.      * @param mass Satellite mass (kg)
  81.      */
  82.     FieldGnssPropagator(final FieldGnssOrbitalElements<T, ?> orbitalElements,
  83.                         final Frame eci, final Frame ecef,
  84.                         final AttitudeProvider provider, final T mass) {
  85.         super(orbitalElements.getDate().getField(), provider);
  86.         // Stores the GNSS orbital elements
  87.         this.orbitalElements = orbitalElements;
  88.        // Sets the Earth Centered Inertial frame
  89.         this.eci  = eci;
  90.         // Sets the Earth Centered Earth Fixed frame
  91.         this.ecef = ecef;
  92.         // Sets initial state
  93.         final FieldOrbit<T> orbit = propagateOrbit(orbitalElements.getDate(), defaultParameters());
  94.         final FieldAttitude<T> attitude = provider.getAttitude(orbit, orbit.getDate(), orbit.getFrame());

  95.         // calling the method from base class because the one overridden below recomputes the orbital elements
  96.         super.resetInitialState(new FieldSpacecraftState<>(orbit, attitude).withMass(mass));

  97.     }

  98.     /**
  99.      * Build a new instance from an initial state.
  100.      * <p>
  101.      * The Keplerian elements already present in the {@code nonKeplerianElements} argument
  102.      * will be ignored as it is the {@code initialState} argument that will be used to
  103.      * build the complete orbital elements of the propagator
  104.      * </p>
  105.      * @param initialState         initial state
  106.      * @param nonKeplerianElements non-Keplerian orbital elements (the Keplerian orbital elements will be ignored)
  107.      * @param ecef                 Earth Centered Earth Fixed frame
  108.      * @param provider             attitude provider
  109.      * @param mass                 spacecraft mass
  110.      */
  111.     FieldGnssPropagator(final FieldSpacecraftState<T> initialState,
  112.                         final FieldGnssOrbitalElements<T, ?> nonKeplerianElements,
  113.                         final Frame ecef, final AttitudeProvider provider, final T mass) {
  114.         this(buildOrbitalElements(initialState, nonKeplerianElements, ecef, provider, mass),
  115.              initialState.getFrame(), ecef, provider, initialState.getMass());
  116.     }

  117.     /** Build default parameters.
  118.      * @return default parameters
  119.      */
  120.     private T[] defaultParameters() {
  121.         final T[] parameters = MathArrays.buildArray(orbitalElements.getDate().getField(), GNSSOrbitalElements.SIZE);
  122.         parameters[GNSSOrbitalElements.TIME_INDEX]      = getMU().newInstance(orbitalElements.getTime());
  123.         parameters[GNSSOrbitalElements.I_DOT_INDEX]     = getMU().newInstance(orbitalElements.getIDot());
  124.         parameters[GNSSOrbitalElements.OMEGA_DOT_INDEX] = getMU().newInstance(orbitalElements.getOmegaDot());
  125.         parameters[GNSSOrbitalElements.CUC_INDEX]       = getMU().newInstance(orbitalElements.getCuc());
  126.         parameters[GNSSOrbitalElements.CUS_INDEX]       = getMU().newInstance(orbitalElements.getCus());
  127.         parameters[GNSSOrbitalElements.CRC_INDEX]       = getMU().newInstance(orbitalElements.getCrc());
  128.         parameters[GNSSOrbitalElements.CRS_INDEX]       = getMU().newInstance(orbitalElements.getCrs());
  129.         parameters[GNSSOrbitalElements.CIC_INDEX]       = getMU().newInstance(orbitalElements.getCic());
  130.         parameters[GNSSOrbitalElements.CIS_INDEX]       = getMU().newInstance(orbitalElements.getCis());
  131.         return parameters;
  132.     }

  133.     /** {@inheritDoc} */
  134.     @Override
  135.     public List<ParameterDriver> getParametersDrivers() {
  136.         return orbitalElements.getParametersDrivers();
  137.     }

  138.     /**
  139.      * Gets the Earth Centered Inertial frame used to propagate the orbit.
  140.      *
  141.      * @return the ECI frame
  142.      */
  143.     public Frame getECI() {
  144.         return eci;
  145.     }

  146.     /**
  147.      * Gets the Earth Centered Earth Fixed frame used to propagate GNSS orbits according to the
  148.      * Interface Control Document.
  149.      *
  150.      * @return the ECEF frame
  151.      */
  152.     public Frame getECEF() {
  153.         return ecef;
  154.     }

  155.     /**
  156.      * Gets the Earth gravity coefficient used for GNSS propagation.
  157.      *
  158.      * @return the Earth gravity coefficient.
  159.      */
  160.     public T getMU() {
  161.         return orbitalElements.getMu();
  162.     }

  163.     /** {@inheritDoc} */
  164.     @Override
  165.     public FieldOrbit<T> propagateOrbit(final FieldAbsoluteDate<T> date,
  166.                                         final T[] parameters) {
  167.         // Gets the PVCoordinates in ECEF frame
  168.         final FieldPVCoordinates<T> pvaInECEF = propagateInEcef(date, parameters);
  169.         // Transforms the PVCoordinates to ECI frame
  170.         final FieldPVCoordinates<T> pvaInECI = ecef.getTransformTo(eci, date).transformPVCoordinates(pvaInECEF);
  171.         // Returns the Cartesian orbit
  172.         return new FieldCartesianOrbit<>(pvaInECI, eci, date, getMU());
  173.     }

  174.     /**
  175.      * Gets the PVCoordinates of the GNSS SV in {@link #getECEF() ECEF frame}.
  176.      *
  177.      * <p>The algorithm uses automatic differentiation to compute velocity and
  178.      * acceleration.</p>
  179.      *
  180.      * @param date the computation date
  181.      * @param parameters propagation parameters
  182.      * @return the GNSS SV PVCoordinates in {@link #getECEF() ECEF frame}
  183.      */
  184.     public FieldPVCoordinates<T> propagateInEcef(final FieldAbsoluteDate<T> date, final T[] parameters) {
  185.         // Duration from GNSS ephemeris Reference date
  186.         final FieldUnivariateDerivative2<T> tk = new FieldUnivariateDerivative2<>(getTk(date),
  187.                                                                                   date.getField().getOne(),
  188.                                                                                   date.getField().getZero());

  189.         // Semi-major axis
  190.         final FieldUnivariateDerivative2<T> ak = tk.multiply(orbitalElements.getADot()).add(orbitalElements.getSma());
  191.         // Mean motion
  192.         final FieldUnivariateDerivative2<T> nA = tk.multiply(orbitalElements.getDeltaN0Dot().multiply(0.5)).
  193.                                                  add(orbitalElements.getDeltaN0()).
  194.                                                  add(orbitalElements.getMeanMotion0());
  195.         // Mean anomaly
  196.         final FieldUnivariateDerivative2<T> mk = tk.multiply(nA).add(orbitalElements.getM0());
  197.         // Eccentric Anomaly
  198.         final FieldUnivariateDerivative2<T> e  = tk.newInstance(orbitalElements.getE());
  199.         final FieldUnivariateDerivative2<T> ek = FieldKeplerianAnomalyUtility.ellipticMeanToEccentric(e, mk);
  200.         // True Anomaly
  201.         final FieldUnivariateDerivative2<T> vk = FieldKeplerianAnomalyUtility.ellipticEccentricToTrue(e, ek);
  202.         // Argument of Latitude
  203.         final FieldUnivariateDerivative2<T> phik    = vk.add(orbitalElements.getPa());
  204.         final FieldSinCos<FieldUnivariateDerivative2<T>> cs2phi = FastMath.sinCos(phik.multiply(2));
  205.         // Argument of Latitude Correction
  206.         final FieldUnivariateDerivative2<T> dphik = cs2phi.cos().multiply(parameters[GNSSOrbitalElements.CUC_INDEX]).
  207.                                                 add(cs2phi.sin().multiply(parameters[GNSSOrbitalElements.CUS_INDEX]));
  208.         // Radius Correction
  209.         final FieldUnivariateDerivative2<T> drk = cs2phi.cos().multiply(parameters[GNSSOrbitalElements.CRC_INDEX]).
  210.                                               add(cs2phi.sin().multiply(parameters[GNSSOrbitalElements.CRS_INDEX]));
  211.         // Inclination Correction
  212.         final FieldUnivariateDerivative2<T> dik = cs2phi.cos().multiply(parameters[GNSSOrbitalElements.CIC_INDEX]).
  213.                                               add(cs2phi.sin().multiply(parameters[GNSSOrbitalElements.CIS_INDEX]));
  214.         // Corrected Argument of Latitude
  215.         final FieldSinCos<FieldUnivariateDerivative2<T>> csuk = FastMath.sinCos(phik.add(dphik));
  216.         // Corrected Radius
  217.         final FieldUnivariateDerivative2<T> rk = ek.cos().multiply(e.negate()).add(1).multiply(ak).add(drk);
  218.         // Corrected Inclination
  219.         final FieldUnivariateDerivative2<T> ik  = tk.multiply(parameters[GNSSOrbitalElements.I_DOT_INDEX]).
  220.                                                   add(orbitalElements.getI0()).add(dik);
  221.         final FieldSinCos<FieldUnivariateDerivative2<T>> csik = FastMath.sinCos(ik);
  222.         // Positions in orbital plane
  223.         final FieldUnivariateDerivative2<T> xk = csuk.cos().multiply(rk);
  224.         final FieldUnivariateDerivative2<T> yk = csuk.sin().multiply(rk);
  225.         // Corrected longitude of ascending node
  226.         final FieldSinCos<FieldUnivariateDerivative2<T>> csomk =
  227.             FastMath.sinCos(tk.multiply(parameters[GNSSOrbitalElements.OMEGA_DOT_INDEX].
  228.                             subtract(orbitalElements.getAngularVelocity())).
  229.                             add(orbitalElements.getOmega0()).
  230.                             subtract(parameters[GNSSOrbitalElements.TIME_INDEX].multiply(orbitalElements.getAngularVelocity())));
  231.         // returns the Earth-fixed coordinates
  232.         final FieldVector3D<FieldUnivariateDerivative2<T>> positionWithDerivatives =
  233.                         new FieldVector3D<>(xk.multiply(csomk.cos()).subtract(yk.multiply(csomk.sin()).multiply(csik.cos())),
  234.                                             xk.multiply(csomk.sin()).add(yk.multiply(csomk.cos()).multiply(csik.cos())),
  235.                                             yk.multiply(csik.sin()));
  236.         return new FieldPVCoordinates<>(positionWithDerivatives);

  237.     }

  238.     /**
  239.      * Gets the duration from GNSS Reference epoch.
  240.      * <p>This takes the GNSS week roll-over into account.</p>
  241.      * @param date the considered date
  242.      * @return the duration from GNSS orbit Reference epoch (s)
  243.      */
  244.     private T getTk(final FieldAbsoluteDate<T> date) {
  245.         // Time from ephemeris reference epoch
  246.         T tk = date.durationFrom(orbitalElements.getDate());
  247.         // Adjusts the time to take roll over week into account
  248.         while (tk.getReal() > 0.5 * orbitalElements.getCycleDuration()) {
  249.             tk = tk.subtract(orbitalElements.getCycleDuration());
  250.         }
  251.         while (tk.getReal() < -0.5 * orbitalElements.getCycleDuration()) {
  252.             tk = tk.add(orbitalElements.getCycleDuration());
  253.         }
  254.         // Returns the time from ephemeris reference epoch
  255.         return tk;
  256.     }

  257.     /** {@inheritDoc} */
  258.     @Override
  259.     public Frame getFrame() {
  260.         return eci;
  261.     }

  262.     /** {@inheritDoc} */
  263.     @Override
  264.     protected T getMass(final FieldAbsoluteDate<T> date) {
  265.         return getInitialState().getMass();
  266.     }

  267.     /** {@inheritDoc} */
  268.     @Override
  269.     public void resetInitialState(final FieldSpacecraftState<T> state) {
  270.         orbitalElements = buildOrbitalElements(state, orbitalElements, ecef, getAttitudeProvider(), state.getMass());
  271.         final FieldOrbit<T> orbit = propagateOrbit(orbitalElements.getDate(), defaultParameters());
  272.         final FieldAttitude<T> attitude = getAttitudeProvider().getAttitude(orbit, orbit.getDate(), orbit.getFrame());
  273.         super.resetInitialState(new FieldSpacecraftState<>(orbit, attitude).withMass(state.getMass()));
  274.     }

  275.     /** {@inheritDoc} */
  276.     @Override
  277.     protected void resetIntermediateState(final FieldSpacecraftState<T> state, final boolean forward) {
  278.         resetInitialState(state);
  279.     }

  280.     /**
  281.      * Build orbital elements from initial state.
  282.      * <p>
  283.      * This method is roughly the inverse of {@link #propagateInEcef(FieldAbsoluteDate, CalculusFieldElement[])},
  284.      * except it starts from a state in inertial frame
  285.      * </p>
  286.      *
  287.      * @param <T> type of the field elements
  288.      * @param initialState    initial state
  289.      * @param nonKeplerianElements non-Keplerian orbital elements (the Keplerian orbital elements will be overridden)
  290.      * @param ecef            Earth Centered Earth Fixed frame
  291.      * @param provider        attitude provider
  292.      * @param mass            satellite mass (kg)
  293.      * @return orbital elements that generate the {@code initialState} when used with a propagator
  294.      */
  295.     private static <T extends CalculusFieldElement<T>> FieldGnssOrbitalElements<T, ?>
  296.         buildOrbitalElements(final FieldSpacecraftState<T> initialState,
  297.                              final FieldGnssOrbitalElements<T, ?> nonKeplerianElements,
  298.                              final Frame ecef, final AttitudeProvider provider,
  299.                              final T mass) {

  300.         final Field<T> field = initialState.getDate().getField();

  301.         // get approximate initial orbit
  302.         final Frame frozenEcef = ecef.getFrozenFrame(initialState.getFrame(),
  303.                                                      initialState.getDate().toAbsoluteDate(),
  304.                                                      "frozen");
  305.         final FieldKeplerianOrbit<T> orbit = approximateInitialOrbit(initialState, nonKeplerianElements, frozenEcef);

  306.         // refine orbit using simple differential correction to reach target PV
  307.         final FieldPVCoordinates<T> targetPV = initialState.getPVCoordinates();
  308.         final FieldGnssOrbitalElements<FieldGradient<T>, ?> gElements = convert(nonKeplerianElements, orbit);
  309.         for (int i = 0; i < MAX_ITER; ++i) {

  310.             // get position-velocity derivatives with respect to initial orbit
  311.             final FieldGnssPropagator<FieldGradient<T>> gPropagator =
  312.                 new FieldGnssPropagator<>(gElements, initialState.getFrame(), ecef, provider,
  313.                                           gElements.getMu().newInstance(mass));
  314.             final FieldPVCoordinates<FieldGradient<T>> gPV = gPropagator.getInitialState().getPVCoordinates();

  315.             // compute Jacobian matrix
  316.             final FieldMatrix<T> jacobian = MatrixUtils.createFieldMatrix(field, FREE_PARAMETERS, FREE_PARAMETERS);
  317.             jacobian.setRow(0, gPV.getPosition().getX().getGradient());
  318.             jacobian.setRow(1, gPV.getPosition().getY().getGradient());
  319.             jacobian.setRow(2, gPV.getPosition().getZ().getGradient());
  320.             jacobian.setRow(3, gPV.getVelocity().getX().getGradient());
  321.             jacobian.setRow(4, gPV.getVelocity().getY().getGradient());
  322.             jacobian.setRow(5, gPV.getVelocity().getZ().getGradient());

  323.             // linear correction to get closer to target PV
  324.             final FieldVector<T> residuals = MatrixUtils.createFieldVector(field, FREE_PARAMETERS);
  325.             residuals.setEntry(0, targetPV.getPosition().getX().subtract(gPV.getPosition().getX().getValue()));
  326.             residuals.setEntry(1, targetPV.getPosition().getY().subtract(gPV.getPosition().getY().getValue()));
  327.             residuals.setEntry(2, targetPV.getPosition().getZ().subtract(gPV.getPosition().getZ().getValue()));
  328.             residuals.setEntry(3, targetPV.getVelocity().getX().subtract(gPV.getVelocity().getX().getValue()));
  329.             residuals.setEntry(4, targetPV.getVelocity().getY().subtract(gPV.getVelocity().getY().getValue()));
  330.             residuals.setEntry(5, targetPV.getVelocity().getZ().subtract(gPV.getVelocity().getZ().getValue()));
  331.             final FieldVector<T> correction = new FieldQRDecomposition<>(jacobian, field.getZero().newInstance(EPS)).
  332.                                               getSolver().
  333.                                               solve(residuals);

  334.             // update initial orbit
  335.             gElements.setSma(gElements.getSma().add(correction.getEntry(0)));
  336.             gElements.setE(gElements.getE().add(correction.getEntry(1)));
  337.             gElements.setI0(gElements.getI0().add(correction.getEntry(2)));
  338.             gElements.setPa(gElements.getPa().add(correction.getEntry(3)));
  339.             gElements.setOmega0(gElements.getOmega0().add(correction.getEntry(4)));
  340.             gElements.setM0(gElements.getM0().add(correction.getEntry(5)));

  341.             final double deltaP = FastMath.sqrt(residuals.getEntry(0).getReal() * residuals.getEntry(0).getReal() +
  342.                                                 residuals.getEntry(1).getReal() * residuals.getEntry(1).getReal() +
  343.                                                 residuals.getEntry(2).getReal() * residuals.getEntry(2).getReal());
  344.             final double deltaV = FastMath.sqrt(residuals.getEntry(3).getReal() * residuals.getEntry(3).getReal() +
  345.                                                 residuals.getEntry(4).getReal() * residuals.getEntry(4).getReal() +
  346.                                                 residuals.getEntry(5).getReal() * residuals.getEntry(5).getReal());

  347.             if (deltaP < TOL_P && deltaV < TOL_V) {
  348.                 break;
  349.             }

  350.         }

  351.         return gElements.changeField(FieldGradient::getValue);

  352.     }

  353.     /** Compute approximate initial orbit.
  354.      * @param <T> type of the field elements
  355.      * @param initialState         initial state
  356.      * @param nonKeplerianElements non-Keplerian orbital elements (the Keplerian orbital elements will be ignored)
  357.      * @param frozenEcef           inertial frame aligned with Earth Centered Earth Fixed frame at orbit date
  358.      * @return approximate initial orbit that generate a state close to {@code initialState}
  359.      */
  360.     private static <T extends CalculusFieldElement<T>> FieldKeplerianOrbit<T>
  361.         approximateInitialOrbit(final FieldSpacecraftState<T> initialState,
  362.                                 final FieldGnssOrbitalElements<T, ?> nonKeplerianElements,
  363.                                 final Frame frozenEcef) {

  364.         // rotate the state to a frame that is inertial but aligned with Earth frame,
  365.         // as analytical model is expressed in Earth frame
  366.         final FieldPVCoordinates<T> pv = initialState.getPVCoordinates(frozenEcef);
  367.         final FieldVector3D<T> p  = pv.getPosition();
  368.         final FieldVector3D<T>      v  = pv.getVelocity();

  369.         // compute Keplerian orbital parameters
  370.         final T   rk  = p.getNorm();

  371.         // compute orbital plane orientation
  372.         final FieldVector3D<T> normal = pv.getMomentum().normalize();
  373.         final T   cosIk  = normal.getZ();
  374.         final T   ik     = FieldVector3D.angle(normal, Vector3D.PLUS_K);

  375.         // compute position in orbital plane
  376.         final T   q   = FastMath.hypot(normal.getX(), normal.getY());
  377.         final T   cos = normal.getY().negate().divide(q);
  378.         final T   sin =  normal.getX().divide(q);
  379.         final T   xk  =  p.getX().multiply(cos).add(p.getY().multiply(sin));
  380.         final T   yk  = p.getY().multiply(cos).subtract(p.getX().multiply(sin)).divide(cosIk);

  381.         // corrected latitude argument
  382.         final T   uk  = FastMath.atan2(yk, xk);

  383.         // recover latitude argument before correction, using a fixed-point method
  384.         T phi = uk;
  385.         for (int i = 0; i < MAX_ITER; ++i) {
  386.             final T previous = phi;
  387.             final FieldSinCos<T> cs2Phi = FastMath.sinCos(phi.multiply(2));
  388.             phi = uk.subtract(cs2Phi.cos().multiply(nonKeplerianElements.getCuc()).add(cs2Phi.sin().multiply(nonKeplerianElements.getCus())));
  389.             if (FastMath.abs(phi.subtract(previous).getReal()) <= EPS) {
  390.                 break;
  391.             }
  392.         }
  393.         final FieldSinCos<T> cs2phi = FastMath.sinCos(phi.multiply(2));

  394.         // recover plane orientation before correction
  395.         // here, we know that tk = 0 since our orbital elements will be at initial state date
  396.         final T i0  = ik.subtract(cs2phi.cos().multiply(nonKeplerianElements.getCic()).add(cs2phi.sin().multiply(nonKeplerianElements.getCis())));
  397.         final T om0 = FastMath.atan2(sin, cos).
  398.                       add(nonKeplerianElements.getAngularVelocity() * nonKeplerianElements.getTime());

  399.         // recover eccentricity and anomaly
  400.         final T mu = initialState.getOrbit().getMu();
  401.         final T rV2OMu           = rk.multiply(v.getNormSq()).divide(mu);
  402.         final T sma              = rk.divide(rV2OMu.negate().add(2));
  403.         final T eCosE            = rV2OMu.subtract(1);
  404.         final T eSinE            = FieldVector3D.dotProduct(p, v).divide(FastMath.sqrt(mu.multiply(sma)));
  405.         final T e                = FastMath.hypot(eCosE, eSinE);
  406.         final T eccentricAnomaly = FastMath.atan2(eSinE, eCosE);
  407.         final T aop              = phi.subtract(eccentricAnomaly);
  408.         final T meanAnomaly      = FieldKeplerianAnomalyUtility.ellipticEccentricToMean(e, eccentricAnomaly);

  409.         return new FieldKeplerianOrbit<>(sma, e, i0, aop, om0, meanAnomaly, PositionAngleType.MEAN,
  410.                                          PositionAngleType.MEAN, frozenEcef,
  411.                                          initialState.getDate(), mu);

  412.     }

  413.     /** Convert orbital elements to gradient.
  414.      * @param <T> type of the field elements
  415.      * @param elements   primitive double elements
  416.      * @param orbit      Keplerian orbit
  417.      * @return converted elements, set up as gradient relative to Keplerian orbit
  418.      */
  419.     private static <T extends CalculusFieldElement<T>> FieldGnssOrbitalElements<FieldGradient<T>, ?>
  420.         convert(final FieldGnssOrbitalElements<T, ?> elements, final FieldKeplerianOrbit<T> orbit) {

  421.         final FieldGnssOrbitalElements<FieldGradient<T>, ?> gElements =
  422.             elements.changeField(t -> FieldGradient.constant(FREE_PARAMETERS, t));

  423.         // Keplerian parameters
  424.         gElements.setSma(FieldGradient.variable(FREE_PARAMETERS, 0, orbit.getA()));
  425.         gElements.setE(FieldGradient.variable(FREE_PARAMETERS, 1, orbit.getE()));
  426.         gElements.setI0(FieldGradient.variable(FREE_PARAMETERS, 2, orbit.getI()));
  427.         gElements.setPa(FieldGradient.variable(FREE_PARAMETERS, 3, orbit.getPerigeeArgument()));
  428.         gElements.setOmega0(FieldGradient.variable(FREE_PARAMETERS, 4, orbit.getRightAscensionOfAscendingNode()));
  429.         gElements.setM0(FieldGradient.variable(FREE_PARAMETERS, 5, orbit.getMeanAnomaly()));

  430.         return gElements;

  431.     }

  432. }