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.atmosphere;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.nio.charset.StandardCharsets;
24  import java.util.Arrays;
25  
26  import org.hipparchus.CalculusFieldElement;
27  import org.hipparchus.exception.DummyLocalizable;
28  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
29  import org.hipparchus.geometry.euclidean.threed.Vector3D;
30  import org.hipparchus.util.FastMath;
31  import org.hipparchus.util.FieldSinCos;
32  import org.hipparchus.util.MathArrays;
33  import org.hipparchus.util.SinCos;
34  import org.orekit.annotation.DefaultDataContext;
35  import org.orekit.bodies.BodyShape;
36  import org.orekit.bodies.FieldGeodeticPoint;
37  import org.orekit.bodies.GeodeticPoint;
38  import org.orekit.data.DataContext;
39  import org.orekit.errors.OrekitException;
40  import org.orekit.errors.OrekitMessages;
41  import org.orekit.frames.Frame;
42  import org.orekit.time.AbsoluteDate;
43  import org.orekit.time.FieldAbsoluteDate;
44  import org.orekit.time.TimeScale;
45  import org.orekit.utils.PVCoordinatesProvider;
46  
47  /** This atmosphere model is the realization of the DTM-2000 model.
48   * <p>
49   * It is described in the paper: <br>
50   *
51   * <b>The DTM-2000 empirical thermosphere model with new data assimilation
52   *  and constraints at lower boundary: accuracy and properties</b><br>
53   *
54   * <i>S. Bruinsma, G. Thuillier and F. Barlier</i> <br>
55   *
56   * Journal of Atmospheric and Solar-Terrestrial Physics 65 (2003) 1053–1070<br>
57   *
58   *</p>
59   *<p>
60   * This model provides dense output for altitudes beyond 120 km.
61   *</p>
62   *
63   * <p>
64   * The model needs geographical and time information to compute general values,
65   * but also needs space weather data : mean and instantaneous solar flux and
66   * geomagnetic indices.
67   * </p>
68   * <p>
69   * Mean solar flux is (for the moment) represented by the F10.7 indices. Instantaneous
70   * flux can be set to the mean value if the data is not available. Geomagnetic
71   * activity is represented by the Kp indice, which goes from 1 (very low activity) to
72   * 9 (high activity).
73   *
74   * <p>
75   * All these data can be found on the <a href="https://www.noaa.gov/">
76   * NOAA (National Oceanic and Atmospheric Administration) website.</a>
77   * </p>
78   *
79   *
80   * @author R. Biancale, S. Bruinsma: original fortran routine
81   * @author Fabien Maussion (java translation)
82   */
83  public class DTM2000 implements Atmosphere {
84  
85      /** Identifier for hydrogen. */
86      public static final int HYDROGEN = 1;
87  
88      /** Identifier for helium. */
89      public static final int HELIUM = 2;
90  
91      /** Identifier for atomic oxygen. */
92      public static final int ATOMIC_OXYGEN = 3;
93  
94      /** Identifier for molecular nitrogen. */
95      public static final int MOLECULAR_NITROGEN = 4;
96  
97      /** Identifier for molecular oxygen. */
98      public static final int MOLECULAR_OXYGEN = 5;
99  
100     /** Identifier for atomic nitrogen. */
101     public static final int ATOMIC_NITROGEN = 6;
102 
103     /** Serializable UID. */
104     private static final long serialVersionUID = 20170705L;
105 
106     // Constants :
107 
108     /** Number of parameters. */
109     private static final int NLATM = 96;
110 
111     /** Thermal diffusion coefficient. */
112     private static final double[] ALEFA = {
113         0, -0.40, -0.38, 0, 0, 0, 0
114     };
115 
116     /** Atomic mass  H, He, O, N2, O2, N. */
117     private static final double[] MA = {
118         0, 1, 4, 16, 28, 32, 14
119     };
120 
121     /** Atomic mass  H, He, O, N2, O2, N. */
122     private static final double[] VMA = {
123         0, 1.6606e-24, 6.6423e-24, 26.569e-24, 46.4958e-24, 53.1381e-24, 23.2479e-24
124     };
125 
126     /** Polar Earth radius. */
127     private static final double RE = 6356.77;
128 
129     /** Reference altitude. */
130     private static final double ZLB0 = 120.0;
131 
132     /** Cosine of the latitude of the magnetic pole (79N, 71W). */
133     private static final double CPMG = .19081;
134 
135     /** Sine of the latitude of the magnetic pole (79N, 71W). */
136     private static final double SPMG = .98163;
137 
138     /** Longitude (in radians) of the magnetic pole (79N, 71W). */
139     private static final double XLMG = -1.2392;
140 
141     /** Gravity acceleration at 120 km altitude. */
142     private static final double GSURF = 980.665;
143 
144     /** Universal gas constant. */
145     private static final double RGAS = 831.4;
146 
147     /** 2 * π / 365. */
148     private static final double ROT = 0.017214206;
149 
150     /** 2 * rot. */
151     private static final double ROT2 = 0.034428412;
152 
153     /** Resources text file. */
154     private static final String DTM2000 = "/assets/org/orekit/dtm_2000.txt";
155 
156     // CHECKSTYLE: stop JavadocVariable check
157 
158     /** Elements coefficients. */
159     private static double[] tt   = null;
160     private static double[] h    = null;
161     private static double[] he   = null;
162     private static double[] o    = null;
163     private static double[] az2  = null;
164     private static double[] o2   = null;
165     private static double[] az   = null;
166     private static double[] t0   = null;
167     private static double[] tp   = null;
168 
169     /** Sun position. */
170     private PVCoordinatesProvider sun;
171 
172     /** External data container. */
173     private DTM2000InputParameters inputParams;
174 
175     /** Earth body shape. */
176     private BodyShape earth;
177 
178     /** UTC time scale. */
179     private final TimeScale utc;
180 
181     /** Simple constructor for independent computation.
182      *
183      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
184      *
185      * @param parameters the solar and magnetic activity data
186      * @param sun the sun position
187      * @param earth the earth body shape
188      * @see #DTM2000(DTM2000InputParameters, PVCoordinatesProvider, BodyShape, TimeScale)
189      */
190     @DefaultDataContext
191     public DTM2000(final DTM2000InputParameters parameters,
192                    final PVCoordinatesProvider sun, final BodyShape earth) {
193         this(parameters, sun, earth, DataContext.getDefault().getTimeScales().getUTC());
194     }
195 
196     /** Simple constructor for independent computation.
197      * @param parameters the solar and magnetic activity data
198      * @param sun the sun position
199      * @param earth the earth body shape
200      * @param utc UTC time scale.
201      * @since 10.1
202      */
203     public DTM2000(final DTM2000InputParameters parameters,
204                    final PVCoordinatesProvider sun,
205                    final BodyShape earth,
206                    final TimeScale utc) {
207 
208         synchronized (DTM2000.class) {
209             // lazy reading of model coefficients
210             if (tt == null) {
211                 readcoefficients();
212             }
213         }
214 
215         this.earth = earth;
216         this.sun = sun;
217         this.inputParams = parameters;
218 
219         this.utc = utc;
220     }
221 
222     /** {@inheritDoc} */
223     public Frame getFrame() {
224         return earth.getBodyFrame();
225     }
226 
227     /** Get the local density with initial entries.
228      * @param day day of year
229      * @param alti altitude in meters
230      * @param lon local longitude (rad)
231      * @param lat local latitude (rad)
232      * @param hl local solar time in rad (O hr = 0 rad)
233      * @param f instantaneous solar flux (F10.7)
234      * @param fbar mean solar flux (F10.7)
235      * @param akp3 3 hrs geomagnetic activity index (1-9)
236      * @param akp24 Mean of last 24 hrs geomagnetic activity index (1-9)
237      * @return the local density (kg/m³)
238      */
239     public double getDensity(final int day,
240                              final double alti, final double lon, final double lat,
241                              final double hl, final double f, final double fbar,
242                              final double akp3, final double akp24) {
243         final double threshold = 120000;
244         if (alti < threshold) {
245             throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD,
246                                       alti, threshold);
247         }
248         final Computation result = new Computation(day, alti / 1000, lon, lat, hl,
249                                                    new double[] {
250                                                        0, f, 0
251                                                    }, new double[] {
252                                                        0, fbar, 0
253                                                    }, new double[] {
254                                                        0, akp3, 0, akp24, 0
255                                                    });
256         return result.ro * 1000;
257     }
258 
259     /** Get the local density with initial entries.
260      * @param day day of year
261      * @param alti altitude in meters
262      * @param lon local longitude (rad)
263      * @param lat local latitude (rad)
264      * @param hl local solar time in rad (O hr = 0 rad)
265      * @param f instantaneous solar flux (F10.7)
266      * @param fbar mean solar flux (F10.7)
267      * @param akp3 3 hrs geomagnetic activity index (1-9)
268      * @param akp24 Mean of last 24 hrs geomagnetic activity index (1-9)
269      * @param <T> type of the field elements
270      * @return the local density (kg/m³)
271           * @since 9.0
272      */
273     public <T extends CalculusFieldElement<T>> T getDensity(final int day,
274                                                         final T alti, final T lon, final T lat,
275                                                         final T hl, final double f, final double fbar,
276                                                         final double akp3, final double akp24) {
277         final double threshold = 120000;
278         if (alti.getReal() < threshold) {
279             throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD,
280                                       alti, threshold);
281         }
282         final FieldComputation<T> result = new FieldComputation<>(day, alti.divide(1000), lon, lat, hl,
283                                                                   new double[] {
284                                                                       0, f, 0
285                                                                   }, new double[] {
286                                                                       0, fbar, 0
287                                                                   }, new double[] {
288                                                                       0, akp3, 0, akp24, 0
289                                                                   });
290         return result.ro.multiply(1000);
291     }
292 
293     /** Store the DTM model elements coefficients in internal arrays.
294      */
295     private static void readcoefficients() {
296 
297         final int size = NLATM + 1;
298         tt   = new double[size];
299         h    = new double[size];
300         he   = new double[size];
301         o    = new double[size];
302         az2  = new double[size];
303         o2   = new double[size];
304         az   = new double[size];
305         t0   = new double[size];
306         tp   = new double[size];
307 
308         try (InputStream in = checkNull(DTM2000.class.getResourceAsStream(DTM2000));
309              BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
310 
311             r.readLine();
312             r.readLine();
313             for (String line = r.readLine(); line != null; line = r.readLine()) {
314                 final int num = Integer.parseInt(line.substring(0, 4).replace(' ', '0'));
315                 line = line.substring(4);
316                 tt[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
317                 line = line.substring(13 + 9);
318                 h[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
319                 line = line.substring(13 + 9);
320                 he[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
321                 line = line.substring(13 + 9);
322                 o[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
323                 line = line.substring(13 + 9);
324                 az2[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
325                 line = line.substring(13 + 9);
326                 o2[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
327                 line = line.substring(13 + 9);
328                 az[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
329                 line = line.substring(13 + 9);
330                 t0[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
331                 line = line.substring(13 + 9);
332                 tp[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
333             }
334         } catch (IOException ioe) {
335             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
336         }
337     }
338 
339     /** Get the local density.
340      * @param date current date
341      * @param position current position in frame
342      * @param frame the frame in which is defined the position
343      * @return local density (kg/m³)
344      */
345     public double getDensity(final AbsoluteDate date, final Vector3D position,
346                              final Frame frame) {
347 
348         // check if data are available :
349         if (date.compareTo(inputParams.getMaxDate()) > 0 ||
350             date.compareTo(inputParams.getMinDate()) < 0) {
351             throw new OrekitException(OrekitMessages.NO_SOLAR_ACTIVITY_AT_DATE,
352                                       date, inputParams.getMinDate(), inputParams.getMaxDate());
353         }
354 
355         // compute day number in current year
356         final int day = date.getComponents(utc).getDate().getDayOfYear();
357         //position in ECEF so we only have to do the transform once
358         final Frame ecef = earth.getBodyFrame();
359         final Vector3D pEcef = frame.getTransformTo(ecef, date)
360                 .transformPosition(position);
361         // compute geodetic position
362         final GeodeticPoint inBody = earth.transform(pEcef, ecef, date);
363         final double alti = inBody.getAltitude();
364         final double lon = inBody.getLongitude();
365         final double lat = inBody.getLatitude();
366 
367         // compute local solar time
368         final Vector3D sunPos = sun.getPVCoordinates(date, ecef).getPosition();
369         final double hl = FastMath.PI + FastMath.atan2(
370                 sunPos.getX() * pEcef.getY() - sunPos.getY() * pEcef.getX(),
371                 sunPos.getX() * pEcef.getX() + sunPos.getY() * pEcef.getY());
372 
373         // get current solar activity data and compute
374         return getDensity(day, alti, lon, lat, hl, inputParams.getInstantFlux(date),
375                           inputParams.getMeanFlux(date), inputParams.getThreeHourlyKP(date),
376                           inputParams.get24HoursKp(date));
377 
378     }
379 
380     /** {@inheritDoc} */
381     @Override
382     public <T extends CalculusFieldElement<T>> T
383         getDensity(final FieldAbsoluteDate<T> date, final FieldVector3D<T> position,
384                    final Frame frame) {
385         // check if data are available :
386         final AbsoluteDate dateD = date.toAbsoluteDate();
387         if (dateD.compareTo(inputParams.getMaxDate()) > 0 ||
388             dateD.compareTo(inputParams.getMinDate()) < 0) {
389             throw new OrekitException(OrekitMessages.NO_SOLAR_ACTIVITY_AT_DATE,
390                                       dateD, inputParams.getMinDate(), inputParams.getMaxDate());
391         }
392 
393         // compute day number in current year
394         final int day = date.getComponents(utc).getDate().getDayOfYear();
395         // position in ECEF so we only have to do the transform once
396         final Frame ecef = earth.getBodyFrame();
397         final FieldVector3D<T> pEcef = frame.getTransformTo(ecef, date).transformPosition(position);
398         // compute geodetic position
399         final FieldGeodeticPoint<T> inBody = earth.transform(pEcef, ecef, date);
400         final T alti = inBody.getAltitude();
401         final T lon = inBody.getLongitude();
402         final T lat = inBody.getLatitude();
403 
404         // compute local solar time
405         final Vector3D sunPos = sun.getPVCoordinates(dateD, ecef).getPosition();
406         final T y  = pEcef.getY().multiply(sunPos.getX()).subtract(pEcef.getX().multiply(sunPos.getY()));
407         final T x  = pEcef.getX().multiply(sunPos.getX()).add(pEcef.getY().multiply(sunPos.getY()));
408         final T hl = y.atan2(x).add(y.getPi());
409 
410         // get current solar activity data and compute
411         return getDensity(day, alti, lon, lat, hl, inputParams.getInstantFlux(dateD),
412                           inputParams.getMeanFlux(dateD), inputParams.getThreeHourlyKP(dateD),
413                           inputParams.get24HoursKp(dateD));
414     }
415 
416     /**
417      * Helper method to check for null resources. Throws an exception if {@code
418      * stream} is null.
419      *
420      * @param stream loaded from the class resources.
421      * @return {@code stream}.
422      */
423     private static InputStream checkNull(final InputStream stream) {
424         if (stream == null) {
425             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_RESOURCE, DTM2000);
426         }
427         return stream;
428     }
429 
430     /** Local holder for intermediate results ensuring the model is reentrant. */
431     private static class Computation {
432 
433         /** Number of days in current year. */
434         private final int day;
435 
436         /** Instant solar flux. f[1] = instantaneous flux; f[2] = 0. (not used). */
437         private final double[] f;
438 
439         /** Mean solar flux. fbar[1] = mean flux; fbar[2] = 0. (not used). */
440         private final double[] fbar;
441 
442         /** Kp coefficients.
443          * <ul>
444          *   <li>akp[1] = 3-hourly kp</li>
445          *   <li>akp[2] = 0 (not used)</li>
446          *   <li>akp[3] = mean kp of last 24 hours</li>
447          *   <li>akp[4] = 0 (not used)</li>
448          * </ul>
449          */
450         private final double[] akp;
451 
452         /** Cosine of the longitude. */
453         private final double clfl;
454 
455         /** Sine of the longitude. */
456         private final double slfl;
457 
458         /** Total density (g/cm3). */
459         private final double ro;
460 
461         // CHECKSTYLE: stop JavadocVariable check
462 
463         /** Legendre coefficients. */
464         private final double p10;
465         private final double p20;
466         private final double p30;
467         private final double p40;
468         private final double p50;
469         private final double p60;
470         private final double p11;
471         private final double p21;
472         private final double p31;
473         private final double p41;
474         private final double p51;
475         private final double p22;
476         private final double p32;
477         private final double p42;
478         private final double p52;
479         private final double p62;
480         private final double p33;
481         private final double p10mg;
482         private final double p20mg;
483         private final double p40mg;
484 
485         /** Local time intermediate values. */
486         private final double hl0;
487         private final double ch;
488         private final double sh;
489         private final double c2h;
490         private final double s2h;
491         private final double c3h;
492         private final double s3h;
493 
494         /** Simple constructor.
495          * @param day day of year
496          * @param altiKM altitude <em>in kilometers</em>
497          * @param lon local longitude (rad)
498          * @param lat local latitude (rad)
499          * @param hl local solar time in rad (O hr = 0 rad)
500          * @param f instantaneous solar flux (F10.7)
501          * @param fbar mean solar flux (F10.7)
502          * @param akp geomagnetic activity index
503          */
504         Computation(final int day,
505                     final double altiKM, final double lon, final double lat,
506                     final double hl, final double[] f, final double[] fbar,
507                     final double[] akp) {
508 
509             this.day  = day;
510             this.f    = f;
511             this.fbar = fbar;
512             this.akp  = akp;
513 
514             // Sine and cosine of local latitude and longitude
515             final SinCos scLat = FastMath.sinCos(lat);
516             final SinCos scLon = FastMath.sinCos(lon);
517 
518             // compute Legendre polynomials wrt geographic pole
519             final double c = scLat.sin();
520             final double c2 = c * c;
521             final double c4 = c2 * c2;
522             final double s = scLat.cos();
523             final double s2 = s * s;
524             p10 = c;
525             p20 = 1.5 * c2 - 0.5;
526             p30 = c * (2.5 * c2 - 1.5);
527             p40 = 4.375 * c4 - 3.75 * c2 + 0.375;
528             p50 = c * (7.875 * c4 - 8.75 * c2 + 1.875);
529             p60 = (5.5 * c * p50 - 2.5 * p40) / 3.0;
530             p11 = s;
531             p21 = 3.0 * c * s;
532             p31 = s * (7.5 * c2 - 1.5);
533             p41 = c * s * (17.5 * c2 - 7.5);
534             p51 = s * (39.375 * c4 - 26.25 * c2 + 1.875);
535             p22 = 3.0 * s2;
536             p32 = 15.0 * c * s2;
537             p42 = s2 * (52.5 * c2 - 7.5);
538             p52 = 3.0 * c * p42 - 2.0 * p32;
539             p62 = 2.75 * c * p52 - 1.75 * p42;
540             p33 = 15.0 * s * s2;
541 
542             // compute Legendre polynomials wrt magnetic pole (79N, 71W)
543             final double clmlmg = FastMath.cos(lon - XLMG);
544             final double cmg  = s * CPMG * clmlmg + c * SPMG;
545             final double cmg2 = cmg * cmg;
546             final double cmg4 = cmg2 * cmg2;
547             p10mg = cmg;
548             p20mg = 1.5 * cmg2 - 0.5;
549             p40mg = 4.375 * cmg4 - 3.75 * cmg2 + 0.375;
550 
551             clfl = scLon.cos();
552             slfl = scLon.sin();
553 
554             // local time
555             hl0 = hl;
556             final SinCos scHlo = FastMath.sinCos(hl0);
557             ch  = scHlo.cos();
558             sh  = scHlo.sin();
559             c2h = ch * ch - sh * sh;
560             s2h = 2.0 * ch * sh;
561             c3h = c2h * ch - s2h * sh;
562             s3h = s2h * ch + c2h * sh;
563 
564             final double zlb = ZLB0; // + dzlb ??
565 
566             final double[] dtt  = new double[tt.length];
567             final double[] dh   = new double[tt.length];
568             final double[] dhe  = new double[tt.length];
569             final double[] dox  = new double[tt.length];
570             final double[] daz2 = new double[tt.length];
571             final double[] do2  = new double[tt.length];
572             final double[] daz  = new double[tt.length];
573             final double[] dt0  = new double[tt.length];
574             final double[] dtp  = new double[tt.length];
575 
576             Arrays.fill(dtt,  Double.NaN);
577             Arrays.fill(dh,   Double.NaN);
578             Arrays.fill(dhe,  Double.NaN);
579             Arrays.fill(dox,  Double.NaN);
580             Arrays.fill(daz2, Double.NaN);
581             Arrays.fill(do2,  Double.NaN);
582             Arrays.fill(daz,  Double.NaN);
583             Arrays.fill(dt0,  Double.NaN);
584             Arrays.fill(dtp,  Double.NaN);
585 
586             //  compute function g(l) / tinf, t120, tp120
587             int kleq = 1;
588             final double gdelt = gFunction(tt, dtt, 1, kleq);
589             dtt[1] = 1.0 + gdelt;
590             final double tinf   = tt[1] * dtt[1];
591 
592             kleq = 0; // equinox
593 
594             if (day < 59 || day > 284) {
595                 kleq = -1; // north winter
596             }
597             if (day > 99 && day < 244) {
598                 kleq = 1; // north summer
599             }
600 
601             final double gdelt0 =  gFunction(t0, dt0, 0, kleq);
602             dt0[1] = (t0[1] + gdelt0) / t0[1];
603             final double t120 = t0[1] + gdelt0;
604             final double gdeltp = gFunction(tp, dtp, 0, kleq);
605             dtp[1] = (tp[1] + gdeltp) / tp[1];
606             final double tp120 = tp[1] + gdeltp;
607 
608             // compute n(z) concentrations: H, He, O, N2, O2, N
609             final double sigma   = tp120 / (tinf - t120);
610             final double dzeta   = (RE + zlb) / (RE + altiKM);
611             final double zeta    = (altiKM - zlb) * dzeta;
612             final double sigzeta = sigma * zeta;
613             final double expsz   = FastMath.exp(-sigzeta);
614             final double tz = tinf - (tinf - t120) * expsz;
615 
616             final double[] dbase = new double[7];
617 
618             kleq = 1;
619 
620             final double gdelh = gFunction(h, dh, 0, kleq);
621             dh[1] = FastMath.exp(gdelh);
622             dbase[1] = h[1] * dh[1];
623 
624             final double gdelhe = gFunction(he, dhe, 0, kleq);
625             dhe[1] = FastMath.exp(gdelhe);
626             dbase[2] = he[1] * dhe[1];
627 
628             final double gdelo = gFunction(o, dox, 1, kleq);
629             dox[1] = FastMath.exp(gdelo);
630             dbase[3] = o[1] * dox[1];
631 
632             final double gdelaz2 = gFunction(az2, daz2, 1, kleq);
633             daz2[1] = FastMath.exp(gdelaz2);
634             dbase[4] = az2[1] * daz2[1];
635 
636             final double gdelo2 = gFunction(o2, do2, 1, kleq);
637             do2[1] = FastMath.exp(gdelo2);
638             dbase[5] = o2[1] * do2[1];
639 
640             final double gdelaz = gFunction(az, daz, 1, kleq);
641             daz[1] = FastMath.exp(gdelaz);
642             dbase[6] = az[1] * daz[1];
643 
644             final double zlbre  = 1.0 + zlb / RE;
645             final double glb    = (GSURF / (zlbre * zlbre)) / (sigma * RGAS * tinf);
646             final double t120tz = t120 / tz;
647 
648             // Partial densities in (g/cm3).
649             // d(1) = hydrogen
650             // d(2) = helium
651             // d(3) = atomic oxygen
652             // d(4) = molecular nitrogen
653             // d(5) = molecular oxygen
654             // d(6) = atomic nitrogen
655             double tmpro = 0;
656             for (int i = 1; i <= 6; i++) {
657                 final double gamma = MA[i] * glb;
658                 final double upapg = 1.0 + ALEFA[i] + gamma;
659                 final double fzI = FastMath.pow(t120tz, upapg) * FastMath.exp(-sigzeta * gamma);
660                 // concentrations of H, He, O, N2, O2, N (particles/cm³)
661                 final double ccI = dbase[i] * fzI;
662                 // contribution of densities of H, He, O, N2, O2, N (g/cm³)
663                 tmpro += ccI * VMA[i];
664             }
665             this.ro = tmpro;
666 
667         }
668 
669         /** Computation of function G.
670          * @param a vector of coefficients for computation
671          * @param da vector of partial derivatives
672          * @param ff0 coefficient flag (1 for Ox, Az, He, T°; 0 for H and tp120)
673          * @param kle_eq season indicator flag (summer, winter, equinox)
674          * @return value of G
675          */
676         private double gFunction(final double[] a, final double[] da,
677                                  final int ff0, final int kle_eq) {
678 
679             final double[] fmfb   = new double[3];
680             final double[] fbm150 = new double[3];
681 
682             // latitude terms
683             da[2]  = p20;
684             da[3]  = p40;
685             da[74] = p10;
686             double a74 = a[74];
687             double a77 = a[77];
688             double a78 = a[78];
689             if (kle_eq == -1) {
690                 // winter
691                 a74 = -a74;
692                 a77 = -a77;
693                 a78 = -a78;
694             }
695             if (kle_eq == 0 ) {
696                 // equinox
697                 a74 = semestrialCorrection(a74);
698                 a77 = semestrialCorrection(a77);
699                 a78 = semestrialCorrection(a78);
700             }
701             da[77] = p30;
702             da[78] = p50;
703             da[79] = p60;
704 
705             // flux terms
706             fmfb[1]   = f[1] - fbar[1];
707             fmfb[2]   = f[2] - fbar[2];
708             fbm150[1] = fbar[1] - 150.0;
709             fbm150[2] = fbar[2];
710             da[4]     = fmfb[1];
711             da[6]     = fbm150[1];
712             da[4]     = da[4] + a[70] * fmfb[2];
713             da[6]     = da[6] + a[71] * fbm150[2];
714             da[70]    = fmfb[2] * (a[4] + 2.0 * a[5] * da[4] + a[82] * p10 +
715                                    a[83] * p20 + a[84] * p30);
716             da[71]    = fbm150[2] * (a[6] + 2.0 * a[69] * da[6] + a[85] * p10 +
717                                      a[86] * p20 + a[87] * p30);
718             da[5]     = da[4] * da[4];
719             da[69]    = da[6] * da[6];
720             da[82]    = da[4] * p10;
721             da[83]    = da[4] * p20;
722             da[84]    = da[4] * p30;
723             da[85]    = da[6] * p20;
724             da[86]    = da[6] * p30;
725             da[87]    = da[6] * p40;
726 
727             // Kp terms
728             final int ikp  = 62;
729             final int ikpm = 67;
730             final double c2fi = 1.0 - p10mg * p10mg;
731             final double dkp  = akp[1] + (a[ikp] + c2fi * a[ikp + 1]) * akp[2];
732             double dakp = a[7] + a[8] * p20mg + a[68] * p40mg +
733                           2.0 * dkp * (a[60] + a[61] * p20mg +
734                                        a[75] * 2.0 * dkp * dkp);
735             da[ikp] = dakp * akp[2];
736             da[ikp + 1] = da[ikp] * c2fi;
737             final double dkpm  = akp[3] + a[ikpm] * akp[4];
738             final double dakpm = a[64] + a[65] * p20mg + a[72] * p40mg +
739                                  2.0 * dkpm * (a[66] + a[73] * p20mg +
740                                                a[76] * 2.0 * dkpm * dkpm);
741             da[ikpm] = dakpm * akp[4];
742             da[7]    = dkp;
743             da[8]    = p20mg * dkp;
744             da[68]   = p40mg * dkp;
745             da[60]   = dkp * dkp;
746             da[61]   = p20mg * da[60];
747             da[75]   = da[60] * da[60];
748             da[64]   = dkpm;
749             da[65]   = p20mg * dkpm;
750             da[72]   = p40mg * dkpm;
751             da[66]   = dkpm * dkpm;
752             da[73]   = p20mg * da[66];
753             da[76]   = da[66] * da[66];
754 
755             // non-periodic g(l) function
756             double f0 = a[4]  * da[4]  + a[5]  * da[5]  + a[6]  * da[6]  +
757                         a[69] * da[69] + a[82] * da[82] + a[83] * da[83] +
758                         a[84] * da[84] + a[85] * da[85] + a[86] * da[86] +
759                         a[87] * da[87];
760             final double f1f = 1.0 + f0 * ff0;
761 
762             f0 = f0 + a[2] * da[2] + a[3] * da[3] + a74 * da[74] +
763                  a77 * da[77] + a[7] * da[7] + a[8] * da[8] +
764                  a[60] * da[60] + a[61] * da[61] + a[68] * da[68] +
765                  a[64] * da[64] + a[65] * da[65] + a[66] * da[66] +
766                  a[72] * da[72] + a[73] * da[73] + a[75] * da[75] +
767                  a[76] * da[76] + a78   * da[78] + a[79] * da[79];
768 //          termes annuels symetriques en latitude
769             da[9]  = FastMath.cos(ROT * (day - a[11]));
770             da[10] = p20 * da[9];
771 //          termes semi-annuels symetriques en latitude
772             da[12] = FastMath.cos(ROT2 * (day - a[14]));
773             da[13] = p20 * da[12];
774 //          termes annuels non symetriques en latitude
775             final double coste = FastMath.cos(ROT * (day - a[18]));
776             da[15] = p10 * coste;
777             da[16] = p30 * coste;
778             da[17] = p50 * coste;
779 //          terme  semi-annuel  non symetrique  en latitude
780             final double cos2te = FastMath.cos(ROT2 * (day - a[20]));
781             da[19] = p10 * cos2te;
782             da[39] = p30 * cos2te;
783             da[59] = p50 * cos2te;
784 //          termes diurnes [et couples annuel]
785             da[21] = p11 * ch;
786             da[22] = p31 * ch;
787             da[23] = p51 * ch;
788             da[24] = da[21] * coste;
789             da[25] = p21 * ch * coste;
790             da[26] = p11 * sh;
791             da[27] = p31 * sh;
792             da[28] = p51 * sh;
793             da[29] = da[26] * coste;
794             da[30] = p21 * sh * coste;
795 //          termes semi-diurnes [et couples annuel]
796             da[31] = p22 * c2h;
797             da[37] = p42 * c2h;
798             da[32] = p32 * c2h * coste;
799             da[33] = p22 * s2h;
800             da[38] = p42 * s2h;
801             da[34] = p32 * s2h * coste;
802             da[88] = p32 * c2h;
803             da[89] = p32 * s2h;
804             da[90] = p52 * c2h;
805             da[91] = p52 * s2h;
806             double a88 = a[88];
807             double a89 = a[89];
808             double a90 = a[90];
809             double a91 = a[91];
810             if (kle_eq == -1) {            //hiver
811                 a88 = -a88;
812                 a89 = -a89;
813                 a90 = -a90;
814                 a91 = -a91;
815             }
816             if (kle_eq == 0) {             //equinox
817                 a88 = semestrialCorrection(a88);
818                 a89 = semestrialCorrection(a89);
819                 a90 = semestrialCorrection(a90);
820                 a91 = semestrialCorrection(a91);
821             }
822             da[92] = p62 * c2h;
823             da[93] = p62 * s2h;
824 //          termes ter-diurnes
825             da[35] = p33 * c3h;
826             da[36] = p33 * s3h;
827 //          fonction g[l] periodique
828             double fp = a[9]  * da[9]  + a[10] * da[10] + a[12] * da[12] + a[13] * da[13] +
829                         a[15] * da[15] + a[16] * da[16] + a[17] * da[17] + a[19] * da[19] +
830                         a[21] * da[21] + a[22] * da[22] + a[23] * da[23] + a[24] * da[24] +
831                         a[25] * da[25] + a[26] * da[26] + a[27] * da[27] + a[28] * da[28] +
832                         a[29] * da[29] + a[30] * da[30] + a[31] * da[31] + a[32] * da[32] +
833                         a[33] * da[33] + a[34] * da[34] + a[35] * da[35] + a[36] * da[36] +
834                         a[37] * da[37] + a[38] * da[38] + a[39] * da[39] + a[59] * da[59] +
835                         a88   * da[88] + a89   * da[89] + a90   * da[90] + a91   * da[91] +
836                         a[92] * da[92] + a[93] * da[93];
837 //          termes d'activite magnetique
838             da[40] = p10 * coste * dkp;
839             da[41] = p30 * coste * dkp;
840             da[42] = p50 * coste * dkp;
841             da[43] = p11 * ch * dkp;
842             da[44] = p31 * ch * dkp;
843             da[45] = p51 * ch * dkp;
844             da[46] = p11 * sh * dkp;
845             da[47] = p31 * sh * dkp;
846             da[48] = p51 * sh * dkp;
847 
848 //          fonction g[l] periodique supplementaire
849             fp += a[40] * da[40] + a[41] * da[41] + a[42] * da[42] + a[43] * da[43] +
850                   a[44] * da[44] + a[45] * da[45] + a[46] * da[46] + a[47] * da[47] +
851                   a[48] * da[48];
852 
853             dakp = (a[40] * p10 + a[41] * p30 + a[42] * p50) * coste +
854                    (a[43] * p11 + a[44] * p31 + a[45] * p51) * ch +
855                    (a[46] * p11 + a[47] * p31 + a[48] * p51) * sh;
856             da[ikp] += dakp * akp[2];
857             da[ikp + 1] = da[ikp] + dakp * c2fi * akp[2];
858 //          termes de longitude
859             da[49] = p11 * clfl;
860             da[50] = p21 * clfl;
861             da[51] = p31 * clfl;
862             da[52] = p41 * clfl;
863             da[53] = p51 * clfl;
864             da[54] = p11 * slfl;
865             da[55] = p21 * slfl;
866             da[56] = p31 * slfl;
867             da[57] = p41 * slfl;
868             da[58] = p51 * slfl;
869 
870 //          fonction g[l] periodique supplementaire
871             fp += a[49] * da[49] + a[50] * da[50] + a[51] * da[51] + a[52] * da[52] +
872                   a[53] * da[53] + a[54] * da[54] + a[55] * da[55] + a[56] * da[56] +
873                   a[57] * da[57] + a[58] * da[58];
874 
875 //          fonction g(l) totale (couplage avec le flux)
876             return f0 + fp * f1f;
877 
878         }
879 
880 
881         /** Apply a correction coefficient to the given parameter.
882          * @param param the parameter to correct
883          * @return the corrected parameter
884          */
885         private double semestrialCorrection(final double param) {
886             final int debeq_pr = 59;
887             final int debeq_au = 244;
888             final double result;
889             if (day >= 100) {
890                 final double xmult  = (day - debeq_au) / 40.0;
891                 result = param - 2.0 * param * xmult;
892             } else {
893                 final double xmult  = (day - debeq_pr) / 40.0;
894                 result = 2.0 * param * xmult - param;
895             }
896             return result;
897         }
898 
899 
900     }
901 
902     /** Local holder for intermediate results ensuring the model is reentrant.
903      * @param <T> type of the field elements
904      */
905     private static class FieldComputation<T extends CalculusFieldElement<T>> {
906 
907         /** Number of days in current year. */
908         private int day;
909 
910         /** Instant solar flux. f[1] = instantaneous flux; f[2] = 0. (not used). */
911         private double[] f = new double[3];
912 
913         /** Mean solar flux. fbar[1] = mean flux; fbar[2] = 0. (not used). */
914         private double[] fbar = new double[3];
915 
916         /** Kp coefficients.
917          * <ul>
918          *   <li>akp[1] = 3-hourly kp</li>
919          *   <li>akp[2] = 0 (not used)</li>
920          *   <li>akp[3] = mean kp of last 24 hours</li>
921          *   <li>akp[4] = 0 (not used)</li>
922          * </ul>
923          */
924         private double[] akp = new double[5];
925 
926         /** Cosine of the longitude. */
927         private final T clfl;
928 
929         /** Sine of the longitude. */
930         private final T slfl;
931 
932         /** Total density (g/cm3). */
933         private final T ro;
934 
935         // CHECKSTYLE: stop JavadocVariable check
936 
937         /** Legendre coefficients. */
938         private final T p10;
939         private final T p20;
940         private final T p30;
941         private final T p40;
942         private final T p50;
943         private final T p60;
944         private final T p11;
945         private final T p21;
946         private final T p31;
947         private final T p41;
948         private final T p51;
949         private final T p22;
950         private final T p32;
951         private final T p42;
952         private final T p52;
953         private final T p62;
954         private final T p33;
955         private final T p10mg;
956         private final T p20mg;
957         private final T p40mg;
958 
959         /** Local time intermediate values. */
960         private final T hl0;
961         private final T ch;
962         private final T sh;
963         private final T c2h;
964         private final T s2h;
965         private final T c3h;
966         private final T s3h;
967 
968         /** Simple constructor.
969          * @param day day of year
970          * @param altiKM altitude <em>in kilometers</em>
971          * @param lon local longitude (rad)
972          * @param lat local latitude (rad)
973          * @param hl local solar time in rad (O hr = 0 rad)
974          * @param f instantaneous solar flux (F10.7)
975          * @param far mean solar flux (F10.7)
976          * @param akp geomagnetic activity index
977          */
978         FieldComputation(final int day,
979                          final T altiKM, final T lon, final T lat,
980                          final T hl, final double[] f, final double[] far,
981                          final double[] akp) {
982 
983             this.day  = day;
984             this.f    = f;
985             this.fbar = far;
986             this.akp  = akp;
987 
988             // Sine and cosine of local latitude and longitude
989             final FieldSinCos<T> scLat = FastMath.sinCos(lat);
990             final FieldSinCos<T> scLon = FastMath.sinCos(lon);
991 
992             // compute Legendre polynomials wrt geographic pole
993             final T c = scLat.sin();
994             final T c2 = c.multiply(c);
995             final T c4 = c2.multiply(c2);
996             final T s = scLat.cos();
997             final T s2 = s.multiply(s);
998             p10 = c;
999             p20 = c2.multiply(1.5).subtract(0.5);
1000             p30 = c.multiply(c2.multiply(2.5).subtract(1.5));
1001             p40 = c4.multiply(4.375).subtract(c2.multiply(3.75)).add(0.375);
1002             p50 = c.multiply(c4.multiply(7.875).subtract(c2.multiply(8.75)).add(1.875));
1003             p60 = (c.multiply(5.5).multiply(p50).subtract(p40.multiply(2.5))).divide(3.0);
1004             p11 = s;
1005             p21 = c.multiply(3.0).multiply(s);
1006             p31 = s.multiply(c2.multiply(7.5).subtract(1.5));
1007             p41 = c.multiply(s).multiply(c2.multiply(17.5).subtract(7.5));
1008             p51 = s.multiply(c4.multiply(39.375).subtract(c2.multiply(26.25)).add(1.875));
1009             p22 = s2.multiply(3.0);
1010             p32 = c.multiply(15.0).multiply(s2);
1011             p42 = s2.multiply(c2.multiply(52.5).subtract(7.5));
1012             p52 = c.multiply(3.0).multiply(p42).subtract(p32.multiply(2.0));
1013             p62 = c.multiply(2.75).multiply(p52).subtract(p42.multiply(1.75));
1014             p33 = s.multiply(15.0).multiply(s2);
1015 
1016             // compute Legendre polynomials wrt magnetic pole (79N, 71W)
1017             final T clmlmg = lon.subtract(XLMG).cos();
1018             final T cmg  = s.multiply(CPMG).multiply(clmlmg).add(c.multiply(SPMG));
1019             final T cmg2 = cmg.multiply(cmg);
1020             final T cmg4 = cmg2.multiply(cmg2);
1021             p10mg = cmg;
1022             p20mg = cmg2.multiply(1.5).subtract(0.5);
1023             p40mg = cmg4.multiply(4.375).subtract(cmg2.multiply(3.75)).add(0.375);
1024 
1025             clfl = scLon.cos();
1026             slfl = scLon.sin();
1027 
1028             // local time
1029             hl0 = hl;
1030             final FieldSinCos<T> scHlo = FastMath.sinCos(hl0);
1031             ch  = scHlo.cos();
1032             sh  = scHlo.sin();
1033             c2h = ch.multiply(ch).subtract(sh.multiply(sh));
1034             s2h = ch.multiply(sh).multiply(2);
1035             c3h = c2h.multiply(ch).subtract(s2h.multiply(sh));
1036             s3h = s2h.multiply(ch).add(c2h.multiply(sh));
1037 
1038             final double zlb = ZLB0; // + dzlb ??
1039 
1040             final T[] dtt  = MathArrays.buildArray(altiKM.getField(), tt.length);
1041             final T[] dh   = MathArrays.buildArray(altiKM.getField(), tt.length);
1042             final T[] dhe  = MathArrays.buildArray(altiKM.getField(), tt.length);
1043             final T[] dox  = MathArrays.buildArray(altiKM.getField(), tt.length);
1044             final T[] daz2 = MathArrays.buildArray(altiKM.getField(), tt.length);
1045             final T[] do2  = MathArrays.buildArray(altiKM.getField(), tt.length);
1046             final T[] daz  = MathArrays.buildArray(altiKM.getField(), tt.length);
1047             final T[] dt0  = MathArrays.buildArray(altiKM.getField(), tt.length);
1048             final T[] dtp  = MathArrays.buildArray(altiKM.getField(), tt.length);
1049 
1050             //  compute function g(l) / tinf, t120, tp120
1051             int kleq = 1;
1052             final T gdelt = gFunction(tt, dtt, 1, kleq);
1053             dtt[1] = gdelt.add(1);
1054             final T tinf   = dtt[1].multiply(tt[1]);
1055 
1056             kleq = 0; // equinox
1057 
1058             if (day < 59 || day > 284) {
1059                 kleq = -1; // north winter
1060             }
1061             if (day > 99 && day < 244) {
1062                 kleq = 1; // north summer
1063             }
1064 
1065             final T gdelt0 =  gFunction(t0, dt0, 0, kleq);
1066             dt0[1] = gdelt0.add(t0[1]).divide(t0[1]);
1067             final T t120 = gdelt0.add(t0[1]);
1068             final T gdeltp = gFunction(tp, dtp, 0, kleq);
1069             dtp[1] = gdeltp.add(tp[1]).divide(tp[1]);
1070             final T tp120 = gdeltp.add(tp[1]);
1071 
1072             // compute n(z) concentrations: H, He, O, N2, O2, N
1073             final T sigma   = tp120.divide(tinf.subtract(t120));
1074             final T dzeta   = altiKM.add(RE).reciprocal().multiply(zlb + RE);
1075             final T zeta    = altiKM.subtract(zlb).multiply(dzeta);
1076             final T sigzeta = sigma.multiply(zeta);
1077             final T expsz   = sigzeta.negate().exp();
1078             final T tz = tinf.subtract(tinf.subtract(t120).multiply(expsz));
1079 
1080             final T[] dbase = MathArrays.buildArray(altiKM.getField(), 7);
1081 
1082             kleq = 1;
1083 
1084             final T gdelh = gFunction(h, dh, 0, kleq);
1085             dh[1] = gdelh.exp();
1086             dbase[1] = dh[1].multiply(h[1]);
1087 
1088             final T gdelhe = gFunction(he, dhe, 0, kleq);
1089             dhe[1] = gdelhe.exp();
1090             dbase[2] = dhe[1].multiply(he[1]);
1091 
1092             final T gdelo = gFunction(o, dox, 1, kleq);
1093             dox[1] = gdelo.exp();
1094             dbase[3] = dox[1].multiply(o[1]);
1095 
1096             final T gdelaz2 = gFunction(az2, daz2, 1, kleq);
1097             daz2[1] = gdelaz2.exp();
1098             dbase[4] = daz2[1].multiply(az2[1]);
1099 
1100             final T gdelo2 = gFunction(o2, do2, 1, kleq);
1101             do2[1] = gdelo2.exp();
1102             dbase[5] = do2[1].multiply(o2[1]);
1103 
1104             final T gdelaz = gFunction(az, daz, 1, kleq);
1105             daz[1] = gdelaz.exp();
1106             dbase[6] = daz[1].multiply(az[1]);
1107 
1108             final double zlbre  = 1.0 + zlb / RE;
1109             final T glb    = sigma.multiply(RGAS).multiply(tinf).reciprocal().multiply(GSURF / (zlbre * zlbre));
1110             final T t120tz = t120.divide(tz);
1111 
1112             // Partial densities in (g/cm3).
1113             // d(1) = hydrogen
1114             // d(2) = helium
1115             // d(3) = atomic oxygen
1116             // d(4) = molecular nitrogen
1117             // d(5) = molecular oxygen
1118             // d(6) = atomic nitrogen
1119             T tmpro = altiKM.getField().getZero();
1120             for (int i = 1; i <= 6; i++) {
1121                 final T gamma = glb.multiply(MA[i]);
1122                 final T upapg = gamma.add(1.0 + ALEFA[i]);
1123                 final T fzI = t120tz.pow(upapg).multiply(sigzeta.negate().multiply(gamma).exp());
1124                 // concentrations of H, He, O, N2, O2, N (particles/cm³)
1125                 final T ccI = dbase[i].multiply(fzI);
1126                 // contribution of densities of H, He, O, N2, O2, N (g/cm³)
1127                 tmpro = tmpro.add(ccI.multiply(VMA[i]));
1128             }
1129             this.ro = tmpro;
1130 
1131         }
1132 
1133         /** Computation of function G.
1134          * @param a vector of coefficients for computation
1135          * @param da vector of partial derivatives
1136          * @param ff0 coefficient flag (1 for Ox, Az, He, T°; 0 for H and tp120)
1137          * @param kle_eq season indicator flag (summer, winter, equinox)
1138          * @return value of G
1139          */
1140         private T gFunction(final double[] a, final T[] da,
1141                             final int ff0, final int kle_eq) {
1142 
1143             final T zero = da[0].getField().getZero();
1144             final double[] fmfb   = new double[3];
1145             final double[] fbm150 = new double[3];
1146 
1147             // latitude terms
1148             da[2]  = p20;
1149             da[3]  = p40;
1150             da[74] = p10;
1151             double a74 = a[74];
1152             double a77 = a[77];
1153             double a78 = a[78];
1154             if (kle_eq == -1) {
1155                 // winter
1156                 a74 = -a74;
1157                 a77 = -a77;
1158                 a78 = -a78;
1159             }
1160             if (kle_eq == 0 ) {
1161                 // equinox
1162                 a74 = semestrialCorrection(a74);
1163                 a77 = semestrialCorrection(a77);
1164                 a78 = semestrialCorrection(a78);
1165             }
1166             da[77] = p30;
1167             da[78] = p50;
1168             da[79] = p60;
1169 
1170             // flux terms
1171             fmfb[1]   = f[1] - fbar[1];
1172             fmfb[2]   = f[2] - fbar[2];
1173             fbm150[1] = fbar[1] - 150.0;
1174             fbm150[2] = fbar[2];
1175             da[4]     = zero.add(fmfb[1]);
1176             da[6]     = zero.add(fbm150[1]);
1177             da[4]     = da[4].add(a[70] * fmfb[2]);
1178             da[6]     = da[6].add(a[71] * fbm150[2]);
1179             da[70]    = da[4].multiply(a[ 5]).multiply(2).
1180                             add(p10.multiply(a[82])).
1181                             add(p20.multiply(a[83])).
1182                             add(p30.multiply(a[84])).
1183                             add(a[4]).
1184                         multiply(fmfb[2]);
1185             da[71]    = da[6].multiply(a[69]).multiply(2).
1186                             add(p10.multiply(a[85])).
1187                             add(p20.multiply(a[86])).
1188                             add(p30.multiply(a[87])).
1189                             add(a[6]).
1190                         multiply(fbm150[2]);
1191             da[5]     = da[4].multiply(da[4]);
1192             da[69]    = da[6].multiply(da[6]);
1193             da[82]    = da[4].multiply(p10);
1194             da[83]    = da[4].multiply(p20);
1195             da[84]    = da[4].multiply(p30);
1196             da[85]    = da[6].multiply(p20);
1197             da[86]    = da[6].multiply(p30);
1198             da[87]    = da[6].multiply(p40);
1199 
1200             // Kp terms
1201             final int ikp  = 62;
1202             final int ikpm = 67;
1203             final T c2fi = p10mg.multiply(p10mg).negate().add(1);
1204             final T dkp  = c2fi.multiply(a[ikp + 1]).add(a[ikp]).multiply(akp[2]).add(akp[1]);
1205             T dakp = p20mg.multiply(a[8]).add(p40mg.multiply(a[68])).
1206                      add(p20mg.multiply(a[61]).add(dkp.multiply(dkp).multiply(2 * a[75]).add(a[60])).multiply(dkp.multiply(2))).
1207                      add(a[7]);
1208             da[ikp]     = dakp.multiply(akp[2]);
1209             da[ikp + 1] = da[ikp].multiply(c2fi);
1210             final double dkpm  = akp[3] + a[ikpm] * akp[4];
1211             final T dakpm = p20mg.multiply(a[65]).add(p40mg.multiply(a[72])).
1212                             add(p20mg.multiply(a[73]).add(a[66] + a[76] * 2.0 * dkpm * dkpm).multiply( 2.0 * dkpm)).
1213                             add(a[64]);
1214             da[ikpm] = dakpm.multiply(akp[4]);
1215             da[7]    = dkp;
1216             da[8]    = p20mg.multiply(dkp);
1217             da[68]   = p40mg.multiply(dkp);
1218             da[60]   = dkp.multiply(dkp);
1219             da[61]   = p20mg.multiply(da[60]);
1220             da[75]   = da[60].multiply(da[60]);
1221             da[64]   = zero.add(dkpm);
1222             da[65]   = p20mg.multiply(dkpm);
1223             da[72]   = p40mg.multiply(dkpm);
1224             da[66]   = zero.add(dkpm * dkpm);
1225             da[73]   = p20mg.multiply(da[66]);
1226             da[76]   = da[66].multiply(da[66]);
1227 
1228             // non-periodic g(l) function
1229             T f0 = da[4].multiply(a[4]).
1230                    add(da[5].multiply(a[5])).
1231                    add(da[6].multiply(a[6])).
1232                    add(da[69].multiply(a[69])).
1233                    add(da[82].multiply(a[82])).
1234                    add(da[83].multiply(a[83])).
1235                    add(da[84].multiply(a[84])).
1236                    add(da[85].multiply(a[85])).
1237                    add(da[86].multiply(a[86])).
1238                    add(da[87].multiply(a[87]));
1239             final T f1f = f0.multiply(ff0).add(1);
1240 
1241             f0 = f0.
1242                  add(da[2].multiply(a[2])).
1243                  add(da[3].multiply(a[3])).
1244                  add(da[7].multiply(a[7])).
1245                  add(da[8].multiply(a[8])).
1246                  add(da[60].multiply(a[60])).
1247                  add(da[61].multiply(a[61])).
1248                  add(da[68].multiply(a[68])).
1249                  add(da[64].multiply(a[64])).
1250                  add(da[65].multiply(a[65])).
1251                  add(da[66].multiply(a[66])).
1252                  add(da[72].multiply(a[72])).
1253                  add(da[73].multiply(a[73])).
1254                  add(da[74].multiply(a74)).
1255                  add(da[75].multiply(a[75])).
1256                  add(da[76].multiply(a[76])).
1257                  add(da[77].multiply(a77)).
1258                  add(da[78].multiply(a78)).
1259                  add(da[79].multiply(a[79]));
1260 //          termes annuels symetriques en latitude
1261             da[9]  = zero.add(FastMath.cos(ROT * (day - a[11])));
1262             da[10] = p20.multiply(da[9]);
1263 //          termes semi-annuels symetriques en latitude
1264             da[12] = zero.add(FastMath.cos(ROT2 * (day - a[14])));
1265             da[13] = p20.multiply(da[12]);
1266 //          termes annuels non symetriques en latitude
1267             final double coste = FastMath.cos(ROT * (day - a[18]));
1268             da[15] = p10.multiply(coste);
1269             da[16] = p30.multiply(coste);
1270             da[17] = p50.multiply(coste);
1271 //          terme  semi-annuel  non symetrique  en latitude
1272             final double cos2te = FastMath.cos(ROT2 * (day - a[20]));
1273             da[19] = p10.multiply(cos2te);
1274             da[39] = p30.multiply(cos2te);
1275             da[59] = p50.multiply(cos2te);
1276 //          termes diurnes [et couples annuel]
1277             da[21] = p11.multiply(ch);
1278             da[22] = p31.multiply(ch);
1279             da[23] = p51.multiply(ch);
1280             da[24] = da[21].multiply(coste);
1281             da[25] = p21.multiply(ch).multiply(coste);
1282             da[26] = p11.multiply(sh);
1283             da[27] = p31.multiply(sh);
1284             da[28] = p51.multiply(sh);
1285             da[29] = da[26].multiply(coste);
1286             da[30] = p21.multiply(sh).multiply(coste);
1287 //          termes semi-diurnes [et couples annuel]
1288             da[31] = p22.multiply(c2h);
1289             da[37] = p42.multiply(c2h);
1290             da[32] = p32.multiply(c2h).multiply(coste);
1291             da[33] = p22.multiply(s2h);
1292             da[38] = p42.multiply(s2h);
1293             da[34] = p32.multiply(s2h).multiply(coste);
1294             da[88] = p32.multiply(c2h);
1295             da[89] = p32.multiply(s2h);
1296             da[90] = p52.multiply(c2h);
1297             da[91] = p52.multiply(s2h);
1298             double a88 = a[88];
1299             double a89 = a[89];
1300             double a90 = a[90];
1301             double a91 = a[91];
1302             if (kle_eq == -1) {            //hiver
1303                 a88 = -a88;
1304                 a89 = -a89;
1305                 a90 = -a90;
1306                 a91 = -a91;
1307             }
1308             if (kle_eq == 0) {             //equinox
1309                 a88 = semestrialCorrection(a88);
1310                 a89 = semestrialCorrection(a89);
1311                 a90 = semestrialCorrection(a90);
1312                 a91 = semestrialCorrection(a91);
1313             }
1314             da[92] = p62.multiply(c2h);
1315             da[93] = p62.multiply(s2h);
1316 //          termes ter-diurnes
1317             da[35] = p33.multiply(c3h);
1318             da[36] = p33.multiply(s3h);
1319 //          fonction g[l] periodique
1320             T fp =     da[ 9].multiply(a[ 9]) .add(da[10].multiply(a[10])).add(da[12].multiply(a[12])).add(da[13].multiply(a[13])).
1321                    add(da[15].multiply(a[15])).add(da[16].multiply(a[16])).add(da[17].multiply(a[17])).add(da[19].multiply(a[19])).
1322                    add(da[21].multiply(a[21])).add(da[22].multiply(a[22])).add(da[23].multiply(a[23])).add(da[24].multiply(a[24])).
1323                    add(da[25].multiply(a[25])).add(da[26].multiply(a[26])).add(da[27].multiply(a[27])).add(da[28].multiply(a[28])).
1324                    add(da[29].multiply(a[29])).add(da[30].multiply(a[30])).add(da[31].multiply(a[31])).add(da[32].multiply(a[32])).
1325                    add(da[33].multiply(a[33])).add(da[34].multiply(a[34])).add(da[35].multiply(a[35])).add(da[36].multiply(a[36])).
1326                    add(da[37].multiply(a[37])).add(da[38].multiply(a[38])).add(da[39].multiply(a[39])).add(da[59].multiply(a[59])).
1327                    add(da[88].multiply(a88))  .add(da[89].multiply(a89)  ).add(da[90].multiply(a90)  ).add(da[91].multiply(a91)  ).
1328                    add(da[92].multiply(a[92])).add(da[93].multiply(a[93]));
1329 //          termes d'activite magnetique
1330             da[40] = p10.multiply(coste).multiply(dkp);
1331             da[41] = p30.multiply(coste).multiply(dkp);
1332             da[42] = p50.multiply(coste).multiply(dkp);
1333             da[43] = p11.multiply(ch).multiply(dkp);
1334             da[44] = p31.multiply(ch).multiply(dkp);
1335             da[45] = p51.multiply(ch).multiply(dkp);
1336             da[46] = p11.multiply(sh).multiply(dkp);
1337             da[47] = p31.multiply(sh).multiply(dkp);
1338             da[48] = p51.multiply(sh).multiply(dkp);
1339 
1340 //          fonction g[l] periodique supplementaire
1341             fp = fp.
1342                   add(da[40].multiply(a[40])).
1343                   add(da[41].multiply(a[41])).
1344                   add(da[42].multiply(a[42])).
1345                   add(da[43].multiply(a[43])).
1346                   add(da[44].multiply(a[44])).
1347                   add(da[45].multiply(a[45])).
1348                   add(da[46].multiply(a[46])).
1349                   add(da[47].multiply(a[47])).
1350                   add(da[48].multiply(a[48]));
1351 
1352             dakp =     p10.multiply(a[40]).add(p30.multiply(a[41])).add(p50.multiply(a[42])).multiply(coste).
1353                    add(p11.multiply(a[40]).add(p31.multiply(a[44])).add(p51.multiply(a[45])).multiply(ch)).
1354                    add(p11.multiply(a[46]).add(p31.multiply(a[47])).add(p51.multiply(a[48])).multiply(sh));
1355             da[ikp] = da[ikp].add(dakp.multiply(akp[2]));
1356             da[ikp + 1] = da[ikp].add(dakp.multiply(c2fi).multiply(akp[2]));
1357 //          termes de longitude
1358             da[49] = p11.multiply(clfl);
1359             da[50] = p21.multiply(clfl);
1360             da[51] = p31.multiply(clfl);
1361             da[52] = p41.multiply(clfl);
1362             da[53] = p51.multiply(clfl);
1363             da[54] = p11.multiply(slfl);
1364             da[55] = p21.multiply(slfl);
1365             da[56] = p31.multiply(slfl);
1366             da[57] = p41.multiply(slfl);
1367             da[58] = p51.multiply(slfl);
1368 
1369 //          fonction g[l] periodique supplementaire
1370             fp = fp.
1371                  add(da[49].multiply(a[49])).
1372                  add(da[50].multiply(a[50])).
1373                  add(da[51].multiply(a[51])).
1374                  add(da[52].multiply(a[52])).
1375                  add(da[53].multiply(a[53])).
1376                  add(da[54].multiply(a[54])).
1377                  add(da[55].multiply(a[55])).
1378                  add(da[56].multiply(a[56])).
1379                  add(da[57].multiply(a[57])).
1380                  add(da[58].multiply(a[58]));
1381 
1382 //          fonction g(l) totale (couplage avec le flux)
1383             return f0.add(fp.multiply(f1f));
1384 
1385         }
1386 
1387 
1388         /** Apply a correction coefficient to the given parameter.
1389          * @param param the parameter to correct
1390          * @return the corrected parameter
1391          */
1392         private double semestrialCorrection(final double param) {
1393             final int debeq_pr = 59;
1394             final int debeq_au = 244;
1395             final double result;
1396             if (day >= 100) {
1397                 final double xmult  = (day - debeq_au) / 40.0;
1398                 result = param - 2.0 * param * xmult;
1399             } else {
1400                 final double xmult  = (day - debeq_pr) / 40.0;
1401                 result = 2.0 * param * xmult - param;
1402             }
1403             return result;
1404         }
1405 
1406     }
1407 
1408 }