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