FieldEcksteinHechlerPropagator.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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 org.hipparchus.Field;
  19. import org.hipparchus.RealFieldElement;
  20. import org.hipparchus.analysis.differentiation.FDSFactory;
  21. import org.hipparchus.analysis.differentiation.FieldDerivativeStructure;
  22. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  23. import org.hipparchus.util.FastMath;
  24. import org.hipparchus.util.MathArrays;
  25. import org.orekit.attitudes.AttitudeProvider;
  26. import org.orekit.errors.OrekitException;
  27. import org.orekit.errors.OrekitMessages;
  28. import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider;
  29. import org.orekit.orbits.FieldCartesianOrbit;
  30. import org.orekit.orbits.FieldCircularOrbit;
  31. import org.orekit.orbits.FieldOrbit;
  32. import org.orekit.orbits.OrbitType;
  33. import org.orekit.orbits.PositionAngle;
  34. import org.orekit.propagation.FieldSpacecraftState;
  35. import org.orekit.time.FieldAbsoluteDate;
  36. import org.orekit.utils.FieldTimeSpanMap;
  37. import org.orekit.utils.TimeStampedFieldPVCoordinates;

  38. /** This class propagates a {@link org.orekit.propagation.FieldSpacecraftState}
  39.  *  using the analytical Eckstein-Hechler model.
  40.  * <p>The Eckstein-Hechler model is suited for near circular orbits
  41.  * (e < 0.1, with poor accuracy between 0.005 and 0.1) and inclination
  42.  * neither equatorial (direct or retrograde) nor critical (direct or
  43.  * retrograde).</p>
  44.  * @see FieldOrbit
  45.  * @author Guylaine Prat
  46.  */
  47. public class FieldEcksteinHechlerPropagator<T extends RealFieldElement<T>> extends FieldAbstractAnalyticalPropagator<T> {

  48.     /** Factory for the derivatives. */
  49.     private final FDSFactory<T> factory;

  50.     /** Initial Eckstein-Hechler model. */
  51.     private FieldEHModel<T> initialModel;

  52.     /** All models. */
  53.     private transient FieldTimeSpanMap<FieldEHModel<T>, T> models;

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

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

  58.     /** Un-normalized zonal coefficients. */
  59.     private double[] ck0;

  60.     /** Build a propagator from FieldOrbit<T> and potential provider.
  61.      * <p>Mass and attitude provider are set to unspecified non-null arbitrary values.</p>
  62.      * @param initialOrbit initial FieldOrbit<T>
  63.      * @param provider for un-normalized zonal coefficients
  64.      * @exception OrekitException if the zonal coefficients cannot be retrieved
  65.      * or if the mean parameters cannot be computed
  66.      */
  67.     public FieldEcksteinHechlerPropagator(final FieldOrbit<T> initialOrbit,
  68.                                           final UnnormalizedSphericalHarmonicsProvider provider)
  69.         throws OrekitException {
  70.         this(initialOrbit, DEFAULT_LAW, initialOrbit.getA().getField().getZero().add(DEFAULT_MASS), provider,
  71.              provider.onDate(initialOrbit.getDate().toAbsoluteDate()));
  72.     }

  73.     /**
  74.      * Private helper constructor.
  75.      * @param initialOrbit initial FieldOrbit<T>
  76.      * @param attitude attitude provider
  77.      * @param mass spacecraft mass
  78.      * @param provider for un-normalized zonal coefficients
  79.      * @param harmonics {@code provider.onDate(initialOrbit.getDate())}
  80.      * @exception OrekitException if the zonal coefficients cannot be retrieved
  81.      */
  82.     public FieldEcksteinHechlerPropagator(final FieldOrbit<T> initialOrbit,
  83.                                           final  AttitudeProvider attitude,
  84.                                           final T mass,
  85.                                           final UnnormalizedSphericalHarmonicsProvider provider,
  86.                                           final UnnormalizedSphericalHarmonicsProvider.UnnormalizedSphericalHarmonics harmonics)
  87.         throws OrekitException {
  88.         this(initialOrbit, attitude,  mass, provider.getAe(), provider.getMu(),
  89.              harmonics.getUnnormalizedCnm(2, 0),
  90.              harmonics.getUnnormalizedCnm(3, 0),
  91.              harmonics.getUnnormalizedCnm(4, 0),
  92.              harmonics.getUnnormalizedCnm(5, 0),
  93.              harmonics.getUnnormalizedCnm(6, 0));
  94.     }

  95.     /** Build a propagator from FieldOrbit<T> and potential.
  96.      * <p>Mass and attitude provider are set to unspecified non-null arbitrary values.</p>
  97.      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
  98.      * are related to both the normalized coefficients
  99.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  100.      *  and the J<sub>n</sub> one as follows:</p>
  101.      * <pre>
  102.      *   C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
  103.      *                      <span style="text-decoration: overline">C</span><sub>n,0</sub>
  104.      *   C<sub>n,0</sub> = -J<sub>n</sub>
  105.      * </pre>
  106.      * @param initialOrbit initial FieldOrbit<T>
  107.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  108.      * @param mu central attraction coefficient (m³/s²)
  109.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  110.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  111.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  112.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  113.      * @param c60 un-normalized zonal coefficient (about -5.41e-7 for Earth)
  114.      * @exception OrekitException if the mean parameters cannot be computed
  115.      * @see org.orekit.utils.Constants
  116.      */
  117.     public FieldEcksteinHechlerPropagator(final FieldOrbit<T> initialOrbit,
  118.                                           final double referenceRadius, final double mu,
  119.                                           final double c20, final double c30, final double c40,
  120.                                           final double c50, final double c60)
  121.         throws OrekitException {
  122.         this(initialOrbit, DEFAULT_LAW, initialOrbit.getDate().getField().getZero().add(DEFAULT_MASS),
  123.              referenceRadius, mu, c20, c30, c40, c50, c60);
  124.     }

  125.     /** Build a propagator from FieldOrbit<T>, mass and potential provider.
  126.      * <p>Attitude law is set to an unspecified non-null arbitrary value.</p>
  127.      * @param initialOrbit initial FieldOrbit<T>
  128.      * @param mass spacecraft mass
  129.      * @param provider for un-normalized zonal coefficients
  130.      * @exception OrekitException if the zonal coefficients cannot be retrieved
  131.      * or if the mean parameters cannot be computed
  132.      */
  133.     public FieldEcksteinHechlerPropagator(final FieldOrbit<T> initialOrbit, final T mass,
  134.                                           final UnnormalizedSphericalHarmonicsProvider provider)
  135.         throws OrekitException {
  136.         this(initialOrbit, DEFAULT_LAW, mass, provider, provider.onDate(initialOrbit.getDate().toAbsoluteDate()));
  137.     }

  138.     /** Build a propagator from FieldOrbit<T>, mass and potential.
  139.      * <p>Attitude law is set to an unspecified non-null arbitrary value.</p>
  140.      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
  141.      * are related to both the normalized coefficients
  142.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  143.      *  and the J<sub>n</sub> one as follows:</p>
  144.      * <pre>
  145.      *   C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
  146.      *                      <span style="text-decoration: overline">C</span><sub>n,0</sub>
  147.      *   C<sub>n,0</sub> = -J<sub>n</sub>
  148.      * </pre>
  149.      * @param initialOrbit initial FieldOrbit<T>
  150.      * @param mass spacecraft mass
  151.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  152.      * @param mu central attraction coefficient (m³/s²)
  153.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  154.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  155.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  156.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  157.      * @param c60 un-normalized zonal coefficient (about -5.41e-7 for Earth)
  158.      * @exception OrekitException if the mean parameters cannot be computed
  159.      */
  160.     public FieldEcksteinHechlerPropagator(final FieldOrbit<T> initialOrbit, final T mass,
  161.                                           final double referenceRadius, final double mu,
  162.                                           final double c20, final double c30, final double c40,
  163.                                           final double c50, final double c60)
  164.         throws OrekitException {
  165.         this(initialOrbit, DEFAULT_LAW, mass, referenceRadius, mu, c20, c30, c40, c50, c60);
  166.     }

  167.     /** Build a propagator from FieldOrbit<T>, attitude provider and potential provider.
  168.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  169.      * @param initialOrbit initial FieldOrbit<T>
  170.      * @param attitudeProv attitude provider
  171.      * @param provider for un-normalized zonal coefficients
  172.      * @exception OrekitException if the zonal coefficients cannot be retrieved
  173.      * or if the mean parameters cannot be computed
  174.      */
  175.     public FieldEcksteinHechlerPropagator(final FieldOrbit<T> initialOrbit,
  176.                                           final AttitudeProvider attitudeProv,
  177.                                           final UnnormalizedSphericalHarmonicsProvider provider)
  178.         throws OrekitException {
  179.         this(initialOrbit, attitudeProv, initialOrbit.getA().getField().getZero().add(DEFAULT_MASS), provider, provider.onDate(initialOrbit.getDate().toAbsoluteDate()));
  180.     }

  181.     /** Build a propagator from FieldOrbit<T>, attitude provider and potential.
  182.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  183.      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
  184.      * are related to both the normalized coefficients
  185.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  186.      *  and the J<sub>n</sub> one as follows:</p>
  187.      * <pre>
  188.      *   C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
  189.      *                     <span style="text-decoration: overline">C</span><sub>n,0</sub>
  190.      *   C<sub>n,0</sub> = -J<sub>n</sub>
  191.      * </pre>
  192.      * @param initialOrbit initial FieldOrbit<T>
  193.      * @param attitudeProv attitude provider
  194.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  195.      * @param mu central attraction coefficient (m³/s²)
  196.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  197.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  198.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  199.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  200.      * @param c60 un-normalized zonal coefficient (about -5.41e-7 for Earth)
  201.      * @exception OrekitException if the mean parameters cannot be computed
  202.      */
  203.     public FieldEcksteinHechlerPropagator(final FieldOrbit<T> initialOrbit,
  204.                                           final AttitudeProvider attitudeProv,
  205.                                           final double referenceRadius, final double mu,
  206.                                           final double c20, final double c30, final double c40,
  207.                                           final double c50, final double c60)
  208.         throws OrekitException {
  209.         this(initialOrbit, attitudeProv, initialOrbit.getDate().getField().getZero().add(DEFAULT_MASS),
  210.              referenceRadius, mu, c20, c30, c40, c50, c60);
  211.     }

  212.     /** Build a propagator from FieldOrbit<T>, attitude provider, mass and potential provider.
  213.      * @param initialOrbit initial FieldOrbit<T>
  214.      * @param attitudeProv attitude provider
  215.      * @param mass spacecraft mass
  216.      * @param provider for un-normalized zonal coefficients
  217.      * @exception OrekitException if the zonal coefficients cannot be retrieved
  218.      * or if the mean parameters cannot be computed
  219.      */
  220.     public FieldEcksteinHechlerPropagator(final FieldOrbit<T> initialOrbit,
  221.                                           final AttitudeProvider attitudeProv,
  222.                                           final T mass,
  223.                                           final UnnormalizedSphericalHarmonicsProvider provider)
  224.         throws OrekitException {
  225.         this(initialOrbit, attitudeProv, mass, provider, provider.onDate(initialOrbit.getDate().toAbsoluteDate()));
  226.     }

  227.     /** Build a propagator from FieldOrbit<T>, attitude provider, mass and potential.
  228.      * <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
  229.      * are related to both the normalized coefficients
  230.      * <span style="text-decoration: overline">C</span><sub>n,0</sub>
  231.      *  and the J<sub>n</sub> one as follows:</p>
  232.      * <pre>
  233.      *   C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
  234.      *                      <span style="text-decoration: overline">C</span><sub>n,0</sub>
  235.      *   C<sub>n,0</sub> = -J<sub>n</sub>
  236.      * </pre>
  237.      * @param initialOrbit initial FieldOrbit<T>
  238.      * @param attitudeProv attitude provider
  239.      * @param mass spacecraft mass
  240.      * @param referenceRadius reference radius of the Earth for the potential model (m)
  241.      * @param mu central attraction coefficient (m³/s²)
  242.      * @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
  243.      * @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
  244.      * @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
  245.      * @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
  246.      * @param c60 un-normalized zonal coefficient (about -5.41e-7 for Earth)
  247.      * @exception OrekitException if the mean parameters cannot be computed
  248.      */
  249.     public FieldEcksteinHechlerPropagator(final FieldOrbit<T> initialOrbit,
  250.                                           final AttitudeProvider attitudeProv,
  251.                                           final T mass,
  252.                                           final double referenceRadius, final double mu,
  253.                                           final double c20, final double c30, final double c40,
  254.                                           final double c50, final double c60)
  255.         throws OrekitException {

  256.         super(mass.getField(), attitudeProv);
  257.         final Field<T> field = mass.getField();
  258.         factory = new FDSFactory<>(field, 1, 2);
  259.         try {

  260.             // store model coefficients
  261.             this.referenceRadius = referenceRadius;
  262.             this.mu  = mu;
  263.             this.ck0 = new double[] {
  264.                 0.0, 0.0, c20, c30, c40, c50, c60
  265.             };

  266.             // compute mean parameters
  267.             // transform into circular adapted parameters used by the Eckstein-Hechler model
  268.             resetInitialState(new FieldSpacecraftState<>(initialOrbit,
  269.                                                          attitudeProv.getAttitude(initialOrbit,
  270.                                                                                   initialOrbit.getDate(),
  271.                                                                                   initialOrbit.getFrame()),
  272.                                                          mass));

  273.         } catch (OrekitException oe) {
  274.             throw new OrekitException(oe);
  275.         }
  276.     }

  277.     /** {@inheritDoc} */
  278.     public void resetInitialState(final FieldSpacecraftState<T> state)
  279.         throws OrekitException {
  280.         super.resetInitialState(state);
  281.         this.initialModel = computeMeanParameters((FieldCircularOrbit<T>) OrbitType.CIRCULAR.convertType(state.getOrbit()),
  282.                                                   state.getMass());
  283.         this.models       = new FieldTimeSpanMap<FieldEHModel<T>, T>(initialModel, state.getA().getField());
  284.     }

  285.     /** {@inheritDoc} */
  286.     protected void resetIntermediateState(final FieldSpacecraftState<T> state, final boolean forward)
  287.         throws OrekitException {
  288.         final FieldEHModel<T> newModel = computeMeanParameters((FieldCircularOrbit<T>) OrbitType.CIRCULAR.convertType(state.getOrbit()),
  289.                                                        state.getMass());
  290.         if (forward) {
  291.             models.addValidAfter(newModel, state.getDate());
  292.         } else {
  293.             models.addValidBefore(newModel, state.getDate());
  294.         }
  295.     }

  296.     /** Compute mean parameters according to the Eckstein-Hechler analytical model.
  297.      * @param osculating osculating FieldOrbit<T>
  298.      * @param mass constant mass
  299.      * @return Eckstein-Hechler mean model
  300.      * @exception OrekitException if FieldOrbit<T> goes outside of supported range
  301.      * (trajectory inside the Brillouin sphere, too eccentric, equatorial, critical
  302.      * inclination) or if convergence cannot be reached
  303.      */
  304.     private FieldEHModel<T> computeMeanParameters(final FieldCircularOrbit<T> osculating, final T mass)
  305.         throws OrekitException {

  306.         // sanity check
  307.         if (osculating.getA().getReal() < referenceRadius) {
  308.             throw new OrekitException(OrekitMessages.TRAJECTORY_INSIDE_BRILLOUIN_SPHERE,
  309.                                            osculating.getA());
  310.         }
  311.         final Field<T> field = mass.getField();
  312.         final T one = field.getOne();
  313.         final T zero = field.getZero();
  314.         // rough initialization of the mean parameters
  315.         FieldEHModel<T> current = new FieldEHModel<>(factory, osculating, mass, referenceRadius, mu, ck0);
  316.         // threshold for each parameter
  317.         final T epsilon         = one .multiply(1.0e-13);
  318.         final T thresholdA      = epsilon.multiply(current.mean.getA().abs().add(1.0));
  319.         final T thresholdE      = epsilon.multiply(current.mean.getE().add(1.0));
  320.         final T thresholdAngles = epsilon.multiply(FastMath.PI);


  321.         int i = 0;
  322.         while (i++ < 100) {

  323.             // recompute the osculating parameters from the current mean parameters
  324.             final FieldDerivativeStructure<T>[] parameters = current.propagateParameters(current.mean.getDate());
  325.             // adapted parameters residuals
  326.             final T deltaA      = osculating.getA()         .subtract(parameters[0].getValue());
  327.             final T deltaEx     = osculating.getCircularEx().subtract(parameters[1].getValue());
  328.             final T deltaEy     = osculating.getCircularEy().subtract(parameters[2].getValue());
  329.             final T deltaI      = osculating.getI()         .subtract(parameters[3].getValue());
  330.             final T deltaRAAN   = normalizeAngle(osculating.getRightAscensionOfAscendingNode().subtract(
  331.                                                                 parameters[4].getValue()),
  332.                                                                 zero);
  333.             final T deltaAlphaM = normalizeAngle(osculating.getAlphaM().subtract(parameters[5].getValue()), zero);
  334.             // update mean parameters
  335.             current = new FieldEHModel<>(factory,
  336.                                          new FieldCircularOrbit<>(current.mean.getA().add(deltaA),
  337.                                                                   current.mean.getCircularEx().add( deltaEx),
  338.                                                                   current.mean.getCircularEy().add( deltaEy),
  339.                                                                   current.mean.getI()         .add( deltaI ),
  340.                                                                   current.mean.getRightAscensionOfAscendingNode().add(deltaRAAN),
  341.                                                                   current.mean.getAlphaM().add(deltaAlphaM),
  342.                                                                   PositionAngle.MEAN,
  343.                                                                   current.mean.getFrame(),
  344.                                                                   current.mean.getDate(), mu),
  345.                                   mass, referenceRadius, mu, ck0);
  346.             // check convergence
  347.             if ((FastMath.abs(deltaA.getReal())      < thresholdA.getReal()) &&
  348.                 (FastMath.abs(deltaEx.getReal())     < thresholdE.getReal()) &&
  349.                 (FastMath.abs(deltaEy.getReal())     < thresholdE.getReal()) &&
  350.                 (FastMath.abs(deltaI.getReal())      < thresholdAngles.getReal()) &&
  351.                 (FastMath.abs(deltaRAAN.getReal())   < thresholdAngles.getReal()) &&
  352.                 (FastMath.abs(deltaAlphaM.getReal()) < thresholdAngles.getReal())) {
  353.                 return current;
  354.             }

  355.         }

  356.         throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_ECKSTEIN_HECHLER_MEAN_PARAMETERS, i);

  357.     }

  358.     /** {@inheritDoc} */
  359.     public FieldCartesianOrbit<T> propagateOrbit(final FieldAbsoluteDate<T> date)
  360.         throws OrekitException {
  361.         // compute Cartesian parameters, taking derivatives into account
  362.         // to make sure velocity and acceleration are consistent
  363.         final FieldEHModel<T> current = models.get(date);
  364.         return new FieldCartesianOrbit<>(toCartesian(date, current.propagateParameters(date)),
  365.                                          current.mean.getFrame(), mu);
  366.     }

  367.     /** Local class for Eckstein-Hechler model, with fixed mean parameters. */
  368.     private static class FieldEHModel<T extends RealFieldElement<T>> {

  369.         /** Factory for the derivatives. */
  370.         private final FDSFactory<T> factory;

  371.         /** Mean FieldOrbit<T>. */
  372.         private final FieldCircularOrbit<T> mean;

  373.         /** Constant mass. */
  374.         private final T mass;
  375.         // CHECKSTYLE: stop JavadocVariable check

  376.         // preprocessed values
  377.         private final T xnotDot;
  378.         private final T rdpom;
  379.         private final T rdpomp;
  380.         private final T eps1;
  381.         private final T eps2;
  382.         private final T xim;
  383.         private final T ommD;
  384.         private final T rdl;
  385.         private final T aMD;

  386.         private final T kh;
  387.         private final T kl;

  388.         private final T ax1;
  389.         private final T ay1;
  390.         private final T as1;
  391.         private final T ac2;
  392.         private final T axy3;
  393.         private final T as3;
  394.         private final T ac4;
  395.         private final T as5;
  396.         private final T ac6;

  397.         private final T ex1;
  398.         private final T exx2;
  399.         private final T exy2;
  400.         private final T ex3;
  401.         private final T ex4;

  402.         private final T ey1;
  403.         private final T eyx2;
  404.         private final T eyy2;
  405.         private final T ey3;
  406.         private final T ey4;

  407.         private final T rx1;
  408.         private final T ry1;
  409.         private final T r2;
  410.         private final T r3;
  411.         private final T rl;

  412.         private final T iy1;
  413.         private final T ix1;
  414.         private final T i2;
  415.         private final T i3;
  416.         private final T ih;

  417.         private final T lx1;
  418.         private final T ly1;
  419.         private final T l2;
  420.         private final T l3;
  421.         private final T ll;

  422.         // CHECKSTYLE: resume JavadocVariable check

  423.         /** Create a model for specified mean FieldOrbit<T>.
  424.          * @param factory factory for the derivatives
  425.          * @param mean mean FieldOrbit<T>
  426.          * @param mass constant mass
  427.          * @param referenceRadius reference radius of the central body attraction model (m)
  428.          * @param mu central attraction coefficient (m³/s²)
  429.          * @param ck0 un-normalized zonal coefficients
  430.          * @exception OrekitException if mean FieldOrbit<T> is not within model supported domain
  431.          */
  432.         FieldEHModel(final FDSFactory<T> factory, final FieldCircularOrbit<T> mean, final T mass,
  433.                      final double referenceRadius, final double mu, final double[] ck0)
  434.             throws OrekitException {

  435.             this.factory         = factory;
  436.             this.mean            = mean;
  437.             this.mass            = mass;
  438.             final T zero = mass.getField().getZero();
  439.             final T one  = mass.getField().getOne();
  440.             // preliminary processing
  441.             T q =  zero.add(referenceRadius).divide(mean.getA());
  442.             T ql = q.multiply(q);
  443.             final T g2 = ql.multiply(ck0[2]);
  444.             ql = ql.multiply(q);
  445.             final T g3 = ql.multiply(ck0[3]);
  446.             ql = ql.multiply(q);
  447.             final T g4 = ql.multiply(ck0[4]);
  448.             ql = ql.multiply(q);
  449.             final T g5 = ql.multiply(ck0[5]);
  450.             ql = ql.multiply(q);
  451.             final T g6 = ql.multiply(ck0[6]);

  452.             final T cosI1 = mean.getI().cos();
  453.             final T sinI1 = mean.getI().sin();
  454.             final T sinI2 = sinI1.multiply(sinI1);
  455.             final T sinI4 = sinI2.multiply(sinI2);
  456.             final T sinI6 = sinI2.multiply(sinI4);

  457.             if (sinI2.getReal() < 1.0e-10) {
  458.                 throw new OrekitException(OrekitMessages.ALMOST_EQUATORIAL_ORBIT,
  459.                                                FastMath.toDegrees(mean.getI().getReal()));
  460.             }

  461.             if (FastMath.abs(sinI2.getReal() - 4.0 / 5.0) < 1.0e-3) {
  462.                 throw new OrekitException(OrekitMessages.ALMOST_CRITICALLY_INCLINED_ORBIT,
  463.                                                FastMath.toDegrees(mean.getI().getReal()));
  464.             }

  465.             if (mean.getE().getReal() > 0.1) {
  466.                 // if 0.005 < e < 0.1 no error is triggered, but accuracy is poor
  467.                 throw new OrekitException(OrekitMessages.TOO_LARGE_ECCENTRICITY_FOR_PROPAGATION_MODEL,
  468.                                                mean.getE());
  469.             }

  470.             xnotDot = zero.add(mu).divide(mean.getA()).sqrt().divide(mean.getA());

  471.             rdpom = g2.multiply(-0.75).multiply(sinI2.multiply(-5.0).add(4.0));
  472.             rdpomp = g4.multiply(7.5).multiply(sinI2.multiply(-31.0 / 8.0).add(1.0).add( sinI4.multiply(49.0 / 16.0))).subtract(
  473.                     g6.multiply(13.125).multiply(one.subtract(sinI2.multiply(8.0)).add(sinI4.multiply(129.0 / 8.0)).subtract(sinI6.multiply(297.0 / 32.0)) ));


  474.             q = zero.add(3.0).divide(rdpom.multiply(32.0));
  475.             eps1 = q.multiply(g4).multiply(sinI2).multiply(sinI2.multiply(-35.0).add(30.0)).subtract(
  476.                    q.multiply(175.0).multiply(g6).multiply(sinI2).multiply(sinI2.multiply(-3.0).add(sinI4.multiply(2.0625)).add(1.0)));
  477.             q = sinI1.multiply(3.0).divide(rdpom.multiply(8.0));
  478.             eps2 = q.multiply(g3).multiply(sinI2.multiply(-5.0).add(4.0)).subtract(q.multiply(g5).multiply(sinI2.multiply(-35.0).add(sinI4.multiply(26.25)).add(10.0)));

  479.             xim = mean.getI();
  480.             ommD = cosI1.multiply(g2.multiply(1.50).subtract(g2.multiply(2.25).multiply(g2).multiply(sinI2.multiply(-19.0 / 6.0).add(2.5))).add(
  481.                             g4.multiply(0.9375).multiply(sinI2.multiply(7.0).subtract(4.0))).add(
  482.                             g6.multiply(3.28125).multiply(sinI2.multiply(-9.0).add(2.0).add(sinI4.multiply(8.25)))));

  483.             rdl = g2.multiply(-1.50).multiply(sinI2.multiply(-4.0).add(3.0)).add(1.0);
  484.             aMD = rdl.add(
  485.                     g2.multiply(2.25).multiply(g2.multiply(sinI2.multiply(-263.0 / 12.0 ).add(9.0).add(sinI4.multiply(341.0 / 24.0))))).add(
  486.                     g4.multiply(15.0 / 16.0).multiply(sinI2.multiply(-31.0).add(8.0).add(sinI4.multiply(24.5)))).add(
  487.                     g6.multiply(105.0 / 32.0).multiply(sinI2.multiply(25.0).add(-10.0 / 3.0).subtract(sinI4.multiply(48.75)).add(sinI6.multiply(27.5))));

  488.             final T qq   = g2.divide(rdl).multiply(-1.5);
  489.             final T qA   = g2.multiply(0.75).multiply(g2).multiply(sinI2);
  490.             final T qB   = g4.multiply(0.25).multiply(sinI2);
  491.             final T qC   = g6.multiply(105.0 / 16.0).multiply(sinI2);
  492.             final T qD   = g3.multiply(-0.75).multiply(sinI1);
  493.             final T qE   = g5.multiply(3.75).multiply(sinI1);
  494.             kh = zero.add(0.375).divide(rdpom);
  495.             kl = kh.divide(sinI1);

  496.             ax1 = qq.multiply(sinI2.multiply(-3.5).add(2.0));
  497.             ay1 = qq.multiply(sinI2.multiply(-2.5).add(2.0));
  498.             as1 = qD.multiply(sinI2.multiply(-5.0).add(4.0)).add(
  499.                   qE.multiply(sinI4.multiply(2.625).add(sinI2.multiply(-3.5)).add(1.0)));
  500.             ac2 = qq.multiply(sinI2).add(
  501.                   qA.multiply(7.0).multiply(sinI2.multiply(-3.0).add(2.0))).add(
  502.                   qB.multiply(sinI2.multiply(-17.5).add(15.0))).add(
  503.                   qC.multiply(sinI2.multiply(3.0).subtract(1.0).subtract(sinI4.multiply(33.0 / 16.0))));
  504.             axy3 = qq.multiply(3.5).multiply(sinI2);
  505.             as3 = qD.multiply(5.0 / 3.0).multiply(sinI2).add(
  506.                   qE.multiply(7.0 / 6.0).multiply(sinI2).multiply(sinI2.multiply(-1.125).add(1)));
  507.             ac4 = qA.multiply(sinI2).add(
  508.                   qB.multiply(4.375).multiply(sinI2)).add(
  509.                   qC.multiply(0.75).multiply(sinI4.multiply(1.1).subtract(sinI2)));

  510.             as5 = qE.multiply(21.0 / 80.0).multiply(sinI4);

  511.             ac6 = qC.multiply(-11.0 / 80.0).multiply(sinI4);

  512.             ex1 = qq.multiply(sinI2.multiply(-1.25).add(1.0));
  513.             exx2 = qq.multiply(0.5).multiply(sinI2.multiply(-5.0).add(3.0));
  514.             exy2 = qq.multiply(sinI2.multiply(-1.5).add(2.0));
  515.             ex3 = qq.multiply(7.0 / 12.0).multiply(sinI2);
  516.             ex4 = qq.multiply(17.0 / 8.0).multiply(sinI2);

  517.             ey1 = qq.multiply(sinI2.multiply(-1.75).add(1.0));
  518.             eyx2 = qq.multiply(sinI2.multiply(-3.0).add(1.0));
  519.             eyy2 = qq.multiply(sinI2.multiply(2.0).subtract(1.5));
  520.             ey3 = qq.multiply(7.0 / 12.0).multiply(sinI2);
  521.             ey4 = qq.multiply(17.0 / 8.0).multiply(sinI2);

  522.             q  = cosI1.multiply(qq).negate();
  523.             rx1 = q.multiply(3.5);
  524.             ry1 = q.multiply(-2.5);
  525.             r2 = q.multiply(-0.5);
  526.             r3 =  q.multiply(7.0 / 6.0);
  527.             rl = g3 .multiply( cosI1).multiply(sinI2.multiply(-15.0).add(4.0)).subtract(
  528.                  g5.multiply(2.5).multiply(cosI1).multiply(sinI2.multiply(-42.0).add(4.0).add(sinI4.multiply(52.5))));

  529.             q = qq.multiply(0.5).multiply(sinI1).multiply(cosI1);
  530.             iy1 =  q;
  531.             ix1 = q.negate();
  532.             i2 =  q;
  533.             i3 =  q.multiply(7.0 / 3.0);
  534.             ih = g3.negate().multiply(cosI1).multiply(sinI2.multiply(-5.0).add(4)).add(
  535.                  g5.multiply(2.5).multiply(cosI1).multiply(sinI2.multiply(-14.0).add(4.0).add(sinI4.multiply(10.5))));
  536.             lx1 = qq.multiply(sinI2.multiply(-77.0 / 8.0).add(7.0));
  537.             ly1 = qq.multiply(sinI2.multiply(55.0 / 8.0).subtract(7.50));
  538.             l2 = qq.multiply(sinI2.multiply(1.25).subtract(0.5));
  539.             l3 = qq.multiply(sinI2.multiply(77.0 / 24.0).subtract(7.0 / 6.0));
  540.             ll = g3.multiply(sinI2.multiply(53.0).subtract(4.0).add(sinI4.multiply(-57.5))).add(
  541.                  g5.multiply(2.5).multiply(sinI2.multiply(-96.0).add(4.0).add(sinI4.multiply(269.5).subtract(sinI6.multiply(183.75)))));

  542.         }

  543.         /** Extrapolate an FieldOrbit<T> up to a specific target date.
  544.          * @param date target date for the FieldOrbit<T>
  545.          * @return propagated parameters
  546.          * @exception OrekitException if some parameters are out of bounds
  547.          */
  548.         public FieldDerivativeStructure<T>[] propagateParameters(final FieldAbsoluteDate<T> date)
  549.             throws OrekitException {
  550.             final Field<T> field = date.durationFrom(mean.getDate()).getField();
  551.             final T one = field.getOne();
  552.             final T zero = field.getZero();
  553.             // Keplerian evolution
  554.             final FieldDerivativeStructure<T> dt =
  555.                     factory.build(date.durationFrom(mean.getDate()), one, zero);
  556.             final FieldDerivativeStructure<T> xnot = dt.multiply(xnotDot);

  557.             // secular effects

  558.             // eccentricity
  559.             final FieldDerivativeStructure<T> x   = xnot.multiply(rdpom.add(rdpomp));
  560.             final FieldDerivativeStructure<T> cx  = x.cos();
  561.             final FieldDerivativeStructure<T> sx  = x.sin();
  562.             final FieldDerivativeStructure<T> exm = cx.multiply(mean.getCircularEx()).
  563.                                             add(sx.multiply(eps2.subtract(one.subtract(eps1).multiply(mean.getCircularEy()))));
  564.             final FieldDerivativeStructure<T> eym = sx.multiply(eps1.add(1.0).multiply(mean.getCircularEx())).
  565.                                             add(cx.multiply(mean.getCircularEy().subtract(eps2))).
  566.                                             add(eps2);
  567.             // no secular effect on inclination

  568.             // right ascension of ascending node
  569.             final FieldDerivativeStructure<T> omm =
  570.                             factory.build(normalizeAngle(mean.getRightAscensionOfAscendingNode().add(ommD.multiply(xnot.getValue())),
  571.                                                          zero.add(FastMath.PI)),
  572.                                           ommD.multiply(xnotDot),
  573.                                           zero);
  574.             // latitude argument
  575.             final FieldDerivativeStructure<T> xlm =
  576.                             factory.build(normalizeAngle(mean.getAlphaM().add(aMD.multiply(xnot.getValue())), zero.add(FastMath.PI)),
  577.                                           aMD.multiply(xnotDot),
  578.                                           zero);

  579.             // periodical terms
  580.             final FieldDerivativeStructure<T> cl1 = xlm.cos();
  581.             final FieldDerivativeStructure<T> sl1 = xlm.sin();
  582.             final FieldDerivativeStructure<T> cl2 = cl1.multiply(cl1).subtract(sl1.multiply(sl1));
  583.             final FieldDerivativeStructure<T> sl2 = cl1.multiply(sl1).add(sl1.multiply(cl1));
  584.             final FieldDerivativeStructure<T> cl3 = cl2.multiply(cl1).subtract(sl2.multiply(sl1));
  585.             final FieldDerivativeStructure<T> sl3 = cl2.multiply(sl1).add(sl2.multiply(cl1));
  586.             final FieldDerivativeStructure<T> cl4 = cl3.multiply(cl1).subtract(sl3.multiply(sl1));
  587.             final FieldDerivativeStructure<T> sl4 = cl3.multiply(sl1).add(sl3.multiply(cl1));
  588.             final FieldDerivativeStructure<T> cl5 = cl4.multiply(cl1).subtract(sl4.multiply(sl1));
  589.             final FieldDerivativeStructure<T> sl5 = cl4.multiply(sl1).add(sl4.multiply(cl1));
  590.             final FieldDerivativeStructure<T> cl6 = cl5.multiply(cl1).subtract(sl5.multiply(sl1));

  591.             final FieldDerivativeStructure<T> qh  = eym.subtract(eps2).multiply(kh);
  592.             final FieldDerivativeStructure<T> ql  = exm.multiply(kl);

  593.             final FieldDerivativeStructure<T> exmCl1 = exm.multiply(cl1);
  594.             final FieldDerivativeStructure<T> exmSl1 = exm.multiply(sl1);
  595.             final FieldDerivativeStructure<T> eymCl1 = eym.multiply(cl1);
  596.             final FieldDerivativeStructure<T> eymSl1 = eym.multiply(sl1);
  597.             final FieldDerivativeStructure<T> exmCl2 = exm.multiply(cl2);
  598.             final FieldDerivativeStructure<T> exmSl2 = exm.multiply(sl2);
  599.             final FieldDerivativeStructure<T> eymCl2 = eym.multiply(cl2);
  600.             final FieldDerivativeStructure<T> eymSl2 = eym.multiply(sl2);
  601.             final FieldDerivativeStructure<T> exmCl3 = exm.multiply(cl3);
  602.             final FieldDerivativeStructure<T> exmSl3 = exm.multiply(sl3);
  603.             final FieldDerivativeStructure<T> eymCl3 = eym.multiply(cl3);
  604.             final FieldDerivativeStructure<T> eymSl3 = eym.multiply(sl3);
  605.             final FieldDerivativeStructure<T> exmCl4 = exm.multiply(cl4);
  606.             final FieldDerivativeStructure<T> exmSl4 = exm.multiply(sl4);
  607.             final FieldDerivativeStructure<T> eymCl4 = eym.multiply(cl4);
  608.             final FieldDerivativeStructure<T> eymSl4 = eym.multiply(sl4);

  609.             // semi major axis
  610.             final FieldDerivativeStructure<T> rda = exmCl1.multiply(ax1).
  611.                                             add(eymSl1.multiply(ay1)).
  612.                                             add(sl1.multiply(as1)).
  613.                                             add(cl2.multiply(ac2)).
  614.                                             add(exmCl3.add(eymSl3).multiply(axy3)).
  615.                                             add(sl3.multiply(as3)).
  616.                                             add(cl4.multiply(ac4)).
  617.                                             add(sl5.multiply(as5)).
  618.                                             add(cl6.multiply(ac6));

  619.             // eccentricity
  620.             final FieldDerivativeStructure<T> rdex = cl1.multiply(ex1).
  621.                                              add(exmCl2.multiply(exx2)).
  622.                                              add(eymSl2.multiply(exy2)).
  623.                                              add(cl3.multiply(ex3)).
  624.                                              add(exmCl4.add(eymSl4).multiply(ex4));
  625.             final FieldDerivativeStructure<T> rdey = sl1.multiply(ey1).
  626.                                              add(exmSl2.multiply(eyx2)).
  627.                                              add(eymCl2.multiply(eyy2)).
  628.                                              add(sl3.multiply(ey3)).
  629.                                              add(exmSl4.subtract(eymCl4).multiply(ey4));

  630.             // ascending node
  631.             final FieldDerivativeStructure<T> rdom = exmSl1.multiply(rx1).
  632.                                              add(eymCl1.multiply(ry1)).
  633.                                              add(sl2.multiply(r2)).
  634.                                              add(eymCl3.subtract(exmSl3).multiply(r3)).
  635.                                              add(ql.multiply(rl));

  636.             // inclination
  637.             final FieldDerivativeStructure<T> rdxi = eymSl1.multiply(iy1).
  638.                                              add(exmCl1.multiply(ix1)).
  639.                                              add(cl2.multiply(i2)).
  640.                                              add(exmCl3.add(eymSl3).multiply(i3)).
  641.                                              add(qh.multiply(ih));

  642.             // latitude argument
  643.             final FieldDerivativeStructure<T> rdxl = exmSl1.multiply(lx1).
  644.                                              add(eymCl1.multiply(ly1)).
  645.                                              add(sl2.multiply(l2)).
  646.                                              add(exmSl3.subtract(eymCl3).multiply(l3)).
  647.                                              add(ql.multiply(ll));
  648.             // osculating parameters
  649.             final FieldDerivativeStructure<T>[] FTD = MathArrays.buildArray(rdxl.getField(), 6);

  650.             FTD[0] = rda.add(1.0).multiply(mean.getA());
  651.             FTD[1] = rdex.add(exm);
  652.             FTD[2] = rdey.add(eym);
  653.             FTD[3] = rdxi.add(xim);
  654.             FTD[4] = rdom.add(omm);
  655.             FTD[5] = rdxl.add(xlm);
  656.             return FTD;

  657.         }

  658.     }

  659.     /** Convert circular parameters <em>with derivatives</em> to Cartesian coordinates.
  660.      * @param date date of the FieldOrbit<T>al parameters
  661.      * @param parameters circular parameters (a, ex, ey, i, raan, alphaM)
  662.      * @return Cartesian coordinates consistent with values and derivatives
  663.      */
  664.     private TimeStampedFieldPVCoordinates<T> toCartesian(final FieldAbsoluteDate<T> date, final FieldDerivativeStructure<T>[] parameters) {

  665.         // evaluate coordinates in the FieldOrbit<T> canonical reference frame
  666.         final FieldDerivativeStructure<T> cosOmega = parameters[4].cos();
  667.         final FieldDerivativeStructure<T> sinOmega = parameters[4].sin();
  668.         final FieldDerivativeStructure<T> cosI     = parameters[3].cos();
  669.         final FieldDerivativeStructure<T> sinI     = parameters[3].sin();
  670.         final FieldDerivativeStructure<T> alphaE   = meanToEccentric(parameters[5], parameters[1], parameters[2]);
  671.         final FieldDerivativeStructure<T> cosAE    = alphaE.cos();
  672.         final FieldDerivativeStructure<T> sinAE    = alphaE.sin();
  673.         final FieldDerivativeStructure<T> ex2      = parameters[1].multiply(parameters[1]);
  674.         final FieldDerivativeStructure<T> ey2      = parameters[2].multiply(parameters[2]);
  675.         final FieldDerivativeStructure<T> exy      = parameters[1].multiply(parameters[2]);
  676.         final FieldDerivativeStructure<T> q        = ex2.add(ey2).subtract(1).negate().sqrt();
  677.         final FieldDerivativeStructure<T> beta     = q.add(1).reciprocal();
  678.         final FieldDerivativeStructure<T> bx2      = beta.multiply(ex2);
  679.         final FieldDerivativeStructure<T> by2      = beta.multiply(ey2);
  680.         final FieldDerivativeStructure<T> bxy      = beta.multiply(exy);
  681.         final FieldDerivativeStructure<T> u        = bxy.multiply(sinAE).subtract(parameters[1].add(by2.subtract(1).multiply(cosAE)));
  682.         final FieldDerivativeStructure<T> v        = bxy.multiply(cosAE).subtract(parameters[2].add(bx2.subtract(1).multiply(sinAE)));
  683.         final FieldDerivativeStructure<T> x        = parameters[0].multiply(u);
  684.         final FieldDerivativeStructure<T> y        = parameters[0].multiply(v);

  685.         // canonical FieldOrbit<T> reference frame
  686.         final FieldVector3D<FieldDerivativeStructure<T>> p =
  687.                 new FieldVector3D<FieldDerivativeStructure<T>>(x.multiply(cosOmega).subtract(y.multiply(cosI.multiply(sinOmega))),
  688.                                                        x.multiply(sinOmega).add(y.multiply(cosI.multiply(cosOmega))),
  689.                                                        y.multiply(sinI));

  690.         // dispatch derivatives
  691.         final FieldVector3D<T> p0 = new FieldVector3D<>(p.getX().getValue(),
  692.                                                         p.getY().getValue(),
  693.                                                         p.getZ().getValue());
  694.         final FieldVector3D<T> p1 = new FieldVector3D<>(p.getX().getPartialDerivative(1),
  695.                                                         p.getY().getPartialDerivative(1),
  696.                                                         p.getZ().getPartialDerivative(1));
  697.         final FieldVector3D<T> p2 = new FieldVector3D<>(p.getX().getPartialDerivative(2),
  698.                                                         p.getY().getPartialDerivative(2),
  699.                                                         p.getZ().getPartialDerivative(2));
  700.         return new TimeStampedFieldPVCoordinates<>(date, p0, p1, p2);

  701.     }

  702.     /** Computes the eccentric latitude argument from the mean latitude argument.
  703.      * @param alphaM = M + Ω mean latitude argument (rad)
  704.      * @param ex e cos(Ω), first component of circular eccentricity vector
  705.      * @param ey e sin(Ω), second component of circular eccentricity vector
  706.      * @return the eccentric latitude argument.
  707.      */
  708.     private FieldDerivativeStructure<T> meanToEccentric(final FieldDerivativeStructure<T> alphaM,
  709.                                                 final FieldDerivativeStructure<T> ex,
  710.                                                 final FieldDerivativeStructure<T> ey) {
  711.         // Generalization of Kepler equation to circular parameters
  712.         // with alphaE = PA + E and
  713.         //      alphaM = PA + M = alphaE - ex.sin(alphaE) + ey.cos(alphaE)
  714.         FieldDerivativeStructure<T> alphaE        = alphaM;
  715.         FieldDerivativeStructure<T> shift         = alphaM.getField().getZero();
  716.         FieldDerivativeStructure<T> alphaEMalphaM = alphaM.getField().getZero();
  717.         FieldDerivativeStructure<T> cosAlphaE     = alphaE.cos();
  718.         FieldDerivativeStructure<T> sinAlphaE     = alphaE.sin();
  719.         int                 iter          = 0;
  720.         do {
  721.             final FieldDerivativeStructure<T> f2 = ex.multiply(sinAlphaE).subtract(ey.multiply(cosAlphaE));
  722.             final FieldDerivativeStructure<T> f1 = alphaM.getField().getOne().subtract(ex.multiply(cosAlphaE)).subtract(ey.multiply(sinAlphaE));
  723.             final FieldDerivativeStructure<T> f0 = alphaEMalphaM.subtract(f2);

  724.             final FieldDerivativeStructure<T> f12 = f1.multiply(2);
  725.             shift = f0.multiply(f12).divide(f1.multiply(f12).subtract(f0.multiply(f2)));

  726.             alphaEMalphaM  = alphaEMalphaM.subtract(shift);
  727.             alphaE         = alphaM.add(alphaEMalphaM);
  728.             cosAlphaE      = alphaE.cos();
  729.             sinAlphaE      = alphaE.sin();

  730.         } while ((++iter < 50) && (FastMath.abs(shift.getValue().getReal()) > 1.0e-12));

  731.         return alphaE;

  732.     }

  733.     /** {@inheritDoc} */
  734.     protected T getMass(final FieldAbsoluteDate<T> date) {
  735.         return models.get(date).mass;
  736.     }
  737.     /**
  738.      * Normalize an angle in a 2&pi; wide interval around a center value.
  739.      * <p>This method has three main uses:</p>
  740.      * <ul>
  741.      *   <li>normalize an angle between 0 and 2&pi;:<br/>
  742.      *       {@code a = MathUtils.normalizeAngle(a, FastMath.PI);}</li>
  743.      *   <li>normalize an angle between -&pi; and +&pi;<br/>
  744.      *       {@code a = MathUtils.normalizeAngle(a, 0.0);}</li>
  745.      *   <li>compute the angle between two defining angular positions:<br>
  746.      *       {@code angle = MathUtils.normalizeAngle(end, start) - start;}</li>
  747.      * </ul>
  748.      * <p>Note that due to numerical accuracy and since &pi; cannot be represented
  749.      * exactly, the result interval is <em>closed</em>, it cannot be half-closed
  750.      * as would be more satisfactory in a purely mathematical view.</p>
  751.      * @param a angle to normalize
  752.      * @param center center of the desired 2&pi; interval for the result
  753.      * @param <T> the type of the field elements
  754.      * @return a-2k&pi; with integer k and center-&pi; &lt;= a-2k&pi; &lt;= center+&pi;
  755.      * @since 1.2
  756.      */
  757.     public static <T extends RealFieldElement<T>> T normalizeAngle(final T a, final T center) {
  758.         return a.subtract(2 * FastMath.PI * FastMath.floor((a.getReal() + FastMath.PI - center.getReal()) / (2 * FastMath.PI)));
  759.     }


  760. }