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  
19  import java.util.Collections;
20  import java.util.List;
21  
22  import org.hipparchus.CalculusFieldElement;
23  import org.hipparchus.Field;
24  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
25  import org.hipparchus.geometry.euclidean.threed.Vector3D;
26  import org.hipparchus.util.FastMath;
27  import org.hipparchus.util.MathArrays;
28  import org.orekit.bodies.BodyShape;
29  import org.orekit.bodies.FieldGeodeticPoint;
30  import org.orekit.bodies.GeodeticPoint;
31  import org.orekit.frames.FieldStaticTransform;
32  import org.orekit.frames.Frame;
33  import org.orekit.frames.StaticTransform;
34  import org.orekit.frames.TopocentricFrame;
35  import org.orekit.models.earth.ionosphere.IonosphericDelayModel;
36  import org.orekit.models.earth.ionosphere.IonosphericModel;
37  import org.orekit.propagation.FieldSpacecraftState;
38  import org.orekit.propagation.SpacecraftState;
39  import org.orekit.time.AbsoluteDate;
40  import org.orekit.time.DateComponents;
41  import org.orekit.time.DateTimeComponents;
42  import org.orekit.time.FieldAbsoluteDate;
43  import org.orekit.time.TimeScale;
44  import org.orekit.utils.ParameterDriver;
45  import org.orekit.utils.units.Unit;
46  
47  /**
48   * NeQuick ionospheric delay model.
49   *
50   * @author Bryan Cazabonne
51   *
52   * @see "European Union (2016). European GNSS (Galileo) Open Service-Ionospheric Correction
53   *       Algorithm for Galileo Single Frequency Users. 1.2."
54   * @see <a href="https://www.itu.int/rec/R-REC-P.531/en">ITU-R P.531</a>
55   *
56   * @since 10.1
57   */
58  public abstract class NeQuickModel implements IonosphericModel, IonosphericDelayModel {
59  
60      /** Mean Earth radius in m (Ref Table 2.5.2). */
61      public static final double RE = 6371200.0;
62  
63      /** Factor for the electron density computation. */
64      private static final double DENSITY_FACTOR = 1.0e11;
65  
66      /** Factor for the path delay computation. */
67      private static final double DELAY_FACTOR = 40.3e16;
68  
69      /** F2 coefficients used by the F2 layer (flatten array for cache efficiency). */
70      private final double[][] flattenF2;
71  
72      /** Fm3 coefficients used by the M(3000)F2 layer(flatten array for cache efficiency). */
73      private final double[][] flattenFm3;
74  
75      /** UTC time scale. */
76      private final TimeScale utc;
77  
78      /** Simple constructor.
79       * @param utc UTC time scale
80       * @since 13.0
81       */
82      protected NeQuickModel(final TimeScale utc) {
83  
84          this.utc = utc;
85  
86          // F2 layer values
87          this.flattenF2  = new double[12][];
88          this.flattenFm3 = new double[12][];
89  
90      }
91  
92      /** Get UTC time scale.
93       * @return UTC time scale
94       * @since 13.0
95       */
96      public TimeScale getUtc() {
97          return utc;
98      }
99  
100     /** {@inheritDoc} */
101     @Override
102     public double pathDelay(final SpacecraftState state, final TopocentricFrame baseFrame,
103                             final double frequency, final double[] parameters) {
104         return pathDelay(state, baseFrame, state.getDate(), frequency, parameters);
105     }
106 
107     /** {@inheritDoc} */
108     @Override
109     public double pathDelay(final SpacecraftState state,
110                             final TopocentricFrame baseFrame, final AbsoluteDate receptionDate,
111                             final double frequency, final double[] parameters) {
112 
113         // Reference body shape
114         final BodyShape ellipsoid = baseFrame.getParentShape();
115 
116         // we use transform from body frame to inert frame and invert it
117         // because base frame could be peered with inertial frame (hence improving performances with caching)
118         // but the reverse is almost never used
119         final Frame           bodyFrame  = ellipsoid.getBodyFrame();
120         final StaticTransform body2Inert = bodyFrame.getStaticTransformTo(state.getFrame(), receptionDate);
121         final Vector3D        position   = body2Inert.getInverse().transformPosition(state.getPosition());
122 
123         // Point
124         final GeodeticPoint recPoint = baseFrame.getPoint();
125         // Date
126         final AbsoluteDate date = state.getDate();
127 
128         // Satellite geodetic coordinates
129         final GeodeticPoint satPoint = ellipsoid.transform(position, bodyFrame, state.getDate());
130 
131         // Total Electron Content
132         final double tec = stec(date, recPoint, satPoint);
133 
134         // Ionospheric delay
135         final double factor = DELAY_FACTOR / (frequency * frequency);
136         return factor * tec;
137     }
138 
139     /** {@inheritDoc} */
140     @Override
141     public <T extends CalculusFieldElement<T>> T pathDelay(final FieldSpacecraftState<T> state,
142                                                            final TopocentricFrame baseFrame,
143                                                            final double frequency,
144                                                            final T[] parameters) {
145         return pathDelay(state, baseFrame, state.getDate(), frequency, parameters);
146     }
147 
148     /** {@inheritDoc} */
149     @Override
150     public <T extends CalculusFieldElement<T>> T pathDelay(final FieldSpacecraftState<T> state,
151                                                            final TopocentricFrame baseFrame,
152                                                            final FieldAbsoluteDate<T> receptionDate,
153                                                            final double frequency,
154                                                            final T[] parameters) {
155         // Reference body shape
156         final BodyShape ellipsoid = baseFrame.getParentShape();
157 
158         // we use transform from body frame to inert frame and invert it
159         // because base frame could be peered with inertial frame (hence improving performances with caching)
160         // but the reverse is almost never used
161         final Frame                   bodyFrame  = ellipsoid.getBodyFrame();
162         final FieldStaticTransform<T> body2Inert = bodyFrame.getStaticTransformTo(state.getFrame(), receptionDate);
163         final FieldVector3D<T>        position   = body2Inert.getInverse().transformPosition(state.getPosition());
164 
165         // Date
166         final FieldAbsoluteDate<T> date = state.getDate();
167         // Point
168         final FieldGeodeticPoint<T> recPoint = baseFrame.getPoint(date.getField());
169 
170         // Satellite geodetic coordinates
171         final FieldGeodeticPoint<T> satPoint = ellipsoid.transform(position, bodyFrame, state.getDate());
172 
173         // Total Electron Content
174         final T tec = stec(date, recPoint, satPoint);
175 
176         // Ionospheric delay
177         final double factor = DELAY_FACTOR / (frequency * frequency);
178         return tec.multiply(factor);
179     }
180 
181     /** {@inheritDoc} */
182     @Override
183     public List<ParameterDriver> getParametersDrivers() {
184         return Collections.emptyList();
185     }
186 
187     /**
188      * This method allows the computation of the Slant Total Electron Content (STEC).
189      * @param date current date
190      * @param recP receiver position
191      * @param satP satellite position
192      * @return the STEC in TECUnits
193      */
194     public double stec(final AbsoluteDate date, final GeodeticPoint recP, final GeodeticPoint satP) {
195         return stec(date.getComponents(utc), new Ray(recP, satP));
196     }
197 
198     /**
199      * This method allows the computation of the Slant Total Electron Content (STEC).
200      * @param <T> type of the elements
201      * @param date current date
202      * @param recP receiver position
203      * @param satP satellite position
204      * @return the STEC in TECUnits
205      */
206     public <T extends CalculusFieldElement<T>> T stec(final FieldAbsoluteDate<T> date,
207                                                       final FieldGeodeticPoint<T> recP,
208                                                       final FieldGeodeticPoint<T> satP) {
209         return stec(date.getComponents(utc), new FieldRay<>(recP, satP));
210     }
211 
212     /** Compute modip for a location.
213      * @param latitude latitude
214      * @param longitude longitude
215      * @return modip at specified location
216      * @since 13.0
217      */
218     protected abstract double computeMODIP(double latitude, double longitude);
219 
220     /** Compute modip for a location.
221      * @param <T> type of the field elements
222      * @param latitude latitude
223      * @param longitude longitude
224      * @return modip at specified location
225      * @since 13.0
226      */
227     protected abstract <T extends CalculusFieldElement<T>> T computeMODIP(T latitude, T longitude);
228 
229     /**
230      * Compute Fourier time series.
231      * @param dateTime current date time components
232      * @param az effective ionisation level
233      * @return Fourier time series
234      * @since 13.0.1
235      */
236     public FourierTimeSeries computeFourierTimeSeries(final DateTimeComponents dateTime, final double az) {
237 
238          // Load the correct CCIR file
239         loadsIfNeeded(dateTime.getDate());
240 
241         return new FourierTimeSeries(dateTime, az,
242                                      flattenF2[dateTime.getDate().getMonth() - 1],
243                                      flattenFm3[dateTime.getDate().getMonth() - 1]);
244 
245     }
246 
247     /**
248      * Computes the electron density at a given height.
249      * @param dateTime date
250      * @param az effective ionization level
251      * @param latitude latitude along the integration path
252      * @param longitude longitude along the integration path
253      * @param h height along the integration path in m
254      * @return electron density [m⁻³]
255      * @since 13.0
256      */
257     public double electronDensity(final DateTimeComponents dateTime, final double az,
258                                   final double latitude, final double longitude, final double h) {
259         return electronDensity(computeFourierTimeSeries(dateTime, az), latitude, longitude, h);
260     }
261 
262     /**
263      * Computes the electron density at a given height.
264      * @param fourierTimeSeries Fourier time series for foF2 and M(3000)F2 layer (flatten array)
265      * @param latitude latitude along the integration path
266      * @param longitude longitude along the integration path
267      * @param h height along the integration path in m
268      * @return electron density [m⁻³]
269      * @since 13.0.1
270      */
271     public double electronDensity(final FourierTimeSeries fourierTimeSeries,
272                                   final double latitude, final double longitude, final double h) {
273 
274         final double modip = computeMODIP(latitude, longitude);
275         final NeQuickParameters parameters = new NeQuickParameters(fourierTimeSeries, latitude, longitude, modip);
276 
277         // Convert height in kilometers
278         final double hInKm = Unit.KILOMETRE.fromSI(h);
279 
280         // Electron density
281         final double n;
282         if (hInKm <= parameters.getHmF2()) {
283             n = bottomElectronDensity(hInKm, parameters);
284         } else {
285             n = topElectronDensity(hInKm, parameters);
286         }
287 
288         return n;
289 
290     }
291 
292     /**
293      * Compute Fourier time series.
294      * @param <T> type of the elements
295      * @param dateTime current date time components
296      * @param az effective ionisation level
297      * @return Fourier time series
298      * @since 13.0.1
299      */
300     public <T extends CalculusFieldElement<T>> FieldFourierTimeSeries<T> computeFourierTimeSeries(final DateTimeComponents dateTime,
301                                                                                                   final T az) {
302 
303          // Load the correct CCIR file
304         loadsIfNeeded(dateTime.getDate());
305 
306         return new FieldFourierTimeSeries<>(dateTime, az,
307                                             flattenF2[dateTime.getDate().getMonth() - 1],
308                                             flattenFm3[dateTime.getDate().getMonth() - 1]);
309 
310     }
311 
312     /**
313      * Computes the electron density at a given height.
314      * @param <T> type of the elements
315      * @param dateTime date
316      * @param az effective ionization level
317      * @param latitude latitude along the integration path
318      * @param longitude longitude along the integration path
319      * @param h height along the integration path in m
320      * @return electron density [m⁻³]
321      * @since 13.0
322      * CalculusFieldElement, CalculusFieldElement, CalculusFieldElement)}
323      */
324     public <T extends CalculusFieldElement<T>> T electronDensity(final DateTimeComponents dateTime, final T az,
325                                                                  final T latitude, final T longitude, final T h) {
326         return electronDensity(computeFourierTimeSeries(dateTime, az), latitude, longitude, h);
327     }
328 
329     /**
330      * Computes the electron density at a given height.
331      * @param <T> type of the elements
332      * @param fourierTimeSeries Fourier time series for foF2 and M(3000)F2 layer (flatten array)
333      * @param latitude latitude along the integration path
334      * @param longitude longitude along the integration path
335      * @param h height along the integration path in m
336      * @return electron density [m⁻³]
337      * @since 13.0.1
338      */
339     public <T extends CalculusFieldElement<T>> T electronDensity(final FieldFourierTimeSeries<T> fourierTimeSeries,
340                                                                  final T latitude, final T longitude, final T h) {
341 
342         final T modip = computeMODIP(latitude, longitude);
343         final FieldNeQuickParameters<T> parameters = new FieldNeQuickParameters<>(fourierTimeSeries, latitude, longitude, modip);
344 
345         // Convert height in kilometers
346         final T hInKm = Unit.KILOMETRE.fromSI(h);
347 
348         // Electron density
349         final T n;
350         if (hInKm.getReal() <= parameters.getHmF2().getReal()) {
351             n = bottomElectronDensity(hInKm, parameters);
352         } else {
353             n = topElectronDensity(hInKm, parameters);
354         }
355 
356         return n;
357 
358     }
359 
360     /**
361      * Computes the electron density of the bottomside.
362      * @param h height in km
363      * @param parameters NeQuick model parameters
364      * @return the electron density N in m⁻³
365      */
366     private double bottomElectronDensity(final double h, final NeQuickParameters parameters) {
367 
368         // Select the relevant B parameter for the current height (Eq. 109 and 110)
369         final double be  = parameters.getBE(h);
370         final double bf1 = parameters.getBF1(h);
371         final double bf2 = parameters.getB2Bot();
372 
373         // Useful array of constants
374         final double[] ct = new double[] {
375             1.0 / bf2, 1.0 / bf1, 1.0 / be
376         };
377 
378         // Compute the exponential argument for each layer (Eq. 111 to 113)
379         final double[] arguments = computeExponentialArguments(h, parameters);
380 
381         // S coefficients
382         final double[] s = new double[3];
383         // Array of corrective terms
384         final double[] ds = new double[3];
385 
386         // Layer amplitudes
387         final double[] amplitudes = parameters.getLayerAmplitudes();
388 
389         // Fill arrays (Eq. 114 to 118)
390         for (int i = 0; i < 3; i++) {
391             if (FastMath.abs(arguments[i]) > 25.0) {
392                 s[i]  = 0.0;
393                 ds[i] = 0.0;
394             } else {
395                 final double expA   = clipExp(arguments[i]);
396                 final double opExpA = 1.0 + expA;
397                 s[i]  = amplitudes[i] * (expA / (opExpA * opExpA));
398                 ds[i] = ct[i] * ((1.0 - expA) / (1.0 + expA));
399             }
400         }
401 
402         // Electron density
403         final double aNo = s[0] + s[1] + s[2];
404         if (applyChapmanParameters(h)) {
405             // Chapman parameters (Eq. 119 and 120)
406             final double bc = 1.0 - 10.0 * (MathArrays.linearCombination(s[0], ds[0], s[1], ds[1], s[2], ds[2]) / aNo);
407             final double z  = 0.1 * (h - 100.0);
408             // Electron density (Eq. 121)
409             return aNo * clipExp(1.0 - bc * z - clipExp(-z)) * DENSITY_FACTOR;
410         } else {
411             return aNo * DENSITY_FACTOR;
412         }
413 
414     }
415 
416     /**
417      * Computes the electron density of the bottomside.
418      * @param <T> type of the elements
419      * @param h height in km
420      * @param parameters NeQuick model parameters
421      * @return the electron density N in m⁻³
422      */
423     private <T extends CalculusFieldElement<T>> T bottomElectronDensity(final T h,
424                                                                         final FieldNeQuickParameters<T> parameters) {
425 
426         // Zero and One
427         final Field<T> field = h.getField();
428         final T zero = field.getZero();
429         final T one  = field.getOne();
430 
431         // Select the relevant B parameter for the current height (Eq. 109 and 110)
432         final T be  = parameters.getBE(h);
433         final T bf1 = parameters.getBF1(h);
434         final T bf2 = parameters.getB2Bot();
435 
436         // Useful array of constants
437         final T[] ct = MathArrays.buildArray(field, 3);
438         ct[0] = bf2.reciprocal();
439         ct[1] = bf1.reciprocal();
440         ct[2] = be.reciprocal();
441 
442         // Compute the exponential argument for each layer (Eq. 111 to 113)
443         final T[] arguments = computeExponentialArguments(h, parameters);
444 
445         // S coefficients
446         final T[] s = MathArrays.buildArray(field, 3);
447         // Array of corrective terms
448         final T[] ds = MathArrays.buildArray(field, 3);
449 
450         // Layer amplitudes
451         final T[] amplitudes = parameters.getLayerAmplitudes();
452 
453         // Fill arrays (Eq. 114 to 118)
454         for (int i = 0; i < 3; i++) {
455             if (FastMath.abs(arguments[i]).getReal() > 25.0) {
456                 s[i]  = zero;
457                 ds[i] = zero;
458             } else {
459                 final T expA   = clipExp(arguments[i]);
460                 final T opExpA = expA.add(1.0);
461                 s[i]  = amplitudes[i].multiply(expA.divide(opExpA.multiply(opExpA)));
462                 ds[i] = ct[i].multiply(expA.negate().add(1.0).divide(expA.add(1.0)));
463             }
464         }
465 
466         // Electron density
467         final T aNo = s[0].add(s[1]).add(s[2]);
468         if (applyChapmanParameters(h.getReal())) {
469             // Chapman parameters (Eq. 119 and 120)
470             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);
471             final T z  = h.subtract(100.0).multiply(0.1);
472             // Electron density (Eq. 121)
473             return aNo.multiply(clipExp(bc.multiply(z).add(clipExp(z.negate())).negate().add(1.0))).multiply(DENSITY_FACTOR);
474         } else {
475             return aNo.multiply(DENSITY_FACTOR);
476         }
477 
478     }
479 
480     /**
481      * Computes the electron density of the topside.
482      * @param h height in km
483      * @param parameters NeQuick model parameters
484      * @return the electron density N in m⁻³
485      */
486     private double topElectronDensity(final double h, final NeQuickParameters parameters) {
487 
488         // Constant parameters (Eq. 122 and 123)
489         final double g = 0.125;
490         final double r = 100.0;
491 
492         // Arguments deltaH and z (Eq. 124 and 125)
493         final double deltaH = h - parameters.getHmF2();
494         final double h0     = computeH0(parameters);
495         final double z      = deltaH / (h0 * (1.0 + (r * g * deltaH) / (r * h0 + g * deltaH)));
496 
497         // Exponential (Eq. 126)
498         final double ee = clipExp(z);
499 
500         // Electron density (Eq. 127)
501         if (ee > 1.0e11) {
502             return (4.0 * parameters.getNmF2() / ee) * DENSITY_FACTOR;
503         } else {
504             final double opExpZ = 1.0 + ee;
505             return ((4.0 * parameters.getNmF2() * ee) / (opExpZ * opExpZ)) * DENSITY_FACTOR;
506         }
507     }
508 
509     /**
510      * Computes the electron density of the topside.
511      * @param <T> type of the elements
512      * @param h height in km
513      * @param parameters NeQuick model parameters
514      * @return the electron density N in m⁻³
515      */
516     private <T extends CalculusFieldElement<T>> T topElectronDensity(final T h,
517                                                                      final FieldNeQuickParameters<T> parameters) {
518 
519         // Constant parameters (Eq. 122 and 123)
520         final double g = 0.125;
521         final double r = 100.0;
522 
523         // Arguments deltaH and z (Eq. 124 and 125)
524         final T deltaH = h.subtract(parameters.getHmF2());
525         final T h0     = computeH0(parameters);
526         final T z      = deltaH.divide(h0.multiply(deltaH.multiply(r).multiply(g).divide(h0.multiply(r).add(deltaH.multiply(g))).add(1.0)));
527 
528         // Exponential (Eq. 126)
529         final T ee = clipExp(z);
530 
531         // Electron density (Eq. 127)
532         if (ee.getReal() > 1.0e11) {
533             return parameters.getNmF2().multiply(4.0).divide(ee).multiply(DENSITY_FACTOR);
534         } else {
535             final T opExpZ = ee.add(1.0);
536             return parameters.getNmF2().multiply(4.0).multiply(ee).divide(opExpZ.multiply(opExpZ)).multiply(DENSITY_FACTOR);
537         }
538     }
539 
540     /**
541      * Lazy loading of CCIR data.
542      * @param date current date components
543      */
544     private void loadsIfNeeded(final DateComponents date) {
545 
546         // Month index
547         final int monthIndex = date.getMonth() - 1;
548 
549         // Check if CCIR has already been loaded for this month
550         if (flattenF2[monthIndex] == null) {
551 
552             // Read file
553             final CCIRLoader loader = new CCIRLoader();
554             loader.loadCCIRCoefficients(date);
555 
556             // Update arrays
557             this.flattenF2[monthIndex]  = flatten(loader.getF2());
558             this.flattenFm3[monthIndex] = flatten(loader.getFm3());
559         }
560     }
561 
562     /** Flatten a 3-dimensions array.
563      * <p>
564      * This method convert 3-dimensions arrays into 1-dimension arrays
565      * optimized to avoid cache misses when looping over all elements.
566      * </p>
567      * @param original original array a[i][j][k]
568      * @return flatten array, for embedded loops on j (outer), k (intermediate), i (inner)
569      */
570     private double[] flatten(final double[][][] original) {
571         final double[] flatten = new double[original.length * original[0].length * original[0][0].length];
572         int index = 0;
573         for (int j = 0; j < original[0].length; j++) {
574             for (int k = 0; k < original[0][0].length; k++) {
575                 for (final double[][] doubles : original) {
576                     flatten[index++] = doubles[j][k];
577                 }
578             }
579         }
580         return flatten;
581     }
582 
583     /**
584      * A clipped exponential function.
585      * <p>
586      * This function, describe in section F.2.12.2 of the reference document, is
587      * recommended for the computation of exponential values.
588      * </p>
589      * @param power power for exponential function
590      * @return clipped exponential value
591      */
592     protected double clipExp(final double power) {
593         if (power > 80.0) {
594             return 5.5406E34;
595         } else if (power < -80) {
596             return 1.8049E-35;
597         } else {
598             return FastMath.exp(power);
599         }
600     }
601 
602     /**
603      * A clipped exponential function.
604      * <p>
605      * This function, describe in section F.2.12.2 of the reference document, is
606      * recommended for the computation of exponential values.
607      * </p>
608      * @param <T> type of the elements
609      * @param power power for exponential function
610      * @return clipped exponential value
611      */
612     protected <T extends CalculusFieldElement<T>> T clipExp(final T power) {
613         if (power.getReal() > 80.0) {
614             return power.newInstance(5.5406E34);
615         } else if (power.getReal() < -80) {
616             return power.newInstance(1.8049E-35);
617         } else {
618             return FastMath.exp(power);
619         }
620     }
621 
622     /**
623      * This method allows the computation of the Slant Total Electron Content (STEC).
624      *
625      * @param dateTime current date
626      * @param ray      ray-perigee parameters
627      * @return the STEC in TECUnits
628      */
629     abstract double stec(DateTimeComponents dateTime, Ray ray);
630 
631     /**
632      * This method allows the computation of the Slant Total Electron Content (STEC).
633      *
634      * @param <T>      type of the field elements
635      * @param dateTime current date
636      * @param ray      ray-perigee parameters
637      * @return the STEC in TECUnits
638      */
639     abstract <T extends CalculusFieldElement<T>> T stec(DateTimeComponents dateTime, FieldRay<T> ray);
640 
641     /**
642      * Check if Chapman parameters should be applied.
643      *
644      * @param hInKm height in kilometers
645      * @return true if Chapman parameters should be applied
646      * @since 13.0
647      */
648     abstract boolean applyChapmanParameters(double hInKm);
649 
650     /**
651      * Compute exponential arguments.
652      * @param h height in km
653      * @param parameters NeQuick model parameters
654      * @return exponential arguments
655      * @since 13.0
656      */
657     abstract double[] computeExponentialArguments(double h, NeQuickParameters parameters);
658 
659     /**
660      * Compute exponential arguments.
661      * @param <T>   type of the field elements
662      * @param h height in km
663      * @param parameters NeQuick model parameters
664      * @return exponential arguments
665      * @since 13.0
666      */
667     abstract <T extends CalculusFieldElement<T>> T[] computeExponentialArguments(T h,
668                                                                                  FieldNeQuickParameters<T> parameters);
669 
670     /**
671      * Compute topside thickness parameter.
672      * @param parameters NeQuick model parameters
673      * @return topside thickness parameter
674      * @since 13.0
675      */
676     abstract double computeH0(NeQuickParameters parameters);
677 
678     /**
679      * Compute topside thickness parameter.
680      * @param <T>   type of the field elements
681      * @param parameters NeQuick model parameters
682      * @return topside thickness parameter
683      * @since 13.0
684      */
685     abstract <T extends CalculusFieldElement<T>> T computeH0(FieldNeQuickParameters<T> parameters);
686 
687 }