1   /* Copyright 2002-2022 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.forces.gravity;
18  
19  
20  import java.util.Collections;
21  import java.util.List;
22  import java.util.stream.Stream;
23  
24  import org.hipparchus.CalculusFieldElement;
25  import org.hipparchus.Field;
26  import org.hipparchus.analysis.differentiation.DerivativeStructure;
27  import org.hipparchus.analysis.differentiation.Gradient;
28  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
29  import org.hipparchus.geometry.euclidean.threed.SphericalCoordinates;
30  import org.hipparchus.geometry.euclidean.threed.Vector3D;
31  import org.hipparchus.linear.Array2DRowRealMatrix;
32  import org.hipparchus.linear.RealMatrix;
33  import org.hipparchus.util.FastMath;
34  import org.hipparchus.util.MathArrays;
35  import org.orekit.forces.AbstractForceModel;
36  import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
37  import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider.NormalizedSphericalHarmonics;
38  import org.orekit.forces.gravity.potential.TideSystem;
39  import org.orekit.forces.gravity.potential.TideSystemProvider;
40  import org.orekit.frames.Frame;
41  import org.orekit.frames.StaticTransform;
42  import org.orekit.propagation.FieldSpacecraftState;
43  import org.orekit.propagation.SpacecraftState;
44  import org.orekit.propagation.events.EventDetector;
45  import org.orekit.propagation.events.FieldEventDetector;
46  import org.orekit.time.AbsoluteDate;
47  import org.orekit.time.FieldAbsoluteDate;
48  import org.orekit.utils.FieldPVCoordinates;
49  import org.orekit.utils.ParameterDriver;
50  
51  /** This class represents the gravitational field of a celestial body.
52   * <p>
53   * The algorithm implemented in this class has been designed by S. A. Holmes
54   * and W. E. Featherstone from Department of Spatial Sciences, Curtin University
55   * of Technology, Perth, Australia. It is described in their 2002 paper: <a
56   * href="https://www.researchgate.net/publication/226460594_A_unified_approach_to_the_Clenshaw_summation_and_the_recursive_computation_of_very_high_degree_and_order_normalised_associated_Legendre_functions">
57   * A unified approach to he Clenshaw summation and the recursive computation of
58   * very high degree and order normalised associated Legendre functions</a>
59   * (Journal of Geodesy (2002) 76: 279–299).
60   * </p>
61   * <p>
62   * This model directly uses normalized coefficients and stable recursion algorithms
63   * so it is more suited to high degree gravity fields than the classical Cunningham
64   * Droziner models which use un-normalized coefficients.
65   * </p>
66   * <p>
67   * Among the different algorithms presented in Holmes and Featherstone paper, this
68   * class implements the <em>modified forward row method</em>. All recursion coefficients
69   * are precomputed and stored for greater performance. This caching was suggested in the
70   * paper but not used due to the large memory requirements. Since 2002, even low end
71   * computers and mobile devices do have sufficient memory so this caching has become
72   * feasible nowadays.
73   * <p>
74   * @author Luc Maisonobe
75   * @since 6.0
76   */
77  
78  public class HolmesFeatherstoneAttractionModel extends AbstractForceModel implements TideSystemProvider {
79  
80      /** Exponent scaling to avoid floating point overflow.
81       * <p>The paper uses 10^280, we prefer a power of two to preserve accuracy thanks to
82       * {@link FastMath#scalb(double, int)}, so we use 2^930 which has the same order of magnitude.
83       */
84      private static final int SCALING = 930;
85  
86      /** Central attraction scaling factor.
87       * <p>
88       * We use a power of 2 to avoid numeric noise introduction
89       * in the multiplications/divisions sequences.
90       * </p>
91       */
92      private static final double MU_SCALE = FastMath.scalb(1.0, 32);
93  
94      /** Driver for gravitational parameter. */
95      private final ParameterDriver gmParameterDriver;
96  
97      /** Provider for the spherical harmonics. */
98      private final NormalizedSphericalHarmonicsProvider provider;
99  
100     /** Rotating body. */
101     private final Frame bodyFrame;
102 
103     /** Recursion coefficients g<sub>n,m</sub>/√j. */
104     private final double[] gnmOj;
105 
106     /** Recursion coefficients h<sub>n,m</sub>/√j. */
107     private final double[] hnmOj;
108 
109     /** Recursion coefficients e<sub>n,m</sub>. */
110     private final double[] enm;
111 
112     /** Scaled sectorial Pbar<sub>m,m</sub>/u<sup>m</sup> &times; 2<sup>-SCALING</sup>. */
113     private final double[] sectorial;
114 
115     /** Creates a new instance.
116      * @param centralBodyFrame rotating body frame
117      * @param provider provider for spherical harmonics
118      * @since 6.0
119      */
120     public HolmesFeatherstoneAttractionModel(final Frame centralBodyFrame,
121                                              final NormalizedSphericalHarmonicsProvider provider) {
122 
123         gmParameterDriver = new ParameterDriver(NewtonianAttraction.CENTRAL_ATTRACTION_COEFFICIENT,
124                                                 provider.getMu(), MU_SCALE, 0.0, Double.POSITIVE_INFINITY);
125 
126         this.provider  = provider;
127         this.bodyFrame = centralBodyFrame;
128 
129         // the pre-computed arrays hold coefficients from triangular arrays in a single
130         // storing neither diagonal elements (n = m) nor the non-diagonal element n=1, m=0
131         final int degree = provider.getMaxDegree();
132         final int size = FastMath.max(0, degree * (degree + 1) / 2 - 1);
133         gnmOj = new double[size];
134         hnmOj = new double[size];
135         enm   = new double[size];
136 
137         // pre-compute the recursion coefficients corresponding to equations 19 and 22
138         // from Holmes and Featherstone paper
139         // for cache efficiency, elements are stored in the same order they will be used
140         // later on, i.e. from rightmost column to leftmost column
141         int index = 0;
142         for (int m = degree; m >= 0; --m) {
143             final int j = (m == 0) ? 2 : 1;
144             for (int n = FastMath.max(2, m + 1); n <= degree; ++n) {
145                 final double f = (n - m) * (n + m + 1);
146                 gnmOj[index] = 2 * (m + 1) / FastMath.sqrt(j * f);
147                 hnmOj[index] = FastMath.sqrt((n + m + 2) * (n - m - 1) / (j * f));
148                 enm[index]   = FastMath.sqrt(f / j);
149                 ++index;
150             }
151         }
152 
153         // scaled sectorial terms corresponding to equation 28 in Holmes and Featherstone paper
154         sectorial    = new double[degree + 1];
155         sectorial[0] = FastMath.scalb(1.0, -SCALING);
156         if (degree > 0) {
157             sectorial[1] = FastMath.sqrt(3) * sectorial[0];
158         }
159         for (int m = 2; m < sectorial.length; ++m) {
160             sectorial[m] = FastMath.sqrt((2 * m + 1) / (2.0 * m)) * sectorial[m - 1];
161         }
162 
163     }
164 
165     /** {@inheritDoc} */
166     @Override
167     public boolean dependsOnPositionOnly() {
168         return true;
169     }
170 
171     /** {@inheritDoc} */
172     public TideSystem getTideSystem() {
173         return provider.getTideSystem();
174     }
175 
176     /** Get the central attraction coefficient μ.
177      * @return mu central attraction coefficient (m³/s²)
178      */
179     public double getMu() {
180         return gmParameterDriver.getValue();
181     }
182 
183     /** Compute the value of the gravity field.
184      * @param date current date
185      * @param position position at which gravity field is desired in body frame
186      * @param mu central attraction coefficient to use
187      * @return value of the gravity field (central and non-central parts summed together)
188      */
189     public double value(final AbsoluteDate date, final Vector3D position,
190                         final double mu) {
191         return mu / position.getNorm() + nonCentralPart(date, position, mu);
192     }
193 
194     /** Compute the non-central part of the gravity field.
195      * @param date current date
196      * @param position position at which gravity field is desired in body frame
197      * @param mu central attraction coefficient to use
198      * @return value of the non-central part of the gravity field
199      */
200     public double nonCentralPart(final AbsoluteDate date, final Vector3D position, final double mu) {
201 
202         final int degree = provider.getMaxDegree();
203         final int order  = provider.getMaxOrder();
204         final NormalizedSphericalHarmonics harmonics = provider.onDate(date);
205 
206         // allocate the columns for recursion
207         double[] pnm0Plus2 = new double[degree + 1];
208         double[] pnm0Plus1 = new double[degree + 1];
209         double[] pnm0      = new double[degree + 1];
210 
211         // compute polar coordinates
212         final double x   = position.getX();
213         final double y   = position.getY();
214         final double z   = position.getZ();
215         final double x2  = x * x;
216         final double y2  = y * y;
217         final double z2  = z * z;
218         final double r2  = x2 + y2 + z2;
219         final double r   = FastMath.sqrt (r2);
220         final double rho = FastMath.sqrt(x2 + y2);
221         final double t   = z / r;   // cos(theta), where theta is the polar angle
222         final double u   = rho / r; // sin(theta), where theta is the polar angle
223         final double tOu = z / rho;
224 
225         // compute distance powers
226         final double[] aOrN = createDistancePowersArray(provider.getAe() / r);
227 
228         // compute longitude cosines/sines
229         final double[][] cosSinLambda = createCosSinArrays(position.getX() / rho, position.getY() / rho);
230 
231         // outer summation over order
232         int    index = 0;
233         double value = 0;
234         for (int m = degree; m >= 0; --m) {
235 
236             // compute tesseral terms without derivatives
237             index = computeTesseral(m, degree, index, t, u, tOu,
238                                     pnm0Plus2, pnm0Plus1, null, pnm0, null, null);
239 
240             if (m <= order) {
241                 // compute contribution of current order to field (equation 5 of the paper)
242 
243                 // inner summation over degree, for fixed order
244                 double sumDegreeS        = 0;
245                 double sumDegreeC        = 0;
246                 for (int n = FastMath.max(2, m); n <= degree; ++n) {
247                     sumDegreeS += pnm0[n] * aOrN[n] * harmonics.getNormalizedSnm(n, m);
248                     sumDegreeC += pnm0[n] * aOrN[n] * harmonics.getNormalizedCnm(n, m);
249                 }
250 
251                 // contribution to outer summation over order
252                 value = value * u + cosSinLambda[1][m] * sumDegreeS + cosSinLambda[0][m] * sumDegreeC;
253 
254             }
255 
256             // rotate the recursion arrays
257             final double[] tmp = pnm0Plus2;
258             pnm0Plus2 = pnm0Plus1;
259             pnm0Plus1 = pnm0;
260             pnm0      = tmp;
261 
262         }
263 
264         // scale back
265         value = FastMath.scalb(value, SCALING);
266 
267         // apply the global mu/r factor
268         return mu * value / r;
269 
270     }
271 
272     /** Compute the gradient of the non-central part of the gravity field.
273      * @param date current date
274      * @param position position at which gravity field is desired in body frame
275      * @param mu central attraction coefficient to use
276      * @return gradient of the non-central part of the gravity field
277      */
278     public double[] gradient(final AbsoluteDate date, final Vector3D position, final double mu) {
279 
280         final int degree = provider.getMaxDegree();
281         final int order  = provider.getMaxOrder();
282         final NormalizedSphericalHarmonics harmonics = provider.onDate(date);
283 
284         // allocate the columns for recursion
285         double[] pnm0Plus2  = new double[degree + 1];
286         double[] pnm0Plus1  = new double[degree + 1];
287         double[] pnm0       = new double[degree + 1];
288         final double[] pnm1 = new double[degree + 1];
289 
290         // compute polar coordinates
291         final double x    = position.getX();
292         final double y    = position.getY();
293         final double z    = position.getZ();
294         final double x2   = x * x;
295         final double y2   = y * y;
296         final double z2   = z * z;
297         final double r2   = x2 + y2 + z2;
298         final double r    = FastMath.sqrt (r2);
299         final double rho2 = x2 + y2;
300         final double rho  = FastMath.sqrt(rho2);
301         final double t    = z / r;   // cos(theta), where theta is the polar angle
302         final double u    = rho / r; // sin(theta), where theta is the polar angle
303         final double tOu  = z / rho;
304 
305         // compute distance powers
306         final double[] aOrN = createDistancePowersArray(provider.getAe() / r);
307 
308         // compute longitude cosines/sines
309         final double[][] cosSinLambda = createCosSinArrays(position.getX() / rho, position.getY() / rho);
310 
311         // outer summation over order
312         int    index = 0;
313         double value = 0;
314         final double[] gradient = new double[3];
315         for (int m = degree; m >= 0; --m) {
316 
317             // compute tesseral terms with derivatives
318             index = computeTesseral(m, degree, index, t, u, tOu,
319                                     pnm0Plus2, pnm0Plus1, null, pnm0, pnm1, null);
320 
321             if (m <= order) {
322                 // compute contribution of current order to field (equation 5 of the paper)
323 
324                 // inner summation over degree, for fixed order
325                 double sumDegreeS        = 0;
326                 double sumDegreeC        = 0;
327                 double dSumDegreeSdR     = 0;
328                 double dSumDegreeCdR     = 0;
329                 double dSumDegreeSdTheta = 0;
330                 double dSumDegreeCdTheta = 0;
331                 for (int n = FastMath.max(2, m); n <= degree; ++n) {
332                     final double qSnm  = aOrN[n] * harmonics.getNormalizedSnm(n, m);
333                     final double qCnm  = aOrN[n] * harmonics.getNormalizedCnm(n, m);
334                     final double nOr   = n / r;
335                     final double s0    = pnm0[n] * qSnm;
336                     final double c0    = pnm0[n] * qCnm;
337                     final double s1    = pnm1[n] * qSnm;
338                     final double c1    = pnm1[n] * qCnm;
339                     sumDegreeS        += s0;
340                     sumDegreeC        += c0;
341                     dSumDegreeSdR     -= nOr * s0;
342                     dSumDegreeCdR     -= nOr * c0;
343                     dSumDegreeSdTheta += s1;
344                     dSumDegreeCdTheta += c1;
345                 }
346 
347                 // contribution to outer summation over order
348                 // beware that we need to order gradient using the mathematical conventions
349                 // compliant with the SphericalCoordinates class, so our lambda is its theta
350                 // (and hence at index 1) and our theta is its phi (and hence at index 2)
351                 final double sML = cosSinLambda[1][m];
352                 final double cML = cosSinLambda[0][m];
353                 value            = value       * u + sML * sumDegreeS        + cML * sumDegreeC;
354                 gradient[0]      = gradient[0] * u + sML * dSumDegreeSdR     + cML * dSumDegreeCdR;
355                 gradient[1]      = gradient[1] * u + m * (cML * sumDegreeS - sML * sumDegreeC);
356                 gradient[2]      = gradient[2] * u + sML * dSumDegreeSdTheta + cML * dSumDegreeCdTheta;
357 
358             }
359 
360             // rotate the recursion arrays
361             final double[] tmp = pnm0Plus2;
362             pnm0Plus2 = pnm0Plus1;
363             pnm0Plus1 = pnm0;
364             pnm0      = tmp;
365 
366         }
367 
368         // scale back
369         value       = FastMath.scalb(value,       SCALING);
370         gradient[0] = FastMath.scalb(gradient[0], SCALING);
371         gradient[1] = FastMath.scalb(gradient[1], SCALING);
372         gradient[2] = FastMath.scalb(gradient[2], SCALING);
373 
374         // apply the global mu/r factor
375         final double muOr = mu / r;
376         value            *= muOr;
377         gradient[0]       = muOr * gradient[0] - value / r;
378         gradient[1]      *= muOr;
379         gradient[2]      *= muOr;
380 
381         // convert gradient from spherical to Cartesian
382         return new SphericalCoordinates(position).toCartesianGradient(gradient);
383 
384     }
385 
386     /** Compute the gradient of the non-central part of the gravity field.
387      * @param date current date
388      * @param position position at which gravity field is desired in body frame
389      * @param mu central attraction coefficient to use
390      * @param <T> type of field used
391      * @return gradient of the non-central part of the gravity field
392      */
393     public <T extends CalculusFieldElement<T>> T[] gradient(final FieldAbsoluteDate<T> date, final FieldVector3D<T> position,
394                                                         final T mu) {
395 
396         final int degree = provider.getMaxDegree();
397         final int order  = provider.getMaxOrder();
398         final NormalizedSphericalHarmonics harmonics = provider.onDate(date.toAbsoluteDate());
399         final T zero = date.getField().getZero();
400         // allocate the columns for recursion
401         T[] pnm0Plus2  = MathArrays.buildArray(date.getField(), degree + 1);
402         T[] pnm0Plus1  = MathArrays.buildArray(date.getField(), degree + 1);
403         T[] pnm0       = MathArrays.buildArray(date.getField(), degree + 1);
404         final T[] pnm1 = MathArrays.buildArray(date.getField(), degree + 1);
405 
406         // compute polar coordinates
407         final T x    = position.getX();
408         final T y    = position.getY();
409         final T z    = position.getZ();
410         final T x2   = x.multiply(x);
411         final T y2   = y.multiply(y);
412         final T z2   = z.multiply(z);
413         final T r2   = x2.add(y2).add(z2);
414         final T r    = r2.sqrt();
415         final T rho2 = x2.add(y2);
416         final T rho  = rho2.sqrt();
417         final T t    = z.divide(r);   // cos(theta), where theta is the polar angle
418         final T u    = rho.divide(r); // sin(theta), where theta is the polar angle
419         final T tOu  = z.divide(rho);
420 
421         // compute distance powers
422         final T[] aOrN = createDistancePowersArray(r.reciprocal().multiply(provider.getAe()));
423 
424         // compute longitude cosines/sines
425         final T[][] cosSinLambda = createCosSinArrays(rho.reciprocal().multiply(position.getX()), rho.reciprocal().multiply(position.getY()));
426         // outer summation over order
427         int    index = 0;
428         T value = zero;
429         final T[] gradient = MathArrays.buildArray(zero.getField(), 3);
430         for (int m = degree; m >= 0; --m) {
431 
432             // compute tesseral terms with derivatives
433             index = computeTesseral(m, degree, index, t, u, tOu,
434                                     pnm0Plus2, pnm0Plus1, null, pnm0, pnm1, null);
435             if (m <= order) {
436                 // compute contribution of current order to field (equation 5 of the paper)
437 
438                 // inner summation over degree, for fixed order
439                 T sumDegreeS        = zero;
440                 T sumDegreeC        = zero;
441                 T dSumDegreeSdR     = zero;
442                 T dSumDegreeCdR     = zero;
443                 T dSumDegreeSdTheta = zero;
444                 T dSumDegreeCdTheta = zero;
445                 for (int n = FastMath.max(2, m); n <= degree; ++n) {
446                     final T qSnm  = aOrN[n].multiply(harmonics.getNormalizedSnm(n, m));
447                     final T qCnm  = aOrN[n].multiply(harmonics.getNormalizedCnm(n, m));
448                     final T nOr   = r.reciprocal().multiply(n);
449                     final T s0    = pnm0[n].multiply(qSnm);
450                     final T c0    = pnm0[n].multiply(qCnm);
451                     final T s1    = pnm1[n].multiply(qSnm);
452                     final T c1    = pnm1[n].multiply(qCnm);
453                     sumDegreeS        = sumDegreeS       .add(s0);
454                     sumDegreeC        = sumDegreeC       .add(c0);
455                     dSumDegreeSdR     = dSumDegreeSdR    .subtract(nOr.multiply(s0));
456                     dSumDegreeCdR     = dSumDegreeCdR    .subtract(nOr.multiply(c0));
457                     dSumDegreeSdTheta = dSumDegreeSdTheta.add(s1);
458                     dSumDegreeCdTheta = dSumDegreeCdTheta.add(c1);
459                 }
460 
461                 // contribution to outer summation over order
462                 // beware that we need to order gradient using the mathematical conventions
463                 // compliant with the SphericalCoordinates class, so our lambda is its theta
464                 // (and hence at index 1) and our theta is its phi (and hence at index 2)
465                 final T sML = cosSinLambda[1][m];
466                 final T cML = cosSinLambda[0][m];
467                 value            = value      .multiply(u).add(sML.multiply(sumDegreeS   )).add(cML.multiply(sumDegreeC));
468                 gradient[0]      = gradient[0].multiply(u).add(sML.multiply(dSumDegreeSdR)).add(cML.multiply(dSumDegreeCdR));
469                 gradient[1]      = gradient[1].multiply(u).add(cML.multiply(sumDegreeS).subtract(sML.multiply(sumDegreeC)).multiply(m));
470                 gradient[2]      = gradient[2].multiply(u).add(sML.multiply(dSumDegreeSdTheta)).add(cML.multiply(dSumDegreeCdTheta));
471             }
472             // rotate the recursion arrays
473             final T[] tmp = pnm0Plus2;
474             pnm0Plus2 = pnm0Plus1;
475             pnm0Plus1 = pnm0;
476             pnm0      = tmp;
477 
478         }
479         // scale back
480         value       = value.scalb(SCALING);
481         gradient[0] = gradient[0].scalb(SCALING);
482         gradient[1] = gradient[1].scalb(SCALING);
483         gradient[2] = gradient[2].scalb(SCALING);
484 
485         // apply the global mu/r factor
486         final T muOr = r.reciprocal().multiply(mu);
487         value            = value.multiply(muOr);
488         gradient[0]       = muOr.multiply(gradient[0]).subtract(value.divide(r));
489         gradient[1]      = gradient[1].multiply(muOr);
490         gradient[2]      = gradient[2].multiply(muOr);
491 
492         // convert gradient from spherical to Cartesian
493         // Cartesian coordinates
494         // remaining spherical coordinates
495         final T rPos     = position.getNorm();
496         // intermediate variables
497         final T xPos    = position.getX();
498         final T yPos    = position.getY();
499         final T zPos    = position.getZ();
500         final T rho2Pos = x.multiply(x).add(y.multiply(y));
501         final T rhoPos  = rho2.sqrt();
502         final T r2Pos   = rho2.add(z.multiply(z));
503 
504         final T[][] jacobianPos = MathArrays.buildArray(zero.getField(), 3, 3);
505 
506         // row representing the gradient of r
507         jacobianPos[0][0] = xPos.divide(rPos);
508         jacobianPos[0][1] = yPos.divide(rPos);
509         jacobianPos[0][2] = zPos.divide(rPos);
510 
511         // row representing the gradient of theta
512         jacobianPos[1][0] =  yPos.negate().divide(rho2Pos);
513         jacobianPos[1][1] =  xPos.divide(rho2Pos);
514         // jacobian[1][2] is already set to 0 at allocation time
515 
516         // row representing the gradient of phi
517         jacobianPos[2][0] = xPos.multiply(zPos).divide(rhoPos.multiply(r2Pos));
518         jacobianPos[2][1] = yPos.multiply(zPos).divide(rhoPos.multiply(r2Pos));
519         jacobianPos[2][2] = rhoPos.negate().divide(r2Pos);
520         final T[] cartGradPos = MathArrays.buildArray(zero.getField(), 3);
521         cartGradPos[0] = gradient[0].multiply(jacobianPos[0][0]).add(gradient[1].multiply(jacobianPos[1][0])).add(gradient[2].multiply(jacobianPos[2][0]));
522         cartGradPos[1] = gradient[0].multiply(jacobianPos[0][1]).add(gradient[1].multiply(jacobianPos[1][1])).add(gradient[2].multiply(jacobianPos[2][1]));
523         cartGradPos[2] = gradient[0].multiply(jacobianPos[0][2])                                      .add(gradient[2].multiply(jacobianPos[2][2]));
524         return cartGradPos;
525 
526     }
527 
528     /** Compute both the gradient and the hessian of the non-central part of the gravity field.
529      * @param date current date
530      * @param position position at which gravity field is desired in body frame
531      * @param mu central attraction coefficient to use
532      * @return gradient and hessian of the non-central part of the gravity field
533      */
534     private GradientHessian gradientHessian(final AbsoluteDate date, final Vector3D position, final double mu) {
535 
536         final int degree = provider.getMaxDegree();
537         final int order  = provider.getMaxOrder();
538         final NormalizedSphericalHarmonics harmonics = provider.onDate(date);
539 
540         // allocate the columns for recursion
541         double[] pnm0Plus2  = new double[degree + 1];
542         double[] pnm0Plus1  = new double[degree + 1];
543         double[] pnm0       = new double[degree + 1];
544         double[] pnm1Plus1  = new double[degree + 1];
545         double[] pnm1       = new double[degree + 1];
546         final double[] pnm2 = new double[degree + 1];
547 
548         // compute polar coordinates
549         final double x    = position.getX();
550         final double y    = position.getY();
551         final double z    = position.getZ();
552         final double x2   = x * x;
553         final double y2   = y * y;
554         final double z2   = z * z;
555         final double r2   = x2 + y2 + z2;
556         final double r    = FastMath.sqrt (r2);
557         final double rho2 = x2 + y2;
558         final double rho  = FastMath.sqrt(rho2);
559         final double t    = z / r;   // cos(theta), where theta is the polar angle
560         final double u    = rho / r; // sin(theta), where theta is the polar angle
561         final double tOu  = z / rho;
562 
563         // compute distance powers
564         final double[] aOrN = createDistancePowersArray(provider.getAe() / r);
565 
566         // compute longitude cosines/sines
567         final double[][] cosSinLambda = createCosSinArrays(position.getX() / rho, position.getY() / rho);
568 
569         // outer summation over order
570         int    index = 0;
571         double value = 0;
572         final double[]   gradient = new double[3];
573         final double[][] hessian  = new double[3][3];
574         for (int m = degree; m >= 0; --m) {
575 
576             // compute tesseral terms
577             index = computeTesseral(m, degree, index, t, u, tOu,
578                                     pnm0Plus2, pnm0Plus1, pnm1Plus1, pnm0, pnm1, pnm2);
579 
580             if (m <= order) {
581                 // compute contribution of current order to field (equation 5 of the paper)
582 
583                 // inner summation over degree, for fixed order
584                 double sumDegreeS               = 0;
585                 double sumDegreeC               = 0;
586                 double dSumDegreeSdR            = 0;
587                 double dSumDegreeCdR            = 0;
588                 double dSumDegreeSdTheta        = 0;
589                 double dSumDegreeCdTheta        = 0;
590                 double d2SumDegreeSdRdR         = 0;
591                 double d2SumDegreeSdRdTheta     = 0;
592                 double d2SumDegreeSdThetadTheta = 0;
593                 double d2SumDegreeCdRdR         = 0;
594                 double d2SumDegreeCdRdTheta     = 0;
595                 double d2SumDegreeCdThetadTheta = 0;
596                 for (int n = FastMath.max(2, m); n <= degree; ++n) {
597                     final double qSnm         = aOrN[n] * harmonics.getNormalizedSnm(n, m);
598                     final double qCnm         = aOrN[n] * harmonics.getNormalizedCnm(n, m);
599                     final double nOr          = n / r;
600                     final double nnP1Or2      = nOr * (n + 1) / r;
601                     final double s0           = pnm0[n] * qSnm;
602                     final double c0           = pnm0[n] * qCnm;
603                     final double s1           = pnm1[n] * qSnm;
604                     final double c1           = pnm1[n] * qCnm;
605                     final double s2           = pnm2[n] * qSnm;
606                     final double c2           = pnm2[n] * qCnm;
607                     sumDegreeS               += s0;
608                     sumDegreeC               += c0;
609                     dSumDegreeSdR            -= nOr * s0;
610                     dSumDegreeCdR            -= nOr * c0;
611                     dSumDegreeSdTheta        += s1;
612                     dSumDegreeCdTheta        += c1;
613                     d2SumDegreeSdRdR         += nnP1Or2 * s0;
614                     d2SumDegreeSdRdTheta     -= nOr * s1;
615                     d2SumDegreeSdThetadTheta += s2;
616                     d2SumDegreeCdRdR         += nnP1Or2 * c0;
617                     d2SumDegreeCdRdTheta     -= nOr * c1;
618                     d2SumDegreeCdThetadTheta += c2;
619                 }
620 
621                 // contribution to outer summation over order
622                 final double sML = cosSinLambda[1][m];
623                 final double cML = cosSinLambda[0][m];
624                 value            = value         * u + sML * sumDegreeS + cML * sumDegreeC;
625                 gradient[0]      = gradient[0]   * u + sML * dSumDegreeSdR + cML * dSumDegreeCdR;
626                 gradient[1]      = gradient[1]   * u + m * (cML * sumDegreeS - sML * sumDegreeC);
627                 gradient[2]      = gradient[2]   * u + sML * dSumDegreeSdTheta + cML * dSumDegreeCdTheta;
628                 hessian[0][0]    = hessian[0][0] * u + sML * d2SumDegreeSdRdR + cML * d2SumDegreeCdRdR;
629                 hessian[1][0]    = hessian[1][0] * u + m * (cML * dSumDegreeSdR - sML * dSumDegreeCdR);
630                 hessian[2][0]    = hessian[2][0] * u + sML * d2SumDegreeSdRdTheta + cML * d2SumDegreeCdRdTheta;
631                 hessian[1][1]    = hessian[1][1] * u - m * m * (sML * sumDegreeS + cML * sumDegreeC);
632                 hessian[2][1]    = hessian[2][1] * u + m * (cML * dSumDegreeSdTheta - sML * dSumDegreeCdTheta);
633                 hessian[2][2]    = hessian[2][2] * u + sML * d2SumDegreeSdThetadTheta + cML * d2SumDegreeCdThetadTheta;
634 
635             }
636 
637             // rotate the recursion arrays
638             final double[] tmp0 = pnm0Plus2;
639             pnm0Plus2 = pnm0Plus1;
640             pnm0Plus1 = pnm0;
641             pnm0      = tmp0;
642             final double[] tmp1 = pnm1Plus1;
643             pnm1Plus1 = pnm1;
644             pnm1      = tmp1;
645 
646         }
647 
648         // scale back
649         value = FastMath.scalb(value, SCALING);
650         for (int i = 0; i < 3; ++i) {
651             gradient[i] = FastMath.scalb(gradient[i], SCALING);
652             for (int j = 0; j <= i; ++j) {
653                 hessian[i][j] = FastMath.scalb(hessian[i][j], SCALING);
654             }
655         }
656 
657 
658         // apply the global mu/r factor
659         final double muOr = mu / r;
660         value         *= muOr;
661         gradient[0]    = muOr * gradient[0] - value / r;
662         gradient[1]   *= muOr;
663         gradient[2]   *= muOr;
664         hessian[0][0]  = muOr * hessian[0][0] - 2 * gradient[0] / r;
665         hessian[1][0]  = muOr * hessian[1][0] -     gradient[1] / r;
666         hessian[2][0]  = muOr * hessian[2][0] -     gradient[2] / r;
667         hessian[1][1] *= muOr;
668         hessian[2][1] *= muOr;
669         hessian[2][2] *= muOr;
670 
671         // convert gradient and Hessian from spherical to Cartesian
672         final SphericalCoordinates sc = new SphericalCoordinates(position);
673         return new GradientHessian(sc.toCartesianGradient(gradient),
674                                    sc.toCartesianHessian(hessian, gradient));
675 
676 
677     }
678 
679     /** Container for gradient and Hessian. */
680     private static class GradientHessian {
681 
682         /** Gradient. */
683         private final double[] gradient;
684 
685         /** Hessian. */
686         private final double[][] hessian;
687 
688         /** Simple constructor.
689          * <p>
690          * A reference to the arrays is stored, they are <strong>not</strong> cloned.
691          * </p>
692          * @param gradient gradient
693          * @param hessian hessian
694          */
695         GradientHessian(final double[] gradient, final double[][] hessian) {
696             this.gradient = gradient;
697             this.hessian  = hessian;
698         }
699 
700         /** Get a reference to the gradient.
701          * @return gradient (a reference to the internal array is returned)
702          */
703         public double[] getGradient() {
704             return gradient;
705         }
706 
707         /** Get a reference to the Hessian.
708          * @return Hessian (a reference to the internal array is returned)
709          */
710         public double[][] getHessian() {
711             return hessian;
712         }
713 
714     }
715 
716     /** Compute a/r powers array.
717      * @param aOr a/r
718      * @return array containing (a/r)<sup>n</sup>
719      */
720     private double[] createDistancePowersArray(final double aOr) {
721 
722         // initialize array
723         final double[] aOrN = new double[provider.getMaxDegree() + 1];
724         aOrN[0] = 1;
725         if (provider.getMaxDegree() > 0) {
726             aOrN[1] = aOr;
727         }
728 
729         // fill up array
730         for (int n = 2; n < aOrN.length; ++n) {
731             final int p = n / 2;
732             final int q = n - p;
733             aOrN[n] = aOrN[p] * aOrN[q];
734         }
735 
736         return aOrN;
737 
738     }
739     /** Compute a/r powers array.
740      * @param aOr a/r
741      * @param <T> type of field used
742      * @return array containing (a/r)<sup>n</sup>
743      */
744     private <T extends CalculusFieldElement<T>> T[] createDistancePowersArray(final T aOr) {
745 
746         // initialize array
747         final T[] aOrN = MathArrays.buildArray(aOr.getField(), provider.getMaxDegree() + 1);
748         aOrN[0] = aOr.getField().getOne();
749         if (provider.getMaxDegree() > 0) {
750             aOrN[1] = aOr;
751         }
752 
753         // fill up array
754         for (int n = 2; n < aOrN.length; ++n) {
755             final int p = n / 2;
756             final int q = n - p;
757             aOrN[n] = aOrN[p].multiply(aOrN[q]);
758         }
759 
760         return aOrN;
761 
762     }
763 
764     /** Compute longitude cosines and sines.
765      * @param cosLambda cos(λ)
766      * @param sinLambda sin(λ)
767      * @return array containing cos(m &times; λ) in row 0
768      * and sin(m &times; λ) in row 1
769      */
770     private double[][] createCosSinArrays(final double cosLambda, final double sinLambda) {
771 
772         // initialize arrays
773         final double[][] cosSin = new double[2][provider.getMaxOrder() + 1];
774         cosSin[0][0] = 1;
775         cosSin[1][0] = 0;
776         if (provider.getMaxOrder() > 0) {
777             cosSin[0][1] = cosLambda;
778             cosSin[1][1] = sinLambda;
779 
780             // fill up array
781             for (int m = 2; m < cosSin[0].length; ++m) {
782 
783                 // m * lambda is split as p * lambda + q * lambda, trying to avoid
784                 // p or q being much larger than the other. This reduces the number of
785                 // intermediate results reused to compute each value, and hence should limit
786                 // as much as possible roundoff error accumulation
787                 // (this does not change the number of floating point operations)
788                 final int p = m / 2;
789                 final int q = m - p;
790 
791                 cosSin[0][m] = cosSin[0][p] * cosSin[0][q] - cosSin[1][p] * cosSin[1][q];
792                 cosSin[1][m] = cosSin[1][p] * cosSin[0][q] + cosSin[0][p] * cosSin[1][q];
793             }
794         }
795 
796         return cosSin;
797 
798     }
799 
800     /** Compute longitude cosines and sines.
801      * @param cosLambda cos(λ)
802      * @param sinLambda sin(λ)
803      * @param <T> type of field used
804      * @return array containing cos(m &times; λ) in row 0
805      * and sin(m &times; λ) in row 1
806      */
807     private <T extends CalculusFieldElement<T>> T[][] createCosSinArrays(final T cosLambda, final T sinLambda) {
808 
809         final T one = cosLambda.getField().getOne();
810         final T zero = cosLambda.getField().getZero();
811         // initialize arrays
812         final T[][] cosSin = MathArrays.buildArray(one.getField(), 2, provider.getMaxOrder() + 1);
813         cosSin[0][0] = one;
814         cosSin[1][0] = zero;
815         if (provider.getMaxOrder() > 0) {
816             cosSin[0][1] = cosLambda;
817             cosSin[1][1] = sinLambda;
818 
819             // fill up array
820             for (int m = 2; m < cosSin[0].length; ++m) {
821 
822                 // m * lambda is split as p * lambda + q * lambda, trying to avoid
823                 // p or q being much larger than the other. This reduces the number of
824                 // intermediate results reused to compute each value, and hence should limit
825                 // as much as possible roundoff error accumulation
826                 // (this does not change the number of floating point operations)
827                 final int p = m / 2;
828                 final int q = m - p;
829 
830                 cosSin[0][m] = cosSin[0][p].multiply(cosSin[0][q]).subtract(cosSin[1][p].multiply(cosSin[1][q]));
831                 cosSin[1][m] = cosSin[1][p].multiply(cosSin[0][q]).add(cosSin[0][p].multiply(cosSin[1][q]));
832 
833             }
834         }
835 
836         return cosSin;
837 
838     }
839 
840     /** Compute one order of tesseral terms.
841      * <p>
842      * This corresponds to equations 27 and 30 of the paper.
843      * </p>
844      * @param m current order
845      * @param degree max degree
846      * @param index index in the flattened array
847      * @param t cos(θ), where θ is the polar angle
848      * @param u sin(θ), where θ is the polar angle
849      * @param tOu t/u
850      * @param pnm0Plus2 array containing scaled P<sub>n,m+2</sub>/u<sup>m+2</sup>
851      * @param pnm0Plus1 array containing scaled P<sub>n,m+1</sub>/u<sup>m+1</sup>
852      * @param pnm1Plus1 array containing scaled dP<sub>n,m+1</sub>/u<sup>m+1</sup>
853      * (may be null if second derivatives are not needed)
854      * @param pnm0 array to fill with scaled P<sub>n,m</sub>/u<sup>m</sup>
855      * @param pnm1 array to fill with scaled dP<sub>n,m</sub>/u<sup>m</sup>
856      * (may be null if first derivatives are not needed)
857      * @param pnm2 array to fill with scaled d²P<sub>n,m</sub>/u<sup>m</sup>
858      * (may be null if second derivatives are not needed)
859      * @return new value for index
860      */
861     private int computeTesseral(final int m, final int degree, final int index,
862                                 final double t, final double u, final double tOu,
863                                 final double[] pnm0Plus2, final double[] pnm0Plus1, final double[] pnm1Plus1,
864                                 final double[] pnm0, final double[] pnm1, final double[] pnm2) {
865 
866         final double u2 = u * u;
867 
868         // initialize recursion from sectorial terms
869         int n = FastMath.max(2, m);
870         if (n == m) {
871             pnm0[n] = sectorial[n];
872             ++n;
873         }
874 
875         // compute tesseral values
876         int localIndex = index;
877         while (n <= degree) {
878 
879             // value (equation 27 of the paper)
880             pnm0[n] = gnmOj[localIndex] * t * pnm0Plus1[n] - hnmOj[localIndex] * u2 * pnm0Plus2[n];
881 
882             ++localIndex;
883             ++n;
884 
885         }
886 
887         if (pnm1 != null) {
888 
889             // initialize recursion from sectorial terms
890             n = FastMath.max(2, m);
891             if (n == m) {
892                 pnm1[n] = m * tOu * pnm0[n];
893                 ++n;
894             }
895 
896             // compute tesseral values and derivatives with respect to polar angle
897             localIndex = index;
898             while (n <= degree) {
899 
900                 // first derivative (equation 30 of the paper)
901                 pnm1[n] = m * tOu * pnm0[n] - enm[localIndex] * u * pnm0Plus1[n];
902 
903                 ++localIndex;
904                 ++n;
905 
906             }
907 
908             if (pnm2 != null) {
909 
910                 // initialize recursion from sectorial terms
911                 n = FastMath.max(2, m);
912                 if (n == m) {
913                     pnm2[n] = m * (tOu * pnm1[n] - pnm0[n] / u2);
914                     ++n;
915                 }
916 
917                 // compute tesseral values and derivatives with respect to polar angle
918                 localIndex = index;
919                 while (n <= degree) {
920 
921                     // second derivative (differential of equation 30 with respect to theta)
922                     pnm2[n] = m * (tOu * pnm1[n] - pnm0[n] / u2) - enm[localIndex] * u * pnm1Plus1[n];
923 
924                     ++localIndex;
925                     ++n;
926 
927                 }
928 
929             }
930 
931         }
932 
933         return localIndex;
934 
935     }
936 
937     /** Compute one order of tesseral terms.
938      * <p>
939      * This corresponds to equations 27 and 30 of the paper.
940      * </p>
941      * @param m current order
942      * @param degree max degree
943      * @param index index in the flattened array
944      * @param t cos(θ), where θ is the polar angle
945      * @param u sin(θ), where θ is the polar angle
946      * @param tOu t/u
947      * @param pnm0Plus2 array containing scaled P<sub>n,m+2</sub>/u<sup>m+2</sup>
948      * @param pnm0Plus1 array containing scaled P<sub>n,m+1</sub>/u<sup>m+1</sup>
949      * @param pnm1Plus1 array containing scaled dP<sub>n,m+1</sub>/u<sup>m+1</sup>
950      * (may be null if second derivatives are not needed)
951      * @param pnm0 array to fill with scaled P<sub>n,m</sub>/u<sup>m</sup>
952      * @param pnm1 array to fill with scaled dP<sub>n,m</sub>/u<sup>m</sup>
953      * (may be null if first derivatives are not needed)
954      * @param pnm2 array to fill with scaled d²P<sub>n,m</sub>/u<sup>m</sup>
955      * (may be null if second derivatives are not needed)
956      * @param <T> instance of field element
957      * @return new value for index
958      */
959     private <T extends CalculusFieldElement<T>> int computeTesseral(final int m, final int degree, final int index,
960                                                                 final T t, final T u, final T tOu,
961                                                                 final T[] pnm0Plus2, final T[] pnm0Plus1, final T[] pnm1Plus1,
962                                                                 final T[] pnm0, final T[] pnm1, final T[] pnm2) {
963 
964         final T u2 = u.multiply(u);
965         final T zero = u.getField().getZero();
966         // initialize recursion from sectorial terms
967         int n = FastMath.max(2, m);
968         if (n == m) {
969             pnm0[n] = zero.add(sectorial[n]);
970             ++n;
971         }
972 
973         // compute tesseral values
974         int localIndex = index;
975         while (n <= degree) {
976 
977             // value (equation 27 of the paper)
978             pnm0[n] = t.multiply(gnmOj[localIndex]).multiply(pnm0Plus1[n]).subtract(u2.multiply(pnm0Plus2[n]).multiply(hnmOj[localIndex]));
979             ++localIndex;
980             ++n;
981 
982         }
983         if (pnm1 != null) {
984 
985             // initialize recursion from sectorial terms
986             n = FastMath.max(2, m);
987             if (n == m) {
988                 pnm1[n] = tOu.multiply(m).multiply(pnm0[n]);
989                 ++n;
990             }
991 
992             // compute tesseral values and derivatives with respect to polar angle
993             localIndex = index;
994             while (n <= degree) {
995 
996                 // first derivative (equation 30 of the paper)
997                 pnm1[n] = tOu.multiply(m).multiply(pnm0[n]).subtract(u.multiply(enm[localIndex]).multiply(pnm0Plus1[n]));
998 
999                 ++localIndex;
1000                 ++n;
1001 
1002             }
1003 
1004             if (pnm2 != null) {
1005 
1006                 // initialize recursion from sectorial terms
1007                 n = FastMath.max(2, m);
1008                 if (n == m) {
1009                     pnm2[n] =   tOu.multiply(pnm1[n]).subtract(pnm0[n].divide(u2)).multiply(m);
1010                     ++n;
1011                 }
1012 
1013                 // compute tesseral values and derivatives with respect to polar angle
1014                 localIndex = index;
1015                 while (n <= degree) {
1016 
1017                     // second derivative (differential of equation 30 with respect to theta)
1018                     pnm2[n] = tOu.multiply(pnm1[n]).subtract(pnm0[n].divide(u2)).multiply(m).subtract(u.multiply(pnm1Plus1[n]).multiply(enm[localIndex]));
1019                     ++localIndex;
1020                     ++n;
1021 
1022                 }
1023 
1024             }
1025 
1026         }
1027         return localIndex;
1028 
1029     }
1030 
1031     /** {@inheritDoc} */
1032     @Override
1033     public Vector3D acceleration(final SpacecraftState s, final double[] parameters) {
1034 
1035         final double mu = parameters[0];
1036 
1037         // get the position in body frame
1038         final AbsoluteDate date       = s.getDate();
1039         final StaticTransform fromBodyFrame =
1040                 bodyFrame.getStaticTransformTo(s.getFrame(), date);
1041         final StaticTransform toBodyFrame   = fromBodyFrame.getInverse();
1042         final Vector3D position       = toBodyFrame.transformPosition(s.getPVCoordinates().getPosition());
1043 
1044         // gradient of the non-central part of the gravity field
1045         return fromBodyFrame.transformVector(new Vector3D(gradient(date, position, mu)));
1046 
1047     }
1048 
1049     /** {@inheritDoc} */
1050     public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(final FieldSpacecraftState<T> s,
1051                                                                          final T[] parameters) {
1052 
1053         final T mu = parameters[0];
1054 
1055         // check for faster computation dedicated to derivatives with respect to state
1056         if (isGradientStateDerivative(s)) {
1057             @SuppressWarnings("unchecked")
1058             final FieldVector3D<Gradient> p = (FieldVector3D<Gradient>) s.getPVCoordinates().getPosition();
1059             @SuppressWarnings("unchecked")
1060             final FieldVector3D<T> a = (FieldVector3D<T>) accelerationWrtState(s.getDate().toAbsoluteDate(),
1061                                                                                s.getFrame(), p,
1062                                                                                (Gradient) mu);
1063             return a;
1064         } else if (isDSStateDerivative(s)) {
1065             @SuppressWarnings("unchecked")
1066             final FieldVector3D<DerivativeStructure> p = (FieldVector3D<DerivativeStructure>) s.getPVCoordinates().getPosition();
1067             @SuppressWarnings("unchecked")
1068             final FieldVector3D<T> a = (FieldVector3D<T>) accelerationWrtState(s.getDate().toAbsoluteDate(),
1069                                                                                s.getFrame(), p,
1070                                                                                (DerivativeStructure) mu);
1071             return a;
1072         }
1073 
1074         // get the position in body frame
1075         final FieldAbsoluteDate<T> date          = s.getDate();
1076         final StaticTransform      fromBodyFrame =
1077                 bodyFrame.getStaticTransformTo(s.getFrame(), date.toAbsoluteDate());
1078         final StaticTransform      toBodyFrame   = fromBodyFrame.getInverse();
1079         final FieldVector3D<T>     position      = toBodyFrame.transformPosition(s.getPVCoordinates().getPosition());
1080 
1081         // gradient of the non-central part of the gravity field
1082         return fromBodyFrame.transformVector(new FieldVector3D<>(gradient(date, position, mu)));
1083 
1084     }
1085 
1086     /** {@inheritDoc} */
1087     public Stream<EventDetector> getEventsDetectors() {
1088         return Stream.empty();
1089     }
1090 
1091     @Override
1092     /** {@inheritDoc} */
1093     public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventsDetectors(final Field<T> field) {
1094         return Stream.empty();
1095     }
1096 
1097     /** Check if a field state corresponds to derivatives with respect to state.
1098      * @param state state to check
1099      * @param <T> type of the filed elements
1100      * @return true if state corresponds to derivatives with respect to state
1101      * @since 9.0
1102      */
1103     private <T extends CalculusFieldElement<T>> boolean isDSStateDerivative(final FieldSpacecraftState<T> state) {
1104         try {
1105             final DerivativeStructure dsMass = (DerivativeStructure) state.getMass();
1106             final int o = dsMass.getOrder();
1107             final int p = dsMass.getFreeParameters();
1108             if (o != 1 || p < 3) {
1109                 return false;
1110             }
1111             @SuppressWarnings("unchecked")
1112             final FieldPVCoordinates<DerivativeStructure> pv = (FieldPVCoordinates<DerivativeStructure>) state.getPVCoordinates();
1113             return isVariable(pv.getPosition().getX(), 0) &&
1114                    isVariable(pv.getPosition().getY(), 1) &&
1115                    isVariable(pv.getPosition().getZ(), 2);
1116         } catch (ClassCastException cce) {
1117             return false;
1118         }
1119     }
1120 
1121     /** Check if a field state corresponds to derivatives with respect to state.
1122      * @param state state to check
1123      * @param <T> type of the filed elements
1124      * @return true if state corresponds to derivatives with respect to state
1125      * @since 10.2
1126      */
1127     private <T extends CalculusFieldElement<T>> boolean isGradientStateDerivative(final FieldSpacecraftState<T> state) {
1128         try {
1129             final Gradient gMass = (Gradient) state.getMass();
1130             final int p = gMass.getFreeParameters();
1131             if (p < 3) {
1132                 return false;
1133             }
1134             @SuppressWarnings("unchecked")
1135             final FieldPVCoordinates<Gradient> pv = (FieldPVCoordinates<Gradient>) state.getPVCoordinates();
1136             return isVariable(pv.getPosition().getX(), 0) &&
1137                    isVariable(pv.getPosition().getY(), 1) &&
1138                    isVariable(pv.getPosition().getZ(), 2);
1139         } catch (ClassCastException cce) {
1140             return false;
1141         }
1142     }
1143 
1144     /** Check if a derivative represents a specified variable.
1145      * @param ds derivative to check
1146      * @param index index of the variable
1147      * @return true if the derivative represents a specified variable
1148      * @since 9.0
1149      */
1150     private boolean isVariable(final DerivativeStructure ds, final int index) {
1151         final double[] derivatives = ds.getAllDerivatives();
1152         boolean check = true;
1153         for (int i = 1; i < derivatives.length; ++i) {
1154             check &= derivatives[i] == ((index + 1 == i) ? 1.0 : 0.0);
1155         }
1156         return check;
1157     }
1158 
1159     /** Check if a derivative represents a specified variable.
1160      * @param g derivative to check
1161      * @param index index of the variable
1162      * @return true if the derivative represents a specified variable
1163      * @since 10.2
1164      */
1165     private boolean isVariable(final Gradient g, final int index) {
1166         final double[] derivatives = g.getGradient();
1167         boolean check = true;
1168         for (int i = 0; i < derivatives.length; ++i) {
1169             check &= derivatives[i] == ((index == i) ? 1.0 : 0.0);
1170         }
1171         return check;
1172     }
1173 
1174     /** Compute acceleration derivatives with respect to state parameters.
1175      * <p>
1176      * From a theoretical point of view, this method computes the same values
1177      * as {@link #acceleration(FieldSpacecraftState, CalculusFieldElement[])} in the
1178      * specific case of {@link DerivativeStructure} with respect to state, so
1179      * it is less general. However, it is *much* faster in this important case.
1180      * <p>
1181      * <p>
1182      * The derivatives should be computed with respect to position. The input
1183      * parameters already take into account the free parameters (6 or 7 depending
1184      * on derivation with respect to mass being considered or not) and order
1185      * (always 1). Free parameters at indices 0, 1 and 2 correspond to derivatives
1186      * with respect to position. Free parameters at indices 3, 4 and 5 correspond
1187      * to derivatives with respect to velocity (these derivatives will remain zero
1188      * as acceleration due to gravity does not depend on velocity). Free parameter
1189      * at index 6 (if present) corresponds to to derivatives with respect to mass
1190      * (this derivative will remain zero as acceleration due to gravity does not
1191      * depend on mass).
1192      * </p>
1193      * @param date current date
1194      * @param frame inertial reference frame for state (both orbit and attitude)
1195      * @param position position of spacecraft in inertial frame
1196      * @param mu central attraction coefficient to use
1197      * @return acceleration with all derivatives specified by the input parameters
1198      * own derivatives
1199      * @since 6.0
1200      */
1201     private FieldVector3D<DerivativeStructure> accelerationWrtState(final AbsoluteDate date, final Frame frame,
1202                                                                     final FieldVector3D<DerivativeStructure> position,
1203                                                                     final DerivativeStructure mu) {
1204 
1205         // free parameters
1206         final int freeParameters = mu.getFreeParameters();
1207 
1208         // get the position in body frame
1209         final StaticTransform fromBodyFrame =
1210                 bodyFrame.getStaticTransformTo(frame, date);
1211         final StaticTransform toBodyFrame   = fromBodyFrame.getInverse();
1212         final Vector3D positionBody   = toBodyFrame.transformPosition(position.toVector3D());
1213 
1214         // compute gradient and Hessian
1215         final GradientHessian gh   = gradientHessian(date, positionBody, mu.getReal());
1216 
1217         // gradient of the non-central part of the gravity field
1218         final double[] gInertial = fromBodyFrame.transformVector(new Vector3D(gh.getGradient())).toArray();
1219 
1220         // Hessian of the non-central part of the gravity field
1221         final RealMatrix hBody     = new Array2DRowRealMatrix(gh.getHessian(), false);
1222         final RealMatrix rot       = new Array2DRowRealMatrix(toBodyFrame.getRotation().getMatrix());
1223         final RealMatrix hInertial = rot.transpose().multiply(hBody).multiply(rot);
1224 
1225         // distribute all partial derivatives in a compact acceleration vector
1226         final double[] derivatives = new double[freeParameters + 1];
1227         final DerivativeStructure[] accDer = new DerivativeStructure[3];
1228         for (int i = 0; i < 3; ++i) {
1229 
1230             // first element is value of acceleration (i.e. gradient of field)
1231             derivatives[0] = gInertial[i];
1232 
1233             // Jacobian of acceleration (i.e. Hessian of field)
1234             derivatives[1] = hInertial.getEntry(i, 0);
1235             derivatives[2] = hInertial.getEntry(i, 1);
1236             derivatives[3] = hInertial.getEntry(i, 2);
1237 
1238             // next element is derivative with respect to parameter mu
1239             if (derivatives.length > 4 && isVariable(mu, 3)) {
1240                 derivatives[4] = gInertial[i] / mu.getReal();
1241             }
1242 
1243             accDer[i] = position.getX().getFactory().build(derivatives);
1244 
1245         }
1246 
1247         return new FieldVector3D<>(accDer);
1248 
1249     }
1250 
1251     /** Compute acceleration derivatives with respect to state parameters.
1252      * <p>
1253      * From a theoretical point of view, this method computes the same values
1254      * as {@link #acceleration(FieldSpacecraftState, CalculusFieldElement[])} in the
1255      * specific case of {@link DerivativeStructure} with respect to state, so
1256      * it is less general. However, it is *much* faster in this important case.
1257      * <p>
1258      * <p>
1259      * The derivatives should be computed with respect to position. The input
1260      * parameters already take into account the free parameters (6 or 7 depending
1261      * on derivation with respect to mass being considered or not) and order
1262      * (always 1). Free parameters at indices 0, 1 and 2 correspond to derivatives
1263      * with respect to position. Free parameters at indices 3, 4 and 5 correspond
1264      * to derivatives with respect to velocity (these derivatives will remain zero
1265      * as acceleration due to gravity does not depend on velocity). Free parameter
1266      * at index 6 (if present) corresponds to to derivatives with respect to mass
1267      * (this derivative will remain zero as acceleration due to gravity does not
1268      * depend on mass).
1269      * </p>
1270      * @param date current date
1271      * @param frame inertial reference frame for state (both orbit and attitude)
1272      * @param position position of spacecraft in inertial frame
1273      * @param mu central attraction coefficient to use
1274      * @return acceleration with all derivatives specified by the input parameters
1275      * own derivatives
1276      * @since 10.2
1277      */
1278     private FieldVector3D<Gradient> accelerationWrtState(final AbsoluteDate date, final Frame frame,
1279                                                          final FieldVector3D<Gradient> position,
1280                                                          final Gradient mu) {
1281 
1282         // free parameters
1283         final int freeParameters = mu.getFreeParameters();
1284 
1285         // get the position in body frame
1286         final StaticTransform fromBodyFrame = bodyFrame.getStaticTransformTo(frame, date);
1287         final StaticTransform toBodyFrame   = fromBodyFrame.getInverse();
1288         final Vector3D positionBody   = toBodyFrame.transformPosition(position.toVector3D());
1289 
1290         // compute gradient and Hessian
1291         final GradientHessian gh   = gradientHessian(date, positionBody, mu.getReal());
1292 
1293         // gradient of the non-central part of the gravity field
1294         final double[] gInertial = fromBodyFrame.transformVector(new Vector3D(gh.getGradient())).toArray();
1295 
1296         // Hessian of the non-central part of the gravity field
1297         final RealMatrix hBody     = new Array2DRowRealMatrix(gh.getHessian(), false);
1298         final RealMatrix rot       = new Array2DRowRealMatrix(toBodyFrame.getRotation().getMatrix());
1299         final RealMatrix hInertial = rot.transpose().multiply(hBody).multiply(rot);
1300 
1301         // distribute all partial derivatives in a compact acceleration vector
1302         final double[] derivatives = new double[freeParameters];
1303         final Gradient[] accDer = new Gradient[3];
1304         for (int i = 0; i < 3; ++i) {
1305 
1306             // Jacobian of acceleration (i.e. Hessian of field)
1307             derivatives[0] = hInertial.getEntry(i, 0);
1308             derivatives[1] = hInertial.getEntry(i, 1);
1309             derivatives[2] = hInertial.getEntry(i, 2);
1310 
1311             // next element is derivative with respect to parameter mu
1312             if (derivatives.length > 3 && isVariable(mu, 3)) {
1313                 derivatives[3] = gInertial[i] / mu.getReal();
1314             }
1315 
1316             accDer[i] = new Gradient(gInertial[i], derivatives);
1317 
1318         }
1319 
1320         return new FieldVector3D<>(accDer);
1321 
1322     }
1323 
1324     /** {@inheritDoc} */
1325     public List<ParameterDriver> getParametersDrivers() {
1326         return Collections.singletonList(gmParameterDriver);
1327     }
1328 
1329 }