HolmesFeatherstoneAttractionModel.java

  1. /* Copyright 2002-2013 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.forces.gravity;


  18. import java.io.Serializable;

  19. import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
  20. import org.apache.commons.math3.geometry.euclidean.threed.FieldRotation;
  21. import org.apache.commons.math3.geometry.euclidean.threed.FieldVector3D;
  22. import org.apache.commons.math3.geometry.euclidean.threed.SphericalCoordinates;
  23. import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
  24. import org.apache.commons.math3.linear.Array2DRowRealMatrix;
  25. import org.apache.commons.math3.linear.RealMatrix;
  26. import org.apache.commons.math3.ode.AbstractParameterizable;
  27. import org.apache.commons.math3.util.FastMath;
  28. import org.orekit.errors.OrekitException;
  29. import org.orekit.forces.ForceModel;
  30. import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
  31. import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider.NormalizedSphericalHarmonics;
  32. import org.orekit.forces.gravity.potential.TideSystem;
  33. import org.orekit.forces.gravity.potential.TideSystemProvider;
  34. import org.orekit.frames.Frame;
  35. import org.orekit.frames.Transform;
  36. import org.orekit.propagation.SpacecraftState;
  37. import org.orekit.propagation.events.EventDetector;
  38. import org.orekit.propagation.numerical.TimeDerivativesEquations;
  39. import org.orekit.time.AbsoluteDate;

  40. /** This class represents the gravitational field of a celestial body.
  41.  * <p>
  42.  * The algorithm implemented in this class has been designed by S. A. Holmes
  43.  * and W. E. Featherstone from Department of Spatial Sciences, Curtin University
  44.  * of Technology, Perth, Australia. It is described in their 2002 paper: <a
  45.  * href="http://cct.gfy.ku.dk/publ_others/ccta1870.pdf">A unified approach to
  46.  * the Clenshaw summation and the recursive computation of very high degree and
  47.  * order normalised associated Legendre functions</a> (Journal of Geodesy (2002)
  48.  * 76: 279–299).
  49.  * </p>
  50.  * <p>
  51.  * This model directly uses normalized coefficients and stable recursion algorithms
  52.  * so it is more suited to high degree gravity fields than the classical {@link
  53.  * CunninghamAttractionModel Cunningham} or {@link DrozinerAttractionModel Droziner}
  54.  * models which use un-normalized coefficients.
  55.  * </p>
  56.  * <p>
  57.  * Among the different algorithms presented in Holmes and Featherstone paper, this
  58.  * class implements the <em>modified forward row method</em>. All recursion coefficients
  59.  * are precomputed and stored for greater performance. This caching was suggested in the
  60.  * paper but not used due to the large memory requirements. Since 2002, even low end
  61.  * computers and mobile devices do have sufficient memory so this caching has become
  62.  * feasible nowadays.
  63.  * <p>
  64.  * @author Luc Maisonobe
  65.  * @since 6.0
  66.  */

  67. public class HolmesFeatherstoneAttractionModel
  68.     extends AbstractParameterizable implements ForceModel, TideSystemProvider {

  69.     /** Exponent scaling to avoid floating point overflow.
  70.      * <p>The paper uses 10^280, we prefer a power of two to preserve accuracy thanks to
  71.      * {@link FastMath#scalb(double, int)}, so we use 2^930 which has the same order of magnitude.
  72.      */
  73.     private static final int SCALING = 930;

  74.     /** Provider for the spherical harmonics. */
  75.     private final NormalizedSphericalHarmonicsProvider provider;

  76.     /** Central attraction coefficient. */
  77.     private double mu;

  78.     /** Rotating body. */
  79.     private final Frame bodyFrame;

  80.     /** Recursion coefficients g<sub>n,m</sub>/&radic;j. */
  81.     private final double[] gnmOj;

  82.     /** Recursion coefficients h<sub>n,m</sub>/&radic;j. */
  83.     private final double[] hnmOj;

  84.     /** Recursion coefficients e<sub>n,m</sub>. */
  85.     private final double[] enm;

  86.     /** Scaled sectorial Pbar<sub>m,m</sub>/u<sup>m</sup> &times; 2<sup>-SCALING</sup>. */
  87.     private final double[] sectorial;

  88.     /** Creates a new instance.
  89.      * @param centralBodyFrame rotating body frame
  90.      * @param provider provider for spherical harmonics
  91.      * @since 6.0
  92.      */
  93.     public HolmesFeatherstoneAttractionModel(final Frame centralBodyFrame,
  94.                                              final NormalizedSphericalHarmonicsProvider provider) {

  95.         super(NewtonianAttraction.CENTRAL_ATTRACTION_COEFFICIENT);

  96.         this.provider  = provider;
  97.         this.mu        = provider.getMu();
  98.         this.bodyFrame = centralBodyFrame;

  99.         // the pre-computed arrays hold coefficients from triangular arrays in a single
  100.         // storing neither diagonal elements (n = m) nor the non-diagonal element n=1, m=0
  101.         final int degree = provider.getMaxDegree();
  102.         final int size = FastMath.max(0, degree * (degree + 1) / 2 - 1);
  103.         gnmOj = new double[size];
  104.         hnmOj = new double[size];
  105.         enm   = new double[size];

  106.         // pre-compute the recursion coefficients corresponding to equations 19 and 22
  107.         // from Holmes and Featherstone paper
  108.         // for cache efficiency, elements are stored in the same order they will be used
  109.         // later on, i.e. from rightmost column to leftmost column
  110.         int index = 0;
  111.         for (int m = degree; m >= 0; --m) {
  112.             final int j = (m == 0) ? 2 : 1;
  113.             for (int n = FastMath.max(2, m + 1); n <= degree; ++n) {
  114.                 final double f = (n - m) * (n + m + 1);
  115.                 gnmOj[index] = 2 * (m + 1) / FastMath.sqrt(j * f);
  116.                 hnmOj[index] = FastMath.sqrt((n + m + 2) * (n - m - 1) / (j * f));
  117.                 enm[index]   = FastMath.sqrt(f / j);
  118.                 ++index;
  119.             }
  120.         }

  121.         // scaled sectorial terms corresponding to equation 28 in Holmes and Featherstone paper
  122.         sectorial    = new double[degree + 1];
  123.         sectorial[0] = FastMath.scalb(1.0, -SCALING);
  124.         sectorial[1] = FastMath.sqrt(3) * sectorial[0];
  125.         for (int m = 2; m < sectorial.length; ++m) {
  126.             sectorial[m] = FastMath.sqrt((2 * m + 1) / (2.0 * m)) * sectorial[m - 1];
  127.         }

  128.     }

  129.     /** {@inheritDoc} */
  130.     public TideSystem getTideSystem() {
  131.         return provider.getTideSystem();
  132.     }

  133.     /** Compute the value of the gravity field.
  134.      * @param date current date
  135.      * @param position position at which gravity field is desired in body frame
  136.      * @return value of the gravity field (central and non-central parts summed together)
  137.      * @exception OrekitException if position cannot be converted to central body frame
  138.      */
  139.     public double value(final AbsoluteDate date, final Vector3D position)
  140.         throws OrekitException {
  141.         return mu / position.getNorm() + nonCentralPart(date, position);
  142.     }

  143.     /** Compute the non-central part of the gravity field.
  144.      * @param date current date
  145.      * @param position position at which gravity field is desired in body frame
  146.      * @return value of the non-central part of the gravity field
  147.      * @exception OrekitException if position cannot be converted to central body frame
  148.      */
  149.     public double nonCentralPart(final AbsoluteDate date, final Vector3D position)
  150.         throws OrekitException {

  151.         final int degree = provider.getMaxDegree();
  152.         final int order  = provider.getMaxOrder();
  153.         final NormalizedSphericalHarmonics harmonics = provider.onDate(date);

  154.         // allocate the columns for recursion
  155.         double[] pnm0Plus2 = new double[degree + 1];
  156.         double[] pnm0Plus1 = new double[degree + 1];
  157.         double[] pnm0      = new double[degree + 1];

  158.         // compute polar coordinates
  159.         final double x   = position.getX();
  160.         final double y   = position.getY();
  161.         final double z   = position.getZ();
  162.         final double x2  = x * x;
  163.         final double y2  = y * y;
  164.         final double z2  = z * z;
  165.         final double r2  = x2 + y2 + z2;
  166.         final double r   = FastMath.sqrt (r2);
  167.         final double rho = FastMath.sqrt(x2 + y2);
  168.         final double t   = z / r;   // cos(theta), where theta is the polar angle
  169.         final double u   = rho / r; // sin(theta), where theta is the polar angle
  170.         final double tOu = z / rho;

  171.         // compute distance powers
  172.         final double[] aOrN = createDistancePowersArray(provider.getAe() / r);

  173.         // compute longitude cosines/sines
  174.         final double[][] cosSinLambda = createCosSinArrays(position.getX() / rho, position.getY() / rho);

  175.         // outer summation over order
  176.         int    index = 0;
  177.         double value = 0;
  178.         for (int m = degree; m >= 0; --m) {

  179.             // compute tesseral terms without derivatives
  180.             index = computeTesseral(m, degree, index, t, u, tOu,
  181.                                     pnm0Plus2, pnm0Plus1, null, pnm0, null, null);

  182.             if (m <= order) {
  183.                 // compute contribution of current order to field (equation 5 of the paper)

  184.                 // inner summation over degree, for fixed order
  185.                 double sumDegreeS        = 0;
  186.                 double sumDegreeC        = 0;
  187.                 for (int n = FastMath.max(2, m); n <= degree; ++n) {
  188.                     sumDegreeS += pnm0[n] * aOrN[n] * harmonics.getNormalizedSnm(n, m);
  189.                     sumDegreeC += pnm0[n] * aOrN[n] * harmonics.getNormalizedCnm(n, m);
  190.                 }

  191.                 // contribution to outer summation over order
  192.                 value = value * u + cosSinLambda[1][m] * sumDegreeS + cosSinLambda[0][m] * sumDegreeC;

  193.             }

  194.             // rotate the recursion arrays
  195.             final double[] tmp = pnm0Plus2;
  196.             pnm0Plus2 = pnm0Plus1;
  197.             pnm0Plus1 = pnm0;
  198.             pnm0      = tmp;

  199.         }

  200.         // scale back
  201.         value = FastMath.scalb(value, SCALING);

  202.         // apply the global mu/r factor
  203.         return mu * value / r;

  204.     }

  205.     /** Compute the gradient of the non-central part of the gravity field.
  206.      * @param date current date
  207.      * @param position position at which gravity field is desired in body frame
  208.      * @return gradient of the non-central part of the gravity field
  209.      * @exception OrekitException if position cannot be converted to central body frame
  210.      */
  211.     public double[] gradient(final AbsoluteDate date, final Vector3D position)
  212.         throws OrekitException {

  213.         final int degree = provider.getMaxDegree();
  214.         final int order  = provider.getMaxOrder();
  215.         final NormalizedSphericalHarmonics harmonics = provider.onDate(date);

  216.         // allocate the columns for recursion
  217.         double[] pnm0Plus2  = new double[degree + 1];
  218.         double[] pnm0Plus1  = new double[degree + 1];
  219.         double[] pnm0       = new double[degree + 1];
  220.         final double[] pnm1 = new double[degree + 1];

  221.         // compute polar coordinates
  222.         final double x    = position.getX();
  223.         final double y    = position.getY();
  224.         final double z    = position.getZ();
  225.         final double x2   = x * x;
  226.         final double y2   = y * y;
  227.         final double z2   = z * z;
  228.         final double r2   = x2 + y2 + z2;
  229.         final double r    = FastMath.sqrt (r2);
  230.         final double rho2 = x2 + y2;
  231.         final double rho  = FastMath.sqrt(rho2);
  232.         final double t    = z / r;   // cos(theta), where theta is the polar angle
  233.         final double u    = rho / r; // sin(theta), where theta is the polar angle
  234.         final double tOu  = z / rho;

  235.         // compute distance powers
  236.         final double[] aOrN = createDistancePowersArray(provider.getAe() / r);

  237.         // compute longitude cosines/sines
  238.         final double[][] cosSinLambda = createCosSinArrays(position.getX() / rho, position.getY() / rho);

  239.         // outer summation over order
  240.         int    index = 0;
  241.         double value = 0;
  242.         final double[] gradient = new double[3];
  243.         for (int m = degree; m >= 0; --m) {

  244.             // compute tesseral terms with derivatives
  245.             index = computeTesseral(m, degree, index, t, u, tOu,
  246.                                     pnm0Plus2, pnm0Plus1, null, pnm0, pnm1, null);

  247.             if (m <= order) {
  248.                 // compute contribution of current order to field (equation 5 of the paper)

  249.                 // inner summation over degree, for fixed order
  250.                 double sumDegreeS        = 0;
  251.                 double sumDegreeC        = 0;
  252.                 double dSumDegreeSdR     = 0;
  253.                 double dSumDegreeCdR     = 0;
  254.                 double dSumDegreeSdTheta = 0;
  255.                 double dSumDegreeCdTheta = 0;
  256.                 for (int n = FastMath.max(2, m); n <= degree; ++n) {
  257.                     final double qSnm  = aOrN[n] * harmonics.getNormalizedSnm(n, m);
  258.                     final double qCnm  = aOrN[n] * harmonics.getNormalizedCnm(n, m);
  259.                     final double nOr   = n / r;
  260.                     final double s0    = pnm0[n] * qSnm;
  261.                     final double c0    = pnm0[n] * qCnm;
  262.                     final double s1    = pnm1[n] * qSnm;
  263.                     final double c1    = pnm1[n] * qCnm;
  264.                     sumDegreeS        += s0;
  265.                     sumDegreeC        += c0;
  266.                     dSumDegreeSdR     -= nOr * s0;
  267.                     dSumDegreeCdR     -= nOr * c0;
  268.                     dSumDegreeSdTheta += s1;
  269.                     dSumDegreeCdTheta += c1;
  270.                 }

  271.                 // contribution to outer summation over order
  272.                 // beware that we need to order gradient using the mathematical conventions
  273.                 // compliant with the SphericalCoordinates class, so our lambda is its theta
  274.                 // (and hence at index 1) and our theta is its phi (and hence at index 2)
  275.                 final double sML = cosSinLambda[1][m];
  276.                 final double cML = cosSinLambda[0][m];
  277.                 value            = value       * u + sML * sumDegreeS        + cML * sumDegreeC;
  278.                 gradient[0]      = gradient[0] * u + sML * dSumDegreeSdR     + cML * dSumDegreeCdR;
  279.                 gradient[1]      = gradient[1] * u + m * (cML * sumDegreeS - sML * sumDegreeC);
  280.                 gradient[2]      = gradient[2] * u + sML * dSumDegreeSdTheta + cML * dSumDegreeCdTheta;

  281.             }

  282.             // rotate the recursion arrays
  283.             final double[] tmp = pnm0Plus2;
  284.             pnm0Plus2 = pnm0Plus1;
  285.             pnm0Plus1 = pnm0;
  286.             pnm0      = tmp;

  287.         }

  288.         // scale back
  289.         value       = FastMath.scalb(value,       SCALING);
  290.         gradient[0] = FastMath.scalb(gradient[0], SCALING);
  291.         gradient[1] = FastMath.scalb(gradient[1], SCALING);
  292.         gradient[2] = FastMath.scalb(gradient[2], SCALING);

  293.         // apply the global mu/r factor
  294.         final double muOr = mu / r;
  295.         value            *= muOr;
  296.         gradient[0]       = muOr * gradient[0] - value / r;
  297.         gradient[1]      *= muOr;
  298.         gradient[2]      *= muOr;

  299.         // convert gradient from spherical to Cartesian
  300.         return new SphericalCoordinates(position).toCartesianGradient(gradient);

  301.     }

  302.     /** Compute both the gradient and the hessian of the non-central part of the gravity field.
  303.      * @param date current date
  304.      * @param position position at which gravity field is desired in body frame
  305.      * @return gradient and hessian of the non-central part of the gravity field
  306.      * @exception OrekitException if position cannot be converted to central body frame
  307.      */
  308.     public GradientHessian gradientHessian(final AbsoluteDate date, final Vector3D position)
  309.         throws OrekitException {

  310.         final int degree = provider.getMaxDegree();
  311.         final int order  = provider.getMaxOrder();
  312.         final NormalizedSphericalHarmonics harmonics = provider.onDate(date);

  313.         // allocate the columns for recursion
  314.         double[] pnm0Plus2  = new double[degree + 1];
  315.         double[] pnm0Plus1  = new double[degree + 1];
  316.         double[] pnm0       = new double[degree + 1];
  317.         double[] pnm1Plus1  = new double[degree + 1];
  318.         double[] pnm1       = new double[degree + 1];
  319.         final double[] pnm2 = new double[degree + 1];

  320.         // compute polar coordinates
  321.         final double x    = position.getX();
  322.         final double y    = position.getY();
  323.         final double z    = position.getZ();
  324.         final double x2   = x * x;
  325.         final double y2   = y * y;
  326.         final double z2   = z * z;
  327.         final double r2   = x2 + y2 + z2;
  328.         final double r    = FastMath.sqrt (r2);
  329.         final double rho2 = x2 + y2;
  330.         final double rho  = FastMath.sqrt(rho2);
  331.         final double t    = z / r;   // cos(theta), where theta is the polar angle
  332.         final double u    = rho / r; // sin(theta), where theta is the polar angle
  333.         final double tOu  = z / rho;

  334.         // compute distance powers
  335.         final double[] aOrN = createDistancePowersArray(provider.getAe() / r);

  336.         // compute longitude cosines/sines
  337.         final double[][] cosSinLambda = createCosSinArrays(position.getX() / rho, position.getY() / rho);

  338.         // outer summation over order
  339.         int    index = 0;
  340.         double value = 0;
  341.         final double[]   gradient = new double[3];
  342.         final double[][] hessian  = new double[3][3];
  343.         for (int m = degree; m >= 0; --m) {

  344.             // compute tesseral terms
  345.             index = computeTesseral(m, degree, index, t, u, tOu,
  346.                                     pnm0Plus2, pnm0Plus1, pnm1Plus1, pnm0, pnm1, pnm2);

  347.             if (m <= order) {
  348.                 // compute contribution of current order to field (equation 5 of the paper)

  349.                 // inner summation over degree, for fixed order
  350.                 double sumDegreeS               = 0;
  351.                 double sumDegreeC               = 0;
  352.                 double dSumDegreeSdR            = 0;
  353.                 double dSumDegreeCdR            = 0;
  354.                 double dSumDegreeSdTheta        = 0;
  355.                 double dSumDegreeCdTheta        = 0;
  356.                 double d2SumDegreeSdRdR         = 0;
  357.                 double d2SumDegreeSdRdTheta     = 0;
  358.                 double d2SumDegreeSdThetadTheta = 0;
  359.                 double d2SumDegreeCdRdR         = 0;
  360.                 double d2SumDegreeCdRdTheta     = 0;
  361.                 double d2SumDegreeCdThetadTheta = 0;
  362.                 for (int n = FastMath.max(2, m); n <= degree; ++n) {
  363.                     final double qSnm         = aOrN[n] * harmonics.getNormalizedSnm(n, m);
  364.                     final double qCnm         = aOrN[n] * harmonics.getNormalizedCnm(n, m);
  365.                     final double nOr          = n / r;
  366.                     final double nnP1Or2      = nOr * (n + 1) / r;
  367.                     final double s0           = pnm0[n] * qSnm;
  368.                     final double c0           = pnm0[n] * qCnm;
  369.                     final double s1           = pnm1[n] * qSnm;
  370.                     final double c1           = pnm1[n] * qCnm;
  371.                     final double s2           = pnm2[n] * qSnm;
  372.                     final double c2           = pnm2[n] * qCnm;
  373.                     sumDegreeS               += s0;
  374.                     sumDegreeC               += c0;
  375.                     dSumDegreeSdR            -= nOr * s0;
  376.                     dSumDegreeCdR            -= nOr * c0;
  377.                     dSumDegreeSdTheta        += s1;
  378.                     dSumDegreeCdTheta        += c1;
  379.                     d2SumDegreeSdRdR         += nnP1Or2 * s0;
  380.                     d2SumDegreeSdRdTheta     -= nOr * s1;
  381.                     d2SumDegreeSdThetadTheta += s2;
  382.                     d2SumDegreeCdRdR         += nnP1Or2 * c0;
  383.                     d2SumDegreeCdRdTheta     -= nOr * c1;
  384.                     d2SumDegreeCdThetadTheta += c2;
  385.                 }

  386.                 // contribution to outer summation over order
  387.                 final double sML = cosSinLambda[1][m];
  388.                 final double cML = cosSinLambda[0][m];
  389.                 value            = value         * u + sML * sumDegreeS + cML * sumDegreeC;
  390.                 gradient[0]      = gradient[0]   * u + sML * dSumDegreeSdR + cML * dSumDegreeCdR;
  391.                 gradient[1]      = gradient[1]   * u + m * (cML * sumDegreeS - sML * sumDegreeC);
  392.                 gradient[2]      = gradient[2]   * u + sML * dSumDegreeSdTheta + cML * dSumDegreeCdTheta;
  393.                 hessian[0][0]    = hessian[0][0] * u + sML * d2SumDegreeSdRdR + cML * d2SumDegreeCdRdR;
  394.                 hessian[1][0]    = hessian[1][0] * u + m * (cML * dSumDegreeSdR - sML * dSumDegreeCdR);
  395.                 hessian[2][0]    = hessian[2][0] * u + sML * d2SumDegreeSdRdTheta + cML * d2SumDegreeCdRdTheta;
  396.                 hessian[1][1]    = hessian[1][1] * u - m * m * (sML * sumDegreeS + cML * sumDegreeC);
  397.                 hessian[2][1]    = hessian[2][1] * u + m * (cML * dSumDegreeSdTheta - sML * dSumDegreeCdTheta);
  398.                 hessian[2][2]    = hessian[2][2] * u + sML * d2SumDegreeSdThetadTheta + cML * d2SumDegreeCdThetadTheta;

  399.             }

  400.             // rotate the recursion arrays
  401.             final double[] tmp0 = pnm0Plus2;
  402.             pnm0Plus2 = pnm0Plus1;
  403.             pnm0Plus1 = pnm0;
  404.             pnm0      = tmp0;
  405.             final double[] tmp1 = pnm1Plus1;
  406.             pnm1Plus1 = pnm1;
  407.             pnm1      = tmp1;

  408.         }

  409.         // scale back
  410.         value = FastMath.scalb(value, SCALING);
  411.         for (int i = 0; i < 3; ++i) {
  412.             gradient[i] = FastMath.scalb(gradient[i], SCALING);
  413.             for (int j = 0; j <= i; ++j) {
  414.                 hessian[i][j] = FastMath.scalb(hessian[i][j], SCALING);
  415.             }
  416.         }


  417.         // apply the global mu/r factor
  418.         final double muOr = mu / r;
  419.         value         *= muOr;
  420.         gradient[0]    = muOr * gradient[0] - value / r;
  421.         gradient[1]   *= muOr;
  422.         gradient[2]   *= muOr;
  423.         hessian[0][0]  = muOr * hessian[0][0] - 2 * gradient[0] / r;
  424.         hessian[1][0]  = muOr * hessian[1][0] -     gradient[1] / r;
  425.         hessian[2][0]  = muOr * hessian[2][0] -     gradient[2] / r;
  426.         hessian[1][1] *= muOr;
  427.         hessian[2][1] *= muOr;
  428.         hessian[2][2] *= muOr;

  429.         // convert gradient and Hessian from spherical to Cartesian
  430.         final SphericalCoordinates sc = new SphericalCoordinates(position);
  431.         return new GradientHessian(sc.toCartesianGradient(gradient),
  432.                                    sc.toCartesianHessian(hessian, gradient));


  433.     }

  434.     /** Container for gradient and Hessian. */
  435.     public static class GradientHessian implements Serializable {

  436.         /** Serializable UID. */
  437.         private static final long serialVersionUID = 20130219L;

  438.         /** Gradient. */
  439.         private final double[] gradient;

  440.         /** Hessian. */
  441.         private final double[][] hessian;

  442.         /** Simple constructor.
  443.          * <p>
  444.          * A reference to the arrays is stored, they are <strong>not</strong> cloned.
  445.          * </p>
  446.          * @param gradient gradient
  447.          * @param hessian hessian
  448.          */
  449.         public GradientHessian(final double[] gradient, final double[][] hessian) {
  450.             this.gradient = gradient;
  451.             this.hessian  = hessian;
  452.         }

  453.         /** Get a reference to the gradient.
  454.          * @return gradient (a reference to the internal array is returned)
  455.          */
  456.         public double[] getGradient() {
  457.             return gradient;
  458.         }

  459.         /** Get a reference to the Hessian.
  460.          * @return Hessian (a reference to the internal array is returned)
  461.          */
  462.         public double[][] getHessian() {
  463.             return hessian;
  464.         }

  465.     }

  466.     /** Compute a/r powers array.
  467.      * @param aOr a/r
  468.      * @return array containing (a/r)<sup>n</sup>
  469.      */
  470.     private double[] createDistancePowersArray(final double aOr) {

  471.         // initialize array
  472.         final double[] aOrN = new double[provider.getMaxDegree() + 1];
  473.         aOrN[0] = 1;
  474.         aOrN[1] = aOr;

  475.         // fill up array
  476.         for (int n = 2; n < aOrN.length; ++n) {
  477.             final int p = n / 2;
  478.             final int q = n - p;
  479.             aOrN[n] = aOrN[p] * aOrN[q];
  480.         }

  481.         return aOrN;

  482.     }

  483.     /** Compute longitude cosines and sines.
  484.      * @param cosLambda cos(&lambda;)
  485.      * @param sinLambda sin(&lambda;)
  486.      * @return array containing cos(m &times; &lambda;) in row 0
  487.      * and sin(m &times; &lambda;) in row 1
  488.      */
  489.     private double[][] createCosSinArrays(final double cosLambda, final double sinLambda) {

  490.         // initialize arrays
  491.         final double[][] cosSin = new double[2][provider.getMaxOrder() + 1];
  492.         cosSin[0][0] = 1;
  493.         cosSin[1][0] = 0;
  494.         if (provider.getMaxOrder() > 0) {
  495.             cosSin[0][1] = cosLambda;
  496.             cosSin[1][1] = sinLambda;

  497.             // fill up array
  498.             for (int m = 2; m < cosSin[0].length; ++m) {

  499.                 // m * lambda is split as p * lambda + q * lambda, trying to avoid
  500.                 // p or q being much larger than the other. This reduces the number of
  501.                 // intermediate results reused to compute each value, and hence should limit
  502.                 // as much as possible roundoff error accumulation
  503.                 // (this does not change the number of floating point operations)
  504.                 final int p = m / 2;
  505.                 final int q = m - p;

  506.                 cosSin[0][m] = cosSin[0][p] * cosSin[0][q] - cosSin[1][p] * cosSin[1][q];
  507.                 cosSin[1][m] = cosSin[1][p] * cosSin[0][q] + cosSin[0][p] * cosSin[1][q];

  508.             }
  509.         }

  510.         return cosSin;

  511.     }

  512.     /** Compute one order of tesseral terms.
  513.      * <p>
  514.      * This corresponds to equations 27 and 30 of the paper.
  515.      * </p>
  516.      * @param m current order
  517.      * @param degree max degree
  518.      * @param index index in the flattened array
  519.      * @param t cos(&theta;), where &theta; is the polar angle
  520.      * @param u sin(&theta;), where &theta; is the polar angle
  521.      * @param tOu t/u
  522.      * @param pnm0Plus2 array containing scaled P<sub>n,m+2</sub>/u<sup>m+2</sup>
  523.      * @param pnm0Plus1 array containing scaled P<sub>n,m+1</sub>/u<sup>m+1</sup>
  524.      * @param pnm1Plus1 array containing scaled dP<sub>n,m+1</sub>/u<sup>m+1</sup>
  525.      * (may be null if second derivatives are not needed)
  526.      * @param pnm0 array to fill with scaled P<sub>n,m</sub>/u<sup>m</sup>
  527.      * @param pnm1 array to fill with scaled dP<sub>n,m</sub>/u<sup>m</sup>
  528.      * (may be null if first derivatives are not needed)
  529.      * @param pnm2 array to fill with scaled d<sup>2</sup>P<sub>n,m</sub>/u<sup>m</sup>
  530.      * (may be null if second derivatives are not needed)
  531.      * @return new value for index
  532.      */
  533.     private int computeTesseral(final int m, final int degree, final int index,
  534.                                 final double t, final double u, final double tOu,
  535.                                 final double[] pnm0Plus2, final double[] pnm0Plus1, final double[] pnm1Plus1,
  536.                                 final double[] pnm0, final double[] pnm1, final double[] pnm2) {

  537.         final double u2 = u * u;

  538.         // initialize recursion from sectorial terms
  539.         int n = FastMath.max(2, m);
  540.         if (n == m) {
  541.             pnm0[n] = sectorial[n];
  542.             ++n;
  543.         }

  544.         // compute tesseral values
  545.         int localIndex = index;
  546.         while (n <= degree) {

  547.             // value (equation 27 of the paper)
  548.             pnm0[n] = gnmOj[localIndex] * t * pnm0Plus1[n] - hnmOj[localIndex] * u2 * pnm0Plus2[n];

  549.             ++localIndex;
  550.             ++n;

  551.         }

  552.         if (pnm1 != null) {

  553.             // initialize recursion from sectorial terms
  554.             n = FastMath.max(2, m);
  555.             if (n == m) {
  556.                 pnm1[n] = m * tOu * pnm0[n];
  557.                 ++n;
  558.             }

  559.             // compute tesseral values and derivatives with respect to polar angle
  560.             localIndex = index;
  561.             while (n <= degree) {

  562.                 // first derivative (equation 30 of the paper)
  563.                 pnm1[n] = m * tOu * pnm0[n] - enm[localIndex] * u * pnm0Plus1[n];

  564.                 ++localIndex;
  565.                 ++n;

  566.             }

  567.             if (pnm2 != null) {

  568.                 // initialize recursion from sectorial terms
  569.                 n = FastMath.max(2, m);
  570.                 if (n == m) {
  571.                     pnm2[n] = m * (tOu * pnm1[n] - pnm0[n] / u2);
  572.                     ++n;
  573.                 }

  574.                 // compute tesseral values and derivatives with respect to polar angle
  575.                 localIndex = index;
  576.                 while (n <= degree) {

  577.                     // second derivative (differential of equation 30 with respect to theta)
  578.                     pnm2[n] = m * (tOu * pnm1[n] - pnm0[n] / u2) - enm[localIndex] * u * pnm1Plus1[n];

  579.                     ++localIndex;
  580.                     ++n;

  581.                 }

  582.             }

  583.         }

  584.         return localIndex;

  585.     }

  586.     /** {@inheritDoc} */
  587.     public void addContribution(final SpacecraftState s, final TimeDerivativesEquations adder)
  588.         throws OrekitException {

  589.         // get the position in body frame
  590.         final AbsoluteDate date       = s.getDate();
  591.         final Transform fromBodyFrame = bodyFrame.getTransformTo(s.getFrame(), date);
  592.         final Transform toBodyFrame   = fromBodyFrame.getInverse();
  593.         final Vector3D position       = toBodyFrame.transformPosition(s.getPVCoordinates().getPosition());

  594.         // gradient of the non-central part of the gravity field
  595.         final Vector3D gInertial = fromBodyFrame.transformVector(new Vector3D(gradient(date, position)));

  596.         adder.addXYZAcceleration(gInertial.getX(), gInertial.getY(), gInertial.getZ());

  597.     }

  598.     /** {@inheritDoc} */
  599.     public EventDetector[] getEventsDetectors() {
  600.         return new EventDetector[0];
  601.     }

  602.     /** {@inheritDoc} */
  603.     public double getParameter(final String name)
  604.         throws IllegalArgumentException {
  605.         complainIfNotSupported(name);
  606.         return mu;
  607.     }

  608.     /** {@inheritDoc} */
  609.     public void setParameter(final String name, final double value)
  610.         throws IllegalArgumentException {
  611.         complainIfNotSupported(name);
  612.         mu = value;
  613.     }

  614.     /** {@inheritDoc} */
  615.     public FieldVector3D<DerivativeStructure> accelerationDerivatives(final AbsoluteDate date, final Frame frame,
  616.                                                                       final FieldVector3D<DerivativeStructure> position, final FieldVector3D<DerivativeStructure> velocity,
  617.                                                                       final FieldRotation<DerivativeStructure> rotation, final DerivativeStructure mass)
  618.         throws OrekitException {

  619.         // get the position in body frame
  620.         final Transform fromBodyFrame = bodyFrame.getTransformTo(frame, date);
  621.         final Transform toBodyFrame   = fromBodyFrame.getInverse();
  622.         final Vector3D positionBody   = toBodyFrame.transformPosition(position.toVector3D());

  623.         // compute gradient and Hessian
  624.         final GradientHessian gh   = gradientHessian(date, positionBody);

  625.         // gradient of the non-central part of the gravity field
  626.         final double[] gInertial = fromBodyFrame.transformVector(new Vector3D(gh.getGradient())).toArray();

  627.         // Hessian of the non-central part of the gravity field
  628.         final RealMatrix hBody     = new Array2DRowRealMatrix(gh.getHessian(), false);
  629.         final RealMatrix rot       = new Array2DRowRealMatrix(toBodyFrame.getRotation().getMatrix());
  630.         final RealMatrix hInertial = rot.transpose().multiply(hBody).multiply(rot);

  631.         // distribute all partial derivatives in a compact acceleration vector
  632.         final int parameters       = mass.getFreeParameters();
  633.         final int order            = mass.getOrder();
  634.         final double[] derivatives = new double[1 + parameters];
  635.         final DerivativeStructure[] accDer = new DerivativeStructure[3];
  636.         for (int i = 0; i < 3; ++i) {

  637.             // first element is value of acceleration (i.e. gradient of field)
  638.             derivatives[0] = gInertial[i];

  639.             // next three elements are one row of the Jacobian of acceleration (i.e. Hessian of field)
  640.             derivatives[1] = hInertial.getEntry(i, 0);
  641.             derivatives[2] = hInertial.getEntry(i, 1);
  642.             derivatives[3] = hInertial.getEntry(i, 2);

  643.             // next elements (three or four depending on mass being used or not) are left as 0

  644.             accDer[i] = new DerivativeStructure(parameters, order, derivatives);

  645.         }

  646.         return new FieldVector3D<DerivativeStructure>(accDer);

  647.     }

  648.     /** {@inheritDoc} */
  649.     public FieldVector3D<DerivativeStructure> accelerationDerivatives(final SpacecraftState s, final String paramName)
  650.         throws OrekitException, IllegalArgumentException {

  651.         complainIfNotSupported(paramName);

  652.         // get the position in body frame
  653.         final AbsoluteDate date       = s.getDate();
  654.         final Transform fromBodyFrame = bodyFrame.getTransformTo(s.getFrame(), date);
  655.         final Transform toBodyFrame   = fromBodyFrame.getInverse();
  656.         final Vector3D position       = toBodyFrame.transformPosition(s.getPVCoordinates().getPosition());

  657.         // gradient of the non-central part of the gravity field
  658.         final Vector3D gInertial = fromBodyFrame.transformVector(new Vector3D(gradient(date, position)));

  659.         return new FieldVector3D<DerivativeStructure>(new DerivativeStructure(1, 1, gInertial.getX(), gInertial.getX() / mu),
  660.                               new DerivativeStructure(1, 1, gInertial.getY(), gInertial.getY() / mu),
  661.                               new DerivativeStructure(1, 1, gInertial.getZ(), gInertial.getZ() / mu));

  662.     }

  663. }