DSSTTesseral.java

  1. /* Copyright 2002-2018 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. import java.io.NotSerializableException;
  19. import java.io.Serializable;
  20. import java.util.ArrayList;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import java.util.SortedMap;
  27. import java.util.SortedSet;
  28. import java.util.TreeMap;

  29. import org.hipparchus.analysis.differentiation.DSFactory;
  30. import org.hipparchus.analysis.differentiation.DerivativeStructure;
  31. import org.hipparchus.exception.LocalizedCoreFormats;
  32. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  33. import org.hipparchus.util.FastMath;
  34. import org.hipparchus.util.MathUtils;
  35. import org.orekit.attitudes.AttitudeProvider;
  36. import org.orekit.errors.OrekitException;
  37. import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider;
  38. import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider.UnnormalizedSphericalHarmonics;
  39. import org.orekit.frames.Frame;
  40. import org.orekit.frames.Transform;
  41. import org.orekit.orbits.Orbit;
  42. import org.orekit.propagation.SpacecraftState;
  43. import org.orekit.propagation.events.EventDetector;
  44. import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
  45. import org.orekit.propagation.semianalytical.dsst.utilities.CoefficientsFactory;
  46. import org.orekit.propagation.semianalytical.dsst.utilities.GHmsjPolynomials;
  47. import org.orekit.propagation.semianalytical.dsst.utilities.GammaMnsFunction;
  48. import org.orekit.propagation.semianalytical.dsst.utilities.JacobiPolynomials;
  49. import org.orekit.propagation.semianalytical.dsst.utilities.ShortPeriodicsInterpolatedCoefficient;
  50. import org.orekit.propagation.semianalytical.dsst.utilities.hansen.HansenTesseralLinear;
  51. import org.orekit.time.AbsoluteDate;
  52. import org.orekit.utils.TimeSpanMap;

  53. /** Tesseral contribution to the central body gravitational perturbation.
  54.  *  <p>
  55.  *  Only resonant tesserals are considered.
  56.  *  </p>
  57.  *
  58.  *  @author Romain Di Costanzo
  59.  *  @author Pascal Parraud
  60.  */
  61. public class DSSTTesseral implements DSSTForceModel {

  62.     /** Minimum period for analytically averaged high-order resonant
  63.      *  central body spherical harmonics in seconds.
  64.      */
  65.     private static final double MIN_PERIOD_IN_SECONDS = 864000.;

  66.     /** Minimum period for analytically averaged high-order resonant
  67.      *  central body spherical harmonics in satellite revolutions.
  68.      */
  69.     private static final double MIN_PERIOD_IN_SAT_REV = 10.;

  70.     /** Number of points for interpolation. */
  71.     private static final int INTERPOLATION_POINTS = 3;

  72.     /** Retrograde factor I.
  73.      *  <p>
  74.      *  DSST model needs equinoctial orbit as internal representation.
  75.      *  Classical equinoctial elements have discontinuities when inclination
  76.      *  is close to zero. In this representation, I = +1. <br>
  77.      *  To avoid this discontinuity, another representation exists and equinoctial
  78.      *  elements can be expressed in a different way, called "retrograde" orbit.
  79.      *  This implies I = -1. <br>
  80.      *  As Orekit doesn't implement the retrograde orbit, I is always set to +1.
  81.      *  But for the sake of consistency with the theory, the retrograde factor
  82.      *  has been kept in the formulas.
  83.      *  </p>
  84.      */
  85.     private static final int I = 1;

  86.     /** Provider for spherical harmonics. */
  87.     private final UnnormalizedSphericalHarmonicsProvider provider;

  88.     /** Central body rotating frame. */
  89.     private final Frame bodyFrame;

  90.     /** Central body rotation rate (rad/s). */
  91.     private final double centralBodyRotationRate;

  92.     /** Central body rotation period (seconds). */
  93.     private final double bodyPeriod;

  94.     /** Maximal degree to consider for harmonics potential. */
  95.     private final int maxDegree;

  96.     /** Maximal degree to consider for short periodics tesseral harmonics potential (without m-daily). */
  97.     private final int maxDegreeTesseralSP;

  98.     /** Maximal degree to consider for short periodics m-daily tesseral harmonics potential . */
  99.     private final int maxDegreeMdailyTesseralSP;

  100.     /** Maximal order to consider for harmonics potential. */
  101.     private final int maxOrder;

  102.     /** Maximal order to consider for short periodics tesseral harmonics potential (without m-daily). */
  103.     private final int maxOrderTesseralSP;

  104.     /** Maximal order to consider for short periodics m-daily tesseral harmonics potential . */
  105.     private final int maxOrderMdailyTesseralSP;

  106.     /** List of resonant orders. */
  107.     private final List<Integer> resOrders;

  108.     /** Maximum power of the eccentricity to use in summation over s. */
  109.     private int maxEccPow;

  110.     /** Maximum power of the eccentricity to use in summation over s for
  111.      * short periodic tesseral harmonics (without m-daily). */
  112.     private final int maxEccPowTesseralSP;

  113.     /** Maximum power of the eccentricity to use in summation over s for
  114.      * m-daily tesseral harmonics. */
  115.     private final int maxEccPowMdailyTesseralSP;

  116.     /** Maximum value for j. */
  117.     private final int maxFrequencyShortPeriodics;

  118.     /** Maximum power of the eccentricity to use in Hansen coefficient Kernel expansion. */
  119.     private int maxHansen;

  120.     /** Keplerian period. */
  121.     private double orbitPeriod;

  122.     /** Ratio of satellite period to central body rotation period. */
  123.     private double ratio;

  124.     // Equinoctial elements (according to DSST notation)
  125.     /** a. */
  126.     private double a;

  127.     /** ex. */
  128.     private double k;

  129.     /** ey. */
  130.     private double h;

  131.     /** hx. */
  132.     private double q;

  133.     /** hy. */
  134.     private double p;

  135.     /** lm. */
  136.     private double lm;

  137.     /** Eccentricity. */
  138.     private double ecc;

  139.     // Common factors for potential computation
  140.     /** &Chi; = 1 / sqrt(1 - e²) = 1 / B. */
  141.     private double chi;

  142.     /** &Chi;². */
  143.     private double chi2;

  144.     // Equinoctial reference frame vectors (according to DSST notation)
  145.     /** Equinoctial frame f vector. */
  146.     private Vector3D f;

  147.     /** Equinoctial frame g vector. */
  148.     private Vector3D g;

  149.     /** Central body rotation angle θ. */
  150.     private double theta;

  151.     /** Direction cosine α. */
  152.     private double alpha;

  153.     /** Direction cosine β. */
  154.     private double beta;

  155.     /** Direction cosine γ. */
  156.     private double gamma;

  157.     // Common factors from equinoctial coefficients
  158.     /** 2 * a / A .*/
  159.     private double ax2oA;

  160.     /** 1 / (A * B) .*/
  161.     private double ooAB;

  162.     /** B / A .*/
  163.     private double BoA;

  164.     /** B / (A * (1 + B)) .*/
  165.     private double BoABpo;

  166.     /** C / (2 * A * B) .*/
  167.     private double Co2AB;

  168.     /** μ / a .*/
  169.     private double moa;

  170.     /** R / a .*/
  171.     private double roa;

  172.     /** ecc². */
  173.     private double e2;

  174.     /** The satellite mean motion. */
  175.     private double meanMotion;

  176.     /** List of non resonant orders with j != 0. */
  177.     private final SortedMap<Integer, List<Integer> > nonResOrders;

  178.     /** A two dimensional array that contains the objects needed to build the Hansen coefficients. <br/>
  179.      * The indexes are s + maxDegree and j */
  180.     private HansenTesseralLinear[][] hansenObjects;

  181.     /** Fourier coefficients. */
  182.     private FourierCjSjCoefficients cjsjFourier;

  183.     /** Short period terms. */
  184.     private TesseralShortPeriodicCoefficients shortPeriodTerms;

  185.     /** Factory for the DerivativeStructure instances. */
  186.     private final DSFactory factory;

  187.     /** Simple constructor.
  188.      * @param centralBodyFrame rotating body frame
  189.      * @param centralBodyRotationRate central body rotation rate (rad/s)
  190.      * @param provider provider for spherical harmonics
  191.      * @param maxDegreeTesseralSP maximal degree to consider for short periodics tesseral harmonics potential
  192.      *  (must be between 2 and {@code provider.getMaxDegree()})
  193.      * @param maxOrderTesseralSP maximal order to consider for short periodics tesseral harmonics potential
  194.      *  (must be between 0 and {@code provider.getMaxOrder()})
  195.      * @param maxEccPowTesseralSP maximum power of the eccentricity to use in summation over s for
  196.      * short periodic tesseral harmonics (without m-daily), should typically not exceed 4 as higher
  197.      * values will exceed computer capacity
  198.      * @param maxFrequencyShortPeriodics maximum frequency in mean longitude for short periodic computations
  199.      * (typically {@code maxDegreeTesseralSP} + {@code maxEccPowTesseralSP and no more than 12})
  200.      * @param maxDegreeMdailyTesseralSP maximal degree to consider for short periodics m-daily tesseral harmonics potential
  201.      *  (must be between 2 and {@code provider.getMaxDegree()})
  202.      * @param maxOrderMdailyTesseralSP maximal order to consider for short periodics m-daily tesseral harmonics potential
  203.      *  (must be between 0 and {@code provider.getMaxOrder()})
  204.      * @param maxEccPowMdailyTesseralSP maximum power of the eccentricity to use in summation over s for
  205.      * m-daily tesseral harmonics, (must be between 0 and {@code maxDegreeMdailyTesseralSP - 2},
  206.      * but should typically not exceed 4 as higher values will exceed computer capacity)
  207.      * @exception OrekitException if degrees or powers are out of range
  208.      * @since 7.2
  209.      */
  210.     public DSSTTesseral(final Frame centralBodyFrame,
  211.                         final double centralBodyRotationRate,
  212.                         final UnnormalizedSphericalHarmonicsProvider provider,
  213.                         final int maxDegreeTesseralSP, final int maxOrderTesseralSP,
  214.                         final int maxEccPowTesseralSP, final int maxFrequencyShortPeriodics,
  215.                         final int maxDegreeMdailyTesseralSP, final int maxOrderMdailyTesseralSP,
  216.                         final int maxEccPowMdailyTesseralSP)
  217.         throws OrekitException {

  218.         // Central body rotating frame
  219.         this.bodyFrame = centralBodyFrame;

  220.         //Save the rotation rate
  221.         this.centralBodyRotationRate = centralBodyRotationRate;

  222.         // Central body rotation period in seconds
  223.         this.bodyPeriod = MathUtils.TWO_PI / centralBodyRotationRate;

  224.         // Provider for spherical harmonics
  225.         this.provider  = provider;
  226.         this.maxDegree = provider.getMaxDegree();
  227.         this.maxOrder  = provider.getMaxOrder();

  228.         //set the maximum degree order for short periodics
  229.         checkIndexRange(maxDegreeTesseralSP, 2, maxDegree);
  230.         this.maxDegreeTesseralSP       = maxDegreeTesseralSP;

  231.         checkIndexRange(maxDegreeMdailyTesseralSP, 2, maxDegree);
  232.         this.maxDegreeMdailyTesseralSP = maxDegreeMdailyTesseralSP;

  233.         checkIndexRange(maxOrderTesseralSP, 0, maxOrder);
  234.         this.maxOrderTesseralSP        = maxOrderTesseralSP;

  235.         checkIndexRange(maxOrderMdailyTesseralSP, 0, maxOrder);
  236.         this.maxOrderMdailyTesseralSP  = maxOrderMdailyTesseralSP;

  237.         // set the maximum value for eccentricity power
  238.         this.maxEccPowTesseralSP       = maxEccPowTesseralSP;

  239.         checkIndexRange(maxEccPowMdailyTesseralSP, 0, maxDegreeMdailyTesseralSP - 2);
  240.         this.maxEccPowMdailyTesseralSP = maxEccPowMdailyTesseralSP;

  241.         // set the maximum value for frequency
  242.         this.maxFrequencyShortPeriodics = maxFrequencyShortPeriodics;

  243.         // Initialize default values
  244.         this.resOrders = new ArrayList<Integer>();
  245.         this.nonResOrders = new TreeMap<Integer, List <Integer> >();
  246.         this.maxEccPow = 0;
  247.         this.maxHansen = 0;

  248.         this.factory = new DSFactory(1, 1);

  249.     }

  250.     /** Check an index range.
  251.      * @param index index value
  252.      * @param min minimum value for index
  253.      * @param max maximum value for index
  254.      * @exception OrekitException if index is out of range
  255.      */
  256.     private void checkIndexRange(final int index, final int min, final int max)
  257.         throws OrekitException {
  258.         if (index < min || index > max) {
  259.             throw new OrekitException(LocalizedCoreFormats.OUT_OF_RANGE_SIMPLE, index, min, max);
  260.         }
  261.     }

  262.     /** {@inheritDoc} */
  263.     @Override
  264.     public List<ShortPeriodTerms> initialize(final AuxiliaryElements aux, final boolean meanOnly)
  265.         throws OrekitException {

  266.         // Keplerian period
  267.         orbitPeriod = aux.getKeplerianPeriod();

  268.         // Set the highest power of the eccentricity in the analytical power
  269.         // series expansion for the averaged high order resonant central body
  270.         // spherical harmonic perturbation
  271.         final double e = aux.getEcc();
  272.         if (e <= 0.005) {
  273.             maxEccPow = 3;
  274.         } else if (e <= 0.02) {
  275.             maxEccPow = 4;
  276.         } else if (e <= 0.1) {
  277.             maxEccPow = 7;
  278.         } else if (e <= 0.2) {
  279.             maxEccPow = 10;
  280.         } else if (e <= 0.3) {
  281.             maxEccPow = 12;
  282.         } else if (e <= 0.4) {
  283.             maxEccPow = 15;
  284.         } else {
  285.             maxEccPow = 20;
  286.         }

  287.         // Set the maximum power of the eccentricity to use in Hansen coefficient Kernel expansion.
  288.         maxHansen = maxEccPow / 2;

  289.         // Ratio of satellite to central body periods to define resonant terms
  290.         ratio = orbitPeriod / bodyPeriod;

  291.         // Compute the resonant tesseral harmonic terms if not set by the user
  292.         getResonantAndNonResonantTerms(meanOnly);

  293.         //initialize the HansenTesseralLinear objects needed
  294.         createHansenObjects(meanOnly);

  295.         final int mMax = FastMath.max(maxOrderTesseralSP, maxOrderMdailyTesseralSP);
  296.         cjsjFourier = new FourierCjSjCoefficients(maxFrequencyShortPeriodics, mMax);

  297.         shortPeriodTerms = new TesseralShortPeriodicCoefficients(bodyFrame, maxOrderMdailyTesseralSP,
  298.                                                                  maxDegreeTesseralSP < 0, nonResOrders,
  299.                                                                  mMax, maxFrequencyShortPeriodics, INTERPOLATION_POINTS,
  300.                                                                  new TimeSpanMap<Slot>(new Slot(mMax, maxFrequencyShortPeriodics,
  301.                                                                                                 INTERPOLATION_POINTS)));

  302.         final List<ShortPeriodTerms> list = new ArrayList<ShortPeriodTerms>();
  303.         list.add(shortPeriodTerms);
  304.         return list;

  305.     }

  306.     /** Create the objects needed for linear transformation.
  307.      *
  308.      * <p>
  309.      * Each {@link org.orekit.propagation.semianalytical.dsst.utilities.hansenHansenTesseralLinear HansenTesseralLinear} uses
  310.      * a fixed value for s and j. Since j varies from -maxJ to +maxJ and s varies from -maxDegree to +maxDegree,
  311.      * a 2 * maxDegree + 1 x 2 * maxJ + 1 matrix of objects should be created. The size of this matrix can be reduced
  312.      * by taking into account the expression (2.7.3-2). This means that is enough to create the objects for  positive
  313.      * values of j and all values of s.
  314.      * </p>
  315.      *
  316.      * @param meanOnly create only the objects required for the mean contribution
  317.      */
  318.     private void createHansenObjects(final boolean meanOnly) {
  319.         //Allocate the two dimensional array
  320.         final int rows     = 2 * maxDegree + 1;
  321.         final int columns  = maxFrequencyShortPeriodics + 1;
  322.         this.hansenObjects = new HansenTesseralLinear[rows][columns];

  323.         if (meanOnly) {
  324.             // loop through the resonant orders
  325.             for (int m : resOrders) {
  326.                 //Compute the corresponding j term
  327.                 final int j = FastMath.max(1, (int) FastMath.round(ratio * m));

  328.                 //Compute the sMin and sMax values
  329.                 final int sMin = FastMath.min(maxEccPow - j, maxDegree);
  330.                 final int sMax = FastMath.min(maxEccPow + j, maxDegree);

  331.                 //loop through the s values
  332.                 for (int s = 0; s <= sMax; s++) {
  333.                     //Compute the n0 value
  334.                     final int n0 = FastMath.max(FastMath.max(2, m), s);

  335.                     //Create the object for the pair j, s
  336.                     this.hansenObjects[s + maxDegree][j] = new HansenTesseralLinear(maxDegree, s, j, n0, maxHansen);

  337.                     if (s > 0 && s <= sMin) {
  338.                         //Also create the object for the pair j, -s
  339.                         this.hansenObjects[maxDegree - s][j] =  new HansenTesseralLinear(maxDegree, -s, j, n0, maxHansen);
  340.                     }
  341.                 }
  342.             }
  343.         } else {
  344.             // create all objects
  345.             for (int j = 0; j <= maxFrequencyShortPeriodics; j++) {
  346.                 for (int s = -maxDegree; s <= maxDegree; s++) {
  347.                     //Compute the n0 value
  348.                     final int n0 = FastMath.max(2, FastMath.abs(s));

  349.                     this.hansenObjects[s + maxDegree][j] = new HansenTesseralLinear(maxDegree, s, j, n0, maxHansen);
  350.                 }
  351.             }
  352.         }
  353.     }

  354.     /** {@inheritDoc} */
  355.     @Override
  356.     public void initializeStep(final AuxiliaryElements aux) throws OrekitException {

  357.         // Equinoctial elements
  358.         a  = aux.getSma();
  359.         k  = aux.getK();
  360.         h  = aux.getH();
  361.         q  = aux.getQ();
  362.         p  = aux.getP();
  363.         lm = aux.getLM();

  364.         // Eccentricity
  365.         ecc = aux.getEcc();
  366.         e2 = ecc * ecc;

  367.         // Equinoctial frame vectors
  368.         f = aux.getVectorF();
  369.         g = aux.getVectorG();

  370.         // Central body rotation angle from equation 2.7.1-(3)(4).
  371.         final Transform t = bodyFrame.getTransformTo(aux.getFrame(), aux.getDate());
  372.         final Vector3D xB = t.transformVector(Vector3D.PLUS_I);
  373.         final Vector3D yB = t.transformVector(Vector3D.PLUS_J);
  374.         theta = FastMath.atan2(-f.dotProduct(yB) + I * g.dotProduct(xB),
  375.                                 f.dotProduct(xB) + I * g.dotProduct(yB));

  376.         // Direction cosines
  377.         alpha = aux.getAlpha();
  378.         beta  = aux.getBeta();
  379.         gamma = aux.getGamma();

  380.         // Equinoctial coefficients
  381.         // A = sqrt(μ * a)
  382.         final double A = aux.getA();
  383.         // B = sqrt(1 - h² - k²)
  384.         final double B = aux.getB();
  385.         // C = 1 + p² + q²
  386.         final double C = aux.getC();
  387.         // Common factors from equinoctial coefficients
  388.         // 2 * a / A
  389.         ax2oA  = 2. * a / A;
  390.         // B / A
  391.         BoA  = B / A;
  392.         // 1 / AB
  393.         ooAB = 1. / (A * B);
  394.         // C / 2AB
  395.         Co2AB = C * ooAB / 2.;
  396.         // B / (A * (1 + B))
  397.         BoABpo = BoA / (1. + B);
  398.         // &mu / a
  399.         moa = provider.getMu() / a;
  400.         // R / a
  401.         roa = provider.getAe() / a;

  402.         // &Chi; = 1 / B
  403.         chi = 1. / B;
  404.         chi2 = chi * chi;

  405.         //mean motion n
  406.         meanMotion = aux.getMeanMotion();
  407.     }

  408.     /** {@inheritDoc} */
  409.     @Override
  410.     public double[] getMeanElementRate(final SpacecraftState spacecraftState) throws OrekitException {

  411.         // Compute potential derivatives
  412.         final double[] dU  = computeUDerivatives(spacecraftState.getDate());
  413.         final double dUda  = dU[0];
  414.         final double dUdh  = dU[1];
  415.         final double dUdk  = dU[2];
  416.         final double dUdl  = dU[3];
  417.         final double dUdAl = dU[4];
  418.         final double dUdBe = dU[5];
  419.         final double dUdGa = dU[6];

  420.         // Compute the cross derivative operator :
  421.         final double UAlphaGamma   = alpha * dUdGa - gamma * dUdAl;
  422.         final double UAlphaBeta    = alpha * dUdBe - beta  * dUdAl;
  423.         final double UBetaGamma    =  beta * dUdGa - gamma * dUdBe;
  424.         final double Uhk           =     h * dUdk  -     k * dUdh;
  425.         final double pUagmIqUbgoAB = (p * UAlphaGamma - I * q * UBetaGamma) * ooAB;
  426.         final double UhkmUabmdUdl  =  Uhk - UAlphaBeta - dUdl;

  427.         final double da =  ax2oA * dUdl;
  428.         final double dh =    BoA * dUdk + k * pUagmIqUbgoAB - h * BoABpo * dUdl;
  429.         final double dk =  -(BoA * dUdh + h * pUagmIqUbgoAB + k * BoABpo * dUdl);
  430.         final double dp =  Co2AB * (p * UhkmUabmdUdl - UBetaGamma);
  431.         final double dq =  Co2AB * (q * UhkmUabmdUdl - I * UAlphaGamma);
  432.         final double dM = -ax2oA * dUda + BoABpo * (h * dUdh + k * dUdk) + pUagmIqUbgoAB;

  433.         return new double[] {da, dk, dh, dq, dp, dM};
  434.     }

  435.     /** {@inheritDoc} */
  436.     @Override
  437.     public void updateShortPeriodTerms(final SpacecraftState... meanStates)
  438.         throws OrekitException {

  439.         final Slot slot = shortPeriodTerms.createSlot(meanStates);

  440.         for (final SpacecraftState meanState : meanStates) {

  441.             initializeStep(new AuxiliaryElements(meanState.getOrbit(), I));

  442.             // Initialise the Hansen coefficients
  443.             for (int s = -maxDegree; s <= maxDegree; s++) {
  444.                 // coefficients with j == 0 are always needed
  445.                 this.hansenObjects[s + maxDegree][0].computeInitValues(e2, chi, chi2);
  446.                 if (maxDegreeTesseralSP >= 0) {
  447.                     // initialize other objects only if required
  448.                     for (int j = 1; j <= maxFrequencyShortPeriodics; j++) {
  449.                         this.hansenObjects[s + maxDegree][j].computeInitValues(e2, chi, chi2);
  450.                     }
  451.                 }
  452.             }

  453.             // Compute coefficients
  454.             // Compute only if there is at least one non-resonant tesseral
  455.             if (!nonResOrders.isEmpty() || maxDegreeTesseralSP < 0) {
  456.                 // Generate the fourrier coefficients
  457.                 cjsjFourier.generateCoefficients(meanState.getDate());

  458.                 // the coefficient 3n / 2a
  459.                 final double tnota = 1.5 * meanMotion / a;

  460.                 // build the mDaily coefficients
  461.                 for (int m = 1; m <= maxOrderMdailyTesseralSP; m++) {
  462.                     // build the coefficients
  463.                     buildCoefficients(meanState.getDate(), slot, m, 0, tnota);
  464.                 }

  465.                 if (maxDegreeTesseralSP >= 0) {
  466.                     // generate the other coefficients, if required
  467.                     for (final Map.Entry<Integer, List<Integer>> entry : nonResOrders.entrySet()) {

  468.                         for (int j : entry.getValue()) {
  469.                             // build the coefficients
  470.                             buildCoefficients(meanState.getDate(), slot, entry.getKey(), j, tnota);
  471.                         }
  472.                     }
  473.                 }
  474.             }

  475.         }

  476.     }

  477.     /** Build a set of coefficients.
  478.      *
  479.      * @param date the current date
  480.      * @param slot slot to which the coefficients belong
  481.      * @param m m index
  482.      * @param j j index
  483.      * @param tnota 3n/2a
  484.      */
  485.     private void buildCoefficients(final AbsoluteDate date, final Slot slot,
  486.                                    final int m, final int j, final double tnota) {
  487.         // Create local arrays
  488.         final double[] currentCijm = new double[] {0., 0., 0., 0., 0., 0.};
  489.         final double[] currentSijm = new double[] {0., 0., 0., 0., 0., 0.};

  490.         // compute the term 1 / (jn - mθ<sup>.</sup>)
  491.         final double oojnmt = 1. / (j * meanMotion - m * centralBodyRotationRate);

  492.         // initialise the coeficients
  493.         for (int i = 0; i < 6; i++) {
  494.             currentCijm[i] = -cjsjFourier.getSijm(i, j, m);
  495.             currentSijm[i] = cjsjFourier.getCijm(i, j, m);
  496.         }
  497.         // Add the separate part for δ<sub>6i</sub>
  498.         currentCijm[5] += tnota * oojnmt * cjsjFourier.getCijm(0, j, m);
  499.         currentSijm[5] += tnota * oojnmt * cjsjFourier.getSijm(0, j, m);

  500.         //Multiply by 1 / (jn - mθ<sup>.</sup>)
  501.         for (int i = 0; i < 6; i++) {
  502.             currentCijm[i] *= oojnmt;
  503.             currentSijm[i] *= oojnmt;
  504.         }

  505.         // Add the coefficients to the interpolation grid
  506.         slot.cijm[m][j + maxFrequencyShortPeriodics].addGridPoint(date, currentCijm);
  507.         slot.sijm[m][j + maxFrequencyShortPeriodics].addGridPoint(date, currentSijm);

  508.     }

  509.     /** {@inheritDoc} */
  510.     @Override
  511.     public EventDetector[] getEventsDetectors() {
  512.         return null;
  513.     }

  514.      /**
  515.       * Get the resonant and non-resonant tesseral terms in the central body spherical harmonic field.
  516.       *
  517.       * @param resonantOnly extract only resonant terms
  518.       */
  519.     private void getResonantAndNonResonantTerms(final boolean resonantOnly) {

  520.         // Compute natural resonant terms
  521.         final double tolerance = 1. / FastMath.max(MIN_PERIOD_IN_SAT_REV,
  522.                                                    MIN_PERIOD_IN_SECONDS / orbitPeriod);

  523.         // Search the resonant orders in the tesseral harmonic field
  524.         resOrders.clear();
  525.         nonResOrders.clear();
  526.         for (int m = 1; m <= maxOrder; m++) {
  527.             final double resonance = ratio * m;
  528.             int jRes = 0;
  529.             final int jComputedRes = (int) FastMath.round(resonance);
  530.             if (jComputedRes > 0 && jComputedRes <= maxFrequencyShortPeriodics && FastMath.abs(resonance - jComputedRes) <= tolerance) {
  531.                 // Store each resonant index and order
  532.                 resOrders.add(m);
  533.                 jRes = jComputedRes;
  534.             }

  535.             if (!resonantOnly && maxDegreeTesseralSP >= 0 && m <= maxOrderTesseralSP) {
  536.                 //compute non resonant orders in the tesseral harmonic field
  537.                 final List<Integer> listJofM = new ArrayList<Integer>();
  538.                 //for the moment we take only the pairs (j,m) with |j| <= maxDegree + maxEccPow (from |s-j| <= maxEccPow and |s| <= maxDegree)
  539.                 for (int j = -maxFrequencyShortPeriodics; j <= maxFrequencyShortPeriodics; j++) {
  540.                     if (j != 0 && j != jRes) {
  541.                         listJofM.add(j);
  542.                     }
  543.                 }

  544.                 nonResOrders.put(m, listJofM);
  545.             }
  546.         }
  547.     }

  548.     /** Computes the potential U derivatives.
  549.      *  <p>The following elements are computed from expression 3.3 - (4).
  550.      *  <pre>
  551.      *  dU / da
  552.      *  dU / dh
  553.      *  dU / dk
  554.      *  dU / dλ
  555.      *  dU / dα
  556.      *  dU / dβ
  557.      *  dU / dγ
  558.      *  </pre>
  559.      *  </p>
  560.      *
  561.      *  @param date current date
  562.      *  @return potential derivatives
  563.      *  @throws OrekitException if an error occurs
  564.      */
  565.     private double[] computeUDerivatives(final AbsoluteDate date) throws OrekitException {

  566.         // Potential derivatives
  567.         double dUda  = 0.;
  568.         double dUdh  = 0.;
  569.         double dUdk  = 0.;
  570.         double dUdl  = 0.;
  571.         double dUdAl = 0.;
  572.         double dUdBe = 0.;
  573.         double dUdGa = 0.;

  574.         // Compute only if there is at least one resonant tesseral
  575.         if (!resOrders.isEmpty()) {
  576.             // Gmsj and Hmsj polynomials
  577.             final GHmsjPolynomials ghMSJ = new GHmsjPolynomials(k, h, alpha, beta, I);

  578.             // GAMMAmns function
  579.             final GammaMnsFunction gammaMNS = new GammaMnsFunction(maxDegree, gamma, I);

  580.             // R / a up to power degree
  581.             final double[] roaPow = new double[maxDegree + 1];
  582.             roaPow[0] = 1.;
  583.             for (int i = 1; i <= maxDegree; i++) {
  584.                 roaPow[i] = roa * roaPow[i - 1];
  585.             }

  586.             // SUM over resonant terms {j,m}
  587.             for (int m : resOrders) {

  588.                 // Resonant index for the current resonant order
  589.                 final int j = FastMath.max(1, (int) FastMath.round(ratio * m));

  590.                 // Phase angle
  591.                 final double jlMmt  = j * lm - m * theta;
  592.                 final double sinPhi = FastMath.sin(jlMmt);
  593.                 final double cosPhi = FastMath.cos(jlMmt);

  594.                 // Potential derivatives components for a given resonant pair {j,m}
  595.                 double dUdaCos  = 0.;
  596.                 double dUdaSin  = 0.;
  597.                 double dUdhCos  = 0.;
  598.                 double dUdhSin  = 0.;
  599.                 double dUdkCos  = 0.;
  600.                 double dUdkSin  = 0.;
  601.                 double dUdlCos  = 0.;
  602.                 double dUdlSin  = 0.;
  603.                 double dUdAlCos = 0.;
  604.                 double dUdAlSin = 0.;
  605.                 double dUdBeCos = 0.;
  606.                 double dUdBeSin = 0.;
  607.                 double dUdGaCos = 0.;
  608.                 double dUdGaSin = 0.;

  609.                 // s-SUM from -sMin to sMax
  610.                 final int sMin = FastMath.min(maxEccPow - j, maxDegree);
  611.                 final int sMax = FastMath.min(maxEccPow + j, maxDegree);
  612.                 for (int s = 0; s <= sMax; s++) {

  613.                     //Compute the initial values for Hansen coefficients using newComb operators
  614.                     this.hansenObjects[s + maxDegree][j].computeInitValues(e2, chi, chi2);

  615.                     // n-SUM for s positive
  616.                     final double[][] nSumSpos = computeNSum(date, j, m, s, maxDegree,
  617.                                                             roaPow, ghMSJ, gammaMNS);
  618.                     dUdaCos  += nSumSpos[0][0];
  619.                     dUdaSin  += nSumSpos[0][1];
  620.                     dUdhCos  += nSumSpos[1][0];
  621.                     dUdhSin  += nSumSpos[1][1];
  622.                     dUdkCos  += nSumSpos[2][0];
  623.                     dUdkSin  += nSumSpos[2][1];
  624.                     dUdlCos  += nSumSpos[3][0];
  625.                     dUdlSin  += nSumSpos[3][1];
  626.                     dUdAlCos += nSumSpos[4][0];
  627.                     dUdAlSin += nSumSpos[4][1];
  628.                     dUdBeCos += nSumSpos[5][0];
  629.                     dUdBeSin += nSumSpos[5][1];
  630.                     dUdGaCos += nSumSpos[6][0];
  631.                     dUdGaSin += nSumSpos[6][1];

  632.                     // n-SUM for s negative
  633.                     if (s > 0 && s <= sMin) {
  634.                         //Compute the initial values for Hansen coefficients using newComb operators
  635.                         this.hansenObjects[maxDegree - s][j].computeInitValues(e2, chi, chi2);

  636.                         final double[][] nSumSneg = computeNSum(date, j, m, -s, maxDegree,
  637.                                                                 roaPow, ghMSJ, gammaMNS);
  638.                         dUdaCos  += nSumSneg[0][0];
  639.                         dUdaSin  += nSumSneg[0][1];
  640.                         dUdhCos  += nSumSneg[1][0];
  641.                         dUdhSin  += nSumSneg[1][1];
  642.                         dUdkCos  += nSumSneg[2][0];
  643.                         dUdkSin  += nSumSneg[2][1];
  644.                         dUdlCos  += nSumSneg[3][0];
  645.                         dUdlSin  += nSumSneg[3][1];
  646.                         dUdAlCos += nSumSneg[4][0];
  647.                         dUdAlSin += nSumSneg[4][1];
  648.                         dUdBeCos += nSumSneg[5][0];
  649.                         dUdBeSin += nSumSneg[5][1];
  650.                         dUdGaCos += nSumSneg[6][0];
  651.                         dUdGaSin += nSumSneg[6][1];
  652.                     }
  653.                 }

  654.                 // Assembly of potential derivatives componants
  655.                 dUda  += cosPhi * dUdaCos  + sinPhi * dUdaSin;
  656.                 dUdh  += cosPhi * dUdhCos  + sinPhi * dUdhSin;
  657.                 dUdk  += cosPhi * dUdkCos  + sinPhi * dUdkSin;
  658.                 dUdl  += cosPhi * dUdlCos  + sinPhi * dUdlSin;
  659.                 dUdAl += cosPhi * dUdAlCos + sinPhi * dUdAlSin;
  660.                 dUdBe += cosPhi * dUdBeCos + sinPhi * dUdBeSin;
  661.                 dUdGa += cosPhi * dUdGaCos + sinPhi * dUdGaSin;
  662.             }

  663.             dUda  *= -moa / a;
  664.             dUdh  *=  moa;
  665.             dUdk  *=  moa;
  666.             dUdl  *=  moa;
  667.             dUdAl *=  moa;
  668.             dUdBe *=  moa;
  669.             dUdGa *=  moa;
  670.         }

  671.         return new double[] {dUda, dUdh, dUdk, dUdl, dUdAl, dUdBe, dUdGa};
  672.     }

  673.     /** Compute the n-SUM for potential derivatives components.
  674.      *  @param date current date
  675.      *  @param j resonant index <i>j</i>
  676.      *  @param m resonant order <i>m</i>
  677.      *  @param s d'Alembert characteristic <i>s</i>
  678.      *  @param maxN maximum possible value for <i>n</i> index
  679.      *  @param roaPow powers of R/a up to degree <i>n</i>
  680.      *  @param ghMSJ G<sup>j</sup><sub>m,s</sub> and H<sup>j</sup><sub>m,s</sub> polynomials
  681.      *  @param gammaMNS &Gamma;<sup>m</sup><sub>n,s</sub>(γ) function
  682.      *  @return Components of U<sub>n</sub> derivatives for fixed j, m, s
  683.      * @throws OrekitException if some error occurred
  684.      */
  685.     private double[][] computeNSum(final AbsoluteDate date,
  686.                                    final int j, final int m, final int s, final int maxN, final double[] roaPow,
  687.                                    final GHmsjPolynomials ghMSJ, final GammaMnsFunction gammaMNS)
  688.         throws OrekitException {

  689.         //spherical harmonics
  690.         final UnnormalizedSphericalHarmonics harmonics = provider.onDate(date);

  691.         // Potential derivatives components
  692.         double dUdaCos  = 0.;
  693.         double dUdaSin  = 0.;
  694.         double dUdhCos  = 0.;
  695.         double dUdhSin  = 0.;
  696.         double dUdkCos  = 0.;
  697.         double dUdkSin  = 0.;
  698.         double dUdlCos  = 0.;
  699.         double dUdlSin  = 0.;
  700.         double dUdAlCos = 0.;
  701.         double dUdAlSin = 0.;
  702.         double dUdBeCos = 0.;
  703.         double dUdBeSin = 0.;
  704.         double dUdGaCos = 0.;
  705.         double dUdGaSin = 0.;

  706.         // I^m
  707.         @SuppressWarnings("unused")
  708.         final int Im = I > 0 ? 1 : (m % 2 == 0 ? 1 : -1);

  709.         // jacobi v, w, indices from 2.7.1-(15)
  710.         final int v = FastMath.abs(m - s);
  711.         final int w = FastMath.abs(m + s);

  712.         // Initialise lower degree nmin = (Max(2, m, |s|)) for summation over n
  713.         final int nmin = FastMath.max(FastMath.max(2, m), FastMath.abs(s));

  714.         //Get the corresponding Hansen object
  715.         final int sIndex = maxDegree + (j < 0 ? -s : s);
  716.         final int jIndex = FastMath.abs(j);
  717.         final HansenTesseralLinear hans = this.hansenObjects[sIndex][jIndex];

  718.         // n-SUM from nmin to N
  719.         for (int n = nmin; n <= maxN; n++) {
  720.             // If (n - s) is odd, the contribution is null because of Vmns
  721.             if ((n - s) % 2 == 0) {

  722.                 // Vmns coefficient
  723.                 final double vMNS   = CoefficientsFactory.getVmns(m, n, s);

  724.                 // Inclination function Gamma and derivative
  725.                 final double gaMNS  = gammaMNS.getValue(m, n, s);
  726.                 final double dGaMNS = gammaMNS.getDerivative(m, n, s);

  727.                 // Hansen kernel value and derivative
  728.                 final double kJNS   = hans.getValue(-n - 1, chi);
  729.                 final double dkJNS  = hans.getDerivative(-n - 1, chi);

  730.                 // Gjms, Hjms polynomials and derivatives
  731.                 final double gMSJ   = ghMSJ.getGmsj(m, s, j);
  732.                 final double hMSJ   = ghMSJ.getHmsj(m, s, j);
  733.                 final double dGdh   = ghMSJ.getdGmsdh(m, s, j);
  734.                 final double dGdk   = ghMSJ.getdGmsdk(m, s, j);
  735.                 final double dGdA   = ghMSJ.getdGmsdAlpha(m, s, j);
  736.                 final double dGdB   = ghMSJ.getdGmsdBeta(m, s, j);
  737.                 final double dHdh   = ghMSJ.getdHmsdh(m, s, j);
  738.                 final double dHdk   = ghMSJ.getdHmsdk(m, s, j);
  739.                 final double dHdA   = ghMSJ.getdHmsdAlpha(m, s, j);
  740.                 final double dHdB   = ghMSJ.getdHmsdBeta(m, s, j);

  741.                 // Jacobi l-index from 2.7.1-(15)
  742.                 final int l = FastMath.min(n - m, n - FastMath.abs(s));
  743.                 // Jacobi polynomial and derivative
  744.                 final DerivativeStructure jacobi =
  745.                         JacobiPolynomials.getValue(l, v, w, factory.variable(0, gamma));

  746.                 // Geopotential coefficients
  747.                 final double cnm = harmonics.getUnnormalizedCnm(n, m);
  748.                 final double snm = harmonics.getUnnormalizedSnm(n, m);

  749.                 // Common factors from expansion of equations 3.3-4
  750.                 final double cf_0      = roaPow[n] * Im * vMNS;
  751.                 final double cf_1      = cf_0 * gaMNS * jacobi.getValue();
  752.                 final double cf_2      = cf_1 * kJNS;
  753.                 final double gcPhs     = gMSJ * cnm + hMSJ * snm;
  754.                 final double gsMhc     = gMSJ * snm - hMSJ * cnm;
  755.                 final double dKgcPhsx2 = 2. * dkJNS * gcPhs;
  756.                 final double dKgsMhcx2 = 2. * dkJNS * gsMhc;
  757.                 final double dUdaCoef  = (n + 1) * cf_2;
  758.                 final double dUdlCoef  = j * cf_2;
  759.                 final double dUdGaCoef = cf_0 * kJNS * (jacobi.getValue() * dGaMNS + gaMNS * jacobi.getPartialDerivative(1));

  760.                 // dU / da components
  761.                 dUdaCos  += dUdaCoef * gcPhs;
  762.                 dUdaSin  += dUdaCoef * gsMhc;

  763.                 // dU / dh components
  764.                 dUdhCos  += cf_1 * (kJNS * (cnm * dGdh + snm * dHdh) + h * dKgcPhsx2);
  765.                 dUdhSin  += cf_1 * (kJNS * (snm * dGdh - cnm * dHdh) + h * dKgsMhcx2);

  766.                 // dU / dk components
  767.                 dUdkCos  += cf_1 * (kJNS * (cnm * dGdk + snm * dHdk) + k * dKgcPhsx2);
  768.                 dUdkSin  += cf_1 * (kJNS * (snm * dGdk - cnm * dHdk) + k * dKgsMhcx2);

  769.                 // dU / dLambda components
  770.                 dUdlCos  +=  dUdlCoef * gsMhc;
  771.                 dUdlSin  += -dUdlCoef * gcPhs;

  772.                 // dU / alpha components
  773.                 dUdAlCos += cf_2 * (dGdA * cnm + dHdA * snm);
  774.                 dUdAlSin += cf_2 * (dGdA * snm - dHdA * cnm);

  775.                 // dU / dBeta components
  776.                 dUdBeCos += cf_2 * (dGdB * cnm + dHdB * snm);
  777.                 dUdBeSin += cf_2 * (dGdB * snm - dHdB * cnm);

  778.                 // dU / dGamma components
  779.                 dUdGaCos += dUdGaCoef * gcPhs;
  780.                 dUdGaSin += dUdGaCoef * gsMhc;
  781.             }
  782.         }

  783.         return new double[][] {{dUdaCos,  dUdaSin},
  784.                                {dUdhCos,  dUdhSin},
  785.                                {dUdkCos,  dUdkSin},
  786.                                {dUdlCos,  dUdlSin},
  787.                                {dUdAlCos, dUdAlSin},
  788.                                {dUdBeCos, dUdBeSin},
  789.                                {dUdGaCos, dUdGaSin}};
  790.     }

  791.     /** {@inheritDoc} */
  792.     @Override
  793.     public void registerAttitudeProvider(final AttitudeProvider attitudeProvider) {
  794.         //nothing is done since this contribution is not sensitive to attitude
  795.     }

  796.     /** Compute the C<sup>j</sup> and the S<sup>j</sup> coefficients.
  797.      *  <p>
  798.      *  Those coefficients are given in Danielson paper by substituting the
  799.      *  disturbing function (2.7.1-16) with m != 0 into (2.2-10)
  800.      *  </p>
  801.      */
  802.     private class FourierCjSjCoefficients {

  803.         /** Absolute limit for j ( -jMax <= j <= jMax ).  */
  804.         private final int jMax;

  805.         /** The C<sub>i</sub><sup>jm</sup> coefficients.
  806.          * <p>
  807.          * The index order is [m][j][i] <br/>
  808.          * The i index corresponds to the C<sub>i</sub><sup>jm</sup> coefficients used to
  809.          * compute the following: <br/>
  810.          * - da/dt <br/>
  811.          * - dk/dt <br/>
  812.          * - dh/dt / dk <br/>
  813.          * - dq/dt <br/>
  814.          * - dp/dt / dα <br/>
  815.          * - dλ/dt / dβ <br/>
  816.          * </p>
  817.          */
  818.         private final double[][][] cCoef;

  819.         /** The S<sub>i</sub><sup>jm</sup> coefficients.
  820.          * <p>
  821.          * The index order is [m][j][i] <br/>
  822.          * The i index corresponds to the C<sub>i</sub><sup>jm</sup> coefficients used to
  823.          * compute the following: <br/>
  824.          * - da/dt <br/>
  825.          * - dk/dt <br/>
  826.          * - dh/dt / dk <br/>
  827.          * - dq/dt <br/>
  828.          * - dp/dt / dα <br/>
  829.          * - dλ/dt / dβ <br/>
  830.          * </p>
  831.          */
  832.         private final double[][][] sCoef;

  833.         /** G<sub>ms</sub><sup>j</sup> and H<sub>ms</sub><sup>j</sup> polynomials. */
  834.         private GHmsjPolynomials ghMSJ;

  835.         /** &Gamma;<sub>ns</sub><sup>m</sup> function. */
  836.         private GammaMnsFunction gammaMNS;

  837.         /** R / a up to power degree. */
  838.         private final double[] roaPow;

  839.         /** Create a set of C<sub>i</sub><sup>jm</sup> and S<sub>i</sub><sup>jm</sup> coefficients.
  840.          *  @param jMax absolute limit for j ( -jMax <= j <= jMax )
  841.          *  @param mMax maximum value for m
  842.          */
  843.         FourierCjSjCoefficients(final int jMax, final int mMax) {
  844.             // initialise fields
  845.             final int rows    = mMax + 1;
  846.             final int columns = 2 * jMax + 1;
  847.             this.jMax         = jMax;
  848.             this.cCoef        = new double[rows][columns][6];
  849.             this.sCoef        = new double[rows][columns][6];
  850.             this.roaPow       = new double[maxDegree + 1];
  851.             roaPow[0] = 1.;
  852.         }

  853.         /**
  854.          * Generate the coefficients.
  855.          * @param date the current date
  856.          * @throws OrekitException if an error occurs while generating the coefficients
  857.          */
  858.         public void generateCoefficients(final AbsoluteDate date) throws OrekitException {
  859.             // Compute only if there is at least one non-resonant tesseral
  860.             if (!nonResOrders.isEmpty() || maxDegreeTesseralSP < 0) {
  861.                 // Gmsj and Hmsj polynomials
  862.                 ghMSJ = new GHmsjPolynomials(k, h, alpha, beta, I);

  863.                 // GAMMAmns function
  864.                 gammaMNS = new GammaMnsFunction(maxDegree, gamma, I);

  865.                 final int maxRoaPower = FastMath.max(maxDegreeTesseralSP, maxDegreeMdailyTesseralSP);

  866.                 // R / a up to power degree
  867.                 for (int i = 1; i <= maxRoaPower; i++) {
  868.                     roaPow[i] = roa * roaPow[i - 1];
  869.                 }

  870.                 //generate the m-daily coefficients
  871.                 for (int m = 1; m <= maxOrderMdailyTesseralSP; m++) {
  872.                     buildFourierCoefficients(date, m, 0, maxDegreeMdailyTesseralSP);
  873.                 }

  874.                 // generate the other coefficients only if required
  875.                 if (maxDegreeTesseralSP >= 0) {
  876.                     for (int m: nonResOrders.keySet()) {
  877.                         final List<Integer> listJ = nonResOrders.get(m);

  878.                         for (int j: listJ) {
  879.                             buildFourierCoefficients(date, m, j, maxDegreeTesseralSP);
  880.                         }
  881.                     }
  882.                 }
  883.             }
  884.         }

  885.         /** Build a set of fourier coefficients for a given m and j.
  886.          *
  887.          * @param date the date of the coefficients
  888.          * @param m m index
  889.          * @param j j index
  890.          * @param maxN  maximum value for n index
  891.          * @throws OrekitException in case of Hansen kernel generation error
  892.          */
  893.         private void buildFourierCoefficients(final AbsoluteDate date,
  894.                final int m, final int j, final int maxN) throws OrekitException {
  895.             // Potential derivatives components for a given non-resonant pair {j,m}
  896.             double dRdaCos  = 0.;
  897.             double dRdaSin  = 0.;
  898.             double dRdhCos  = 0.;
  899.             double dRdhSin  = 0.;
  900.             double dRdkCos  = 0.;
  901.             double dRdkSin  = 0.;
  902.             double dRdlCos  = 0.;
  903.             double dRdlSin  = 0.;
  904.             double dRdAlCos = 0.;
  905.             double dRdAlSin = 0.;
  906.             double dRdBeCos = 0.;
  907.             double dRdBeSin = 0.;
  908.             double dRdGaCos = 0.;
  909.             double dRdGaSin = 0.;

  910.             // s-SUM from -sMin to sMax
  911.             final int sMin = j == 0 ? maxEccPowMdailyTesseralSP : maxEccPowTesseralSP;
  912.             final int sMax = j == 0 ? maxEccPowMdailyTesseralSP : maxEccPowTesseralSP;
  913.             for (int s = 0; s <= sMax; s++) {

  914.                 // n-SUM for s positive
  915.                 final double[][] nSumSpos = computeNSum(date, j, m, s, maxN,
  916.                                                         roaPow, ghMSJ, gammaMNS);
  917.                 dRdaCos  += nSumSpos[0][0];
  918.                 dRdaSin  += nSumSpos[0][1];
  919.                 dRdhCos  += nSumSpos[1][0];
  920.                 dRdhSin  += nSumSpos[1][1];
  921.                 dRdkCos  += nSumSpos[2][0];
  922.                 dRdkSin  += nSumSpos[2][1];
  923.                 dRdlCos  += nSumSpos[3][0];
  924.                 dRdlSin  += nSumSpos[3][1];
  925.                 dRdAlCos += nSumSpos[4][0];
  926.                 dRdAlSin += nSumSpos[4][1];
  927.                 dRdBeCos += nSumSpos[5][0];
  928.                 dRdBeSin += nSumSpos[5][1];
  929.                 dRdGaCos += nSumSpos[6][0];
  930.                 dRdGaSin += nSumSpos[6][1];

  931.                 // n-SUM for s negative
  932.                 if (s > 0 && s <= sMin) {
  933.                     final double[][] nSumSneg = computeNSum(date, j, m, -s, maxN,
  934.                                                             roaPow, ghMSJ, gammaMNS);
  935.                     dRdaCos  += nSumSneg[0][0];
  936.                     dRdaSin  += nSumSneg[0][1];
  937.                     dRdhCos  += nSumSneg[1][0];
  938.                     dRdhSin  += nSumSneg[1][1];
  939.                     dRdkCos  += nSumSneg[2][0];
  940.                     dRdkSin  += nSumSneg[2][1];
  941.                     dRdlCos  += nSumSneg[3][0];
  942.                     dRdlSin  += nSumSneg[3][1];
  943.                     dRdAlCos += nSumSneg[4][0];
  944.                     dRdAlSin += nSumSneg[4][1];
  945.                     dRdBeCos += nSumSneg[5][0];
  946.                     dRdBeSin += nSumSneg[5][1];
  947.                     dRdGaCos += nSumSneg[6][0];
  948.                     dRdGaSin += nSumSneg[6][1];
  949.                 }
  950.             }
  951.             dRdaCos  *= -moa / a;
  952.             dRdaSin  *= -moa / a;
  953.             dRdhCos  *=  moa;
  954.             dRdhSin  *=  moa;
  955.             dRdkCos  *=  moa;
  956.             dRdkSin *=  moa;
  957.             dRdlCos *=  moa;
  958.             dRdlSin *=  moa;
  959.             dRdAlCos *=  moa;
  960.             dRdAlSin *=  moa;
  961.             dRdBeCos *=  moa;
  962.             dRdBeSin *=  moa;
  963.             dRdGaCos *=  moa;
  964.             dRdGaSin *=  moa;

  965.             // Compute the cross derivative operator :
  966.             final double RAlphaGammaCos   = alpha * dRdGaCos - gamma * dRdAlCos;
  967.             final double RAlphaGammaSin   = alpha * dRdGaSin - gamma * dRdAlSin;
  968.             final double RAlphaBetaCos    = alpha * dRdBeCos - beta  * dRdAlCos;
  969.             final double RAlphaBetaSin    = alpha * dRdBeSin - beta  * dRdAlSin;
  970.             final double RBetaGammaCos    =  beta * dRdGaCos - gamma * dRdBeCos;
  971.             final double RBetaGammaSin    =  beta * dRdGaSin - gamma * dRdBeSin;
  972.             final double RhkCos           =     h * dRdkCos  -     k * dRdhCos;
  973.             final double RhkSin           =     h * dRdkSin  -     k * dRdhSin;
  974.             final double pRagmIqRbgoABCos = (p * RAlphaGammaCos - I * q * RBetaGammaCos) * ooAB;
  975.             final double pRagmIqRbgoABSin = (p * RAlphaGammaSin - I * q * RBetaGammaSin) * ooAB;
  976.             final double RhkmRabmdRdlCos  =  RhkCos - RAlphaBetaCos - dRdlCos;
  977.             final double RhkmRabmdRdlSin  =  RhkSin - RAlphaBetaSin - dRdlSin;

  978.             // da/dt
  979.             cCoef[m][j + jMax][0] = ax2oA * dRdlCos;
  980.             sCoef[m][j + jMax][0] = ax2oA * dRdlSin;

  981.             // dk/dt
  982.             cCoef[m][j + jMax][1] = -(BoA * dRdhCos + h * pRagmIqRbgoABCos + k * BoABpo * dRdlCos);
  983.             sCoef[m][j + jMax][1] = -(BoA * dRdhSin + h * pRagmIqRbgoABSin + k * BoABpo * dRdlSin);

  984.             // dh/dt
  985.             cCoef[m][j + jMax][2] = BoA * dRdkCos + k * pRagmIqRbgoABCos - h * BoABpo * dRdlCos;
  986.             sCoef[m][j + jMax][2] = BoA * dRdkSin + k * pRagmIqRbgoABSin - h * BoABpo * dRdlSin;

  987.             // dq/dt
  988.             cCoef[m][j + jMax][3] = Co2AB * (q * RhkmRabmdRdlCos - I * RAlphaGammaCos);
  989.             sCoef[m][j + jMax][3] = Co2AB * (q * RhkmRabmdRdlSin - I * RAlphaGammaSin);

  990.             // dp/dt
  991.             cCoef[m][j + jMax][4] = Co2AB * (p * RhkmRabmdRdlCos - RBetaGammaCos);
  992.             sCoef[m][j + jMax][4] = Co2AB * (p * RhkmRabmdRdlSin - RBetaGammaSin);

  993.             // dλ/dt
  994.             cCoef[m][j + jMax][5] = -ax2oA * dRdaCos + BoABpo * (h * dRdhCos + k * dRdkCos) + pRagmIqRbgoABCos;
  995.             sCoef[m][j + jMax][5] = -ax2oA * dRdaSin + BoABpo * (h * dRdhSin + k * dRdkSin) + pRagmIqRbgoABSin;
  996.         }

  997.         /** Get the coefficient C<sub>i</sub><sup>jm</sup>.
  998.          * @param i i index - corresponds to the required variation
  999.          * @param j j index
  1000.          * @param m m index
  1001.          * @return the coefficient C<sub>i</sub><sup>jm</sup>
  1002.          */
  1003.         public double getCijm(final int i, final int j, final int m) {
  1004.             return cCoef[m][j + jMax][i];
  1005.         }

  1006.         /** Get the coefficient S<sub>i</sub><sup>jm</sup>.
  1007.          * @param i i index - corresponds to the required variation
  1008.          * @param j j index
  1009.          * @param m m index
  1010.          * @return the coefficient S<sub>i</sub><sup>jm</sup>
  1011.          */
  1012.         public double getSijm(final int i, final int j, final int m) {
  1013.             return sCoef[m][j + jMax][i];
  1014.         }
  1015.     }

  1016.     /** 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
  1017.      * the short-periodic zonal contribution.
  1018.      *   <p>
  1019.      *  Those coefficients are given by expression 2.5.4-5 from the Danielson paper.
  1020.      *   </p>
  1021.      *
  1022.      * @author Sorin Scortan
  1023.      *
  1024.      */
  1025.     private static class TesseralShortPeriodicCoefficients implements ShortPeriodTerms {

  1026.         /** Serializable UID. */
  1027.         private static final long serialVersionUID = 20151119L;

  1028.         /** Retrograde factor I.
  1029.          *  <p>
  1030.          *  DSST model needs equinoctial orbit as internal representation.
  1031.          *  Classical equinoctial elements have discontinuities when inclination
  1032.          *  is close to zero. In this representation, I = +1. <br>
  1033.          *  To avoid this discontinuity, another representation exists and equinoctial
  1034.          *  elements can be expressed in a different way, called "retrograde" orbit.
  1035.          *  This implies I = -1. <br>
  1036.          *  As Orekit doesn't implement the retrograde orbit, I is always set to +1.
  1037.          *  But for the sake of consistency with the theory, the retrograde factor
  1038.          *  has been kept in the formulas.
  1039.          *  </p>
  1040.          */
  1041.         private static final int I = 1;

  1042.         /** Central body rotating frame. */
  1043.         private final Frame bodyFrame;

  1044.         /** Maximal order to consider for short periodics m-daily tesseral harmonics potential. */
  1045.         private final int maxOrderMdailyTesseralSP;

  1046.         /** Flag to take into account only M-dailies harmonic tesserals for short periodic perturbations.  */
  1047.         private final boolean mDailiesOnly;

  1048.         /** List of non resonant orders with j != 0. */
  1049.         private final SortedMap<Integer, List<Integer> > nonResOrders;

  1050.         /** Maximum value for m index. */
  1051.         private final int mMax;

  1052.         /** Maximum value for j index. */
  1053.         private final int jMax;

  1054.         /** Number of points used in the interpolation process. */
  1055.         private final int interpolationPoints;

  1056.         /** All coefficients slots. */
  1057.         private final transient TimeSpanMap<Slot> slots;

  1058.         /** Constructor.
  1059.          * @param bodyFrame central body rotating frame
  1060.          * @param maxOrderMdailyTesseralSP maximal order to consider for short periodics m-daily tesseral harmonics potential
  1061.          * @param mDailiesOnly flag to take into account only M-dailies harmonic tesserals for short periodic perturbations
  1062.          * @param nonResOrders lst of non resonant orders with j != 0
  1063.          * @param mMax maximum value for m index
  1064.          * @param jMax maximum value for j index
  1065.          * @param interpolationPoints number of points used in the interpolation process
  1066.          * @param slots all coefficients slots
  1067.          */
  1068.         TesseralShortPeriodicCoefficients(final Frame bodyFrame, final int maxOrderMdailyTesseralSP,
  1069.                                           final boolean mDailiesOnly, final SortedMap<Integer, List<Integer> > nonResOrders,
  1070.                                           final int mMax, final int jMax, final int interpolationPoints,
  1071.                                           final TimeSpanMap<Slot> slots) {
  1072.             this.bodyFrame                = bodyFrame;
  1073.             this.maxOrderMdailyTesseralSP = maxOrderMdailyTesseralSP;
  1074.             this.mDailiesOnly             = mDailiesOnly;
  1075.             this.nonResOrders             = nonResOrders;
  1076.             this.mMax                     = mMax;
  1077.             this.jMax                     = jMax;
  1078.             this.interpolationPoints      = interpolationPoints;
  1079.             this.slots                    = slots;
  1080.         }

  1081.         /** Get the slot valid for some date.
  1082.          * @param meanStates mean states defining the slot
  1083.          * @return slot valid at the specified date
  1084.          */
  1085.         public Slot createSlot(final SpacecraftState... meanStates) {
  1086.             final Slot         slot  = new Slot(mMax, jMax, interpolationPoints);
  1087.             final AbsoluteDate first = meanStates[0].getDate();
  1088.             final AbsoluteDate last  = meanStates[meanStates.length - 1].getDate();
  1089.             if (first.compareTo(last) <= 0) {
  1090.                 slots.addValidAfter(slot, first);
  1091.             } else {
  1092.                 slots.addValidBefore(slot, first);
  1093.             }
  1094.             return slot;
  1095.         }

  1096.         /** {@inheritDoc} */
  1097.         @Override
  1098.         public double[] value(final Orbit meanOrbit) throws OrekitException {

  1099.             // select the coefficients slot
  1100.             final Slot slot = slots.get(meanOrbit.getDate());

  1101.             // Initialise the short periodic variations
  1102.             final double[] shortPeriodicVariation = new double[6];

  1103.             // Compute only if there is at least one non-resonant tesseral or
  1104.             // only the m-daily tesseral should be taken into account
  1105.             if (!nonResOrders.isEmpty() || mDailiesOnly) {

  1106.                 //Build an auxiliary object
  1107.                 final AuxiliaryElements aux = new AuxiliaryElements(meanOrbit, I);

  1108.                 // Central body rotation angle from equation 2.7.1-(3)(4).
  1109.                 final Transform t = bodyFrame.getTransformTo(aux.getFrame(), aux.getDate());
  1110.                 final Vector3D xB = t.transformVector(Vector3D.PLUS_I);
  1111.                 final Vector3D yB = t.transformVector(Vector3D.PLUS_J);
  1112.                 final Vector3D  f = aux.getVectorF();
  1113.                 final Vector3D  g = aux.getVectorG();
  1114.                 final double currentTheta = FastMath.atan2(-f.dotProduct(yB) + I * g.dotProduct(xB),
  1115.                                                             f.dotProduct(xB) + I * g.dotProduct(yB));

  1116.                 //Add the m-daily contribution
  1117.                 for (int m = 1; m <= maxOrderMdailyTesseralSP; m++) {
  1118.                     // Phase angle
  1119.                     final double jlMmt  = -m * currentTheta;
  1120.                     final double sinPhi = FastMath.sin(jlMmt);
  1121.                     final double cosPhi = FastMath.cos(jlMmt);

  1122.                     // compute contribution for each element
  1123.                     final double[] c = slot.getCijm(0, m, meanOrbit.getDate());
  1124.                     final double[] s = slot.getSijm(0, m, meanOrbit.getDate());
  1125.                     for (int i = 0; i < 6; i++) {
  1126.                         shortPeriodicVariation[i] += c[i] * cosPhi + s[i] * sinPhi;
  1127.                     }
  1128.                 }

  1129.                 // loop through all non-resonant (j,m) pairs
  1130.                 for (final Map.Entry<Integer, List<Integer>> entry : nonResOrders.entrySet()) {
  1131.                     final int           m     = entry.getKey();
  1132.                     final List<Integer> listJ = entry.getValue();

  1133.                     for (int j : listJ) {
  1134.                         // Phase angle
  1135.                         final double jlMmt  = j * meanOrbit.getLM() - m * currentTheta;
  1136.                         final double sinPhi = FastMath.sin(jlMmt);
  1137.                         final double cosPhi = FastMath.cos(jlMmt);

  1138.                         // compute contribution for each element
  1139.                         final double[] c = slot.getCijm(j, m, meanOrbit.getDate());
  1140.                         final double[] s = slot.getSijm(j, m, meanOrbit.getDate());
  1141.                         for (int i = 0; i < 6; i++) {
  1142.                             shortPeriodicVariation[i] += c[i] * cosPhi + s[i] * sinPhi;
  1143.                         }

  1144.                     }
  1145.                 }
  1146.             }

  1147.             return shortPeriodicVariation;

  1148.         }

  1149.         /** {@inheritDoc} */
  1150.         @Override
  1151.         public String getCoefficientsKeyPrefix() {
  1152.             return "DSST-central-body-tesseral-";
  1153.         }

  1154.         /** {@inheritDoc}
  1155.          * <p>
  1156.          * For tesseral terms contributions,there are maxOrderMdailyTesseralSP
  1157.          * m-daily cMm coefficients, maxOrderMdailyTesseralSP m-daily sMm
  1158.          * coefficients, nbNonResonant cjm coefficients and nbNonResonant
  1159.          * sjm coefficients, where maxOrderMdailyTesseralSP and nbNonResonant both
  1160.          * depend on the orbit. The j index is the integer multiplier for the true
  1161.          * longitude argument and the m index is the integer multiplier for m-dailies.
  1162.          * </p>
  1163.          */
  1164.         @Override
  1165.         public Map<String, double[]> getCoefficients(final AbsoluteDate date, final Set<String> selected)
  1166.             throws OrekitException {

  1167.             // select the coefficients slot
  1168.             final Slot slot = slots.get(date);

  1169.             if (!nonResOrders.isEmpty() || mDailiesOnly) {
  1170.                 final Map<String, double[]> coefficients =
  1171.                                 new HashMap<String, double[]>(12 * maxOrderMdailyTesseralSP +
  1172.                                                               12 * nonResOrders.size());

  1173.                 for (int m = 1; m <= maxOrderMdailyTesseralSP; m++) {
  1174.                     storeIfSelected(coefficients, selected, slot.getCijm(0, m, date), "cM", m);
  1175.                     storeIfSelected(coefficients, selected, slot.getSijm(0, m, date), "sM", m);
  1176.                 }

  1177.                 for (final Map.Entry<Integer, List<Integer>> entry : nonResOrders.entrySet()) {
  1178.                     final int           m     = entry.getKey();
  1179.                     final List<Integer> listJ = entry.getValue();

  1180.                     for (int j : listJ) {
  1181.                         for (int i = 0; i < 6; ++i) {
  1182.                             storeIfSelected(coefficients, selected, slot.getCijm(j, m, date), "c", j, m);
  1183.                             storeIfSelected(coefficients, selected, slot.getSijm(j, m, date), "s", j, m);
  1184.                         }
  1185.                     }
  1186.                 }

  1187.                 return coefficients;

  1188.             } else {
  1189.                 return Collections.emptyMap();
  1190.             }

  1191.         }

  1192.         /** Put a coefficient in a map if selected.
  1193.          * @param map map to populate
  1194.          * @param selected set of coefficients that should be put in the map
  1195.          * (empty set means all coefficients are selected)
  1196.          * @param value coefficient value
  1197.          * @param id coefficient identifier
  1198.          * @param indices list of coefficient indices
  1199.          */
  1200.         private void storeIfSelected(final Map<String, double[]> map, final Set<String> selected,
  1201.                                      final double[] value, final String id, final int... indices) {
  1202.             final StringBuilder keyBuilder = new StringBuilder(getCoefficientsKeyPrefix());
  1203.             keyBuilder.append(id);
  1204.             for (int index : indices) {
  1205.                 keyBuilder.append('[').append(index).append(']');
  1206.             }
  1207.             final String key = keyBuilder.toString();
  1208.             if (selected.isEmpty() || selected.contains(key)) {
  1209.                 map.put(key, value);
  1210.             }
  1211.         }

  1212.         /** Replace the instance with a data transfer object for serialization.
  1213.          * @return data transfer object that will be serialized
  1214.          * @exception NotSerializableException if an additional state provider is not serializable
  1215.          */
  1216.         private Object writeReplace() throws NotSerializableException {

  1217.             // slots transitions
  1218.             final SortedSet<TimeSpanMap.Transition<Slot>> transitions     = slots.getTransitions();
  1219.             final AbsoluteDate[]                          transitionDates = new AbsoluteDate[transitions.size()];
  1220.             final Slot[]                                  allSlots        = new Slot[transitions.size() + 1];
  1221.             int i = 0;
  1222.             for (final TimeSpanMap.Transition<Slot> transition : transitions) {
  1223.                 if (i == 0) {
  1224.                     // slot before the first transition
  1225.                     allSlots[i] = transition.getBefore();
  1226.                 }
  1227.                 if (i < transitionDates.length) {
  1228.                     transitionDates[i] = transition.getDate();
  1229.                     allSlots[++i]      = transition.getAfter();
  1230.                 }
  1231.             }

  1232.             return new DataTransferObject(bodyFrame, maxOrderMdailyTesseralSP,
  1233.                                           mDailiesOnly, nonResOrders,
  1234.                                           mMax, jMax, interpolationPoints,
  1235.                                           transitionDates, allSlots);

  1236.         }


  1237.         /** Internal class used only for serialization. */
  1238.         private static class DataTransferObject implements Serializable {

  1239.             /** Serializable UID. */
  1240.             private static final long serialVersionUID = 20160319L;

  1241.             /** Central body rotating frame. */
  1242.             private final Frame bodyFrame;

  1243.             /** Maximal order to consider for short periodics m-daily tesseral harmonics potential. */
  1244.             private final int maxOrderMdailyTesseralSP;

  1245.             /** Flag to take into account only M-dailies harmonic tesserals for short periodic perturbations.  */
  1246.             private final boolean mDailiesOnly;

  1247.             /** List of non resonant orders with j != 0. */
  1248.             private final SortedMap<Integer, List<Integer> > nonResOrders;

  1249.             /** Maximum value for m index. */
  1250.             private final int mMax;

  1251.             /** Maximum value for j index. */
  1252.             private final int jMax;

  1253.             /** Number of points used in the interpolation process. */
  1254.             private final int interpolationPoints;

  1255.             /** Transitions dates. */
  1256.             private final AbsoluteDate[] transitionDates;

  1257.             /** All slots. */
  1258.             private final Slot[] allSlots;

  1259.             /** Simple constructor.
  1260.              * @param bodyFrame central body rotating frame
  1261.              * @param maxOrderMdailyTesseralSP maximal order to consider for short periodics m-daily tesseral harmonics potential
  1262.              * @param mDailiesOnly flag to take into account only M-dailies harmonic tesserals for short periodic perturbations
  1263.              * @param nonResOrders lst of non resonant orders with j != 0
  1264.              * @param mMax maximum value for m index
  1265.              * @param jMax maximum value for j index
  1266.              * @param interpolationPoints number of points used in the interpolation process
  1267.              * @param transitionDates transitions dates
  1268.              * @param allSlots all slots
  1269.              */
  1270.             DataTransferObject(final Frame bodyFrame, final int maxOrderMdailyTesseralSP,
  1271.                                final boolean mDailiesOnly, final SortedMap<Integer, List<Integer> > nonResOrders,
  1272.                                final int mMax, final int jMax, final int interpolationPoints,
  1273.                                final AbsoluteDate[] transitionDates, final Slot[] allSlots) {
  1274.                 this.bodyFrame                = bodyFrame;
  1275.                 this.maxOrderMdailyTesseralSP = maxOrderMdailyTesseralSP;
  1276.                 this.mDailiesOnly             = mDailiesOnly;
  1277.                 this.nonResOrders             = nonResOrders;
  1278.                 this.mMax                     = mMax;
  1279.                 this.jMax                     = jMax;
  1280.                 this.interpolationPoints      = interpolationPoints;
  1281.                 this.transitionDates          = transitionDates;
  1282.                 this.allSlots                 = allSlots;
  1283.             }

  1284.             /** Replace the deserialized data transfer object with a {@link TesseralShortPeriodicCoefficients}.
  1285.              * @return replacement {@link TesseralShortPeriodicCoefficients}
  1286.              */
  1287.             private Object readResolve() {

  1288.                 final TimeSpanMap<Slot> slots = new TimeSpanMap<Slot>(allSlots[0]);
  1289.                 for (int i = 0; i < transitionDates.length; ++i) {
  1290.                     slots.addValidAfter(allSlots[i + 1], transitionDates[i]);
  1291.                 }

  1292.                 return new TesseralShortPeriodicCoefficients(bodyFrame, maxOrderMdailyTesseralSP, mDailiesOnly,
  1293.                                                              nonResOrders, mMax, jMax, interpolationPoints,
  1294.                                                              slots);

  1295.             }

  1296.         }

  1297.     }

  1298.     /** Coefficients valid for one time slot. */
  1299.     private static class Slot implements Serializable {

  1300.         /** Serializable UID. */
  1301.         private static final long serialVersionUID = 20160319L;

  1302.         /** The coefficients C<sub>i</sub><sup>j</sup><sup>m</sup>.
  1303.          * <p>
  1304.          * The index order is cijm[m][j][i] <br/>
  1305.          * i corresponds to the equinoctial element, as follows: <br/>
  1306.          * - i=0 for a <br/>
  1307.          * - i=1 for k <br/>
  1308.          * - i=2 for h <br/>
  1309.          * - i=3 for q <br/>
  1310.          * - i=4 for p <br/>
  1311.          * - i=5 for λ <br/>
  1312.          * </p>
  1313.          */
  1314.         private final ShortPeriodicsInterpolatedCoefficient[][] cijm;

  1315.         /** The coefficients S<sub>i</sub><sup>j</sup><sup>m</sup>.
  1316.          * <p>
  1317.          * The index order is sijm[m][j][i] <br/>
  1318.          * i corresponds to the equinoctial element, as follows: <br/>
  1319.          * - i=0 for a <br/>
  1320.          * - i=1 for k <br/>
  1321.          * - i=2 for h <br/>
  1322.          * - i=3 for q <br/>
  1323.          * - i=4 for p <br/>
  1324.          * - i=5 for λ <br/>
  1325.          * </p>
  1326.          */
  1327.         private final ShortPeriodicsInterpolatedCoefficient[][] sijm;

  1328.         /** Simple constructor.
  1329.          *  @param mMax maximum value for m index
  1330.          *  @param jMax maximum value for j index
  1331.          *  @param interpolationPoints number of points used in the interpolation process
  1332.          */
  1333.         Slot(final int mMax, final int jMax, final int interpolationPoints) {

  1334.             final int rows    = mMax + 1;
  1335.             final int columns = 2 * jMax + 1;
  1336.             cijm = new ShortPeriodicsInterpolatedCoefficient[rows][columns];
  1337.             sijm = new ShortPeriodicsInterpolatedCoefficient[rows][columns];
  1338.             for (int m = 1; m <= mMax; m++) {
  1339.                 for (int j = -jMax; j <= jMax; j++) {
  1340.                     cijm[m][j + jMax] = new ShortPeriodicsInterpolatedCoefficient(interpolationPoints);
  1341.                     sijm[m][j + jMax] = new ShortPeriodicsInterpolatedCoefficient(interpolationPoints);
  1342.                 }
  1343.             }

  1344.         }

  1345.         /** Get C<sub>i</sub><sup>j</sup><sup>m</sup>.
  1346.          *
  1347.          * @param j j index
  1348.          * @param m m index
  1349.          * @param date the date
  1350.          * @return C<sub>i</sub><sup>j</sup><sup>m</sup>
  1351.          */
  1352.         double[] getCijm(final int j, final int m, final AbsoluteDate date) {
  1353.             final int jMax = (cijm[m].length - 1) / 2;
  1354.             return cijm[m][j + jMax].value(date);
  1355.         }

  1356.         /** Get S<sub>i</sub><sup>j</sup><sup>m</sup>.
  1357.          *
  1358.          * @param j j index
  1359.          * @param m m index
  1360.          * @param date the date
  1361.          * @return S<sub>i</sub><sup>j</sup><sup>m</sup>
  1362.          */
  1363.         double[] getSijm(final int j, final int m, final AbsoluteDate date) {
  1364.             final int jMax = (cijm[m].length - 1) / 2;
  1365.             return sijm[m][j + jMax].value(date);
  1366.         }

  1367.     }

  1368. }