JB2008.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.forces.drag.atmosphere;

  18. import org.hipparchus.Field;
  19. import org.hipparchus.RealFieldElement;
  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.MathArrays;
  24. import org.hipparchus.util.MathUtils;
  25. import org.orekit.bodies.BodyShape;
  26. import org.orekit.bodies.FieldGeodeticPoint;
  27. import org.orekit.bodies.GeodeticPoint;
  28. import org.orekit.errors.OrekitException;
  29. import org.orekit.errors.OrekitMessages;
  30. import org.orekit.frames.Frame;
  31. import org.orekit.time.AbsoluteDate;
  32. import org.orekit.time.FieldAbsoluteDate;
  33. import org.orekit.utils.Constants;
  34. import org.orekit.utils.PVCoordinatesProvider;

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

  73.     /** Serializable UID. */
  74.     private static final long serialVersionUID = -4201270765122160831L;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  145.     /** Sun position. */
  146.     private PVCoordinatesProvider sun;

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

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

  151.     /** Constructor with space environment information for internal computation.
  152.      * @param parameters the solar and magnetic activity data
  153.      * @param sun the sun position
  154.      * @param earth the earth body shape
  155.      */
  156.     public JB2008(final JB2008InputParameters parameters,
  157.                   final PVCoordinatesProvider sun, final BodyShape earth) {
  158.         this.earth = earth;
  159.         this.sun = sun;
  160.         this.inputParams = parameters;
  161.     }

  162.     /** {@inheritDoc} */
  163.     public Frame getFrame() {
  164.         return earth.getBodyFrame();
  165.     }

  166.     /** Get the local density with initial entries.
  167.      * @param dateMJD date and time, in modified julian days and fraction
  168.      * @param sunRA Right Ascension of Sun (radians)
  169.      * @param sunDecli Declination of Sun (radians)
  170.      * @param satLon Right Ascension of position (radians)
  171.      * @param satLat Geocentric latitude of position (radians)
  172.      * @param satAlt Height of position (m)
  173.      * @param f10 10.7-cm Solar flux (1e<sup>-22</sup>*Watt/(m²*Hertz))<br>
  174.      *        (Tabular time 1.0 day earlier)
  175.      * @param f10B 10.7-cm Solar Flux, averaged 81-day centered on the input time<br>
  176.      *        (Tabular time 1.0 day earlier)
  177.      * @param s10 EUV index (26-34 nm) scaled to F10<br>
  178.      *        (Tabular time 1 day earlier)
  179.      * @param s10B UV 81-day averaged centered index
  180.      *        (Tabular time 1 day earlier)
  181.      * @param xm10 MG2 index scaled to F10<br>
  182.      *        (Tabular time 2.0 days earlier)
  183.      * @param xm10B MG2 81-day ave. centered index<br>
  184.      *        (Tabular time 2.0 days earlier)
  185.      * @param y10 Solar X-Ray & Lya index scaled to F10<br>
  186.      *        (Tabular time 5.0 days earlier)
  187.      * @param y10B Solar X-Ray & Lya 81-day ave. centered index<br>
  188.      *        (Tabular time 5.0 days earlier)
  189.      * @param dstdtc Temperature change computed from Dst index
  190.      * @return total mass-Density at input position (kg/m³)
  191.      * @exception OrekitException if altitude is below 90 km
  192.      */
  193.     public double getDensity(final double dateMJD, final double sunRA, final double sunDecli,
  194.                              final double satLon, final double satLat, final double satAlt,
  195.                              final double f10, final double f10B, final double s10,
  196.                              final double s10B, final double xm10, final double xm10B,
  197.                              final double y10, final double y10B, final double dstdtc)
  198.         throws OrekitException {

  199.         if (satAlt < ALT_MIN) {
  200.             throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD, satAlt, ALT_MIN);
  201.         }

  202.         final double altKm = satAlt / 1000.0;

  203.         // Equation (14)
  204.         final double fn  = FastMath.min(1.0, FastMath.pow(f10B / 240., 0.25));
  205.         final double fsb = f10B * fn + s10B * (1. - fn);
  206.         final double tsubc = 392.4 + 3.227 * fsb + 0.298 * (f10 - f10B) + 2.259 * (s10 - s10B) +
  207.                              0.312 * (xm10 - xm10B) + 0.178 * (y10 - y10B);

  208.         // Equation (15)
  209.         final double eta   = 0.5 * FastMath.abs(satLat - sunDecli);
  210.         final double theta = 0.5 * FastMath.abs(satLat + sunDecli);

  211.         // Equation (16)
  212.         final double h   = satLon - sunRA;
  213.         final double tau = h - 0.64577182 + 0.10471976 * FastMath.sin(h + 0.75049158);
  214.         double solarTime = FastMath.toDegrees(h + FastMath.PI) / 15.0;
  215.         while (solarTime >= 24) {
  216.             solarTime -= 24.;
  217.         }
  218.         while (solarTime < 0) {
  219.             solarTime += 24.;
  220.         }

  221.         // Equation (17)
  222.         final double cosEta  = FastMath.pow(FastMath.cos(eta), 2.5);
  223.         final double sinTeta = FastMath.pow(FastMath.sin(theta), 2.5);
  224.         final double cosTau  = FastMath.abs(FastMath.cos(0.5 * tau));
  225.         final double df = sinTeta + (cosEta - sinTeta) * cosTau * cosTau * cosTau;
  226.         final double tsubl = tsubc * (1. + 0.31 * df);

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

  229.         // Compute the local exospheric temperature.
  230.         // Add geomagnetic storm effect from input dTc value
  231.         final double[] temp = new double[2];
  232.         temp[0] = tsubl + dstdtc;
  233.         final double tinf = temp[0] + dtclst;

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

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

  238.         // The TC array will be an argument in the call to localTemp,
  239.         // which evaluates Eq. (10) or (13)
  240.         final double[] tc = new double[4];
  241.         tc[0] = tsubx;
  242.         tc[1] = gsubx;
  243.         // A of Equation (13)
  244.         tc[2] = (tinf - tsubx) * 2. / FastMath.PI;
  245.         tc[3] = gsubx / tc[2];

  246.         // Equation (5)
  247.         final double z1 = 90.;
  248.         final double z2 = FastMath.min(altKm, 105.0);
  249.         double al = FastMath.log(z2 / z1);
  250.         int n = 1 + (int) FastMath.floor(al / R1);
  251.         double zr = FastMath.exp(al / n);
  252.         final double mb1 = mBar(z1);
  253.         final double tloc1 = localTemp(z1, tc);
  254.         double zend  = z1;
  255.         double sub2  = 0.;
  256.         double ain   = mb1 * gravity(z1) / tloc1;
  257.         double mb2   = 0;
  258.         double tloc2 = 0;
  259.         double z     = 0;
  260.         double gravl = 0;

  261.         for (int i = 0; i < n; ++i) {
  262.             z = zend;
  263.             zend = zr * z;
  264.             final double dz = 0.25 * (zend - z);
  265.             double sum1 = WT[0] * ain;
  266.             for (int j = 1; j < 5; ++j) {
  267.                 z += dz;
  268.                 mb2   = mBar(z);
  269.                 tloc2 = localTemp(z, tc);
  270.                 gravl = gravity(z);
  271.                 ain   = mb2 * gravl / tloc2;
  272.                 sum1 += WT[j] * ain;
  273.             }
  274.             sub2 += dz * sum1;
  275.         }

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

  277.         // Equation (2)
  278.         final double anm = AVOGAD * rho;
  279.         final double an  = anm / mb2;

  280.         // Equation (3)
  281.         double fact2  = anm / 28.960;
  282.         final double[] aln = new double[6];
  283.         aln[0] = FastMath.log(FRAC[0] * fact2);
  284.         aln[3] = FastMath.log(FRAC[2] * fact2);
  285.         aln[4] = FastMath.log(FRAC[3] * fact2);
  286.         // Equation (4)
  287.         aln[1] = FastMath.log(fact2 * (1. + FRAC[1]) - an);
  288.         aln[2] = FastMath.log(2. * (an - fact2));

  289.         if (altKm <= 105.0) {
  290.             temp[1] = tloc2;
  291.             // Put in negligible hydrogen for use in DO-LOOP 13
  292.             aln[5] = aln[4] - 25.0;
  293.         } else {
  294.             // Equation (6)
  295.             al   = FastMath.log(FastMath.min(altKm, 500.0) / z);
  296.             n    = 1 + (int) FastMath.floor(al / R2);
  297.             zr   = FastMath.exp(al / n);
  298.             sub2 = 0.;
  299.             ain  = gravl / tloc2;

  300.             double tloc3 = 0;
  301.             for (int i = 0; i < n; ++i) {
  302.                 z = zend;
  303.                 zend = zr * z;
  304.                 final double dz = 0.25 * (zend - z);
  305.                 double sum1 = WT[0] * ain;
  306.                 for (int j = 1; j < 5; ++j) {
  307.                     z += dz;
  308.                     tloc3 = localTemp(z, tc);
  309.                     gravl = gravity(z);
  310.                     ain   = gravl / tloc3;
  311.                     sum1 += WT[j] * ain;
  312.                 }
  313.                 sub2 += dz * sum1;
  314.             }

  315.             al = FastMath.log(FastMath.max(altKm, 500.0) / z);
  316.             final double r = (altKm > 500.0) ? R3 : R2;
  317.             n = 1 + (int) FastMath.floor(al / r);
  318.             zr = FastMath.exp(al / n);
  319.             double sum3 = 0.;
  320.             double tloc4 = 0;
  321.             for (int i = 0; i < n; ++i) {
  322.                 z = zend;
  323.                 zend = zr * z;
  324.                 final double dz = 0.25 * (zend - z);
  325.                 double sum1 = WT[0] * ain;
  326.                 for (int j = 1; j < 5; ++j) {
  327.                     z += dz;
  328.                     tloc4 = localTemp(z, tc);
  329.                     gravl = gravity(z);
  330.                     ain   = gravl / tloc4;
  331.                     sum1 += WT[j] * ain;
  332.                 }
  333.                 sum3 += dz * sum1;
  334.             }
  335.             final double altr;
  336.             final double hSign;
  337.             if (altKm <= 500.) {
  338.                 temp[1] = tloc3;
  339.                 altr = FastMath.log(tloc3 / tloc2);
  340.                 fact2 = sub2 / RSTAR;
  341.                 hSign = 1.0;
  342.             } else {
  343.                 temp[1] = tloc4;
  344.                 altr = FastMath.log(tloc4 / tloc2);
  345.                 fact2 = (sub2 + sum3) / RSTAR;
  346.                 hSign = -1.0;
  347.             }
  348.             for (int i = 0; i < 5; ++i) {
  349.                 aln[i] = aln[i] - (1.0 + ALPHA[i]) * altr - fact2 * AMW[i];
  350.             }

  351.             // Equation (7)
  352.             final double al10t5 = FastMath.log10(tinf);
  353.             final double alnh5 = (5.5 * al10t5 - 39.40) * al10t5 + 73.13;
  354.             aln[5] = LOG10 * (alnh5 + 6.) + hSign * (FastMath.log(tloc4 / tloc3) + sum3 * AMW[5] / RSTAR);
  355.         }

  356.         // Equation (24) - J70 Seasonal-Latitudinal Variation
  357.         final double capPhi = ((dateMJD - 36204.0) / 365.2422) % 1;
  358.         final int signum = (satLat >= 0.) ? 1 : -1;
  359.         final double sinLat = FastMath.sin(satLat);
  360.         final double hm90  = altKm - 90.;
  361.         final double dlrsl = 0.02 * hm90 * FastMath.exp(-0.045 * hm90) *
  362.                              signum * FastMath.sin(MathUtils.TWO_PI * capPhi + 1.72) * sinLat * sinLat;

  363.         // Equation (23) - Computes the semiannual variation
  364.         double dlrsa = 0;
  365.         if (z < 2000.0) {
  366.             // Use new semiannual model dLog(rho)
  367.             dlrsa = semian08(dayOfYear(dateMJD), altKm, f10B, s10B, xm10B);
  368.         }

  369.         // Sum the delta-log-rhos and apply to the number densities.
  370.         // In CIRA72 the following equation contains an actual sum,
  371.         // namely DLR = LOG10 * (DLRGM + DLRSA + DLRSL)
  372.         // However, for Jacchia 70, there is no DLRGM or DLRSA.
  373.         final double dlr = LOG10 * (dlrsl + dlrsa);
  374.         for (int i = 0; i < 6; ++i) {
  375.             aln[i] += dlr;
  376.         }

  377.         // Compute mass-density and mean-molecular-weight and
  378.         // convert number density logs from natural to common.
  379.         double sumnm = 0.0;
  380.         for (int i = 0; i < 6; ++i) {
  381.             sumnm += FastMath.exp(aln[i]) * AMW[i];
  382.         }
  383.         rho = sumnm / AVOGAD;

  384.         // Compute the high altitude exospheric density correction factor
  385.         double fex = 1.0;
  386.         if ((altKm >= 1000.0) && (altKm < 1500.0)) {
  387.             final double zeta = (altKm - 1000.) * 0.002;
  388.             final double f15c = CHT[0] + CHT[1] * f10B + (CHT[2] + CHT[3] * f10B) * 1500.0;
  389.             final double f15cZeta = (CHT[2] + CHT[3] * f10B) * 500.0;
  390.             final double fex2 = 3.0 * f15c - f15cZeta - 3.0;
  391.             final double fex3 = f15cZeta - 2.0 * f15c + 2.0;
  392.             fex += zeta * zeta * (fex2 + fex3 * zeta);
  393.         } else if (altKm >= 1500.0) {
  394.             fex = CHT[0] + CHT[1] * f10B + CHT[2] * altKm + CHT[3] * f10B * altKm;
  395.         }

  396.         // Apply the exospheric density correction factor.
  397.         rho *= fex;

  398.         return rho;
  399.     }

  400.     /** Get the local density with initial entries.
  401.      * @param dateMJD date and time, in modified julian days and fraction
  402.      * @param sunRA Right Ascension of Sun (radians)
  403.      * @param sunDecli Declination of Sun (radians)
  404.      * @param satLon Right Ascension of position (radians)
  405.      * @param satLat Geocentric latitude of position (radians)
  406.      * @param satAlt Height of position (m)
  407.      * @param f10 10.7-cm Solar flux (1e<sup>-22</sup>*Watt/(m²*Hertz))<br>
  408.      *        (Tabular time 1.0 day earlier)
  409.      * @param f10B 10.7-cm Solar Flux, averaged 81-day centered on the input time<br>
  410.      *        (Tabular time 1.0 day earlier)
  411.      * @param s10 EUV index (26-34 nm) scaled to F10<br>
  412.      *        (Tabular time 1 day earlier)
  413.      * @param s10B UV 81-day averaged centered index
  414.      *        (Tabular time 1 day earlier)
  415.      * @param xm10 MG2 index scaled to F10<br>
  416.      *        (Tabular time 2.0 days earlier)
  417.      * @param xm10B MG2 81-day ave. centered index<br>
  418.      *        (Tabular time 2.0 days earlier)
  419.      * @param y10 Solar X-Ray & Lya index scaled to F10<br>
  420.      *        (Tabular time 5.0 days earlier)
  421.      * @param y10B Solar X-Ray & Lya 81-day ave. centered index<br>
  422.      *        (Tabular time 5.0 days earlier)
  423.      * @param dstdtc Temperature change computed from Dst index
  424.      * @param <T> type fo the field elements
  425.      * @return total mass-Density at input position (kg/m³)
  426.      * @exception OrekitException if altitude is below 90 km
  427.      */
  428.     public <T extends RealFieldElement<T>> T getDensity(final T dateMJD, final T sunRA, final T sunDecli,
  429.                                                         final T satLon, final T satLat, final T satAlt,
  430.                                                         final double f10, final double f10B, final double s10,
  431.                                                         final double s10B, final double xm10, final double xm10B,
  432.                                                         final double y10, final double y10B, final double dstdtc)
  433.         throws OrekitException {

  434.         if (satAlt.getReal() < ALT_MIN) {
  435.             throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD,
  436.                                       satAlt.getReal(), ALT_MIN);
  437.         }

  438.         final Field<T> field  = satAlt.getField();
  439.         final T altKm = satAlt.divide(1000.0);

  440.         // Equation (14)
  441.         final double fn  = FastMath.min(1.0, FastMath.pow(f10B / 240., 0.25));
  442.         final double fsb = f10B * fn + s10B * (1. - fn);
  443.         final double tsubc = 392.4 + 3.227 * fsb + 0.298 * (f10 - f10B) + 2.259 * (s10 - s10B) +
  444.                              0.312 * (xm10 - xm10B) + 0.178 * (y10 - y10B);

  445.         // Equation (15)
  446.         final T eta   = satLat.subtract(sunDecli).abs().multiply(0.5);
  447.         final T theta = satLat.add(sunDecli).abs().multiply(0.5);

  448.         // Equation (16)
  449.         final T h   = satLon.subtract(sunRA);
  450.         final T tau = h.subtract(0.64577182).add(h.add(0.75049158).sin().multiply(0.10471976));
  451.         T solarTime = h.add(FastMath.PI).multiply(12.0 / FastMath.PI);
  452.         while (solarTime.getReal() >= 24) {
  453.             solarTime = solarTime.subtract(24);
  454.         }
  455.         while (solarTime.getReal() < 0) {
  456.             solarTime = solarTime.add(24);
  457.         }

  458.         // Equation (17)
  459.         final T cos     = eta.cos();
  460.         final T cosEta  = cos.multiply(cos).multiply(cos.sqrt());
  461.         final T sin     = theta.sin();
  462.         final T sinTeta = sin.multiply(sin).multiply(sin.sqrt());
  463.         final T cosTau  = tau.multiply(0.5).cos().abs();
  464.         final T df      = sinTeta.add(cosEta.subtract(sinTeta).multiply(cosTau).multiply(cosTau).multiply(cosTau));
  465.         final T tsubl   = df.multiply(0.31).add(1).multiply(tsubc);

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

  468.         // Compute the local exospheric temperature.
  469.         // Add geomagnetic storm effect from input dTc value
  470.         final T[] temp = MathArrays.buildArray(field, 2);
  471.         temp[0] = tsubl.add(dstdtc);
  472.         final T tinf = temp[0].add(dtclst);

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

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

  477.         // The TC array will be an argument in the call to localTemp,
  478.         // which evaluates Eq. (10) or (13)
  479.         final T[] tc = MathArrays.buildArray(field, 4);
  480.         tc[0] = tsubx;
  481.         tc[1] = gsubx;
  482.         // A of Equation (13)
  483.         tc[2] = tinf.subtract(tsubx).multiply(2. / FastMath.PI);
  484.         tc[3] = gsubx.divide(tc[2]);

  485.         // Equation (5)
  486.         final T z1 = field.getZero().add(90.);
  487.         final T z2 = min(105.0, altKm);
  488.         T al = z2.divide(z1).log();
  489.         int n = 1 + (int) FastMath.floor(al.getReal() / R1);
  490.         T zr = al.divide(n).exp();
  491.         final T mb1 = mBar(z1);
  492.         final T tloc1 = localTemp(z1, tc);
  493.         T zend  = z1;
  494.         T sub2  = field.getZero();
  495.         T ain   = mb1.multiply(gravity(z1)).divide(tloc1);
  496.         T mb2   = field.getZero();
  497.         T tloc2 = field.getZero();
  498.         T z     = field.getZero();
  499.         T gravl = field.getZero();
  500.         for (int i = 0; i < n; ++i) {
  501.             z = zend;
  502.             zend = zr.multiply(z);
  503.             final T dz = zend.subtract(z).multiply(0.25);
  504.             T sum1 = ain.multiply(WT[0]);
  505.             for (int j = 1; j < 5; ++j) {
  506.                 z = z.add(dz);
  507.                 mb2   = mBar(z);
  508.                 tloc2 = localTemp(z, tc);
  509.                 gravl = gravity(z);
  510.                 ain   = mb2.multiply(gravl).divide(tloc2);
  511.                 sum1  = sum1.add(ain.multiply(WT[j]));
  512.             }
  513.             sub2 = sub2.add(dz.multiply(sum1));
  514.         }

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

  516.         // Equation (2)
  517.         final T anm = rho.multiply(AVOGAD);
  518.         final T an  = anm.divide(mb2);

  519.         // Equation (3)
  520.         T fact2  = anm.divide(28.960);
  521.         final T[] aln = MathArrays.buildArray(field, 6);
  522.         aln[0] = fact2.multiply(FRAC[0]).log();
  523.         aln[3] = fact2.multiply(FRAC[2]).log();
  524.         aln[4] = fact2.multiply(FRAC[3]).log();
  525.         // Equation (4)
  526.         aln[1] = fact2.multiply(1. + FRAC[1]).subtract(an).log();
  527.         aln[2] = an.subtract(fact2).multiply(2).log();

  528.         if (altKm.getReal() <= 105.0) {
  529.             temp[1] = tloc2;
  530.             // Put in negligible hydrogen for use in DO-LOOP 13
  531.             aln[5] = aln[4].subtract(25.0);
  532.         } else {
  533.             // Equation (6)
  534.             al   = min(500.0, altKm).divide(z).log();
  535.             n    = 1 + (int) FastMath.floor(al.getReal() / R2);
  536.             zr   = al.divide(n).exp();
  537.             sub2 = field.getZero();
  538.             ain  = gravl.divide(tloc2);

  539.             T tloc3 = field.getZero();
  540.             for (int i = 0; i < n; ++i) {
  541.                 z = zend;
  542.                 zend = zr.multiply(z);
  543.                 final T dz = zend.subtract(z).multiply(0.25);
  544.                 T sum1 = ain.multiply(WT[0]);
  545.                 for (int j = 1; j < 5; ++j) {
  546.                     z = z.add(dz);
  547.                     tloc3 = localTemp(z, tc);
  548.                     gravl = gravity(z);
  549.                     ain   = gravl.divide(tloc3);
  550.                     sum1  = sum1.add(ain.multiply(WT[j]));
  551.                 }
  552.                 sub2 = sub2.add(dz.multiply(sum1));
  553.             }

  554.             al = max(500.0, altKm).divide(z).log();
  555.             final double r = (altKm.getReal() > 500.0) ? R3 : R2;
  556.             n = 1 + (int) FastMath.floor(al.getReal() / r);
  557.             zr = al.divide(n).exp();
  558.             T sum3 = field.getZero();
  559.             T tloc4 = 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.                     tloc4 = localTemp(z, tc);
  568.                     gravl = gravity(z);
  569.                     ain   = gravl.divide(tloc4);
  570.                     sum1  = sum1.add(ain.multiply(WT[j]));
  571.                 }
  572.                 sum3 = sum3.add(dz.multiply(sum1));
  573.             }
  574.             final T altr;
  575.             final double hSign;
  576.             if (altKm.getReal() <= 500.) {
  577.                 temp[1] = tloc3;
  578.                 altr = tloc3.divide(tloc2).log();
  579.                 fact2 = sub2.divide(RSTAR);
  580.                 hSign = 1.0;
  581.             } else {
  582.                 temp[1] = tloc4;
  583.                 altr = tloc4.divide(tloc2).log();
  584.                 fact2 = sub2.add(sum3).divide(RSTAR);
  585.                 hSign = -1.0;
  586.             }
  587.             for (int i = 0; i < 5; ++i) {
  588.                 aln[i] = aln[i].subtract(altr.multiply(1.0 + ALPHA[i])).subtract(fact2.multiply(AMW[i]));
  589.             }

  590.             // Equation (7)
  591.             final T al10t5 = tinf.log10();
  592.             final T alnh5 = al10t5.multiply(5.5).subtract(39.40).multiply(al10t5).add(73.13);
  593.             aln[5] = alnh5.add(6.).multiply(LOG10).
  594.                      add(tloc4.divide(tloc3).log().add(sum3.multiply(AMW[5] / RSTAR)).multiply(hSign));
  595.         }

  596.         // Equation (24) - J70 Seasonal-Latitudinal Variation
  597.         T capPhi = dateMJD.subtract(36204.0).divide(365.2422);
  598.         capPhi = capPhi.subtract(FastMath.floor(capPhi.getReal()));
  599.         final int signum = (satLat.getReal() >= 0.) ? 1 : -1;
  600.         final T sinLat = satLat.sin();
  601.         final T hm90  = altKm.subtract(90.);
  602.         final T dlrsl = hm90.multiply(0.02).multiply(hm90.multiply(-0.045).exp()).
  603.                         multiply(capPhi.multiply(MathUtils.TWO_PI).add(1.72).sin()).
  604.                         multiply(signum).multiply(sinLat).multiply(sinLat);

  605.         // Equation (23) - Computes the semiannual variation
  606.         T dlrsa = field.getZero();
  607.         if (z.getReal() < 2000.0) {
  608.             // Use new semiannual model dLog(rho)
  609.             dlrsa = semian08(dayOfYear(dateMJD), altKm, f10B, s10B, xm10B);
  610.         }

  611.         // Sum the delta-log-rhos and apply to the number densities.
  612.         // In CIRA72 the following equation contains an actual sum,
  613.         // namely DLR = LOG10 * (DLRGM + DLRSA + DLRSL)
  614.         // However, for Jacchia 70, there is no DLRGM or DLRSA.
  615.         final T dlr = dlrsl.add(dlrsa).multiply(LOG10);
  616.         for (int i = 0; i < 6; ++i) {
  617.             aln[i] = aln[i].add(dlr);
  618.         }

  619.         // Compute mass-density and mean-molecular-weight and
  620.         // convert number density logs from natural to common.
  621.         T sumnm = field.getZero();
  622.         for (int i = 0; i < 6; ++i) {
  623.             sumnm = sumnm.add(aln[i].exp().multiply(AMW[i]));
  624.         }
  625.         rho = sumnm.divide(AVOGAD);

  626.         // Compute the high altitude exospheric density correction factor
  627.         T fex = field.getOne();
  628.         if ((altKm.getReal() >= 1000.0) && (altKm.getReal() < 1500.0)) {
  629.             final T zeta = altKm.subtract(1000.).multiply(0.002);
  630.             final double f15c     = CHT[0] + CHT[1] * f10B + (CHT[2] + CHT[3] * f10B) * 1500.0;
  631.             final double f15cZeta = (CHT[2] + CHT[3] * f10B) * 500.0;
  632.             final double fex2     = 3.0 * f15c - f15cZeta - 3.0;
  633.             final double fex3     = f15cZeta - 2.0 * f15c + 2.0;
  634.             fex = fex.add(zeta.multiply(zeta).multiply(zeta.multiply(fex3).add(fex2)));
  635.         } else if (altKm.getReal() >= 1500.0) {
  636.             fex = altKm.multiply(CHT[3] * f10B).add(altKm.multiply(CHT[2])).add(CHT[0] + CHT[1] * f10B);
  637.         }

  638.         // Apply the exospheric density correction factor.
  639.         rho = rho.multiply(fex);

  640.         return rho;
  641.     }

  642.     /** Compute daily temperature correction for Jacchia-Bowman model.
  643.      * @param f10 solar flux index
  644.      * @param solarTime local solar time (hours in [0, 24[)
  645.      * @param satLat sat lat (radians)
  646.      * @param satAlt height (km)
  647.      * @return dTc correction
  648.      */
  649.     private static double dTc(final double f10, final double solarTime,
  650.                               final double satLat, final double satAlt) {
  651.         double dTc = 0.;
  652.         final double st = solarTime / 24.0;
  653.         final double cs = FastMath.cos(satLat);
  654.         final double fs = (f10 - 100.0) / 100.0;

  655.         // Calculates dTc according to height
  656.         if ((satAlt >= 120) && (satAlt <= 200)) {
  657.             final double dtc200 = poly2CDTC(fs, st, cs);
  658.             final double dtc200dz = poly1CDTC(fs, st, cs);
  659.             final double cc = 3.0 * dtc200 - dtc200dz;
  660.             final double dd = dtc200 - cc;
  661.             final double zp = (satAlt - 120.0) / 80.0;
  662.             dTc = zp * zp * (cc + dd * zp);
  663.         } else if ((satAlt > 200.0) && (satAlt <= 240.0)) {
  664.             final double h = (satAlt - 200.0) / 50.0;
  665.             dTc = poly1CDTC(fs, st, cs) * h + poly2CDTC(fs, st, cs);
  666.         } else if ((satAlt > 240.0) && (satAlt <= 300.0)) {
  667.             final double h = 0.8;
  668.             final double bb = poly1CDTC(fs, st, cs);
  669.             final double aa = bb * h + poly2CDTC(fs, st, cs);
  670.             final double p2BDT = poly2BDTC(st);
  671.             final double dtc300 = poly1BDTC(fs, st, cs, 3 * p2BDT);
  672.             final double dtc300dz = cs * p2BDT;
  673.             final double cc = 3.0 * dtc300 - dtc300dz - 3.0 * aa - 2.0 * bb;
  674.             final double dd = dtc300 - aa - bb - cc;
  675.             final double zp = (satAlt - 240.0) / 60.0;
  676.             dTc = aa + zp * (bb + zp * (cc + zp * dd));
  677.         } else if ((satAlt > 300.0) && (satAlt <= 600.0)) {
  678.             final double h = satAlt / 100.0;
  679.             dTc = poly1BDTC(fs, st, cs, h * poly2BDTC(st));
  680.         } else if ((satAlt > 600.0) && (satAlt <= 800.0)) {
  681.             final double poly2 = poly2BDTC(st);
  682.             final double aa = poly1BDTC(fs, st, cs, 6 * poly2);
  683.             final double bb = cs * poly2;
  684.             final double cc = -(3.0 * aa + 4.0 * bb) / 4.0;
  685.             final double dd = (aa + bb) / 4.0;
  686.             final double zp = (satAlt - 600.0) / 100.0;
  687.             dTc = aa + zp * (bb + zp * (cc + zp * dd));
  688.         }

  689.         return dTc;
  690.     }

  691.     /** Compute daily temperature correction for Jacchia-Bowman model.
  692.      * @param f10 solar flux index
  693.      * @param solarTime local solar time (hours in [0, 24[)
  694.      * @param satLat sat lat (radians)
  695.      * @param satAlt height (km)
  696.      * @param <T> type of the filed elements
  697.      * @return dTc correction
  698.      */
  699.     private static <T extends RealFieldElement<T>> T dTc(final double f10, final T solarTime,
  700.                                                          final T satLat, final T satAlt) {
  701.         T dTc = solarTime.getField().getZero();
  702.         final T      st = solarTime.divide(24.0);
  703.         final T      cs = satLat.cos();
  704.         final double fs = (f10 - 100.0) / 100.0;

  705.         // Calculates dTc according to height
  706.         if ((satAlt.getReal() >= 120) && (satAlt.getReal() <= 200)) {
  707.             final T dtc200   = poly2CDTC(fs, st, cs);
  708.             final T dtc200dz = poly1CDTC(fs, st, cs);
  709.             final T cc       = dtc200.multiply(3).subtract(dtc200dz);
  710.             final T dd       = dtc200.subtract(cc);
  711.             final T zp       = satAlt.subtract(120.0).divide(80.0);
  712.             dTc = zp.multiply(zp).multiply(cc.add(dd.multiply(zp)));
  713.         } else if ((satAlt.getReal() > 200.0) && (satAlt.getReal() <= 240.0)) {
  714.             final T h = satAlt.subtract(200.0).divide(50.0);
  715.             dTc = poly1CDTC(fs, st, cs).multiply(h).add(poly2CDTC(fs, st, cs));
  716.         } else if ((satAlt.getReal() > 240.0) && (satAlt.getReal() <= 300.0)) {
  717.             final T h = solarTime.getField().getZero().add(0.8);
  718.             final T bb = poly1CDTC(fs, st, cs);
  719.             final T aa = bb.multiply(h).add(poly2CDTC(fs, st, cs));
  720.             final T p2BDT = poly2BDTC(st);
  721.             final T dtc300 = poly1BDTC(fs, st, cs, p2BDT.multiply(3));
  722.             final T dtc300dz = cs.multiply(p2BDT);
  723.             final T cc = dtc300.multiply(3).subtract(dtc300dz).subtract(aa.multiply(3)).subtract(bb.multiply(2));
  724.             final T dd = dtc300.subtract(aa).subtract(bb).subtract(cc);
  725.             final T zp = satAlt.subtract(240.0).divide(60.0);
  726.             dTc = aa.add(zp.multiply(bb.add(zp.multiply(cc.add(zp.multiply(dd))))));
  727.         } else if ((satAlt.getReal() > 300.0) && (satAlt.getReal() <= 600.0)) {
  728.             final T h = satAlt.divide(100.0);
  729.             dTc = poly1BDTC(fs, st, cs, h.multiply(poly2BDTC(st)));
  730.         } else if ((satAlt.getReal() > 600.0) && (satAlt.getReal() <= 800.0)) {
  731.             final T poly2 = poly2BDTC(st);
  732.             final T aa = poly1BDTC(fs, st, cs, poly2.multiply(6));
  733.             final T bb = cs.multiply(poly2);
  734.             final T cc = aa.multiply(3).add(bb.multiply(4)).divide(-4.0);
  735.             final T dd = aa.add(bb).divide(4.0);
  736.             final T zp = satAlt.subtract(600.0).divide(100.0);
  737.             dTc = aa.add(zp.multiply(bb.add(zp.multiply(cc.add(zp.multiply(dd))))));
  738.         }

  739.         return dTc;
  740.     }

  741.     /** Calculates first polynomial with CDTC array.
  742.      * @param fs scaled flux f10
  743.      * @param st local solar time in [0, 1[
  744.      * @param cs cosine of satLat
  745.      * @return the value of the polynomial
  746.      */
  747.     private static double poly1CDTC(final double fs, final double st, final double cs) {
  748.         return CDTC[0] +
  749.                 fs * (CDTC[1] + st * (CDTC[2] + st * (CDTC[3] + st * (CDTC[4] + st * (CDTC[5] + st * CDTC[6]))))) +
  750.                 cs * st * (CDTC[7] + st * (CDTC[8] + st * (CDTC[9] + st * (CDTC[10] + st * CDTC[11])))) +
  751.                 cs * (CDTC[12] + fs * (CDTC[13] + st * (CDTC[14] + st * CDTC[15])));
  752.     }

  753.     /** Calculates first polynomial with CDTC array.
  754.      * @param fs scaled flux f10
  755.      * @param st local solar time in [0, 1[
  756.      * @param cs cosine of satLat
  757.      * @param <T> type fo the field elements
  758.      * @return the value of the polynomial
  759.      */
  760.     private static <T extends RealFieldElement<T>>  T poly1CDTC(final double fs, final T st, final T cs) {
  761.         return    st.multiply(CDTC[6]).
  762.               add(CDTC[5]).multiply(st).
  763.               add(CDTC[4]).multiply(st).
  764.               add(CDTC[3]).multiply(st).
  765.               add(CDTC[2]).multiply(st).
  766.               add(CDTC[1]).multiply(fs).
  767.               add(st.multiply(CDTC[11]).
  768.                   add(CDTC[10]).multiply(st).
  769.                   add(CDTC[ 9]).multiply(st).
  770.                   add(CDTC[ 8]).multiply(st).
  771.                   add(CDTC[7]).multiply(st).multiply(cs)).
  772.               add(st.multiply(CDTC[15]).
  773.                   add(CDTC[14]).multiply(st).
  774.                   add(CDTC[13]).multiply(fs).
  775.                   add(CDTC[12]).multiply(cs)).
  776.               add(CDTC[0]);
  777.     }

  778.     /** Calculates second polynomial with CDTC array.
  779.      * @param fs scaled flux f10
  780.      * @param st local solar time in [0, 1[
  781.      * @param cs cosine of satLat
  782.      * @return the value of the polynomial
  783.      */
  784.     private static double poly2CDTC(final double fs, final double st, final double cs) {
  785.         return CDTC[16] + st * cs * (CDTC[17] + st * (CDTC[18] + st * CDTC[19])) +
  786.                           fs * cs * (CDTC[20] + st * (CDTC[21] + st * CDTC[22]));
  787.     }

  788.     /** Calculates second polynomial with CDTC array.
  789.      * @param fs scaled flux f10
  790.      * @param st local solar time in [0, 1[
  791.      * @param cs cosine of satLat
  792.      * @param <T> type fo the field elements
  793.      * @return the value of the polynomial
  794.      */
  795.     private static <T extends RealFieldElement<T>>  T poly2CDTC(final double fs, final T st, final T cs) {
  796.         return         st.multiply(CDTC[19]).
  797.                    add(CDTC[18]).multiply(st).
  798.                    add(CDTC[17]).multiply(cs).multiply(st).
  799.                add(    st.multiply(CDTC[22]).
  800.                    add(CDTC[21]).multiply(st).
  801.                    add(CDTC[20]).multiply(cs).multiply(fs)).
  802.                add(CDTC[16]);
  803.     }

  804.     /** Calculates first polynomial with BDTC array.
  805.      * @param fs scaled flux f10
  806.      * @param st local solar time in [0, 1[
  807.      * @param cs cosine of satLat
  808.      * @param hp scaled height * poly2BDTC
  809.      * @return the value of the polynomial
  810.      */
  811.     private static double poly1BDTC(final double fs, final double st, final double cs, final double hp) {
  812.         return BDTC[0] +
  813.                 fs * (BDTC[1] + st * (BDTC[2] + st * (BDTC[3] + st * (BDTC[4] + st * (BDTC[5] + st * BDTC[6]))))) +
  814.                 cs * (st * (BDTC[7] + st * (BDTC[8] + st * (BDTC[9] + st * (BDTC[10] + st * BDTC[11])))) + hp + BDTC[18]);
  815.     }

  816.     /** Calculates first polynomial with BDTC array.
  817.      * @param fs scaled flux f10
  818.      * @param st local solar time in [0, 1[
  819.      * @param cs cosine of satLat
  820.      * @param hp scaled height * poly2BDTC
  821.      * @param <T> type fo the field elements
  822.      * @return the value of the polynomial
  823.      */
  824.     private static <T extends RealFieldElement<T>>  T poly1BDTC(final double fs, final T st, final T cs, final T hp) {
  825.         return     st.multiply(BDTC[6]).
  826.                add(BDTC[5]).multiply(st).
  827.                add(BDTC[4]).multiply(st).
  828.                add(BDTC[3]).multiply(st).
  829.                add(BDTC[2]).multiply(st).
  830.                add(BDTC[1]).multiply(fs).
  831.                add(    st.multiply(BDTC[11]).
  832.                    add(BDTC[10]).multiply(st).
  833.                    add(BDTC[ 9]).multiply(st).
  834.                    add(BDTC[ 8]).multiply(st).
  835.                    add(BDTC[ 7]).multiply(st).
  836.                    add(hp).add(BDTC[18]).multiply(cs)).
  837.                add(BDTC[0]);
  838.     }

  839.     /** Calculates second polynomial with BDTC array.
  840.      * @param st local solar time in [0, 1[
  841.      * @return the value of the polynomial
  842.      */
  843.     private static double poly2BDTC(final double st) {
  844.         return BDTC[12] + st * (BDTC[13] + st * (BDTC[14] + st * (BDTC[15] + st * (BDTC[16] + st * BDTC[17]))));
  845.     }

  846.     /** Calculates second polynomial with BDTC array.
  847.      * @param st local solar time in [0, 1[
  848.      * @param <T> type fo the field elements
  849.      * @return the value of the polynomial
  850.      */
  851.     private static <T extends RealFieldElement<T>>  T poly2BDTC(final T st) {
  852.         return     st.multiply(BDTC[17]).
  853.                add(BDTC[16]).multiply(st).
  854.                add(BDTC[15]).multiply(st).
  855.                add(BDTC[14]).multiply(st).
  856.                add(BDTC[13]).multiply(st).
  857.                add(BDTC[12]);
  858.     }

  859.     /** Evaluates mean molecualr mass - Equation (1).
  860.      * @param z altitude (km)
  861.      * @return mean molecular mass
  862.      */
  863.     private static double mBar(final double z) {
  864.         final double dz = z - 100.;
  865.         double amb = CMB[6];
  866.         for (int i = 5; i >= 0; --i) {
  867.             amb = dz * amb + CMB[i];
  868.         }
  869.         return amb;
  870.     }

  871.     /** Evaluates mean molecualr mass - Equation (1).
  872.      * @param z altitude (km)
  873.      * @return mean molecular mass
  874.      * @param <T> type fo the field elements
  875.      */
  876.     private static <T extends RealFieldElement<T>>  T mBar(final T z) {
  877.         final T dz = z.subtract(100.);
  878.         T amb = z.getField().getZero().add(CMB[6]);
  879.         for (int i = 5; i >= 0; --i) {
  880.             amb = dz.multiply(amb).add(CMB[i]);
  881.         }
  882.         return amb;
  883.     }

  884.     /** Evaluates the local temperature, Eq. (10) or (13) depending on altitude.
  885.      * @param z altitude
  886.      * @param tc tc array
  887.      * @return temperature profile
  888.      */
  889.     private static double localTemp(final double z, final double[] tc) {
  890.         final double dz = z - 125.;
  891.         if (dz <= 0.) {
  892.             return ((-9.8204695e-6 * dz - 7.3039742e-4) * dz * dz + 1.0) * dz * tc[1] + tc[0];
  893.         } else {
  894.             return tc[0] + tc[2] * FastMath.atan(tc[3] * dz * (1 + 4.5e-6 * FastMath.pow(dz, 2.5)));
  895.         }
  896.     }

  897.     /** Evaluates the local temperature, Eq. (10) or (13) depending on altitude.
  898.      * @param z altitude
  899.      * @param tc tc array
  900.      * @return temperature profile
  901.      * @param <T> type fo the field elements
  902.      */
  903.     private static <T extends RealFieldElement<T>>  T localTemp(final T z, final T[] tc) {
  904.         final T dz = z.subtract(125.);
  905.         if (dz.getReal() <= 0.) {
  906.             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]);
  907.         } else {
  908.             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]);
  909.         }
  910.     }

  911.     /** Evaluates the gravity at the altitude - Equation (8).
  912.      * @param z altitude (km)
  913.      * @return the gravity (m/s2)
  914.      */
  915.     private static double gravity(final double z) {
  916.         final double tmp = 1.0 + z / EARTH_RADIUS;
  917.         return Constants.G0_STANDARD_GRAVITY / (tmp * tmp);
  918.     }

  919.     /** Evaluates the gravity at the altitude - Equation (8).
  920.      * @param z altitude (km)
  921.      * @return the gravity (m/s2)
  922.      * @param <T> type fo the field elements
  923.      */
  924.     private static <T extends RealFieldElement<T>>  T gravity(final T z) {
  925.         final T tmp = z.divide(EARTH_RADIUS).add(1);
  926.         return tmp.multiply(tmp).reciprocal().multiply(Constants.G0_STANDARD_GRAVITY);
  927.     }

  928.     /** Compute semi-annual variation (delta log(rho)).
  929.      * @param doy day of year
  930.      * @param alt height (km)
  931.      * @param f10B average 81-day centered f10
  932.      * @param s10B average 81-day centered s10
  933.      * @param xm10B average 81-day centered xn10
  934.      * @return semi-annual variation
  935.      */
  936.     private static double semian08(final double doy, final double alt,
  937.                                    final double f10B, final double s10B, final double xm10B) {

  938.         final double htz = alt / 1000.0;

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

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

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

  945.         // SEMIANNUAL PHASE FUNCTION
  946.         final double tau   = MathUtils.TWO_PI * (doy - 1.0) / 365;
  947.         final double taux2 = tau + tau;
  948.         final double sin1P = FastMath.sin(tau);
  949.         final double cos1P = FastMath.cos(tau);
  950.         final double sin2P = FastMath.sin(taux2);
  951.         final double cos2P = FastMath.cos(taux2);
  952.         final double gtz = GTM[0] + GTM[1] * sin1P + GTM[2] * cos1P + GTM[3] * sin2P + GTM[4] * cos2P +
  953.                            fsmb * (GTM[5] + GTM[6] * sin1P + GTM[7] * cos1P + GTM[8] * sin2P + GTM[9] * cos2P);

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

  955.     }

  956.     /** Compute semi-annual variation (delta log(rho)).
  957.      * @param doy day of year
  958.      * @param alt height (km)
  959.      * @param f10B average 81-day centered f10
  960.      * @param s10B average 81-day centered s10
  961.      * @param xm10B average 81-day centered xn10
  962.      * @return semi-annual variation
  963.      * @param <T> type fo the field elements
  964.      */
  965.     private static <T extends RealFieldElement<T>>  T semian08(final T doy, final T alt,
  966.                                                                final double f10B, final double s10B, final double xm10B) {

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

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

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

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

  974.         // SEMIANNUAL PHASE FUNCTION
  975.         final T tau   = doy.subtract(1).divide(365).multiply(MathUtils.TWO_PI);
  976.         final T taux2 = tau.add(tau);
  977.         final T sin1P = tau.sin();
  978.         final T cos1P = tau.cos();
  979.         final T sin2P = taux2.sin();
  980.         final T cos2P = taux2.cos();
  981.         final T gtz =           cos2P.multiply(GTM[9]).
  982.                             add(sin2P.multiply(GTM[8])).
  983.                             add(cos1P.multiply(GTM[7])).
  984.                             add(sin1P.multiply(GTM[6])).
  985.                             add(GTM[5]).multiply(fsmb).
  986.                         add(    cos2P.multiply(GTM[4]).
  987.                             add(sin2P.multiply(GTM[3])).
  988.                             add(cos1P.multiply(GTM[2])).
  989.                             add(sin1P.multiply(GTM[1])).
  990.                             add(GTM[0]));

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

  992.     }

  993.     /** Compute day of year.
  994.      * @param dateMJD Modified Julian date
  995.      * @return the number days in year
  996.      */
  997.     private static double dayOfYear(final double dateMJD) {
  998.         final double d1950 = dateMJD - 33281;

  999.         int iyday = (int) d1950;
  1000.         final double frac = d1950 - iyday;
  1001.         iyday = iyday + 364;

  1002.         int itemp = iyday / 1461;

  1003.         iyday = iyday - itemp * 1461;
  1004.         itemp = iyday / 365;
  1005.         if (itemp >= 3) {
  1006.             itemp = 3;
  1007.         }
  1008.         iyday = iyday - 365 * itemp + 1;
  1009.         return iyday + frac;
  1010.     }

  1011.     /** Compute day of year.
  1012.      * @param dateMJD Modified Julian date
  1013.      * @param <T> type of the field elements
  1014.      * @return the number days in year
  1015.      */
  1016.     private static <T extends RealFieldElement<T>> T dayOfYear(final T dateMJD) {
  1017.         final T d1950 = dateMJD.subtract(33281);

  1018.         int iyday = (int) d1950.getReal();
  1019.         final T frac = d1950.subtract(iyday);
  1020.         iyday = iyday + 364;

  1021.         int itemp = iyday / 1461;

  1022.         iyday = iyday - itemp * 1461;
  1023.         itemp = iyday / 365;
  1024.         if (itemp >= 3) {
  1025.             itemp = 3;
  1026.         }
  1027.         iyday = iyday - 365 * itemp + 1;
  1028.         return frac.add(iyday);
  1029.     }

  1030.     // OUTPUT:

  1031.     /** Compute min of two values, one double and one field element.
  1032.      * @param d double value
  1033.      * @param f field element
  1034.      * @param <T> type fo the field elements
  1035.      * @return min value
  1036.      */
  1037.     private <T extends RealFieldElement<T>> T min(final double d, final T f) {
  1038.         return (f.getReal() > d) ? f.getField().getZero().add(d) : f;
  1039.     }

  1040.     /** Compute max of two values, one double and one field element.
  1041.      * @param d double value
  1042.      * @param f field element
  1043.      * @param <T> type fo the field elements
  1044.      * @return max value
  1045.      */
  1046.     private <T extends RealFieldElement<T>> T max(final double d, final T f) {
  1047.         return (f.getReal() <= d) ? f.getField().getZero().add(d) : f;
  1048.     }

  1049.     /** Get the local density.
  1050.      * @param date current date
  1051.      * @param position current position in frame
  1052.      * @param frame the frame in which is defined the position
  1053.      * @return local density (kg/m³)
  1054.      * @exception OrekitException if date is out of range of solar activity
  1055.      */
  1056.     public double getDensity(final AbsoluteDate date, final Vector3D position,
  1057.                              final Frame frame)
  1058.         throws OrekitException {
  1059.         // check if data are available :
  1060.         if (date.compareTo(inputParams.getMaxDate()) > 0 ||
  1061.             date.compareTo(inputParams.getMinDate()) < 0) {
  1062.             throw new OrekitException(OrekitMessages.NO_SOLAR_ACTIVITY_AT_DATE,
  1063.                                       date, inputParams.getMinDate(), inputParams.getMaxDate());
  1064.         }

  1065.         // compute MJD date
  1066.         final double dateMJD = date.durationFrom(AbsoluteDate.MODIFIED_JULIAN_EPOCH) / Constants.JULIAN_DAY;

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

  1069.         // compute sun position
  1070.         final Frame ecef = earth.getBodyFrame();
  1071.         final Vector3D sunPos = sun.getPVCoordinates(date, ecef).getPosition();
  1072.         final GeodeticPoint sunInBody = earth.transform(sunPos, ecef, date);

  1073.         return getDensity(dateMJD,
  1074.                           sunInBody.getLongitude(), sunInBody.getLatitude(),
  1075.                           inBody.getLongitude(), inBody.getLatitude(), inBody.getAltitude(),
  1076.                           inputParams.getF10(date), inputParams.getF10B(date),
  1077.                           inputParams.getS10(date), inputParams.getS10B(date),
  1078.                           inputParams.getXM10(date), inputParams.getXM10B(date),
  1079.                           inputParams.getY10(date), inputParams.getY10B(date),
  1080.                           inputParams.getDSTDTC(date));

  1081.     }

  1082.     /** {@inheritDoc} */
  1083.     @Override
  1084.     public <T extends RealFieldElement<T>> T getDensity(final FieldAbsoluteDate<T> date,
  1085.                                                         final FieldVector3D<T> position,
  1086.                                                         final Frame frame)
  1087.         throws OrekitException {

  1088.         // check if data are available :
  1089.         final AbsoluteDate dateD = date.toAbsoluteDate();
  1090.         if ((dateD.compareTo(inputParams.getMaxDate()) > 0) ||
  1091.                         (dateD.compareTo(inputParams.getMinDate()) < 0)) {
  1092.             throw new OrekitException(OrekitMessages.NO_SOLAR_ACTIVITY_AT_DATE,
  1093.                                       dateD, inputParams.getMinDate(), inputParams.getMaxDate());
  1094.         }

  1095.         // compute MJD date
  1096.         final T dateMJD = date.durationFrom(AbsoluteDate.MODIFIED_JULIAN_EPOCH).divide(Constants.JULIAN_DAY);

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

  1099.         // compute sun position
  1100.         final Frame ecef = earth.getBodyFrame();
  1101.         final FieldVector3D<T> sunPos = new FieldVector3D<>(date.getField(),
  1102.                         sun.getPVCoordinates(dateD, ecef).getPosition());
  1103.         final FieldGeodeticPoint<T> sunInBody = earth.transform(sunPos, ecef, date);

  1104.         return getDensity(dateMJD,
  1105.                           sunInBody.getLongitude(), sunInBody.getLatitude(),
  1106.                           inBody.getLongitude(), inBody.getLatitude(), inBody.getAltitude(),
  1107.                           inputParams.getF10(dateD), inputParams.getF10B(dateD),
  1108.                           inputParams.getS10(dateD), inputParams.getS10B(dateD),
  1109.                           inputParams.getXM10(dateD), inputParams.getXM10B(dateD),
  1110.                           inputParams.getY10(dateD), inputParams.getY10B(dateD),
  1111.                           inputParams.getDSTDTC(dateD));

  1112.     }

  1113. }