NeQuickParameters.java

  1. /* Copyright 2002-2024 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;

  18. import org.hipparchus.util.FastMath;
  19. import org.hipparchus.util.SinCos;
  20. import org.orekit.time.DateComponents;
  21. import org.orekit.time.DateTimeComponents;
  22. import org.orekit.time.TimeComponents;

  23. /**
  24.  * This class perfoms the computation of the parameters used by the NeQuick model.
  25.  *
  26.  * @author Bryan Cazabonne
  27.  *
  28.  * @see "European Union (2016). European GNSS (Galileo) Open Service-Ionospheric Correction
  29.  *       Algorithm for Galileo Single Frequency Users. 1.2."
  30.  *
  31.  * @since 10.1
  32.  */
  33. class NeQuickParameters {

  34.     /** Solar zenith angle at day night transition, degrees. */
  35.     private static final double X0 = 86.23292796211615;

  36.     /** F2 layer maximum density. */
  37.     private final double nmF2;

  38.     /** F2 layer maximum density height [km]. */
  39.     private final double hmF2;

  40.     /** F1 layer maximum density height [km]. */
  41.     private final double hmF1;

  42.     /** E layer maximum density height [km]. */
  43.     private final double hmE;

  44.     /** F2 layer bottom thickness parameter [km]. */
  45.     private final double b2Bot;

  46.     /** F1 layer top thickness parameter [km]. */
  47.     private final double b1Top;

  48.     /** F1 layer bottom thickness parameter [km]. */
  49.     private final double b1Bot;

  50.     /** E layer top thickness parameter [km]. */
  51.     private final double beTop;

  52.     /** E layer bottom thickness parameter [km]. */
  53.     private final double beBot;

  54.     /** topside thickness parameter [km]. */
  55.     private final double h0;

  56.     /** Layer amplitudes. */
  57.     private final double[] amplitudes;

  58.     /**
  59.      * Build a new instance.
  60.      * @param dateTime current date time components
  61.      * @param flattenF2 F2 coefficients used by the F2 layer (flatten array)
  62.      * @param flattenFm3 Fm3 coefficients used by the F2 layer (flatten array)
  63.      * @param latitude latitude of a point along the integration path, in radians
  64.      * @param longitude longitude of a point along the integration path, in radians
  65.      * @param alpha effective ionisation level coefficients
  66.      * @param modipGrip modip grid
  67.      */
  68.     NeQuickParameters(final DateTimeComponents dateTime,
  69.                       final double[] flattenF2, final double[] flattenFm3,
  70.                       final double latitude, final double longitude,
  71.                       final double[] alpha, final double[][] modipGrip) {

  72.         // MODIP in degrees
  73.         final double modip = computeMODIP(latitude, longitude, modipGrip);
  74.         // Effective ionisation level Az
  75.         final double az = computeAz(modip, alpha);
  76.         // Effective sunspot number (Eq. 19)
  77.         final double azr = FastMath.sqrt(167273.0 + (az - 63.7) * 1123.6) - 408.99;
  78.         // Date and Time components
  79.         final DateComponents date = dateTime.getDate();
  80.         final TimeComponents time = dateTime.getTime();
  81.         // Hours
  82.         final double hours  = time.getSecondsInUTCDay() / 3600.0;
  83.         // Effective solar zenith angle in radians
  84.         final double xeff = computeEffectiveSolarAngle(date.getMonth(), hours, latitude, longitude);

  85.         // E layer maximum density height in km (Eq. 78)
  86.         this.hmE = 120.0;
  87.         // E layer critical frequency in MHz
  88.         final double foE = computefoE(date.getMonth(), az, xeff, latitude);
  89.         // E layer maximum density in 10^11 m-3 (Eq. 36)
  90.         final double nmE = 0.124 * foE * foE;

  91.         // Time argument (Eq. 49)
  92.         final double t = FastMath.toRadians(15 * hours) - FastMath.PI;
  93.         // Compute Fourier time series for foF2 and M(3000)F2
  94.         final double[] scT = sinCos(t, 6);
  95.         final double[] cf2 = computeCF2(flattenF2, azr, scT);
  96.         final double[] cm3 = computeCm3(flattenFm3, azr, scT);
  97.         // F2 layer critical frequency in MHz
  98.         final double[] scL = sinCos(longitude, 8);
  99.         final double foF2 = computefoF2(modip, cf2, latitude, scL);
  100.         // Maximum Usable Frequency factor
  101.         final double mF2  = computeMF2(modip, cm3, latitude, scL);
  102.         // F2 layer maximum density in 10^11 m-3
  103.         this.nmF2 = 0.124 * foF2 * foF2;
  104.         // F2 layer maximum density height in km
  105.         this.hmF2 = computehmF2(foE, foF2, mF2);

  106.         // F1 layer critical frequency in MHz
  107.         final double foF1 = computefoF1(foE, foF2);
  108.         // F1 layer maximum density in 10^11 m-3
  109.         final double nmF1;
  110.         if (foF1 <= 0.0 && foE > 2.0) {
  111.             final double foEpopf = foE + 0.5;
  112.             nmF1 = 0.124 * foEpopf * foEpopf;
  113.         } else {
  114.             nmF1 = 0.124 * foF1 * foF1;
  115.         }
  116.         // F1 layer maximum density height in km
  117.         this.hmF1 = 0.5 * (hmF2 + hmE);

  118.         // Thickness parameters (Eq. 85 to 89)
  119.         final double a = 0.01 * clipExp(-3.467 + 0.857 * FastMath.log(foF2 * foF2) + 2.02 * FastMath.log(mF2));
  120.         this.b2Bot = 0.385 * nmF2 / a;
  121.         this.b1Top = 0.3 * (hmF2 - hmF1);
  122.         this.b1Bot = 0.5 * (hmF1 - hmE);
  123.         this.beTop = FastMath.max(b1Bot, 7.0);
  124.         this.beBot = 5.0;

  125.         // Layer amplitude coefficients
  126.         this.amplitudes = computeLayerAmplitudes(nmE, nmF1, foF1);

  127.         // Topside thickness parameter
  128.         this.h0 = computeH0(date.getMonth(), azr);
  129.     }

  130.     /**
  131.      * Get the F2 layer maximum density.
  132.      * @return nmF2
  133.      */
  134.     public double getNmF2() {
  135.         return nmF2;
  136.     }

  137.     /**
  138.      * Get the F2 layer maximum density height.
  139.      * @return hmF2 in km
  140.      */
  141.     public double getHmF2() {
  142.         return hmF2;
  143.     }

  144.     /**
  145.      * Get the F1 layer maximum density height.
  146.      * @return hmF1 in km
  147.      */
  148.     public double getHmF1() {
  149.         return hmF1;
  150.     }

  151.     /**
  152.      * Get the E layer maximum density height.
  153.      * @return hmE in km
  154.      */
  155.     public double getHmE() {
  156.         return hmE;
  157.     }

  158.     /**
  159.      * Get the F2 layer thickness parameter (bottom).
  160.      * @return B2Bot in km
  161.      */
  162.     public double getB2Bot() {
  163.         return b2Bot;
  164.     }

  165.     /**
  166.      * Get the F1 layer thickness parameter (top).
  167.      * @return B1Top in km
  168.      */
  169.     public double getB1Top() {
  170.         return b1Top;
  171.     }

  172.     /**
  173.      * Get the F1 layer thickness parameter (bottom).
  174.      * @return B1Bot in km
  175.      */
  176.     public double getB1Bot() {
  177.         return b1Bot;
  178.     }

  179.     /**
  180.      * Get the E layer thickness parameter (bottom).
  181.      * @return BeBot in km
  182.      */
  183.     public double getBEBot() {
  184.         return beBot;
  185.     }

  186.     /**
  187.      * Get the E layer thickness parameter (top).
  188.      * @return BeTop in km
  189.      */
  190.     public double getBETop() {
  191.         return beTop;
  192.     }

  193.     /**
  194.      * Get the F2, F1 and E layer amplitudes.
  195.      * <p>
  196.      * The resulting element is an array having the following form:
  197.      * <ul>
  198.      * <li>double[0] = A1 → F2 layer amplitude
  199.      * <li>double[1] = A2 → F1 layer amplitude
  200.      * <li>double[2] = A3 → E  layer amplitude
  201.      * </ul>
  202.      * @return layer amplitudes
  203.      */
  204.     public double[] getLayerAmplitudes() {
  205.         return amplitudes.clone();
  206.     }

  207.     /**
  208.      * Get the topside thickness parameter H0.
  209.      * @return H0 in km
  210.      */
  211.     public double getH0() {
  212.         return h0;
  213.     }

  214.     /**
  215.      * Computes the value of the modified dip latitude (MODIP) for the
  216.      * given latitude and longitude.
  217.      *
  218.      * @param lat receiver latitude, radians
  219.      * @param lon receiver longitude, radians
  220.      * @param stModip modip grid
  221.      * @return the MODIP in degrees
  222.      */
  223.     private double computeMODIP(final double lat, final double lon, final double[][] stModip) {

  224.         // For the MODIP computation, latitude and longitude have to be converted in degrees
  225.         final double latitude = FastMath.toDegrees(lat);
  226.         final double longitude = FastMath.toDegrees(lon);

  227.         // Extreme cases
  228.         if (latitude == 90.0 || latitude == -90.0) {
  229.             return latitude;
  230.         }

  231.         // Auxiliary parameter l (Eq. 6 to 8)
  232.         final int lF = (int) ((longitude + 180) * 0.1);
  233.         int l = lF - 2;
  234.         if (l < -2) {
  235.             l += 36;
  236.         } else if (l > 33) {
  237.             l -= 36;
  238.         }

  239.         // Auxiliary parameter a (Eq. 9 to 11)
  240.         final double a  = 0.2 * (latitude + 90) + 1.0;
  241.         final double aF = FastMath.floor(a);
  242.         // Eq. 10
  243.         final double x = a - aF;
  244.         // Eq. 11
  245.         final int i = (int) aF - 2;

  246.         // zi coefficients (Eq. 12 and 13)
  247.         final double z1 = interpolate(stModip[i + 1][l + 2], stModip[i + 2][l + 2], stModip[i + 3][l + 2], stModip[i + 4][l + 2], x);
  248.         final double z2 = interpolate(stModip[i + 1][l + 3], stModip[i + 2][l + 3], stModip[i + 3][l + 3], stModip[i + 4][l + 3], x);
  249.         final double z3 = interpolate(stModip[i + 1][l + 4], stModip[i + 2][l + 4], stModip[i + 3][l + 4], stModip[i + 4][l + 4], x);
  250.         final double z4 = interpolate(stModip[i + 1][l + 5], stModip[i + 2][l + 5], stModip[i + 3][l + 5], stModip[i + 4][l + 5], x);

  251.         // Auxiliary parameter b (Eq. 14 and 15)
  252.         final double b  = (longitude + 180) * 0.1;
  253.         final double bF = FastMath.floor(b);
  254.         final double y  = b - bF;

  255.         // MODIP (Ref Eq. 16)
  256.         return interpolate(z1, z2, z3, z4, y);

  257.     }

  258.     /**
  259.      * This method computes the effective ionisation level Az.
  260.      * <p>
  261.      * This parameter is used for the computation of the Total Electron Content (TEC).
  262.      * </p>
  263.      * @param modip modified dip latitude (MODIP) in degrees
  264.      * @param alpha effective ionisation level coefficients
  265.      * @return the ionisation level Az
  266.      */
  267.     private double computeAz(final double modip, final double[] alpha) {
  268.         // Particular condition (Eq. 17)
  269.         if (alpha[0] == 0.0 && alpha[1] == 0.0 && alpha[2] == 0.0) {
  270.             return 63.7;
  271.         }
  272.         // Az = a0 + modip * a1 + modip^2 * a2 (Eq. 18)
  273.         double az = alpha[0] + modip * (alpha[1] + modip * alpha[2]);
  274.         // If Az < 0 -> Az = 0
  275.         az = FastMath.max(0.0, az);
  276.         // If Az > 400 -> Az = 400
  277.         az = FastMath.min(400.0, az);
  278.         return az;
  279.     }

  280.     /**
  281.      * This method computes the effective solar zenith angle.
  282.      * <p>
  283.      * The effective solar zenith angle is compute as a function of the
  284.      * solar zenith angle and the solar zenith angle at day night transition.
  285.      * </p>
  286.      * @param month current month of the year
  287.      * @param hours universal time (hours)
  288.      * @param latitude in radians
  289.      * @param longitude in radians
  290.      * @return the effective solar zenith angle, radians
  291.      */
  292.     private double computeEffectiveSolarAngle(final int month,
  293.                                               final double hours,
  294.                                               final double latitude,
  295.                                               final double longitude) {
  296.         // Local time (Eq.4)
  297.         final double lt = hours + longitude / FastMath.toRadians(15.0);
  298.         // Day of year at the middle of the month (Eq. 20)
  299.         final double dy = 30.5 * month - 15.0;
  300.         // Time (Eq. 21)
  301.         final double t = dy + (18 - hours) / 24;
  302.         // Arguments am and al (Eq. 22 and 23)
  303.         final double am = FastMath.toRadians(0.9856 * t - 3.289);
  304.         final double al = am + FastMath.toRadians(1.916 * FastMath.sin(am) + 0.020 * FastMath.sin(2.0 * am) + 282.634);
  305.         // Sine and cosine of solar declination (Eq. 24 and 25)
  306.         final double sDec = 0.39782 * FastMath.sin(al);
  307.         final double cDec = FastMath.sqrt(1. - sDec * sDec);
  308.         // Solar zenith angle, deg (Eq. 26 and 27)
  309.         final SinCos scLat   = FastMath.sinCos(latitude);
  310.         final double coef    = (FastMath.PI / 12) * (12 - lt);
  311.         final double cZenith = scLat.sin() * sDec + scLat.cos() * cDec * FastMath.cos(coef);
  312.         final double angle   = FastMath.atan2(FastMath.sqrt(1.0 - cZenith * cZenith), cZenith);
  313.         final double x       = FastMath.toDegrees(angle);
  314.         // Effective solar zenith angle (Eq. 28)
  315.         final double xeff = join(90.0 - 0.24 * clipExp(20.0 - 0.2 * x), x, 12.0, x - X0);
  316.         return FastMath.toRadians(xeff);
  317.     }

  318.     /**
  319.      * This method computes the E layer critical frequency at a given location.
  320.      * @param month current month
  321.      * @param az ffective ionisation level
  322.      * @param xeff effective solar zenith angle in radians
  323.      * @param latitude latitude in radians
  324.      * @return the E layer critical frequency at a given location in MHz
  325.      */
  326.     private double computefoE(final int month, final double az,
  327.                               final double xeff, final double latitude) {
  328.         // The latitude has to be converted in degrees
  329.         final double lat = FastMath.toDegrees(latitude);
  330.         // Square root of the effective ionisation level
  331.         final double sqAz = FastMath.sqrt(az);
  332.         // seas parameter (Eq. 30 to 32)
  333.         final int seas;
  334.         if (month == 1 || month == 2 || month == 11 || month == 12) {
  335.             seas = -1;
  336.         } else if (month == 3 || month == 4 || month == 9 || month == 10) {
  337.             seas = 0;
  338.         } else {
  339.             seas = 1;
  340.         }
  341.         // Latitudinal dependence (Eq. 33 and 34)
  342.         final double ee = clipExp(0.3 * lat);
  343.         final double seasp = seas * ((ee - 1.0) / (ee + 1.0));
  344.         // Critical frequency (Eq. 35)
  345.         final double coef = 1.112 - 0.019 * seasp;
  346.         return FastMath.sqrt(coef * coef * sqAz * FastMath.pow(FastMath.cos(xeff), 0.6) + 0.49);

  347.     }

  348.     /**
  349.      * Computes the F2 layer height of maximum electron density.
  350.      * @param foE E layer layer critical frequency in MHz
  351.      * @param foF2 F2 layer layer critical frequency in MHz
  352.      * @param mF2 maximum usable frequency factor
  353.      * @return hmF2 in km
  354.      */
  355.     private double computehmF2(final double foE, final double foF2, final double mF2) {
  356.         // Ratio
  357.         final double fo = foF2 / foE;
  358.         final double ratio = join(fo, 1.75, 20.0, fo - 1.75);

  359.         // deltaM parameter
  360.         double deltaM = -0.012;
  361.         if (foE >= 1e-30) {
  362.             deltaM += 0.253 / (ratio - 1.215);
  363.         }

  364.         // hmF2 Eq. 80
  365.         final double mF2Sq = mF2 * mF2;
  366.         final double temp  = FastMath.sqrt((0.0196 * mF2Sq + 1) / (1.2967 * mF2Sq - 1.0));
  367.         return ((1490.0 * mF2 * temp) / (mF2 + deltaM)) - 176.0;

  368.     }

  369.     /** Compute sines and cosines.
  370.      * @param a argument
  371.      * @param n number of terms
  372.      * @return sin(a), cos(a), sin(2a), cos(2a) … sin(n a), cos(n a) array
  373.      * @since 12.1.3
  374.      */
  375.     private double[] sinCos(final double a, final int n) {

  376.         final SinCos sc0 = FastMath.sinCos(a);
  377.         SinCos sci = sc0;
  378.         final double[] sc = new double[2 * n];
  379.         int isc = 0;
  380.         sc[isc++] = sci.sin();
  381.         sc[isc++] = sci.cos();
  382.         for (int i = 1; i < n; i++) {
  383.             sci = SinCos.sum(sc0, sci);
  384.             sc[isc++] = sci.sin();
  385.             sc[isc++] = sci.cos();
  386.         }

  387.         return sc;

  388.     }

  389.     /**
  390.      * Computes cf2 coefficients.
  391.      * @param flattenF2 F2 coefficients used by the F2 layer (flatten array)
  392.      * @param azr effective sunspot number (Eq. 19)
  393.      * @param scT sines/cosines array of time argument
  394.      * @return the cf2 coefficients array
  395.      */
  396.     private double[] computeCF2(final double[] flattenF2, final double azr, final double[] scT) {

  397.         // interpolation coefficients for effective spot number
  398.         final double azr01 = azr * 0.01;
  399.         final double omazr01 = 1 - azr01;

  400.         // Eq. 44 and Eq. 50 merged into one loop
  401.         final double[] cf2 = new double[76];
  402.         int index = 0;
  403.         for (int i = 0; i < cf2.length; i++) {
  404.             cf2[i] = omazr01 * flattenF2[index     ] + azr01 * flattenF2[index +  1] +
  405.                     (omazr01 * flattenF2[index +  2] + azr01 * flattenF2[index +  3]) * scT[ 0] +
  406.                     (omazr01 * flattenF2[index +  4] + azr01 * flattenF2[index +  5]) * scT[ 1] +
  407.                     (omazr01 * flattenF2[index +  6] + azr01 * flattenF2[index +  7]) * scT[ 2] +
  408.                     (omazr01 * flattenF2[index +  8] + azr01 * flattenF2[index +  9]) * scT[ 3] +
  409.                     (omazr01 * flattenF2[index + 10] + azr01 * flattenF2[index + 11]) * scT[ 4] +
  410.                     (omazr01 * flattenF2[index + 12] + azr01 * flattenF2[index + 13]) * scT[ 5] +
  411.                     (omazr01 * flattenF2[index + 14] + azr01 * flattenF2[index + 15]) * scT[ 6] +
  412.                     (omazr01 * flattenF2[index + 16] + azr01 * flattenF2[index + 17]) * scT[ 7] +
  413.                     (omazr01 * flattenF2[index + 18] + azr01 * flattenF2[index + 19]) * scT[ 8] +
  414.                     (omazr01 * flattenF2[index + 20] + azr01 * flattenF2[index + 21]) * scT[ 9] +
  415.                     (omazr01 * flattenF2[index + 22] + azr01 * flattenF2[index + 23]) * scT[10] +
  416.                     (omazr01 * flattenF2[index + 24] + azr01 * flattenF2[index + 25]) * scT[11];
  417.             index += 26;
  418.         }
  419.         return cf2;
  420.     }

  421.     /**
  422.      * Computes Cm3 coefficients.
  423.      * @param flattenFm3 Fm3 coefficients used by the F2 layer (flatten array)
  424.      * @param azr effective sunspot number (Eq. 19)
  425.      * @param scT sines/cosines array of time argument
  426.      * @return the Cm3 coefficients array
  427.      */
  428.     private double[] computeCm3(final double[] flattenFm3, final double azr, final double[] scT) {

  429.         // interpolation coefficients for effective spot number
  430.         final double azr01 = azr * 0.01;
  431.         final double omazr01 = 1 - azr01;

  432.         // Eq. 44 and Eq. 51 merged into one loop
  433.         final double[] cm3 = new double[49];
  434.         int index = 0;
  435.         for (int i = 0; i < cm3.length; i++) {
  436.             cm3[i] = omazr01 * flattenFm3[index     ] + azr01 * flattenFm3[index +  1] +
  437.                     (omazr01 * flattenFm3[index +  2] + azr01 * flattenFm3[index +  3]) * scT[ 0] +
  438.                     (omazr01 * flattenFm3[index +  4] + azr01 * flattenFm3[index +  5]) * scT[ 1] +
  439.                     (omazr01 * flattenFm3[index +  6] + azr01 * flattenFm3[index +  7]) * scT[ 2] +
  440.                     (omazr01 * flattenFm3[index +  8] + azr01 * flattenFm3[index +  9]) * scT[ 3] +
  441.                     (omazr01 * flattenFm3[index + 10] + azr01 * flattenFm3[index + 11]) * scT[ 4] +
  442.                     (omazr01 * flattenFm3[index + 12] + azr01 * flattenFm3[index + 13]) * scT[ 5] +
  443.                     (omazr01 * flattenFm3[index + 14] + azr01 * flattenFm3[index + 15]) * scT[ 6] +
  444.                     (omazr01 * flattenFm3[index + 16] + azr01 * flattenFm3[index + 17]) * scT[ 7];
  445.             index += 18;
  446.         }
  447.         return cm3;
  448.     }

  449.     /**
  450.      * This method computes the F2 layer critical frequency.
  451.      * @param modip modified DIP latitude, in degrees
  452.      * @param cf2 Fourier time series for foF2
  453.      * @param latitude latitude in radians
  454.      * @param scL sines/cosines array of longitude argument
  455.      * @return the F2 layer critical frequency, MHz
  456.      */
  457.     private double computefoF2(final double modip, final double[] cf2,
  458.                                final double latitude, final double[] scL) {

  459.         // Legendre grades (Eq. 63)
  460.         final int[] q = new int[] {
  461.             12, 12, 9, 5, 2, 1, 1, 1, 1
  462.         };

  463.         double frequency = cf2[0];

  464.         // MODIP coefficients Eq. 57
  465.         final double sinMODIP = FastMath.sin(FastMath.toRadians(modip));
  466.         final double[] m = new double[12];
  467.         m[0] = 1.0;
  468.         for (int i = 1; i < q[0]; i++) {
  469.             m[i] = sinMODIP * m[i - 1];
  470.             frequency += m[i] * cf2[i];
  471.         }

  472.         // latitude and longitude terms
  473.         int index = 12;
  474.         final double cosLat1 = FastMath.cos(latitude);
  475.         double cosLatI = cosLat1;
  476.         for (int i = 1; i < q.length; i++) {
  477.             final double c = cosLatI * scL[2 * i - 1];
  478.             final double s = cosLatI * scL[2 * i - 2];
  479.             for (int j = 0; j < q[i]; j++) {
  480.                 frequency += m[j] * c * cf2[index++];
  481.                 frequency += m[j] * s * cf2[index++];
  482.             }
  483.             cosLatI *= cosLat1; // Eq. 58
  484.         }

  485.         return frequency;

  486.     }

  487.     /**
  488.      * This method computes the Maximum Usable Frequency factor.
  489.      * @param modip modified DIP latitude, in degrees
  490.      * @param cm3 Fourier time series for M(3000)F2
  491.      * @param latitude latitude in radians
  492.      * @param scL sines/cosines array of longitude argument
  493.      * @return the Maximum Usable Frequency factor
  494.      */
  495.     private double computeMF2(final double modip, final double[] cm3,
  496.                               final double latitude, final double[] scL) {

  497.         // Legendre grades (Eq. 71)
  498.         final int[] r = new int[] {
  499.             7, 8, 6, 3, 2, 1, 1
  500.         };

  501.         double m3000 = cm3[0];

  502.         // MODIP coefficients Eq. 57
  503.         final double sinMODIP = FastMath.sin(FastMath.toRadians(modip));
  504.         final double[] m = new double[12];
  505.         m[0] = 1.0;
  506.         for (int i = 1; i < 12; i++) {
  507.             m[i] = sinMODIP * m[i - 1];
  508.             if (i < 7) {
  509.                 m3000 += m[i] * cm3[i];
  510.             }
  511.         }

  512.         // latitude and longitude terms
  513.         int index = 7;
  514.         final double cosLat1 = FastMath.cos(latitude);
  515.         double cosLatI = cosLat1;
  516.         for (int i = 1; i < r.length; i++) {
  517.             final double c = cosLatI * scL[2 * i - 1];
  518.             final double s = cosLatI * scL[2 * i - 2];
  519.             for (int j = 0; j < r[i]; j++) {
  520.                 m3000 += m[j] * c * cm3[index++];
  521.                 m3000 += m[j] * s * cm3[index++];
  522.             }
  523.             cosLatI *= cosLat1; // Eq. 58
  524.         }

  525.         return m3000;

  526.     }

  527.     /**
  528.      * This method computes the F1 layer critical frequency.
  529.      * <p>
  530.      * This computation performs the algorithm exposed in Annex F
  531.      * of the reference document.
  532.      * </p>
  533.      * @param foE the E layer critical frequency, MHz
  534.      * @return the F1 layer critical frequency, MHz
  535.      * @param foF2 the F2 layer critical frequency, MHz
  536.      */
  537.     private double computefoF1(final double foE, final double foF2) {
  538.         final double temp  = join(1.4 * foE, 0.0, 1000.0, foE - 2.0);
  539.         final double temp2 = join(0.0, temp, 1000.0, foE - temp);
  540.         final double value = join(temp2, 0.85 * temp2, 60.0, 0.85 * foF2 - temp2);
  541.         if (value < 1.0E-6) {
  542.             return 0.0;
  543.         } else {
  544.             return value;
  545.         }
  546.     }

  547.     /**
  548.      * This method allows the computation of the F2, F1 and E layer amplitudes.
  549.      * <p>
  550.      * The resulting element is an array having the following form:
  551.      * <ul>
  552.      * <li>double[0] = A1 → F2 layer amplitude
  553.      * <li>double[1] = A2 → F1 layer amplitude
  554.      * <li>double[2] = A3 → E  layer amplitude
  555.      * </ul>
  556.      * </p>
  557.      * @param nmE E layer maximum density in 10^11 m-3
  558.      * @param nmF1 F1 layer maximum density in 10^11 m-3
  559.      * @param foF1 F1 layer critical frequency in MHz
  560.      * @return a three components array containing the layer amplitudes
  561.      */
  562.     private double[] computeLayerAmplitudes(final double nmE, final double nmF1, final double foF1) {
  563.         // Initialize array
  564.         final double[] amplitude = new double[3];

  565.         // F2 layer amplitude (Eq. 90)
  566.         final double a1 = 4.0 * nmF2;
  567.         amplitude[0]   = a1;

  568.         // F1 and E layer amplitudes (Eq. 91 to 98)
  569.         if (foF1 < 0.5) {
  570.             amplitude[1] = 0.0;
  571.             amplitude[2] = 4.0 * (nmE - epst(a1, hmF2, b2Bot, hmE));
  572.         } else {
  573.             double a2a = 0.0;
  574.             double a3a = 4.0 * nmE;
  575.             for (int i = 0; i < 5; i++) {
  576.                 a2a = 4.0 * (nmF1 - epst(a1, hmF2, b2Bot, hmF1) - epst(a3a, hmE, beTop, hmF1));
  577.                 a2a = join(a2a, 0.8 * nmF1, 1.0, a2a - 0.8 * nmF1);
  578.                 a3a = 4.0 * (nmE - epst(a2a, hmF1, b1Bot, hmE) - epst(a1, hmF2, b2Bot, hmE));
  579.             }
  580.             amplitude[1] = a2a;
  581.             amplitude[2] = join(a3a, 0.05, 60.0, a3a - 0.005);
  582.         }

  583.         return amplitude;
  584.     }

  585.     /**
  586.      * This method computes the topside thickness parameter H0.
  587.      *
  588.      * @param month current month
  589.      * @param azr effective sunspot number
  590.      * @return H0 in km
  591.      */
  592.     private double computeH0(final int month, final double azr) {

  593.         // Auxiliary parameter ka (Eq. 99 and 100)
  594.         final double ka;
  595.         if (month > 3 && month < 10) {
  596.             // month = 4,5,6,7,8,9
  597.             ka = 6.705 - 0.014 * azr - 0.008 * hmF2;
  598.         } else {
  599.             // month = 1,2,3,10,11,12
  600.             final double ratio = hmF2 / b2Bot;
  601.             ka = -7.77 + 0.097 * ratio * ratio + 0.153 * nmF2;
  602.         }

  603.         // Auxiliary parameter kb (Eq. 101 and 102)
  604.         double kb = join(ka, 2.0, 1.0, ka - 2.0);
  605.         kb = join(8.0, kb, 1.0, kb - 8.0);

  606.         // Auxiliary parameter Ha (Eq. 103)
  607.         final double hA = kb * b2Bot;

  608.         // Auxiliary parameters x and v (Eq. 104 and 105)
  609.         final double x = 0.01 * (hA - 150.0);
  610.         final double v = (0.041163 * x - 0.183981) * x + 1.424472;

  611.         // Topside thickness parameter (Eq. 106)
  612.         return hA / v;

  613.     }

  614.     /**
  615.      * A clipped exponential function.
  616.      * <p>
  617.      * This function, describe in section F.2.12.2 of the reference document, is
  618.      * recommanded for the computation of exponential values.
  619.      * </p>
  620.      * @param power power for exponential function
  621.      * @return clipped exponential value
  622.      */
  623.     private double clipExp(final double power) {
  624.         if (power > 80.0) {
  625.             return 5.5406E34;
  626.         } else if (power < -80) {
  627.             return 1.8049E-35;
  628.         } else {
  629.             return FastMath.exp(power);
  630.         }
  631.     }

  632.     /**
  633.      * This method provides a third order interpolation function
  634.      * as recommended in the reference document (Ref Eq. 128 to Eq. 138)
  635.      *
  636.      * @param z1 z1 coefficient
  637.      * @param z2 z2 coefficient
  638.      * @param z3 z3 coefficient
  639.      * @param z4 z4 coefficient
  640.      * @param x position
  641.      * @return a third order interpolation
  642.      */
  643.     private double interpolate(final double z1, final double z2,
  644.                                final double z3, final double z4,
  645.                                final double x) {

  646.         if (FastMath.abs(2.0 * x) < 1e-10) {
  647.             return z2;
  648.         }

  649.         final double delta = 2.0 * x - 1.0;
  650.         final double g1 = z3 + z2;
  651.         final double g2 = z3 - z2;
  652.         final double g3 = z4 + z1;
  653.         final double g4 = (z4 - z1) / 3.0;
  654.         final double a0 = 9.0 * g1 - g3;
  655.         final double a1 = 9.0 * g2 - g4;
  656.         final double a2 = g3 - g1;
  657.         final double a3 = g4 - g2;
  658.         return 0.0625 * (a0 + delta * (a1 + delta * (a2 + delta * a3)));

  659.     }

  660.     /**
  661.      * Allows smooth joining of functions f1 and f2
  662.      * (i.e. continuous first derivatives) at origin.
  663.      * <p>
  664.      * This function, describe in section F.2.12.1 of the reference document, is
  665.      * recommanded for computational efficiency.
  666.      * </p>
  667.      * @param dF1 first function
  668.      * @param dF2 second function
  669.      * @param dA width of transition region
  670.      * @param dX x value
  671.      * @return the computed value
  672.      */
  673.     private double join(final double dF1, final double dF2,
  674.                         final double dA, final double dX) {
  675.         final double ee = clipExp(dA * dX);
  676.         return (dF1 * ee + dF2) / (ee + 1.0);
  677.     }

  678.     /**
  679.      * The Epstein function.
  680.      * <p>
  681.      * This function, describe in section 2.5.1 of the reference document, is used
  682.      * as a basis analytical function in NeQuick for the construction of the ionospheric layers.
  683.      * </p>
  684.      * @param x x parameter
  685.      * @param y y parameter
  686.      * @param z z parameter
  687.      * @param w w parameter
  688.      * @return value of the epstein function
  689.      */
  690.     private double epst(final double x, final double y,
  691.                         final double z, final double w) {
  692.         final double ex  = clipExp((w - y) / z);
  693.         final double opex = 1.0 + ex;
  694.         return x * ex / (opex * opex);

  695.     }

  696. }