1   /* Copyright 2002-2026 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.propagation.semianalytical.dsst.forces;
18  
19  import java.lang.reflect.Array;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.SortedMap;
28  import java.util.TreeMap;
29  
30  import org.hipparchus.CalculusFieldElement;
31  import org.hipparchus.Field;
32  import org.hipparchus.analysis.differentiation.FieldGradient;
33  import org.hipparchus.exception.LocalizedCoreFormats;
34  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
35  import org.hipparchus.geometry.euclidean.threed.Vector3D;
36  import org.hipparchus.util.FastMath;
37  import org.hipparchus.util.FieldSinCos;
38  import org.hipparchus.util.MathArrays;
39  import org.hipparchus.util.MathUtils;
40  import org.hipparchus.util.SinCos;
41  import org.orekit.attitudes.AttitudeProvider;
42  import org.orekit.errors.OrekitException;
43  import org.orekit.errors.OrekitInternalError;
44  import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider;
45  import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider.UnnormalizedSphericalHarmonics;
46  import org.orekit.frames.FieldStaticTransform;
47  import org.orekit.frames.Frame;
48  import org.orekit.frames.StaticTransform;
49  import org.orekit.orbits.FieldOrbit;
50  import org.orekit.orbits.Orbit;
51  import org.orekit.propagation.FieldSpacecraftState;
52  import org.orekit.propagation.PropagationType;
53  import org.orekit.propagation.SpacecraftState;
54  import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
55  import org.orekit.propagation.semianalytical.dsst.utilities.CoefficientsFactory;
56  import org.orekit.propagation.semianalytical.dsst.utilities.FieldAuxiliaryElements;
57  import org.orekit.propagation.semianalytical.dsst.utilities.FieldGHmsjPolynomials;
58  import org.orekit.propagation.semianalytical.dsst.utilities.FieldGammaMnsFunction;
59  import org.orekit.propagation.semianalytical.dsst.utilities.FieldShortPeriodicsInterpolatedCoefficient;
60  import org.orekit.propagation.semianalytical.dsst.utilities.GHmsjPolynomials;
61  import org.orekit.propagation.semianalytical.dsst.utilities.GammaMnsFunction;
62  import org.orekit.propagation.semianalytical.dsst.utilities.JacobiPolynomials;
63  import org.orekit.propagation.semianalytical.dsst.utilities.ShortPeriodicsInterpolatedCoefficient;
64  import org.orekit.propagation.semianalytical.dsst.utilities.hansen.FieldHansenTesseralLinear;
65  import org.orekit.propagation.semianalytical.dsst.utilities.hansen.HansenTesseralLinear;
66  import org.orekit.time.AbsoluteDate;
67  import org.orekit.time.FieldAbsoluteDate;
68  import org.orekit.time.TimeInterval;
69  import org.orekit.utils.FieldTimeSpanMap;
70  import org.orekit.utils.drivers.ParameterDriver;
71  import org.orekit.utils.TimeSpanMap;
72  
73  /** Tesseral contribution to the central body gravitational perturbation.
74   *  <p>
75   *  Only resonant tesserals are considered.
76   *  </p>
77   *
78   *  @author Romain Di Costanzo
79   *  @author Pascal Parraud
80   *  @author Bryan Cazabonne (field translation)
81   */
82  public class DSSTTesseral implements DSSTForceModel {
83  
84      /**  Name of the prefix for short period coefficients keys. */
85      public static final String SHORT_PERIOD_PREFIX = "DSST-central-body-tesseral-";
86  
87      /** Identifier for cMm coefficients. */
88      public static final String CM_COEFFICIENTS = "cM";
89  
90      /** Identifier for sMm coefficients. */
91      public static final String SM_COEFFICIENTS = "sM";
92  
93      /** Retrograde factor I.
94       *  <p>
95       *  DSST model needs equinoctial orbit as internal representation.
96       *  Classical equinoctial elements have discontinuities when inclination
97       *  is close to zero. In this representation, I = +1. <br>
98       *  To avoid this discontinuity, another representation exists and equinoctial
99       *  elements can be expressed in a different way, called "retrograde" orbit.
100      *  This implies I = -1. <br>
101      *  As Orekit doesn't implement the retrograde orbit, I is always set to +1.
102      *  But for the sake of consistency with the theory, the retrograde factor
103      *  has been kept in the formulas.
104      *  </p>
105      */
106     private static final int I = 1;
107 
108     /** Central attraction scaling factor.
109      * <p>
110      * We use a power of 2 to avoid numeric noise introduction
111      * in the multiplications/divisions sequences.
112      * </p>
113      */
114     private static final double MU_SCALE = FastMath.scalb(1.0, 32);
115 
116     /** Minimum period for analytically averaged high-order resonant
117      *  central body spherical harmonics in seconds.
118      */
119     private static final double MIN_PERIOD_IN_SECONDS = 864000.;
120 
121     /** Minimum period for analytically averaged high-order resonant
122      *  central body spherical harmonics in satellite revolutions.
123      */
124     private static final double MIN_PERIOD_IN_SAT_REV = 10.;
125 
126     /** Number of points for interpolation. */
127     private static final int INTERPOLATION_POINTS = 3;
128 
129     /** Provider for spherical harmonics. */
130     private final UnnormalizedSphericalHarmonicsProvider provider;
131 
132     /** Central body rotating frame. */
133     private final Frame bodyFrame;
134 
135     /** Central body rotation rate (rad/s). */
136     private final double centralBodyRotationRate;
137 
138     /** Central body rotation period (seconds). */
139     private final double bodyPeriod;
140 
141     /** Maximal degree to consider for harmonics potential. */
142     private final int maxDegree;
143 
144     /** Maximal degree to consider for short periodics tesseral harmonics potential (without m-daily). */
145     private final int maxDegreeTesseralSP;
146 
147     /** Maximal degree to consider for short periodics m-daily tesseral harmonics potential . */
148     private final int maxDegreeMdailyTesseralSP;
149 
150     /** Maximal order to consider for harmonics potential. */
151     private final int maxOrder;
152 
153     /** Maximal order to consider for short periodics tesseral harmonics potential (without m-daily). */
154     private final int maxOrderTesseralSP;
155 
156     /** Maximal order to consider for short periodics m-daily tesseral harmonics potential . */
157     private final int maxOrderMdailyTesseralSP;
158 
159     /** Maximum power of the eccentricity to use in summation over s for
160      * short periodic tesseral harmonics (without m-daily). */
161     private final int maxEccPowTesseralSP;
162 
163     /** Maximum power of the eccentricity to use in summation over s for
164      * m-daily tesseral harmonics. */
165     private final int maxEccPowMdailyTesseralSP;
166 
167     /** Maximum value for j. */
168     private final int maxFrequencyShortPeriodics;
169 
170     /** Maximum power of the eccentricity to use in summation over s. */
171     private int maxEccPow;
172 
173     /** Maximum power of the eccentricity to use in Hansen coefficient Kernel expansion. */
174     private int maxHansen;
175 
176     /** Maximum value between maxOrderMdailyTesseralSP and maxOrderTesseralSP. */
177     private int mMax;
178 
179     /** List of non resonant orders with j != 0. */
180     private final SortedMap<Integer, List<Integer> > nonResOrders;
181 
182     /** List of resonant orders. */
183     private final List<Integer> resOrders;
184 
185     /** Short period terms. */
186     private TesseralShortPeriodicCoefficients shortPeriodTerms;
187 
188     /** Short period terms. */
189     private final Map<Field<?>, FieldTesseralShortPeriodicCoefficients<?>> fieldShortPeriodTerms;
190 
191     /** Driver for gravitational parameter. */
192     private final ParameterDriver gmParameterDriver;
193 
194     /** Hansen objects. */
195     private HansenObjects hansen;
196 
197     /** Hansen objects for field elements. */
198     private final Map<Field<?>, FieldHansenObjects<?>> fieldHansen;
199 
200     /** Simple constructor with default reference values.
201      * <p>
202      * When this constructor is used, maximum allowed values are used
203      * for the short periodic coefficients:
204      * </p>
205      * <ul>
206      *    <li> {@link #maxDegreeTesseralSP} is set to {@code provider.getMaxDegree()} </li>
207      *    <li> {@link #maxOrderTesseralSP} is set to {@code provider.getMaxOrder()}. </li>
208      *    <li> {@link #maxEccPowTesseralSP} is set to {@code min(4, provider.getMaxOrder())} </li>
209      *    <li> {@link #maxFrequencyShortPeriodics} is set to {@code min(provider.getMaxDegree() + 4, 12)}.
210      *         This parameter should not exceed 12 as higher values will exceed computer capacity </li>
211      *    <li> {@link #maxDegreeMdailyTesseralSP} is set to {@code provider.getMaxDegree()} </li>
212      *    <li> {@link #maxOrderMdailyTesseralSP} is set to {@code provider.getMaxOrder()} </li>
213      *    <li> {@link #maxEccPowMdailyTesseralSP} is set to min(provider.getMaxDegree() - 2, 4).
214      *         This parameter should not exceed 4 as higher values will exceed computer capacity </li>
215      * </ul>
216      * @param centralBodyFrame rotating body frame
217      * @param centralBodyRotationRate central body rotation rate (rad/s)
218      * @param provider provider for spherical harmonics
219      * @since 10.1
220      */
221     public DSSTTesseral(final Frame centralBodyFrame,
222                         final double centralBodyRotationRate,
223                         final UnnormalizedSphericalHarmonicsProvider provider) {
224         this(centralBodyFrame, centralBodyRotationRate, provider, provider.getMaxDegree(),
225              provider.getMaxOrder(), FastMath.min(4, provider.getMaxOrder()),  FastMath.min(12, provider.getMaxDegree() + 4),
226              provider.getMaxDegree(), provider.getMaxOrder(), FastMath.min(4, provider.getMaxDegree() - 2));
227     }
228 
229     /** Simple constructor.
230      * @param centralBodyFrame rotating body frame
231      * @param centralBodyRotationRate central body rotation rate (rad/s)
232      * @param provider provider for spherical harmonics
233      * @param maxDegreeTesseralSP maximal degree to consider for short periodics tesseral harmonics potential
234      *  (must be between 2 and {@code provider.getMaxDegree()})
235      * @param maxOrderTesseralSP maximal order to consider for short periodics tesseral harmonics potential
236      *  (must be between 0 and {@code provider.getMaxOrder()})
237      * @param maxEccPowTesseralSP maximum power of the eccentricity to use in summation over s for
238      * short periodic tesseral harmonics (without m-daily), should typically not exceed 4 as higher
239      * values will exceed computer capacity
240      * (must be between 0 and {@code provider.getMaxOrder()} though, however if order = 0 the value can be anything
241      *  since it won't be used in the code)
242      * @param maxFrequencyShortPeriodics maximum frequency in mean longitude for short periodic computations
243      * (typically {@code maxDegreeTesseralSP} + {@code maxEccPowTesseralSP and no more than 12})
244      * @param maxDegreeMdailyTesseralSP maximal degree to consider for short periodics m-daily tesseral harmonics potential
245      *  (must be between 2 and {@code provider.getMaxDegree()})
246      * @param maxOrderMdailyTesseralSP maximal order to consider for short periodics m-daily tesseral harmonics potential
247      *  (must be between 0 and {@code provider.getMaxOrder()})
248      * @param maxEccPowMdailyTesseralSP maximum power of the eccentricity to use in summation over s for
249      * m-daily tesseral harmonics, (must be between 0 and {@code maxDegreeMdailyTesseralSP - 2},
250      * but should typically not exceed 4 as higher values will exceed computer capacity)
251      * @since 7.2
252      */
253     public DSSTTesseral(final Frame centralBodyFrame,
254                         final double centralBodyRotationRate,
255                         final UnnormalizedSphericalHarmonicsProvider provider,
256                         final int maxDegreeTesseralSP, final int maxOrderTesseralSP,
257                         final int maxEccPowTesseralSP, final int maxFrequencyShortPeriodics,
258                         final int maxDegreeMdailyTesseralSP, final int maxOrderMdailyTesseralSP,
259                         final int maxEccPowMdailyTesseralSP) {
260 
261         gmParameterDriver = new ParameterDriver(DSSTNewtonianAttraction.CENTRAL_ATTRACTION_COEFFICIENT,
262                                                 provider.getMu(), MU_SCALE,
263                                                 0.0, Double.POSITIVE_INFINITY, TimeInterval.UNLIMITED);
264 
265         // Central body rotating frame
266         this.bodyFrame = centralBodyFrame;
267 
268         //Save the rotation rate
269         this.centralBodyRotationRate = centralBodyRotationRate;
270 
271         // Central body rotation period in seconds
272         this.bodyPeriod = MathUtils.TWO_PI / centralBodyRotationRate;
273 
274         // Provider for spherical harmonics
275         this.provider      = provider;
276         this.maxDegree     = provider.getMaxDegree();
277         this.maxOrder      = provider.getMaxOrder();
278 
279         //set the maximum degree order for short periodics
280         checkIndexRange(maxDegreeTesseralSP, 2, maxDegree);
281         this.maxDegreeTesseralSP       = maxDegreeTesseralSP;
282 
283         checkIndexRange(maxDegreeMdailyTesseralSP, 2, maxDegree);
284         this.maxDegreeMdailyTesseralSP = maxDegreeMdailyTesseralSP;
285 
286         checkIndexRange(maxOrderTesseralSP, 0, maxOrder);
287         this.maxOrderTesseralSP        = maxOrderTesseralSP;
288 
289         checkIndexRange(maxOrderMdailyTesseralSP, 0, maxOrder);
290         this.maxOrderMdailyTesseralSP  = maxOrderMdailyTesseralSP;
291 
292         // set the maximum value for eccentricity power
293         if (maxOrder > 0) {
294             // Range check can be silently ignored if order = 0
295             checkIndexRange(maxEccPowTesseralSP, 0, maxOrder);
296         }
297         this.maxEccPowTesseralSP       = maxEccPowTesseralSP;
298 
299         checkIndexRange(maxEccPowMdailyTesseralSP, 0, maxDegreeMdailyTesseralSP - 2);
300         this.maxEccPowMdailyTesseralSP = maxEccPowMdailyTesseralSP;
301 
302         // set the maximum value for frequency
303         this.maxFrequencyShortPeriodics = maxFrequencyShortPeriodics;
304 
305         // Initialize default values
306         this.resOrders    = new ArrayList<>();
307         this.nonResOrders = new TreeMap<>();
308 
309         // Initialize default values
310         this.fieldShortPeriodTerms = new HashMap<>();
311         this.fieldHansen           = new HashMap<>();
312         this.maxEccPow             = 0;
313         this.maxHansen             = 0;
314 
315     }
316 
317     /** Check an index range.
318      * @param index index value
319      * @param min minimum value for index
320      * @param max maximum value for index
321      */
322     private void checkIndexRange(final int index, final int min, final int max) {
323         if (index < min || index > max) {
324             throw new OrekitException(LocalizedCoreFormats.OUT_OF_RANGE_SIMPLE, index, min, max);
325         }
326     }
327 
328     /** {@inheritDoc} */
329     @Override
330     public List<ShortPeriodTerms> initializeShortPeriodTerms(final AuxiliaryElements auxiliaryElements,
331                                              final PropagationType type,
332                                              final double[] parameters) {
333 
334         // Initializes specific parameters.
335 
336         final DSSTTesseralContext context = initializeStep(auxiliaryElements, parameters);
337 
338         // Set the highest power of the eccentricity in the analytical power
339         // series expansion for the averaged high order resonant central body
340         // spherical harmonic perturbation
341         maxEccPow = getMaxEccPow(auxiliaryElements.getEcc());
342 
343         // Set the maximum power of the eccentricity to use in Hansen coefficient Kernel expansion.
344         maxHansen = maxEccPow / 2;
345 
346         // The following terms are only used for hansen objects initialization
347         final double ratio = context.getRatio();
348 
349         // Compute the non resonant tesseral harmonic terms if not set by the user
350         getResonantAndNonResonantTerms(type, context.getOrbitPeriod(), ratio);
351 
352         hansen = new HansenObjects(ratio, type);
353 
354         mMax = FastMath.max(maxOrderTesseralSP, maxOrderMdailyTesseralSP);
355 
356         shortPeriodTerms = new TesseralShortPeriodicCoefficients(bodyFrame, maxOrderMdailyTesseralSP,
357                                                                  maxDegreeTesseralSP < 0, nonResOrders,
358                                                                  mMax, maxFrequencyShortPeriodics, INTERPOLATION_POINTS,
359                                                                  new TimeSpanMap<>(new Slot(mMax, maxFrequencyShortPeriodics, INTERPOLATION_POINTS)));
360 
361         final List<ShortPeriodTerms> list = new ArrayList<>();
362         list.add(shortPeriodTerms);
363         return list;
364 
365     }
366 
367     /** {@inheritDoc} */
368     @Override
369     public <T extends CalculusFieldElement<T>> List<FieldShortPeriodTerms<T>> initializeShortPeriodTerms(final FieldAuxiliaryElements<T> auxiliaryElements,
370                                                                                      final PropagationType type,
371                                                                                      final T[] parameters) {
372 
373         // Field used by default
374         final Field<T> field = auxiliaryElements.getDate().getField();
375 
376         // Initializes specific parameters.
377         final FieldDSSTTesseralContext<T> context = initializeStep(auxiliaryElements, parameters);
378 
379         // Set the highest power of the eccentricity in the analytical power
380         // series expansion for the averaged high order resonant central body
381         // spherical harmonic perturbation
382         maxEccPow = getMaxEccPow(auxiliaryElements.getEcc().getReal());
383 
384         // Set the maximum power of the eccentricity to use in Hansen coefficient Kernel expansion.
385         maxHansen = maxEccPow / 2;
386 
387         // The following terms are only used for hansen objects initialization
388         final T ratio = context.getRatio();
389 
390         // Compute the non resonant tesseral harmonic terms if not set by the user
391         // Field information is not important here
392         getResonantAndNonResonantTerms(type, context.getOrbitPeriod().getReal(), ratio.getReal());
393 
394         mMax = FastMath.max(maxOrderTesseralSP, maxOrderMdailyTesseralSP);
395 
396         fieldHansen.put(field, new FieldHansenObjects<>(ratio, type));
397 
398         final FieldTesseralShortPeriodicCoefficients<T> ftspc =
399                         new FieldTesseralShortPeriodicCoefficients<>(bodyFrame, maxOrderMdailyTesseralSP,
400                                                                      maxDegreeTesseralSP < 0, nonResOrders,
401                                                                      mMax, maxFrequencyShortPeriodics, INTERPOLATION_POINTS,
402                                                                      new FieldTimeSpanMap<>(new FieldSlot<>(mMax,
403                                                                                                             maxFrequencyShortPeriodics,
404                                                                                                             INTERPOLATION_POINTS),
405                                                                                             field));
406 
407         fieldShortPeriodTerms.put(field, ftspc);
408         return Collections.singletonList(ftspc);
409 
410     }
411 
412     /**
413      * Get the maximum power of the eccentricity to use in summation over s.
414      * @param e eccentricity
415      * @return the maximum power of the eccentricity
416      */
417     private int getMaxEccPow(final double e) {
418         // maxEccPow depends on satellite eccentricity
419         if (e <= 0.005) {
420             return 3;
421         } else if (e <= 0.02) {
422             return 4;
423         } else if (e <= 0.1) {
424             return 7;
425         } else if (e <= 0.2) {
426             return 10;
427         } else if (e <= 0.3) {
428             return 12;
429         } else if (e <= 0.4) {
430             return 15;
431         } else {
432             return 20;
433         }
434     }
435 
436     /** Performs initialization at each integration step for the current force model.
437      *  <p>
438      *  This method aims at being called before mean elements rates computation.
439      *  </p>
440      *  @param auxiliaryElements auxiliary elements related to the current orbit
441      *  @param parameters values of the force model parameters
442      *  @return new force model context
443      */
444     private DSSTTesseralContext initializeStep(final AuxiliaryElements auxiliaryElements, final double[] parameters) {
445         return new DSSTTesseralContext(auxiliaryElements, bodyFrame, provider, maxFrequencyShortPeriodics, bodyPeriod, parameters);
446     }
447 
448     /** Performs initialization at each integration step for the current force model.
449      *  <p>
450      *  This method aims at being called before mean elements rates computation.
451      *  </p>
452      *  @param <T> type of the elements
453      *  @param auxiliaryElements auxiliary elements related to the current orbit
454      *  @param parameters list of each estimated values for each driver of the force model parameters
455          *                (each span of each driver)
456      *  @return new force model context
457      */
458     private <T extends CalculusFieldElement<T>> FieldDSSTTesseralContext<T> initializeStep(final FieldAuxiliaryElements<T> auxiliaryElements,
459                                                                                        final T[] parameters) {
460         return new FieldDSSTTesseralContext<>(auxiliaryElements, bodyFrame, provider, maxFrequencyShortPeriodics, bodyPeriod, parameters);
461     }
462 
463     /** {@inheritDoc} */
464     @Override
465     public double[] getMeanElementRate(final SpacecraftState spacecraftState,
466                                        final AuxiliaryElements auxiliaryElements, final double[] parameters) {
467 
468         // Container for attributes
469 
470         final DSSTTesseralContext context = initializeStep(auxiliaryElements, parameters);
471 
472         // Access to potential U derivatives
473         final UAnddU udu = new UAnddU(spacecraftState.getDate(), context, hansen);
474 
475         // Compute the cross derivative operator :
476         final double UAlphaGamma   = context.getAlpha() * udu.getdUdGa() - context.getGamma() * udu.getdUdAl();
477         final double UAlphaBeta    = context.getAlpha() * udu.getdUdBe() - context.getBeta()  * udu.getdUdAl();
478         final double UBetaGamma    = context.getBeta() * udu.getdUdGa() - context.getGamma() * udu.getdUdBe();
479         final double Uhk           = auxiliaryElements.getH() * udu.getdUdk()  - auxiliaryElements.getK() * udu.getdUdh();
480         final double pUagmIqUbgoAB = (auxiliaryElements.getP() * UAlphaGamma - I * auxiliaryElements.getQ() * UBetaGamma) * context.getOoAB();
481         final double UhkmUabmdUdl  = Uhk - UAlphaBeta - udu.getdUdl();
482 
483         final double da =  context.getAx2oA() * udu.getdUdl();
484         final double dh =  context.getBoA() * udu.getdUdk() + auxiliaryElements.getK() * pUagmIqUbgoAB - auxiliaryElements.getH() * context.getBoABpo() * udu.getdUdl();
485         final double dk =  -(context.getBoA() * udu.getdUdh() + auxiliaryElements.getH() * pUagmIqUbgoAB + auxiliaryElements.getK() * context.getBoABpo() * udu.getdUdl());
486         final double dp =  context.getCo2AB() * (auxiliaryElements.getP() * UhkmUabmdUdl - UBetaGamma);
487         final double dq =  context.getCo2AB() * (auxiliaryElements.getQ() * UhkmUabmdUdl - I * UAlphaGamma);
488         final double dM = -context.getAx2oA() * udu.getdUda() + context.getBoABpo() * (auxiliaryElements.getH() * udu.getdUdh() + auxiliaryElements.getK() * udu.getdUdk()) + pUagmIqUbgoAB;
489 
490         return new double[] {da, dk, dh, dq, dp, dM};
491     }
492 
493     /** {@inheritDoc} */
494     @Override
495     public <T extends CalculusFieldElement<T>> T[] getMeanElementRate(final FieldSpacecraftState<T> spacecraftState,
496                                                                   final FieldAuxiliaryElements<T> auxiliaryElements,
497                                                                   final T[] parameters) {
498 
499         // Field used by default
500         final Field<T> field = auxiliaryElements.getDate().getField();
501 
502         // Container for attributes
503 
504         final FieldDSSTTesseralContext<T> context = initializeStep(auxiliaryElements, parameters);
505 
506         @SuppressWarnings("unchecked")
507         final FieldHansenObjects<T> fho = (FieldHansenObjects<T>) fieldHansen.get(field);
508         // Access to potential U derivatives
509         final FieldUAnddU<T> udu = new FieldUAnddU<>(spacecraftState.getDate(), context, fho);
510 
511         // Compute the cross derivative operator :
512         final T UAlphaGamma   = udu.getdUdGa().multiply(context.getAlpha()).subtract(udu.getdUdAl().multiply(context.getGamma()));
513         final T UAlphaBeta    = udu.getdUdBe().multiply(context.getAlpha()).subtract(udu.getdUdAl().multiply(context.getBeta()));
514         final T UBetaGamma    = udu.getdUdGa().multiply(context.getBeta()).subtract(udu.getdUdBe().multiply(context.getGamma()));
515         final T Uhk           = udu.getdUdk().multiply(auxiliaryElements.getH()).subtract(udu.getdUdh().multiply(auxiliaryElements.getK()));
516         final T pUagmIqUbgoAB = (UAlphaGamma.multiply(auxiliaryElements.getP()).subtract(UBetaGamma.multiply(auxiliaryElements.getQ()).multiply(I))).multiply(context.getOoAB());
517         final T UhkmUabmdUdl  = Uhk.subtract(UAlphaBeta).subtract(udu.getdUdl());
518 
519         final T da = udu.getdUdl().multiply(context.getAx2oA());
520         final T dh = udu.getdUdk().multiply(context.getBoA()).add(pUagmIqUbgoAB.multiply(auxiliaryElements.getK())).subtract(udu.getdUdl().multiply(auxiliaryElements.getH()).multiply(context.getBoABpo()));
521         final T dk = (udu.getdUdh().multiply(context.getBoA()).add(pUagmIqUbgoAB.multiply(auxiliaryElements.getH())).add(udu.getdUdl().multiply(context.getBoABpo()).multiply(auxiliaryElements.getK()))).negate();
522         final T dp = context.getCo2AB().multiply(auxiliaryElements.getP().multiply(UhkmUabmdUdl).subtract(UBetaGamma));
523         final T dq = context.getCo2AB().multiply(auxiliaryElements.getQ().multiply(UhkmUabmdUdl).subtract(UAlphaGamma.multiply(I)));
524         final T dM = pUagmIqUbgoAB.add(udu.getdUda().multiply(context.getAx2oA()).negate()).add((udu.getdUdh().multiply(auxiliaryElements.getH()).add(udu.getdUdk().multiply(auxiliaryElements.getK()))).multiply(context.getBoABpo()));
525 
526         final T[] elements = MathArrays.buildArray(field, 6);
527         elements[0] = da;
528         elements[1] = dk;
529         elements[2] = dh;
530         elements[3] = dq;
531         elements[4] = dp;
532         elements[5] = dM;
533 
534         return elements;
535 
536     }
537 
538     /** {@inheritDoc} */
539     @Override
540     public void updateShortPeriodTerms(final double[] parameters, final SpacecraftState... meanStates) {
541 
542         final Slot slot = shortPeriodTerms.createSlot(meanStates);
543 
544         for (final SpacecraftState meanState : meanStates) {
545 
546             final AuxiliaryElements auxiliaryElements = new AuxiliaryElements(meanState.getOrbit(), I);
547 
548             final DSSTTesseralContext context = initializeStep(auxiliaryElements, parameters);
549 
550             // Initialise the Hansen coefficients
551             for (int s = -maxDegree; s <= maxDegree; s++) {
552                 // coefficients with j == 0 are always needed
553                 hansen.computeHansenObjectsInitValues(context, s + maxDegree, 0);
554                 if (maxDegreeTesseralSP >= 0) {
555                     // initialize other objects only if required
556                     for (int j = 1; j <= maxFrequencyShortPeriodics; j++) {
557                         hansen.computeHansenObjectsInitValues(context, s + maxDegree, j);
558                     }
559                 }
560             }
561 
562             final FourierCjSjCoefficients cjsjFourier = new FourierCjSjCoefficients(maxFrequencyShortPeriodics, mMax);
563 
564             // Compute coefficients
565             // Compute only if there is at least one non-resonant tesseral
566             if (!nonResOrders.isEmpty() || maxDegreeTesseralSP < 0) {
567                 // Generate the fourrier coefficients
568                 cjsjFourier.generateCoefficients(meanState.getDate(), context, hansen);
569 
570                 // the coefficient 3n / 2a
571                 final double tnota = 1.5 * context.getMeanMotion() / auxiliaryElements.getSma();
572 
573                 // build the mDaily coefficients
574                 for (int m = 1; m <= maxOrderMdailyTesseralSP; m++) {
575                     // build the coefficients
576                     buildCoefficients(cjsjFourier, meanState.getDate(), slot, m, 0, tnota, context);
577                 }
578 
579                 if (maxDegreeTesseralSP >= 0) {
580                     // generate the other coefficients, if required
581                     for (final Map.Entry<Integer, List<Integer>> entry : nonResOrders.entrySet()) {
582 
583                         for (int j : entry.getValue()) {
584                             // build the coefficients
585                             buildCoefficients(cjsjFourier, meanState.getDate(), slot, entry.getKey(), j, tnota, context);
586                         }
587                     }
588                 }
589             }
590 
591         }
592 
593     }
594 
595     /** {@inheritDoc} */
596     @Override
597     @SuppressWarnings("unchecked")
598     public <T extends CalculusFieldElement<T>> void updateShortPeriodTerms(final T[] parameters,
599                                                                        final FieldSpacecraftState<T>... meanStates) {
600 
601         // Field used by default
602         final Field<T> field = meanStates[0].getDate().getField();
603 
604         final FieldTesseralShortPeriodicCoefficients<T> ftspc =
605             (FieldTesseralShortPeriodicCoefficients<T>) fieldShortPeriodTerms.get(field);
606         final FieldSlot<T> slot = ftspc.createSlot(meanStates);
607 
608         for (final FieldSpacecraftState<T> meanState : meanStates) {
609 
610             final FieldAuxiliaryElements<T> auxiliaryElements = new FieldAuxiliaryElements<>(meanState.getOrbit(), I);
611 
612             final FieldDSSTTesseralContext<T> context = initializeStep(auxiliaryElements, parameters);
613 
614             final FieldHansenObjects<T> fho = (FieldHansenObjects<T>) fieldHansen.get(field);
615             // Initialise the Hansen coefficients
616             for (int s = -maxDegree; s <= maxDegree; s++) {
617                 // coefficients with j == 0 are always needed
618                 fho.computeHansenObjectsInitValues(context, s + maxDegree, 0);
619                 if (maxDegreeTesseralSP >= 0) {
620                     // initialize other objects only if required
621                     for (int j = 1; j <= maxFrequencyShortPeriodics; j++) {
622                         fho.computeHansenObjectsInitValues(context, s + maxDegree, j);
623                     }
624                 }
625             }
626 
627             final FieldFourierCjSjCoefficients<T> cjsjFourier =
628                 new FieldFourierCjSjCoefficients<>(maxFrequencyShortPeriodics, mMax, field);
629 
630             // Compute coefficients
631             // Compute only if there is at least one non-resonant tesseral
632             if (!nonResOrders.isEmpty() || maxDegreeTesseralSP < 0) {
633                 // Generate the fourrier coefficients
634                 cjsjFourier.generateCoefficients(meanState.getDate(), context, fho, field);
635 
636                 // the coefficient 3n / 2a
637                 final T tnota = context.getMeanMotion().multiply(1.5).divide(auxiliaryElements.getSma());
638 
639                 // build the mDaily coefficients
640                 for (int m = 1; m <= maxOrderMdailyTesseralSP; m++) {
641                     // build the coefficients
642                     buildCoefficients(cjsjFourier, meanState.getDate(), slot, m, 0, tnota, context, field);
643                 }
644 
645                 if (maxDegreeTesseralSP >= 0) {
646                     // generate the other coefficients, if required
647                     for (final Map.Entry<Integer, List<Integer>> entry : nonResOrders.entrySet()) {
648 
649                         for (int j : entry.getValue()) {
650                             // build the coefficients
651                             buildCoefficients(cjsjFourier, meanState.getDate(), slot, entry.getKey(), j, tnota, context, field);
652                         }
653                     }
654                 }
655             }
656 
657         }
658 
659     }
660 
661     /** {@inheritDoc} */
662     public List<ParameterDriver> getParametersDrivers() {
663         return Collections.singletonList(gmParameterDriver);
664     }
665 
666     /** Build a set of coefficients.
667      * @param cjsjFourier the fourier coefficients C<sub>i</sub><sup>j</sup> and the S<sub>i</sub><sup>j</sup>
668      * @param date the current date
669      * @param slot slot to which the coefficients belong
670      * @param m m index
671      * @param j j index
672      * @param tnota 3n/2a
673      * @param context container for attributes
674      */
675     private void buildCoefficients(final FourierCjSjCoefficients cjsjFourier,
676                                    final AbsoluteDate date, final Slot slot,
677                                    final int m, final int j, final double tnota, final DSSTTesseralContext context) {
678 
679         // Create local arrays
680         final double[] currentCijm = new double[] {0., 0., 0., 0., 0., 0.};
681         final double[] currentSijm = new double[] {0., 0., 0., 0., 0., 0.};
682 
683         // compute the term 1 / (jn - mθ<sup>.</sup>)
684         final double oojnmt = 1. / (j * context.getMeanMotion() - m * centralBodyRotationRate);
685 
686         // initialise the coeficients
687         for (int i = 0; i < 6; i++) {
688             currentCijm[i] = -cjsjFourier.getSijm(i, j, m);
689             currentSijm[i] = cjsjFourier.getCijm(i, j, m);
690         }
691         // Add the separate part for δ<sub>6i</sub>
692         currentCijm[5] += tnota * oojnmt * cjsjFourier.getCijm(0, j, m);
693         currentSijm[5] += tnota * oojnmt * cjsjFourier.getSijm(0, j, m);
694 
695         //Multiply by 1 / (jn - mθ<sup>.</sup>)
696         for (int i = 0; i < 6; i++) {
697             currentCijm[i] *= oojnmt;
698             currentSijm[i] *= oojnmt;
699         }
700 
701         // Add the coefficients to the interpolation grid
702         slot.cijm[m][j + maxFrequencyShortPeriodics].addGridPoint(date, currentCijm);
703         slot.sijm[m][j + maxFrequencyShortPeriodics].addGridPoint(date, currentSijm);
704 
705     }
706 
707      /** Build a set of coefficients.
708      * @param <T> the type of the field elements
709      * @param cjsjFourier the fourier coefficients C<sub>i</sub><sup>j</sup> and the S<sub>i</sub><sup>j</sup>
710      * @param date the current date
711      * @param slot slot to which the coefficients belong
712      * @param m m index
713      * @param j j index
714      * @param tnota 3n/2a
715      * @param context container for attributes
716      * @param field field used by default
717      */
718     private <T extends CalculusFieldElement<T>> void buildCoefficients(final FieldFourierCjSjCoefficients<T> cjsjFourier,
719                                                                    final FieldAbsoluteDate<T> date,
720                                                                    final FieldSlot<T> slot,
721                                                                    final int m, final int j, final T tnota,
722                                                                    final FieldDSSTTesseralContext<T> context,
723                                                                    final Field<T> field) {
724 
725         // Zero
726         final T zero = field.getZero();
727 
728         // Create local arrays
729         final T[] currentCijm = MathArrays.buildArray(field, 6);
730         final T[] currentSijm = MathArrays.buildArray(field, 6);
731 
732         Arrays.fill(currentCijm, zero);
733         Arrays.fill(currentSijm, zero);
734 
735         // compute the term 1 / (jn - mθ<sup>.</sup>)
736         final T oojnmt = (context.getMeanMotion().multiply(j).subtract(m * centralBodyRotationRate)).reciprocal();
737 
738         // initialise the coeficients
739         for (int i = 0; i < 6; i++) {
740             currentCijm[i] = cjsjFourier.getSijm(i, j, m).negate();
741             currentSijm[i] = cjsjFourier.getCijm(i, j, m);
742         }
743         // Add the separate part for δ<sub>6i</sub>
744         currentCijm[5] = currentCijm[5].add(tnota.multiply(oojnmt).multiply(cjsjFourier.getCijm(0, j, m)));
745         currentSijm[5] = currentSijm[5].add(tnota.multiply(oojnmt).multiply(cjsjFourier.getSijm(0, j, m)));
746 
747         //Multiply by 1 / (jn - mθ<sup>.</sup>)
748         for (int i = 0; i < 6; i++) {
749             currentCijm[i] = currentCijm[i].multiply(oojnmt);
750             currentSijm[i] = currentSijm[i].multiply(oojnmt);
751         }
752 
753         // Add the coefficients to the interpolation grid
754         slot.cijm[m][j + maxFrequencyShortPeriodics].addGridPoint(date, currentCijm);
755         slot.sijm[m][j + maxFrequencyShortPeriodics].addGridPoint(date, currentSijm);
756 
757     }
758 
759      /**
760       * Get the resonant and non-resonant tesseral terms in the central body spherical harmonic field.
761       *
762       * @param type type of the elements used during the propagation
763       * @param orbitPeriod Keplerian period
764       * @param ratio ratio of satellite period to central body rotation period
765       */
766     private void getResonantAndNonResonantTerms(final PropagationType type, final double orbitPeriod,
767                                                 final double ratio) {
768 
769         // Compute natural resonant terms
770         final double tolerance = 1. / FastMath.max(MIN_PERIOD_IN_SAT_REV,
771                                                    MIN_PERIOD_IN_SECONDS / orbitPeriod);
772 
773         // Search the resonant orders in the tesseral harmonic field
774         resOrders.clear();
775         nonResOrders.clear();
776         for (int m = 1; m <= maxOrder; m++) {
777             final double resonance = ratio * m;
778             int jRes = 0;
779             final int jComputedRes = (int) FastMath.round(resonance);
780             if (jComputedRes > 0 && jComputedRes <= maxFrequencyShortPeriodics && FastMath.abs(resonance - jComputedRes) <= tolerance) {
781                 // Store each resonant index and order
782                 resOrders.add(m);
783                 jRes = jComputedRes;
784             }
785 
786             if (type == PropagationType.OSCULATING && maxDegreeTesseralSP >= 0 && m <= maxOrderTesseralSP) {
787                 //compute non resonant orders in the tesseral harmonic field
788                 final List<Integer> listJofM = new ArrayList<>();
789                 //for the moment we take only the pairs (j,m) with |j| <= maxDegree + maxEccPow (from |s-j| <= maxEccPow and |s| <= maxDegree)
790                 for (int j = -maxFrequencyShortPeriodics; j <= maxFrequencyShortPeriodics; j++) {
791                     if (j != 0 && j != jRes) {
792                         listJofM.add(j);
793                     }
794                 }
795 
796                 nonResOrders.put(m, listJofM);
797             }
798         }
799     }
800 
801     /** Compute the n-SUM for potential derivatives components.
802      *  @param date current date
803      *  @param j resonant index <i>j</i>
804      *  @param m resonant order <i>m</i>
805      *  @param s d'Alembert characteristic <i>s</i>
806      *  @param maxN maximum possible value for <i>n</i> index
807      *  @param roaPow powers of R/a up to degree <i>n</i>
808      *  @param ghMSJ G<sup>j</sup><sub>m,s</sub> and H<sup>j</sup><sub>m,s</sub> polynomials
809      *  @param gammaMNS &Gamma;<sup>m</sup><sub>n,s</sub>(γ) function
810      *  @param context container for attributes
811      *  @param hansenObjects initialization of hansen objects
812      *  @return Components of U<sub>n</sub> derivatives for fixed j, m, s
813      */
814     private double[][] computeNSum(final AbsoluteDate date,
815                                    final int j, final int m, final int s, final int maxN, final double[] roaPow,
816                                    final GHmsjPolynomials ghMSJ, final GammaMnsFunction gammaMNS, final DSSTTesseralContext context,
817                                    final HansenObjects hansenObjects) {
818 
819         // Auxiliary elements related to the current orbit
820         final AuxiliaryElements auxiliaryElements = context.getAuxiliaryElements();
821 
822         //spherical harmonics
823         final UnnormalizedSphericalHarmonics harmonics = provider.onDate(date);
824 
825         // Potential derivatives components
826         double dUdaCos  = 0.;
827         double dUdaSin  = 0.;
828         double dUdhCos  = 0.;
829         double dUdhSin  = 0.;
830         double dUdkCos  = 0.;
831         double dUdkSin  = 0.;
832         double dUdlCos  = 0.;
833         double dUdlSin  = 0.;
834         double dUdAlCos = 0.;
835         double dUdAlSin = 0.;
836         double dUdBeCos = 0.;
837         double dUdBeSin = 0.;
838         double dUdGaCos = 0.;
839         double dUdGaSin = 0.;
840 
841         // I^m
842         @SuppressWarnings("unused")
843         final int Im = I > 0 ? 1 : (m % 2 == 0 ? 1 : -1);
844 
845         // jacobi v, w, indices from 2.7.1-(15)
846         final int v = FastMath.abs(m - s);
847         final int w = FastMath.abs(m + s);
848 
849         // Initialise lower degree nmin = (Max(2, m, |s|)) for summation over n
850         final int nmin = FastMath.max(FastMath.max(2, m), FastMath.abs(s));
851 
852         //Get the corresponding Hansen object
853         final int sIndex = maxDegree + (j < 0 ? -s : s);
854         final int jIndex = FastMath.abs(j);
855         final HansenTesseralLinear hans = hansenObjects.getHansenObjects()[sIndex][jIndex];
856 
857         // n-SUM from nmin to N
858         for (int n = nmin; n <= maxN; n++) {
859             // If (n - s) is odd, the contribution is null because of Vmns
860             if ((n - s) % 2 == 0) {
861 
862                 // Vmns coefficient
863                 final double vMNS   = CoefficientsFactory.getVmns(m, n, s);
864 
865                 // Inclination function Gamma and derivative
866                 final double gaMNS  = gammaMNS.getValue(m, n, s);
867                 final double dGaMNS = gammaMNS.getDerivative(m, n, s);
868 
869                 // Hansen kernel value and derivative
870                 final double kJNS   = hans.getValue(-n - 1, context.getChi());
871                 final double dkJNS  = hans.getDerivative(-n - 1, context.getChi());
872 
873                 // Gjms, Hjms polynomials and derivatives
874                 final double gMSJ   = ghMSJ.getGmsj(m, s, j);
875                 final double hMSJ   = ghMSJ.getHmsj(m, s, j);
876                 final double dGdh   = ghMSJ.getdGmsdh(m, s, j);
877                 final double dGdk   = ghMSJ.getdGmsdk(m, s, j);
878                 final double dGdA   = ghMSJ.getdGmsdAlpha(m, s, j);
879                 final double dGdB   = ghMSJ.getdGmsdBeta(m, s, j);
880                 final double dHdh   = ghMSJ.getdHmsdh(m, s, j);
881                 final double dHdk   = ghMSJ.getdHmsdk(m, s, j);
882                 final double dHdA   = ghMSJ.getdHmsdAlpha(m, s, j);
883                 final double dHdB   = ghMSJ.getdHmsdBeta(m, s, j);
884 
885                 // Jacobi l-index from 2.7.1-(15)
886                 final int l = FastMath.min(n - m, n - FastMath.abs(s));
887                 // Jacobi polynomial and derivative
888                 final double[] jacobi = JacobiPolynomials.getValueAndDerivative(l, v, w, context.getGamma());
889 
890                 // Geopotential coefficients
891                 final double cnm = harmonics.getUnnormalizedCnm(n, m);
892                 final double snm = harmonics.getUnnormalizedSnm(n, m);
893 
894                 // Common factors from expansion of equations 3.3-4
895                 final double cf_0      = roaPow[n] * Im * vMNS;
896                 final double cf_1      = cf_0 * gaMNS * jacobi[0]; //jacobi.getValue();
897                 final double cf_2      = cf_1 * kJNS;
898                 final double gcPhs     = gMSJ * cnm + hMSJ * snm;
899                 final double gsMhc     = gMSJ * snm - hMSJ * cnm;
900                 final double dKgcPhsx2 = 2. * dkJNS * gcPhs;
901                 final double dKgsMhcx2 = 2. * dkJNS * gsMhc;
902                 final double dUdaCoef  = (n + 1) * cf_2;
903                 final double dUdlCoef  = j * cf_2;
904                 //final double dUdGaCoef = cf_0 * kJNS * (jacobi.getValue() * dGaMNS + gaMNS * jacobi.getGradient()[0]);
905                 final double dUdGaCoef = cf_0 * kJNS * (jacobi[0] * dGaMNS + gaMNS * jacobi[1]);
906 
907                 // dU / da components
908                 dUdaCos  += dUdaCoef * gcPhs;
909                 dUdaSin  += dUdaCoef * gsMhc;
910 
911                 // dU / dh components
912                 dUdhCos  += cf_1 * (kJNS * (cnm * dGdh + snm * dHdh) + auxiliaryElements.getH() * dKgcPhsx2);
913                 dUdhSin  += cf_1 * (kJNS * (snm * dGdh - cnm * dHdh) + auxiliaryElements.getH() * dKgsMhcx2);
914 
915                 // dU / dk components
916                 dUdkCos  += cf_1 * (kJNS * (cnm * dGdk + snm * dHdk) + auxiliaryElements.getK() * dKgcPhsx2);
917                 dUdkSin  += cf_1 * (kJNS * (snm * dGdk - cnm * dHdk) + auxiliaryElements.getK() * dKgsMhcx2);
918 
919                 // dU / dLambda components
920                 dUdlCos  +=  dUdlCoef * gsMhc;
921                 dUdlSin  += -dUdlCoef * gcPhs;
922 
923                 // dU / alpha components
924                 dUdAlCos += cf_2 * (dGdA * cnm + dHdA * snm);
925                 dUdAlSin += cf_2 * (dGdA * snm - dHdA * cnm);
926 
927                 // dU / dBeta components
928                 dUdBeCos += cf_2 * (dGdB * cnm + dHdB * snm);
929                 dUdBeSin += cf_2 * (dGdB * snm - dHdB * cnm);
930 
931                 // dU / dGamma components
932                 dUdGaCos += dUdGaCoef * gcPhs;
933                 dUdGaSin += dUdGaCoef * gsMhc;
934             }
935         }
936 
937         return new double[][] { { dUdaCos,  dUdaSin  },
938                                 { dUdhCos,  dUdhSin  },
939                                 { dUdkCos,  dUdkSin  },
940                                 { dUdlCos,  dUdlSin  },
941                                 { dUdAlCos, dUdAlSin },
942                                 { dUdBeCos, dUdBeSin },
943                                 { dUdGaCos, dUdGaSin } };
944     }
945 
946     /** Compute the n-SUM for potential derivatives components.
947      *  @param <T> the type of the field elements
948      *  @param date current date
949      *  @param j resonant index <i>j</i>
950      *  @param m resonant order <i>m</i>
951      *  @param s d'Alembert characteristic <i>s</i>
952      *  @param maxN maximum possible value for <i>n</i> index
953      *  @param roaPow powers of R/a up to degree <i>n</i>
954      *  @param ghMSJ G<sup>j</sup><sub>m,s</sub> and H<sup>j</sup><sub>m,s</sub> polynomials
955      *  @param gammaMNS &Gamma;<sup>m</sup><sub>n,s</sub>(γ) function
956      *  @param context container for attributes
957      *  @param hansenObjects initialization of hansen objects
958      *  @return Components of U<sub>n</sub> derivatives for fixed j, m, s
959      */
960     private <T extends CalculusFieldElement<T>> T[][] computeNSum(final FieldAbsoluteDate<T> date,
961                                                               final int j, final int m, final int s, final int maxN,
962                                                               final T[] roaPow,
963                                                               final FieldGHmsjPolynomials<T> ghMSJ,
964                                                               final FieldGammaMnsFunction<T> gammaMNS,
965                                                               final FieldDSSTTesseralContext<T> context,
966                                                               final FieldHansenObjects<T> hansenObjects) {
967 
968         // Auxiliary elements related to the current orbit
969         final FieldAuxiliaryElements<T> auxiliaryElements = context.getFieldAuxiliaryElements();
970         // Zero for initialization
971         final Field<T> field = date.getField();
972         final T zero = field.getZero();
973 
974         //spherical harmonics
975         final UnnormalizedSphericalHarmonics harmonics = provider.onDate(date.toAbsoluteDate());
976 
977         // Potential derivatives components
978         T dUdaCos  = zero;
979         T dUdaSin  = zero;
980         T dUdhCos  = zero;
981         T dUdhSin  = zero;
982         T dUdkCos  = zero;
983         T dUdkSin  = zero;
984         T dUdlCos  = zero;
985         T dUdlSin  = zero;
986         T dUdAlCos = zero;
987         T dUdAlSin = zero;
988         T dUdBeCos = zero;
989         T dUdBeSin = zero;
990         T dUdGaCos = zero;
991         T dUdGaSin = zero;
992 
993         // I^m
994         @SuppressWarnings("unused")
995         final int Im = I > 0 ? 1 : (m % 2 == 0 ? 1 : -1);
996 
997         // jacobi v, w, indices from 2.7.1-(15)
998         final int v = FastMath.abs(m - s);
999         final int w = FastMath.abs(m + s);
1000 
1001         // Initialise lower degree nmin = (Max(2, m, |s|)) for summation over n
1002         final int nmin = FastMath.max(FastMath.max(2, m), FastMath.abs(s));
1003 
1004         //Get the corresponding Hansen object
1005         final int sIndex = maxDegree + (j < 0 ? -s : s);
1006         final int jIndex = FastMath.abs(j);
1007         final FieldHansenTesseralLinear<T> hans = hansenObjects.getHansenObjects()[sIndex][jIndex];
1008 
1009         // n-SUM from nmin to N
1010         for (int n = nmin; n <= maxN; n++) {
1011             // If (n - s) is odd, the contribution is null because of Vmns
1012             if ((n - s) % 2 == 0) {
1013 
1014                 // Vmns coefficient
1015                 final T vMNS   = zero.newInstance(CoefficientsFactory.getVmns(m, n, s));
1016 
1017                 // Inclination function Gamma and derivative
1018                 final T gaMNS  = gammaMNS.getValue(m, n, s);
1019                 final T dGaMNS = gammaMNS.getDerivative(m, n, s);
1020 
1021                 // Hansen kernel value and derivative
1022                 final T kJNS   = hans.getValue(-n - 1, context.getChi());
1023                 final T dkJNS  = hans.getDerivative(-n - 1, context.getChi());
1024 
1025                 // Gjms, Hjms polynomials and derivatives
1026                 final T gMSJ   = ghMSJ.getGmsj(m, s, j);
1027                 final T hMSJ   = ghMSJ.getHmsj(m, s, j);
1028                 final T dGdh   = ghMSJ.getdGmsdh(m, s, j);
1029                 final T dGdk   = ghMSJ.getdGmsdk(m, s, j);
1030                 final T dGdA   = ghMSJ.getdGmsdAlpha(m, s, j);
1031                 final T dGdB   = ghMSJ.getdGmsdBeta(m, s, j);
1032                 final T dHdh   = ghMSJ.getdHmsdh(m, s, j);
1033                 final T dHdk   = ghMSJ.getdHmsdk(m, s, j);
1034                 final T dHdA   = ghMSJ.getdHmsdAlpha(m, s, j);
1035                 final T dHdB   = ghMSJ.getdHmsdBeta(m, s, j);
1036 
1037                 // Jacobi l-index from 2.7.1-(15)
1038                 final int l = FastMath.min(n - m, n - FastMath.abs(s));
1039                 // Jacobi polynomial and derivative
1040                 final FieldGradient<T> jacobi =
1041                         JacobiPolynomials.getValue(l, v, w, FieldGradient.variable(1, 0, context.getGamma()));
1042 
1043                 // Geopotential coefficients
1044                 final T cnm = zero.newInstance(harmonics.getUnnormalizedCnm(n, m));
1045                 final T snm = zero.newInstance(harmonics.getUnnormalizedSnm(n, m));
1046 
1047                 // Common factors from expansion of equations 3.3-4
1048                 final T cf_0      = roaPow[n].multiply(Im).multiply(vMNS);
1049                 final T cf_1      = cf_0.multiply(gaMNS).multiply(jacobi.getValue());
1050                 final T cf_2      = cf_1.multiply(kJNS);
1051                 final T gcPhs     = gMSJ.multiply(cnm).add(hMSJ.multiply(snm));
1052                 final T gsMhc     = gMSJ.multiply(snm).subtract(hMSJ.multiply(cnm));
1053                 final T dKgcPhsx2 = dkJNS.multiply(gcPhs).multiply(2.);
1054                 final T dKgsMhcx2 = dkJNS.multiply(gsMhc).multiply(2.);
1055                 final T dUdaCoef  = cf_2.multiply(n + 1);
1056                 final T dUdlCoef  = cf_2.multiply(j);
1057                 final T dUdGaCoef = cf_0.multiply(kJNS).multiply(dGaMNS.multiply(jacobi.getValue()).add(gaMNS.multiply(jacobi.getGradient()[0])));
1058 
1059                 // dU / da components
1060                 dUdaCos  = dUdaCos.add(dUdaCoef.multiply(gcPhs));
1061                 dUdaSin  = dUdaSin.add(dUdaCoef.multiply(gsMhc));
1062 
1063                 // dU / dh components
1064                 dUdhCos  = dUdhCos.add(cf_1.multiply(kJNS.multiply(cnm.multiply(dGdh).add(snm.multiply(dHdh))).add(dKgcPhsx2.multiply(auxiliaryElements.getH()))));
1065                 dUdhSin  = dUdhSin.add(cf_1.multiply(kJNS.multiply(snm.multiply(dGdh).subtract(cnm.multiply(dHdh))).add(dKgsMhcx2.multiply(auxiliaryElements.getH()))));
1066 
1067                 // dU / dk components
1068                 dUdkCos  = dUdkCos.add(cf_1.multiply(kJNS.multiply(cnm.multiply(dGdk).add(snm.multiply(dHdk))).add(dKgcPhsx2.multiply(auxiliaryElements.getK()))));
1069                 dUdkSin  = dUdkSin.add(cf_1.multiply(kJNS.multiply(snm.multiply(dGdk).subtract(cnm.multiply(dHdk))).add(dKgsMhcx2.multiply(auxiliaryElements.getK()))));
1070 
1071                 // dU / dLambda components
1072                 dUdlCos  = dUdlCos.add(dUdlCoef.multiply(gsMhc));
1073                 dUdlSin  = dUdlSin.add(dUdlCoef.multiply(gcPhs).negate());
1074 
1075                 // dU / alpha components
1076                 dUdAlCos = dUdAlCos.add(cf_2.multiply(dGdA.multiply(cnm).add(dHdA.multiply(snm))));
1077                 dUdAlSin = dUdAlSin.add(cf_2.multiply(dGdA.multiply(snm).subtract(dHdA.multiply(cnm))));
1078 
1079                 // dU / dBeta components
1080                 dUdBeCos = dUdBeCos.add(cf_2.multiply(dGdB.multiply(cnm).add(dHdB.multiply(snm))));
1081                 dUdBeSin = dUdBeSin.add(cf_2.multiply(dGdB.multiply(snm).subtract(dHdB.multiply(cnm))));
1082 
1083                 // dU / dGamma components
1084                 dUdGaCos = dUdGaCos.add(dUdGaCoef.multiply(gcPhs));
1085                 dUdGaSin = dUdGaSin.add(dUdGaCoef.multiply(gsMhc));
1086             }
1087         }
1088 
1089         final T[][] derivatives = MathArrays.buildArray(field, 7, 2);
1090         derivatives[0][0] = dUdaCos;
1091         derivatives[0][1] = dUdaSin;
1092         derivatives[1][0] = dUdhCos;
1093         derivatives[1][1] = dUdhSin;
1094         derivatives[2][0] = dUdkCos;
1095         derivatives[2][1] = dUdkSin;
1096         derivatives[3][0] = dUdlCos;
1097         derivatives[3][1] = dUdlSin;
1098         derivatives[4][0] = dUdAlCos;
1099         derivatives[4][1] = dUdAlSin;
1100         derivatives[5][0] = dUdBeCos;
1101         derivatives[5][1] = dUdBeSin;
1102         derivatives[6][0] = dUdGaCos;
1103         derivatives[6][1] = dUdGaSin;
1104 
1105         return derivatives;
1106 
1107     }
1108 
1109     /** {@inheritDoc} */
1110     @Override
1111     public void registerAttitudeProvider(final AttitudeProvider attitudeProvider) {
1112         //nothing is done since this contribution is not sensitive to attitude
1113     }
1114 
1115     /** Compute the C<sup>j</sup> and the S<sup>j</sup> coefficients.
1116      *  <p>
1117      *  Those coefficients are given in Danielson paper by substituting the
1118      *  disturbing function (2.7.1-16) with m != 0 into (2.2-10)
1119      *  </p>
1120      */
1121     private class FourierCjSjCoefficients {
1122 
1123         /** Absolute limit for j ( -jMax <= j <= jMax ).  */
1124         private final int jMax;
1125 
1126         /** The C<sub>i</sub><sup>jm</sup> coefficients.
1127          * <p>
1128          * The index order is [m][j][i] <br/>
1129          * The i index corresponds to the C<sub>i</sub><sup>jm</sup> coefficients used to
1130          * compute the following: <br/>
1131          * - da/dt <br/>
1132          * - dk/dt <br/>
1133          * - dh/dt / dk <br/>
1134          * - dq/dt <br/>
1135          * - dp/dt / dα <br/>
1136          * - dλ/dt / dβ <br/>
1137          * </p>
1138          */
1139         private final double[][][] cCoef;
1140 
1141         /** The S<sub>i</sub><sup>jm</sup> coefficients.
1142          * <p>
1143          * The index order is [m][j][i] <br/>
1144          * The i index corresponds to the C<sub>i</sub><sup>jm</sup> coefficients used to
1145          * compute the following: <br/>
1146          * - da/dt <br/>
1147          * - dk/dt <br/>
1148          * - dh/dt / dk <br/>
1149          * - dq/dt <br/>
1150          * - dp/dt / dα <br/>
1151          * - dλ/dt / dβ <br/>
1152          * </p>
1153          */
1154         private final double[][][] sCoef;
1155 
1156         /** G<sub>ms</sub><sup>j</sup> and H<sub>ms</sub><sup>j</sup> polynomials. */
1157         private GHmsjPolynomials ghMSJ;
1158 
1159         /** &Gamma;<sub>ns</sub><sup>m</sup> function. */
1160         private GammaMnsFunction gammaMNS;
1161 
1162         /** R / a up to power degree. */
1163         private final double[] roaPow;
1164 
1165         /** Create a set of C<sub>i</sub><sup>jm</sup> and S<sub>i</sub><sup>jm</sup> coefficients.
1166          *  @param jMax absolute limit for j ( -jMax <= j <= jMax )
1167          *  @param mMax maximum value for m
1168          */
1169         FourierCjSjCoefficients(final int jMax, final int mMax) {
1170             // initialise fields
1171             final int rows    = mMax + 1;
1172             final int columns = 2 * jMax + 1;
1173             this.jMax         = jMax;
1174             this.cCoef        = new double[rows][columns][6];
1175             this.sCoef        = new double[rows][columns][6];
1176             this.roaPow       = new double[maxDegree + 1];
1177             roaPow[0] = 1.;
1178         }
1179 
1180         /**
1181          * Generate the coefficients.
1182          * @param date the current date
1183          * @param context container for attributes
1184          * @param hansenObjects initialization of hansen objects
1185          */
1186         public void generateCoefficients(final AbsoluteDate date, final DSSTTesseralContext context,
1187                                          final HansenObjects hansenObjects) {
1188 
1189             final AuxiliaryElements auxiliaryElements = context.getAuxiliaryElements();
1190 
1191             // Compute only if there is at least one non-resonant tesseral
1192             if (!nonResOrders.isEmpty() || maxDegreeTesseralSP < 0) {
1193                 // Gmsj and Hmsj polynomials
1194                 ghMSJ = new GHmsjPolynomials(auxiliaryElements.getK(), auxiliaryElements.getH(), context.getAlpha(), context.getBeta(), I);
1195 
1196                 // GAMMAmns function
1197                 gammaMNS = new GammaMnsFunction(maxDegree, context.getGamma(), I);
1198 
1199                 final int maxRoaPower = FastMath.max(maxDegreeTesseralSP, maxDegreeMdailyTesseralSP);
1200 
1201                 // R / a up to power degree
1202                 for (int i = 1; i <= maxRoaPower; i++) {
1203                     roaPow[i] = context.getRoa() * roaPow[i - 1];
1204                 }
1205 
1206                 //generate the m-daily coefficients
1207                 for (int m = 1; m <= maxOrderMdailyTesseralSP; m++) {
1208                     buildFourierCoefficients(date, m, 0, maxDegreeMdailyTesseralSP, context, hansenObjects);
1209                 }
1210 
1211                 // generate the other coefficients only if required
1212                 if (maxDegreeTesseralSP >= 0) {
1213                     for (int m: nonResOrders.keySet()) {
1214                         final List<Integer> listJ = nonResOrders.get(m);
1215 
1216                         for (int j: listJ) {
1217                             buildFourierCoefficients(date, m, j, maxDegreeTesseralSP, context, hansenObjects);
1218                         }
1219                     }
1220                 }
1221             }
1222         }
1223 
1224         /** Build a set of fourier coefficients for a given m and j.
1225          *
1226          * @param date the date of the coefficients
1227          * @param m m index
1228          * @param j j index
1229          * @param maxN  maximum value for n index
1230          * @param context container for attributes
1231          * @param hansenObjects initialization of hansen objects
1232          */
1233         private void buildFourierCoefficients(final AbsoluteDate date,
1234                final int m, final int j, final int maxN, final DSSTTesseralContext context,
1235                final HansenObjects hansenObjects) {
1236 
1237             final AuxiliaryElements auxiliaryElements = context.getAuxiliaryElements();
1238 
1239             // Potential derivatives components for a given non-resonant pair {j,m}
1240             double dRdaCos  = 0.;
1241             double dRdaSin  = 0.;
1242             double dRdhCos  = 0.;
1243             double dRdhSin  = 0.;
1244             double dRdkCos  = 0.;
1245             double dRdkSin  = 0.;
1246             double dRdlCos  = 0.;
1247             double dRdlSin  = 0.;
1248             double dRdAlCos = 0.;
1249             double dRdAlSin = 0.;
1250             double dRdBeCos = 0.;
1251             double dRdBeSin = 0.;
1252             double dRdGaCos = 0.;
1253             double dRdGaSin = 0.;
1254 
1255             // s-SUM from -sMin to sMax
1256             final int sMin = j == 0 ? maxEccPowMdailyTesseralSP : maxEccPowTesseralSP;
1257             final int sMax = j == 0 ? maxEccPowMdailyTesseralSP : maxEccPowTesseralSP;
1258             for (int s = 0; s <= sMax; s++) {
1259 
1260                 // n-SUM for s positive
1261                 final double[][] nSumSpos = computeNSum(date, j, m, s, maxN,
1262                                                         roaPow, ghMSJ, gammaMNS, context, hansenObjects);
1263                 dRdaCos  += nSumSpos[0][0];
1264                 dRdaSin  += nSumSpos[0][1];
1265                 dRdhCos  += nSumSpos[1][0];
1266                 dRdhSin  += nSumSpos[1][1];
1267                 dRdkCos  += nSumSpos[2][0];
1268                 dRdkSin  += nSumSpos[2][1];
1269                 dRdlCos  += nSumSpos[3][0];
1270                 dRdlSin  += nSumSpos[3][1];
1271                 dRdAlCos += nSumSpos[4][0];
1272                 dRdAlSin += nSumSpos[4][1];
1273                 dRdBeCos += nSumSpos[5][0];
1274                 dRdBeSin += nSumSpos[5][1];
1275                 dRdGaCos += nSumSpos[6][0];
1276                 dRdGaSin += nSumSpos[6][1];
1277 
1278                 // n-SUM for s negative
1279                 if (s > 0 && s <= sMin) {
1280                     final double[][] nSumSneg = computeNSum(date, j, m, -s, maxN,
1281                                                             roaPow, ghMSJ, gammaMNS, context, hansenObjects);
1282                     dRdaCos  += nSumSneg[0][0];
1283                     dRdaSin  += nSumSneg[0][1];
1284                     dRdhCos  += nSumSneg[1][0];
1285                     dRdhSin  += nSumSneg[1][1];
1286                     dRdkCos  += nSumSneg[2][0];
1287                     dRdkSin  += nSumSneg[2][1];
1288                     dRdlCos  += nSumSneg[3][0];
1289                     dRdlSin  += nSumSneg[3][1];
1290                     dRdAlCos += nSumSneg[4][0];
1291                     dRdAlSin += nSumSneg[4][1];
1292                     dRdBeCos += nSumSneg[5][0];
1293                     dRdBeSin += nSumSneg[5][1];
1294                     dRdGaCos += nSumSneg[6][0];
1295                     dRdGaSin += nSumSneg[6][1];
1296                 }
1297             }
1298             final double muOnA = context.getMuoa();
1299             dRdaCos  *= -muOnA / auxiliaryElements.getSma();
1300             dRdaSin  *= -muOnA / auxiliaryElements.getSma();
1301             dRdhCos  *=  muOnA;
1302             dRdhSin  *=  muOnA;
1303             dRdkCos  *=  muOnA;
1304             dRdkSin  *=  muOnA;
1305             dRdlCos  *=  muOnA;
1306             dRdlSin  *=  muOnA;
1307             dRdAlCos *=  muOnA;
1308             dRdAlSin *=  muOnA;
1309             dRdBeCos *=  muOnA;
1310             dRdBeSin *=  muOnA;
1311             dRdGaCos *=  muOnA;
1312             dRdGaSin *=  muOnA;
1313 
1314             // Compute the cross derivative operator :
1315             final double RAlphaGammaCos   = context.getAlpha() * dRdGaCos - context.getGamma() * dRdAlCos;
1316             final double RAlphaGammaSin   = context.getAlpha() * dRdGaSin - context.getGamma() * dRdAlSin;
1317             final double RAlphaBetaCos    = context.getAlpha() * dRdBeCos - context.getBeta()  * dRdAlCos;
1318             final double RAlphaBetaSin    = context.getAlpha() * dRdBeSin - context.getBeta()  * dRdAlSin;
1319             final double RBetaGammaCos    =  context.getBeta() * dRdGaCos - context.getGamma() * dRdBeCos;
1320             final double RBetaGammaSin    =  context.getBeta() * dRdGaSin - context.getGamma() * dRdBeSin;
1321             final double RhkCos           =     auxiliaryElements.getH() * dRdkCos  -     auxiliaryElements.getK() * dRdhCos;
1322             final double RhkSin           =     auxiliaryElements.getH() * dRdkSin  -     auxiliaryElements.getK() * dRdhSin;
1323             final double pRagmIqRbgoABCos = (auxiliaryElements.getP() * RAlphaGammaCos - I * auxiliaryElements.getQ() * RBetaGammaCos) * context.getOoAB();
1324             final double pRagmIqRbgoABSin = (auxiliaryElements.getP() * RAlphaGammaSin - I * auxiliaryElements.getQ() * RBetaGammaSin) * context.getOoAB();
1325             final double RhkmRabmdRdlCos  =  RhkCos - RAlphaBetaCos - dRdlCos;
1326             final double RhkmRabmdRdlSin  =  RhkSin - RAlphaBetaSin - dRdlSin;
1327 
1328             // da/dt
1329             cCoef[m][j + jMax][0] = context.getAx2oA() * dRdlCos;
1330             sCoef[m][j + jMax][0] = context.getAx2oA() * dRdlSin;
1331 
1332             // dk/dt
1333             cCoef[m][j + jMax][1] = -(context.getBoA() * dRdhCos + auxiliaryElements.getH() * pRagmIqRbgoABCos + auxiliaryElements.getK() * context.getBoABpo() * dRdlCos);
1334             sCoef[m][j + jMax][1] = -(context.getBoA() * dRdhSin + auxiliaryElements.getH() * pRagmIqRbgoABSin + auxiliaryElements.getK() * context.getBoABpo() * dRdlSin);
1335 
1336             // dh/dt
1337             cCoef[m][j + jMax][2] = context.getBoA() * dRdkCos + auxiliaryElements.getK() * pRagmIqRbgoABCos - auxiliaryElements.getH() * context.getBoABpo() * dRdlCos;
1338             sCoef[m][j + jMax][2] = context.getBoA() * dRdkSin + auxiliaryElements.getK() * pRagmIqRbgoABSin - auxiliaryElements.getH() * context.getBoABpo() * dRdlSin;
1339 
1340             // dq/dt
1341             cCoef[m][j + jMax][3] = context.getCo2AB() * (auxiliaryElements.getQ() * RhkmRabmdRdlCos - I * RAlphaGammaCos);
1342             sCoef[m][j + jMax][3] = context.getCo2AB() * (auxiliaryElements.getQ() * RhkmRabmdRdlSin - I * RAlphaGammaSin);
1343 
1344             // dp/dt
1345             cCoef[m][j + jMax][4] = context.getCo2AB() * (auxiliaryElements.getP() * RhkmRabmdRdlCos - RBetaGammaCos);
1346             sCoef[m][j + jMax][4] = context.getCo2AB() * (auxiliaryElements.getP() * RhkmRabmdRdlSin - RBetaGammaSin);
1347 
1348             // dλ/dt
1349             cCoef[m][j + jMax][5] = -context.getAx2oA() * dRdaCos + context.getBoABpo() * (auxiliaryElements.getH() * dRdhCos + auxiliaryElements.getK() * dRdkCos) + pRagmIqRbgoABCos;
1350             sCoef[m][j + jMax][5] = -context.getAx2oA() * dRdaSin + context.getBoABpo() * (auxiliaryElements.getH() * dRdhSin + auxiliaryElements.getK() * dRdkSin) + pRagmIqRbgoABSin;
1351         }
1352 
1353         /** Get the coefficient C<sub>i</sub><sup>jm</sup>.
1354          * @param i i index - corresponds to the required variation
1355          * @param j j index
1356          * @param m m index
1357          * @return the coefficient C<sub>i</sub><sup>jm</sup>
1358          */
1359         public double getCijm(final int i, final int j, final int m) {
1360             return cCoef[m][j + jMax][i];
1361         }
1362 
1363         /** Get the coefficient S<sub>i</sub><sup>jm</sup>.
1364          * @param i i index - corresponds to the required variation
1365          * @param j j index
1366          * @param m m index
1367          * @return the coefficient S<sub>i</sub><sup>jm</sup>
1368          */
1369         public double getSijm(final int i, final int j, final int m) {
1370             return sCoef[m][j + jMax][i];
1371         }
1372     }
1373 
1374     /** Compute the C<sup>j</sup> and the S<sup>j</sup> coefficients.
1375      *  <p>
1376      *  Those coefficients are given in Danielson paper by substituting the
1377      *  disturbing function (2.7.1-16) with m != 0 into (2.2-10)
1378      *  </p>
1379      */
1380     private class FieldFourierCjSjCoefficients <T extends CalculusFieldElement<T>> {
1381 
1382         /** Absolute limit for j ( -jMax <= j <= jMax ).  */
1383         private final int jMax;
1384 
1385         /** The C<sub>i</sub><sup>jm</sup> coefficients.
1386          * <p>
1387          * The index order is [m][j][i] <br/>
1388          * The i index corresponds to the C<sub>i</sub><sup>jm</sup> coefficients used to
1389          * compute the following: <br/>
1390          * - da/dt <br/>
1391          * - dk/dt <br/>
1392          * - dh/dt / dk <br/>
1393          * - dq/dt <br/>
1394          * - dp/dt / dα <br/>
1395          * - dλ/dt / dβ <br/>
1396          * </p>
1397          */
1398         private final T[][][] cCoef;
1399 
1400         /** The S<sub>i</sub><sup>jm</sup> coefficients.
1401          * <p>
1402          * The index order is [m][j][i] <br/>
1403          * The i index corresponds to the C<sub>i</sub><sup>jm</sup> coefficients used to
1404          * compute the following: <br/>
1405          * - da/dt <br/>
1406          * - dk/dt <br/>
1407          * - dh/dt / dk <br/>
1408          * - dq/dt <br/>
1409          * - dp/dt / dα <br/>
1410          * - dλ/dt / dβ <br/>
1411          * </p>
1412          */
1413         private final T[][][] sCoef;
1414 
1415         /** G<sub>ms</sub><sup>j</sup> and H<sub>ms</sub><sup>j</sup> polynomials. */
1416         private FieldGHmsjPolynomials<T> ghMSJ;
1417 
1418         /** &Gamma;<sub>ns</sub><sup>m</sup> function. */
1419         private FieldGammaMnsFunction<T> gammaMNS;
1420 
1421         /** R / a up to power degree. */
1422         private final T[] roaPow;
1423 
1424         /** Create a set of C<sub>i</sub><sup>jm</sup> and S<sub>i</sub><sup>jm</sup> coefficients.
1425          *  @param jMax absolute limit for j ( -jMax <= j <= jMax )
1426          *  @param mMax maximum value for m
1427          *  @param field field used by default
1428          */
1429         FieldFourierCjSjCoefficients(final int jMax, final int mMax, final Field<T> field) {
1430             // initialise fields
1431             final T zero = field.getZero();
1432             final int rows    = mMax + 1;
1433             final int columns = 2 * jMax + 1;
1434             this.jMax         = jMax;
1435             this.cCoef        = MathArrays.buildArray(field, rows, columns, 6);
1436             this.sCoef        = MathArrays.buildArray(field, rows, columns, 6);
1437             this.roaPow       = MathArrays.buildArray(field, maxDegree + 1);
1438             roaPow[0] = zero.newInstance(1.);
1439         }
1440 
1441         /**
1442          * Generate the coefficients.
1443          * @param date the current date
1444          * @param context container for attributes
1445          * @param hansenObjects initialization of hansen objects
1446          * @param field field used by default
1447          */
1448         public void generateCoefficients(final FieldAbsoluteDate<T> date,
1449                                          final FieldDSSTTesseralContext<T> context,
1450                                          final FieldHansenObjects<T> hansenObjects,
1451                                          final Field<T> field) {
1452 
1453             final FieldAuxiliaryElements<T> auxiliaryElements = context.getFieldAuxiliaryElements();
1454             // Compute only if there is at least one non-resonant tesseral
1455             if (!nonResOrders.isEmpty() || maxDegreeTesseralSP < 0) {
1456                 // Gmsj and Hmsj polynomials
1457                 ghMSJ = new FieldGHmsjPolynomials<>(auxiliaryElements.getK(), auxiliaryElements.getH(), context.getAlpha(), context.getBeta(), I, field);
1458 
1459                 // GAMMAmns function
1460                 gammaMNS = new FieldGammaMnsFunction<>(maxDegree, context.getGamma(), I, field);
1461 
1462                 final int maxRoaPower = FastMath.max(maxDegreeTesseralSP, maxDegreeMdailyTesseralSP);
1463 
1464                 // R / a up to power degree
1465                 for (int i = 1; i <= maxRoaPower; i++) {
1466                     roaPow[i] = context.getRoa().multiply(roaPow[i - 1]);
1467                 }
1468 
1469                 //generate the m-daily coefficients
1470                 for (int m = 1; m <= maxOrderMdailyTesseralSP; m++) {
1471                     buildFourierCoefficients(date, m, 0, maxDegreeMdailyTesseralSP, context, hansenObjects, field);
1472                 }
1473 
1474                 // generate the other coefficients only if required
1475                 if (maxDegreeTesseralSP >= 0) {
1476                     for (int m: nonResOrders.keySet()) {
1477                         final List<Integer> listJ = nonResOrders.get(m);
1478 
1479                         for (int j: listJ) {
1480                             buildFourierCoefficients(date, m, j, maxDegreeTesseralSP, context, hansenObjects, field);
1481                         }
1482                     }
1483                 }
1484             }
1485         }
1486 
1487         /** Build a set of fourier coefficients for a given m and j.
1488          *
1489          * @param date the date of the coefficients
1490          * @param m m index
1491          * @param j j index
1492          * @param maxN  maximum value for n index
1493          * @param context container for attributes
1494          * @param hansenObjects initialization of hansen objects
1495          * @param field field used by default
1496          */
1497         private void buildFourierCoefficients(final FieldAbsoluteDate<T> date,
1498                                               final int m, final int j, final int maxN,
1499                                               final FieldDSSTTesseralContext<T> context,
1500                                               final FieldHansenObjects<T> hansenObjects,
1501                                               final Field<T> field) {
1502 
1503             // Zero
1504             final T zero = field.getZero();
1505             // Common parameters
1506             final FieldAuxiliaryElements<T> auxiliaryElements = context.getFieldAuxiliaryElements();
1507 
1508             // Potential derivatives components for a given non-resonant pair {j,m}
1509             T dRdaCos  = zero;
1510             T dRdaSin  = zero;
1511             T dRdhCos  = zero;
1512             T dRdhSin  = zero;
1513             T dRdkCos  = zero;
1514             T dRdkSin  = zero;
1515             T dRdlCos  = zero;
1516             T dRdlSin  = zero;
1517             T dRdAlCos = zero;
1518             T dRdAlSin = zero;
1519             T dRdBeCos = zero;
1520             T dRdBeSin = zero;
1521             T dRdGaCos = zero;
1522             T dRdGaSin = zero;
1523 
1524             // s-SUM from -sMin to sMax
1525             final int sMin = j == 0 ? maxEccPowMdailyTesseralSP : maxEccPowTesseralSP;
1526             final int sMax = j == 0 ? maxEccPowMdailyTesseralSP : maxEccPowTesseralSP;
1527             for (int s = 0; s <= sMax; s++) {
1528 
1529                 // n-SUM for s positive
1530                 final T[][] nSumSpos = computeNSum(date, j, m, s, maxN,
1531                                                         roaPow, ghMSJ, gammaMNS, context, hansenObjects);
1532                 dRdaCos  =  dRdaCos.add(nSumSpos[0][0]);
1533                 dRdaSin  =  dRdaSin.add(nSumSpos[0][1]);
1534                 dRdhCos  =  dRdhCos.add(nSumSpos[1][0]);
1535                 dRdhSin  =  dRdhSin.add(nSumSpos[1][1]);
1536                 dRdkCos  =  dRdkCos.add(nSumSpos[2][0]);
1537                 dRdkSin  =  dRdkSin.add(nSumSpos[2][1]);
1538                 dRdlCos  =  dRdlCos.add(nSumSpos[3][0]);
1539                 dRdlSin  =  dRdlSin.add(nSumSpos[3][1]);
1540                 dRdAlCos = dRdAlCos.add(nSumSpos[4][0]);
1541                 dRdAlSin = dRdAlSin.add(nSumSpos[4][1]);
1542                 dRdBeCos = dRdBeCos.add(nSumSpos[5][0]);
1543                 dRdBeSin = dRdBeSin.add(nSumSpos[5][1]);
1544                 dRdGaCos = dRdGaCos.add(nSumSpos[6][0]);
1545                 dRdGaSin = dRdGaSin.add(nSumSpos[6][1]);
1546 
1547                 // n-SUM for s negative
1548                 if (s > 0 && s <= sMin) {
1549                     final T[][] nSumSneg = computeNSum(date, j, m, -s, maxN,
1550                                                             roaPow, ghMSJ, gammaMNS, context, hansenObjects);
1551                     dRdaCos  =  dRdaCos.add(nSumSneg[0][0]);
1552                     dRdaSin  =  dRdaSin.add(nSumSneg[0][1]);
1553                     dRdhCos  =  dRdhCos.add(nSumSneg[1][0]);
1554                     dRdhSin  =  dRdhSin.add(nSumSneg[1][1]);
1555                     dRdkCos  =  dRdkCos.add(nSumSneg[2][0]);
1556                     dRdkSin  =  dRdkSin.add(nSumSneg[2][1]);
1557                     dRdlCos  =  dRdlCos.add(nSumSneg[3][0]);
1558                     dRdlSin  =  dRdlSin.add(nSumSneg[3][1]);
1559                     dRdAlCos = dRdAlCos.add(nSumSneg[4][0]);
1560                     dRdAlSin = dRdAlSin.add(nSumSneg[4][1]);
1561                     dRdBeCos = dRdBeCos.add(nSumSneg[5][0]);
1562                     dRdBeSin = dRdBeSin.add(nSumSneg[5][1]);
1563                     dRdGaCos = dRdGaCos.add(nSumSneg[6][0]);
1564                     dRdGaSin = dRdGaSin.add(nSumSneg[6][1]);
1565                 }
1566             }
1567             final T muOnA = context.getMuoa();
1568             dRdaCos  =  dRdaCos.multiply(muOnA.negate().divide(auxiliaryElements.getSma()));
1569             dRdaSin  =  dRdaSin.multiply(muOnA.negate().divide(auxiliaryElements.getSma()));
1570             dRdhCos  =  dRdhCos.multiply(muOnA);
1571             dRdhSin  =  dRdhSin.multiply(muOnA);
1572             dRdkCos  =  dRdkCos.multiply(muOnA);
1573             dRdkSin  =  dRdkSin.multiply(muOnA);
1574             dRdlCos  =  dRdlCos.multiply(muOnA);
1575             dRdlSin  =  dRdlSin.multiply(muOnA);
1576             dRdAlCos = dRdAlCos.multiply(muOnA);
1577             dRdAlSin = dRdAlSin.multiply(muOnA);
1578             dRdBeCos = dRdBeCos.multiply(muOnA);
1579             dRdBeSin = dRdBeSin.multiply(muOnA);
1580             dRdGaCos = dRdGaCos.multiply(muOnA);
1581             dRdGaSin = dRdGaSin.multiply(muOnA);
1582 
1583             // Compute the cross derivative operator :
1584             final T RAlphaGammaCos   = context.getAlpha().multiply(dRdGaCos).subtract(context.getGamma().multiply(dRdAlCos));
1585             final T RAlphaGammaSin   = context.getAlpha().multiply(dRdGaSin).subtract(context.getGamma().multiply(dRdAlSin));
1586             final T RAlphaBetaCos    = context.getAlpha().multiply(dRdBeCos).subtract(context.getBeta().multiply(dRdAlCos));
1587             final T RAlphaBetaSin    = context.getAlpha().multiply(dRdBeSin).subtract(context.getBeta().multiply(dRdAlSin));
1588             final T RBetaGammaCos    =  context.getBeta().multiply(dRdGaCos).subtract(context.getGamma().multiply(dRdBeCos));
1589             final T RBetaGammaSin    =  context.getBeta().multiply(dRdGaSin).subtract(context.getGamma().multiply(dRdBeSin));
1590             final T RhkCos           =     auxiliaryElements.getH().multiply(dRdkCos).subtract(auxiliaryElements.getK().multiply(dRdhCos));
1591             final T RhkSin           =     auxiliaryElements.getH().multiply(dRdkSin).subtract(auxiliaryElements.getK().multiply(dRdhSin));
1592             final T pRagmIqRbgoABCos = (auxiliaryElements.getP().multiply(RAlphaGammaCos).subtract(auxiliaryElements.getQ().multiply(RBetaGammaCos).multiply(I))).multiply(context.getOoAB());
1593             final T pRagmIqRbgoABSin = (auxiliaryElements.getP().multiply(RAlphaGammaSin).subtract(auxiliaryElements.getQ().multiply(RBetaGammaSin).multiply(I))).multiply(context.getOoAB());
1594             final T RhkmRabmdRdlCos  =  RhkCos.subtract(RAlphaBetaCos).subtract(dRdlCos);
1595             final T RhkmRabmdRdlSin  =  RhkSin.subtract(RAlphaBetaSin).subtract(dRdlSin);
1596 
1597             // da/dt
1598             cCoef[m][j + jMax][0] = context.getAx2oA().multiply(dRdlCos);
1599             sCoef[m][j + jMax][0] = context.getAx2oA().multiply(dRdlSin);
1600 
1601             // dk/dt
1602             cCoef[m][j + jMax][1] = (context.getBoA().multiply(dRdhCos).add(auxiliaryElements.getH().multiply(pRagmIqRbgoABCos)).add(auxiliaryElements.getK().multiply(context.getBoABpo()).multiply(dRdlCos))).negate();
1603             sCoef[m][j + jMax][1] = (context.getBoA().multiply(dRdhSin).add(auxiliaryElements.getH().multiply(pRagmIqRbgoABSin)).add(auxiliaryElements.getK().multiply(context.getBoABpo()).multiply(dRdlSin))).negate();
1604 
1605             // dh/dt
1606             cCoef[m][j + jMax][2] = context.getBoA().multiply(dRdkCos).add(auxiliaryElements.getK().multiply(pRagmIqRbgoABCos)).subtract(auxiliaryElements.getH().multiply(context.getBoABpo()).multiply(dRdlCos));
1607             sCoef[m][j + jMax][2] = context.getBoA().multiply(dRdkSin).add(auxiliaryElements.getK().multiply(pRagmIqRbgoABSin)).subtract(auxiliaryElements.getH().multiply(context.getBoABpo()).multiply(dRdlSin));
1608 
1609             // dq/dt
1610             cCoef[m][j + jMax][3] = context.getCo2AB().multiply(auxiliaryElements.getQ().multiply(RhkmRabmdRdlCos).subtract(RAlphaGammaCos.multiply(I)));
1611             sCoef[m][j + jMax][3] = context.getCo2AB().multiply(auxiliaryElements.getQ().multiply(RhkmRabmdRdlSin).subtract(RAlphaGammaSin.multiply(I)));
1612 
1613             // dp/dt
1614             cCoef[m][j + jMax][4] = context.getCo2AB().multiply(auxiliaryElements.getP().multiply(RhkmRabmdRdlCos).subtract(RBetaGammaCos));
1615             sCoef[m][j + jMax][4] = context.getCo2AB().multiply(auxiliaryElements.getP().multiply(RhkmRabmdRdlSin).subtract(RBetaGammaSin));
1616 
1617             // dλ/dt
1618             cCoef[m][j + jMax][5] = context.getAx2oA().negate().multiply(dRdaCos).add(context.getBoABpo().multiply(auxiliaryElements.getH().multiply(dRdhCos).add(auxiliaryElements.getK().multiply(dRdkCos)))).add(pRagmIqRbgoABCos);
1619             sCoef[m][j + jMax][5] = context.getAx2oA().negate().multiply(dRdaSin).add(context.getBoABpo().multiply(auxiliaryElements.getH().multiply(dRdhSin).add(auxiliaryElements.getK().multiply(dRdkSin)))).add(pRagmIqRbgoABSin);
1620         }
1621 
1622         /** Get the coefficient C<sub>i</sub><sup>jm</sup>.
1623          * @param i i index - corresponds to the required variation
1624          * @param j j index
1625          * @param m m index
1626          * @return the coefficient C<sub>i</sub><sup>jm</sup>
1627          */
1628         public T getCijm(final int i, final int j, final int m) {
1629             return cCoef[m][j + jMax][i];
1630         }
1631 
1632         /** Get the coefficient S<sub>i</sub><sup>jm</sup>.
1633          * @param i i index - corresponds to the required variation
1634          * @param j j index
1635          * @param m m index
1636          * @return the coefficient S<sub>i</sub><sup>jm</sup>
1637          */
1638         public T getSijm(final int i, final int j, final int m) {
1639             return sCoef[m][j + jMax][i];
1640         }
1641     }
1642 
1643     /** The C<sup>i</sup><sub>m</sub><sub>t</sub> and S<sup>i</sup><sub>m</sub><sub>t</sub> coefficients used to compute
1644      * the short-periodic zonal contribution.
1645      *   <p>
1646      *  Those coefficients are given by expression 2.5.4-5 from the Danielson paper.
1647      *   </p>
1648      *
1649      * @author Sorin Scortan
1650      *
1651      */
1652     private static class TesseralShortPeriodicCoefficients implements ShortPeriodTerms {
1653 
1654         /** Retrograde factor I.
1655          *  <p>
1656          *  DSST model needs equinoctial orbit as internal representation.
1657          *  Classical equinoctial elements have discontinuities when inclination
1658          *  is close to zero. In this representation, I = +1. <br>
1659          *  To avoid this discontinuity, another representation exists and equinoctial
1660          *  elements can be expressed in a different way, called "retrograde" orbit.
1661          *  This implies I = -1. <br>
1662          *  As Orekit doesn't implement the retrograde orbit, I is always set to +1.
1663          *  But for the sake of consistency with the theory, the retrograde factor
1664          *  has been kept in the formulas.
1665          *  </p>
1666          */
1667         private static final int I = 1;
1668 
1669         /** Central body rotating frame. */
1670         private final Frame bodyFrame;
1671 
1672         /** Maximal order to consider for short periodics m-daily tesseral harmonics potential. */
1673         private final int maxOrderMdailyTesseralSP;
1674 
1675         /** Flag to take into account only M-dailies harmonic tesserals for short periodic perturbations.  */
1676         private final boolean mDailiesOnly;
1677 
1678         /** List of non resonant orders with j != 0. */
1679         private final SortedMap<Integer, List<Integer> > nonResOrders;
1680 
1681         /** Maximum value for m index. */
1682         private final int mMax;
1683 
1684         /** Maximum value for j index. */
1685         private final int jMax;
1686 
1687         /** Number of points used in the interpolation process. */
1688         private final int interpolationPoints;
1689 
1690         /** All coefficients slots. */
1691         private final TimeSpanMap<Slot> slots;
1692 
1693         /** Constructor.
1694          * @param bodyFrame central body rotating frame
1695          * @param maxOrderMdailyTesseralSP maximal order to consider for short periodics m-daily tesseral harmonics potential
1696          * @param mDailiesOnly flag to take into account only M-dailies harmonic tesserals for short periodic perturbations
1697          * @param nonResOrders lst of non resonant orders with j != 0
1698          * @param mMax maximum value for m index
1699          * @param jMax maximum value for j index
1700          * @param interpolationPoints number of points used in the interpolation process
1701          * @param slots all coefficients slots
1702          */
1703         TesseralShortPeriodicCoefficients(final Frame bodyFrame, final int maxOrderMdailyTesseralSP,
1704                                           final boolean mDailiesOnly, final SortedMap<Integer, List<Integer> > nonResOrders,
1705                                           final int mMax, final int jMax, final int interpolationPoints,
1706                                           final TimeSpanMap<Slot> slots) {
1707             this.bodyFrame                = bodyFrame;
1708             this.maxOrderMdailyTesseralSP = maxOrderMdailyTesseralSP;
1709             this.mDailiesOnly             = mDailiesOnly;
1710             this.nonResOrders             = nonResOrders;
1711             this.mMax                     = mMax;
1712             this.jMax                     = jMax;
1713             this.interpolationPoints      = interpolationPoints;
1714             this.slots                    = slots;
1715         }
1716 
1717         /** Get the slot valid for some date.
1718          * @param meanStates mean states defining the slot
1719          * @return slot valid at the specified date
1720          */
1721         public Slot createSlot(final SpacecraftState... meanStates) {
1722             final Slot         slot  = new Slot(mMax, jMax, interpolationPoints);
1723             final AbsoluteDate first = meanStates[0].getDate();
1724             final AbsoluteDate last  = meanStates[meanStates.length - 1].getDate();
1725             final int compare = first.compareTo(last);
1726             if (compare < 0) {
1727                 slots.addValidAfter(slot, first, false);
1728             } else if (compare > 0) {
1729                 slots.addValidBefore(slot, first, false);
1730             } else {
1731                 // single date, valid for all time
1732                 slots.addValidAfter(slot, AbsoluteDate.PAST_INFINITY, false);
1733             }
1734             return slot;
1735         }
1736 
1737         /** {@inheritDoc} */
1738         @Override
1739         public double[] value(final Orbit meanOrbit) {
1740 
1741             // select the coefficients slot
1742             final Slot slot = slots.get(meanOrbit.getDate());
1743 
1744             // Initialise the short periodic variations
1745             final double[] shortPeriodicVariation = new double[6];
1746 
1747             // Compute only if there is at least one non-resonant tesseral or
1748             // only the m-daily tesseral should be taken into account
1749             if (!nonResOrders.isEmpty() || mDailiesOnly) {
1750 
1751                 //Build an auxiliary object
1752                 final AuxiliaryElements auxiliaryElements = new AuxiliaryElements(meanOrbit, I);
1753 
1754                 // Central body rotation angle from equation 2.7.1-(3)(4).
1755                 final StaticTransform t = bodyFrame.getStaticTransformTo(
1756                         auxiliaryElements.getFrame(),
1757                         auxiliaryElements.getDate());
1758                 final Vector3D xB = t.transformVector(Vector3D.PLUS_I);
1759                 final Vector3D yB = t.transformVector(Vector3D.PLUS_J);
1760                 final Vector3D  f = auxiliaryElements.getVectorF();
1761                 final Vector3D  g = auxiliaryElements.getVectorG();
1762                 final double currentTheta = FastMath.atan2(-f.dotProduct(yB) + I * g.dotProduct(xB),
1763                                                             f.dotProduct(xB) + I * g.dotProduct(yB));
1764 
1765                 //Add the m-daily contribution
1766                 for (int m = 1; m <= maxOrderMdailyTesseralSP; m++) {
1767                     // Phase angle
1768                     final double jlMmt  = -m * currentTheta;
1769                     final SinCos scPhi  = FastMath.sinCos(jlMmt);
1770                     final double sinPhi = scPhi.sin();
1771                     final double cosPhi = scPhi.cos();
1772 
1773                     // compute contribution for each element
1774                     final double[] c = slot.getCijm(0, m, meanOrbit.getDate());
1775                     final double[] s = slot.getSijm(0, m, meanOrbit.getDate());
1776                     for (int i = 0; i < 6; i++) {
1777                         shortPeriodicVariation[i] += c[i] * cosPhi + s[i] * sinPhi;
1778                     }
1779                 }
1780 
1781                 // loop through all non-resonant (j,m) pairs
1782                 for (final Map.Entry<Integer, List<Integer>> entry : nonResOrders.entrySet()) {
1783                     final int           m     = entry.getKey();
1784                     final List<Integer> listJ = entry.getValue();
1785 
1786                     for (int j : listJ) {
1787                         // Phase angle
1788                         final double jlMmt  = j * meanOrbit.getLM() - m * currentTheta;
1789                         final SinCos scPhi  = FastMath.sinCos(jlMmt);
1790                         final double sinPhi = scPhi.sin();
1791                         final double cosPhi = scPhi.cos();
1792 
1793                         // compute contribution for each element
1794                         final double[] c = slot.getCijm(j, m, meanOrbit.getDate());
1795                         final double[] s = slot.getSijm(j, m, meanOrbit.getDate());
1796                         for (int i = 0; i < 6; i++) {
1797                             shortPeriodicVariation[i] += c[i] * cosPhi + s[i] * sinPhi;
1798                         }
1799 
1800                     }
1801                 }
1802             }
1803 
1804             return shortPeriodicVariation;
1805 
1806         }
1807 
1808         /** {@inheritDoc} */
1809         @Override
1810         public String getCoefficientsKeyPrefix() {
1811             return DSSTTesseral.SHORT_PERIOD_PREFIX;
1812         }
1813 
1814         /** {@inheritDoc}
1815          * <p>
1816          * For tesseral terms contributions,there are maxOrderMdailyTesseralSP
1817          * m-daily cMm coefficients, maxOrderMdailyTesseralSP m-daily sMm
1818          * coefficients, nbNonResonant cjm coefficients and nbNonResonant
1819          * sjm coefficients, where maxOrderMdailyTesseralSP and nbNonResonant both
1820          * depend on the orbit. The j index is the integer multiplier for the true
1821          * longitude argument and the m index is the integer multiplier for m-dailies.
1822          * </p>
1823          */
1824         @Override
1825         public Map<String, double[]> getCoefficients(final AbsoluteDate date, final Set<String> selected) {
1826 
1827             // select the coefficients slot
1828             final Slot slot = slots.get(date);
1829 
1830             if (!nonResOrders.isEmpty() || mDailiesOnly) {
1831                 final Map<String, double[]> coefficients = new HashMap<>(12 * maxOrderMdailyTesseralSP + 12 * nonResOrders.size());
1832 
1833                 for (int m = 1; m <= maxOrderMdailyTesseralSP; m++) {
1834                     storeIfSelected(coefficients, selected, slot.getCijm(0, m, date), DSSTTesseral.CM_COEFFICIENTS, m);
1835                     storeIfSelected(coefficients, selected, slot.getSijm(0, m, date), DSSTTesseral.SM_COEFFICIENTS, m);
1836                 }
1837 
1838                 for (final Map.Entry<Integer, List<Integer>> entry : nonResOrders.entrySet()) {
1839                     final int           m     = entry.getKey();
1840                     final List<Integer> listJ = entry.getValue();
1841 
1842                     for (int j : listJ) {
1843                         for (int i = 0; i < 6; ++i) {
1844                             storeIfSelected(coefficients, selected, slot.getCijm(j, m, date), "c", j, m);
1845                             storeIfSelected(coefficients, selected, slot.getSijm(j, m, date), "s", j, m);
1846                         }
1847                     }
1848                 }
1849 
1850                 return coefficients;
1851 
1852             } else {
1853                 return Collections.emptyMap();
1854             }
1855 
1856         }
1857 
1858         /** Put a coefficient in a map if selected.
1859          * @param map map to populate
1860          * @param selected set of coefficients that should be put in the map
1861          * (empty set means all coefficients are selected)
1862          * @param value coefficient value
1863          * @param id coefficient identifier
1864          * @param indices list of coefficient indices
1865          */
1866         private void storeIfSelected(final Map<String, double[]> map, final Set<String> selected,
1867                                      final double[] value, final String id, final int... indices) {
1868             final StringBuilder keyBuilder = new StringBuilder(getCoefficientsKeyPrefix());
1869             keyBuilder.append(id);
1870             for (int index : indices) {
1871                 keyBuilder.append('[').append(index).append(']');
1872             }
1873             final String key = keyBuilder.toString();
1874             if (selected.isEmpty() || selected.contains(key)) {
1875                 map.put(key, value);
1876             }
1877         }
1878 
1879     }
1880 
1881     /** The C<sup>i</sup><sub>m</sub><sub>t</sub> and S<sup>i</sup><sub>m</sub><sub>t</sub> coefficients used to compute
1882      * the short-periodic zonal contribution.
1883      *   <p>
1884      *  Those coefficients are given by expression 2.5.4-5 from the Danielson paper.
1885      *   </p>
1886      *
1887      * @author Sorin Scortan
1888      *
1889      */
1890     private static class FieldTesseralShortPeriodicCoefficients <T extends CalculusFieldElement<T>> implements FieldShortPeriodTerms<T> {
1891 
1892         /** Retrograde factor I.
1893          *  <p>
1894          *  DSST model needs equinoctial orbit as internal representation.
1895          *  Classical equinoctial elements have discontinuities when inclination
1896          *  is close to zero. In this representation, I = +1. <br>
1897          *  To avoid this discontinuity, another representation exists and equinoctial
1898          *  elements can be expressed in a different way, called "retrograde" orbit.
1899          *  This implies I = -1. <br>
1900          *  As Orekit doesn't implement the retrograde orbit, I is always set to +1.
1901          *  But for the sake of consistency with the theory, the retrograde factor
1902          *  has been kept in the formulas.
1903          *  </p>
1904          */
1905         private static final int I = 1;
1906 
1907         /** Central body rotating frame. */
1908         private final Frame bodyFrame;
1909 
1910         /** Maximal order to consider for short periodics m-daily tesseral harmonics potential. */
1911         private final int maxOrderMdailyTesseralSP;
1912 
1913         /** Flag to take into account only M-dailies harmonic tesserals for short periodic perturbations.  */
1914         private final boolean mDailiesOnly;
1915 
1916         /** List of non resonant orders with j != 0. */
1917         private final SortedMap<Integer, List<Integer> > nonResOrders;
1918 
1919         /** Maximum value for m index. */
1920         private final int mMax;
1921 
1922         /** Maximum value for j index. */
1923         private final int jMax;
1924 
1925         /** Number of points used in the interpolation process. */
1926         private final int interpolationPoints;
1927 
1928         /** All coefficients slots. */
1929         private final FieldTimeSpanMap<FieldSlot<T>, T> slots;
1930 
1931         /** Constructor.
1932          * @param bodyFrame central body rotating frame
1933          * @param maxOrderMdailyTesseralSP maximal order to consider for short periodics m-daily tesseral harmonics potential
1934          * @param mDailiesOnly flag to take into account only M-dailies harmonic tesserals for short periodic perturbations
1935          * @param nonResOrders lst of non resonant orders with j != 0
1936          * @param mMax maximum value for m index
1937          * @param jMax maximum value for j index
1938          * @param interpolationPoints number of points used in the interpolation process
1939          * @param slots all coefficients slots
1940          */
1941         FieldTesseralShortPeriodicCoefficients(final Frame bodyFrame, final int maxOrderMdailyTesseralSP,
1942                                                final boolean mDailiesOnly, final SortedMap<Integer, List<Integer> > nonResOrders,
1943                                                final int mMax, final int jMax, final int interpolationPoints,
1944                                                final FieldTimeSpanMap<FieldSlot<T>, T> slots) {
1945             this.bodyFrame                = bodyFrame;
1946             this.maxOrderMdailyTesseralSP = maxOrderMdailyTesseralSP;
1947             this.mDailiesOnly             = mDailiesOnly;
1948             this.nonResOrders             = nonResOrders;
1949             this.mMax                     = mMax;
1950             this.jMax                     = jMax;
1951             this.interpolationPoints      = interpolationPoints;
1952             this.slots                    = slots;
1953         }
1954 
1955         /** Get the slot valid for some date.
1956          * @param meanStates mean states defining the slot
1957          * @return slot valid at the specified date
1958          */
1959         @SuppressWarnings("unchecked")
1960         public FieldSlot<T> createSlot(final FieldSpacecraftState<T>... meanStates) {
1961             final FieldSlot<T>         slot  = new FieldSlot<>(mMax, jMax, interpolationPoints);
1962             final FieldAbsoluteDate<T> first = meanStates[0].getDate();
1963             final FieldAbsoluteDate<T> last  = meanStates[meanStates.length - 1].getDate();
1964             if (first.compareTo(last) <= 0) {
1965                 slots.addValidAfter(slot, first, false);
1966             } else {
1967                 slots.addValidBefore(slot, first, false);
1968             }
1969             return slot;
1970         }
1971 
1972         /** {@inheritDoc} */
1973         @Override
1974         public T[] value(final FieldOrbit<T> meanOrbit) {
1975 
1976             // select the coefficients slot
1977             final FieldSlot<T> slot = slots.get(meanOrbit.getDate());
1978 
1979             // Initialise the short periodic variations
1980             final T[] shortPeriodicVariation = MathArrays.buildArray(meanOrbit.getDate().getField(), 6);
1981 
1982             // Compute only if there is at least one non-resonant tesseral or
1983             // only the m-daily tesseral should be taken into account
1984             if (!nonResOrders.isEmpty() || mDailiesOnly) {
1985 
1986                 //Build an auxiliary object
1987                 final FieldAuxiliaryElements<T> auxiliaryElements = new FieldAuxiliaryElements<>(meanOrbit, I);
1988 
1989                 // Central body rotation angle from equation 2.7.1-(3)(4).
1990                 final FieldStaticTransform<T> t = bodyFrame.getStaticTransformTo(auxiliaryElements.getFrame(), auxiliaryElements.getDate());
1991                 final FieldVector3D<T> xB = t.transformVector(Vector3D.PLUS_I);
1992                 final FieldVector3D<T> yB = t.transformVector(Vector3D.PLUS_J);
1993                 final FieldVector3D<T>  f = auxiliaryElements.getVectorF();
1994                 final FieldVector3D<T>  g = auxiliaryElements.getVectorG();
1995                 final T currentTheta = FastMath.atan2(f.dotProduct(yB).negate().add(g.dotProduct(xB).multiply(I)),
1996                                                       f.dotProduct(xB).add(g.dotProduct(yB).multiply(I)));
1997 
1998                 //Add the m-daily contribution
1999                 for (int m = 1; m <= maxOrderMdailyTesseralSP; m++) {
2000                     // Phase angle
2001                     final T jlMmt              = currentTheta.multiply(-m);
2002                     final FieldSinCos<T> scPhi = FastMath.sinCos(jlMmt);
2003                     final T sinPhi             = scPhi.sin();
2004                     final T cosPhi             = scPhi.cos();
2005 
2006                     // compute contribution for each element
2007                     final T[] c = slot.getCijm(0, m, meanOrbit.getDate());
2008                     final T[] s = slot.getSijm(0, m, meanOrbit.getDate());
2009                     for (int i = 0; i < 6; i++) {
2010                         shortPeriodicVariation[i] = shortPeriodicVariation[i].add(c[i].multiply(cosPhi).add(s[i].multiply(sinPhi)));
2011                     }
2012                 }
2013 
2014                 // loop through all non-resonant (j,m) pairs
2015                 for (final Map.Entry<Integer, List<Integer>> entry : nonResOrders.entrySet()) {
2016                     final int           m     = entry.getKey();
2017                     final List<Integer> listJ = entry.getValue();
2018 
2019                     for (int j : listJ) {
2020                         // Phase angle
2021                         final T jlMmt              = meanOrbit.getLM().multiply(j).subtract(currentTheta.multiply(m));
2022                         final FieldSinCos<T> scPhi = FastMath.sinCos(jlMmt);
2023                         final T sinPhi             = scPhi.sin();
2024                         final T cosPhi             = scPhi.cos();
2025 
2026                         // compute contribution for each element
2027                         final T[] c = slot.getCijm(j, m, meanOrbit.getDate());
2028                         final T[] s = slot.getSijm(j, m, meanOrbit.getDate());
2029                         for (int i = 0; i < 6; i++) {
2030                             shortPeriodicVariation[i] = shortPeriodicVariation[i].add(c[i].multiply(cosPhi).add(s[i].multiply(sinPhi)));
2031                         }
2032 
2033                     }
2034                 }
2035             }
2036 
2037             return shortPeriodicVariation;
2038 
2039         }
2040 
2041         /** {@inheritDoc} */
2042         @Override
2043         public String getCoefficientsKeyPrefix() {
2044             return DSSTTesseral.SHORT_PERIOD_PREFIX;
2045         }
2046 
2047         /** {@inheritDoc}
2048          * <p>
2049          * For tesseral terms contributions,there are maxOrderMdailyTesseralSP
2050          * m-daily cMm coefficients, maxOrderMdailyTesseralSP m-daily sMm
2051          * coefficients, nbNonResonant cjm coefficients and nbNonResonant
2052          * sjm coefficients, where maxOrderMdailyTesseralSP and nbNonResonant both
2053          * depend on the orbit. The j index is the integer multiplier for the true
2054          * longitude argument and the m index is the integer multiplier for m-dailies.
2055          * </p>
2056          */
2057         @Override
2058         public Map<String, T[]> getCoefficients(final FieldAbsoluteDate<T> date, final Set<String> selected) {
2059 
2060             // select the coefficients slot
2061             final FieldSlot<T> slot = slots.get(date);
2062 
2063             if (!nonResOrders.isEmpty() || mDailiesOnly) {
2064                 final Map<String, T[]> coefficients = new HashMap<>(12 * maxOrderMdailyTesseralSP + 12 * nonResOrders.size());
2065 
2066                 for (int m = 1; m <= maxOrderMdailyTesseralSP; m++) {
2067                     storeIfSelected(coefficients, selected, slot.getCijm(0, m, date), DSSTTesseral.CM_COEFFICIENTS, m);
2068                     storeIfSelected(coefficients, selected, slot.getSijm(0, m, date), DSSTTesseral.SM_COEFFICIENTS, m);
2069                 }
2070 
2071                 for (final Map.Entry<Integer, List<Integer>> entry : nonResOrders.entrySet()) {
2072                     final int           m     = entry.getKey();
2073                     final List<Integer> listJ = entry.getValue();
2074 
2075                     for (int j : listJ) {
2076                         for (int i = 0; i < 6; ++i) {
2077                             storeIfSelected(coefficients, selected, slot.getCijm(j, m, date), "c", j, m);
2078                             storeIfSelected(coefficients, selected, slot.getSijm(j, m, date), "s", j, m);
2079                         }
2080                     }
2081                 }
2082 
2083                 return coefficients;
2084 
2085             } else {
2086                 return Collections.emptyMap();
2087             }
2088 
2089         }
2090 
2091         /** Put a coefficient in a map if selected.
2092          * @param map map to populate
2093          * @param selected set of coefficients that should be put in the map
2094          * (empty set means all coefficients are selected)
2095          * @param value coefficient value
2096          * @param id coefficient identifier
2097          * @param indices list of coefficient indices
2098          */
2099         private void storeIfSelected(final Map<String, T[]> map, final Set<String> selected,
2100                                      final T[] value, final String id, final int... indices) {
2101             final StringBuilder keyBuilder = new StringBuilder(getCoefficientsKeyPrefix());
2102             keyBuilder.append(id);
2103             for (int index : indices) {
2104                 keyBuilder.append('[').append(index).append(']');
2105             }
2106             final String key = keyBuilder.toString();
2107             if (selected.isEmpty() || selected.contains(key)) {
2108                 map.put(key, value);
2109             }
2110         }
2111     }
2112 
2113     /** Coefficients valid for one time slot. */
2114     private static class Slot {
2115 
2116         /** The coefficients C<sub>i</sub><sup>j</sup><sup>m</sup>.
2117          * <p>
2118          * The index order is cijm[m][j][i] <br/>
2119          * i corresponds to the equinoctial element, as follows: <br/>
2120          * - i=0 for a <br/>
2121          * - i=1 for k <br/>
2122          * - i=2 for h <br/>
2123          * - i=3 for q <br/>
2124          * - i=4 for p <br/>
2125          * - i=5 for λ <br/>
2126          * </p>
2127          */
2128         private final ShortPeriodicsInterpolatedCoefficient[][] cijm;
2129 
2130         /** The coefficients S<sub>i</sub><sup>j</sup><sup>m</sup>.
2131          * <p>
2132          * The index order is sijm[m][j][i] <br/>
2133          * i corresponds to the equinoctial element, as follows: <br/>
2134          * - i=0 for a <br/>
2135          * - i=1 for k <br/>
2136          * - i=2 for h <br/>
2137          * - i=3 for q <br/>
2138          * - i=4 for p <br/>
2139          * - i=5 for λ <br/>
2140          * </p>
2141          */
2142         private final ShortPeriodicsInterpolatedCoefficient[][] sijm;
2143 
2144         /** Simple constructor.
2145          *  @param mMax maximum value for m index
2146          *  @param jMax maximum value for j index
2147          *  @param interpolationPoints number of points used in the interpolation process
2148          */
2149         Slot(final int mMax, final int jMax, final int interpolationPoints) {
2150 
2151             final int rows    = mMax + 1;
2152             final int columns = 2 * jMax + 1;
2153             cijm = new ShortPeriodicsInterpolatedCoefficient[rows][columns];
2154             sijm = new ShortPeriodicsInterpolatedCoefficient[rows][columns];
2155             for (int m = 1; m <= mMax; m++) {
2156                 for (int j = -jMax; j <= jMax; j++) {
2157                     cijm[m][j + jMax] = new ShortPeriodicsInterpolatedCoefficient(interpolationPoints);
2158                     sijm[m][j + jMax] = new ShortPeriodicsInterpolatedCoefficient(interpolationPoints);
2159                 }
2160             }
2161 
2162         }
2163 
2164         /** Get C<sub>i</sub><sup>j</sup><sup>m</sup>.
2165          *
2166          * @param j j index
2167          * @param m m index
2168          * @param date the date
2169          * @return C<sub>i</sub><sup>j</sup><sup>m</sup>
2170          */
2171         double[] getCijm(final int j, final int m, final AbsoluteDate date) {
2172             final int jMax = (cijm[m].length - 1) / 2;
2173             return cijm[m][j + jMax].value(date);
2174         }
2175 
2176         /** Get S<sub>i</sub><sup>j</sup><sup>m</sup>.
2177          *
2178          * @param j j index
2179          * @param m m index
2180          * @param date the date
2181          * @return S<sub>i</sub><sup>j</sup><sup>m</sup>
2182          */
2183         double[] getSijm(final int j, final int m, final AbsoluteDate date) {
2184             final int jMax = (cijm[m].length - 1) / 2;
2185             return sijm[m][j + jMax].value(date);
2186         }
2187 
2188     }
2189 
2190     /** Coefficients valid for one time slot. */
2191     private static class FieldSlot <T extends CalculusFieldElement<T>> {
2192 
2193         /** The coefficients C<sub>i</sub><sup>j</sup><sup>m</sup>.
2194          * <p>
2195          * The index order is cijm[m][j][i] <br/>
2196          * i corresponds to the equinoctial element, as follows: <br/>
2197          * - i=0 for a <br/>
2198          * - i=1 for k <br/>
2199          * - i=2 for h <br/>
2200          * - i=3 for q <br/>
2201          * - i=4 for p <br/>
2202          * - i=5 for λ <br/>
2203          * </p>
2204          */
2205         private final FieldShortPeriodicsInterpolatedCoefficient<T>[][] cijm;
2206 
2207         /** The coefficients S<sub>i</sub><sup>j</sup><sup>m</sup>.
2208          * <p>
2209          * The index order is sijm[m][j][i] <br/>
2210          * i corresponds to the equinoctial element, as follows: <br/>
2211          * - i=0 for a <br/>
2212          * - i=1 for k <br/>
2213          * - i=2 for h <br/>
2214          * - i=3 for q <br/>
2215          * - i=4 for p <br/>
2216          * - i=5 for λ <br/>
2217          * </p>
2218          */
2219         private final FieldShortPeriodicsInterpolatedCoefficient<T>[][] sijm;
2220 
2221         /** Simple constructor.
2222          *  @param mMax maximum value for m index
2223          *  @param jMax maximum value for j index
2224          *  @param interpolationPoints number of points used in the interpolation process
2225          */
2226         @SuppressWarnings("unchecked")
2227         FieldSlot(final int mMax, final int jMax, final int interpolationPoints) {
2228 
2229             final int rows    = mMax + 1;
2230             final int columns = 2 * jMax + 1;
2231             cijm = (FieldShortPeriodicsInterpolatedCoefficient<T>[][]) Array.newInstance(FieldShortPeriodicsInterpolatedCoefficient.class, rows, columns);
2232             sijm = (FieldShortPeriodicsInterpolatedCoefficient<T>[][]) Array.newInstance(FieldShortPeriodicsInterpolatedCoefficient.class, rows, columns);
2233             for (int m = 1; m <= mMax; m++) {
2234                 for (int j = -jMax; j <= jMax; j++) {
2235                     cijm[m][j + jMax] = new FieldShortPeriodicsInterpolatedCoefficient<>(interpolationPoints);
2236                     sijm[m][j + jMax] = new FieldShortPeriodicsInterpolatedCoefficient<>(interpolationPoints);
2237                 }
2238             }
2239 
2240         }
2241 
2242         /** Get C<sub>i</sub><sup>j</sup><sup>m</sup>.
2243          *
2244          * @param j j index
2245          * @param m m index
2246          * @param date the date
2247          * @return C<sub>i</sub><sup>j</sup><sup>m</sup>
2248          */
2249         T[] getCijm(final int j, final int m, final FieldAbsoluteDate<T> date) {
2250             final int jMax = (cijm[m].length - 1) / 2;
2251             return cijm[m][j + jMax].value(date);
2252         }
2253 
2254         /** Get S<sub>i</sub><sup>j</sup><sup>m</sup>.
2255          *
2256          * @param j j index
2257          * @param m m index
2258          * @param date the date
2259          * @return S<sub>i</sub><sup>j</sup><sup>m</sup>
2260          */
2261         T[] getSijm(final int j, final int m, final FieldAbsoluteDate<T> date) {
2262             final int jMax = (cijm[m].length - 1) / 2;
2263             return sijm[m][j + jMax].value(date);
2264         }
2265 
2266     }
2267 
2268     /** Compute potential and potential derivatives with respect to orbital parameters.
2269      *  <p>The following elements are computed from expression 3.3 - (4).
2270      *  <pre>
2271      *  dU / da
2272      *  dU / dh
2273      *  dU / dk
2274      *  dU / dλ
2275      *  dU / dα
2276      *  dU / dβ
2277      *  dU / dγ
2278      *  </pre>
2279      *  </p>
2280      */
2281     private class UAnddU {
2282 
2283         /** dU / da. */
2284         private  double dUda;
2285 
2286         /** dU / dk. */
2287         private double dUdk;
2288 
2289         /** dU / dh. */
2290         private double dUdh;
2291 
2292         /** dU / dl. */
2293         private double dUdl;
2294 
2295         /** dU / dAlpha. */
2296         private double dUdAl;
2297 
2298         /** dU / dBeta. */
2299         private double dUdBe;
2300 
2301         /** dU / dGamma. */
2302         private double dUdGa;
2303 
2304         /** Simple constuctor.
2305          * @param date current date
2306          * @param context container for attributes
2307          * @param hansen hansen objects
2308          */
2309         UAnddU(final AbsoluteDate date, final DSSTTesseralContext context, final HansenObjects hansen) {
2310 
2311             // Auxiliary elements related to the current orbit
2312             final AuxiliaryElements auxiliaryElements = context.getAuxiliaryElements();
2313 
2314             // Potential derivatives
2315             dUda  = 0.;
2316             dUdh  = 0.;
2317             dUdk  = 0.;
2318             dUdl  = 0.;
2319             dUdAl = 0.;
2320             dUdBe = 0.;
2321             dUdGa = 0.;
2322 
2323             // Compute only if there is at least one resonant tesseral
2324             if (!resOrders.isEmpty()) {
2325                 // Gmsj and Hmsj polynomials
2326                 final GHmsjPolynomials ghMSJ = new GHmsjPolynomials(auxiliaryElements.getK(), auxiliaryElements.getH(), context.getAlpha(), context.getBeta(), I);
2327 
2328                 // GAMMAmns function
2329                 final GammaMnsFunction gammaMNS = new GammaMnsFunction(maxDegree, context.getGamma(), I);
2330 
2331                 // R / a up to power degree
2332                 final double[] roaPow = new double[maxDegree + 1];
2333                 roaPow[0] = 1.;
2334                 for (int i = 1; i <= maxDegree; i++) {
2335                     roaPow[i] = context.getRoa() * roaPow[i - 1];
2336                 }
2337 
2338                 // SUM over resonant terms {j,m}
2339                 for (int m : resOrders) {
2340 
2341                     // Resonant index for the current resonant order
2342                     final int j = FastMath.max(1, (int) FastMath.round(context.getRatio() * m));
2343 
2344                     // Phase angle
2345                     final double jlMmt  = j * auxiliaryElements.getLM() - m * context.getTheta();
2346                     final SinCos scPhi  = FastMath.sinCos(jlMmt);
2347                     final double sinPhi = scPhi.sin();
2348                     final double cosPhi = scPhi.cos();
2349 
2350                     // Potential derivatives components for a given resonant pair {j,m}
2351                     double dUdaCos  = 0.;
2352                     double dUdaSin  = 0.;
2353                     double dUdhCos  = 0.;
2354                     double dUdhSin  = 0.;
2355                     double dUdkCos  = 0.;
2356                     double dUdkSin  = 0.;
2357                     double dUdlCos  = 0.;
2358                     double dUdlSin  = 0.;
2359                     double dUdAlCos = 0.;
2360                     double dUdAlSin = 0.;
2361                     double dUdBeCos = 0.;
2362                     double dUdBeSin = 0.;
2363                     double dUdGaCos = 0.;
2364                     double dUdGaSin = 0.;
2365 
2366                     // s-SUM from -sMin to sMax
2367                     final int sMin = FastMath.min(maxEccPow - j, maxDegree);
2368                     final int sMax = FastMath.min(maxEccPow + j, maxDegree);
2369                     for (int s = 0; s <= sMax; s++) {
2370 
2371                         //Compute the initial values for Hansen coefficients using newComb operators
2372                         hansen.computeHansenObjectsInitValues(context, s + maxDegree, j);
2373 
2374                         // n-SUM for s positive
2375                         final double[][] nSumSpos = computeNSum(date, j, m, s, maxDegree,
2376                                                                 roaPow, ghMSJ, gammaMNS, context, hansen);
2377                         dUdaCos  += nSumSpos[0][0];
2378                         dUdaSin  += nSumSpos[0][1];
2379                         dUdhCos  += nSumSpos[1][0];
2380                         dUdhSin  += nSumSpos[1][1];
2381                         dUdkCos  += nSumSpos[2][0];
2382                         dUdkSin  += nSumSpos[2][1];
2383                         dUdlCos  += nSumSpos[3][0];
2384                         dUdlSin  += nSumSpos[3][1];
2385                         dUdAlCos += nSumSpos[4][0];
2386                         dUdAlSin += nSumSpos[4][1];
2387                         dUdBeCos += nSumSpos[5][0];
2388                         dUdBeSin += nSumSpos[5][1];
2389                         dUdGaCos += nSumSpos[6][0];
2390                         dUdGaSin += nSumSpos[6][1];
2391 
2392                         // n-SUM for s negative
2393                         if (s > 0 && s <= sMin) {
2394                             //Compute the initial values for Hansen coefficients using newComb operators
2395                             hansen.computeHansenObjectsInitValues(context, maxDegree - s, j);
2396 
2397                             final double[][] nSumSneg = computeNSum(date, j, m, -s, maxDegree,
2398                                                                     roaPow, ghMSJ, gammaMNS, context, hansen);
2399                             dUdaCos  += nSumSneg[0][0];
2400                             dUdaSin  += nSumSneg[0][1];
2401                             dUdhCos  += nSumSneg[1][0];
2402                             dUdhSin  += nSumSneg[1][1];
2403                             dUdkCos  += nSumSneg[2][0];
2404                             dUdkSin  += nSumSneg[2][1];
2405                             dUdlCos  += nSumSneg[3][0];
2406                             dUdlSin  += nSumSneg[3][1];
2407                             dUdAlCos += nSumSneg[4][0];
2408                             dUdAlSin += nSumSneg[4][1];
2409                             dUdBeCos += nSumSneg[5][0];
2410                             dUdBeSin += nSumSneg[5][1];
2411                             dUdGaCos += nSumSneg[6][0];
2412                             dUdGaSin += nSumSneg[6][1];
2413                         }
2414                     }
2415 
2416                     // Assembly of potential derivatives componants
2417                     dUda  += cosPhi * dUdaCos  + sinPhi * dUdaSin;
2418                     dUdh  += cosPhi * dUdhCos  + sinPhi * dUdhSin;
2419                     dUdk  += cosPhi * dUdkCos  + sinPhi * dUdkSin;
2420                     dUdl  += cosPhi * dUdlCos  + sinPhi * dUdlSin;
2421                     dUdAl += cosPhi * dUdAlCos + sinPhi * dUdAlSin;
2422                     dUdBe += cosPhi * dUdBeCos + sinPhi * dUdBeSin;
2423                     dUdGa += cosPhi * dUdGaCos + sinPhi * dUdGaSin;
2424                 }
2425 
2426                 final double muOnA = context.getMuoa();
2427                 this.dUda  = dUda * (-muOnA / auxiliaryElements.getSma());
2428                 this.dUdh  = dUdh * muOnA;
2429                 this.dUdk  = dUdk * muOnA;
2430                 this.dUdl  = dUdl * muOnA;
2431                 this.dUdAl = dUdAl * muOnA;
2432                 this.dUdBe = dUdBe * muOnA;
2433                 this.dUdGa = dUdGa * muOnA;
2434             }
2435 
2436         }
2437 
2438         /** Return value of dU / da.
2439          * @return dUda
2440          */
2441         public double getdUda() {
2442             return dUda;
2443         }
2444 
2445         /** Return value of dU / dk.
2446          * @return dUdk
2447          */
2448         public double getdUdk() {
2449             return dUdk;
2450         }
2451 
2452         /** Return value of dU / dh.
2453          * @return dUdh
2454          */
2455         public double getdUdh() {
2456             return dUdh;
2457         }
2458 
2459         /** Return value of dU / dl.
2460          * @return dUdl
2461          */
2462         public double getdUdl() {
2463             return dUdl;
2464         }
2465 
2466         /** Return value of dU / dAlpha.
2467          * @return dUdAl
2468          */
2469         public double getdUdAl() {
2470             return dUdAl;
2471         }
2472 
2473         /** Return value of dU / dBeta.
2474          * @return dUdBe
2475          */
2476         public double getdUdBe() {
2477             return dUdBe;
2478         }
2479 
2480         /** Return value of dU / dGamma.
2481          * @return dUdGa
2482          */
2483         public double getdUdGa() {
2484             return dUdGa;
2485         }
2486 
2487     }
2488 
2489     /**  Computes the potential U derivatives.
2490      *  <p>The following elements are computed from expression 3.3 - (4).
2491      *  <pre>
2492      *  dU / da
2493      *  dU / dh
2494      *  dU / dk
2495      *  dU / dλ
2496      *  dU / dα
2497      *  dU / dβ
2498      *  dU / dγ
2499      *  </pre>
2500      *  </p>
2501      */
2502     private class FieldUAnddU <T extends CalculusFieldElement<T>> {
2503 
2504         /** dU / da. */
2505         private T dUda;
2506 
2507         /** dU / dk. */
2508         private T dUdk;
2509 
2510         /** dU / dh. */
2511         private T dUdh;
2512 
2513         /** dU / dl. */
2514         private T dUdl;
2515 
2516         /** dU / dAlpha. */
2517         private T dUdAl;
2518 
2519         /** dU / dBeta. */
2520         private T dUdBe;
2521 
2522         /** dU / dGamma. */
2523         private T dUdGa;
2524 
2525         /** Simple constuctor.
2526          * @param date current date
2527          * @param context container for attributes
2528          * @param hansen hansen objects
2529          */
2530         FieldUAnddU(final FieldAbsoluteDate<T> date, final FieldDSSTTesseralContext<T> context,
2531                     final FieldHansenObjects<T> hansen) {
2532 
2533             // Auxiliary elements related to the current orbit
2534             final FieldAuxiliaryElements<T> auxiliaryElements = context.getFieldAuxiliaryElements();
2535 
2536             // Zero for initialization
2537             final Field<T> field = date.getField();
2538             final T zero = field.getZero();
2539 
2540             // Potential derivatives
2541             dUda  = zero;
2542             dUdh  = zero;
2543             dUdk  = zero;
2544             dUdl  = zero;
2545             dUdAl = zero;
2546             dUdBe = zero;
2547             dUdGa = zero;
2548 
2549             // Compute only if there is at least one resonant tesseral
2550             if (!resOrders.isEmpty()) {
2551                 // Gmsj and Hmsj polynomials
2552                 final FieldGHmsjPolynomials<T> ghMSJ = new FieldGHmsjPolynomials<>(auxiliaryElements.getK(), auxiliaryElements.getH(), context.getAlpha(), context.getBeta(), I, field);
2553 
2554                 // GAMMAmns function
2555                 final FieldGammaMnsFunction<T> gammaMNS = new FieldGammaMnsFunction<>(maxDegree, context.getGamma(), I, field);
2556 
2557                 // R / a up to power degree
2558                 final T[] roaPow = MathArrays.buildArray(field, maxDegree + 1);
2559                 roaPow[0] = zero.newInstance(1.);
2560                 for (int i = 1; i <= maxDegree; i++) {
2561                     roaPow[i] = roaPow[i - 1].multiply(context.getRoa());
2562                 }
2563 
2564                 // SUM over resonant terms {j,m}
2565                 for (int m : resOrders) {
2566 
2567                     // Resonant index for the current resonant order
2568                     final int j = FastMath.max(1, (int) FastMath.round(context.getRatio().multiply(m)));
2569 
2570                     // Phase angle
2571                     final T jlMmt              = auxiliaryElements.getLM().multiply(j).subtract(context.getTheta().multiply(m));
2572                     final FieldSinCos<T> scPhi = FastMath.sinCos(jlMmt);
2573                     final T sinPhi             = scPhi.sin();
2574                     final T cosPhi             = scPhi.cos();
2575 
2576                     // Potential derivatives components for a given resonant pair {j,m}
2577                     T dUdaCos  = zero;
2578                     T dUdaSin  = zero;
2579                     T dUdhCos  = zero;
2580                     T dUdhSin  = zero;
2581                     T dUdkCos  = zero;
2582                     T dUdkSin  = zero;
2583                     T dUdlCos  = zero;
2584                     T dUdlSin  = zero;
2585                     T dUdAlCos = zero;
2586                     T dUdAlSin = zero;
2587                     T dUdBeCos = zero;
2588                     T dUdBeSin = zero;
2589                     T dUdGaCos = zero;
2590                     T dUdGaSin = zero;
2591 
2592                     // s-SUM from -sMin to sMax
2593                     final int sMin = FastMath.min(maxEccPow - j, maxDegree);
2594                     final int sMax = FastMath.min(maxEccPow + j, maxDegree);
2595                     for (int s = 0; s <= sMax; s++) {
2596 
2597                         //Compute the initial values for Hansen coefficients using newComb operators
2598                         hansen.computeHansenObjectsInitValues(context, s + maxDegree, j);
2599 
2600                         // n-SUM for s positive
2601                         final T[][] nSumSpos = computeNSum(date, j, m, s, maxDegree,
2602                                                                 roaPow, ghMSJ, gammaMNS, context, hansen);
2603                         dUdaCos  = dUdaCos.add(nSumSpos[0][0]);
2604                         dUdaSin  = dUdaSin.add(nSumSpos[0][1]);
2605                         dUdhCos  = dUdhCos.add(nSumSpos[1][0]);
2606                         dUdhSin  = dUdhSin.add(nSumSpos[1][1]);
2607                         dUdkCos  = dUdkCos.add(nSumSpos[2][0]);
2608                         dUdkSin  = dUdkSin.add(nSumSpos[2][1]);
2609                         dUdlCos  = dUdlCos.add(nSumSpos[3][0]);
2610                         dUdlSin  = dUdlSin.add(nSumSpos[3][1]);
2611                         dUdAlCos = dUdAlCos.add(nSumSpos[4][0]);
2612                         dUdAlSin = dUdAlSin.add(nSumSpos[4][1]);
2613                         dUdBeCos = dUdBeCos.add(nSumSpos[5][0]);
2614                         dUdBeSin = dUdBeSin.add(nSumSpos[5][1]);
2615                         dUdGaCos = dUdGaCos.add(nSumSpos[6][0]);
2616                         dUdGaSin = dUdGaSin.add(nSumSpos[6][1]);
2617 
2618                         // n-SUM for s negative
2619                         if (s > 0 && s <= sMin) {
2620                             //Compute the initial values for Hansen coefficients using newComb operators
2621                             hansen.computeHansenObjectsInitValues(context, maxDegree - s, j);
2622 
2623                             final T[][] nSumSneg = computeNSum(date, j, m, -s, maxDegree,
2624                                                                     roaPow, ghMSJ, gammaMNS, context, hansen);
2625                             dUdaCos  = dUdaCos.add(nSumSneg[0][0]);
2626                             dUdaSin  = dUdaSin.add(nSumSneg[0][1]);
2627                             dUdhCos  = dUdhCos.add(nSumSneg[1][0]);
2628                             dUdhSin  = dUdhSin.add(nSumSneg[1][1]);
2629                             dUdkCos  = dUdkCos.add(nSumSneg[2][0]);
2630                             dUdkSin  = dUdkSin.add(nSumSneg[2][1]);
2631                             dUdlCos  = dUdlCos.add(nSumSneg[3][0]);
2632                             dUdlSin  = dUdlSin.add(nSumSneg[3][1]);
2633                             dUdAlCos = dUdAlCos.add(nSumSneg[4][0]);
2634                             dUdAlSin = dUdAlSin.add(nSumSneg[4][1]);
2635                             dUdBeCos = dUdBeCos.add(nSumSneg[5][0]);
2636                             dUdBeSin = dUdBeSin.add(nSumSneg[5][1]);
2637                             dUdGaCos = dUdGaCos.add(nSumSneg[6][0]);
2638                             dUdGaSin = dUdGaSin.add(nSumSneg[6][1]);
2639                         }
2640                     }
2641 
2642                     // Assembly of potential derivatives componants
2643                     dUda  = dUda.add(dUdaCos.multiply(cosPhi).add(dUdaSin.multiply(sinPhi)));
2644                     dUdh  = dUdh.add(dUdhCos.multiply(cosPhi).add(dUdhSin.multiply(sinPhi)));
2645                     dUdk  = dUdk.add(dUdkCos.multiply(cosPhi).add(dUdkSin.multiply(sinPhi)));
2646                     dUdl  = dUdl.add(dUdlCos.multiply(cosPhi).add(dUdlSin.multiply(sinPhi)));
2647                     dUdAl = dUdAl.add(dUdAlCos.multiply(cosPhi).add(dUdAlSin.multiply(sinPhi)));
2648                     dUdBe = dUdBe.add(dUdBeCos.multiply(cosPhi).add(dUdBeSin.multiply(sinPhi)));
2649                     dUdGa = dUdGa.add(dUdGaCos.multiply(cosPhi).add(dUdGaSin.multiply(sinPhi)));
2650                 }
2651 
2652                 final T muOnA = context.getMuoa();
2653                 dUda  =  dUda.multiply(muOnA.divide(auxiliaryElements.getSma())).negate();
2654                 dUdh  =  dUdh.multiply(muOnA);
2655                 dUdk  =  dUdk.multiply(muOnA);
2656                 dUdl  =  dUdl.multiply(muOnA);
2657                 dUdAl =  dUdAl.multiply(muOnA);
2658                 dUdBe =  dUdBe.multiply(muOnA);
2659                 dUdGa =  dUdGa.multiply(muOnA);
2660             }
2661         }
2662 
2663         /** Return value of dU / da.
2664          * @return dUda
2665          */
2666         public T getdUda() {
2667             return dUda;
2668         }
2669 
2670         /** Return value of dU / dk.
2671          * @return dUdk
2672          */
2673         public T getdUdk() {
2674             return dUdk;
2675         }
2676 
2677         /** Return value of dU / dh.
2678          * @return dUdh
2679          */
2680         public T getdUdh() {
2681             return dUdh;
2682         }
2683 
2684         /** Return value of dU / dl.
2685          * @return dUdl
2686          */
2687         public T getdUdl() {
2688             return dUdl;
2689         }
2690 
2691         /** Return value of dU / dAlpha.
2692          * @return dUdAl
2693          */
2694         public T getdUdAl() {
2695             return dUdAl;
2696         }
2697 
2698         /** Return value of dU / dBeta.
2699          * @return dUdBe
2700          */
2701         public T getdUdBe() {
2702             return dUdBe;
2703         }
2704 
2705         /** Return value of dU / dGamma.
2706          * @return dUdGa
2707          */
2708         public T getdUdGa() {
2709             return dUdGa;
2710         }
2711 
2712     }
2713 
2714     /** Computes init values of the Hansen Objects. */
2715     private class HansenObjects {
2716 
2717         /** A two dimensional array that contains the objects needed to build the Hansen coefficients. <br/>
2718          * The indexes are s + maxDegree and j */
2719         private final HansenTesseralLinear[][] hansenObjects;
2720 
2721         /** Simple constructor.
2722          * @param ratio Ratio of satellite period to central body rotation period
2723          * @param type type of the elements used during the propagation
2724          */
2725         HansenObjects(final double ratio,
2726                       final PropagationType type) {
2727 
2728             //Allocate the two dimensional array
2729             final int rows     = 2 * maxDegree + 1;
2730             final int columns  = maxFrequencyShortPeriodics + 1;
2731             this.hansenObjects = new HansenTesseralLinear[rows][columns];
2732 
2733             switch (type) {
2734                 case MEAN:
2735                     // loop through the resonant orders
2736                     for (int m : resOrders) {
2737                         //Compute the corresponding j term
2738                         final int j = FastMath.max(1, (int) FastMath.round(ratio * m));
2739 
2740                         //Compute the sMin and sMax values
2741                         final int sMin = FastMath.min(maxEccPow - j, maxDegree);
2742                         final int sMax = FastMath.min(maxEccPow + j, maxDegree);
2743 
2744                         //loop through the s values
2745                         for (int s = 0; s <= sMax; s++) {
2746                             //Compute the n0 value
2747                             final int n0 = FastMath.max(FastMath.max(2, m), s);
2748 
2749                             //Create the object for the pair j, s
2750                             this.hansenObjects[s + maxDegree][j] = new HansenTesseralLinear(maxDegree, s, j, n0, maxHansen);
2751 
2752                             if (s > 0 && s <= sMin) {
2753                                 //Also create the object for the pair j, -s
2754                                 this.hansenObjects[maxDegree - s][j] =  new HansenTesseralLinear(maxDegree, -s, j, n0, maxHansen);
2755                             }
2756                         }
2757                     }
2758                     break;
2759 
2760                 case OSCULATING:
2761                     // create all objects
2762                     for (int j = 0; j <= maxFrequencyShortPeriodics; j++) {
2763                         for (int s = -maxDegree; s <= maxDegree; s++) {
2764                             //Compute the n0 value
2765                             final int n0 = FastMath.max(2, FastMath.abs(s));
2766                             this.hansenObjects[s + maxDegree][j] = new HansenTesseralLinear(maxDegree, s, j, n0, maxHansen);
2767                         }
2768                     }
2769                     break;
2770 
2771                 default:
2772                     throw new OrekitInternalError(null);
2773             }
2774 
2775         }
2776 
2777         /** Compute init values for hansen objects.
2778          * @param context container for attributes
2779          * @param rows number of rows of the hansen matrix
2780          * @param columns columns number of columns of the hansen matrix
2781          */
2782         public void computeHansenObjectsInitValues(final DSSTTesseralContext context, final int rows, final int columns) {
2783             hansenObjects[rows][columns].computeInitValues(context.getE2(), context.getChi(), context.getChi2());
2784         }
2785 
2786         /** Get hansen object.
2787          * @return hansenObjects
2788          */
2789         public HansenTesseralLinear[][] getHansenObjects() {
2790             return hansenObjects;
2791         }
2792 
2793     }
2794 
2795     /** Computes init values of the Hansen Objects. */
2796     private class FieldHansenObjects<T extends CalculusFieldElement<T>> {
2797 
2798         /** A two dimensional array that contains the objects needed to build the Hansen coefficients. <br/>
2799          * The indexes are s + maxDegree and j */
2800         private final FieldHansenTesseralLinear<T>[][] hansenObjects;
2801 
2802         /** Simple constructor.
2803          * @param ratio Ratio of satellite period to central body rotation period
2804          * @param type type of the elements used during the propagation
2805          */
2806         @SuppressWarnings("unchecked")
2807         FieldHansenObjects(final T ratio,
2808                            final PropagationType type) {
2809 
2810             // Set the maximum power of the eccentricity to use in Hansen coefficient Kernel expansion.
2811             maxHansen = maxEccPow / 2;
2812 
2813             //Allocate the two dimensional array
2814             final int rows     = 2 * maxDegree + 1;
2815             final int columns  = maxFrequencyShortPeriodics + 1;
2816             this.hansenObjects = (FieldHansenTesseralLinear<T>[][]) Array.newInstance(FieldHansenTesseralLinear.class, rows, columns);
2817 
2818             switch (type) {
2819                 case MEAN:
2820                  // loop through the resonant orders
2821                     for (int m : resOrders) {
2822                         //Compute the corresponding j term
2823                         final int j = FastMath.max(1, (int) FastMath.round(ratio.multiply(m)));
2824 
2825                         //Compute the sMin and sMax values
2826                         final int sMin = FastMath.min(maxEccPow - j, maxDegree);
2827                         final int sMax = FastMath.min(maxEccPow + j, maxDegree);
2828 
2829                         //loop through the s values
2830                         for (int s = 0; s <= sMax; s++) {
2831                             //Compute the n0 value
2832                             final int n0 = FastMath.max(FastMath.max(2, m), s);
2833 
2834                             //Create the object for the pair j, s
2835                             this.hansenObjects[s + maxDegree][j] = new FieldHansenTesseralLinear<>(maxDegree, s, j, n0, maxHansen, ratio.getField());
2836 
2837                             if (s > 0 && s <= sMin) {
2838                                 //Also create the object for the pair j, -s
2839                                 this.hansenObjects[maxDegree - s][j] =  new FieldHansenTesseralLinear<>(maxDegree, -s, j, n0, maxHansen, ratio.getField());
2840                             }
2841                         }
2842                     }
2843                     break;
2844 
2845                 case OSCULATING:
2846                     // create all objects
2847                     for (int j = 0; j <= maxFrequencyShortPeriodics; j++) {
2848                         for (int s = -maxDegree; s <= maxDegree; s++) {
2849                             //Compute the n0 value
2850                             final int n0 = FastMath.max(2, FastMath.abs(s));
2851                             this.hansenObjects[s + maxDegree][j] = new FieldHansenTesseralLinear<>(maxDegree, s, j, n0, maxHansen, ratio.getField());
2852                         }
2853                     }
2854                     break;
2855 
2856                 default:
2857                     throw new OrekitInternalError(null);
2858             }
2859 
2860         }
2861 
2862         /** Compute init values for hansen objects.
2863          * @param context container for attributes
2864          * @param rows number of rows of the hansen matrix
2865          * @param columns columns number of columns of the hansen matrix
2866          */
2867         public void computeHansenObjectsInitValues(final FieldDSSTTesseralContext<T> context,
2868                                                    final int rows, final int columns) {
2869             hansenObjects[rows][columns].computeInitValues(context.getE2(), context.getChi(), context.getChi2());
2870         }
2871 
2872         /** Get hansen object.
2873          * @return hansenObjects
2874          */
2875         public FieldHansenTesseralLinear<T>[][] getHansenObjects() {
2876             return hansenObjects;
2877         }
2878 
2879     }
2880 
2881 }