1   /* Copyright 2002-2013 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.propagation.semianalytical.dsst.forces;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.TreeMap;
22  
23  import org.apache.commons.math3.analysis.differentiation.DerivativeStructure;
24  import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
25  import org.apache.commons.math3.util.FastMath;
26  import org.apache.commons.math3.util.MathUtils;
27  import org.orekit.errors.OrekitException;
28  import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider;
29  import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider.UnnormalizedSphericalHarmonics;
30  import org.orekit.frames.Frame;
31  import org.orekit.frames.Transform;
32  import org.orekit.propagation.SpacecraftState;
33  import org.orekit.propagation.events.EventDetector;
34  import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
35  import org.orekit.propagation.semianalytical.dsst.utilities.CoefficientsFactory;
36  import org.orekit.propagation.semianalytical.dsst.utilities.CoefficientsFactory.MNSKey;
37  import org.orekit.propagation.semianalytical.dsst.utilities.GHmsjPolynomials;
38  import org.orekit.propagation.semianalytical.dsst.utilities.GammaMnsFunction;
39  import org.orekit.propagation.semianalytical.dsst.utilities.JacobiPolynomials;
40  import org.orekit.propagation.semianalytical.dsst.utilities.NewcombOperators;
41  import org.orekit.time.AbsoluteDate;
42  
43  /** Tesseral contribution to the {@link DSSTCentralBody central body gravitational
44   *  perturbation}.
45   *  <p>
46   *  Only resonant tesserals are considered.
47   *  </p>
48   *
49   *  @author Romain Di Costanzo
50   *  @author Pascal Parraud
51   */
52  class TesseralContribution implements DSSTForceModel {
53  
54      /** Minimum period for analytically averaged high-order resonant
55       *  central body spherical harmonics in seconds.
56       */
57      private static final double MIN_PERIOD_IN_SECONDS = 864000.;
58  
59      /** Minimum period for analytically averaged high-order resonant
60       *  central body spherical harmonics in satellite revolutions.
61       */
62      private static final double MIN_PERIOD_IN_SAT_REV = 10.;
63  
64      /** Provider for spherical harmonics. */
65      private final UnnormalizedSphericalHarmonicsProvider provider;
66  
67      /** Central body rotating frame. */
68      private final Frame bodyFrame;
69  
70      /** Central body rotation period (seconds). */
71      private final double bodyPeriod;
72  
73      /** Maximal degree to consider for harmonics potential. */
74      private final int maxDegree;
75  
76      /** List of resonant orders. */
77      private final List<Integer> resOrders;
78  
79      /** Factorial. */
80      private final double[] fact;
81  
82      /** Maximum power of the eccentricity to use in summation over s. */
83      private int maxEccPow;
84  
85      /** Maximum power of the eccentricity to use in Hansen coefficient Kernel expansion. */
86      private int maxHansen;
87  
88      /** Keplerian period. */
89      private double orbitPeriod;
90  
91      /** Ratio of satellite period to central body rotation period. */
92      private double ratio;
93  
94      /** Retrograde factor. */
95      private int    I;
96  
97      // Equinoctial elements (according to DSST notation)
98      /** a. */
99      private double a;
100     /** ex. */
101     private double k;
102     /** ey. */
103     private double h;
104     /** hx. */
105     private double q;
106     /** hy. */
107     private double p;
108     /** lm. */
109     private double lm;
110 
111     /** Eccentricity. */
112     private double ecc;
113 
114     // Equinoctial reference frame vectors (according to DSST notation)
115     /** Equinoctial frame f vector. */
116     private Vector3D f;
117     /** Equinoctial frame g vector. */
118     private Vector3D g;
119 
120     /** Central body rotation angle &theta;. */
121     private double theta;
122 
123     /** Direction cosine &alpha;. */
124     private double alpha;
125     /** Direction cosine &beta;. */
126     private double beta;
127     /** Direction cosine &gamma;. */
128     private double gamma;
129 
130     // Common factors from equinoctial coefficients
131     /** 2 * a / A .*/
132     private double ax2oA;
133     /** 1 / (A * B) .*/
134     private double ooAB;
135     /** B / A .*/
136     private double BoA;
137     /** B / (A * (1 + B)) .*/
138     private double BoABpo;
139     /** C / (2 * A * B) .*/
140     private double Co2AB;
141     /** &mu; / a .*/
142     private double moa;
143     /** R / a .*/
144     private double roa;
145 
146     /** Single constructor.
147      *  @param centralBodyFrame rotating body frame
148      *  @param centralBodyRotationRate central body rotation rate (rad/s)
149      *  @param provider provider for spherical harmonics
150      */
151     public TesseralContribution(final Frame centralBodyFrame,
152                                 final double centralBodyRotationRate,
153                                 final UnnormalizedSphericalHarmonicsProvider provider) {
154 
155         // Central body rotating frame
156         this.bodyFrame = centralBodyFrame;
157 
158         // Central body rotation period in seconds
159         this.bodyPeriod = MathUtils.TWO_PI / centralBodyRotationRate;
160 
161         // Provider for spherical harmonics
162         this.provider  = provider;
163         this.maxDegree = provider.getMaxDegree();
164 
165         // Initialize default values
166         this.resOrders = new ArrayList<Integer>();
167         this.maxEccPow = 0;
168         this.maxHansen = 0;
169 
170        // Factorials computation
171         final int maxFact = 2 * maxDegree + 1;
172         this.fact = new double[maxFact];
173         fact[0] = 1;
174         for (int i = 1; i < maxFact; i++) {
175             fact[i] = i * fact[i - 1];
176         }
177 
178     }
179 
180     /** {@inheritDoc} */
181     public void initialize(final AuxiliaryElements aux)
182         throws OrekitException {
183 
184         // Keplerian period
185         orbitPeriod = aux.getKeplerianPeriod();
186 
187         // Ratio of satellite to central body periods to define resonant terms
188         ratio = orbitPeriod / bodyPeriod;
189 
190         // Compute the resonant tesseral harmonic terms if not set by the user
191         getResonantTerms();
192 
193         // Set the highest power of the eccentricity in the analytical power
194         // series expansion for the averaged high order resonant central body
195         // spherical harmonic perturbation
196         final double e = aux.getEcc();
197         if (e <= 0.005) {
198             maxEccPow = 3;
199         } else if (e <= 0.02) {
200             maxEccPow = 4;
201         } else if (e <= 0.1) {
202             maxEccPow = 7;
203         } else if (e <= 0.2) {
204             maxEccPow = 10;
205         } else if (e <= 0.3) {
206             maxEccPow = 12;
207         } else if (e <= 0.4) {
208             maxEccPow = 15;
209         } else {
210             maxEccPow = 20;
211         }
212 
213         // Set the maximum power of the eccentricity to use in Hansen coefficient Kernel expansion.
214         maxHansen = maxEccPow / 2;
215 
216     }
217 
218     /** {@inheritDoc} */
219     public void initializeStep(final AuxiliaryElements aux) throws OrekitException {
220 
221         // Equinoctial elements
222         a  = aux.getSma();
223         k  = aux.getK();
224         h  = aux.getH();
225         q  = aux.getQ();
226         p  = aux.getP();
227         lm = aux.getLM();
228 
229         // Eccentricity
230         ecc = aux.getEcc();
231 
232         // Retrograde factor
233         I = aux.getRetrogradeFactor();
234 
235         // Equinoctial frame vectors
236         f = aux.getVectorF();
237         g = aux.getVectorG();
238 
239         // Central body rotation angle from equation 2.7.1-(3)(4).
240         final Transform t = bodyFrame.getTransformTo(aux.getFrame(), aux.getDate());
241         final Vector3D xB = t.transformVector(Vector3D.PLUS_I);
242         final Vector3D yB = t.transformVector(Vector3D.PLUS_J);
243         theta = FastMath.atan2(-f.dotProduct(yB) + I * g.dotProduct(xB),
244                                 f.dotProduct(xB) + I * g.dotProduct(yB));
245 
246         // Direction cosines
247         alpha = aux.getAlpha();
248         beta  = aux.getBeta();
249         gamma = aux.getGamma();
250 
251         // Equinoctial coefficients
252         // A = sqrt(&mu; * a)
253         final double A = aux.getA();
254         // B = sqrt(1 - h<sup>2</sup> - k<sup>2</sup>)
255         final double B = aux.getB();
256         // C = 1 + p<sup>2</sup> + q<sup>2</sup>
257         final double C = aux.getC();
258         // Common factors from equinoctial coefficients
259         // a / A
260         ax2oA  = 2. * a / A;
261         // B / A
262         BoA  = B / A;
263         // 1 / AB
264         ooAB = 1. / (A * B);
265         // C / 2AB
266         Co2AB = C * ooAB / 2.;
267         // B / (A * (1 + B))
268         BoABpo = BoA / (1. + B);
269         // &mu / a
270         moa = provider.getMu() / a;
271         // R / a
272         roa = provider.getAe() / a;
273 
274     }
275 
276     /** {@inheritDoc} */
277     public double[] getMeanElementRate(final SpacecraftState spacecraftState) throws OrekitException {
278 
279         // Compute potential derivatives
280         final double[] dU  = computeUDerivatives(spacecraftState.getDate());
281         final double dUda  = dU[0];
282         final double dUdh  = dU[1];
283         final double dUdk  = dU[2];
284         final double dUdl  = dU[3];
285         final double dUdAl = dU[4];
286         final double dUdBe = dU[5];
287         final double dUdGa = dU[6];
288 
289         // Compute the cross derivative operator :
290         final double UAlphaGamma   = alpha * dUdGa - gamma * dUdAl;
291         final double UAlphaBeta    = alpha * dUdBe - beta  * dUdAl;
292         final double UBetaGamma    =  beta * dUdGa - gamma * dUdBe;
293         final double Uhk           =     h * dUdk  -     k * dUdh;
294         final double pUagmIqUbgoAB = (p * UAlphaGamma - I * q * UBetaGamma) * ooAB;
295         final double UhkmUabmdUdl  =  Uhk - UAlphaBeta - dUdl;
296 
297         final double da =  ax2oA * dUdl;
298         final double dh =    BoA * dUdk + k * pUagmIqUbgoAB - h * BoABpo * dUdl;
299         final double dk =  -(BoA * dUdh + h * pUagmIqUbgoAB + k * BoABpo * dUdl);
300         final double dp =  Co2AB * (p * UhkmUabmdUdl - UBetaGamma);
301         final double dq =  Co2AB * (q * UhkmUabmdUdl - I * UAlphaGamma);
302         final double dM = -ax2oA * dUda + BoABpo * (h * dUdh + k * dUdk) + pUagmIqUbgoAB;
303 
304         return new double[] {da, dk, dh, dq, dp, dM};
305     }
306 
307     /** {@inheritDoc} */
308     public double[] getShortPeriodicVariations(final AbsoluteDate date,
309                                                final double[] meanElements)
310         throws OrekitException {
311         // TODO: not implemented yet, Short Periodic Variations are set to null
312         return new double[] {0., 0., 0., 0., 0., 0.};
313     }
314 
315     /** {@inheritDoc} */
316     public EventDetector[] getEventsDetectors() {
317         return null;
318     }
319 
320     /** Get the resonant tesseral terms in the central body spherical harmonic field.
321      */
322     private void getResonantTerms() {
323 
324         // Compute natural resonant terms
325         final double tolerance = 1. / FastMath.max(MIN_PERIOD_IN_SAT_REV,
326                                                    MIN_PERIOD_IN_SECONDS / orbitPeriod);
327 
328         // Search the resonant orders in the tesseral harmonic field
329         final int maxOrder = provider.getMaxOrder();
330         for (int m = 1; m <= maxOrder; m++) {
331             final double resonance = ratio * m;
332             final int j = (int) FastMath.round(resonance);
333             if (j > 0 && FastMath.abs(resonance - j) <= tolerance) {
334                 // Store each resonant index and order
335                 resOrders.add(m);
336             }
337         }
338     }
339 
340     /** Computes the potential U derivatives.
341      *  <p>The following elements are computed from expression 3.3 - (4).
342      *  <pre>
343      *  dU / da
344      *  dU / dh
345      *  dU / dk
346      *  dU / d&lambda;
347      *  dU / d&alpha;
348      *  dU / d&beta;
349      *  dU / d&gamma;
350      *  </pre>
351      *  </p>
352      *
353      *  @param date current date
354      *  @return potential derivatives
355      *  @throws OrekitException if an error occurs
356      */
357     private double[] computeUDerivatives(final AbsoluteDate date) throws OrekitException {
358 
359         // Potential derivatives
360         double dUda  = 0.;
361         double dUdh  = 0.;
362         double dUdk  = 0.;
363         double dUdl  = 0.;
364         double dUdAl = 0.;
365         double dUdBe = 0.;
366         double dUdGa = 0.;
367 
368         // Compute only if there is at least one resonant tesseral
369         if (!resOrders.isEmpty()) {
370             // Gmsj and Hmsj polynomials
371             final GHmsjPolynomials ghMSJ = new GHmsjPolynomials(k, h, alpha, beta, I);
372 
373             // GAMMAmns function
374             final GammaMnsFunction gammaMNS = new GammaMnsFunction(fact, gamma, I);
375 
376             // Hansen coefficients
377             final HansenTesseral hansen = new HansenTesseral(ecc, maxHansen);
378 
379             // R / a up to power degree
380             final double[] roaPow = new double[maxDegree + 1];
381             roaPow[0] = 1.;
382             for (int i = 1; i <= maxDegree; i++) {
383                 roaPow[i] = roa * roaPow[i - 1];
384             }
385 
386             // SUM over resonant terms {j,m}
387             for (int m : resOrders) {
388 
389                 // Resonant index for the current resonant order
390                 final int j = FastMath.max(1, (int) FastMath.round(ratio * m));
391 
392                 // Phase angle
393                 final double jlMmt  = j * lm - m * theta;
394                 final double sinPhi = FastMath.sin(jlMmt);
395                 final double cosPhi = FastMath.cos(jlMmt);
396 
397                 // Potential derivatives components for a given resonant pair {j,m}
398                 double dUdaCos  = 0.;
399                 double dUdaSin  = 0.;
400                 double dUdhCos  = 0.;
401                 double dUdhSin  = 0.;
402                 double dUdkCos  = 0.;
403                 double dUdkSin  = 0.;
404                 double dUdlCos  = 0.;
405                 double dUdlSin  = 0.;
406                 double dUdAlCos = 0.;
407                 double dUdAlSin = 0.;
408                 double dUdBeCos = 0.;
409                 double dUdBeSin = 0.;
410                 double dUdGaCos = 0.;
411                 double dUdGaSin = 0.;
412 
413                 // s-SUM from -sMin to sMax
414                 final int sMin = FastMath.min(maxEccPow - j, maxDegree);
415                 final int sMax = FastMath.min(maxEccPow + j, maxDegree);
416                 for (int s = 0; s <= sMax; s++) {
417 
418                     // n-SUM for s positive
419                     final double[][] nSumSpos = computeNSum(date, j, m, s,
420                                                             roaPow, ghMSJ, gammaMNS, hansen);
421                     dUdaCos  += nSumSpos[0][0];
422                     dUdaSin  += nSumSpos[0][1];
423                     dUdhCos  += nSumSpos[1][0];
424                     dUdhSin  += nSumSpos[1][1];
425                     dUdkCos  += nSumSpos[2][0];
426                     dUdkSin  += nSumSpos[2][1];
427                     dUdlCos  += nSumSpos[3][0];
428                     dUdlSin  += nSumSpos[3][1];
429                     dUdAlCos += nSumSpos[4][0];
430                     dUdAlSin += nSumSpos[4][1];
431                     dUdBeCos += nSumSpos[5][0];
432                     dUdBeSin += nSumSpos[5][1];
433                     dUdGaCos += nSumSpos[6][0];
434                     dUdGaSin += nSumSpos[6][1];
435 
436                     // n-SUM for s negative
437                     if (s > 0 && s <= sMin) {
438                         final double[][] nSumSneg = computeNSum(date, j, m, -s,
439                                                                 roaPow, ghMSJ, gammaMNS, hansen);
440                         dUdaCos  += nSumSneg[0][0];
441                         dUdaSin  += nSumSneg[0][1];
442                         dUdhCos  += nSumSneg[1][0];
443                         dUdhSin  += nSumSneg[1][1];
444                         dUdkCos  += nSumSneg[2][0];
445                         dUdkSin  += nSumSneg[2][1];
446                         dUdlCos  += nSumSneg[3][0];
447                         dUdlSin  += nSumSneg[3][1];
448                         dUdAlCos += nSumSneg[4][0];
449                         dUdAlSin += nSumSneg[4][1];
450                         dUdBeCos += nSumSneg[5][0];
451                         dUdBeSin += nSumSneg[5][1];
452                         dUdGaCos += nSumSneg[6][0];
453                         dUdGaSin += nSumSneg[6][1];
454                     }
455                 }
456 
457                 // Assembly of potential derivatives componants
458                 dUda  += cosPhi * dUdaCos  + sinPhi * dUdaSin;
459                 dUdh  += cosPhi * dUdhCos  + sinPhi * dUdhSin;
460                 dUdk  += cosPhi * dUdkCos  + sinPhi * dUdkSin;
461                 dUdl  += cosPhi * dUdlCos  + sinPhi * dUdlSin;
462                 dUdAl += cosPhi * dUdAlCos + sinPhi * dUdAlSin;
463                 dUdBe += cosPhi * dUdBeCos + sinPhi * dUdBeSin;
464                 dUdGa += cosPhi * dUdGaCos + sinPhi * dUdGaSin;
465             }
466 
467             dUda  *= -moa / a;
468             dUdh  *=  moa;
469             dUdk  *=  moa;
470             dUdl  *=  moa;
471             dUdAl *=  moa;
472             dUdBe *=  moa;
473             dUdGa *=  moa;
474         }
475 
476         return new double[] {dUda, dUdh, dUdk, dUdl, dUdAl, dUdBe, dUdGa};
477     }
478 
479     /** Compute the n-SUM for potential derivatives components.
480      *  @param date current date
481      *  @param j resonant index <i>j</i>
482      *  @param m resonant order <i>m</i>
483      *  @param s d'Alembert characteristic <i>s</i>
484      *  @param roaPow powers of R/a up to degree <i>n</i>
485      *  @param ghMSJ G<sup>j</sup><sub>m,s</sub> and H<sup>j</sup><sub>m,s</sub> polynomials
486      *  @param gammaMNS &Gamma;<sup>m</sup><sub>n,s</sub>(&gamma;) function
487      *  @param hansen Hansen coefficients
488      *  @return Components of U<sub>n</sub> derivatives for fixed j, m, s
489      * @throws OrekitException if some error occurred
490      */
491     private double[][] computeNSum(final AbsoluteDate date,
492                                              final int j, final int m, final int s,
493                                              final double[] roaPow,
494                                              final GHmsjPolynomials ghMSJ,
495                                              final GammaMnsFunction gammaMNS,
496                                              final HansenTesseral hansen) throws OrekitException {
497 
498         //spherical harmonics
499         final UnnormalizedSphericalHarmonics harmonics = provider.onDate(date);
500 
501         // Potential derivatives components
502         double dUdaCos  = 0.;
503         double dUdaSin  = 0.;
504         double dUdhCos  = 0.;
505         double dUdhSin  = 0.;
506         double dUdkCos  = 0.;
507         double dUdkSin  = 0.;
508         double dUdlCos  = 0.;
509         double dUdlSin  = 0.;
510         double dUdAlCos = 0.;
511         double dUdAlSin = 0.;
512         double dUdBeCos = 0.;
513         double dUdBeSin = 0.;
514         double dUdGaCos = 0.;
515         double dUdGaSin = 0.;
516 
517         // I^m
518         final int Im = I > 0 ? 1 : (m % 2 == 0 ? 1 : -1);
519 
520         // jacobi v, w, indices from 2.7.1-(15)
521         final int v = FastMath.abs(m - s);
522         final int w = FastMath.abs(m + s);
523 
524         // Initialise lower degree nmin = (Max(2, m, |s|)) for summation over n
525         final int nmin = FastMath.max(FastMath.max(2, m), FastMath.abs(s));
526         // n max value for computing Hansen kernel from Newcomb operators
527         final int nmax = nmin + 3;
528 
529         // n-SUM from nmin to N
530         for (int n = nmin; n <= maxDegree; n++) {
531 
532             // Compute Hansen kernel values and derivatives
533             if (n <= nmax) {
534                 // from Newcomb operators
535                 hansen.valueFromNewcomb(j, -n - 1, s);
536                 hansen.derivFromNewcomb(j, -n - 1, s);
537             } else {
538                 // from recurrence relations
539                 hansen.valueFromRecurrence(j, -n - 1, s);
540                 hansen.derivFromRecurrence(j, -n - 1, s);
541             }
542 
543             // If (n - s) is odd, the contribution is null because of Vmns
544             if ((n - s) % 2 == 0) {
545 
546                 // Vmns coefficient
547                 final double fns    = fact[n + FastMath.abs(s)];
548                 final double vMNS   = CoefficientsFactory.getVmns(m, n, s, fns, fact[n - m]);
549 
550                 // Inclination function Gamma and derivative
551                 final double gaMNS  = gammaMNS.getValue(m, n, s);
552                 final double dGaMNS = gammaMNS.getDerivative(m, n, s);
553 
554                 // Hansen kernel value and derivative
555                 final double kJNS   = hansen.getValue(j, -n - 1, s);
556                 final double dkJNS  = hansen.getDeriv(j, -n - 1, s);
557 
558                 // Gjms, Hjms polynomials and derivatives
559                 final double gMSJ   = ghMSJ.getGmsj(m, s, j);
560                 final double hMSJ   = ghMSJ.getHmsj(m, s, j);
561                 final double dGdh   = ghMSJ.getdGmsdh(m, s, j);
562                 final double dGdk   = ghMSJ.getdGmsdk(m, s, j);
563                 final double dGdA   = ghMSJ.getdGmsdAlpha(m, s, j);
564                 final double dGdB   = ghMSJ.getdGmsdBeta(m, s, j);
565                 final double dHdh   = ghMSJ.getdHmsdh(m, s, j);
566                 final double dHdk   = ghMSJ.getdHmsdk(m, s, j);
567                 final double dHdA   = ghMSJ.getdHmsdAlpha(m, s, j);
568                 final double dHdB   = ghMSJ.getdHmsdBeta(m, s, j);
569 
570                 // Jacobi l-index from 2.7.1-(15)
571                 final int l = FastMath.min(n - m, n - FastMath.abs(s));
572                 // Jacobi polynomial and derivative
573                 final DerivativeStructure jacobi =
574                         JacobiPolynomials.getValue(l, v , w, new DerivativeStructure(1, 1, 0, gamma));
575 
576                 // Geopotential coefficients
577                 final double cnm = harmonics.getUnnormalizedCnm(n, m);
578                 final double snm = harmonics.getUnnormalizedSnm(n, m);
579 
580                 // Common factors from expansion of equations 3.3-4
581                 final double cf_0      = roaPow[n] * Im * vMNS;
582                 final double cf_1      = cf_0 * gaMNS * jacobi.getValue();
583                 final double cf_2      = cf_1 * kJNS;
584                 final double gcPhs     = gMSJ * cnm + hMSJ * snm;
585                 final double gsMhc     = gMSJ * snm - hMSJ * cnm;
586                 final double dKgcPhsx2 = 2. * dkJNS * gcPhs;
587                 final double dKgsMhcx2 = 2. * dkJNS * gsMhc;
588                 final double dUdaCoef  = (n + 1) * cf_2;
589                 final double dUdlCoef  = j * cf_2;
590                 final double dUdGaCoef = cf_0 * kJNS * (jacobi.getValue() * dGaMNS + gaMNS * jacobi.getPartialDerivative(1));
591 
592                 // dU / da components
593                 dUdaCos  += dUdaCoef * gcPhs;
594                 dUdaSin  += dUdaCoef * gsMhc;
595 
596                 // dU / dh components
597                 dUdhCos  += cf_1 * (kJNS * (cnm * dGdh + snm * dHdh) + h * dKgcPhsx2);
598                 dUdhSin  += cf_1 * (kJNS * (snm * dGdh - cnm * dHdh) + h * dKgsMhcx2);
599 
600                 // dU / dk components
601                 dUdkCos  += cf_1 * (kJNS * (cnm * dGdk + snm * dHdk) + k * dKgcPhsx2);
602                 dUdkSin  += cf_1 * (kJNS * (snm * dGdk - cnm * dHdk) + k * dKgsMhcx2);
603 
604                 // dU / dLambda components
605                 dUdlCos  +=  dUdlCoef * gsMhc;
606                 dUdlSin  += -dUdlCoef * gcPhs;
607 
608                 // dU / alpha components
609                 dUdAlCos += cf_2 * (dGdA * cnm + dHdA * snm);
610                 dUdAlSin += cf_2 * (dGdA * snm - dHdA * cnm);
611 
612                 // dU / dBeta components
613                 dUdBeCos += cf_2 * (dGdB * cnm + dHdB * snm);
614                 dUdBeSin += cf_2 * (dGdB * snm - dHdB * cnm);
615 
616                 // dU / dGamma components
617                 dUdGaCos += dUdGaCoef * gcPhs;
618                 dUdGaSin += dUdGaCoef * gsMhc;
619             }
620         }
621 
622         return new double[][] {{dUdaCos,  dUdaSin},
623                                {dUdhCos,  dUdhSin},
624                                {dUdkCos,  dUdkSin},
625                                {dUdlCos,  dUdlSin},
626                                {dUdAlCos, dUdAlSin},
627                                {dUdBeCos, dUdBeSin},
628                                {dUdGaCos, dUdGaSin}};
629     }
630 
631     /** Hansen coefficients for tesseral contribution to central body force model.
632      *  <p>
633      *  Hansen coefficients are functions of the eccentricity.
634      *  For a given eccentricity, the computed elements are stored in a map.
635      *  </p>
636      */
637     private static class HansenTesseral {
638 
639         /** Map to store every Hansen kernel value computed. */
640         private TreeMap<MNSKey, Double> values;
641 
642         /** Map to store every Hansen kernel derivative computed. */
643         private TreeMap<MNSKey, Double> derivatives;
644 
645         /** Eccentricity. */
646         private final double e2;
647 
648         /** 1 - e<sup>2</sup>. */
649         private final double ome2;
650 
651         /** &chi; = 1 / sqrt(1- e<sup>2</sup>). */
652         private final double chi;
653 
654         /** &chi;<sup>2</sup> = 1 / (1- e<sup>2</sup>). */
655         private final double chi2;
656 
657         /** d&chi; / de<sup>2</sup> = &chi;<sup>3</sup> / 2. */
658         private final double dchi;
659 
660         /** d&chi;<sup>2</sup> / de<sup>2</sup> = &chi;<sup>4</sup>. */
661         private final double dchi2;
662 
663         /** Max power of e<sup>2</sup> in serie expansion
664          *  using Newcomb operator for Hansen kernel computation.
665          */
666         private final int    maxNewcomb;
667 
668         /** Simple constructor.
669          *  @param ecc eccentricity
670          *  @param maxHansen maximum power of e<sup>2</sup> to use in series expansion for the Hansen coefficient
671          */
672         public HansenTesseral(final double ecc, final int maxHansen) {
673             this.values      = new TreeMap<CoefficientsFactory.MNSKey, Double>();
674             this.derivatives = new TreeMap<CoefficientsFactory.MNSKey, Double>();
675             this.maxNewcomb  = maxHansen;
676             this.e2    = ecc * ecc;
677             this.ome2  = 1. - e2;
678             this.chi   = 1. / FastMath.sqrt(ome2);
679             this.chi2  = chi * chi;
680             this.dchi  = 0.5 * chi * chi2;
681             this.dchi2 = chi2 * chi2;
682         }
683 
684         /** Get the K<sub>j</sub><sup>-n-1,s</sup> value.
685          * @param j j value
686          * @param mnm1 -n-1 value
687          * @param s s value
688          * @return K<sub>j</sub><sup>-n-1,s</sup>
689          */
690         public double getValue(final int j, final int mnm1, final int s) {
691             return values.get(new MNSKey(j, mnm1, s));
692         }
693 
694         /** Get the dK<sub>j</sub><sup>-n-1,s</sup> / de<sup>2</sup> derivative.
695          *  @param j j value
696          *  @param mnm1 -n-1 value
697          *  @param s s value
698          *  @return dK<sub>j</sub><sup>-n-1,s</sup> / de<sup>2</sup>
699          */
700         public double getDeriv(final int j, final int mnm1, final int s) {
701             return derivatives.get(new MNSKey(j, mnm1, s));
702         }
703 
704         /** Compute the K<sub>j</sub><sup>-n-1,s</sup> from equation 2.7.3-(10).<br>
705          *  <p>
706          *  The coefficient value is evaluated from the {@link NewcombOperators} elements.
707          *  </p>
708          *  @param j j value
709          *  @param mnm1 -n-1 value
710          *  @param s s value
711          */
712         public void valueFromNewcomb(final int j, final int mnm1, final int s) {
713             // Initialization
714             final int aHT = FastMath.max(j - s, 0);
715             final int bHT = FastMath.max(s - j, 0);
716             // Expansion until maxNewcomb, the maximum power in e^2 for the Kernel value
717             double sum = NewcombOperators.getValue(maxNewcomb + aHT, maxNewcomb + bHT, mnm1, s);
718             for (int alphaHT = maxNewcomb - 1; alphaHT >= 0; alphaHT--) {
719                 sum *= e2;
720                 sum += NewcombOperators.getValue(alphaHT + aHT, alphaHT + bHT, mnm1, s);
721             }
722             // Kernel value from equation 2.7.3-(10)
723             final double value = FastMath.pow(chi2, -mnm1 - 1) * sum / chi;
724             // Storage of the Kernel value in the map
725             values.put(new MNSKey(j, mnm1, s), value);
726         }
727 
728         /** Compute dK<sub>j</sub><sup>-n-1,s</sup>/de<sup>2</sup> from equation 3.3-(5).
729          *  <p>
730          *  The derivative value is evaluated from the {@link NewcombOperators} elements.
731          *  </p>
732          *  @param j j value
733          *  @param mnm1 -n-1 value
734          *  @param s s value
735          */
736         public void derivFromNewcomb(final int j, final int mnm1, final int s) {
737             // Initialization
738             final int aHT = FastMath.max(j - s, 0);
739             final int bHT = FastMath.max(s - j, 0);
740             // Expansion until maxNewcomb-1, the maximum power in e^2 for the Kernel derivative
741             double sum = maxNewcomb * NewcombOperators.getValue(maxNewcomb + aHT, maxNewcomb + bHT, mnm1, s);
742             for (int alphaHT = maxNewcomb - 1; alphaHT >= 1; alphaHT--) {
743                 sum *= e2;
744                 sum += alphaHT * NewcombOperators.getValue(alphaHT + aHT, alphaHT + bHT, mnm1, s);
745             }
746             // Kernel derivative from equation 3.3-(5)
747             final MNSKey key  = new MNSKey(j, mnm1, s);
748             final double coef = -(mnm1 + 1.5);
749             final double Kjns = values.get(key);
750             final double derivative = coef * chi2 * Kjns + FastMath.pow(chi2, -mnm1 - 1) * sum / chi;
751             // Storage of the Kernel derivative in the map
752             derivatives.put(new MNSKey(j, mnm1, s), derivative);
753         }
754 
755         /** Compute the K<sub>j</sub><sup>-n-1,s</sup> coefficient from equation 2.7.3-(9).
756          *
757          *  @param j j value
758          *  @param mnm1 -n-1 value
759          *  @param s s value
760          */
761         public void valueFromRecurrence(final int j, final int mnm1, final int s) {
762             final int n = -mnm1 - 1;
763             final double kmn    = values.get(new MNSKey(j, -n, s));
764             final double kmnp1  = values.get(new MNSKey(j, -n + 1, s));
765             final double kmnp3  = values.get(new MNSKey(j, -n + 3, s));
766             final double den    = (3 - n) * (1 - n + s) * (1 - n - s);
767             final double ck     = chi2 / den;
768             final double ckmn   = (3 - n) * (1 - n) * (3 - 2 * n);
769             final double ckmnp1 = (2 - n) * ((3 - n) * (1 - n) + (2 * j * s) / chi);
770             final double ckmnp3 = j * j * (1 - n);
771             final double value  = ck * (ckmn * kmn - ckmnp1 * kmnp1 + ckmnp3 * kmnp3);
772             // Store value
773             values.put(new MNSKey(j, mnm1, s), value);
774         }
775 
776         /** Compute dK<sub>j</sub><sup>-n-1,s</sup>/de<sup>2</sup> from derivation of equation 2.7.3-(9).
777          *
778          *  @param j j value
779          *  @param mnm1 -n-1 value
780          *  @param s s value
781          */
782         public void derivFromRecurrence(final int j, final int mnm1, final int s) {
783             final int n = -mnm1 - 1;
784             final MNSKey keymn    = new MNSKey(j, -n, s);
785             final MNSKey keymnp1  = new MNSKey(j, -n + 1, s);
786             final MNSKey keymnp3  = new MNSKey(j, -n + 3, s);
787             final double kmn      = values.get(keymn);
788             final double kmnp1    = values.get(keymnp1);
789             final double kmnp3    = values.get(keymnp3);
790             final double dkmn     = derivatives.get(keymn);
791             final double dkmnp1   = derivatives.get(keymnp1);
792             final double dkmnp3   = derivatives.get(keymnp3);
793             final double den      = (3 - n) * (1 - n + s) * (1 - n - s);
794             final double cdkmn    = (3 - n) * (1 - n) * (3 - 2 * n);
795             final double c0dkmnp1 = (n - 2) * (3 - n) * (1 - n);
796             final double c1dkmnp1 = (n - 2) * (2 * j * s);
797             final double cdkmnp3  = j * j * (1 - n);
798             final double deriv    = chi2  * (cdkmn * dkmn + c0dkmnp1 * dkmnp1 + cdkmnp3 * dkmnp3) +
799                                     chi   * c1dkmnp1 * dkmnp1 +
800                                     dchi2 * (cdkmn * kmn + c0dkmnp1 * kmnp1  + cdkmnp3 * kmnp3)  +
801                                     dchi  * c1dkmnp1 * kmnp1;
802             // Store derivative
803             derivatives.put(new MNSKey(j, mnm1, s), deriv / den);
804         }
805     }
806 }