NeQuickModel.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.ionosphere.nequick;

  18. import java.util.Collections;
  19. import java.util.List;

  20. import org.hipparchus.CalculusFieldElement;
  21. import org.hipparchus.Field;
  22. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  23. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  24. import org.hipparchus.util.FastMath;
  25. import org.hipparchus.util.MathArrays;
  26. import org.orekit.bodies.BodyShape;
  27. import org.orekit.bodies.FieldGeodeticPoint;
  28. import org.orekit.bodies.GeodeticPoint;
  29. import org.orekit.frames.FieldStaticTransform;
  30. import org.orekit.frames.Frame;
  31. import org.orekit.frames.StaticTransform;
  32. import org.orekit.frames.TopocentricFrame;
  33. import org.orekit.models.earth.ionosphere.IonosphericDelayModel;
  34. import org.orekit.models.earth.ionosphere.IonosphericModel;
  35. import org.orekit.propagation.FieldSpacecraftState;
  36. import org.orekit.propagation.SpacecraftState;
  37. import org.orekit.time.AbsoluteDate;
  38. import org.orekit.time.DateComponents;
  39. import org.orekit.time.DateTimeComponents;
  40. import org.orekit.time.FieldAbsoluteDate;
  41. import org.orekit.time.TimeScale;
  42. import org.orekit.utils.ParameterDriver;
  43. import org.orekit.utils.units.Unit;

  44. /**
  45.  * NeQuick ionospheric delay model.
  46.  *
  47.  * @author Bryan Cazabonne
  48.  *
  49.  * @see "European Union (2016). European GNSS (Galileo) Open Service-Ionospheric Correction
  50.  *       Algorithm for Galileo Single Frequency Users. 1.2."
  51.  * @see <a href="https://www.itu.int/rec/R-REC-P.531/en">ITU-R P.531</a>
  52.  *
  53.  * @since 10.1
  54.  */
  55. public abstract class NeQuickModel implements IonosphericModel, IonosphericDelayModel {

  56.     /** Mean Earth radius in m (Ref Table 2.5.2). */
  57.     public static final double RE = 6371200.0;

  58.     /** Factor for the electron density computation. */
  59.     private static final double DENSITY_FACTOR = 1.0e11;

  60.     /** Factor for the path delay computation. */
  61.     private static final double DELAY_FACTOR = 40.3e16;

  62.     /** F2 coefficients used by the F2 layer (flatten array for cache efficiency). */
  63.     private final double[][] flattenF2;

  64.     /** Fm3 coefficients used by the M(3000)F2 layer(flatten array for cache efficiency). */
  65.     private final double[][] flattenFm3;

  66.     /** UTC time scale. */
  67.     private final TimeScale utc;

  68.     /** Simple constructor.
  69.      * @param utc UTC time scale
  70.      * @since 13.0
  71.      */
  72.     protected NeQuickModel(final TimeScale utc) {

  73.         this.utc = utc;

  74.         // F2 layer values
  75.         this.flattenF2  = new double[12][];
  76.         this.flattenFm3 = new double[12][];

  77.     }

  78.     /** Get UTC time scale.
  79.      * @return UTC time scale
  80.      * @since 13.0
  81.      */
  82.     public TimeScale getUtc() {
  83.         return utc;
  84.     }

  85.     /** {@inheritDoc} */
  86.     @Override
  87.     public double pathDelay(final SpacecraftState state, final TopocentricFrame baseFrame,
  88.                             final double frequency, final double[] parameters) {
  89.         return pathDelay(state, baseFrame, state.getDate(), frequency, parameters);
  90.     }

  91.     /** {@inheritDoc} */
  92.     @Override
  93.     public double pathDelay(final SpacecraftState state,
  94.                             final TopocentricFrame baseFrame, final AbsoluteDate receptionDate,
  95.                             final double frequency, final double[] parameters) {

  96.         // Reference body shape
  97.         final BodyShape ellipsoid = baseFrame.getParentShape();

  98.         // we use transform from body frame to inert frame and invert it
  99.         // because base frame could be peered with inertial frame (hence improving performances with caching)
  100.         // but the reverse is almost never used
  101.         final Frame           bodyFrame  = ellipsoid.getBodyFrame();
  102.         final StaticTransform body2Inert = bodyFrame.getStaticTransformTo(state.getFrame(), receptionDate);
  103.         final Vector3D        position   = body2Inert.getInverse().transformPosition(state.getPosition());

  104.         // Point
  105.         final GeodeticPoint recPoint = baseFrame.getPoint();
  106.         // Date
  107.         final AbsoluteDate date = state.getDate();

  108.         // Satellite geodetic coordinates
  109.         final GeodeticPoint satPoint = ellipsoid.transform(position, bodyFrame, state.getDate());

  110.         // Total Electron Content
  111.         final double tec = stec(date, recPoint, satPoint);

  112.         // Ionospheric delay
  113.         final double factor = DELAY_FACTOR / (frequency * frequency);
  114.         return factor * tec;
  115.     }

  116.     /** {@inheritDoc} */
  117.     @Override
  118.     public <T extends CalculusFieldElement<T>> T pathDelay(final FieldSpacecraftState<T> state,
  119.                                                            final TopocentricFrame baseFrame,
  120.                                                            final double frequency,
  121.                                                            final T[] parameters) {
  122.         return pathDelay(state, baseFrame, state.getDate(), frequency, parameters);
  123.     }

  124.     /** {@inheritDoc} */
  125.     @Override
  126.     public <T extends CalculusFieldElement<T>> T pathDelay(final FieldSpacecraftState<T> state,
  127.                                                            final TopocentricFrame baseFrame,
  128.                                                            final FieldAbsoluteDate<T> receptionDate,
  129.                                                            final double frequency,
  130.                                                            final T[] parameters) {
  131.         // Reference body shape
  132.         final BodyShape ellipsoid = baseFrame.getParentShape();

  133.         // we use transform from body frame to inert frame and invert it
  134.         // because base frame could be peered with inertial frame (hence improving performances with caching)
  135.         // but the reverse is almost never used
  136.         final Frame                   bodyFrame  = ellipsoid.getBodyFrame();
  137.         final FieldStaticTransform<T> body2Inert = bodyFrame.getStaticTransformTo(state.getFrame(), receptionDate);
  138.         final FieldVector3D<T>        position   = body2Inert.getInverse().transformPosition(state.getPosition());

  139.         // Date
  140.         final FieldAbsoluteDate<T> date = state.getDate();
  141.         // Point
  142.         final FieldGeodeticPoint<T> recPoint = baseFrame.getPoint(date.getField());

  143.         // Satellite geodetic coordinates
  144.         final FieldGeodeticPoint<T> satPoint = ellipsoid.transform(position, bodyFrame, state.getDate());

  145.         // Total Electron Content
  146.         final T tec = stec(date, recPoint, satPoint);

  147.         // Ionospheric delay
  148.         final double factor = DELAY_FACTOR / (frequency * frequency);
  149.         return tec.multiply(factor);
  150.     }

  151.     /** {@inheritDoc} */
  152.     @Override
  153.     public List<ParameterDriver> getParametersDrivers() {
  154.         return Collections.emptyList();
  155.     }

  156.     /**
  157.      * This method allows the computation of the Slant Total Electron Content (STEC).
  158.      * @param date current date
  159.      * @param recP receiver position
  160.      * @param satP satellite position
  161.      * @return the STEC in TECUnits
  162.      */
  163.     public double stec(final AbsoluteDate date, final GeodeticPoint recP, final GeodeticPoint satP) {
  164.         return stec(date.getComponents(utc), new Ray(recP, satP));
  165.     }

  166.     /**
  167.      * This method allows the computation of the Slant Total Electron Content (STEC).
  168.      * @param <T> type of the elements
  169.      * @param date current date
  170.      * @param recP receiver position
  171.      * @param satP satellite position
  172.      * @return the STEC in TECUnits
  173.      */
  174.     public <T extends CalculusFieldElement<T>> T stec(final FieldAbsoluteDate<T> date,
  175.                                                       final FieldGeodeticPoint<T> recP,
  176.                                                       final FieldGeodeticPoint<T> satP) {
  177.         return stec(date.getComponents(utc), new FieldRay<>(recP, satP));
  178.     }

  179.     /** Compute modip for a location.
  180.      * @param latitude latitude
  181.      * @param longitude longitude
  182.      * @return modip at specified location
  183.      * @since 13.0
  184.      */
  185.     protected abstract double computeMODIP(double latitude, double longitude);

  186.     /** Compute modip for a location.
  187.      * @param <T> type of the field elements
  188.      * @param latitude latitude
  189.      * @param longitude longitude
  190.      * @return modip at specified location
  191.      * @since 13.0
  192.      */
  193.     protected abstract <T extends CalculusFieldElement<T>> T computeMODIP(T latitude, T longitude);

  194.     /**
  195.      * Compute Fourier time series.
  196.      * @param dateTime current date time components
  197.      * @param az effective ionisation level
  198.      * @return Fourier time series
  199.      * @since 13.0.1
  200.      */
  201.     public FourierTimeSeries computeFourierTimeSeries(final DateTimeComponents dateTime, final double az) {

  202.          // Load the correct CCIR file
  203.         loadsIfNeeded(dateTime.getDate());

  204.         return new FourierTimeSeries(dateTime, az,
  205.                                      flattenF2[dateTime.getDate().getMonth() - 1],
  206.                                      flattenFm3[dateTime.getDate().getMonth() - 1]);

  207.     }

  208.     /**
  209.      * Computes the electron density at a given height.
  210.      * @param dateTime date
  211.      * @param az effective ionization level
  212.      * @param latitude latitude along the integration path
  213.      * @param longitude longitude along the integration path
  214.      * @param h height along the integration path in m
  215.      * @return electron density [m⁻³]
  216.      * @since 13.0
  217.      */
  218.     public double electronDensity(final DateTimeComponents dateTime, final double az,
  219.                                   final double latitude, final double longitude, final double h) {
  220.         return electronDensity(computeFourierTimeSeries(dateTime, az), latitude, longitude, h);
  221.     }

  222.     /**
  223.      * Computes the electron density at a given height.
  224.      * @param fourierTimeSeries Fourier time series for foF2 and M(3000)F2 layer (flatten array)
  225.      * @param latitude latitude along the integration path
  226.      * @param longitude longitude along the integration path
  227.      * @param h height along the integration path in m
  228.      * @return electron density [m⁻³]
  229.      * @since 13.0.1
  230.      */
  231.     public double electronDensity(final FourierTimeSeries fourierTimeSeries,
  232.                                   final double latitude, final double longitude, final double h) {

  233.         final double modip = computeMODIP(latitude, longitude);
  234.         final NeQuickParameters parameters = new NeQuickParameters(fourierTimeSeries, latitude, longitude, modip);

  235.         // Convert height in kilometers
  236.         final double hInKm = Unit.KILOMETRE.fromSI(h);

  237.         // Electron density
  238.         final double n;
  239.         if (hInKm <= parameters.getHmF2()) {
  240.             n = bottomElectronDensity(hInKm, parameters);
  241.         } else {
  242.             n = topElectronDensity(hInKm, parameters);
  243.         }

  244.         return n;

  245.     }

  246.     /**
  247.      * Compute Fourier time series.
  248.      * @param <T> type of the elements
  249.      * @param dateTime current date time components
  250.      * @param az effective ionisation level
  251.      * @return Fourier time series
  252.      * @since 13.0.1
  253.      */
  254.     public <T extends CalculusFieldElement<T>> FieldFourierTimeSeries<T> computeFourierTimeSeries(final DateTimeComponents dateTime,
  255.                                                                                                   final T az) {

  256.          // Load the correct CCIR file
  257.         loadsIfNeeded(dateTime.getDate());

  258.         return new FieldFourierTimeSeries<>(dateTime, az,
  259.                                             flattenF2[dateTime.getDate().getMonth() - 1],
  260.                                             flattenFm3[dateTime.getDate().getMonth() - 1]);

  261.     }

  262.     /**
  263.      * Computes the electron density at a given height.
  264.      * @param <T> type of the elements
  265.      * @param dateTime date
  266.      * @param az effective ionization level
  267.      * @param latitude latitude along the integration path
  268.      * @param longitude longitude along the integration path
  269.      * @param h height along the integration path in m
  270.      * @return electron density [m⁻³]
  271.      * @since 13.0
  272.      * CalculusFieldElement, CalculusFieldElement, CalculusFieldElement)}
  273.      */
  274.     public <T extends CalculusFieldElement<T>> T electronDensity(final DateTimeComponents dateTime, final T az,
  275.                                                                  final T latitude, final T longitude, final T h) {
  276.         return electronDensity(computeFourierTimeSeries(dateTime, az), latitude, longitude, h);
  277.     }

  278.     /**
  279.      * Computes the electron density at a given height.
  280.      * @param <T> type of the elements
  281.      * @param fourierTimeSeries Fourier time series for foF2 and M(3000)F2 layer (flatten array)
  282.      * @param latitude latitude along the integration path
  283.      * @param longitude longitude along the integration path
  284.      * @param h height along the integration path in m
  285.      * @return electron density [m⁻³]
  286.      * @since 13.0.1
  287.      */
  288.     public <T extends CalculusFieldElement<T>> T electronDensity(final FieldFourierTimeSeries<T> fourierTimeSeries,
  289.                                                                  final T latitude, final T longitude, final T h) {

  290.         final T modip = computeMODIP(latitude, longitude);
  291.         final FieldNeQuickParameters<T> parameters = new FieldNeQuickParameters<>(fourierTimeSeries, latitude, longitude, modip);

  292.         // Convert height in kilometers
  293.         final T hInKm = Unit.KILOMETRE.fromSI(h);

  294.         // Electron density
  295.         final T n;
  296.         if (hInKm.getReal() <= parameters.getHmF2().getReal()) {
  297.             n = bottomElectronDensity(hInKm, parameters);
  298.         } else {
  299.             n = topElectronDensity(hInKm, parameters);
  300.         }

  301.         return n;

  302.     }

  303.     /**
  304.      * Computes the electron density of the bottomside.
  305.      * @param h height in km
  306.      * @param parameters NeQuick model parameters
  307.      * @return the electron density N in m⁻³
  308.      */
  309.     private double bottomElectronDensity(final double h, final NeQuickParameters parameters) {

  310.         // Select the relevant B parameter for the current height (Eq. 109 and 110)
  311.         final double be  = parameters.getBE(h);
  312.         final double bf1 = parameters.getBF1(h);
  313.         final double bf2 = parameters.getB2Bot();

  314.         // Useful array of constants
  315.         final double[] ct = new double[] {
  316.             1.0 / bf2, 1.0 / bf1, 1.0 / be
  317.         };

  318.         // Compute the exponential argument for each layer (Eq. 111 to 113)
  319.         final double[] arguments = computeExponentialArguments(h, parameters);

  320.         // S coefficients
  321.         final double[] s = new double[3];
  322.         // Array of corrective terms
  323.         final double[] ds = new double[3];

  324.         // Layer amplitudes
  325.         final double[] amplitudes = parameters.getLayerAmplitudes();

  326.         // Fill arrays (Eq. 114 to 118)
  327.         for (int i = 0; i < 3; i++) {
  328.             if (FastMath.abs(arguments[i]) > 25.0) {
  329.                 s[i]  = 0.0;
  330.                 ds[i] = 0.0;
  331.             } else {
  332.                 final double expA   = clipExp(arguments[i]);
  333.                 final double opExpA = 1.0 + expA;
  334.                 s[i]  = amplitudes[i] * (expA / (opExpA * opExpA));
  335.                 ds[i] = ct[i] * ((1.0 - expA) / (1.0 + expA));
  336.             }
  337.         }

  338.         // Electron density
  339.         final double aNo = s[0] + s[1] + s[2];
  340.         if (applyChapmanParameters(h)) {
  341.             // Chapman parameters (Eq. 119 and 120)
  342.             final double bc = 1.0 - 10.0 * (MathArrays.linearCombination(s[0], ds[0], s[1], ds[1], s[2], ds[2]) / aNo);
  343.             final double z  = 0.1 * (h - 100.0);
  344.             // Electron density (Eq. 121)
  345.             return aNo * clipExp(1.0 - bc * z - clipExp(-z)) * DENSITY_FACTOR;
  346.         } else {
  347.             return aNo * DENSITY_FACTOR;
  348.         }

  349.     }

  350.     /**
  351.      * Computes the electron density of the bottomside.
  352.      * @param <T> type of the elements
  353.      * @param h height in km
  354.      * @param parameters NeQuick model parameters
  355.      * @return the electron density N in m⁻³
  356.      */
  357.     private <T extends CalculusFieldElement<T>> T bottomElectronDensity(final T h,
  358.                                                                         final FieldNeQuickParameters<T> parameters) {

  359.         // Zero and One
  360.         final Field<T> field = h.getField();
  361.         final T zero = field.getZero();
  362.         final T one  = field.getOne();

  363.         // Select the relevant B parameter for the current height (Eq. 109 and 110)
  364.         final T be  = parameters.getBE(h);
  365.         final T bf1 = parameters.getBF1(h);
  366.         final T bf2 = parameters.getB2Bot();

  367.         // Useful array of constants
  368.         final T[] ct = MathArrays.buildArray(field, 3);
  369.         ct[0] = bf2.reciprocal();
  370.         ct[1] = bf1.reciprocal();
  371.         ct[2] = be.reciprocal();

  372.         // Compute the exponential argument for each layer (Eq. 111 to 113)
  373.         final T[] arguments = computeExponentialArguments(h, parameters);

  374.         // S coefficients
  375.         final T[] s = MathArrays.buildArray(field, 3);
  376.         // Array of corrective terms
  377.         final T[] ds = MathArrays.buildArray(field, 3);

  378.         // Layer amplitudes
  379.         final T[] amplitudes = parameters.getLayerAmplitudes();

  380.         // Fill arrays (Eq. 114 to 118)
  381.         for (int i = 0; i < 3; i++) {
  382.             if (FastMath.abs(arguments[i]).getReal() > 25.0) {
  383.                 s[i]  = zero;
  384.                 ds[i] = zero;
  385.             } else {
  386.                 final T expA   = clipExp(arguments[i]);
  387.                 final T opExpA = expA.add(1.0);
  388.                 s[i]  = amplitudes[i].multiply(expA.divide(opExpA.multiply(opExpA)));
  389.                 ds[i] = ct[i].multiply(expA.negate().add(1.0).divide(expA.add(1.0)));
  390.             }
  391.         }

  392.         // Electron density
  393.         final T aNo = s[0].add(s[1]).add(s[2]);
  394.         if (applyChapmanParameters(h.getReal())) {
  395.             // Chapman parameters (Eq. 119 and 120)
  396.             final T bc = one.linearCombination(s[0], ds[0], s[1], ds[1], s[2], ds[2]).divide(aNo).multiply(10.0).negate().add(1.0);
  397.             final T z  = h.subtract(100.0).multiply(0.1);
  398.             // Electron density (Eq. 121)
  399.             return aNo.multiply(clipExp(bc.multiply(z).add(clipExp(z.negate())).negate().add(1.0))).multiply(DENSITY_FACTOR);
  400.         } else {
  401.             return aNo.multiply(DENSITY_FACTOR);
  402.         }

  403.     }

  404.     /**
  405.      * Computes the electron density of the topside.
  406.      * @param h height in km
  407.      * @param parameters NeQuick model parameters
  408.      * @return the electron density N in m⁻³
  409.      */
  410.     private double topElectronDensity(final double h, final NeQuickParameters parameters) {

  411.         // Constant parameters (Eq. 122 and 123)
  412.         final double g = 0.125;
  413.         final double r = 100.0;

  414.         // Arguments deltaH and z (Eq. 124 and 125)
  415.         final double deltaH = h - parameters.getHmF2();
  416.         final double h0     = computeH0(parameters);
  417.         final double z      = deltaH / (h0 * (1.0 + (r * g * deltaH) / (r * h0 + g * deltaH)));

  418.         // Exponential (Eq. 126)
  419.         final double ee = clipExp(z);

  420.         // Electron density (Eq. 127)
  421.         if (ee > 1.0e11) {
  422.             return (4.0 * parameters.getNmF2() / ee) * DENSITY_FACTOR;
  423.         } else {
  424.             final double opExpZ = 1.0 + ee;
  425.             return ((4.0 * parameters.getNmF2() * ee) / (opExpZ * opExpZ)) * DENSITY_FACTOR;
  426.         }
  427.     }

  428.     /**
  429.      * Computes the electron density of the topside.
  430.      * @param <T> type of the elements
  431.      * @param h height in km
  432.      * @param parameters NeQuick model parameters
  433.      * @return the electron density N in m⁻³
  434.      */
  435.     private <T extends CalculusFieldElement<T>> T topElectronDensity(final T h,
  436.                                                                      final FieldNeQuickParameters<T> parameters) {

  437.         // Constant parameters (Eq. 122 and 123)
  438.         final double g = 0.125;
  439.         final double r = 100.0;

  440.         // Arguments deltaH and z (Eq. 124 and 125)
  441.         final T deltaH = h.subtract(parameters.getHmF2());
  442.         final T h0     = computeH0(parameters);
  443.         final T z      = deltaH.divide(h0.multiply(deltaH.multiply(r).multiply(g).divide(h0.multiply(r).add(deltaH.multiply(g))).add(1.0)));

  444.         // Exponential (Eq. 126)
  445.         final T ee = clipExp(z);

  446.         // Electron density (Eq. 127)
  447.         if (ee.getReal() > 1.0e11) {
  448.             return parameters.getNmF2().multiply(4.0).divide(ee).multiply(DENSITY_FACTOR);
  449.         } else {
  450.             final T opExpZ = ee.add(1.0);
  451.             return parameters.getNmF2().multiply(4.0).multiply(ee).divide(opExpZ.multiply(opExpZ)).multiply(DENSITY_FACTOR);
  452.         }
  453.     }

  454.     /**
  455.      * Lazy loading of CCIR data.
  456.      * @param date current date components
  457.      */
  458.     private void loadsIfNeeded(final DateComponents date) {

  459.         // Month index
  460.         final int monthIndex = date.getMonth() - 1;

  461.         // Check if CCIR has already been loaded for this month
  462.         if (flattenF2[monthIndex] == null) {

  463.             // Read file
  464.             final CCIRLoader loader = new CCIRLoader();
  465.             loader.loadCCIRCoefficients(date);

  466.             // Update arrays
  467.             this.flattenF2[monthIndex]  = flatten(loader.getF2());
  468.             this.flattenFm3[monthIndex] = flatten(loader.getFm3());
  469.         }
  470.     }

  471.     /** Flatten a 3-dimensions array.
  472.      * <p>
  473.      * This method convert 3-dimensions arrays into 1-dimension arrays
  474.      * optimized to avoid cache misses when looping over all elements.
  475.      * </p>
  476.      * @param original original array a[i][j][k]
  477.      * @return flatten array, for embedded loops on j (outer), k (intermediate), i (inner)
  478.      */
  479.     private double[] flatten(final double[][][] original) {
  480.         final double[] flatten = new double[original.length * original[0].length * original[0][0].length];
  481.         int index = 0;
  482.         for (int j = 0; j < original[0].length; j++) {
  483.             for (int k = 0; k < original[0][0].length; k++) {
  484.                 for (final double[][] doubles : original) {
  485.                     flatten[index++] = doubles[j][k];
  486.                 }
  487.             }
  488.         }
  489.         return flatten;
  490.     }

  491.     /**
  492.      * A clipped exponential function.
  493.      * <p>
  494.      * This function, describe in section F.2.12.2 of the reference document, is
  495.      * recommended for the computation of exponential values.
  496.      * </p>
  497.      * @param power power for exponential function
  498.      * @return clipped exponential value
  499.      */
  500.     protected double clipExp(final double power) {
  501.         if (power > 80.0) {
  502.             return 5.5406E34;
  503.         } else if (power < -80) {
  504.             return 1.8049E-35;
  505.         } else {
  506.             return FastMath.exp(power);
  507.         }
  508.     }

  509.     /**
  510.      * A clipped exponential function.
  511.      * <p>
  512.      * This function, describe in section F.2.12.2 of the reference document, is
  513.      * recommended for the computation of exponential values.
  514.      * </p>
  515.      * @param <T> type of the elements
  516.      * @param power power for exponential function
  517.      * @return clipped exponential value
  518.      */
  519.     protected <T extends CalculusFieldElement<T>> T clipExp(final T power) {
  520.         if (power.getReal() > 80.0) {
  521.             return power.newInstance(5.5406E34);
  522.         } else if (power.getReal() < -80) {
  523.             return power.newInstance(1.8049E-35);
  524.         } else {
  525.             return FastMath.exp(power);
  526.         }
  527.     }

  528.     /**
  529.      * This method allows the computation of the Slant Total Electron Content (STEC).
  530.      *
  531.      * @param dateTime current date
  532.      * @param ray      ray-perigee parameters
  533.      * @return the STEC in TECUnits
  534.      */
  535.     abstract double stec(DateTimeComponents dateTime, Ray ray);

  536.     /**
  537.      * This method allows the computation of the Slant Total Electron Content (STEC).
  538.      *
  539.      * @param <T>      type of the field elements
  540.      * @param dateTime current date
  541.      * @param ray      ray-perigee parameters
  542.      * @return the STEC in TECUnits
  543.      */
  544.     abstract <T extends CalculusFieldElement<T>> T stec(DateTimeComponents dateTime, FieldRay<T> ray);

  545.     /**
  546.      * Check if Chapman parameters should be applied.
  547.      *
  548.      * @param hInKm height in kilometers
  549.      * @return true if Chapman parameters should be applied
  550.      * @since 13.0
  551.      */
  552.     abstract boolean applyChapmanParameters(double hInKm);

  553.     /**
  554.      * Compute exponential arguments.
  555.      * @param h height in km
  556.      * @param parameters NeQuick model parameters
  557.      * @return exponential arguments
  558.      * @since 13.0
  559.      */
  560.     abstract double[] computeExponentialArguments(double h, NeQuickParameters parameters);

  561.     /**
  562.      * Compute exponential arguments.
  563.      * @param <T>   type of the field elements
  564.      * @param h height in km
  565.      * @param parameters NeQuick model parameters
  566.      * @return exponential arguments
  567.      * @since 13.0
  568.      */
  569.     abstract <T extends CalculusFieldElement<T>> T[] computeExponentialArguments(T h,
  570.                                                                                  FieldNeQuickParameters<T> parameters);

  571.     /**
  572.      * Compute topside thickness parameter.
  573.      * @param parameters NeQuick model parameters
  574.      * @return topside thickness parameter
  575.      * @since 13.0
  576.      */
  577.     abstract double computeH0(NeQuickParameters parameters);

  578.     /**
  579.      * Compute topside thickness parameter.
  580.      * @param <T>   type of the field elements
  581.      * @param parameters NeQuick model parameters
  582.      * @return topside thickness parameter
  583.      * @since 13.0
  584.      */
  585.     abstract <T extends CalculusFieldElement<T>> T computeH0(FieldNeQuickParameters<T> parameters);

  586. }