AbstractJacchiaBowmanModel.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 java.util.stream.IntStream;
  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.Field;
  21. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  22. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  23. import org.hipparchus.util.FastMath;
  24. import org.hipparchus.util.MathArrays;
  25. import org.hipparchus.util.MathUtils;
  26. import org.orekit.bodies.BodyShape;
  27. import org.orekit.bodies.FieldGeodeticPoint;
  28. import org.orekit.bodies.GeodeticPoint;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.frames.Frame;
  32. import org.orekit.time.AbsoluteDate;
  33. import org.orekit.time.DateTimeComponents;
  34. import org.orekit.time.FieldAbsoluteDate;
  35. import org.orekit.time.TimeScale;
  36. import org.orekit.utils.Constants;
  37. import org.orekit.utils.ExtendedPositionProvider;

  38. /**
  39.  * Base class for Jacchia-Bowman atmospheric models.
  40.  * @see JB2006
  41.  * @see JB2008
  42.  * @author Bruce R Bowman (HQ AFSPC, Space Analysis Division), Feb 2006: FORTRAN routine
  43.  * @author Fabien Maussion (java translation)
  44.  * @author Bryan Cazabonne (field elements translation)
  45.  * @since 13.1
  46.  */
  47. public abstract class AbstractJacchiaBowmanModel extends AbstractSunInfluencedAtmosphere {

  48.     /** Minimum altitude (m) for JB models use. */
  49.     private static final double ALT_MIN = 90000.;

  50.     /** The alpha are the thermal diffusion coefficients in equation (6). */
  51.     private static final double[] ALPHA = {0, 0, 0, 0, -0.38};

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

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

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

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

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

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

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

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

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

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

  72.     /** DTC relative data. */
  73.     private static final double[] BDT_SUB = {-0.457512297e+01, -0.512114909e+01, -0.693003609e+02,
  74.                                              0.203716701e+03, 0.703316291e+03, -0.194349234e+04,
  75.                                              0.110651308e+04, -0.174378996e+03, 0.188594601e+04,
  76.                                              -0.709371517e+04, 0.922454523e+04, -0.384508073e+04,
  77.                                              -0.645841789e+01, 0.409703319e+02, -0.482006560e+03,
  78.                                              0.181870931e+04, -0.237389204e+04, 0.996703815e+03,
  79.                                              0.361416936e+02};

  80.     /** DTC relative data.  */
  81.     private static final double[] CDT_SUB = {-0.155986211e+02, -0.512114909e+01, -0.693003609e+02,
  82.                                              0.203716701e+03, 0.703316291e+03, -0.194349234e+04,
  83.                                              0.110651308e+04, -0.220835117e+03, 0.143256989e+04,
  84.                                              -0.318481844e+04, 0.328981513e+04, -0.135332119e+04,
  85.                                              0.199956489e+02, -0.127093998e+02, 0.212825156e+02,
  86.                                              -0.275555432e+01, 0.110234982e+02, 0.148881951e+03,
  87.                                              -0.751640284e+03, 0.637876542e+03, 0.127093998e+02,
  88.                                              -0.212825156e+02, 0.275555432e+01};

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

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

  93.     /** UTC time scale. */
  94.     private final TimeScale utc;

  95.     /** Earth body shape. */
  96.     private final BodyShape earth;

  97.     /** Earliest epoch of solar activity data. */
  98.     private final AbsoluteDate minDataEpoch;

  99.     /** Latest epoch of solar activity data. */
  100.     private final AbsoluteDate maxDataEpoch;

  101.     /**
  102.      * Constructor.
  103.      *
  104.      * @param sun          position provider.
  105.      * @param utc          UTC time scale. Used to computed the day fraction.
  106.      * @param earth        the earth body shape
  107.      * @param minDataEpoch earliest epoch of solar activity data
  108.      * @param maxDataEpoch latest epoch of solar activity data
  109.      */
  110.     protected AbstractJacchiaBowmanModel(final ExtendedPositionProvider sun, final TimeScale utc, final BodyShape earth,
  111.                                          final AbsoluteDate minDataEpoch, final AbsoluteDate maxDataEpoch) {
  112.         super(sun);
  113.         this.utc          = utc;
  114.         this.earth        = earth;
  115.         this.minDataEpoch = minDataEpoch;
  116.         this.maxDataEpoch = maxDataEpoch;
  117.     }

  118.     /** Get the UTC time scale.
  119.      * @return UTC time scale
  120.      */
  121.     public TimeScale getUtc() {
  122.         return utc;
  123.     }

  124.     /** Get the Earth body shape.
  125.      * @return the Earth body shape
  126.      */
  127.     public BodyShape getEarth() {
  128.         return earth;
  129.     }

  130.     /** {@inheritDoc}*/
  131.     @Override
  132.     public double getDensity(final AbsoluteDate date, final Vector3D position, final Frame frame) {

  133.         // Verify availability of data
  134.         if (date.compareTo(maxDataEpoch) > 0 || date.compareTo(minDataEpoch) < 0) {
  135.             throw new OrekitException(OrekitMessages.NO_SOLAR_ACTIVITY_AT_DATE, date, minDataEpoch, maxDataEpoch);
  136.         }

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

  139.         // compute sun position
  140.         final Frame ecef = getFrame();
  141.         final Vector3D sunPos = getSunPosition(date, ecef);
  142.         final GeodeticPoint sunInBody = earth.transform(sunPos, ecef, date);
  143.         return computeDensity(date, sunInBody.getLongitude(), sunInBody.getLatitude(), inBody.getLongitude(), inBody.getLatitude(), inBody.getAltitude());
  144.     }

  145.     /** {@inheritDoc}*/
  146.     @Override
  147.     public <T extends CalculusFieldElement<T>> T getDensity(final FieldAbsoluteDate<T> date, final FieldVector3D<T> position, final Frame frame) {

  148.         // Verify availability of data
  149.         final AbsoluteDate dateD = date.toAbsoluteDate();
  150.         if (dateD.compareTo(maxDataEpoch) > 0 || dateD.compareTo(minDataEpoch) < 0) {
  151.             throw new OrekitException(OrekitMessages.NO_SOLAR_ACTIVITY_AT_DATE, dateD, minDataEpoch, maxDataEpoch);
  152.         }

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

  155.         // compute sun position
  156.         final Frame ecef = getFrame();
  157.         final FieldVector3D<T> sunPos = getSunPosition(date, ecef);
  158.         final FieldGeodeticPoint<T> sunInBody = earth.transform(sunPos, ecef, date);
  159.         return computeDensity(date,
  160.                               sunInBody.getLongitude(), sunInBody.getLatitude(),
  161.                               inBody.getLongitude(), inBody.getLatitude(), inBody.getAltitude());
  162.     }

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

  168.     /** Computes the local density with initial entries.
  169.      * @param date computation epoch
  170.      * @param sunRA Right Ascension of Sun (radians)
  171.      * @param sunDecli Declination of Sun (radians)
  172.      * @param satLon Right Ascension of position (radians)
  173.      * @param satLat Geocentric latitude of position (radians)
  174.      * @param satAlt Height of position (m)
  175.      * @return total mass-Density at input position (kg/m³)
  176.      */
  177.     protected double computeDensity(final AbsoluteDate date,
  178.                                     final double sunRA, final double sunDecli,
  179.                                     final double satLon, final double satLat, final double satAlt) {

  180.         if (satAlt < ALT_MIN) {
  181.             throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD, satAlt, ALT_MIN);
  182.         }
  183.         final double altKm = satAlt / 1000.0;

  184.         final DateTimeComponents dt = date.getComponents(utc);
  185.         final double dateMJD = dt.getDate().getMJD() +
  186.                                dt.getTime().getSecondsInLocalDay() / Constants.JULIAN_DAY;

  187.         // Equation (14)
  188.         // Temperature equation obtained using numerous satellites for the years from 1996 through 2004 when all new solar indices were available
  189.         final double tsubc = computeTc(date);

  190.         // Equation (15)
  191.         final double eta = 0.5 * FastMath.abs(satLat - sunDecli);
  192.         final double theta = 0.5 * FastMath.abs(satLat + sunDecli);

  193.         // Equation (16)
  194.         final double h   = satLon - sunRA;
  195.         final double tau = h - 0.64577182 + 0.10471976 * FastMath.sin(h + 0.75049158);
  196.         final double solarTime = solarTimeHour(h);

  197.         // Equation (17)
  198.         final double tsubl = tSubL(eta, theta, tau, tsubc);

  199.         // Compute correction to dTc for local solar time and lat correction
  200.         final double dtclst = dTc(getF10(date), solarTime, satLat, altKm);

  201.         // Compute the local exospheric temperature.
  202.         final double tInf = computeTInf(date, tsubl, dtclst);

  203.         // Equation (9)
  204.         final double tsubx = tSubX(tInf);

  205.         // Equation (11)
  206.         final double gsubx = gSubX(tsubx);

  207.         // The TC array will be an argument in the call to "localTemp"
  208.         final double[] tc = tSubCArray(tsubx, gsubx, tInf);

  209.         // Equation (5)
  210.         final double z1    = 90.;
  211.         final double z2    = FastMath.min(altKm, 105.0);
  212.         double al          = FastMath.log(z2 / z1);
  213.         int n              = (int) FastMath.floor(al / R1) + 1;
  214.         double zr          = FastMath.exp(al / n);
  215.         final double mb1   = mBar(z1);
  216.         final double tloc1 = localTemp(z1, tc);
  217.         double zend        = z1;
  218.         double sub2        = 0.;
  219.         double ain         = mb1 * gravity(z1) / tloc1;
  220.         double mb2         = 0;
  221.         double tloc2       = 0;
  222.         double z           = 0;
  223.         double gravl       = 0;

  224.         for (int i = 0; i < n; ++i) {
  225.             z = zend;
  226.             zend = zr * z;
  227.             final double dz = 0.25 * (zend - z);
  228.             double sum1 = WT[0] * ain;
  229.             for (int j = 1; j < 5; ++j) {
  230.                 z += dz;
  231.                 mb2   = mBar(z);
  232.                 tloc2 = localTemp(z, tc);
  233.                 gravl = gravity(z);
  234.                 ain   = mb2 * gravl / tloc2;
  235.                 sum1 += WT[j] * ain;
  236.             }
  237.             sub2 += dz * sum1;
  238.         }
  239.         double rho = 3.46e-6 * mb2 * tloc1 / FastMath.exp(sub2 / RSTAR) / (mb1 * tloc2);

  240.         // Equation (2)
  241.         final double anm = AVOGAD * rho;
  242.         final double an = anm / mb2;

  243.         // Equation (3)
  244.         double fact2 = anm / 28.960;
  245.         final double[] aln = new double[6];
  246.         aln[0] = FastMath.log(FRAC[0] * fact2);
  247.         aln[3] = FastMath.log(FRAC[2] * fact2);
  248.         aln[4] = FastMath.log(FRAC[3] * fact2);
  249.         // Equation (4)
  250.         aln[1] = FastMath.log(fact2 * (1. + FRAC[1]) - an);
  251.         aln[2] = FastMath.log(2. * (an - fact2));

  252.         if (altKm <= 105.0) {
  253.             // Put in negligible hydrogen for use in DO-LOOP 13
  254.             aln[5] = aln[4] - 25.0;
  255.         }
  256.         else {
  257.             // Equation (6)
  258.             al = FastMath.log(FastMath.min(altKm, 500.0) / z);
  259.             n = 1 + (int) FastMath.floor(al / R2);
  260.             zr = FastMath.exp(al / n);
  261.             sub2 = 0.;
  262.             ain = gravl / tloc2;

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

  278.             al = FastMath.log(FastMath.max(altKm, 500.0) / z);
  279.             final double r = (altKm > 500.0) ? R3 : R2;
  280.             n = 1 + (int) FastMath.floor(al / r);
  281.             zr = FastMath.exp(al / n);
  282.             double sum3 = 0.;
  283.             double tloc4 = 0;
  284.             for (int i = 0; i < n; ++i) {
  285.                 z = zend;
  286.                 zend = zr * z;
  287.                 final double dz = 0.25 * (zend - z);
  288.                 double sum1 = WT[0] * ain;
  289.                 for (int j = 1; j < 5; ++j) {
  290.                     z += dz;
  291.                     tloc4 = localTemp(z, tc);
  292.                     gravl = gravity(z);
  293.                     ain = gravl / tloc4;
  294.                     sum1 = sum1 + WT[j] * ain;
  295.                 }
  296.                 sum3 = sum3 + dz * sum1;
  297.             }
  298.             final double altr;
  299.             final double hSign;
  300.             if (altKm <= 500.) {
  301.                 altr = FastMath.log(tloc3 / tloc2);
  302.                 fact2 = sub2 / RSTAR;
  303.                 hSign = 1.0;
  304.             }
  305.             else {
  306.                 altr = FastMath.log(tloc4 / tloc2);
  307.                 fact2 = (sub2 + sum3) / RSTAR;
  308.                 hSign = -1.0;
  309.             }
  310.             for (int i = 0; i < 5; ++i) {
  311.                 aln[i] = aln[i] - (1.0 + ALPHA[i]) * altr - fact2 * AMW[i];
  312.             }

  313.             // Equation (7) - Note that in CIRA72, AL10T5 = DLOG10(T500)
  314.             final double al10t5 = FastMath.log10(tInf);
  315.             final double alnh5 = (5.5 * al10t5 - 39.40) * al10t5 + 73.13;
  316.             aln[5] = LOG10 * (alnh5 + 6.) + hSign * (FastMath.log(tloc4 / tloc3) + sum3 * AMW[5] / RSTAR);
  317.         }

  318.         // Equation (24)  - J70 Seasonal-Latitudinal Variation
  319.         final double dlrsl = dlrsl(altKm, dateMJD, satLat);

  320.         // Equation (23) - Computes the semiannual variation
  321.         double dlrsa = 0;
  322.         if (z < 2000.0) {
  323.             // Use new semiannual model DELTA LOG RHO
  324.             dlrsa = semian(date, dayOfYear(dateMJD), altKm);
  325.         }

  326.         // Sum the delta-log-rhos and apply to the number densities.
  327.         // In CIRA72 the following equation contains an actual sum,
  328.         // namely DLR = AL10 * (DLRGM + DLRSA + DLRSL)
  329.         // However, for Jacchia 70, there is no DLRGM or DLRSA.
  330.         final double dlr = LOG10 * (dlrsl + dlrsa);
  331.         for (int i = 0; i < 6; ++i) {
  332.             aln[i] += dlr;
  333.         }

  334.         // Compute mass-density and mean-molecular-weight and
  335.         // convert number density logs from natural to common.
  336.         final double sumnm = IntStream.range(0, 6).mapToDouble(i -> FastMath.exp(aln[i]) * AMW[i]).sum();
  337.         rho = sumnm / AVOGAD;

  338.         // Compute the high altitude exospheric density correction factor
  339.         final double fex = densityCorrectionFactor(altKm, getF10B(date));

  340.         // Apply the exospheric density correction factor.
  341.         rho *= fex;

  342.         return rho;
  343.     }

  344.     /** Computes the local density with initial entries.
  345.      * @param date computation epoch
  346.      * @param sunRA Right Ascension of Sun (radians)
  347.      * @param sunDecli Declination of Sun (radians)
  348.      * @param satLon Right Ascension of position (radians)
  349.      * @param satLat Geocentric latitude of position (radians)
  350.      * @param satAlt Height of position (m)
  351.      * @param <T> type of the elements
  352.      * @return total mass-Density at input position (kg/m³)
  353.      */
  354.     protected <T extends CalculusFieldElement<T>> T computeDensity(final FieldAbsoluteDate<T> date,
  355.                                                                    final T sunRA, final T sunDecli,
  356.                                                                    final T satLon, final T satLat, final T satAlt) {

  357.         if (satAlt.getReal() < ALT_MIN) {
  358.             throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD, satAlt, ALT_MIN);
  359.         }

  360.         final AbsoluteDate dateR = date.toAbsoluteDate();
  361.         final DateTimeComponents components = date.getComponents(utc);
  362.         final T dateMJD = date.durationFrom(new FieldAbsoluteDate<>(date.getField(), components, utc))
  363.                               .add(components.getTime().getSecondsInLocalDay())
  364.                               .divide(Constants.JULIAN_DAY)
  365.                               .add(components.getDate().getMJD());

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

  368.         // Equation (14) (Temperature equation)
  369.         final double tsubc = computeTc(dateR);

  370.         // Equation (15)
  371.         final T eta   = FastMath.abs(satLat.subtract(sunDecli)).multiply(0.5);
  372.         final T theta = FastMath.abs(satLat.add(sunDecli)).multiply(0.5);

  373.         // Equation (16)
  374.         final T h = satLon.subtract(sunRA);
  375.         final T tau = h.subtract(0.64577182).add(h.add(0.75049158).sin().multiply(0.10471976));
  376.         final T solarTime = solarTimeHour(h);

  377.         // Equation (17)
  378.         final T tsubl = tSubL(eta, theta, tau, tsubc);

  379.         // Compute correction to dTc for local solar time and lat correction
  380.         final T dtclst = dTc(getF10(dateR), solarTime, satLat, altKm);

  381.         // Compute the local exospheric temperature.
  382.         final T tinf = computeTInf(dateR, tsubl, dtclst);

  383.         // Equation (9)
  384.         final T tsubx = tSubX(tinf);

  385.         // Equation (11)
  386.         final T gsubx = gSubX(tsubx);

  387.         // The TC array will be an argument in the call to "localTemp"
  388.         final T[] tc = tSubCArray(tsubx, gsubx, tinf, field);

  389.         // Equation (5)
  390.         final T z1    = field.getZero().newInstance(90.);
  391.         final T z2    = FastMath.min(altKm, 105.0);
  392.         T al          = z2.divide(z1).log();
  393.         int n         = 1 + (int) FastMath.floor(al.getReal() / R1);
  394.         T zr          = al.divide(n).exp();
  395.         final T mb1   = mBar(z1);
  396.         final T tloc1 = localTemp(z1, tc);
  397.         T zend        = z1;
  398.         T sub2        = field.getZero();
  399.         T ain         = mb1.multiply(gravity(z1)).divide(tloc1);
  400.         T mb2         = field.getZero();
  401.         T tloc2       = field.getZero();
  402.         T z           = field.getZero();
  403.         T gravl       = field.getZero();

  404.         for (int i = 0; i < n; ++i) {
  405.             z = zend;
  406.             zend = zr.multiply(z);
  407.             final T dz = zend.subtract(z).multiply(0.25);
  408.             T sum1 = ain.multiply(WT[0]);
  409.             for (int j = 1; j < 5; ++j) {
  410.                 z = z.add(dz);
  411.                 mb2   = mBar(z);
  412.                 tloc2 = localTemp(z, tc);
  413.                 gravl = gravity(z);
  414.                 ain   = mb2.multiply(gravl).divide(tloc2);
  415.                 sum1  = sum1.add(ain.multiply(WT[j]));
  416.             }
  417.             sub2 = sub2.add(dz.multiply(sum1));
  418.         }
  419.         T rho = mb2.multiply(3.46e-6).multiply(tloc1).divide(sub2.divide(RSTAR).exp().multiply(mb1.multiply(tloc2)));

  420.         // Equation (2)
  421.         final T anm = rho.multiply(AVOGAD);
  422.         final T an  = anm.divide(mb2);

  423.         // Equation (3)
  424.         T fact2  = anm.divide(28.960);
  425.         final T[] aln = MathArrays.buildArray(field, 6);
  426.         aln[0] = fact2.multiply(FRAC[0]).log();
  427.         aln[3] = fact2.multiply(FRAC[2]).log();
  428.         aln[4] = fact2.multiply(FRAC[3]).log();

  429.         // Equation (4)
  430.         aln[1] = fact2.multiply(1. + FRAC[1]).subtract(an).log();
  431.         aln[2] = an.subtract(fact2).multiply(2).log();

  432.         if (altKm.getReal() <= 105.0) {
  433.             // Put in negligible hydrogen for use in DO-LOOP 13
  434.             aln[5] = aln[4].subtract(25.0);
  435.         }
  436.         else {
  437.             // Equation (6)
  438.             al   = FastMath.min(altKm, 500.0).divide(z).log();
  439.             n    = 1 + (int) FastMath.floor(al.getReal() / R2);
  440.             zr   = al.divide(n).exp();
  441.             sub2 = field.getZero();
  442.             ain  = gravl.divide(tloc2);

  443.             T tloc3 = field.getZero();
  444.             for (int i = 0; i < n; ++i) {
  445.                 z = zend;
  446.                 zend = zr.multiply(z);
  447.                 final T dz = zend.subtract(z).multiply(0.25);
  448.                 T sum1 = ain.multiply(WT[0]);
  449.                 for (int j = 1; j < 5; ++j) {
  450.                     z     = z.add(dz);
  451.                     tloc3 = localTemp(z, tc);
  452.                     gravl = gravity(z);
  453.                     ain   = gravl.divide(tloc3);
  454.                     sum1  = sum1.add(ain.multiply(WT[j]));
  455.                 }
  456.                 sub2 = sub2.add(dz.multiply(sum1));
  457.             }

  458.             al = FastMath.max(altKm, 500.0).divide(z).log();
  459.             final double r = (altKm.getReal() > 500.0) ? R3 : R2;
  460.             n = 1 + (int) FastMath.floor(al.getReal() / r);
  461.             zr = al.divide(n).exp();
  462.             T sum3 = field.getZero();
  463.             T tloc4 = field.getZero();
  464.             for (int i = 0; i < n; ++i) {
  465.                 z = zend;
  466.                 zend = zr.multiply(z);
  467.                 final T dz = zend.subtract(z).multiply(0.25);
  468.                 T sum1 = ain.multiply(WT[0]);
  469.                 for (int j = 1; j < 5; ++j) {
  470.                     z = z.add(dz);
  471.                     tloc4 = localTemp(z, tc);
  472.                     gravl = gravity(z);
  473.                     ain   = gravl.divide(tloc4);
  474.                     sum1  = sum1.add(ain.multiply(WT[j]));
  475.                 }
  476.                 sum3 = sum3.add(dz.multiply(sum1));
  477.             }
  478.             final T altr;
  479.             final double hSign;
  480.             if (altKm.getReal() <= 500.) {
  481.                 altr = tloc3.divide(tloc2).log();
  482.                 fact2 = sub2.divide(RSTAR);
  483.                 hSign = 1.0;
  484.             }
  485.             else {
  486.                 altr = tloc4.divide(tloc2).log();
  487.                 fact2 = sub2.add(sum3).divide(RSTAR);
  488.                 hSign = -1.0;
  489.             }
  490.             for (int i = 0; i < 5; ++i) {
  491.                 aln[i] = aln[i].subtract(altr.multiply(1.0 + ALPHA[i])).subtract(fact2.multiply(AMW[i]));
  492.             }

  493.             // Equation (7) - Note that in CIRA72, AL10T5 = DLOG10(T500)
  494.             final T al10t5 = tinf.log10();
  495.             final T alnh5 = al10t5.multiply(5.5).subtract(39.40).multiply(al10t5).add(73.13);
  496.             aln[5] = alnh5.add(6.).multiply(LOG10).add(tloc4.divide(tloc3).log().add(sum3.multiply(AMW[5] / RSTAR)).multiply(hSign));
  497.         }

  498.         // Equation (24)  - J70 Seasonal-Latitudinal Variation
  499.         final T dlrsl = dlrsl(altKm, dateMJD, satLat);

  500.         // Equation (23) - Computes the semiannual variation
  501.         T dlrsa = field.getZero();
  502.         if (z.getReal() < 2000.0) {
  503.             // Use new semiannual model DELTA LOG RHO
  504.             dlrsa = semian(dateR, dayOfYear(dateMJD), altKm);
  505.         }

  506.         // Sum the delta-log-rhos and apply to the number densities.
  507.         // In CIRA72 the following equation contains an actual sum,
  508.         // namely DLR = AL10 * (DLRGM + DLRSA + DLRSL)
  509.         // However, for Jacchia 70, there is no DLRGM or DLRSA.
  510.         final T dlr = dlrsl.add(dlrsa).multiply(LOG10);
  511.         for (int i = 0; i < 6; ++i) {
  512.             aln[i] = aln[i].add(dlr);
  513.         }

  514.         // Compute mass-density and mean-molecular-weight and
  515.         // convert number density logs from natural to common.
  516.         T sumnm = field.getZero();
  517.         for (int i = 0; i < 6; ++i) {
  518.             sumnm = sumnm.add(aln[i].exp().multiply(AMW[i]));
  519.         }
  520.         rho = sumnm.divide(AVOGAD);

  521.         // Compute the high altitude exospheric density correction factor
  522.         final T fex = densityCorrectionFactor(altKm, getF10B(dateR), field);

  523.         // Apply the exospheric density correction factor.
  524.         rho = rho.multiply(fex);

  525.         return rho;
  526.     }

  527.     /** Computes the semi-annual variation (delta log(rho)).
  528.      * @param date computation epoch
  529.      * @param day day of year
  530.      * @param altKm height (km)
  531.      * @return semi-annual variation
  532.      */
  533.     protected abstract double semian(AbsoluteDate date, double day, double altKm);

  534.     /**
  535.      * Computes the local exospheric temperature.
  536.      * @param date computation epoch
  537.      * @param tsubl exospheric temperature ("tsubl" term), given by Equation (17)
  538.      * @param dtclst correction to dTc for local solar time and lat correction
  539.      * @return the local exospheric temperature
  540.      */
  541.     protected abstract double computeTInf(AbsoluteDate date, double tsubl, double dtclst);

  542.     /** Computes the temperature equation.
  543.      * @param date computation epoch
  544.      * @return the temperature equation
  545.      */
  546.     protected abstract double computeTc(AbsoluteDate date);

  547.     /** Get the 10.7-cm Solar flux (1e<sup>-22</sup>*Watt/(m²*Hertz))<br> (Tabular time 1.0 day earlier).
  548.      * @param date computation epoch
  549.      * @return the 10.7-cm Solar flux from model input parameters
  550.      */
  551.     protected abstract double getF10(AbsoluteDate date);

  552.     /** Get the 10.7-cm Solar Flux, averaged 81-day centered on the input time<br> (Tabular time 1.0 day earlier).
  553.      * @param date computation epoch
  554.      * @return the 10.7-cm Solar Flux, averaged 81-day centered on the input time
  555.      */
  556.     protected abstract double getF10B(AbsoluteDate date);

  557.     /** Computes the semi-annual variation (delta log(rho)).
  558.      * @param date computation epoch
  559.      * @param day day of year
  560.      * @param altKm height (km)
  561.      * @param <T> type of the elements
  562.      * @return semi-annual variation
  563.      */
  564.     protected abstract <T extends CalculusFieldElement<T>> T semian(AbsoluteDate date, T day, T altKm);

  565.     /**
  566.      * Computes the local exospheric temperature.
  567.      * @param date computation epoch
  568.      * @param tsubl exospheric temperature ("tsubl" term), given by Equation (17)
  569.      * @param dtclst correction to dTc for local solar time and lat correction
  570.      * @param <T> type of the elements
  571.      * @return the local exospheric temperature
  572.      */
  573.     protected abstract <T extends CalculusFieldElement<T>> T computeTInf(AbsoluteDate date, T tsubl, T dtclst);

  574.     /** Evaluates the solar time in hours.
  575.      * @param h difference between the Right Ascension of position and the Right Ascension of Sun (radians)
  576.      * @return the solar time in hours
  577.      */
  578.     private static double solarTimeHour(final double h) {
  579.         final double solTimeHour = FastMath.toDegrees(h + FastMath.PI) / 15.0;
  580.         if (solTimeHour >= 24) {
  581.             return solTimeHour - 24.;
  582.         }
  583.         if (solTimeHour < 0) {
  584.             return solTimeHour + 24.;
  585.         }
  586.         return solTimeHour;
  587.     }

  588.     /** Evaluates the solar time in hours.
  589.      * @param h difference between the Right Ascension of position and the Right Ascension of Sun (radians)
  590.      * @param <T> type of the field elements
  591.      * @return the solar time in hours
  592.      */
  593.     private static <T extends CalculusFieldElement<T>> T solarTimeHour(final T h) {
  594.         final T solarTime = FastMath.toDegrees(h.add(FastMath.PI)).divide(15.0);
  595.         if (solarTime.getReal() >= 24) {
  596.             return solarTime.subtract(24);
  597.         }
  598.         if (solarTime.getReal() < 0) {
  599.             return solarTime.add(24);
  600.         }
  601.         return solarTime;
  602.     }

  603.     /** Evaluates the exospheric temperature ("tsubl" term), Equation (17).
  604.      * <p>
  605.      * This temperature corresponds to the exospheric temperature in low
  606.      * geomagnetic conditions.
  607.      * </p>
  608.      * @param eta "eta" term, Equation (15)
  609.      * @param theta "theta" term, Equation (15)
  610.      * @param tau "tau" term, Equation (16)
  611.      * @param tsubc exospheric temperature Tc, Equation (14)
  612.      * @return Tl temperature computed by Equation (17)
  613.      */
  614.     private static double tSubL(final double eta, final double theta,
  615.                                final double tau, final double tsubc) {
  616.         final double cosEta   = FastMath.pow(FastMath.cos(eta), 2.5);
  617.         final double sinTheta = FastMath.pow(FastMath.sin(theta), 2.5);
  618.         final double cosTau   = FastMath.abs(FastMath.cos(0.5 * tau));
  619.         final double df       = sinTheta + (cosEta - sinTheta) * cosTau * cosTau * cosTau;
  620.         return tsubc * (1. + 0.31 * df);
  621.     }

  622.     /** Evaluates exospheric temperature ("tsubl" term), Equation (17).
  623.      * <p>
  624.      * This temperature corresponds to the exospheric temperature in low
  625.      * geomagnetic conditions.
  626.      * </p>
  627.      * @param eta "eta" term, Equation (15)
  628.      * @param theta "theta" term, Equation (15)
  629.      * @param tau "tau" term, Equation (16)
  630.      * @param tsubc exospheric temperature Tc, Equation (14)
  631.      * @param <T> type of the field elements
  632.      * @return Tl temperature computed by Equation (17)
  633.      */
  634.     private static <T extends CalculusFieldElement<T>>  T tSubL(final T eta, final T theta,
  635.                                                                final T tau, final double tsubc) {
  636.         final T cos      = eta.cos();
  637.         final T cosEta   = cos.square().multiply(cos.sqrt());
  638.         final T sin      = theta.sin();
  639.         final T sinTheta = sin.square().multiply(sin.sqrt());
  640.         final T cosTau   = tau.multiply(0.5).cos().abs();
  641.         final T df       = sinTheta.add(cosEta.subtract(sinTheta).multiply(cosTau).multiply(cosTau).multiply(cosTau));
  642.         return df.multiply(0.31).add(1).multiply(tsubc);
  643.     }

  644.     /** Evaluates the inflection temperature ("tsubx" term), Equation (9).
  645.      * <p>
  646.      * This temperature corresponds to the temperature at the inflexion altitude.
  647.      * At this altitude, the temperature profile has an inflection point.
  648.      * </p>
  649.      * @param tInf local exospheric temperature
  650.      * @return Tx temperature at inflection point, computed by Equation (9)
  651.      */
  652.     private static double tSubX(final double tInf) {
  653.         return 444.3807 + 0.02385 * tInf - 392.8292 * FastMath.exp(-0.0021357 * tInf);
  654.     }

  655.     /** Evaluates the inflection temperature ("tsubx" term), Equation (9).
  656.      * <p>
  657.      * This temperature corresponds to the temperature at the inflexion altitude.
  658.      * At this altitude, the temperature profile has an inflection point.
  659.      * </p>
  660.      * @param tInf local exospheric temperature
  661.      * @param <T> type of the field elements
  662.      * @return Tx temperature at inflection point, computed by Equation (9)
  663.      */
  664.     private static <T extends CalculusFieldElement<T>>  T tSubX(final T tInf) {
  665.         return tInf.multiply(0.02385).add(444.3807).subtract(tInf.multiply(-0.0021357).exp().multiply(392.8292));
  666.     }

  667.     /** Evaluates the temperature gradient at the inflection point ("gsubx" term), Equation (11).
  668.      * @param tSubX temperature at inflection point
  669.      * @return the temperature gradient at the inflection point computed by Equation (11)
  670.      */
  671.     private static double gSubX(final double tSubX) {
  672.         return 0.054285714 * (tSubX - 183.);
  673.     }

  674.     /** Evaluates the temperature gradient at the inflection point ("gsubx" term), Equation (11).
  675.      * @param tSubX temperature at inflection point
  676.      * @param <T> type of the field elements
  677.      * @return the temperature gradient at the inflection point computed by Equation (11)
  678.      */
  679.     private static <T extends CalculusFieldElement<T>> T gSubX(final T tSubX) {
  680.         return tSubX.subtract(183.).multiply(0.054285714);
  681.     }

  682.     /** Evaluates the Tc array.
  683.      * <p>
  684.      * The Tc array will be used to evaluates {@link #localTemp(double, double[])}.
  685.      * </p>
  686.      * @param tSubX temperature at inflection point
  687.      * @param gSubX the temperature gradient at the inflection point
  688.      * @param tInf local exospheric temperature
  689.      * @return the Tc array
  690.      */
  691.     private static double[] tSubCArray(final double tSubX, final double gSubX,
  692.                                       final double tInf) {
  693.         final double[] tc = new double[4];
  694.         tc[0] = tSubX;
  695.         tc[1] = gSubX;
  696.         tc[2] = (tInf - tSubX) / MathUtils.SEMI_PI;
  697.         tc[3] = gSubX / tc[2];
  698.         return tc;
  699.     }

  700.     /** Evaluates the Tc array.
  701.      * <p>
  702.      * The Tc array will be used to evaluates {@link #localTemp(CalculusFieldElement, CalculusFieldElement[])}
  703.      * </p>
  704.      * @param tSubX temperature at inflection point
  705.      * @param gSubX the temperature gradient at the inflection point
  706.      * @param tInf local exospheric temperature
  707.      * @param field field for the elements
  708.      * @param <T> type of the field elements
  709.      * @return the Tc array
  710.      */
  711.     private static <T extends CalculusFieldElement<T>> T[] tSubCArray(final T tSubX, final T gSubX,
  712.                                                                      final T tInf, final Field<T> field) {
  713.         final T[] tc = MathArrays.buildArray(field, 4);
  714.         tc[0] = tSubX;
  715.         tc[1] = gSubX;
  716.         tc[2] = tInf.subtract(tSubX).divide(MathUtils.SEMI_PI);
  717.         tc[3] = gSubX.divide(tc[2]);
  718.         return tc;
  719.     }

  720.     /** Evaluates the local temperature, Equation (10) or (13) depending on altitude.
  721.      * @param z  altitude
  722.      * @param tc tc array
  723.      * @return temperature profile
  724.      */
  725.     private static double localTemp(final double z, final double[] tc) {
  726.         final double dz = z - 125;
  727.         if (dz <= 0) {
  728.             return ((-9.8204695e-6 * dz - 7.3039742e-4) * dz * dz + 1.0) * dz * tc[1] + tc[0];
  729.         }
  730.         else {
  731.             return tc[0] + tc[2] * FastMath.atan(tc[3] * dz * (1 + 4.5e-6 * FastMath.pow(dz, 2.5)));
  732.         }
  733.     }

  734.     /** Evaluates the local temperature, Equation (10) or (13) depending on altitude.
  735.      * @param z altitude
  736.      * @param tc tc array
  737.      * @return temperature profile
  738.      * @param <T> type of the field elements
  739.      */
  740.     private static <T extends CalculusFieldElement<T>> T localTemp(final T z, final T[] tc) {
  741.         final T dz = z.subtract(125.);
  742.         if (dz.getReal() <= 0.) {
  743.             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]);
  744.         } else {
  745.             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]);
  746.         }
  747.     }

  748.     /** Evaluates mean molecualr mass, Equation (1).
  749.      * @param z altitude (km)
  750.      * @return mean molecular mass
  751.      */
  752.     private static double mBar(final double z) {
  753.         final double dz = z - 100.;
  754.         double amb = CXAMB[6];
  755.         for (int i = 5; i >= 0; --i) {
  756.             amb = dz * amb + CXAMB[i];
  757.         }
  758.         return amb;
  759.     }

  760.     /** Evaluates mean molecualr mass, Equation (1).
  761.      * @param z altitude (km)
  762.      * @return mean molecular mass
  763.      * @param <T> type of the field elements
  764.      */
  765.     private static <T extends CalculusFieldElement<T>>  T mBar(final T z) {
  766.         final T dz = z.subtract(100.);
  767.         T amb = z.getField().getZero().newInstance(CXAMB[6]);
  768.         for (int i = 5; i >= 0; --i) {
  769.             amb = dz.multiply(amb).add(CXAMB[i]);
  770.         }
  771.         return amb;
  772.     }

  773.     /** Evaluates the gravity at the altitude, Equation (8).
  774.      * @param z altitude (km)
  775.      * @return the gravity field (m/s2)
  776.      */
  777.     private static double gravity(final double z) {
  778.         final double temp = 1.0 + z / EARTH_RADIUS;
  779.         return Constants.G0_STANDARD_GRAVITY / (temp * temp);
  780.     }

  781.     /** Evaluates the gravity at the altitude, Equation (8).
  782.      * @param z altitude (km)
  783.      * @return the gravity (m/s2)
  784.      * @param <T> type of the field elements
  785.      */
  786.     private static <T extends CalculusFieldElement<T>> T gravity(final T z) {
  787.         final T tmp = z.divide(EARTH_RADIUS).add(1);
  788.         return tmp.multiply(tmp).reciprocal().multiply(Constants.G0_STANDARD_GRAVITY);
  789.     }

  790.     /** Compute day of year.
  791.      * @param dateMJD Modified Julian date
  792.      * @return the number days in year
  793.      */
  794.     private static double dayOfYear(final double dateMJD) {
  795.         final double d1950 = dateMJD - 33281;

  796.         int iyday = (int) d1950;
  797.         final double frac = d1950 - iyday;
  798.         iyday = iyday + 364;

  799.         int itemp = iyday / 1461;

  800.         iyday = iyday - itemp * 1461;
  801.         itemp = iyday / 365;
  802.         if (itemp >= 3) {
  803.             itemp = 3;
  804.         }
  805.         iyday = iyday - 365 * itemp + 1;
  806.         return iyday + frac;
  807.     }

  808.     /** Compute day of year.
  809.      * @param dateMJD Modified Julian date
  810.      * @param <T> type of the field elements
  811.      * @return the number days in year
  812.      */
  813.     private static <T extends CalculusFieldElement<T>> T dayOfYear(final T dateMJD) {
  814.         final T d1950 = dateMJD.subtract(33281);

  815.         int iyday = (int) d1950.getReal();
  816.         final T frac = d1950.subtract(iyday);
  817.         iyday = iyday + 364;

  818.         int itemp = iyday / 1461;

  819.         iyday = iyday - itemp * 1461;
  820.         itemp = iyday / 365;
  821.         if (itemp >= 3) {
  822.             itemp = 3;
  823.         }
  824.         iyday = iyday - 365 * itemp + 1;
  825.         return frac.add(iyday);
  826.     }

  827.     /** Evaluates the J70 Seasonal-Latitudinal Variation, Equation (24).
  828.      * @param altKm satellite altitude (km)
  829.      * @param dateMJD Modified Julian date
  830.      * @param satLat Geocentric latitude of position (radians)
  831.      * @return the J70 Seasonal-Latitudinal Variation
  832.      */
  833.     private static double dlrsl(final double altKm, final double dateMJD, final double satLat) {
  834.         final double capPhi = ((dateMJD - 36204.0) / 365.2422) % 1;
  835.         final int signum = (satLat >= 0) ? 1 : -1;
  836.         final double sinLat = FastMath.sin(satLat);
  837.         final double hm90  = altKm - 90.;
  838.         return 0.02 * hm90 * FastMath.exp(-0.045 * hm90) * signum * FastMath.sin(MathUtils.TWO_PI * capPhi + 1.72) * sinLat * sinLat;
  839.     }

  840.     /** Evaluates the J70 Seasonal-Latitudinal Variation, Equation (24).
  841.      * @param altKm satellite altitude (km)
  842.      * @param dateMJD Modified Julian date
  843.      * @param satLat Geocentric latitude of position (radians)
  844.      * @param <T> type of the elements
  845.      * @return the J70 Seasonal-Latitudinal Variation
  846.      */
  847.     private static <T extends CalculusFieldElement<T>> T dlrsl(final T altKm, final T dateMJD, final T satLat) {
  848.         T capPhi = dateMJD.subtract(36204.0).divide(365.2422);
  849.         capPhi = capPhi.subtract(FastMath.floor(capPhi.getReal()));
  850.         final int signum = (satLat.getReal() >= 0.) ? 1 : -1;
  851.         final T sinLat = satLat.sin();
  852.         final T hm90  = altKm.subtract(90.);
  853.         return hm90.multiply(0.02).multiply(hm90.multiply(-0.045).exp()).multiply(capPhi.multiply(MathUtils.TWO_PI).add(1.72).sin()).multiply(signum).multiply(sinLat).multiply(sinLat);
  854.     }

  855.     /** Evaluates the high altitude exospheric density correction factor.
  856.      * @param altKm satellite altitude in km
  857.      * @param f10B 10.7-cm Solar Flux, averaged 81-day centered on the input time
  858.      * @return the high altitude exospheric density correction factor
  859.      */
  860.     private static double densityCorrectionFactor(final double altKm, final double f10B) {
  861.         if (altKm >= 1000.0 && altKm < 1500.0) {
  862.             final double zeta = (altKm - 1000.) * 0.002;
  863.             final double f15c = CHT[0] + CHT[1] * f10B + CHT[2] * 1500.0 + CHT[3] * f10B * 1500.0;
  864.             final double f15cZeta = (CHT[2] + CHT[3] * f10B) * 500.0;
  865.             final double fex2 = 3.0 * f15c - f15cZeta - 3.0;
  866.             final double fex3 = f15cZeta - 2.0 * f15c + 2.0;
  867.             return 1.0 + zeta * zeta * (fex2 + fex3 * zeta);
  868.         }
  869.         if (altKm >= 1500.0) {
  870.             return CHT[0] + CHT[1] * f10B + CHT[2] * altKm + CHT[3] * f10B * altKm;
  871.         }
  872.         return 1.0;
  873.     }

  874.     /** Evaluates the high altitude exospheric density correction factor.
  875.      * @param altKm satellite altitude in km
  876.      * @param f10B 10.7-cm Solar Flux, averaged 81-day centered on the input time
  877.      * @param field field of the elements
  878.      * @param <T> type of the field elements
  879.      * @return the high altitude exospheric density correction factor
  880.      */
  881.     private static <T extends CalculusFieldElement<T>> T densityCorrectionFactor(final T altKm, final double f10B,
  882.                                                                                 final Field<T> field) {
  883.         if (altKm.getReal() >= 1000.0 && altKm.getReal() < 1500.0) {
  884.             final T zeta = altKm.subtract(1000.).multiply(0.002);
  885.             final double f15c = CHT[0] + CHT[1] * f10B + CHT[2] * 1500.0 + CHT[3] * f10B * 1500.0;
  886.             final double f15cZeta = (CHT[2] + CHT[3] * f10B) * 500.0;
  887.             final double fex2 = 3.0 * f15c - f15cZeta - 3.0;
  888.             final double fex3 = f15cZeta - 2.0 * f15c + 2.0;
  889.             return field.getOne().add(zeta.multiply(zeta).multiply(zeta.multiply(fex3).add(fex2)));
  890.         }
  891.         if (altKm.getReal() >= 1500.0) {
  892.             return altKm.multiply(CHT[3] * f10B).add(altKm.multiply(CHT[2])).add(CHT[0] + CHT[1] * f10B);
  893.         }
  894.         return field.getOne();
  895.     }

  896.     /** Compute daily temperature correction for Jacchia-Bowman model.
  897.      * @param f10 solar flux index
  898.      * @param solarTime local solar time (hours in [0, 24[)
  899.      * @param satLat sat lat (radians)
  900.      * @param satAlt height (km)
  901.      * @return dTc correction
  902.      */
  903.     private static double dTc(final double f10, final double solarTime,
  904.                               final double satLat, final double satAlt) {
  905.         final double st = solarTime / 24.0;
  906.         final double cs = FastMath.cos(satLat);
  907.         final double fs = (f10 - 100.0) / 100.0;

  908.         // Calculates dTc according to height
  909.         if (satAlt >= 120 && satAlt <= 200) {
  910.             final double dtc200   = poly2CDTC(fs, st, cs);
  911.             final double dtc200dz = poly1CDTC(fs, st, cs);
  912.             final double cc       = 3.0 * dtc200 - dtc200dz;
  913.             final double dd       = dtc200 - cc;
  914.             final double zp       = (satAlt - 120.0) / 80.0;
  915.             return zp * zp * (cc + dd * zp);

  916.         } else if (satAlt > 200.0 && satAlt <= 240.0) {
  917.             final double h = (satAlt - 200.0) / 50.0;
  918.             return poly1CDTC(fs, st, cs) * h + poly2CDTC(fs, st, cs);

  919.         } else if (satAlt > 240.0 && satAlt <= 300.0) {
  920.             final double h        = 0.8;
  921.             final double bb       = poly1CDTC(fs, st, cs);
  922.             final double aa       = bb * h + poly2CDTC(fs, st, cs);
  923.             final double p2BDT    = poly2BDTC(st);
  924.             final double dtc300   = poly1BDTC(fs, st, cs, 3 * p2BDT);
  925.             final double dtc300dz = cs * p2BDT;
  926.             final double cc       = 3.0 * dtc300 - dtc300dz - 3.0 * aa - 2.0 * bb;
  927.             final double dd       = dtc300 - aa - bb - cc;
  928.             final double zp       = (satAlt - 240.0) / 60.0;
  929.             return aa + zp * (bb + zp * (cc + zp * dd));

  930.         } else if (satAlt > 300.0 && satAlt <= 600.0) {
  931.             final double h = satAlt / 100.0;
  932.             return poly1BDTC(fs, st, cs, h * poly2BDTC(st));

  933.         } else if (satAlt > 600.0 && satAlt <= 800.0) {
  934.             final double poly2 = poly2BDTC(st);
  935.             final double aa    = poly1BDTC(fs, st, cs, 6 * poly2);
  936.             final double bb    = cs * poly2;
  937.             final double cc    = -(3.0 * aa + 4.0 * bb) / 4.0;
  938.             final double dd    = (aa + bb) / 4.0;
  939.             final double zp    = (satAlt - 600.0) / 100.0;
  940.             return aa + zp * (bb + zp * (cc + zp * dd));

  941.         }
  942.         return 0.;
  943.     }

  944.     /** Compute daily temperature correction for Jacchia-Bowman model.
  945.      * @param f10 solar flux index
  946.      * @param solarTime local solar time (hours in [0, 24[)
  947.      * @param satLat sat lat (radians)
  948.      * @param satAlt height (km)
  949.      * @param <T> type of the filed elements
  950.      * @return dTc correction
  951.      */
  952.     private static <T extends CalculusFieldElement<T>> T dTc(final double f10, final T solarTime,
  953.                                                              final T satLat, final T satAlt) {
  954.         final T      st = solarTime.divide(24.0);
  955.         final T      cs = satLat.cos();
  956.         final double fs = (f10 - 100.0) / 100.0;

  957.         // Calculates dTc according to height
  958.         if (satAlt.getReal() >= 120 && satAlt.getReal() <= 200) {
  959.             final T dtc200   = poly2CDTC(fs, st, cs);
  960.             final T dtc200dz = poly1CDTC(fs, st, cs);
  961.             final T cc       = dtc200.multiply(3).subtract(dtc200dz);
  962.             final T dd       = dtc200.subtract(cc);
  963.             final T zp       = satAlt.subtract(120.0).divide(80.0);
  964.             return zp.multiply(zp).multiply(cc.add(dd.multiply(zp)));

  965.         } else if (satAlt.getReal() > 200.0 && satAlt.getReal() <= 240.0) {
  966.             final T h = satAlt.subtract(200.0).divide(50.0);
  967.             return poly1CDTC(fs, st, cs).multiply(h).add(poly2CDTC(fs, st, cs));

  968.         } else if (satAlt.getReal() > 240.0 && satAlt.getReal() <= 300.0) {
  969.             final T h        = solarTime.getField().getZero().newInstance(0.8);
  970.             final T bb       = poly1CDTC(fs, st, cs);
  971.             final T aa       = bb.multiply(h).add(poly2CDTC(fs, st, cs));
  972.             final T p2BDT    = poly2BDTC(st);
  973.             final T dtc300   = poly1BDTC(fs, st, cs, p2BDT.multiply(3));
  974.             final T dtc300dz = cs.multiply(p2BDT);
  975.             final T cc       = dtc300.multiply(3).subtract(dtc300dz).subtract(aa.multiply(3)).subtract(bb.multiply(2));
  976.             final T dd       = dtc300.subtract(aa).subtract(bb).subtract(cc);
  977.             final T zp       = satAlt.subtract(240.0).divide(60.0);
  978.             return aa.add(zp.multiply(bb.add(zp.multiply(cc.add(zp.multiply(dd))))));

  979.         } else if (satAlt.getReal() > 300.0 && satAlt.getReal() <= 600.0) {
  980.             final T h = satAlt.divide(100.0);
  981.             return poly1BDTC(fs, st, cs, h.multiply(poly2BDTC(st)));

  982.         } else if (satAlt.getReal() > 600.0 && satAlt.getReal() <= 800.0) {
  983.             final T poly2 = poly2BDTC(st);
  984.             final T aa    = poly1BDTC(fs, st, cs, poly2.multiply(6));
  985.             final T bb    = cs.multiply(poly2);
  986.             final T cc    = aa.multiply(3).add(bb.multiply(4)).divide(-4.0);
  987.             final T dd    = aa.add(bb).divide(4.0);
  988.             final T zp    = satAlt.subtract(600.0).divide(100.0);
  989.             return aa.add(zp.multiply(bb.add(zp.multiply(cc.add(zp.multiply(dd))))));

  990.         }
  991.         return satLat.getField().getZero();
  992.     }

  993.     /** Calculates first polynomial with CDTC array.
  994.      * @param fs scaled flux f10
  995.      * @param st local solar time in [0, 1[
  996.      * @param cs cosine of satLat
  997.      * @return the value of the polynomial
  998.      */
  999.     private static double poly1CDTC(final double fs, final double st, final double cs) {
  1000.         return CDT_SUB[0] +
  1001.                fs * (CDT_SUB[1] + st * (CDT_SUB[2] + st * (CDT_SUB[3] + st * (CDT_SUB[4] + st * (CDT_SUB[5] + st * CDT_SUB[6]))))) +
  1002.                cs * st * (CDT_SUB[7] + st * (CDT_SUB[8] + st * (CDT_SUB[9] + st * (CDT_SUB[10] + st * CDT_SUB[11])))) +
  1003.                cs * (CDT_SUB[12] + fs * (CDT_SUB[13] + st * (CDT_SUB[14] + st * CDT_SUB[15])));
  1004.     }

  1005.     /** Calculates first polynomial with CDTC array.
  1006.      * @param fs scaled flux f10
  1007.      * @param st local solar time in [0, 1[
  1008.      * @param cs cosine of satLat
  1009.      * @param <T> type of the field elements
  1010.      * @return the value of the polynomial
  1011.      */
  1012.     private static <T extends CalculusFieldElement<T>>  T poly1CDTC(final double fs, final T st, final T cs) {
  1013.         return st.multiply(CDT_SUB[6]).
  1014.                add(CDT_SUB[5]).multiply(st).
  1015.                add(CDT_SUB[4]).multiply(st).
  1016.                add(CDT_SUB[3]).multiply(st).
  1017.                add(CDT_SUB[2]).multiply(st).
  1018.                add(CDT_SUB[1]).multiply(fs).
  1019.                add(st.multiply(CDT_SUB[11]).
  1020.                add(CDT_SUB[10]).multiply(st).
  1021.                add(CDT_SUB[ 9]).multiply(st).
  1022.                add(CDT_SUB[ 8]).multiply(st).
  1023.                add(CDT_SUB[7]).multiply(st).multiply(cs)).
  1024.                add(st.multiply(CDT_SUB[15]).
  1025.                add(CDT_SUB[14]).multiply(st).
  1026.                add(CDT_SUB[13]).multiply(fs).
  1027.                add(CDT_SUB[12]).multiply(cs)).
  1028.                add(CDT_SUB[0]);
  1029.     }

  1030.     /** Calculates second polynomial with CDTC array.
  1031.      * @param fs scaled flux f10
  1032.      * @param st local solar time in [0, 1[
  1033.      * @param cs cosine of satLat
  1034.      * @return the value of the polynomial
  1035.      */
  1036.     private static double poly2CDTC(final double fs, final double st, final double cs) {
  1037.         return CDT_SUB[16] + st * cs * (CDT_SUB[17] + st * (CDT_SUB[18] + st * CDT_SUB[19])) +
  1038.                fs * cs * (CDT_SUB[20] + st * (CDT_SUB[21] + st * CDT_SUB[22]));
  1039.     }

  1040.     /** Calculates second polynomial with CDTC array.
  1041.      * @param fs scaled flux f10
  1042.      * @param st local solar time in [0, 1[
  1043.      * @param cs cosine of satLat
  1044.      * @param <T> type of the field elements
  1045.      * @return the value of the polynomial
  1046.      */
  1047.     private static <T extends CalculusFieldElement<T>>  T poly2CDTC(final double fs, final T st, final T cs) {
  1048.         return st.multiply(CDT_SUB[19]).
  1049.                add(CDT_SUB[18]).multiply(st).
  1050.                add(CDT_SUB[17]).multiply(cs).multiply(st).
  1051.                add(st.multiply(CDT_SUB[22]).
  1052.                add(CDT_SUB[21]).multiply(st).
  1053.                add(CDT_SUB[20]).multiply(cs).multiply(fs)).
  1054.                add(CDT_SUB[16]);
  1055.     }

  1056.     /** Calculates first polynomial with BDTC array.
  1057.      * @param fs scaled flux f10
  1058.      * @param st local solar time in [0, 1[
  1059.      * @param cs cosine of satLat
  1060.      * @param hp scaled height * poly2BDTC
  1061.      * @return the value of the polynomial
  1062.      */
  1063.     private static double poly1BDTC(final double fs, final double st, final double cs, final double hp) {
  1064.         return BDT_SUB[0] +
  1065.                fs * (BDT_SUB[1] + st * (BDT_SUB[2] + st * (BDT_SUB[3] + st * (BDT_SUB[4] + st * (BDT_SUB[5] + st * BDT_SUB[6]))))) +
  1066.                cs * (st * (BDT_SUB[7] + st * (BDT_SUB[8] + st * (BDT_SUB[9] + st * (BDT_SUB[10] + st * BDT_SUB[11])))) + hp + BDT_SUB[18]);
  1067.     }

  1068.     /** Calculates first polynomial with BDTC array.
  1069.      * @param fs scaled flux f10
  1070.      * @param st local solar time in [0, 1[
  1071.      * @param cs cosine of satLat
  1072.      * @param hp scaled height * poly2BDTC
  1073.      * @param <T> type of the field elements
  1074.      * @return the value of the polynomial
  1075.      */
  1076.     private static <T extends CalculusFieldElement<T>>  T poly1BDTC(final double fs, final T st, final T cs, final T hp) {
  1077.         return st.multiply(BDT_SUB[6]).
  1078.                add(BDT_SUB[5]).multiply(st).
  1079.                add(BDT_SUB[4]).multiply(st).
  1080.                add(BDT_SUB[3]).multiply(st).
  1081.                add(BDT_SUB[2]).multiply(st).
  1082.                add(BDT_SUB[1]).multiply(fs).
  1083.                add(st.multiply(BDT_SUB[11]).
  1084.                add(BDT_SUB[10]).multiply(st).
  1085.                add(BDT_SUB[ 9]).multiply(st).
  1086.                add(BDT_SUB[ 8]).multiply(st).
  1087.                add(BDT_SUB[ 7]).multiply(st).
  1088.                add(hp).add(BDT_SUB[18]).multiply(cs)).
  1089.                add(BDT_SUB[0]);
  1090.     }

  1091.     /** Calculates second polynomial with BDTC array.
  1092.      * @param st local solar time in [0, 1[
  1093.      * @return the value of the polynomial
  1094.      */
  1095.     private static double poly2BDTC(final double st) {
  1096.         return BDT_SUB[12] + st * (BDT_SUB[13] + st * (BDT_SUB[14] + st * (BDT_SUB[15] + st * (BDT_SUB[16] + st * BDT_SUB[17]))));
  1097.     }

  1098.         /** Calculates second polynomial with BDTC array.
  1099.      * @param st local solar time in [0, 1[
  1100.      * @param <T> type of the field elements
  1101.      * @return the value of the polynomial
  1102.      */
  1103.     private static <T extends CalculusFieldElement<T>>  T poly2BDTC(final T st) {
  1104.         return st.multiply(BDT_SUB[17]).
  1105.                add(BDT_SUB[16]).multiply(st).
  1106.                add(BDT_SUB[15]).multiply(st).
  1107.                add(BDT_SUB[14]).multiply(st).
  1108.                add(BDT_SUB[13]).multiply(st).
  1109.                add(BDT_SUB[12]);
  1110.     }

  1111. }