1   /* Copyright 2002-2021 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  
19  import org.hipparchus.Field;
20  import org.hipparchus.CalculusFieldElement;
21  import org.hipparchus.util.FastMath;
22  import org.hipparchus.util.FieldSinCos;
23  import org.hipparchus.util.MathArrays;
24  import org.hipparchus.util.SinCos;
25  import org.orekit.time.DateComponents;
26  import org.orekit.time.DateTimeComponents;
27  import org.orekit.time.TimeComponents;
28  
29  /**
30   * This class perfoms the computation of the parameters used by the NeQuick model.
31   *
32   * @author Bryan Cazabonne
33   *
34   * @see "European Union (2016). European GNSS (Galileo) Open Service-Ionospheric Correction
35   *       Algorithm for Galileo Single Frequency Users. 1.2."
36   *
37   * @since 10.1
38   */
39  class FieldNeQuickParameters <T extends CalculusFieldElement<T>> {
40  
41      /** Solar zenith angle at day night transition, degrees. */
42      private static final double X0 = 86.23292796211615;
43  
44      /** F2 layer maximum density. */
45      private final T nmF2;
46  
47      /** F2 layer maximum density height [km]. */
48      private final T hmF2;
49  
50      /** F1 layer maximum density height [km]. */
51      private final T hmF1;
52  
53      /** E layer maximum density height [km]. */
54      private final T hmE;
55  
56      /** F2 layer bottom thickness parameter [km]. */
57      private final T b2Bot;
58  
59      /** F1 layer top thickness parameter [km]. */
60      private final T b1Top;
61  
62      /** F1 layer bottom thickness parameter [km]. */
63      private final T b1Bot;
64  
65      /** E layer top thickness parameter [km]. */
66      private final T beTop;
67  
68      /** E layer bottom thickness parameter [km]. */
69      private final T beBot;
70  
71      /** topside thickness parameter [km]. */
72      private final T h0;
73  
74      /** Layer amplitudes. */
75      private final T[] amplitudes;
76  
77      /**
78       * Build a new instance.
79       * @param field field of the elements
80       * @param dateTime current date time components
81       * @param f2 F2 coefficients used by the F2 layer
82       * @param fm3 Fm3 coefficients used by the F2 layer
83       * @param latitude latitude of a point along the integration path, in radians
84       * @param longitude longitude of a point along the integration path, in radians
85       * @param alpha effective ionisation level coefficients
86       * @param modipGrip modip grid
87       */
88      FieldNeQuickParameters(final Field<T> field, final DateTimeComponents dateTime, final double[][][] f2,
89                             final double[][][] fm3, final T latitude, final T longitude,
90                             final double[] alpha, final double[][] modipGrip) {
91  
92          // Zero
93          final T zero = field.getZero();
94  
95          // MODIP in degrees
96          final T modip = computeMODIP(latitude, longitude, modipGrip);
97          // Effective ionisation level Az
98          final T az = computeAz(modip, alpha);
99          // Effective sunspot number (Eq. 19)
100         final T azr = FastMath.sqrt(az.subtract(63.7).multiply(1123.6).add(167273.0)).subtract(408.99);
101         // Date and Time components
102         final DateComponents date = dateTime.getDate();
103         final TimeComponents time = dateTime.getTime();
104         // Hours
105         final double hours  = time.getSecondsInUTCDay() / 3600.0;
106         // Effective solar zenith angle in radians
107         final T xeff = computeEffectiveSolarAngle(date.getMonth(), hours, latitude, longitude);
108 
109         // Coefficients for F2 layer parameters
110         // Compute the array of interpolated coefficients for foF2 (Eq. 44)
111         final T[][] af2 = MathArrays.buildArray(field, 76, 13);
112         for (int j = 0; j < 76; j++) {
113             for (int k = 0; k < 13; k++ ) {
114                 af2[j][k] = azr.multiply(0.01).negate().add(1.0).multiply(f2[0][j][k]).add(azr.multiply(0.01).multiply(f2[1][j][k]));
115             }
116         }
117 
118         // Compute the array of interpolated coefficients for M(3000)F2 (Eq. 46)
119         final T[][] am3 = MathArrays.buildArray(field, 49, 9);
120         for (int j = 0; j < 49; j++) {
121             for (int k = 0; k < 9; k++ ) {
122                 am3[j][k] = azr.multiply(0.01).negate().add(1.0).multiply(fm3[0][j][k]).add(azr.multiply(0.01).multiply(fm3[1][j][k]));
123             }
124         }
125 
126         // E layer maximum density height in km (Eq. 78)
127         this.hmE = field.getZero().add(120.0);
128         // E layer critical frequency in MHz
129         final T foE = computefoE(date.getMonth(), az, xeff, latitude);
130         // E layer maximum density in 10^11 m-3 (Eq. 36)
131         final T nmE = foE.multiply(foE).multiply(0.124);
132 
133         // Time argument (Eq. 49)
134         final double t = FastMath.toRadians(15 * hours) - FastMath.PI;
135         // Compute Fourier time series for foF2 and M(3000)F2
136         final T[] cf2 = computeCF2(field, af2, t);
137         final T[] cm3 = computeCm3(field, am3, t);
138         // F2 layer critical frequency in MHz
139         final T foF2 = computefoF2(field, modip, cf2, latitude, longitude);
140         // Maximum Usable Frequency factor
141         final T mF2  = computeMF2(field, modip, cm3, latitude, longitude);
142         // F2 layer maximum density in 10^11 m-3
143         this.nmF2 = foF2.multiply(foF2).multiply(0.124);
144         // F2 layer maximum density height in km
145         this.hmF2 = computehmF2(field, foE, foF2, mF2);
146 
147         // F1 layer critical frequency in MHz
148         final T foF1 = computefoF1(field, foE, foF2);
149         // F1 layer maximum density in 10^11 m-3
150         final T nmF1;
151         if (foF1.getReal() <= 0.0 && foE.getReal() > 2.0) {
152             final T foEpopf = foE.add(0.5);
153             nmF1 = foEpopf.multiply(foEpopf).multiply(0.124);
154         } else {
155             nmF1 = foF1.multiply(foF1).multiply(0.124);
156         }
157         // F1 layer maximum density height in km
158         this.hmF1 = hmF2.add(hmE).multiply(0.5);
159 
160         // Thickness parameters (Eq. 85 to 89)
161         final T a = clipExp(FastMath.log(foF2.multiply(foF2)).multiply(0.857).add(FastMath.log(mF2).multiply(2.02)).add(-3.467)).multiply(0.01);
162         this.b2Bot = nmF2.divide(a).multiply(0.385);
163         this.b1Top = hmF2.subtract(hmF1).multiply(0.3);
164         this.b1Bot = hmF1.subtract(hmE).multiply(0.5);
165         this.beTop = FastMath.max(b1Bot, zero.add(7.0));
166         this.beBot = zero.add(5.0);
167 
168         // Layer amplitude coefficients
169         this.amplitudes = computeLayerAmplitudes(field, nmE, nmF1, foF1);
170 
171         // Topside thickness parameter
172         this.h0 = computeH0(field, date.getMonth(), azr);
173     }
174 
175     /**
176      * Get the F2 layer maximum density.
177      * @return nmF2
178      */
179     public T getNmF2() {
180         return nmF2;
181     }
182 
183     /**
184      * Get the F2 layer maximum density height.
185      * @return hmF2 in km
186      */
187     public T getHmF2() {
188         return hmF2;
189     }
190 
191     /**
192      * Get the F1 layer maximum density height.
193      * @return hmF1 in km
194      */
195     public T getHmF1() {
196         return hmF1;
197     }
198 
199     /**
200      * Get the E layer maximum density height.
201      * @return hmE in km
202      */
203     public T getHmE() {
204         return hmE;
205     }
206 
207     /**
208      * Get the F2 layer thickness parameter (bottom).
209      * @return B2Bot in km
210      */
211     public T getB2Bot() {
212         return b2Bot;
213     }
214 
215     /**
216      * Get the F1 layer thickness parameter (top).
217      * @return B1Top in km
218      */
219     public T getB1Top() {
220         return b1Top;
221     }
222 
223     /**
224      * Get the F1 layer thickness parameter (bottom).
225      * @return B1Bot in km
226      */
227     public T getB1Bot() {
228         return b1Bot;
229     }
230 
231     /**
232      * Get the E layer thickness parameter (bottom).
233      * @return BeBot in km
234      */
235     public T getBEBot() {
236         return beBot;
237     }
238 
239     /**
240      * Get the E layer thickness parameter (top).
241      * @return BeTop in km
242      */
243     public T getBETop() {
244         return beTop;
245     }
246 
247     /**
248      * Get the F2, F1 and E layer amplitudes.
249      * <p>
250      * The resulting element is an array having the following form:
251      * <ul>
252      * <li>double[0] = A1 → F2 layer amplitude
253      * <li>double[1] = A2 → F1 layer amplitude
254      * <li>double[2] = A3 → E  layer amplitude
255      * </ul>
256      * @return layer amplitudes
257      */
258     public T[] getLayerAmplitudes() {
259         return amplitudes.clone();
260     }
261 
262     /**
263      * Get the topside thickness parameter H0.
264      * @return H0 in km
265      */
266     public T getH0() {
267         return h0;
268     }
269 
270     /**
271      * Computes the value of the modified dip latitude (MODIP) for the
272      * given latitude and longitude.
273      *
274      * @param lat receiver latitude, radians
275      * @param lon receiver longitude, radians
276      * @param stModip modip grid
277      * @return the MODIP in degrees
278      */
279     private T computeMODIP(final T lat, final T lon, final double[][] stModip) {
280 
281         // Zero
282         final T zero = lat.getField().getZero();
283 
284         // For the MODIP computation, the latitude and longitude have to be converted in degrees
285         final T latitude  = FastMath.toDegrees(lat);
286         final T longitude = FastMath.toDegrees(lon);
287 
288         // Extreme cases
289         if (latitude.getReal() == 90.0 || latitude.getReal() == -90.0) {
290             return latitude;
291         }
292 
293         // Auxiliary parameter l (Eq. 6 to 8)
294         final int lF = (int) ((longitude.getReal() + 180) * 0.1);
295         int l = lF - 2;
296         if (l < 0) {
297             l += 36;
298         } else if (l > 33) {
299             l -= 36;
300         }
301 
302         // Auxiliary parameter a (Eq. 9 to 11)
303         final T a  = latitude.add(90).multiply(0.2).add(1.0);
304         final T aF = FastMath.floor(a);
305         // Eq. 10
306         final T x = a.subtract(aF);
307         // Eq. 11
308         final int i = (int) aF.getReal() - 2;
309 
310         // zi coefficients (Eq. 12 and 13)
311         final T z1 = interpolate(zero.add(stModip[i + 1][l + 2]), zero.add(stModip[i + 2][l + 2]),
312                                       zero.add(stModip[i + 3][l + 2]), zero.add(stModip[i + 4][l + 2]), x);
313         final T z2 = interpolate(zero.add(stModip[i + 1][l + 3]), zero.add(stModip[i + 2][l + 3]),
314                                       zero.add(stModip[i + 3][l + 3]), zero.add(stModip[i + 4][l + 3]), x);
315         final T z3 = interpolate(zero.add(stModip[i + 1][l + 4]), zero.add(stModip[i + 2][l + 4]),
316                                       zero.add(stModip[i + 3][l + 4]), zero.add(stModip[i + 4][l + 4]), x);
317         final T z4 = interpolate(zero.add(stModip[i + 1][l + 5]), zero.add(stModip[i + 2][l + 5]),
318                                       zero.add(stModip[i + 3][l + 5]), zero.add(stModip[i + 4][l + 5]), x);
319 
320         // Auxiliary parameter b (Eq. 14 and 15)
321         final T b  = longitude.add(180).multiply(0.1);
322         final T bF = FastMath.floor(b);
323         final T y  = b.subtract(bF);
324 
325         // MODIP (Ref Eq. 16)
326         final T modip = interpolate(z1, z2, z3, z4, y);
327 
328         return modip;
329     }
330 
331     /**
332      * This method computes the effective ionisation level Az.
333      * <p>
334      * This parameter is used for the computation of the Total Electron Content (TEC).
335      * </p>
336      * @param modip modified dip latitude (MODIP) in degrees
337      * @param alpha effective ionisation level coefficients
338      * @return the ionisation level Az
339      */
340     private T computeAz(final T modip, final double[] alpha) {
341         // Field
342         final Field<T> field = modip.getField();
343         // Zero
344         final T zero = field.getZero();
345         // Particular condition (Eq. 17)
346         if (alpha[0] == 0.0 && alpha[1] == 0.0 && alpha[2] == 0.0) {
347             return zero.add(63.7);
348         }
349         // Az = a0 + modip * a1 + modip^2 * a2 (Eq. 18)
350         T az = modip.multiply(alpha[2]).add(alpha[1]).multiply(modip).add(alpha[0]);
351         // If Az < 0 -> Az = 0
352         az = FastMath.max(zero, az);
353         // If Az > 400 -> Az = 400
354         az = FastMath.min(zero.add(400.0), az);
355         return az;
356     }
357 
358     /**
359      * This method computes the effective solar zenith angle.
360      * <p>
361      * The effective solar zenith angle is compute as a function of the
362      * solar zenith angle and the solar zenith angle at day night transition.
363      * </p>
364      * @param month current month of the year
365      * @param hours universal time (hours)
366      * @param latitude in radians
367      * @param longitude in radians
368      * @return the effective solar zenith angle, radians
369      */
370     private T computeEffectiveSolarAngle(final int month,
371                                          final double hours,
372                                          final T latitude,
373                                          final T longitude) {
374         // Zero
375         final T zero = latitude.getField().getZero();
376         // Local time (Eq.4)
377         final T lt = longitude.divide(FastMath.toRadians(15.0)).add(hours);
378         // Day of year at the middle of the month (Eq. 20)
379         final double dy = 30.5 * month - 15.0;
380         // Time (Eq. 21)
381         final double t = dy + (18 - hours) / 24;
382         // Arguments am and al (Eq. 22 and 23)
383         final double am = FastMath.toRadians(0.9856 * t - 3.289);
384         final double al = am + FastMath.toRadians(1.916 * FastMath.sin(am) + 0.020 * FastMath.sin(2.0 * am) + 282.634);
385         // Sine and cosine of solar declination (Eq. 24 and 25)
386         final double sDec = 0.39782 * FastMath.sin(al);
387         final double cDec = FastMath.sqrt(1. - sDec * sDec);
388         // Solar zenith angle, deg (Eq. 26 and 27)
389         final FieldSinCos<T> scLat   = FastMath.sinCos(latitude);
390         final T coef    = lt.negate().add(12.0).multiply(FastMath.PI / 12);
391         final T cZenith = scLat.sin().multiply(sDec).add(scLat.cos().multiply(cDec).multiply(FastMath.cos(coef)));
392         final T angle   = FastMath.atan2(FastMath.sqrt(cZenith.multiply(cZenith).negate().add(1.0)), cZenith);
393         final T x       = FastMath.toDegrees(angle);
394         // Effective solar zenith angle (Eq. 28)
395         final T xeff = join(clipExp(x.multiply(0.2).negate().add(20.0)).multiply(0.24).negate().add(90.0), x, zero.add(12.0), x.subtract(X0));
396         return FastMath.toRadians(xeff);
397     }
398 
399     /**
400      * This method computes the E layer critical frequency at a given location.
401      * @param month current month
402      * @param az ffective ionisation level
403      * @param xeff effective solar zenith angle in radians
404      * @param latitude latitude in radians
405      * @return the E layer critical frequency at a given location in MHz
406      */
407     private T computefoE(final int month, final T az,
408                          final T xeff, final T latitude) {
409         // The latitude has to be converted in degrees
410         final T lat = FastMath.toDegrees(latitude);
411         // Square root of the effective ionisation level
412         final T sqAz = FastMath.sqrt(az);
413         // seas parameter (Eq. 30 to 32)
414         final int seas;
415         if (month == 1 || month == 2 || month == 11 || month == 12) {
416             seas = -1;
417         } else if (month == 3 || month == 4 || month == 9 || month == 10) {
418             seas = 0;
419         } else {
420             seas = 1;
421         }
422         // Latitudinal dependence (Eq. 33 and 34)
423         final T ee = clipExp(lat.multiply(0.3));
424         final T seasp = ee.subtract(1.0).divide(ee.add(1.0)).multiply(seas);
425         // Critical frequency (Eq. 35)
426         final T coef = seasp.multiply(0.019).negate().add(1.112);
427         final T foE = FastMath.sqrt(coef .multiply(coef).multiply(sqAz).multiply(FastMath.cos(xeff).pow(0.6)).add(0.49));
428         return foE;
429     }
430 
431     /**
432      * Computes the F2 layer height of maximum electron density.
433      * @param field field of the elements
434      * @param foE E layer layer critical frequency in MHz
435      * @param foF2 F2 layer layer critical frequency in MHz
436      * @param mF2 maximum usable frequency factor
437      * @return hmF2 in km
438      */
439     private T computehmF2(final Field<T> field, final T foE, final T foF2, final T mF2) {
440         // Zero
441         final T zero = field.getZero();
442         // Ratio
443         final T fo = foF2.divide(foE);
444         final T ratio = join(fo, zero.add(1.75), zero.add(20.0), fo.subtract(1.75));
445 
446         // deltaM parameter
447         T deltaM = zero.subtract(0.012);
448         if (foE.getReal() >= 1e-30) {
449             deltaM = deltaM.add(ratio.subtract(1.215).divide(0.253).reciprocal());
450         }
451 
452         // hmF2 Eq. 80
453         final T mF2Sq = mF2.multiply(mF2);
454         final T temp  = FastMath.sqrt(mF2Sq.multiply(0.0196).add(1.0).divide(mF2Sq.multiply(1.2967).subtract(1.0)));
455         final T height  = mF2.multiply(1490.0).multiply(temp).divide(mF2.add(deltaM)).subtract(176.0);
456         return height;
457     }
458 
459     /**
460      * Computes cf2 coefficients.
461      * @param field field of the elements
462      * @param af2 interpolated coefficients for foF2
463      * @param t time argument
464      * @return the cf2 coefficients array
465      */
466     private T[] computeCF2(final Field<T> field, final T[][] af2, final double t) {
467         // Eq. 50
468         final T[] cf2 = MathArrays.buildArray(field, 76);
469         for (int i = 0; i < cf2.length; i++) {
470             T sum = field.getZero();
471             for (int k = 0; k < 6; k++) {
472                 final SinCos sc = FastMath.sinCos((k + 1) * t);
473                 sum = sum.add(af2[i][2 * k + 1].multiply(sc.sin()).add(af2[i][2 * (k + 1)].multiply(sc.cos())));
474             }
475             cf2[i] = af2[i][0].add(sum);
476         }
477         return cf2;
478     }
479 
480     /**
481      * Computes Cm3 coefficients.
482      * @param field field of the elements
483      * @param am3 interpolated coefficients for foF2
484      * @param t time argument
485      * @return the Cm3 coefficients array
486      */
487     private T[] computeCm3(final Field<T> field, final T[][] am3, final double t) {
488         // Eq. 51
489         final T[] cm3 = MathArrays.buildArray(field, 49);
490         for (int i = 0; i < cm3.length; i++) {
491             T sum = field.getZero();
492             for (int k = 0; k < 4; k++) {
493                 final SinCos sc = FastMath.sinCos((k + 1) * t);
494                 sum = sum.add(am3[i][2 * k + 1].multiply(sc.sin()).add(am3[i][2 * (k + 1)].multiply(sc.cos())));
495             }
496             cm3[i] = am3[i][0].add(sum);
497         }
498         return cm3;
499     }
500 
501     /**
502      * This method computes the F2 layer critical frequency.
503      * @param field field of the elements
504      * @param modip modified DIP latitude, in degrees
505      * @param cf2 Fourier time series for foF2
506      * @param latitude latitude in radians
507      * @param longitude longitude in radians
508      * @return the F2 layer critical frequency, MHz
509      */
510     private T computefoF2(final Field<T> field, final T modip, final T[] cf2,
511                           final T latitude, final T longitude) {
512 
513         // One
514         final T one = field.getOne();
515 
516         // Legendre grades (Eq. 63)
517         final int[] q = new int[] {
518             12, 12, 9, 5, 2, 1, 1, 1, 1
519         };
520 
521         // Array for geographic terms
522         final T[] g = MathArrays.buildArray(field, cf2.length);
523         g[0] = one;
524 
525         // MODIP coefficients Eq. 57
526         final T sinMODIP = FastMath.sin(FastMath.toRadians(modip));
527         final T[] m = MathArrays.buildArray(field, 12);
528         m[0] = one;
529         for (int i = 1; i < q[0]; i++) {
530             m[i] = sinMODIP.multiply(m[i - 1]);
531             g[i] = m[i];
532         }
533 
534         // Latitude coefficients (Eq. 58)
535         final T cosLat = FastMath.cos(latitude);
536         final T[] p = MathArrays.buildArray(field, 8);
537         p[0] = cosLat;
538         for (int n = 2; n < 9; n++) {
539             p[n - 1] = cosLat.multiply(p[n - 2]);
540         }
541 
542         // latitude and longitude terms
543         int index = 12;
544         for (int i = 1; i < q.length; i++) {
545             final FieldSinCos<T> sc = FastMath.sinCos(longitude.multiply(i));
546             for (int j = 0; j < q[i]; j++) {
547                 g[index++] = m[j].multiply(p[i - 1]).multiply(sc.cos());
548                 g[index++] = m[j].multiply(p[i - 1]).multiply(sc.sin());
549             }
550         }
551 
552         // Compute foF2 by linear combination
553         final T frequency = one.linearCombination(g, cf2);
554         return frequency;
555     }
556 
557     /**
558      * This method computes the Maximum Usable Frequency factor.
559      * @param field field of the elements
560      * @param modip modified DIP latitude, in degrees
561      * @param cm3 Fourier time series for M(3000)F2
562      * @param latitude latitude in radians
563      * @param longitude longitude in radians
564      * @return the Maximum Usable Frequency factor
565      */
566     private T computeMF2(final Field<T> field, final T modip, final T[] cm3,
567                          final T latitude, final T longitude) {
568 
569         // One
570         final T one = field.getOne();
571         // Legendre grades (Eq. 71)
572         final int[] r = new int[] {
573             7, 8, 6, 3, 2, 1, 1
574         };
575 
576         // Array for geographic terms
577         final T[] g = MathArrays.buildArray(field, cm3.length);
578         g[0] = one;
579 
580         // MODIP coefficients Eq. 57
581         final T sinMODIP = FastMath.sin(FastMath.toRadians(modip));
582         final T[] m = MathArrays.buildArray(field, 12);
583         m[0] = one;
584         for (int i = 1; i < 12; i++) {
585             m[i] = sinMODIP.multiply(m[i - 1]);
586             if (i < 7) {
587                 g[i] = m[i];
588             }
589         }
590 
591         // Latitude coefficients (Eq. 58)
592         final T cosLat = FastMath.cos(latitude);
593         final T[] p = MathArrays.buildArray(field, 8);
594         p[0] = cosLat;
595         for (int n = 2; n < 9; n++) {
596             p[n - 1] = cosLat.multiply(p[n - 2]);
597         }
598 
599         // latitude and longitude terms
600         int index = 7;
601         for (int i = 1; i < r.length; i++) {
602             final FieldSinCos<T> sc = FastMath.sinCos(longitude.multiply(i));
603             for (int j = 0; j < r[i]; j++) {
604                 g[index++] = m[j].multiply(p[i - 1]).multiply(sc.cos());
605                 g[index++] = m[j].multiply(p[i - 1]).multiply(sc.sin());
606             }
607         }
608 
609         // Compute m3000 by linear combination
610         final T m3000 = one.linearCombination(g, cm3);
611         return m3000;
612     }
613 
614     /**
615      * This method computes the F1 layer critical frequency.
616      * <p>
617      * This computation performs the algorithm exposed in Annex F
618      * of the reference document.
619      * </p>
620      * @param field field of the elements
621      * @param foE the E layer critical frequency, MHz
622      * @return the F1 layer critical frequency, MHz
623      * @param foF2 the F2 layer critical frequency, MHz
624      */
625     private T computefoF1(final Field<T> field, final T foE, final T foF2) {
626         final T zero = field.getZero();
627         final T temp  = join(foE.multiply(1.4), zero, zero.add(1000.0), foE.subtract(2.0));
628         final T temp2 = join(zero, temp, zero.add(1000.0), foE.subtract(temp));
629         final T value = join(temp2, temp2.multiply(0.85), zero.add(60.0), foF2.multiply(0.85).subtract(temp2));
630         if (value.getReal() < 1.0E-6) {
631             return zero;
632         } else {
633             return value;
634         }
635     }
636 
637     /**
638      * This method allows the computation of the F2, F1 and E layer amplitudes.
639      * <p>
640      * The resulting element is an array having the following form:
641      * <ul>
642      * <li>double[0] = A1 → F2 layer amplitude
643      * <li>double[1] = A2 → F1 layer amplitude
644      * <li>double[2] = A3 → E  layer amplitude
645      * </ul>
646      * </p>
647      * @param field field of the elements
648      * @param nmE E layer maximum density in 10^11 m-3
649      * @param nmF1 F1 layer maximum density in 10^11 m-3
650      * @param foF1 F1 layer critical frequency in MHz
651      * @return a three components array containing the layer amplitudes
652      */
653     private T[] computeLayerAmplitudes(final Field<T> field, final T nmE, final T nmF1, final T foF1) {
654         // Zero
655         final T zero = field.getZero();
656 
657         // Initialize array
658         final T[] amplitude = MathArrays.buildArray(field, 3);
659 
660         // F2 layer amplitude (Eq. 90)
661         final T a1 = nmF2.multiply(4.0);
662         amplitude[0] = a1;
663 
664         // F1 and E layer amplitudes (Eq. 91 to 98)
665         if (foF1.getReal() < 0.5) {
666             amplitude[1] = zero;
667             amplitude[2] = nmE.subtract(epst(a1, hmF2, b2Bot, hmE)).multiply(4.0);
668         } else {
669             T a2a = zero;
670             T a3a = nmE.multiply(4.0);
671             for (int i = 0; i < 5; i++) {
672                 a2a = nmF1.subtract(epst(a1, hmF2, b2Bot, hmF1)).subtract(epst(a3a, hmE, beTop, hmF1)).multiply(4.0);
673                 a2a = join(a2a, nmF1.multiply(0.8), field.getOne(), a2a.subtract(nmF1.multiply(0.8)));
674                 a3a = nmE.subtract(epst(a2a, hmF1, b1Bot, hmE)).subtract(epst(a1, hmF2, b2Bot, hmE)).multiply(4.0);
675             }
676             amplitude[1] = a2a;
677             amplitude[2] = join(a3a, zero.add(0.05), zero.add(60.0), a3a.subtract(0.005));
678         }
679 
680         return amplitude;
681     }
682 
683     /**
684      * This method computes the topside thickness parameter H0.
685      *
686      * @param field field of the elements
687      * @param month current month
688      * @param azr effective sunspot number
689      * @return H0 in km
690      */
691     private T computeH0(final Field<T> field, final int month, final T azr) {
692 
693         // One
694         final T one = field.getOne();
695 
696         // Auxiliary parameter ka (Eq. 99 and 100)
697         final T ka;
698         if (month > 3 && month < 10) {
699             // month = 4,5,6,7,8,9
700             ka = azr.multiply(0.014).add(hmF2.multiply(0.008)).negate().add(6.705);
701         } else {
702             // month = 1,2,3,10,11,12
703             final T ratio = hmF2.divide(b2Bot);
704             ka = ratio.multiply(ratio).multiply(0.097).add(nmF2.multiply(0.153)).add(-7.77);
705         }
706 
707         // Auxiliary parameter kb (Eq. 101 and 102)
708         T kb = join(ka, one.multiply(2.0), one, ka.subtract(2.0));
709         kb = join(one.multiply(8.0), kb, one, kb.subtract(8.0));
710 
711         // Auxiliary parameter Ha (Eq. 103)
712         final T hA = kb.multiply(b2Bot);
713 
714         // Auxiliary parameters x and v (Eq. 104 and 105)
715         final T x = hA.subtract(150.0).multiply(0.01);
716         final T v = x.multiply(0.041163).subtract(0.183981).multiply(x).add(1.424472);
717 
718         // Topside thickness parameter (Eq. 106)
719         final T h = hA.divide(v);
720         return h;
721     }
722 
723     /**
724      * A clipped exponential function.
725      * <p>
726      * This function, describe in section F.2.12.2 of the reference document, is
727      * recommanded for the computation of exponential values.
728      * </p>
729      * @param power power for exponential function
730      * @return clipped exponential value
731      */
732     private T clipExp(final T power) {
733         final T zero = power.getField().getZero();
734         if (power.getReal() > 80.0) {
735             return zero.add(5.5406E34);
736         } else if (power.getReal() < -80) {
737             return zero.add(1.8049E-35);
738         } else {
739             return FastMath.exp(power);
740         }
741     }
742 
743     /**
744      * This method provides a third order interpolation function
745      * as recommended in the reference document (Ref Eq. 128 to Eq. 138)
746      *
747      * @param z1 z1 coefficient
748      * @param z2 z2 coefficient
749      * @param z3 z3 coefficient
750      * @param z4 z4 coefficient
751      * @param x position
752      * @return a third order interpolation
753      */
754     private T interpolate(final T z1, final T z2,
755                           final T z3, final T z4,
756                           final T x) {
757 
758         if (FastMath.abs(2.0 * x.getReal()) < 1e-10) {
759             return z2;
760         }
761 
762         final T delta = x.multiply(2.0).subtract(1.0);
763         final T g1 = z3.add(z2);
764         final T g2 = z3.subtract(z2);
765         final T g3 = z4.add(z1);
766         final T g4 = z4.subtract(z1).divide(3.0);
767         final T a0 = g1.multiply(9.0).subtract(g3);
768         final T a1 = g2.multiply(9.0).subtract(g4);
769         final T a2 = g3.subtract(g1);
770         final T a3 = g4.subtract(g2);
771         final T zx = delta.multiply(a3).add(a2).multiply(delta).add(a1).multiply(delta).add(a0).multiply(0.0625);
772 
773         return zx;
774     }
775 
776     /**
777      * Allows smooth joining of functions f1 and f2
778      * (i.e. continuous first derivatives) at origin.
779      * <p>
780      * This function, describe in section F.2.12.1 of the reference document, is
781      * recommanded for computational efficiency.
782      * </p>
783      * @param dF1 first function
784      * @param dF2 second function
785      * @param dA width of transition region
786      * @param dX x value
787      * @return the computed value
788      */
789     private T join(final T dF1, final T dF2,
790                    final T dA, final T dX) {
791         final T ee = clipExp(dA.multiply(dX));
792         return dF1.multiply(ee).add(dF2).divide(ee.add(1.0));
793     }
794 
795     /**
796      * The Epstein function.
797      * <p>
798      * This function, describe in section 2.5.1 of the reference document, is used
799      * as a basis analytical function in NeQuick for the construction of the ionospheric layers.
800      * </p>
801      * @param x x parameter
802      * @param y y parameter
803      * @param z z parameter
804      * @param w w parameter
805      * @return value of the epstein function
806      */
807     private T epst(final T x, final T y,
808                    final T z, final T w) {
809         final T ex  = clipExp(w.subtract(y).divide(z));
810         final T opex = ex.add(1.0);
811         final T epst = x.multiply(ex).divide(opex.multiply(opex));
812         return epst;
813     }
814 
815 }