JB2008.java

  1. /* Copyright 2002-2025 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.models.earth.atmosphere;

  18. import org.hipparchus.Field;
  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  21. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  22. import org.hipparchus.util.FastMath;
  23. import org.hipparchus.util.FieldSinCos;
  24. import org.hipparchus.util.MathArrays;
  25. import org.hipparchus.util.MathUtils;
  26. import org.hipparchus.util.SinCos;
  27. import org.orekit.annotation.DefaultDataContext;
  28. import org.orekit.bodies.BodyShape;
  29. import org.orekit.bodies.FieldGeodeticPoint;
  30. import org.orekit.bodies.GeodeticPoint;
  31. import org.orekit.data.DataContext;
  32. import org.orekit.errors.OrekitException;
  33. import org.orekit.errors.OrekitMessages;
  34. import org.orekit.frames.Frame;
  35. import org.orekit.time.AbsoluteDate;
  36. import org.orekit.time.DateTimeComponents;
  37. import org.orekit.time.FieldAbsoluteDate;
  38. import org.orekit.time.TimeScale;
  39. import org.orekit.utils.Constants;
  40. import org.orekit.utils.ExtendedPositionProvider;

  41. /** This is the realization of the Jacchia-Bowman 2008 atmospheric model.
  42.  * <p>
  43.  * It is described in the paper:<br>
  44.  * <a href="https://www.researchgate.net/publication/228621668_A_New_Empirical_Thermospheric_Density_Model_JB2008_Using_New_Solar_and_Geomagnetic_Indices">A
  45.  * New Empirical Thermospheric Density Model JB2008 Using New Solar Indices</a><br>
  46.  * <i>Bruce R. Bowman &amp; al.</i><br>
  47.  * AIAA 2008-6438<br>
  48.  * </p>
  49.  * <p>
  50.  * Two computation methods are proposed to the user:
  51.  * <ul>
  52.  * <li>one OREKIT independent and compliant with initial FORTRAN routine entry values:
  53.  *     {@link #getDensity(double, double, double, double, double, double, double, double, double, double, double, double, double, double, double)}. </li>
  54.  * <li>one compliant with OREKIT Atmosphere interface, necessary to the
  55.  *     {@link org.orekit.forces.drag.DragForce drag force model} computation.</li>
  56.  * </ul>
  57.  * <p>
  58.  * This model provides dense output for all altitudes and positions. Output data are :
  59.  * <ul>
  60.  * <li>Exospheric Temperature above Input Position (deg K)</li>
  61.  * <li>Temperature at Input Position (deg K)</li>
  62.  * <li>Total Mass-Density at Input Position (kg/m³)</li>
  63.  * </ul>
  64.  * <p>
  65.  * The model needs geographical and time information to compute general values,
  66.  * but also needs space weather data : mean and daily solar flux, retrieved through
  67.  * different indices, and planetary geomagnetic indices.<br>
  68.  * More information on these indices can be found on the <a
  69.  * href="http://sol.spacenvironment.net/~JB2008/indices.html">
  70.  * official JB2008 website.</a>
  71.  * </p>
  72.  *
  73.  * @author Bruce R Bowman (HQ AFSPC, Space Analysis Division), 2008: FORTRAN routine
  74.  * @author Pascal Parraud (java translation)
  75.  */
  76. public class JB2008 extends AbstractSunInfluencedAtmosphere {

  77.     /** Minimum altitude (m) for JB2008 use. */
  78.     private static final double ALT_MIN = 90000.;

  79.     /** Earth radius (km). */
  80.     private static final double EARTH_RADIUS = 6356.766;

  81.     /** Natural logarithm of 10.0. */
  82.     private static final double LOG10  = FastMath.log(10.);

  83.     /** Avogadro's number in mks units (molecules/kmol). */
  84.     private static final double AVOGAD = 6.02257e26;

  85.     /** Universal gas-constant in mks units (joules/K/kmol). */
  86.     private static final double RSTAR  = 8.31432;

  87.     /** The alpha are the thermal diffusion coefficients in Equation (6). */
  88.     private static final double[] ALPHA = {
  89.         0, 0, 0, 0, -0.38
  90.     };

  91.     /** Molecular weights in order: N2, O2, O, Ar, He and H. */
  92.     private static final double[] AMW = {
  93.         28.0134, 31.9988, 15.9994, 39.9480, 4.0026, 1.00797
  94.     };

  95.     /** The FRAC are the assumed sea-level volume fractions in order: N2, O2, Ar, and He. */
  96.     private static final double[] FRAC = {
  97.         0.78110, 0.20955, 9.3400e-3, 1.2890e-5
  98.     };

  99.     /** Value used to establish height step sizes in the regime 90km to 105km. */
  100.     private static final double R1 = 0.010;

  101.     /** Value used to establish height step sizes in the regime 105km to 500km. */
  102.     private static final double R2 = 0.025;

  103.     /** Value used to establish height step sizes in the regime above 500km. */
  104.     private static final double R3 = 0.075;

  105.     /** Weights for the Newton-Cotes five-points quadrature formula. */
  106.     private static final double[] WT = {
  107.         0.311111111111111, 1.422222222222222, 0.533333333333333, 1.422222222222222, 0.311111111111111
  108.     };

  109.     /** Coefficients for high altitude density correction. */
  110.     private static final double[] CHT = {
  111.         0.22, -0.20e-02, 0.115e-02, -0.211e-05
  112.     };

  113.     /** FZ global model values (1997-2006 fit).  */
  114.     private static final double[] FZM = {
  115.         0.2689e+00, -0.1176e-01, 0.2782e-01, -0.2782e-01, 0.3470e-03
  116.     };

  117.     /** GT global model values (1997-2006 fit). */
  118.     private static final double[] GTM = {
  119.         -0.3633e+00, 0.8506e-01,  0.2401e+00, -0.1897e+00, -0.2554e+00,
  120.         -0.1790e-01, 0.5650e-03, -0.6407e-03, -0.3418e-02, -0.1252e-02
  121.     };

  122.     /** Mbar polynomial coeffcients. */
  123.     private static final double[] CMB = {
  124.         28.15204, -8.5586e-2, +1.2840e-4, -1.0056e-5, -1.0210e-5, +1.5044e-6, +9.9826e-8
  125.     };

  126.     /** DTC relative data. */
  127.     private static final double[] BDTC = {
  128.         -0.457512297e+01, -0.512114909e+01, -0.693003609e+02,
  129.         0.203716701e+03,  0.703316291e+03, -0.194349234e+04,
  130.         0.110651308e+04, -0.174378996e+03,  0.188594601e+04,
  131.         -0.709371517e+04,  0.922454523e+04, -0.384508073e+04,
  132.         -0.645841789e+01,  0.409703319e+02, -0.482006560e+03,
  133.         0.181870931e+04, -0.237389204e+04,  0.996703815e+03,
  134.         0.361416936e+02
  135.     };

  136.     /** DTC relative data. */
  137.     private static final double[] CDTC = {
  138.         -0.155986211e+02, -0.512114909e+01, -0.693003609e+02,
  139.         0.203716701e+03,  0.703316291e+03, -0.194349234e+04,
  140.         0.110651308e+04, -0.220835117e+03,  0.143256989e+04,
  141.         -0.318481844e+04,  0.328981513e+04, -0.135332119e+04,
  142.         0.199956489e+02, -0.127093998e+02,  0.212825156e+02,
  143.         -0.275555432e+01,  0.110234982e+02,  0.148881951e+03,
  144.         -0.751640284e+03,  0.637876542e+03,  0.127093998e+02,
  145.         -0.212825156e+02,  0.275555432e+01
  146.     };

  147.     /** External data container. */
  148.     private JB2008InputParameters inputParams;

  149.     /** Earth body shape. */
  150.     private final BodyShape earth;

  151.     /** UTC time scale. */
  152.     private final TimeScale utc;

  153.     /** Constructor with space environment information for internal computation.
  154.      *
  155.      * <p>This method uses the {@link DataContext#getDefault() default data context}.
  156.      *
  157.      * @param parameters the solar and magnetic activity data
  158.      * @param sun the sun position
  159.      * @param earth the earth body shape
  160.      * @see #JB2008(JB2008InputParameters, ExtendedPositionProvider, BodyShape, TimeScale)
  161.      */
  162.     @DefaultDataContext
  163.     public JB2008(final JB2008InputParameters parameters,
  164.                   final ExtendedPositionProvider sun, final BodyShape earth) {
  165.         this(parameters, sun, earth, DataContext.getDefault().getTimeScales().getUTC());
  166.     }

  167.     /**
  168.      * Constructor with space environment information for internal computation.
  169.      *
  170.      * @param parameters the solar and magnetic activity data
  171.      * @param sun        the sun position
  172.      * @param earth      the earth body shape
  173.      * @param utc        UTC time scale. Used to computed the day fraction.
  174.      * @since 10.1
  175.      */
  176.     public JB2008(final JB2008InputParameters parameters,
  177.                   final ExtendedPositionProvider sun,
  178.                   final BodyShape earth,
  179.                   final TimeScale utc) {
  180.         super(sun);
  181.         this.earth = earth;
  182.         this.inputParams = parameters;
  183.         this.utc = utc;
  184.     }

  185.     /** {@inheritDoc} */
  186.     public Frame getFrame() {
  187.         return earth.getBodyFrame();
  188.     }

  189.     /** Get the local density with initial entries.
  190.      * @param dateMJD date and time, in modified julian days and fraction
  191.      * @param sunRA Right Ascension of Sun (radians)
  192.      * @param sunDecli Declination of Sun (radians)
  193.      * @param satLon Right Ascension of position (radians)
  194.      * @param satLat Geocentric latitude of position (radians)
  195.      * @param satAlt Height of position (m)
  196.      * @param f10 10.7-cm Solar flux (1e<sup>-22</sup>*Watt/(m²*Hertz))<br>
  197.      *        (Tabular time 1.0 day earlier)
  198.      * @param f10B 10.7-cm Solar Flux, averaged 81-day centered on the input time<br>
  199.      *        (Tabular time 1.0 day earlier)
  200.      * @param s10 EUV index (26-34 nm) scaled to F10<br>
  201.      *        (Tabular time 1 day earlier)
  202.      * @param s10B UV 81-day averaged centered index
  203.      *        (Tabular time 1 day earlier)
  204.      * @param xm10 MG2 index scaled to F10<br>
  205.      *        (Tabular time 2.0 days earlier)
  206.      * @param xm10B MG2 81-day ave. centered index<br>
  207.      *        (Tabular time 2.0 days earlier)
  208.      * @param y10 Solar X-Ray &amp; Lya index scaled to F10<br>
  209.      *        (Tabular time 5.0 days earlier)
  210.      * @param y10B Solar X-Ray &amp; Lya 81-day ave. centered index<br>
  211.      *        (Tabular time 5.0 days earlier)
  212.      * @param dstdtc Temperature change computed from Dst index
  213.      * @return total mass-Density at input position (kg/m³)
  214.      */
  215.     public double getDensity(final double dateMJD, final double sunRA, final double sunDecli,
  216.                              final double satLon, final double satLat, final double satAlt,
  217.                              final double f10, final double f10B, final double s10,
  218.                              final double s10B, final double xm10, final double xm10B,
  219.                              final double y10, final double y10B, final double dstdtc) {

  220.         if (satAlt < ALT_MIN) {
  221.             throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD, satAlt, ALT_MIN);
  222.         }

  223.         final double altKm = satAlt / 1000.0;

  224.         // Equation (14)
  225.         final double fn  = FastMath.min(1.0, FastMath.pow(f10B / 240., 0.25));
  226.         final double fsb = f10B * fn + s10B * (1. - fn);
  227.         final double tsubc = 392.4 + 3.227 * fsb + 0.298 * (f10 - f10B) + 2.259 * (s10 - s10B) +
  228.                              0.312 * (xm10 - xm10B) + 0.178 * (y10 - y10B);

  229.         // Equation (15)
  230.         final double eta   = 0.5 * FastMath.abs(satLat - sunDecli);
  231.         final double theta = 0.5 * FastMath.abs(satLat + sunDecli);

  232.         // Equation (16)
  233.         final double h   = satLon - sunRA;
  234.         final double tau = h - 0.64577182 + 0.10471976 * FastMath.sin(h + 0.75049158);
  235.         double solarTime = FastMath.toDegrees(h + FastMath.PI) / 15.0;
  236.         while (solarTime >= 24) {
  237.             solarTime -= 24.;
  238.         }
  239.         while (solarTime < 0) {
  240.             solarTime += 24.;
  241.         }

  242.         // Equation (17)
  243.         final double cosEta  = FastMath.pow(FastMath.cos(eta), 2.5);
  244.         final double sinTeta = FastMath.pow(FastMath.sin(theta), 2.5);
  245.         final double cosTau  = FastMath.abs(FastMath.cos(0.5 * tau));
  246.         final double df = sinTeta + (cosEta - sinTeta) * cosTau * cosTau * cosTau;
  247.         final double tsubl = tsubc * (1. + 0.31 * df);

  248.         // Compute correction to dTc for local solar time and lat correction
  249.         final double dtclst = dTc(f10, solarTime, satLat, altKm);

  250.         // Compute the local exospheric temperature.
  251.         // Add geomagnetic storm effect from input dTc value
  252.         final double[] temp = new double[2];
  253.         temp[0] = tsubl + dstdtc;
  254.         final double tinf = temp[0] + dtclst;

  255.         // Equation (9)
  256.         final double tsubx = 444.3807 + 0.02385 * tinf - 392.8292 * FastMath.exp(-0.0021357 * tinf);

  257.         // Equation (11)
  258.         final double gsubx = 0.054285714 * (tsubx - 183.);

  259.         // The TC array will be an argument in the call to localTemp,
  260.         // which evaluates Eq. (10) or (13)
  261.         final double[] tc = new double[4];
  262.         tc[0] = tsubx;
  263.         tc[1] = gsubx;
  264.         // A of Equation (13)
  265.         tc[2] = (tinf - tsubx) * 2. / FastMath.PI;
  266.         tc[3] = gsubx / tc[2];

  267.         // Equation (5)
  268.         final double z1 = 90.;
  269.         final double z2 = FastMath.min(altKm, 105.0);
  270.         double al = FastMath.log(z2 / z1);
  271.         int n = 1 + (int) FastMath.floor(al / R1);
  272.         double zr = FastMath.exp(al / n);
  273.         final double mb1 = mBar(z1);
  274.         final double tloc1 = localTemp(z1, tc);
  275.         double zend  = z1;
  276.         double sub2  = 0.;
  277.         double ain   = mb1 * gravity(z1) / tloc1;
  278.         double mb2   = 0;
  279.         double tloc2 = 0;
  280.         double z     = 0;
  281.         double gravl = 0;

  282.         for (int i = 0; i < n; ++i) {
  283.             z = zend;
  284.             zend = zr * z;
  285.             final double dz = 0.25 * (zend - z);
  286.             double sum1 = WT[0] * ain;
  287.             for (int j = 1; j < 5; ++j) {
  288.                 z += dz;
  289.                 mb2   = mBar(z);
  290.                 tloc2 = localTemp(z, tc);
  291.                 gravl = gravity(z);
  292.                 ain   = mb2 * gravl / tloc2;
  293.                 sum1 += WT[j] * ain;
  294.             }
  295.             sub2 += dz * sum1;
  296.         }

  297.         double rho = 3.46e-6 * mb2 * tloc1 / FastMath.exp(sub2 / RSTAR) / (mb1 * tloc2);

  298.         // Equation (2)
  299.         final double anm = AVOGAD * rho;
  300.         final double an  = anm / mb2;

  301.         // Equation (3)
  302.         double fact2  = anm / 28.960;
  303.         final double[] aln = new double[6];
  304.         aln[0] = FastMath.log(FRAC[0] * fact2);
  305.         aln[3] = FastMath.log(FRAC[2] * fact2);
  306.         aln[4] = FastMath.log(FRAC[3] * fact2);
  307.         // Equation (4)
  308.         aln[1] = FastMath.log(fact2 * (1. + FRAC[1]) - an);
  309.         aln[2] = FastMath.log(2. * (an - fact2));

  310.         if (altKm <= 105.0) {
  311.             temp[1] = tloc2;
  312.             // Put in negligible hydrogen for use in DO-LOOP 13
  313.             aln[5] = aln[4] - 25.0;
  314.         } else {
  315.             // Equation (6)
  316.             al   = FastMath.log(FastMath.min(altKm, 500.0) / z);
  317.             n    = 1 + (int) FastMath.floor(al / R2);
  318.             zr   = FastMath.exp(al / n);
  319.             sub2 = 0.;
  320.             ain  = gravl / tloc2;

  321.             double tloc3 = 0;
  322.             for (int i = 0; i < n; ++i) {
  323.                 z = zend;
  324.                 zend = zr * z;
  325.                 final double dz = 0.25 * (zend - z);
  326.                 double sum1 = WT[0] * ain;
  327.                 for (int j = 1; j < 5; ++j) {
  328.                     z += dz;
  329.                     tloc3 = localTemp(z, tc);
  330.                     gravl = gravity(z);
  331.                     ain   = gravl / tloc3;
  332.                     sum1 += WT[j] * ain;
  333.                 }
  334.                 sub2 += dz * sum1;
  335.             }

  336.             al = FastMath.log(FastMath.max(altKm, 500.0) / z);
  337.             final double r = (altKm > 500.0) ? R3 : R2;
  338.             n = 1 + (int) FastMath.floor(al / r);
  339.             zr = FastMath.exp(al / n);
  340.             double sum3 = 0.;
  341.             double tloc4 = 0;
  342.             for (int i = 0; i < n; ++i) {
  343.                 z = zend;
  344.                 zend = zr * z;
  345.                 final double dz = 0.25 * (zend - z);
  346.                 double sum1 = WT[0] * ain;
  347.                 for (int j = 1; j < 5; ++j) {
  348.                     z += dz;
  349.                     tloc4 = localTemp(z, tc);
  350.                     gravl = gravity(z);
  351.                     ain   = gravl / tloc4;
  352.                     sum1 += WT[j] * ain;
  353.                 }
  354.                 sum3 += dz * sum1;
  355.             }
  356.             final double altr;
  357.             final double hSign;
  358.             if (altKm <= 500.) {
  359.                 temp[1] = tloc3;
  360.                 altr = FastMath.log(tloc3 / tloc2);
  361.                 fact2 = sub2 / RSTAR;
  362.                 hSign = 1.0;
  363.             } else {
  364.                 temp[1] = tloc4;
  365.                 altr = FastMath.log(tloc4 / tloc2);
  366.                 fact2 = (sub2 + sum3) / RSTAR;
  367.                 hSign = -1.0;
  368.             }
  369.             for (int i = 0; i < 5; ++i) {
  370.                 aln[i] = aln[i] - (1.0 + ALPHA[i]) * altr - fact2 * AMW[i];
  371.             }

  372.             // Equation (7)
  373.             final double al10t5 = FastMath.log10(tinf);
  374.             final double alnh5 = (5.5 * al10t5 - 39.40) * al10t5 + 73.13;
  375.             aln[5] = LOG10 * (alnh5 + 6.) + hSign * (FastMath.log(tloc4 / tloc3) + sum3 * AMW[5] / RSTAR);
  376.         }

  377.         // Equation (24) - J70 Seasonal-Latitudinal Variation
  378.         final double capPhi = ((dateMJD - 36204.0) / 365.2422) % 1;
  379.         final int signum = (satLat >= 0.) ? 1 : -1;
  380.         final double sinLat = FastMath.sin(satLat);
  381.         final double hm90  = altKm - 90.;
  382.         final double dlrsl = 0.02 * hm90 * FastMath.exp(-0.045 * hm90) *
  383.                              signum * FastMath.sin(MathUtils.TWO_PI * capPhi + 1.72) * sinLat * sinLat;

  384.         // Equation (23) - Computes the semiannual variation
  385.         double dlrsa = 0;
  386.         if (z < 2000.0) {
  387.             // Use new semiannual model dLog(rho)
  388.             dlrsa = semian08(dayOfYear(dateMJD), altKm, f10B, s10B, xm10B);
  389.         }

  390.         // Sum the delta-log-rhos and apply to the number densities.
  391.         // In CIRA72 the following equation contains an actual sum,
  392.         // namely DLR = LOG10 * (DLRGM + DLRSA + DLRSL)
  393.         // However, for Jacchia 70, there is no DLRGM or DLRSA.
  394.         final double dlr = LOG10 * (dlrsl + dlrsa);
  395.         for (int i = 0; i < 6; ++i) {
  396.             aln[i] += dlr;
  397.         }

  398.         // Compute mass-density and mean-molecular-weight and
  399.         // convert number density logs from natural to common.
  400.         double sumnm = 0.0;
  401.         for (int i = 0; i < 6; ++i) {
  402.             sumnm += FastMath.exp(aln[i]) * AMW[i];
  403.         }
  404.         rho = sumnm / AVOGAD;

  405.         // Compute the high altitude exospheric density correction factor
  406.         double fex = 1.0;
  407.         if (altKm >= 1000.0 && altKm < 1500.0) {
  408.             final double zeta = (altKm - 1000.) * 0.002;
  409.             final double f15c = CHT[0] + CHT[1] * f10B + (CHT[2] + CHT[3] * f10B) * 1500.0;
  410.             final double f15cZeta = (CHT[2] + CHT[3] * f10B) * 500.0;
  411.             final double fex2 = 3.0 * f15c - f15cZeta - 3.0;
  412.             final double fex3 = f15cZeta - 2.0 * f15c + 2.0;
  413.             fex += zeta * zeta * (fex2 + fex3 * zeta);
  414.         } else if (altKm >= 1500.0) {
  415.             fex = CHT[0] + CHT[1] * f10B + CHT[2] * altKm + CHT[3] * f10B * altKm;
  416.         }

  417.         // Apply the exospheric density correction factor.
  418.         rho *= fex;

  419.         return rho;
  420.     }

  421.     /** Get the local density with initial entries.
  422.      * @param dateMJD date and time, in modified julian days and fraction
  423.      * @param sunRA Right Ascension of Sun (radians)
  424.      * @param sunDecli Declination of Sun (radians)
  425.      * @param satLon Right Ascension of position (radians)
  426.      * @param satLat Geocentric latitude of position (radians)
  427.      * @param satAlt Height of position (m)
  428.      * @param f10 10.7-cm Solar flux (1e<sup>-22</sup>*Watt/(m²*Hertz))<br>
  429.      *        (Tabular time 1.0 day earlier)
  430.      * @param f10B 10.7-cm Solar Flux, averaged 81-day centered on the input time<br>
  431.      *        (Tabular time 1.0 day earlier)
  432.      * @param s10 EUV index (26-34 nm) scaled to F10<br>
  433.      *        (Tabular time 1 day earlier)
  434.      * @param s10B UV 81-day averaged centered index
  435.      *        (Tabular time 1 day earlier)
  436.      * @param xm10 MG2 index scaled to F10<br>
  437.      *        (Tabular time 2.0 days earlier)
  438.      * @param xm10B MG2 81-day ave. centered index<br>
  439.      *        (Tabular time 2.0 days earlier)
  440.      * @param y10 Solar X-Ray &amp; Lya index scaled to F10<br>
  441.      *        (Tabular time 5.0 days earlier)
  442.      * @param y10B Solar X-Ray &amp; Lya 81-day ave. centered index<br>
  443.      *        (Tabular time 5.0 days earlier)
  444.      * @param dstdtc Temperature change computed from Dst index
  445.      * @param <T> type of the field elements
  446.      * @return total mass-Density at input position (kg/m³)
  447.      */
  448.     public <T extends CalculusFieldElement<T>> T getDensity(final T dateMJD, final T sunRA, final T sunDecli,
  449.                                                         final T satLon, final T satLat, final T satAlt,
  450.                                                         final double f10, final double f10B, final double s10,
  451.                                                         final double s10B, final double xm10, final double xm10B,
  452.                                                         final double y10, final double y10B, final double dstdtc) {

  453.         if (satAlt.getReal() < ALT_MIN) {
  454.             throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD,
  455.                                       satAlt.getReal(), ALT_MIN);
  456.         }

  457.         final Field<T> field  = satAlt.getField();
  458.         final T pi    = field.getOne().getPi();
  459.         final T altKm = satAlt.divide(1000.0);

  460.         // Equation (14)
  461.         final double fn  = FastMath.min(1.0, FastMath.pow(f10B / 240., 0.25));
  462.         final double fsb = f10B * fn + s10B * (1. - fn);
  463.         final double tsubc = 392.4 + 3.227 * fsb + 0.298 * (f10 - f10B) + 2.259 * (s10 - s10B) +
  464.                              0.312 * (xm10 - xm10B) + 0.178 * (y10 - y10B);

  465.         // Equation (15)
  466.         final T eta   = satLat.subtract(sunDecli).abs().multiply(0.5);
  467.         final T theta = satLat.add(sunDecli).abs().multiply(0.5);

  468.         // Equation (16)
  469.         final T h   = satLon.subtract(sunRA);
  470.         final T tau = h.subtract(0.64577182).add(h.add(0.75049158).sin().multiply(0.10471976));
  471.         T solarTime = FastMath.toDegrees(h.add(pi)).divide(15.0);
  472.         while (solarTime.getReal() >= 24) {
  473.             solarTime = solarTime.subtract(24);
  474.         }
  475.         while (solarTime.getReal() < 0) {
  476.             solarTime = solarTime.add(24);
  477.         }

  478.         // Equation (17)
  479.         final T cos     = eta.cos();
  480.         final T cosEta  = cos.square().multiply(cos.sqrt());
  481.         final T sin     = theta.sin();
  482.         final T sinTeta = sin.square().multiply(sin.sqrt());
  483.         final T cosTau  = tau.multiply(0.5).cos().abs();
  484.         final T df      = sinTeta.add(cosEta.subtract(sinTeta).multiply(cosTau).multiply(cosTau).multiply(cosTau));
  485.         final T tsubl   = df.multiply(0.31).add(1).multiply(tsubc);

  486.         // Compute correction to dTc for local solar time and lat correction
  487.         final T dtclst = dTc(f10, solarTime, satLat, altKm);

  488.         // Compute the local exospheric temperature.
  489.         // Add geomagnetic storm effect from input dTc value
  490.         final T[] temp = MathArrays.buildArray(field, 2);
  491.         temp[0] = tsubl.add(dstdtc);
  492.         final T tinf = temp[0].add(dtclst);

  493.         // Equation (9)
  494.         final T tsubx = tinf.multiply(0.02385).add(444.3807).subtract(tinf.multiply(-0.0021357).exp().multiply(392.8292));

  495.         // Equation (11)
  496.         final T gsubx = tsubx.subtract(183.).multiply(0.054285714);

  497.         // The TC array will be an argument in the call to localTemp,
  498.         // which evaluates Eq. (10) or (13)
  499.         final T[] tc = MathArrays.buildArray(field, 4);
  500.         tc[0] = tsubx;
  501.         tc[1] = gsubx;
  502.         // A of Equation (13)
  503.         tc[2] = tinf.subtract(tsubx).multiply(pi.reciprocal().multiply(2.0));
  504.         tc[3] = gsubx.divide(tc[2]);

  505.         // Equation (5)
  506.         final T z1 = field.getZero().newInstance(90.);
  507.         final T z2 = min(105.0, altKm);
  508.         T al = z2.divide(z1).log();
  509.         int n = 1 + (int) FastMath.floor(al.getReal() / R1);
  510.         T zr = al.divide(n).exp();
  511.         final T mb1 = mBar(z1);
  512.         final T tloc1 = localTemp(z1, tc);
  513.         T zend  = z1;
  514.         T sub2  = field.getZero();
  515.         T ain   = mb1.multiply(gravity(z1)).divide(tloc1);
  516.         T mb2   = field.getZero();
  517.         T tloc2 = field.getZero();
  518.         T z     = field.getZero();
  519.         T gravl = field.getZero();
  520.         for (int i = 0; i < n; ++i) {
  521.             z = zend;
  522.             zend = zr.multiply(z);
  523.             final T dz = zend.subtract(z).multiply(0.25);
  524.             T sum1 = ain.multiply(WT[0]);
  525.             for (int j = 1; j < 5; ++j) {
  526.                 z = z.add(dz);
  527.                 mb2   = mBar(z);
  528.                 tloc2 = localTemp(z, tc);
  529.                 gravl = gravity(z);
  530.                 ain   = mb2.multiply(gravl).divide(tloc2);
  531.                 sum1  = sum1.add(ain.multiply(WT[j]));
  532.             }
  533.             sub2 = sub2.add(dz.multiply(sum1));
  534.         }

  535.         T rho = mb2.multiply(3.46e-6).multiply(tloc1).divide(sub2.divide(RSTAR).exp().multiply(mb1.multiply(tloc2)));

  536.         // Equation (2)
  537.         final T anm = rho.multiply(AVOGAD);
  538.         final T an  = anm.divide(mb2);

  539.         // Equation (3)
  540.         T fact2  = anm.divide(28.960);
  541.         final T[] aln = MathArrays.buildArray(field, 6);
  542.         aln[0] = fact2.multiply(FRAC[0]).log();
  543.         aln[3] = fact2.multiply(FRAC[2]).log();
  544.         aln[4] = fact2.multiply(FRAC[3]).log();
  545.         // Equation (4)
  546.         aln[1] = fact2.multiply(1. + FRAC[1]).subtract(an).log();
  547.         aln[2] = an.subtract(fact2).multiply(2).log();

  548.         if (altKm.getReal() <= 105.0) {
  549.             temp[1] = tloc2;
  550.             // Put in negligible hydrogen for use in DO-LOOP 13
  551.             aln[5] = aln[4].subtract(25.0);
  552.         } else {
  553.             // Equation (6)
  554.             al   = min(500.0, altKm).divide(z).log();
  555.             n    = 1 + (int) FastMath.floor(al.getReal() / R2);
  556.             zr   = al.divide(n).exp();
  557.             sub2 = field.getZero();
  558.             ain  = gravl.divide(tloc2);

  559.             T tloc3 = field.getZero();
  560.             for (int i = 0; i < n; ++i) {
  561.                 z = zend;
  562.                 zend = zr.multiply(z);
  563.                 final T dz = zend.subtract(z).multiply(0.25);
  564.                 T sum1 = ain.multiply(WT[0]);
  565.                 for (int j = 1; j < 5; ++j) {
  566.                     z = z.add(dz);
  567.                     tloc3 = localTemp(z, tc);
  568.                     gravl = gravity(z);
  569.                     ain   = gravl.divide(tloc3);
  570.                     sum1  = sum1.add(ain.multiply(WT[j]));
  571.                 }
  572.                 sub2 = sub2.add(dz.multiply(sum1));
  573.             }

  574.             al = max(500.0, altKm).divide(z).log();
  575.             final double r = (altKm.getReal() > 500.0) ? R3 : R2;
  576.             n = 1 + (int) FastMath.floor(al.getReal() / r);
  577.             zr = al.divide(n).exp();
  578.             T sum3 = field.getZero();
  579.             T tloc4 = field.getZero();
  580.             for (int i = 0; i < n; ++i) {
  581.                 z = zend;
  582.                 zend = zr.multiply(z);
  583.                 final T dz = zend.subtract(z).multiply(0.25);
  584.                 T sum1 = ain.multiply(WT[0]);
  585.                 for (int j = 1; j < 5; ++j) {
  586.                     z = z.add(dz);
  587.                     tloc4 = localTemp(z, tc);
  588.                     gravl = gravity(z);
  589.                     ain   = gravl.divide(tloc4);
  590.                     sum1  = sum1.add(ain.multiply(WT[j]));
  591.                 }
  592.                 sum3 = sum3.add(dz.multiply(sum1));
  593.             }
  594.             final T altr;
  595.             final double hSign;
  596.             if (altKm.getReal() <= 500.) {
  597.                 temp[1] = tloc3;
  598.                 altr = tloc3.divide(tloc2).log();
  599.                 fact2 = sub2.divide(RSTAR);
  600.                 hSign = 1.0;
  601.             } else {
  602.                 temp[1] = tloc4;
  603.                 altr = tloc4.divide(tloc2).log();
  604.                 fact2 = sub2.add(sum3).divide(RSTAR);
  605.                 hSign = -1.0;
  606.             }
  607.             for (int i = 0; i < 5; ++i) {
  608.                 aln[i] = aln[i].subtract(altr.multiply(1.0 + ALPHA[i])).subtract(fact2.multiply(AMW[i]));
  609.             }

  610.             // Equation (7)
  611.             final T al10t5 = tinf.log10();
  612.             final T alnh5 = al10t5.multiply(5.5).subtract(39.40).multiply(al10t5).add(73.13);
  613.             aln[5] = alnh5.add(6.).multiply(LOG10).
  614.                      add(tloc4.divide(tloc3).log().add(sum3.multiply(AMW[5] / RSTAR)).multiply(hSign));
  615.         }

  616.         // Equation (24) - J70 Seasonal-Latitudinal Variation
  617.         T capPhi = dateMJD.subtract(36204.0).divide(365.2422);
  618.         capPhi = capPhi.subtract(FastMath.floor(capPhi.getReal()));
  619.         final int signum = (satLat.getReal() >= 0.) ? 1 : -1;
  620.         final T sinLat = satLat.sin();
  621.         final T hm90  = altKm.subtract(90.);
  622.         final T dlrsl = hm90.multiply(0.02).multiply(hm90.multiply(-0.045).exp()).
  623.                         multiply(capPhi.multiply(MathUtils.TWO_PI).add(1.72).sin()).
  624.                         multiply(signum).multiply(sinLat).multiply(sinLat);

  625.         // Equation (23) - Computes the semiannual variation
  626.         T dlrsa = field.getZero();
  627.         if (z.getReal() < 2000.0) {
  628.             // Use new semiannual model dLog(rho)
  629.             dlrsa = semian08(dayOfYear(dateMJD), altKm, f10B, s10B, xm10B);
  630.         }

  631.         // Sum the delta-log-rhos and apply to the number densities.
  632.         // In CIRA72 the following equation contains an actual sum,
  633.         // namely DLR = LOG10 * (DLRGM + DLRSA + DLRSL)
  634.         // However, for Jacchia 70, there is no DLRGM or DLRSA.
  635.         final T dlr = dlrsl.add(dlrsa).multiply(LOG10);
  636.         for (int i = 0; i < 6; ++i) {
  637.             aln[i] = aln[i].add(dlr);
  638.         }

  639.         // Compute mass-density and mean-molecular-weight and
  640.         // convert number density logs from natural to common.
  641.         T sumnm = field.getZero();
  642.         for (int i = 0; i < 6; ++i) {
  643.             sumnm = sumnm.add(aln[i].exp().multiply(AMW[i]));
  644.         }
  645.         rho = sumnm.divide(AVOGAD);

  646.         // Compute the high altitude exospheric density correction factor
  647.         T fex = field.getOne();
  648.         if (altKm.getReal() >= 1000.0 && altKm.getReal() < 1500.0) {
  649.             final T zeta = altKm.subtract(1000.).multiply(0.002);
  650.             final double f15c     = CHT[0] + CHT[1] * f10B + (CHT[2] + CHT[3] * f10B) * 1500.0;
  651.             final double f15cZeta = (CHT[2] + CHT[3] * f10B) * 500.0;
  652.             final double fex2     = 3.0 * f15c - f15cZeta - 3.0;
  653.             final double fex3     = f15cZeta - 2.0 * f15c + 2.0;
  654.             fex = fex.add(zeta.multiply(zeta).multiply(zeta.multiply(fex3).add(fex2)));
  655.         } else if (altKm.getReal() >= 1500.0) {
  656.             fex = altKm.multiply(CHT[3] * f10B).add(altKm.multiply(CHT[2])).add(CHT[0] + CHT[1] * f10B);
  657.         }

  658.         // Apply the exospheric density correction factor.
  659.         rho = rho.multiply(fex);

  660.         return rho;
  661.     }

  662.     /** Compute daily temperature correction for Jacchia-Bowman model.
  663.      * @param f10 solar flux index
  664.      * @param solarTime local solar time (hours in [0, 24[)
  665.      * @param satLat sat lat (radians)
  666.      * @param satAlt height (km)
  667.      * @return dTc correction
  668.      */
  669.     private static double dTc(final double f10, final double solarTime,
  670.                               final double satLat, final double satAlt) {
  671.         double dTc = 0.;
  672.         final double st = solarTime / 24.0;
  673.         final double cs = FastMath.cos(satLat);
  674.         final double fs = (f10 - 100.0) / 100.0;

  675.         // Calculates dTc according to height
  676.         if (satAlt >= 120 && satAlt <= 200) {
  677.             final double dtc200 = poly2CDTC(fs, st, cs);
  678.             final double dtc200dz = poly1CDTC(fs, st, cs);
  679.             final double cc = 3.0 * dtc200 - dtc200dz;
  680.             final double dd = dtc200 - cc;
  681.             final double zp = (satAlt - 120.0) / 80.0;
  682.             dTc = zp * zp * (cc + dd * zp);
  683.         } else if (satAlt > 200.0 && satAlt <= 240.0) {
  684.             final double h = (satAlt - 200.0) / 50.0;
  685.             dTc = poly1CDTC(fs, st, cs) * h + poly2CDTC(fs, st, cs);
  686.         } else if (satAlt > 240.0 && satAlt <= 300.0) {
  687.             final double h = 0.8;
  688.             final double bb = poly1CDTC(fs, st, cs);
  689.             final double aa = bb * h + poly2CDTC(fs, st, cs);
  690.             final double p2BDT = poly2BDTC(st);
  691.             final double dtc300 = poly1BDTC(fs, st, cs, 3 * p2BDT);
  692.             final double dtc300dz = cs * p2BDT;
  693.             final double cc = 3.0 * dtc300 - dtc300dz - 3.0 * aa - 2.0 * bb;
  694.             final double dd = dtc300 - aa - bb - cc;
  695.             final double zp = (satAlt - 240.0) / 60.0;
  696.             dTc = aa + zp * (bb + zp * (cc + zp * dd));
  697.         } else if (satAlt > 300.0 && satAlt <= 600.0) {
  698.             final double h = satAlt / 100.0;
  699.             dTc = poly1BDTC(fs, st, cs, h * poly2BDTC(st));
  700.         } else if (satAlt > 600.0 && satAlt <= 800.0) {
  701.             final double poly2 = poly2BDTC(st);
  702.             final double aa = poly1BDTC(fs, st, cs, 6 * poly2);
  703.             final double bb = cs * poly2;
  704.             final double cc = -(3.0 * aa + 4.0 * bb) / 4.0;
  705.             final double dd = (aa + bb) / 4.0;
  706.             final double zp = (satAlt - 600.0) / 100.0;
  707.             dTc = aa + zp * (bb + zp * (cc + zp * dd));
  708.         }

  709.         return dTc;
  710.     }

  711.     /** Compute daily temperature correction for Jacchia-Bowman model.
  712.      * @param f10 solar flux index
  713.      * @param solarTime local solar time (hours in [0, 24[)
  714.      * @param satLat sat lat (radians)
  715.      * @param satAlt height (km)
  716.      * @param <T> type of the filed elements
  717.      * @return dTc correction
  718.      */
  719.     private static <T extends CalculusFieldElement<T>> T dTc(final double f10, final T solarTime,
  720.                                                          final T satLat, final T satAlt) {
  721.         T dTc = solarTime.getField().getZero();
  722.         final T      st = solarTime.divide(24.0);
  723.         final T      cs = satLat.cos();
  724.         final double fs = (f10 - 100.0) / 100.0;

  725.         // Calculates dTc according to height
  726.         if (satAlt.getReal() >= 120 && satAlt.getReal() <= 200) {
  727.             final T dtc200   = poly2CDTC(fs, st, cs);
  728.             final T dtc200dz = poly1CDTC(fs, st, cs);
  729.             final T cc       = dtc200.multiply(3).subtract(dtc200dz);
  730.             final T dd       = dtc200.subtract(cc);
  731.             final T zp       = satAlt.subtract(120.0).divide(80.0);
  732.             dTc = zp.multiply(zp).multiply(cc.add(dd.multiply(zp)));
  733.         } else if (satAlt.getReal() > 200.0 && satAlt.getReal() <= 240.0) {
  734.             final T h = satAlt.subtract(200.0).divide(50.0);
  735.             dTc = poly1CDTC(fs, st, cs).multiply(h).add(poly2CDTC(fs, st, cs));
  736.         } else if (satAlt.getReal() > 240.0 && satAlt.getReal() <= 300.0) {
  737.             final T h = solarTime.getField().getZero().newInstance(0.8);
  738.             final T bb = poly1CDTC(fs, st, cs);
  739.             final T aa = bb.multiply(h).add(poly2CDTC(fs, st, cs));
  740.             final T p2BDT = poly2BDTC(st);
  741.             final T dtc300 = poly1BDTC(fs, st, cs, p2BDT.multiply(3));
  742.             final T dtc300dz = cs.multiply(p2BDT);
  743.             final T cc = dtc300.multiply(3).subtract(dtc300dz).subtract(aa.multiply(3)).subtract(bb.multiply(2));
  744.             final T dd = dtc300.subtract(aa).subtract(bb).subtract(cc);
  745.             final T zp = satAlt.subtract(240.0).divide(60.0);
  746.             dTc = aa.add(zp.multiply(bb.add(zp.multiply(cc.add(zp.multiply(dd))))));
  747.         } else if (satAlt.getReal() > 300.0 && satAlt.getReal() <= 600.0) {
  748.             final T h = satAlt.divide(100.0);
  749.             dTc = poly1BDTC(fs, st, cs, h.multiply(poly2BDTC(st)));
  750.         } else if (satAlt.getReal() > 600.0 && satAlt.getReal() <= 800.0) {
  751.             final T poly2 = poly2BDTC(st);
  752.             final T aa = poly1BDTC(fs, st, cs, poly2.multiply(6));
  753.             final T bb = cs.multiply(poly2);
  754.             final T cc = aa.multiply(3).add(bb.multiply(4)).divide(-4.0);
  755.             final T dd = aa.add(bb).divide(4.0);
  756.             final T zp = satAlt.subtract(600.0).divide(100.0);
  757.             dTc = aa.add(zp.multiply(bb.add(zp.multiply(cc.add(zp.multiply(dd))))));
  758.         }

  759.         return dTc;
  760.     }

  761.     /** Calculates first polynomial with CDTC array.
  762.      * @param fs scaled flux f10
  763.      * @param st local solar time in [0, 1[
  764.      * @param cs cosine of satLat
  765.      * @return the value of the polynomial
  766.      */
  767.     private static double poly1CDTC(final double fs, final double st, final double cs) {
  768.         return CDTC[0] +
  769.                 fs * (CDTC[1] + st * (CDTC[2] + st * (CDTC[3] + st * (CDTC[4] + st * (CDTC[5] + st * CDTC[6]))))) +
  770.                 cs * st * (CDTC[7] + st * (CDTC[8] + st * (CDTC[9] + st * (CDTC[10] + st * CDTC[11])))) +
  771.                 cs * (CDTC[12] + fs * (CDTC[13] + st * (CDTC[14] + st * CDTC[15])));
  772.     }

  773.     /** Calculates first polynomial with CDTC array.
  774.      * @param fs scaled flux f10
  775.      * @param st local solar time in [0, 1[
  776.      * @param cs cosine of satLat
  777.      * @param <T> type of the field elements
  778.      * @return the value of the polynomial
  779.      */
  780.     private static <T extends CalculusFieldElement<T>>  T poly1CDTC(final double fs, final T st, final T cs) {
  781.         return    st.multiply(CDTC[6]).
  782.               add(CDTC[5]).multiply(st).
  783.               add(CDTC[4]).multiply(st).
  784.               add(CDTC[3]).multiply(st).
  785.               add(CDTC[2]).multiply(st).
  786.               add(CDTC[1]).multiply(fs).
  787.               add(st.multiply(CDTC[11]).
  788.                   add(CDTC[10]).multiply(st).
  789.                   add(CDTC[ 9]).multiply(st).
  790.                   add(CDTC[ 8]).multiply(st).
  791.                   add(CDTC[7]).multiply(st).multiply(cs)).
  792.               add(st.multiply(CDTC[15]).
  793.                   add(CDTC[14]).multiply(st).
  794.                   add(CDTC[13]).multiply(fs).
  795.                   add(CDTC[12]).multiply(cs)).
  796.               add(CDTC[0]);
  797.     }

  798.     /** Calculates second polynomial with CDTC array.
  799.      * @param fs scaled flux f10
  800.      * @param st local solar time in [0, 1[
  801.      * @param cs cosine of satLat
  802.      * @return the value of the polynomial
  803.      */
  804.     private static double poly2CDTC(final double fs, final double st, final double cs) {
  805.         return CDTC[16] + st * cs * (CDTC[17] + st * (CDTC[18] + st * CDTC[19])) +
  806.                           fs * cs * (CDTC[20] + st * (CDTC[21] + st * CDTC[22]));
  807.     }

  808.     /** Calculates second polynomial with CDTC array.
  809.      * @param fs scaled flux f10
  810.      * @param st local solar time in [0, 1[
  811.      * @param cs cosine of satLat
  812.      * @param <T> type of the field elements
  813.      * @return the value of the polynomial
  814.      */
  815.     private static <T extends CalculusFieldElement<T>>  T poly2CDTC(final double fs, final T st, final T cs) {
  816.         return         st.multiply(CDTC[19]).
  817.                    add(CDTC[18]).multiply(st).
  818.                    add(CDTC[17]).multiply(cs).multiply(st).
  819.                add(    st.multiply(CDTC[22]).
  820.                    add(CDTC[21]).multiply(st).
  821.                    add(CDTC[20]).multiply(cs).multiply(fs)).
  822.                add(CDTC[16]);
  823.     }

  824.     /** Calculates first polynomial with BDTC array.
  825.      * @param fs scaled flux f10
  826.      * @param st local solar time in [0, 1[
  827.      * @param cs cosine of satLat
  828.      * @param hp scaled height * poly2BDTC
  829.      * @return the value of the polynomial
  830.      */
  831.     private static double poly1BDTC(final double fs, final double st, final double cs, final double hp) {
  832.         return BDTC[0] +
  833.                 fs * (BDTC[1] + st * (BDTC[2] + st * (BDTC[3] + st * (BDTC[4] + st * (BDTC[5] + st * BDTC[6]))))) +
  834.                 cs * (st * (BDTC[7] + st * (BDTC[8] + st * (BDTC[9] + st * (BDTC[10] + st * BDTC[11])))) + hp + BDTC[18]);
  835.     }

  836.     /** Calculates first polynomial with BDTC array.
  837.      * @param fs scaled flux f10
  838.      * @param st local solar time in [0, 1[
  839.      * @param cs cosine of satLat
  840.      * @param hp scaled height * poly2BDTC
  841.      * @param <T> type of the field elements
  842.      * @return the value of the polynomial
  843.      */
  844.     private static <T extends CalculusFieldElement<T>>  T poly1BDTC(final double fs, final T st, final T cs, final T hp) {
  845.         return     st.multiply(BDTC[6]).
  846.                add(BDTC[5]).multiply(st).
  847.                add(BDTC[4]).multiply(st).
  848.                add(BDTC[3]).multiply(st).
  849.                add(BDTC[2]).multiply(st).
  850.                add(BDTC[1]).multiply(fs).
  851.                add(    st.multiply(BDTC[11]).
  852.                    add(BDTC[10]).multiply(st).
  853.                    add(BDTC[ 9]).multiply(st).
  854.                    add(BDTC[ 8]).multiply(st).
  855.                    add(BDTC[ 7]).multiply(st).
  856.                    add(hp).add(BDTC[18]).multiply(cs)).
  857.                add(BDTC[0]);
  858.     }

  859.     /** Calculates second polynomial with BDTC array.
  860.      * @param st local solar time in [0, 1[
  861.      * @return the value of the polynomial
  862.      */
  863.     private static double poly2BDTC(final double st) {
  864.         return BDTC[12] + st * (BDTC[13] + st * (BDTC[14] + st * (BDTC[15] + st * (BDTC[16] + st * BDTC[17]))));
  865.     }

  866.     /** Calculates second polynomial with BDTC array.
  867.      * @param st local solar time in [0, 1[
  868.      * @param <T> type of the field elements
  869.      * @return the value of the polynomial
  870.      */
  871.     private static <T extends CalculusFieldElement<T>>  T poly2BDTC(final T st) {
  872.         return     st.multiply(BDTC[17]).
  873.                add(BDTC[16]).multiply(st).
  874.                add(BDTC[15]).multiply(st).
  875.                add(BDTC[14]).multiply(st).
  876.                add(BDTC[13]).multiply(st).
  877.                add(BDTC[12]);
  878.     }

  879.     /** Evaluates mean molecualr mass - Equation (1).
  880.      * @param z altitude (km)
  881.      * @return mean molecular mass
  882.      */
  883.     private static double mBar(final double z) {
  884.         final double dz = z - 100.;
  885.         double amb = CMB[6];
  886.         for (int i = 5; i >= 0; --i) {
  887.             amb = dz * amb + CMB[i];
  888.         }
  889.         return amb;
  890.     }

  891.     /** Evaluates mean molecualr mass - Equation (1).
  892.      * @param z altitude (km)
  893.      * @return mean molecular mass
  894.      * @param <T> type of the field elements
  895.      */
  896.     private static <T extends CalculusFieldElement<T>>  T mBar(final T z) {
  897.         final T dz = z.subtract(100.);
  898.         T amb = z.getField().getZero().newInstance(CMB[6]);
  899.         for (int i = 5; i >= 0; --i) {
  900.             amb = dz.multiply(amb).add(CMB[i]);
  901.         }
  902.         return amb;
  903.     }

  904.     /** Evaluates the local temperature, Eq. (10) or (13) depending on altitude.
  905.      * @param z altitude
  906.      * @param tc tc array
  907.      * @return temperature profile
  908.      */
  909.     private static double localTemp(final double z, final double[] tc) {
  910.         final double dz = z - 125.;
  911.         if (dz <= 0.) {
  912.             return ((-9.8204695e-6 * dz - 7.3039742e-4) * dz * dz + 1.0) * dz * tc[1] + tc[0];
  913.         } else {
  914.             return tc[0] + tc[2] * FastMath.atan(tc[3] * dz * (1 + 4.5e-6 * FastMath.pow(dz, 2.5)));
  915.         }
  916.     }

  917.     /** Evaluates the local temperature, Eq. (10) or (13) depending on altitude.
  918.      * @param z altitude
  919.      * @param tc tc array
  920.      * @return temperature profile
  921.      * @param <T> type of the field elements
  922.      */
  923.     private static <T extends CalculusFieldElement<T>>  T localTemp(final T z, final T[] tc) {
  924.         final T dz = z.subtract(125.);
  925.         if (dz.getReal() <= 0.) {
  926.             return dz.multiply(-9.8204695e-6).subtract(7.3039742e-4).multiply(dz).multiply(dz).add(1.0).multiply(dz).multiply(tc[1]).add(tc[0]);
  927.         } else {
  928.             return dz.multiply(dz).multiply(dz.sqrt()).multiply(4.5e-6).add(1).multiply(dz).multiply(tc[3]).atan().multiply(tc[2]).add(tc[0]);
  929.         }
  930.     }

  931.     /** Evaluates the gravity at the altitude - Equation (8).
  932.      * @param z altitude (km)
  933.      * @return the gravity (m/s2)
  934.      */
  935.     private static double gravity(final double z) {
  936.         final double tmp = 1.0 + z / EARTH_RADIUS;
  937.         return Constants.G0_STANDARD_GRAVITY / (tmp * tmp);
  938.     }

  939.     /** Evaluates the gravity at the altitude - Equation (8).
  940.      * @param z altitude (km)
  941.      * @return the gravity (m/s2)
  942.      * @param <T> type of the field elements
  943.      */
  944.     private static <T extends CalculusFieldElement<T>>  T gravity(final T z) {
  945.         final T tmp = z.divide(EARTH_RADIUS).add(1);
  946.         return tmp.multiply(tmp).reciprocal().multiply(Constants.G0_STANDARD_GRAVITY);
  947.     }

  948.     /** Compute semi-annual variation (delta log(rho)).
  949.      * @param doy day of year
  950.      * @param alt height (km)
  951.      * @param f10B average 81-day centered f10
  952.      * @param s10B average 81-day centered s10
  953.      * @param xm10B average 81-day centered xn10
  954.      * @return semi-annual variation
  955.      */
  956.     private static double semian08(final double doy, final double alt,
  957.                                    final double f10B, final double s10B, final double xm10B) {

  958.         final double htz = alt / 1000.0;

  959.         // COMPUTE NEW 81-DAY CENTERED SOLAR INDEX FOR FZ
  960.         double fsmb = f10B - 0.70 * s10B - 0.04 * xm10B;

  961.         // SEMIANNUAL AMPLITUDE
  962.         final double fzz = FZM[0] + fsmb * (FZM[1] + htz * (FZM[2] + FZM[3] * htz + FZM[4] * fsmb));

  963.         // COMPUTE DAILY 81-DAY CENTERED SOLAR INDEX FOR GT
  964.         fsmb  = f10B - 0.75 * s10B - 0.37 * xm10B;

  965.         // SEMIANNUAL PHASE FUNCTION
  966.         final double tau   = MathUtils.TWO_PI * (doy - 1.0) / 365;
  967.         final SinCos sc1P = FastMath.sinCos(tau);
  968.         final SinCos sc2P = SinCos.sum(sc1P, sc1P);
  969.         final double gtz = GTM[0] + GTM[1] * sc1P.sin() + GTM[2] * sc1P.cos() + GTM[3] * sc2P.sin() + GTM[4] * sc2P.cos() +
  970.                    fsmb * (GTM[5] + GTM[6] * sc1P.sin() + GTM[7] * sc1P.cos() + GTM[8] * sc2P.sin() + GTM[9] * sc2P.cos());

  971.         return FastMath.max(1.0e-6, fzz) * gtz;

  972.     }

  973.     /** Compute semi-annual variation (delta log(rho)).
  974.      * @param doy day of year
  975.      * @param alt height (km)
  976.      * @param f10B average 81-day centered f10
  977.      * @param s10B average 81-day centered s10
  978.      * @param xm10B average 81-day centered xn10
  979.      * @return semi-annual variation
  980.      * @param <T> type of the field elements
  981.      */
  982.     private static <T extends CalculusFieldElement<T>>  T semian08(final T doy, final T alt,
  983.                                                                final double f10B, final double s10B, final double xm10B) {

  984.         final T htz = alt.divide(1000.0);

  985.         // COMPUTE NEW 81-DAY CENTERED SOLAR INDEX FOR FZ
  986.         double fsmb = f10B - 0.70 * s10B - 0.04 * xm10B;

  987.         // SEMIANNUAL AMPLITUDE
  988.         final T fzz = htz.multiply(FZM[3]).add(FZM[2] + FZM[4] * fsmb).multiply(htz).add(FZM[1]).multiply(fsmb).add(FZM[0]);

  989.         // COMPUTE DAILY 81-DAY CENTERED SOLAR INDEX FOR GT
  990.         fsmb  = f10B - 0.75 * s10B - 0.37 * xm10B;

  991.         // SEMIANNUAL PHASE FUNCTION
  992.         final T tau   = doy.subtract(1).divide(365).multiply(MathUtils.TWO_PI);
  993.         final FieldSinCos<T> sc1P = FastMath.sinCos(tau);
  994.         final FieldSinCos<T> sc2P = FieldSinCos.sum(sc1P, sc1P);
  995.         final T gtz =           sc2P.cos().multiply(GTM[9]).
  996.                             add(sc2P.sin().multiply(GTM[8])).
  997.                             add(sc1P.cos().multiply(GTM[7])).
  998.                             add(sc1P.sin().multiply(GTM[6])).
  999.                             add(GTM[5]).multiply(fsmb).
  1000.                         add(    sc2P.cos().multiply(GTM[4]).
  1001.                             add(sc2P.sin().multiply(GTM[3])).
  1002.                             add(sc1P.cos().multiply(GTM[2])).
  1003.                             add(sc1P.sin().multiply(GTM[1])).
  1004.                             add(GTM[0]));

  1005.         return fzz.getReal() > 1.0e-6 ? gtz.multiply(fzz) : gtz.multiply(1.0e-6);

  1006.     }

  1007.     /** Compute day of year.
  1008.      * @param dateMJD Modified Julian date
  1009.      * @return the number days in year
  1010.      */
  1011.     private static double dayOfYear(final double dateMJD) {
  1012.         final double d1950 = dateMJD - 33281;

  1013.         int iyday = (int) d1950;
  1014.         final double frac = d1950 - iyday;
  1015.         iyday = iyday + 364;

  1016.         int itemp = iyday / 1461;

  1017.         iyday = iyday - itemp * 1461;
  1018.         itemp = iyday / 365;
  1019.         if (itemp >= 3) {
  1020.             itemp = 3;
  1021.         }
  1022.         iyday = iyday - 365 * itemp + 1;
  1023.         return iyday + frac;
  1024.     }

  1025.     /** Compute day of year.
  1026.      * @param dateMJD Modified Julian date
  1027.      * @param <T> type of the field elements
  1028.      * @return the number days in year
  1029.      */
  1030.     private static <T extends CalculusFieldElement<T>> T dayOfYear(final T dateMJD) {
  1031.         final T d1950 = dateMJD.subtract(33281);

  1032.         int iyday = (int) d1950.getReal();
  1033.         final T frac = d1950.subtract(iyday);
  1034.         iyday = iyday + 364;

  1035.         int itemp = iyday / 1461;

  1036.         iyday = iyday - itemp * 1461;
  1037.         itemp = iyday / 365;
  1038.         if (itemp >= 3) {
  1039.             itemp = 3;
  1040.         }
  1041.         iyday = iyday - 365 * itemp + 1;
  1042.         return frac.add(iyday);
  1043.     }

  1044.     // OUTPUT:

  1045.     /** Compute min of two values, one double and one field element.
  1046.      * @param d double value
  1047.      * @param f field element
  1048.      * @param <T> type of the field elements
  1049.      * @return min value
  1050.      */
  1051.     private <T extends CalculusFieldElement<T>> T min(final double d, final T f) {
  1052.         return (f.getReal() > d) ? f.getField().getZero().newInstance(d) : f;
  1053.     }

  1054.     /** Compute max of two values, one double and one field element.
  1055.      * @param d double value
  1056.      * @param f field element
  1057.      * @param <T> type of the field elements
  1058.      * @return max value
  1059.      */
  1060.     private <T extends CalculusFieldElement<T>> T max(final double d, final T f) {
  1061.         return (f.getReal() <= d) ? f.getField().getZero().newInstance(d) : f;
  1062.     }

  1063.     /** Get the local density.
  1064.      * @param date current date
  1065.      * @param position current position in frame
  1066.      * @param frame the frame in which is defined the position
  1067.      * @return local density (kg/m³)
  1068.      */
  1069.     public double getDensity(final AbsoluteDate date, final Vector3D position,
  1070.                              final Frame frame) {
  1071.         // check if data are available :
  1072.         if (date.compareTo(inputParams.getMaxDate()) > 0 ||
  1073.             date.compareTo(inputParams.getMinDate()) < 0) {
  1074.             throw new OrekitException(OrekitMessages.NO_SOLAR_ACTIVITY_AT_DATE,
  1075.                                       date, inputParams.getMinDate(), inputParams.getMaxDate());
  1076.         }

  1077.         // compute MJD date
  1078.         final DateTimeComponents dt = date.getComponents(utc);
  1079.         final double dateMJD = dt.getDate().getMJD() +
  1080.                 dt.getTime().getSecondsInLocalDay() / Constants.JULIAN_DAY;

  1081.         // compute geodetic position
  1082.         final GeodeticPoint inBody = earth.transform(position, frame, date);

  1083.         // compute sun position
  1084.         final Frame ecef = earth.getBodyFrame();
  1085.         final Vector3D sunPos = getSunPosition(date, ecef);
  1086.         final GeodeticPoint sunInBody = earth.transform(sunPos, ecef, date);

  1087.         return getDensity(dateMJD,
  1088.                           sunInBody.getLongitude(), sunInBody.getLatitude(),
  1089.                           inBody.getLongitude(), inBody.getLatitude(), inBody.getAltitude(),
  1090.                           inputParams.getF10(date), inputParams.getF10B(date),
  1091.                           inputParams.getS10(date), inputParams.getS10B(date),
  1092.                           inputParams.getXM10(date), inputParams.getXM10B(date),
  1093.                           inputParams.getY10(date), inputParams.getY10B(date),
  1094.                           inputParams.getDSTDTC(date));

  1095.     }

  1096.     /** {@inheritDoc} */
  1097.     @Override
  1098.     public <T extends CalculusFieldElement<T>> T getDensity(final FieldAbsoluteDate<T> date,
  1099.                                                         final FieldVector3D<T> position,
  1100.                                                         final Frame frame) {

  1101.         // check if data are available :
  1102.         final AbsoluteDate dateD = date.toAbsoluteDate();
  1103.         if (dateD.compareTo(inputParams.getMaxDate()) > 0 ||
  1104.                         dateD.compareTo(inputParams.getMinDate()) < 0) {
  1105.             throw new OrekitException(OrekitMessages.NO_SOLAR_ACTIVITY_AT_DATE,
  1106.                                       dateD, inputParams.getMinDate(), inputParams.getMaxDate());
  1107.         }

  1108.         // compute MJD date
  1109.         final DateTimeComponents components = date.getComponents(utc);
  1110.         final T dateMJD = date
  1111.                 .durationFrom(new FieldAbsoluteDate<>(date.getField(), components, utc))
  1112.                 .add(components.getTime().getSecondsInLocalDay())
  1113.                 .divide(Constants.JULIAN_DAY)
  1114.                 .add(components.getDate().getMJD());

  1115.         // compute geodetic position (km and °)
  1116.         final FieldGeodeticPoint<T> inBody = earth.transform(position, frame, date);

  1117.         // compute sun position
  1118.         final Frame ecef = earth.getBodyFrame();
  1119.         final FieldVector3D<T> sunPos = getSunPosition(date, ecef);
  1120.         final FieldGeodeticPoint<T> sunInBody = earth.transform(sunPos, ecef, date);

  1121.         return getDensity(dateMJD,
  1122.                           sunInBody.getLongitude(), sunInBody.getLatitude(),
  1123.                           inBody.getLongitude(), inBody.getLatitude(), inBody.getAltitude(),
  1124.                           inputParams.getF10(dateD), inputParams.getF10B(dateD),
  1125.                           inputParams.getS10(dateD), inputParams.getS10B(dateD),
  1126.                           inputParams.getXM10(dateD), inputParams.getXM10B(dateD),
  1127.                           inputParams.getY10(dateD), inputParams.getY10B(dateD),
  1128.                           inputParams.getDSTDTC(dateD));

  1129.     }

  1130. }