ZonalContribution.java

  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. import java.util.TreeMap;

  19. import org.apache.commons.math3.util.FastMath;
  20. import org.orekit.errors.OrekitException;
  21. import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider;
  22. import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider.UnnormalizedSphericalHarmonics;
  23. import org.orekit.propagation.SpacecraftState;
  24. import org.orekit.propagation.events.EventDetector;
  25. import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
  26. import org.orekit.propagation.semianalytical.dsst.utilities.CoefficientsFactory;
  27. import org.orekit.propagation.semianalytical.dsst.utilities.CoefficientsFactory.NSKey;
  28. import org.orekit.propagation.semianalytical.dsst.utilities.UpperBounds;
  29. import org.orekit.time.AbsoluteDate;

  30. /** Zonal contribution to the {@link DSSTCentralBody central body gravitational perturbation}.
  31.  *
  32.  *   @author Romain Di Costanzo
  33.  *   @author Pascal Parraud
  34.  */
  35. class ZonalContribution implements DSSTForceModel {

  36.     /** Truncation tolerance. */
  37.     private static final double TRUNCATION_TOLERANCE = 1e-4;

  38.     /** Provider for spherical harmonics. */
  39.     private final UnnormalizedSphericalHarmonicsProvider provider;

  40.     /** Maximal degree to consider for harmonics potential. */
  41.     private final int maxDegree;

  42.     /** Maximal degree to consider for harmonics potential. */
  43.     private final int maxOrder;

  44.     /** Factorial. */
  45.     private final double[] fact;

  46.     /** Coefficient used to define the mean disturbing function V<sub>ns</sub> coefficient. */
  47.     private final TreeMap<NSKey, Double> Vns;

  48.     /** Highest power of the eccentricity to be used in series expansion. */
  49.     private int maxEccPow;

  50.     // Equinoctial elements (according to DSST notation)
  51.     /** a. */
  52.     private double a;
  53.     /** ex. */
  54.     private double k;
  55.     /** ey. */
  56.     private double h;
  57.     /** hx. */
  58.     private double q;
  59.     /** hy. */
  60.     private double p;
  61.     /** Retrograde factor. */
  62.     private int    I;

  63.     /** Eccentricity. */
  64.     private double ecc;

  65.     /** Direction cosine &alpha. */
  66.     private double alpha;
  67.     /** Direction cosine &beta. */
  68.     private double beta;
  69.     /** Direction cosine &gamma. */
  70.     private double gamma;

  71.     // Common factors for potential computation
  72.     /** &Chi; = 1 / sqrt(1 - e<sup>2</sup>) = 1 / B. */
  73.     private double X;
  74.     /** &Chi;<sup>2</sup>. */
  75.     private double XX;
  76.     /** &Chi;<sup>3</sup>. */
  77.     private double XXX;
  78.     /** 1 / (A * B) .*/
  79.     private double ooAB;
  80.     /** B / A .*/
  81.     private double BoA;
  82.     /** B / A(1 + B) .*/
  83.     private double BoABpo;
  84.     /** -C / (2 * A * B) .*/
  85.     private double mCo2AB;
  86.     /** -2 * a / A .*/
  87.     private double m2aoA;
  88.     /** &mu; / a .*/
  89.     private double muoa;

  90.     /** Simple constructor.
  91.      * @param provider provider for spherical harmonics
  92.      */
  93.     public ZonalContribution(final UnnormalizedSphericalHarmonicsProvider provider) {

  94.         this.provider  = provider;
  95.         this.maxDegree = provider.getMaxDegree();
  96.         this.maxOrder  = provider.getMaxOrder();

  97.         // Vns coefficients
  98.         this.Vns = CoefficientsFactory.computeVns(maxDegree + 1);

  99.         // Factorials computation
  100.         final int maxFact = 2 * maxDegree + 1;
  101.         this.fact = new double[maxFact];
  102.         fact[0] = 1.;
  103.         for (int i = 1; i < maxFact; i++) {
  104.             fact[i] = i * fact[i - 1];
  105.         }

  106.         // Initialize default values
  107.         this.maxEccPow = (maxDegree == 2) ? 0 : Integer.MIN_VALUE;
  108.     }

  109.     /** Get the spherical harmonics provider.
  110.      *  @return the spherical harmonics provider
  111.      */
  112.     public UnnormalizedSphericalHarmonicsProvider getProvider() {
  113.         return provider;
  114.     }

  115.     /** Computes the highest power of the eccentricity to appear in the truncated
  116.      *  analytical power series expansion.
  117.      *  <p>
  118.      *  This method computes the upper value for the central body potential and
  119.      *  determines the maximal power for the eccentricity producing potential
  120.      *  terms bigger than a defined tolerance.
  121.      *  </p>
  122.      *  @param aux auxiliary elements related to the current orbit
  123.      *  @throws OrekitException if some specific error occurs
  124.      */
  125.     public void initialize(final AuxiliaryElements aux)
  126.         throws OrekitException {

  127.         if (maxDegree == 2) {
  128.             maxEccPow = 0;
  129.         } else {
  130.             // Initializes specific parameters.
  131.             initializeStep(aux);
  132.             final UnnormalizedSphericalHarmonics harmonics = provider.onDate(aux.getDate());

  133.             // Utilities for truncation
  134.             final double ax2or = 2. * a / provider.getAe();
  135.             double xmuran = provider.getMu() / a;
  136.             // Set a lower bound for eccentricity
  137.             final double eo2  = FastMath.max(0.0025, ecc / 2.);
  138.             final double x2o2 = XX / 2.;
  139.             final double[] eccPwr = new double[maxDegree + 1];
  140.             final double[] chiPwr = new double[maxDegree + 1];
  141.             final double[] hafPwr = new double[maxDegree + 1];
  142.             eccPwr[0] = 1.;
  143.             chiPwr[0] = X;
  144.             hafPwr[0] = 1.;
  145.             for (int i = 1; i <= maxDegree; i++) {
  146.                 eccPwr[i] = eccPwr[i - 1] * eo2;
  147.                 chiPwr[i] = chiPwr[i - 1] * x2o2;
  148.                 hafPwr[i] = hafPwr[i - 1] * 0.5;
  149.                 xmuran  /= ax2or;
  150.             }

  151.             // Set highest power of e and degree of current spherical harmonic.
  152.             maxEccPow = 0;
  153.             int n = maxDegree;
  154.             // Loop over n
  155.             do {
  156.                 // Set order of current spherical harmonic.
  157.                 int m = 0;
  158.                 // Loop over m
  159.                 do {
  160.                     // Compute magnitude of current spherical harmonic coefficient.
  161.                     final double cnm = harmonics.getUnnormalizedCnm(n, m);
  162.                     final double snm = harmonics.getUnnormalizedSnm(n, m);
  163.                     final double csnm = FastMath.hypot(cnm, snm);
  164.                     if (csnm == 0.) break;
  165.                     // Set magnitude of last spherical harmonic term.
  166.                     double lastTerm = 0.;
  167.                     // Set current power of e and related indices.
  168.                     int nsld2 = (n - maxEccPow - 1) / 2;
  169.                     int l = n - 2 * nsld2;
  170.                     // Loop over l
  171.                     double term = 0.;
  172.                     do {
  173.                         // Compute magnitude of current spherical harmonic term.
  174.                         if (m < l) {
  175.                             term = csnm * xmuran *
  176.                                     (fact[n - l] / (fact[n - m])) *
  177.                                     (fact[n + l] / (fact[nsld2] * fact[nsld2 + l])) *
  178.                                     eccPwr[l] * UpperBounds.getDnl(XX, chiPwr[l], n, l) *
  179.                                     (UpperBounds.getRnml(gamma, n, l, m, 1, I) + UpperBounds.getRnml(gamma, n, l, m, -1, I));
  180.                         } else {
  181.                             term = csnm * xmuran *
  182.                                     (fact[n + m] / (fact[nsld2] * fact[nsld2 + l])) *
  183.                                     eccPwr[l] * hafPwr[m - l] * UpperBounds.getDnl(XX, chiPwr[l], n, l) *
  184.                                     (UpperBounds.getRnml(gamma, n, m, l, 1, I) + UpperBounds.getRnml(gamma, n, m, l, -1, I));
  185.                         }
  186.                         // Is the current spherical harmonic term bigger than the truncation tolerance ?
  187.                         if (term >= TRUNCATION_TOLERANCE) {
  188.                             maxEccPow = l;
  189.                         } else {
  190.                             // Is the current term smaller than the last term ?
  191.                             if (term < lastTerm) {
  192.                                 break;
  193.                             }
  194.                         }
  195.                         // Proceed to next power of e.
  196.                         lastTerm = term;
  197.                         l += 2;
  198.                         nsld2--;
  199.                     } while (l < n);
  200.                     // Is the current spherical harmonic term bigger than the truncation tolerance ?
  201.                     if (term >= TRUNCATION_TOLERANCE) {
  202.                         maxEccPow = FastMath.min(maxDegree - 2, maxEccPow);
  203.                         return;
  204.                     }
  205.                     // Proceed to next order.
  206.                     m++;
  207.                 } while (m <= FastMath.min(n, maxOrder));
  208.                 // Procced to next degree.
  209.                 xmuran *= ax2or;
  210.                 n--;
  211.             } while (n > maxEccPow + 2);

  212.             maxEccPow = FastMath.min(maxDegree - 2, maxEccPow);
  213.         }
  214.     }

  215.     /** {@inheritDoc} */
  216.     public void initializeStep(final AuxiliaryElements aux) throws OrekitException {

  217.         // Equinoctial elements
  218.         a = aux.getSma();
  219.         k = aux.getK();
  220.         h = aux.getH();
  221.         q = aux.getQ();
  222.         p = aux.getP();

  223.         // Retrograde factor
  224.         I = aux.getRetrogradeFactor();

  225.         // Eccentricity
  226.         ecc = aux.getEcc();

  227.         // Direction cosines
  228.         alpha = aux.getAlpha();
  229.         beta  = aux.getBeta();
  230.         gamma = aux.getGamma();

  231.         // Equinoctial coefficients
  232.         final double AA = aux.getA();
  233.         final double BB = aux.getB();
  234.         final double CC = aux.getC();

  235.         // &Chi; = 1 / B
  236.         X = 1. / BB;
  237.         XX = X * X;
  238.         XXX = X * XX;
  239.         // 1 / AB
  240.         ooAB = 1. / (AA * BB);
  241.         // B / A
  242.         BoA = BB / AA;
  243.         // -C / 2AB
  244.         mCo2AB = -CC * ooAB / 2.;
  245.         // B / A(1 + B)
  246.         BoABpo = BoA / (1. + BB);
  247.         // -2 * a / A
  248.         m2aoA = -2 * a / AA;
  249.         // &mu; / a
  250.         muoa = provider.getMu() / a;

  251.     }

  252.     /** {@inheritDoc} */
  253.     public double[] getMeanElementRate(final SpacecraftState spacecraftState) throws OrekitException {

  254.         // Compute potential derivative
  255.         final double[] dU  = computeUDerivatives(spacecraftState.getDate());
  256.         final double dUda  = dU[0];
  257.         final double dUdk  = dU[1];
  258.         final double dUdh  = dU[2];
  259.         final double dUdAl = dU[3];
  260.         final double dUdBe = dU[4];
  261.         final double dUdGa = dU[5];

  262.         // Compute cross derivatives [Eq. 2.2-(8)]
  263.         // U(alpha,gamma) = alpha * dU/dgamma - gamma * dU/dalpha
  264.         final double UAlphaGamma   = alpha * dUdGa - gamma * dUdAl;
  265.         // U(beta,gamma) = beta * dU/dgamma - gamma * dU/dbeta
  266.         final double UBetaGamma    =  beta * dUdGa - gamma * dUdBe;
  267.         // Common factor
  268.         final double pUAGmIqUBGoAB = (p * UAlphaGamma - I * q * UBetaGamma) * ooAB;

  269.         // Compute mean elements rates [Eq. 3.1-(1)]
  270.         final double da =  0.;
  271.         final double dh =  BoA * dUdk + k * pUAGmIqUBGoAB;
  272.         final double dk = -BoA * dUdh - h * pUAGmIqUBGoAB;
  273.         final double dp =  mCo2AB * UBetaGamma;
  274.         final double dq =  mCo2AB * UAlphaGamma * I;
  275.         final double dM =  m2aoA * dUda + BoABpo * (h * dUdh + k * dUdk) + pUAGmIqUBGoAB;

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

  278.     /** {@inheritDoc} */
  279.     public double[] getShortPeriodicVariations(final AbsoluteDate date,
  280.                                                final double[] meanElements)
  281.         throws OrekitException {
  282.         // TODO: not implemented yet, Short Periodic Variations are set to null
  283.         return new double[] {0., 0., 0., 0., 0., 0.};
  284.     }

  285.     /** {@inheritDoc} */
  286.     public EventDetector[] getEventsDetectors() {
  287.         return null;
  288.     }

  289.     /** Compute the derivatives of the gravitational potential U [Eq. 3.1-(6)].
  290.      *  <p>
  291.      *  The result is the array
  292.      *  [dU/da, dU/dk, dU/dh, dU/d&alpha;, dU/d&beta;, dU/d&gamma;]
  293.      *  </p>
  294.      *  @param date current date
  295.      *  @return potential derivatives
  296.      *  @throws OrekitException if an error occurs in hansen computation
  297.      */
  298.     private double[] computeUDerivatives(final AbsoluteDate date) throws OrekitException {

  299.         final UnnormalizedSphericalHarmonics harmonics = provider.onDate(date);

  300.         // Hansen coefficients
  301.         final HansenZonal hansen = new HansenZonal();
  302.         // Gs and Hs coefficients
  303.         final double[][] GsHs = CoefficientsFactory.computeGsHs(k, h, alpha, beta, maxEccPow);
  304.         // Qns coefficients
  305.         final double[][] Qns  = CoefficientsFactory.computeQns(gamma, maxDegree, maxEccPow);
  306.         // r / a up to power degree
  307.         final double roa = provider.getAe() / a;

  308.         final double[] roaPow = new double[maxDegree + 1];
  309.         roaPow[0] = 1.;
  310.         for (int i = 1; i <= maxDegree; i++) {
  311.             roaPow[i] = roa * roaPow[i - 1];
  312.         }

  313.         // Potential derivatives
  314.         double dUda  = 0.;
  315.         double dUdk  = 0.;
  316.         double dUdh  = 0.;
  317.         double dUdAl = 0.;
  318.         double dUdBe = 0.;
  319.         double dUdGa = 0.;

  320.         for (int s = 0; s <= maxEccPow; s++) {
  321.             // Get the current Gs coefficient
  322.             final double gs = GsHs[0][s];

  323.             // Compute Gs partial derivatives from 3.1-(9)
  324.             double dGsdh  = 0.;
  325.             double dGsdk  = 0.;
  326.             double dGsdAl = 0.;
  327.             double dGsdBe = 0.;
  328.             if (s > 0) {
  329.                 // First get the G(s-1) and the H(s-1) coefficients
  330.                 final double sxgsm1 = s * GsHs[0][s - 1];
  331.                 final double sxhsm1 = s * GsHs[1][s - 1];
  332.                 // Then compute derivatives
  333.                 dGsdh  = beta  * sxgsm1 - alpha * sxhsm1;
  334.                 dGsdk  = alpha * sxgsm1 + beta  * sxhsm1;
  335.                 dGsdAl = k * sxgsm1 - h * sxhsm1;
  336.                 dGsdBe = h * sxgsm1 + k * sxhsm1;
  337.             }

  338.             // Kronecker symbol (2 - delta(0,s))
  339.             final double d0s = (s == 0) ? 1 : 2;

  340.             for (int n = s + 2; n <= maxDegree; n++) {
  341.                 // (n - s) must be even
  342.                 if ((n - s) % 2 == 0) {
  343.                     // Extract data from previous computation :
  344.                     final double kns   = hansen.getValue(-n - 1, s);
  345.                     final double dkns  = hansen.getDerivative(-n - 1, s);
  346.                     final double vns   = Vns.get(new NSKey(n, s));
  347.                     final double coef0 = d0s * roaPow[n] * vns * -harmonics.getUnnormalizedCnm(n, 0);
  348.                     final double coef1 = coef0 * Qns[n][s];
  349.                     final double coef2 = coef1 * kns;
  350.                     // dQns/dGamma = Q(n, s + 1) from Equation 3.1-(8)
  351.                     final double dqns  = Qns[n][s + 1];

  352.                     // Compute dU / da :
  353.                     dUda += coef2 * (n + 1) * gs;
  354.                     // Compute dU / dEx
  355.                     dUdk += coef1 * (kns * dGsdk + k * XXX * gs * dkns);
  356.                     // Compute dU / dEy
  357.                     dUdh += coef1 * (kns * dGsdh + h * XXX * gs * dkns);
  358.                     // Compute dU / dAlpha
  359.                     dUdAl += coef2 * dGsdAl;
  360.                     // Compute dU / dBeta
  361.                     dUdBe += coef2 * dGsdBe;
  362.                     // Compute dU / dGamma
  363.                     dUdGa += coef0 * kns * dqns * gs;
  364.                 }
  365.             }
  366.         }

  367.         return new double[] {
  368.             dUda  *  muoa / a,
  369.             dUdk  * -muoa,
  370.             dUdh  * -muoa,
  371.             dUdAl * -muoa,
  372.             dUdBe * -muoa,
  373.             dUdGa * -muoa
  374.         };
  375.     }

  376.     /** Hansen coefficients for zonal contribution to central body force model.
  377.      *  <p>
  378.      *  Hansen coefficients are functions of the eccentricity.
  379.      *  For a given eccentricity, the computed elements are stored in a map.
  380.      *  </p>
  381.      */
  382.     private class HansenZonal {

  383.         /** Map to store every Hansen coefficient computed. */
  384.         private TreeMap<NSKey, Double> coefficients;

  385.         /** Map to store every Hansen coefficient derivative computed. */
  386.         private TreeMap<NSKey, Double> derivatives;

  387.         /** Simple constructor. */
  388.         public HansenZonal() {
  389.             coefficients = new TreeMap<CoefficientsFactory.NSKey, Double>();
  390.             derivatives  = new TreeMap<CoefficientsFactory.NSKey, Double>();
  391.             initialize();
  392.         }

  393.         /** Get the K<sub>0</sub><sup>-n-1,s</sup> coefficient value.
  394.          *  @param mnm1 -n-1 value
  395.          *  @param s s value
  396.          *  @return K<sub>0</sub><sup>-n-1,s</sup>
  397.          */
  398.         public double getValue(final int mnm1, final int s) {
  399.             if (coefficients.containsKey(new NSKey(mnm1, s))) {
  400.                 return coefficients.get(new NSKey(mnm1, s));
  401.             } else {
  402.                 // Compute the K0(-n-1,s) coefficients
  403.                 return computeValue(-mnm1 - 1, s);
  404.             }
  405.         }

  406.         /** Get the dK<sub>0</sub><sup>-n-1,s</sup> / d&chi; coefficient derivative.
  407.          *  @param mnm1 -n-1 value
  408.          *  @param s s value
  409.          *  @return dK<sub>0</sub><sup>-n-1,s</sup> / d&chi;
  410.          */
  411.         public double getDerivative(final int mnm1, final int s) {
  412.             if (derivatives.containsKey(new NSKey(mnm1, s))) {
  413.                 return derivatives.get(new NSKey(mnm1, s));
  414.             } else {
  415.                 // Compute the dK0(-n-1,s) / dX derivative
  416.                 return computeDerivative(-mnm1 - 1, s);
  417.             }
  418.         }

  419.         /** Kernels initialization. */
  420.         private void initialize() {
  421.             // coefficients
  422. //            coefficients.put(new NSKey(0, 0), 1.);
  423.             coefficients.put(new NSKey(-1, 0), 0.);
  424.             coefficients.put(new NSKey(-1, 1), 0.);
  425.             coefficients.put(new NSKey(-2, 0), X);
  426.             coefficients.put(new NSKey(-2, 1), 0.);
  427.             coefficients.put(new NSKey(-3, 0), XXX);
  428.             coefficients.put(new NSKey(-3, 1), 0.5 * XXX);
  429.             // derivatives
  430.             derivatives.put(new NSKey(-1, 0), 0.);
  431.             derivatives.put(new NSKey(-2, 0), 1.);
  432.             derivatives.put(new NSKey(-2, 1), 0.);
  433.             derivatives.put(new NSKey(-3, 1), 1.5 * XX);
  434.         }

  435.         /** Compute the K<sub>0</sub><sup>-n-1,s</sup> coefficient from equation 2.7.3-(6).
  436.          * @param n n  positive value. For a given 'n', the K<sub>0</sub><sup>-n-1,s</sup> is returned.
  437.          * @param s s value
  438.          * @return K<sub>0</sub><sup>-n-1,s</sup>
  439.          */
  440.         private double computeValue(final int n, final int s) {
  441.             // Initialize return value
  442.             double kns = 0.;

  443.             final NSKey key = new NSKey(-n - 1, s);

  444.             if (coefficients.containsKey(key)) {
  445.                 kns = coefficients.get(key);
  446.             } else {
  447.                 if (n == s) {
  448.                     kns = 0.;
  449.                 } else if (n == (s + 1)) {
  450.                     kns = FastMath.pow(X, 1 + 2 * s) / FastMath.pow(2, s);
  451.                 } else {
  452.                     final NSKey keymNS = new NSKey(-n, s);
  453.                     double kmNS = 0.;
  454.                     if (coefficients.containsKey(keymNS)) {
  455.                         kmNS = coefficients.get(keymNS);
  456.                     } else {
  457.                         kmNS = computeValue(n - 1, s);
  458.                     }

  459.                     final NSKey keymNp1S = new NSKey(-(n - 1), s);
  460.                     double kmNp1S = 0.;
  461.                     if (coefficients.containsKey(keymNp1S)) {
  462.                         kmNp1S = coefficients.get(keymNp1S);
  463.                     } else {
  464.                         kmNp1S = computeValue(n - 2, s);
  465.                     }

  466.                     kns = (n - 1.) * XX * ((2. * n - 3.) * kmNS - (n - 2.) * kmNp1S);
  467.                     kns /= (n + s - 1.) * (n - s - 1.);
  468.                 }
  469.                 // Add K(-n-1, s)
  470.                 coefficients.put(key, kns);
  471.             }
  472.             return kns;
  473.         }

  474.         /** Compute dK<sub>0</sub><sup>-n-1,s</sup> / d&chi; from Equation 3.1-(7).
  475.          *  @param n n positive value. For a given 'n', the dK<sub>0</sub><sup>-n-1,s</sup> / d&chi; is returned.
  476.          *  @param s s value
  477.          *  @return dK<sub>0</sub><sup>-n-1,s</sup> / d&chi;
  478.          */
  479.         private double computeDerivative(final int n, final int s) {
  480.             // Initialize return value
  481.             double dkdxns = 0.;

  482.             final NSKey key = new NSKey(-n - 1, s);
  483.             if (n == s) {
  484.                 derivatives.put(key, 0.);
  485.             } else if (n == s + 1) {
  486.                 dkdxns = (1. + 2. * s) * FastMath.pow(X, 2 * s) / FastMath.pow(2, s);
  487.             } else {
  488.                 final NSKey keymN = new NSKey(-n, s);
  489.                 double dkmN = 0.;
  490.                 if (derivatives.containsKey(keymN)) {
  491.                     dkmN = derivatives.get(keymN);
  492.                 } else {
  493.                     dkmN = computeDerivative(n - 1, s);
  494.                 }
  495.                 final NSKey keymNp1 = new NSKey(-n + 1, s);
  496.                 double dkmNp1 = 0.;
  497.                 if (derivatives.containsKey(keymNp1)) {
  498.                     dkmNp1 = derivatives.get(keymNp1);
  499.                 } else {
  500.                     dkmNp1 = computeDerivative(n - 2, s);
  501.                 }
  502.                 final double kns = getValue(-n - 1, s);
  503.                 dkdxns = (n - 1) * XX * ((2. * n - 3.) * dkmN - (n - 2.) * dkmNp1) / ((n + s - 1.) * (n - s - 1.));
  504.                 dkdxns += 2. * kns / X;
  505.             }

  506.             derivatives.put(key, dkdxns);
  507.             return dkdxns;
  508.         }

  509.     }

  510. }