DTM2000.java
/* Copyright 2002-2024 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.models.earth.atmosphere;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.exception.DummyLocalizable;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.FieldSinCos;
import org.hipparchus.util.MathArrays;
import org.hipparchus.util.SinCos;
import org.orekit.annotation.DefaultDataContext;
import org.orekit.bodies.BodyShape;
import org.orekit.bodies.FieldGeodeticPoint;
import org.orekit.bodies.GeodeticPoint;
import org.orekit.data.DataContext;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.frames.Frame;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.time.TimeScale;
import org.orekit.utils.ExtendedPositionProvider;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/** This atmosphere model is the realization of the DTM-2000 model.
* <p>
* It is described in the paper: <br>
*
* <b>The DTM-2000 empirical thermosphere model with new data assimilation
* and constraints at lower boundary: accuracy and properties</b><br>
*
* <i>S. Bruinsma, G. Thuillier and F. Barlier</i> <br>
*
* Journal of Atmospheric and Solar-Terrestrial Physics 65 (2003) 1053–1070<br>
*
*</p>
*<p>
* This model provides dense output for altitudes beyond 120 km.
*</p>
*
* <p>
* The model needs geographical and time information to compute general values,
* but also needs space weather data : mean and instantaneous solar flux and
* geomagnetic indices.
* </p>
* <p>
* Mean solar flux is (for the moment) represented by the F10.7 indices. Instantaneous
* flux can be set to the mean value if the data is not available. Geomagnetic
* activity is represented by the Kp index, which goes from 1 (very low activity) to
* 9 (high activity).
*
* <p>
* All these data can be found on the <a href="https://www.noaa.gov/">
* NOAA (National Oceanic and Atmospheric Administration) website.</a>
* </p>
*
*
* @author R. Biancale, S. Bruinsma: original fortran routine
* @author Fabien Maussion (java translation)
*/
public class DTM2000 extends AbstractSunInfluencedAtmosphere {
/** Serializable UID. */
private static final long serialVersionUID = 20170705L;
// Constants :
/** Number of parameters. */
private static final int NLATM = 96;
/** Thermal diffusion coefficient. */
private static final double[] ALEFA = {
0, -0.40, -0.38, 0, 0, 0, 0
};
/** Atomic mass H, He, O, N2, O2, N. */
private static final double[] MA = {
0, 1, 4, 16, 28, 32, 14
};
/** Atomic mass H, He, O, N2, O2, N. */
private static final double[] VMA = {
0, 1.6606e-24, 6.6423e-24, 26.569e-24, 46.4958e-24, 53.1381e-24, 23.2479e-24
};
/** Polar Earth radius. */
private static final double RE = 6356.77;
/** Reference altitude. */
private static final double ZLB0 = 120.0;
/** Cosine of the latitude of the magnetic pole (79N, 71W). */
private static final double CPMG = .19081;
/** Sine of the latitude of the magnetic pole (79N, 71W). */
private static final double SPMG = .98163;
/** Longitude (in radians) of the magnetic pole (79N, 71W). */
private static final double XLMG = -1.2392;
/** Gravity acceleration at 120 km altitude. */
private static final double GSURF = 980.665;
/** Universal gas constant. */
private static final double RGAS = 831.4;
/** 2 * π / 365. */
private static final double ROT = 0.017214206;
/** 2 * rot. */
private static final double ROT2 = 0.034428412;
/** Resources text file. */
private static final String DTM2000 = "/assets/org/orekit/dtm_2000.txt";
// CHECKSTYLE: stop JavadocVariable check
/** Elements coefficients. */
private static double[] tt = null;
private static double[] h = null;
private static double[] he = null;
private static double[] o = null;
private static double[] az2 = null;
private static double[] o2 = null;
private static double[] az = null;
private static double[] t0 = null;
private static double[] tp = null;
/** External data container. */
private DTM2000InputParameters inputParams;
/** Earth body shape. */
private BodyShape earth;
/** UTC time scale. */
private final TimeScale utc;
/** Simple constructor for independent computation.
*
* <p>This constructor uses the {@link DataContext#getDefault() default data context}.
*
* @param parameters the solar and magnetic activity data
* @param sun the sun position
* @param earth the earth body shape
* @see #DTM2000(DTM2000InputParameters, ExtendedPositionProvider, BodyShape, TimeScale)
*/
@DefaultDataContext
public DTM2000(final DTM2000InputParameters parameters,
final ExtendedPositionProvider sun, final BodyShape earth) {
this(parameters, sun, earth, DataContext.getDefault().getTimeScales().getUTC());
}
/** Simple constructor for independent computation.
* @param parameters the solar and magnetic activity data
* @param sun the sun position
* @param earth the earth body shape
* @param utc UTC time scale.
* @since 10.1
*/
public DTM2000(final DTM2000InputParameters parameters,
final ExtendedPositionProvider sun,
final BodyShape earth,
final TimeScale utc) {
super(sun);
synchronized (DTM2000.class) {
// lazy reading of model coefficients
if (tt == null) {
readcoefficients();
}
}
this.earth = earth;
this.inputParams = parameters;
this.utc = utc;
}
/** {@inheritDoc} */
public Frame getFrame() {
return earth.getBodyFrame();
}
/** Get the local density with initial entries.
* @param day day of year
* @param alti altitude in meters
* @param lon local longitude (rad)
* @param lat local latitude (rad)
* @param hl local solar time in rad (O hr = 0 rad)
* @param f instantaneous solar flux (F10.7)
* @param fbar mean solar flux (F10.7)
* @param akp3 3 hrs geomagnetic activity index (1-9)
* @param akp24 Mean of last 24 hrs geomagnetic activity index (1-9)
* @return the local density (kg/m³)
*/
public double getDensity(final int day,
final double alti, final double lon, final double lat,
final double hl, final double f, final double fbar,
final double akp3, final double akp24) {
final double threshold = 120000;
if (alti < threshold) {
throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD,
alti, threshold);
}
final Computation result = new Computation(day, alti / 1000, lon, lat, hl,
new double[] {
0, f, 0
}, new double[] {
0, fbar, 0
}, new double[] {
0, akp3, 0, akp24, 0
});
return result.ro * 1000;
}
/** Get the local density with initial entries.
* @param day day of year
* @param alti altitude in meters
* @param lon local longitude (rad)
* @param lat local latitude (rad)
* @param hl local solar time in rad (O hr = 0 rad)
* @param f instantaneous solar flux (F10.7)
* @param fbar mean solar flux (F10.7)
* @param akp3 3 hrs geomagnetic activity index (1-9)
* @param akp24 Mean of last 24 hrs geomagnetic activity index (1-9)
* @param <T> type of the field elements
* @return the local density (kg/m³)
* @since 9.0
*/
public <T extends CalculusFieldElement<T>> T getDensity(final int day,
final T alti, final T lon, final T lat,
final T hl, final double f, final double fbar,
final double akp3, final double akp24) {
final double threshold = 120000;
if (alti.getReal() < threshold) {
throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD,
alti, threshold);
}
final FieldComputation<T> result = new FieldComputation<>(day, alti.divide(1000), lon, lat, hl,
new double[] {
0, f, 0
}, new double[] {
0, fbar, 0
}, new double[] {
0, akp3, 0, akp24, 0
});
return result.ro.multiply(1000);
}
/** Store the DTM model elements coefficients in internal arrays.
*/
private static void readcoefficients() {
final int size = NLATM + 1;
tt = new double[size];
h = new double[size];
he = new double[size];
o = new double[size];
az2 = new double[size];
o2 = new double[size];
az = new double[size];
t0 = new double[size];
tp = new double[size];
try (InputStream in = checkNull(DTM2000.class.getResourceAsStream(DTM2000));
BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
r.readLine();
r.readLine();
for (String line = r.readLine(); line != null; line = r.readLine()) {
final int num = Integer.parseInt(line.substring(0, 4).replace(' ', '0'));
line = line.substring(4);
tt[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
line = line.substring(13 + 9);
h[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
line = line.substring(13 + 9);
he[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
line = line.substring(13 + 9);
o[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
line = line.substring(13 + 9);
az2[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
line = line.substring(13 + 9);
o2[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
line = line.substring(13 + 9);
az[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
line = line.substring(13 + 9);
t0[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
line = line.substring(13 + 9);
tp[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
}
} catch (IOException ioe) {
throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
}
}
/** Get the local density.
* @param date current date
* @param position current position in frame
* @param frame the frame in which is defined the position
* @return local density (kg/m³)
*/
public double getDensity(final AbsoluteDate date, final Vector3D position,
final Frame frame) {
// check if data are available :
if (date.compareTo(inputParams.getMaxDate()) > 0 ||
date.compareTo(inputParams.getMinDate()) < 0) {
throw new OrekitException(OrekitMessages.NO_SOLAR_ACTIVITY_AT_DATE,
date, inputParams.getMinDate(), inputParams.getMaxDate());
}
// compute day number in current year
final int day = date.getComponents(utc).getDate().getDayOfYear();
//position in ECEF so we only have to do the transform once
final Frame ecef = earth.getBodyFrame();
final Vector3D pEcef = frame.getStaticTransformTo(ecef, date)
.transformPosition(position);
// compute geodetic position
final GeodeticPoint inBody = earth.transform(pEcef, ecef, date);
final double alti = inBody.getAltitude();
final double lon = inBody.getLongitude();
final double lat = inBody.getLatitude();
// compute local solar time
final Vector3D sunPos = getSunPosition(date, ecef);
final double hl = FastMath.PI + FastMath.atan2(
sunPos.getX() * pEcef.getY() - sunPos.getY() * pEcef.getX(),
sunPos.getX() * pEcef.getX() + sunPos.getY() * pEcef.getY());
// get current solar activity data and compute
return getDensity(day, alti, lon, lat, hl, inputParams.getInstantFlux(date),
inputParams.getMeanFlux(date), inputParams.getThreeHourlyKP(date),
inputParams.get24HoursKp(date));
}
/** {@inheritDoc} */
@Override
public <T extends CalculusFieldElement<T>> T
getDensity(final FieldAbsoluteDate<T> date, final FieldVector3D<T> position,
final Frame frame) {
// check if data are available :
final AbsoluteDate dateD = date.toAbsoluteDate();
if (dateD.compareTo(inputParams.getMaxDate()) > 0 ||
dateD.compareTo(inputParams.getMinDate()) < 0) {
throw new OrekitException(OrekitMessages.NO_SOLAR_ACTIVITY_AT_DATE,
dateD, inputParams.getMinDate(), inputParams.getMaxDate());
}
// compute day number in current year
final int day = date.getComponents(utc).getDate().getDayOfYear();
// position in ECEF so we only have to do the transform once
final Frame ecef = earth.getBodyFrame();
final FieldVector3D<T> pEcef = frame.getStaticTransformTo(ecef, date).transformPosition(position);
// compute geodetic position
final FieldGeodeticPoint<T> inBody = earth.transform(pEcef, ecef, date);
final T alti = inBody.getAltitude();
final T lon = inBody.getLongitude();
final T lat = inBody.getLatitude();
// compute local solar time
final FieldVector3D<T> sunPos = getSunPosition(date, ecef);
final T y = pEcef.getY().multiply(sunPos.getX()).subtract(pEcef.getX().multiply(sunPos.getY()));
final T x = pEcef.getX().multiply(sunPos.getX()).add(pEcef.getY().multiply(sunPos.getY()));
final T hl = y.atan2(x).add(y.getPi());
// get current solar activity data and compute
return getDensity(day, alti, lon, lat, hl, inputParams.getInstantFlux(dateD),
inputParams.getMeanFlux(dateD), inputParams.getThreeHourlyKP(dateD),
inputParams.get24HoursKp(dateD));
}
/**
* Helper method to check for null resources. Throws an exception if {@code
* stream} is null.
*
* @param stream loaded from the class resources.
* @return {@code stream}.
*/
private static InputStream checkNull(final InputStream stream) {
if (stream == null) {
throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_RESOURCE, DTM2000);
}
return stream;
}
/** Local holder for intermediate results ensuring the model is reentrant. */
private static class Computation {
/** Number of days in current year. */
private final int day;
/** Instant solar flux. f[1] = instantaneous flux; f[2] = 0. (not used). */
private final double[] f;
/** Mean solar flux. fbar[1] = mean flux; fbar[2] = 0. (not used). */
private final double[] fbar;
/** Kp coefficients.
* <ul>
* <li>akp[1] = 3-hourly kp</li>
* <li>akp[2] = 0 (not used)</li>
* <li>akp[3] = mean kp of last 24 hours</li>
* <li>akp[4] = 0 (not used)</li>
* </ul>
*/
private final double[] akp;
/** Cosine of the longitude. */
private final double clfl;
/** Sine of the longitude. */
private final double slfl;
/** Total density (g/cm3). */
private final double ro;
// CHECKSTYLE: stop JavadocVariable check
/** Legendre coefficients. */
private final double p10;
private final double p20;
private final double p30;
private final double p40;
private final double p50;
private final double p60;
private final double p11;
private final double p21;
private final double p31;
private final double p41;
private final double p51;
private final double p22;
private final double p32;
private final double p42;
private final double p52;
private final double p62;
private final double p33;
private final double p10mg;
private final double p20mg;
private final double p40mg;
/** Local time intermediate values. */
private final double hl0;
private final double ch;
private final double sh;
private final double c2h;
private final double s2h;
private final double c3h;
private final double s3h;
/** Simple constructor.
* @param day day of year
* @param altiKM altitude <em>in kilometers</em>
* @param lon local longitude (rad)
* @param lat local latitude (rad)
* @param hl local solar time in rad (O hr = 0 rad)
* @param f instantaneous solar flux (F10.7)
* @param fbar mean solar flux (F10.7)
* @param akp geomagnetic activity index
*/
Computation(final int day,
final double altiKM, final double lon, final double lat,
final double hl, final double[] f, final double[] fbar,
final double[] akp) {
this.day = day;
this.f = f;
this.fbar = fbar;
this.akp = akp;
// Sine and cosine of local latitude and longitude
final SinCos scLat = FastMath.sinCos(lat);
final SinCos scLon = FastMath.sinCos(lon);
// compute Legendre polynomials wrt geographic pole
final double c = scLat.sin();
final double c2 = c * c;
final double c4 = c2 * c2;
final double s = scLat.cos();
final double s2 = s * s;
p10 = c;
p20 = 1.5 * c2 - 0.5;
p30 = c * (2.5 * c2 - 1.5);
p40 = 4.375 * c4 - 3.75 * c2 + 0.375;
p50 = c * (7.875 * c4 - 8.75 * c2 + 1.875);
p60 = (5.5 * c * p50 - 2.5 * p40) / 3.0;
p11 = s;
p21 = 3.0 * c * s;
p31 = s * (7.5 * c2 - 1.5);
p41 = c * s * (17.5 * c2 - 7.5);
p51 = s * (39.375 * c4 - 26.25 * c2 + 1.875);
p22 = 3.0 * s2;
p32 = 15.0 * c * s2;
p42 = s2 * (52.5 * c2 - 7.5);
p52 = 3.0 * c * p42 - 2.0 * p32;
p62 = 2.75 * c * p52 - 1.75 * p42;
p33 = 15.0 * s * s2;
// compute Legendre polynomials wrt magnetic pole (79N, 71W)
final double clmlmg = FastMath.cos(lon - XLMG);
final double cmg = s * CPMG * clmlmg + c * SPMG;
final double cmg2 = cmg * cmg;
final double cmg4 = cmg2 * cmg2;
p10mg = cmg;
p20mg = 1.5 * cmg2 - 0.5;
p40mg = 4.375 * cmg4 - 3.75 * cmg2 + 0.375;
clfl = scLon.cos();
slfl = scLon.sin();
// local time
hl0 = hl;
final SinCos scHlo = FastMath.sinCos(hl0);
ch = scHlo.cos();
sh = scHlo.sin();
c2h = ch * ch - sh * sh;
s2h = 2.0 * ch * sh;
c3h = c2h * ch - s2h * sh;
s3h = s2h * ch + c2h * sh;
final double zlb = ZLB0; // + dzlb ??
final double[] dtt = new double[tt.length];
final double[] dh = new double[tt.length];
final double[] dhe = new double[tt.length];
final double[] dox = new double[tt.length];
final double[] daz2 = new double[tt.length];
final double[] do2 = new double[tt.length];
final double[] daz = new double[tt.length];
final double[] dt0 = new double[tt.length];
final double[] dtp = new double[tt.length];
Arrays.fill(dtt, Double.NaN);
Arrays.fill(dh, Double.NaN);
Arrays.fill(dhe, Double.NaN);
Arrays.fill(dox, Double.NaN);
Arrays.fill(daz2, Double.NaN);
Arrays.fill(do2, Double.NaN);
Arrays.fill(daz, Double.NaN);
Arrays.fill(dt0, Double.NaN);
Arrays.fill(dtp, Double.NaN);
// compute function g(l) / tinf, t120, tp120
int kleq = 1;
final double gdelt = gFunction(tt, dtt, 1, kleq);
dtt[1] = 1.0 + gdelt;
final double tinf = tt[1] * dtt[1];
kleq = 0; // equinox
if (day < 59 || day > 284) {
kleq = -1; // north winter
}
if (day > 99 && day < 244) {
kleq = 1; // north summer
}
final double gdelt0 = gFunction(t0, dt0, 0, kleq);
dt0[1] = (t0[1] + gdelt0) / t0[1];
final double t120 = t0[1] + gdelt0;
final double gdeltp = gFunction(tp, dtp, 0, kleq);
dtp[1] = (tp[1] + gdeltp) / tp[1];
final double tp120 = tp[1] + gdeltp;
// compute n(z) concentrations: H, He, O, N2, O2, N
final double sigma = tp120 / (tinf - t120);
final double dzeta = (RE + zlb) / (RE + altiKM);
final double zeta = (altiKM - zlb) * dzeta;
final double sigzeta = sigma * zeta;
final double expsz = FastMath.exp(-sigzeta);
final double tz = tinf - (tinf - t120) * expsz;
final double[] dbase = new double[7];
kleq = 1;
final double gdelh = gFunction(h, dh, 0, kleq);
dh[1] = FastMath.exp(gdelh);
dbase[1] = h[1] * dh[1];
final double gdelhe = gFunction(he, dhe, 0, kleq);
dhe[1] = FastMath.exp(gdelhe);
dbase[2] = he[1] * dhe[1];
final double gdelo = gFunction(o, dox, 1, kleq);
dox[1] = FastMath.exp(gdelo);
dbase[3] = o[1] * dox[1];
final double gdelaz2 = gFunction(az2, daz2, 1, kleq);
daz2[1] = FastMath.exp(gdelaz2);
dbase[4] = az2[1] * daz2[1];
final double gdelo2 = gFunction(o2, do2, 1, kleq);
do2[1] = FastMath.exp(gdelo2);
dbase[5] = o2[1] * do2[1];
final double gdelaz = gFunction(az, daz, 1, kleq);
daz[1] = FastMath.exp(gdelaz);
dbase[6] = az[1] * daz[1];
final double zlbre = 1.0 + zlb / RE;
final double glb = (GSURF / (zlbre * zlbre)) / (sigma * RGAS * tinf);
final double t120tz = t120 / tz;
// Partial densities in (g/cm3).
// d(1) = hydrogen
// d(2) = helium
// d(3) = atomic oxygen
// d(4) = molecular nitrogen
// d(5) = molecular oxygen
// d(6) = atomic nitrogen
double tmpro = 0;
for (int i = 1; i <= 6; i++) {
final double gamma = MA[i] * glb;
final double upapg = 1.0 + ALEFA[i] + gamma;
final double fzI = FastMath.exp(FastMath.log(t120tz) * upapg - sigzeta * gamma);
// concentrations of H, He, O, N2, O2, N (particles/cm³)
final double ccI = dbase[i] * fzI;
// contribution of densities of H, He, O, N2, O2, N (g/cm³)
tmpro += ccI * VMA[i];
}
this.ro = tmpro;
}
/** Computation of function G.
* @param a vector of coefficients for computation
* @param da vector of partial derivatives
* @param ff0 coefficient flag (1 for Ox, Az, He, T°; 0 for H and tp120)
* @param kle_eq season indicator flag (summer, winter, equinox)
* @return value of G
*/
private double gFunction(final double[] a, final double[] da,
final int ff0, final int kle_eq) {
final double[] fmfb = new double[3];
final double[] fbm150 = new double[3];
// latitude terms
da[2] = p20;
da[3] = p40;
da[74] = p10;
double a74 = a[74];
double a77 = a[77];
double a78 = a[78];
if (kle_eq == -1) {
// winter
a74 = -a74;
a77 = -a77;
a78 = -a78;
}
if (kle_eq == 0 ) {
// equinox
a74 = semestrialCorrection(a74);
a77 = semestrialCorrection(a77);
a78 = semestrialCorrection(a78);
}
da[77] = p30;
da[78] = p50;
da[79] = p60;
// flux terms
fmfb[1] = f[1] - fbar[1];
fmfb[2] = f[2] - fbar[2];
fbm150[1] = fbar[1] - 150.0;
fbm150[2] = fbar[2];
da[4] = fmfb[1];
da[6] = fbm150[1];
da[4] = da[4] + a[70] * fmfb[2];
da[6] = da[6] + a[71] * fbm150[2];
da[70] = fmfb[2] * (a[4] + 2.0 * a[5] * da[4] + a[82] * p10 +
a[83] * p20 + a[84] * p30);
da[71] = fbm150[2] * (a[6] + 2.0 * a[69] * da[6] + a[85] * p10 +
a[86] * p20 + a[87] * p30);
da[5] = da[4] * da[4];
da[69] = da[6] * da[6];
da[82] = da[4] * p10;
da[83] = da[4] * p20;
da[84] = da[4] * p30;
da[85] = da[6] * p20;
da[86] = da[6] * p30;
da[87] = da[6] * p40;
// Kp terms
final int ikp = 62;
final int ikpm = 67;
final double c2fi = 1.0 - p10mg * p10mg;
final double dkp = akp[1] + (a[ikp] + c2fi * a[ikp + 1]) * akp[2];
double dakp = a[7] + a[8] * p20mg + a[68] * p40mg +
2.0 * dkp * (a[60] + a[61] * p20mg +
a[75] * 2.0 * dkp * dkp);
da[ikp] = dakp * akp[2];
da[ikp + 1] = da[ikp] * c2fi;
final double dkpm = akp[3] + a[ikpm] * akp[4];
final double dakpm = a[64] + a[65] * p20mg + a[72] * p40mg +
2.0 * dkpm * (a[66] + a[73] * p20mg +
a[76] * 2.0 * dkpm * dkpm);
da[ikpm] = dakpm * akp[4];
da[7] = dkp;
da[8] = p20mg * dkp;
da[68] = p40mg * dkp;
da[60] = dkp * dkp;
da[61] = p20mg * da[60];
da[75] = da[60] * da[60];
da[64] = dkpm;
da[65] = p20mg * dkpm;
da[72] = p40mg * dkpm;
da[66] = dkpm * dkpm;
da[73] = p20mg * da[66];
da[76] = da[66] * da[66];
// non-periodic g(l) function
double f0 = a[4] * da[4] + a[5] * da[5] + a[6] * da[6] +
a[69] * da[69] + a[82] * da[82] + a[83] * da[83] +
a[84] * da[84] + a[85] * da[85] + a[86] * da[86] +
a[87] * da[87];
final double f1f = 1.0 + f0 * ff0;
f0 = f0 + a[2] * da[2] + a[3] * da[3] + a74 * da[74] +
a77 * da[77] + a[7] * da[7] + a[8] * da[8] +
a[60] * da[60] + a[61] * da[61] + a[68] * da[68] +
a[64] * da[64] + a[65] * da[65] + a[66] * da[66] +
a[72] * da[72] + a[73] * da[73] + a[75] * da[75] +
a[76] * da[76] + a78 * da[78] + a[79] * da[79];
// termes annuels symetriques en latitude
da[9] = FastMath.cos(ROT * (day - a[11]));
da[10] = p20 * da[9];
// termes semi-annuels symetriques en latitude
da[12] = FastMath.cos(ROT2 * (day - a[14]));
da[13] = p20 * da[12];
// termes annuels non symetriques en latitude
final double coste = FastMath.cos(ROT * (day - a[18]));
da[15] = p10 * coste;
da[16] = p30 * coste;
da[17] = p50 * coste;
// terme semi-annuel non symetrique en latitude
final double cos2te = FastMath.cos(ROT2 * (day - a[20]));
da[19] = p10 * cos2te;
da[39] = p30 * cos2te;
da[59] = p50 * cos2te;
// termes diurnes [et couples annuel]
da[21] = p11 * ch;
da[22] = p31 * ch;
da[23] = p51 * ch;
da[24] = da[21] * coste;
da[25] = p21 * ch * coste;
da[26] = p11 * sh;
da[27] = p31 * sh;
da[28] = p51 * sh;
da[29] = da[26] * coste;
da[30] = p21 * sh * coste;
// termes semi-diurnes [et couples annuel]
da[31] = p22 * c2h;
da[37] = p42 * c2h;
da[32] = p32 * c2h * coste;
da[33] = p22 * s2h;
da[38] = p42 * s2h;
da[34] = p32 * s2h * coste;
da[88] = p32 * c2h;
da[89] = p32 * s2h;
da[90] = p52 * c2h;
da[91] = p52 * s2h;
double a88 = a[88];
double a89 = a[89];
double a90 = a[90];
double a91 = a[91];
if (kle_eq == -1) { //hiver
a88 = -a88;
a89 = -a89;
a90 = -a90;
a91 = -a91;
}
if (kle_eq == 0) { //equinox
a88 = semestrialCorrection(a88);
a89 = semestrialCorrection(a89);
a90 = semestrialCorrection(a90);
a91 = semestrialCorrection(a91);
}
da[92] = p62 * c2h;
da[93] = p62 * s2h;
// termes ter-diurnes
da[35] = p33 * c3h;
da[36] = p33 * s3h;
// fonction g[l] periodique
double fp = a[9] * da[9] + a[10] * da[10] + a[12] * da[12] + a[13] * da[13] +
a[15] * da[15] + a[16] * da[16] + a[17] * da[17] + a[19] * da[19] +
a[21] * da[21] + a[22] * da[22] + a[23] * da[23] + a[24] * da[24] +
a[25] * da[25] + a[26] * da[26] + a[27] * da[27] + a[28] * da[28] +
a[29] * da[29] + a[30] * da[30] + a[31] * da[31] + a[32] * da[32] +
a[33] * da[33] + a[34] * da[34] + a[35] * da[35] + a[36] * da[36] +
a[37] * da[37] + a[38] * da[38] + a[39] * da[39] + a[59] * da[59] +
a88 * da[88] + a89 * da[89] + a90 * da[90] + a91 * da[91] +
a[92] * da[92] + a[93] * da[93];
// termes d'activite magnetique
da[40] = p10 * coste * dkp;
da[41] = p30 * coste * dkp;
da[42] = p50 * coste * dkp;
da[43] = p11 * ch * dkp;
da[44] = p31 * ch * dkp;
da[45] = p51 * ch * dkp;
da[46] = p11 * sh * dkp;
da[47] = p31 * sh * dkp;
da[48] = p51 * sh * dkp;
// fonction g[l] periodique supplementaire
fp += a[40] * da[40] + a[41] * da[41] + a[42] * da[42] + a[43] * da[43] +
a[44] * da[44] + a[45] * da[45] + a[46] * da[46] + a[47] * da[47] +
a[48] * da[48];
dakp = (a[40] * p10 + a[41] * p30 + a[42] * p50) * coste +
(a[43] * p11 + a[44] * p31 + a[45] * p51) * ch +
(a[46] * p11 + a[47] * p31 + a[48] * p51) * sh;
da[ikp] += dakp * akp[2];
da[ikp + 1] = da[ikp] + dakp * c2fi * akp[2];
// termes de longitude
da[49] = p11 * clfl;
da[50] = p21 * clfl;
da[51] = p31 * clfl;
da[52] = p41 * clfl;
da[53] = p51 * clfl;
da[54] = p11 * slfl;
da[55] = p21 * slfl;
da[56] = p31 * slfl;
da[57] = p41 * slfl;
da[58] = p51 * slfl;
// fonction g[l] periodique supplementaire
fp += a[49] * da[49] + a[50] * da[50] + a[51] * da[51] + a[52] * da[52] +
a[53] * da[53] + a[54] * da[54] + a[55] * da[55] + a[56] * da[56] +
a[57] * da[57] + a[58] * da[58];
// fonction g(l) totale (couplage avec le flux)
return f0 + fp * f1f;
}
/** Apply a correction coefficient to the given parameter.
* @param param the parameter to correct
* @return the corrected parameter
*/
private double semestrialCorrection(final double param) {
final int debeq_pr = 59;
final int debeq_au = 244;
final double result;
if (day >= 100) {
final double xmult = (day - debeq_au) / 40.0;
result = param - 2.0 * param * xmult;
} else {
final double xmult = (day - debeq_pr) / 40.0;
result = 2.0 * param * xmult - param;
}
return result;
}
}
/** Local holder for intermediate results ensuring the model is reentrant.
* @param <T> type of the field elements
*/
private static class FieldComputation<T extends CalculusFieldElement<T>> {
/** Number of days in current year. */
private int day;
/** Instant solar flux. f[1] = instantaneous flux; f[2] = 0. (not used). */
private double[] f = new double[3];
/** Mean solar flux. fbar[1] = mean flux; fbar[2] = 0. (not used). */
private double[] fbar = new double[3];
/** Kp coefficients.
* <ul>
* <li>akp[1] = 3-hourly kp</li>
* <li>akp[2] = 0 (not used)</li>
* <li>akp[3] = mean kp of last 24 hours</li>
* <li>akp[4] = 0 (not used)</li>
* </ul>
*/
private double[] akp = new double[5];
/** Cosine of the longitude. */
private final T clfl;
/** Sine of the longitude. */
private final T slfl;
/** Total density (g/cm3). */
private final T ro;
// CHECKSTYLE: stop JavadocVariable check
/** Legendre coefficients. */
private final T p10;
private final T p20;
private final T p30;
private final T p40;
private final T p50;
private final T p60;
private final T p11;
private final T p21;
private final T p31;
private final T p41;
private final T p51;
private final T p22;
private final T p32;
private final T p42;
private final T p52;
private final T p62;
private final T p33;
private final T p10mg;
private final T p20mg;
private final T p40mg;
/** Local time intermediate values. */
private final T hl0;
private final T ch;
private final T sh;
private final T c2h;
private final T s2h;
private final T c3h;
private final T s3h;
/** Simple constructor.
* @param day day of year
* @param altiKM altitude <em>in kilometers</em>
* @param lon local longitude (rad)
* @param lat local latitude (rad)
* @param hl local solar time in rad (O hr = 0 rad)
* @param f instantaneous solar flux (F10.7)
* @param far mean solar flux (F10.7)
* @param akp geomagnetic activity index
*/
FieldComputation(final int day,
final T altiKM, final T lon, final T lat,
final T hl, final double[] f, final double[] far,
final double[] akp) {
this.day = day;
this.f = f;
this.fbar = far;
this.akp = akp;
// Sine and cosine of local latitude and longitude
final FieldSinCos<T> scLat = FastMath.sinCos(lat);
final FieldSinCos<T> scLon = FastMath.sinCos(lon);
// compute Legendre polynomials wrt geographic pole
final T c = scLat.sin();
final T c2 = c.square();
final T c4 = c2.square();
final T s = scLat.cos();
final T s2 = s.multiply(s);
p10 = c;
p20 = c2.multiply(1.5).subtract(0.5);
p30 = c.multiply(c2.multiply(2.5).subtract(1.5));
p40 = c4.multiply(4.375).subtract(c2.multiply(3.75)).add(0.375);
p50 = c.multiply(c4.multiply(7.875).subtract(c2.multiply(8.75)).add(1.875));
p60 = (c.multiply(5.5).multiply(p50).subtract(p40.multiply(2.5))).divide(3.0);
p11 = s;
p21 = c.multiply(3.0).multiply(s);
p31 = s.multiply(c2.multiply(7.5).subtract(1.5));
p41 = c.multiply(s).multiply(c2.multiply(17.5).subtract(7.5));
p51 = s.multiply(c4.multiply(39.375).subtract(c2.multiply(26.25)).add(1.875));
p22 = s2.multiply(3.0);
p32 = c.multiply(15.0).multiply(s2);
p42 = s2.multiply(c2.multiply(52.5).subtract(7.5));
p52 = c.multiply(3.0).multiply(p42).subtract(p32.multiply(2.0));
p62 = c.multiply(2.75).multiply(p52).subtract(p42.multiply(1.75));
p33 = s.multiply(15.0).multiply(s2);
// compute Legendre polynomials wrt magnetic pole (79N, 71W)
final T clmlmg = lon.subtract(XLMG).cos();
final T cmg = s.multiply(CPMG).multiply(clmlmg).add(c.multiply(SPMG));
final T cmg2 = cmg.multiply(cmg);
final T cmg4 = cmg2.multiply(cmg2);
p10mg = cmg;
p20mg = cmg2.multiply(1.5).subtract(0.5);
p40mg = cmg4.multiply(4.375).subtract(cmg2.multiply(3.75)).add(0.375);
clfl = scLon.cos();
slfl = scLon.sin();
// local time
hl0 = hl;
final FieldSinCos<T> scHlo = FastMath.sinCos(hl0);
ch = scHlo.cos();
sh = scHlo.sin();
c2h = ch.multiply(ch).subtract(sh.multiply(sh));
s2h = ch.multiply(sh).multiply(2);
c3h = c2h.multiply(ch).subtract(s2h.multiply(sh));
s3h = s2h.multiply(ch).add(c2h.multiply(sh));
final double zlb = ZLB0; // + dzlb ??
final T[] dtt = MathArrays.buildArray(altiKM.getField(), tt.length);
final T[] dh = MathArrays.buildArray(altiKM.getField(), tt.length);
final T[] dhe = MathArrays.buildArray(altiKM.getField(), tt.length);
final T[] dox = MathArrays.buildArray(altiKM.getField(), tt.length);
final T[] daz2 = MathArrays.buildArray(altiKM.getField(), tt.length);
final T[] do2 = MathArrays.buildArray(altiKM.getField(), tt.length);
final T[] daz = MathArrays.buildArray(altiKM.getField(), tt.length);
final T[] dt0 = MathArrays.buildArray(altiKM.getField(), tt.length);
final T[] dtp = MathArrays.buildArray(altiKM.getField(), tt.length);
// compute function g(l) / tinf, t120, tp120
int kleq = 1;
final T gdelt = gFunction(tt, dtt, 1, kleq);
dtt[1] = gdelt.add(1);
final T tinf = dtt[1].multiply(tt[1]);
kleq = 0; // equinox
if (day < 59 || day > 284) {
kleq = -1; // north winter
}
if (day > 99 && day < 244) {
kleq = 1; // north summer
}
final T gdelt0 = gFunction(t0, dt0, 0, kleq);
dt0[1] = gdelt0.add(t0[1]).divide(t0[1]);
final T t120 = gdelt0.add(t0[1]);
final T gdeltp = gFunction(tp, dtp, 0, kleq);
dtp[1] = gdeltp.add(tp[1]).divide(tp[1]);
final T tp120 = gdeltp.add(tp[1]);
// compute n(z) concentrations: H, He, O, N2, O2, N
final T sigma = tp120.divide(tinf.subtract(t120));
final T dzeta = altiKM.add(RE).reciprocal().multiply(zlb + RE);
final T zeta = altiKM.subtract(zlb).multiply(dzeta);
final T sigzeta = sigma.multiply(zeta);
final T expsz = sigzeta.negate().exp();
final T tz = tinf.subtract(tinf.subtract(t120).multiply(expsz));
final T[] dbase = MathArrays.buildArray(altiKM.getField(), 7);
kleq = 1;
final T gdelh = gFunction(h, dh, 0, kleq);
dh[1] = gdelh.exp();
dbase[1] = dh[1].multiply(h[1]);
final T gdelhe = gFunction(he, dhe, 0, kleq);
dhe[1] = gdelhe.exp();
dbase[2] = dhe[1].multiply(he[1]);
final T gdelo = gFunction(o, dox, 1, kleq);
dox[1] = gdelo.exp();
dbase[3] = dox[1].multiply(o[1]);
final T gdelaz2 = gFunction(az2, daz2, 1, kleq);
daz2[1] = gdelaz2.exp();
dbase[4] = daz2[1].multiply(az2[1]);
final T gdelo2 = gFunction(o2, do2, 1, kleq);
do2[1] = gdelo2.exp();
dbase[5] = do2[1].multiply(o2[1]);
final T gdelaz = gFunction(az, daz, 1, kleq);
daz[1] = gdelaz.exp();
dbase[6] = daz[1].multiply(az[1]);
final double zlbre = 1.0 + zlb / RE;
final T glb = sigma.multiply(RGAS).multiply(tinf).reciprocal().multiply(GSURF / (zlbre * zlbre));
final T t120tz = t120.divide(tz);
// Partial densities in (g/cm3).
// d(1) = hydrogen
// d(2) = helium
// d(3) = atomic oxygen
// d(4) = molecular nitrogen
// d(5) = molecular oxygen
// d(6) = atomic nitrogen
T tmpro = altiKM.getField().getZero();
for (int i = 1; i <= 6; i++) {
final T gamma = glb.multiply(MA[i]);
final T upapg = gamma.add(1.0 + ALEFA[i]);
final T fzI = (t120tz.log().multiply(upapg).subtract(sigzeta.multiply(gamma))).exp();
// concentrations of H, He, O, N2, O2, N (particles/cm³)
final T ccI = dbase[i].multiply(fzI);
// contribution of densities of H, He, O, N2, O2, N (g/cm³)
tmpro = tmpro.add(ccI.multiply(VMA[i]));
}
this.ro = tmpro;
}
/** Computation of function G.
* @param a vector of coefficients for computation
* @param da vector of partial derivatives
* @param ff0 coefficient flag (1 for Ox, Az, He, T°; 0 for H and tp120)
* @param kle_eq season indicator flag (summer, winter, equinox)
* @return value of G
*/
private T gFunction(final double[] a, final T[] da,
final int ff0, final int kle_eq) {
final T zero = da[0].getField().getZero();
final double[] fmfb = new double[3];
final double[] fbm150 = new double[3];
// latitude terms
da[2] = p20;
da[3] = p40;
da[74] = p10;
double a74 = a[74];
double a77 = a[77];
double a78 = a[78];
if (kle_eq == -1) {
// winter
a74 = -a74;
a77 = -a77;
a78 = -a78;
}
if (kle_eq == 0 ) {
// equinox
a74 = semestrialCorrection(a74);
a77 = semestrialCorrection(a77);
a78 = semestrialCorrection(a78);
}
da[77] = p30;
da[78] = p50;
da[79] = p60;
// flux terms
fmfb[1] = f[1] - fbar[1];
fmfb[2] = f[2] - fbar[2];
fbm150[1] = fbar[1] - 150.0;
fbm150[2] = fbar[2];
da[4] = zero.newInstance(fmfb[1]);
da[6] = zero.newInstance(fbm150[1]);
da[4] = da[4].add(a[70] * fmfb[2]);
da[6] = da[6].add(a[71] * fbm150[2]);
da[70] = da[4].multiply(a[ 5]).multiply(2).
add(p10.multiply(a[82])).
add(p20.multiply(a[83])).
add(p30.multiply(a[84])).
add(a[4]).
multiply(fmfb[2]);
da[71] = da[6].multiply(a[69]).multiply(2).
add(p10.multiply(a[85])).
add(p20.multiply(a[86])).
add(p30.multiply(a[87])).
add(a[6]).
multiply(fbm150[2]);
da[5] = da[4].multiply(da[4]);
da[69] = da[6].multiply(da[6]);
da[82] = da[4].multiply(p10);
da[83] = da[4].multiply(p20);
da[84] = da[4].multiply(p30);
da[85] = da[6].multiply(p20);
da[86] = da[6].multiply(p30);
da[87] = da[6].multiply(p40);
// Kp terms
final int ikp = 62;
final int ikpm = 67;
final T c2fi = p10mg.multiply(p10mg).negate().add(1);
final T dkp = c2fi.multiply(a[ikp + 1]).add(a[ikp]).multiply(akp[2]).add(akp[1]);
T dakp = p20mg.multiply(a[8]).add(p40mg.multiply(a[68])).
add(p20mg.multiply(a[61]).add(dkp.square().multiply(2 * a[75]).add(a[60])).multiply(dkp.multiply(2))).
add(a[7]);
da[ikp] = dakp.multiply(akp[2]);
da[ikp + 1] = da[ikp].multiply(c2fi);
final double dkpm = akp[3] + a[ikpm] * akp[4];
final T dakpm = p20mg.multiply(a[65]).add(p40mg.multiply(a[72])).
add(p20mg.multiply(a[73]).add(a[66] + a[76] * 2.0 * dkpm * dkpm).multiply( 2.0 * dkpm)).
add(a[64]);
da[ikpm] = dakpm.multiply(akp[4]);
da[7] = dkp;
da[8] = p20mg.multiply(dkp);
da[68] = p40mg.multiply(dkp);
da[60] = dkp.square();
da[61] = p20mg.multiply(da[60]);
da[75] = da[60].square();
da[64] = zero.newInstance(dkpm);
da[65] = p20mg.multiply(dkpm);
da[72] = p40mg.multiply(dkpm);
da[66] = zero.newInstance(dkpm * dkpm);
da[73] = p20mg.multiply(da[66]);
da[76] = da[66].square();
// non-periodic g(l) function
T f0 = da[4].multiply(a[4]).
add(da[5].multiply(a[5])).
add(da[6].multiply(a[6])).
add(da[69].multiply(a[69])).
add(da[82].multiply(a[82])).
add(da[83].multiply(a[83])).
add(da[84].multiply(a[84])).
add(da[85].multiply(a[85])).
add(da[86].multiply(a[86])).
add(da[87].multiply(a[87]));
final T f1f = f0.multiply(ff0).add(1);
f0 = f0.
add(da[2].multiply(a[2])).
add(da[3].multiply(a[3])).
add(da[7].multiply(a[7])).
add(da[8].multiply(a[8])).
add(da[60].multiply(a[60])).
add(da[61].multiply(a[61])).
add(da[68].multiply(a[68])).
add(da[64].multiply(a[64])).
add(da[65].multiply(a[65])).
add(da[66].multiply(a[66])).
add(da[72].multiply(a[72])).
add(da[73].multiply(a[73])).
add(da[74].multiply(a74)).
add(da[75].multiply(a[75])).
add(da[76].multiply(a[76])).
add(da[77].multiply(a77)).
add(da[78].multiply(a78)).
add(da[79].multiply(a[79]));
// termes annuels symetriques en latitude
da[9] = zero.newInstance(FastMath.cos(ROT * (day - a[11])));
da[10] = p20.multiply(da[9]);
// termes semi-annuels symetriques en latitude
da[12] = zero.newInstance(FastMath.cos(ROT2 * (day - a[14])));
da[13] = p20.multiply(da[12]);
// termes annuels non symetriques en latitude
final double coste = FastMath.cos(ROT * (day - a[18]));
da[15] = p10.multiply(coste);
da[16] = p30.multiply(coste);
da[17] = p50.multiply(coste);
// terme semi-annuel non symetrique en latitude
final double cos2te = FastMath.cos(ROT2 * (day - a[20]));
da[19] = p10.multiply(cos2te);
da[39] = p30.multiply(cos2te);
da[59] = p50.multiply(cos2te);
// termes diurnes [et couples annuel]
da[21] = p11.multiply(ch);
da[22] = p31.multiply(ch);
da[23] = p51.multiply(ch);
da[24] = da[21].multiply(coste);
da[25] = p21.multiply(ch).multiply(coste);
da[26] = p11.multiply(sh);
da[27] = p31.multiply(sh);
da[28] = p51.multiply(sh);
da[29] = da[26].multiply(coste);
da[30] = p21.multiply(sh).multiply(coste);
// termes semi-diurnes [et couples annuel]
da[31] = p22.multiply(c2h);
da[37] = p42.multiply(c2h);
da[32] = p32.multiply(c2h).multiply(coste);
da[33] = p22.multiply(s2h);
da[38] = p42.multiply(s2h);
da[34] = p32.multiply(s2h).multiply(coste);
da[88] = p32.multiply(c2h);
da[89] = p32.multiply(s2h);
da[90] = p52.multiply(c2h);
da[91] = p52.multiply(s2h);
double a88 = a[88];
double a89 = a[89];
double a90 = a[90];
double a91 = a[91];
if (kle_eq == -1) { //hiver
a88 = -a88;
a89 = -a89;
a90 = -a90;
a91 = -a91;
}
if (kle_eq == 0) { //equinox
a88 = semestrialCorrection(a88);
a89 = semestrialCorrection(a89);
a90 = semestrialCorrection(a90);
a91 = semestrialCorrection(a91);
}
da[92] = p62.multiply(c2h);
da[93] = p62.multiply(s2h);
// termes ter-diurnes
da[35] = p33.multiply(c3h);
da[36] = p33.multiply(s3h);
// fonction g[l] periodique
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])).
add(da[15].multiply(a[15])).add(da[16].multiply(a[16])).add(da[17].multiply(a[17])).add(da[19].multiply(a[19])).
add(da[21].multiply(a[21])).add(da[22].multiply(a[22])).add(da[23].multiply(a[23])).add(da[24].multiply(a[24])).
add(da[25].multiply(a[25])).add(da[26].multiply(a[26])).add(da[27].multiply(a[27])).add(da[28].multiply(a[28])).
add(da[29].multiply(a[29])).add(da[30].multiply(a[30])).add(da[31].multiply(a[31])).add(da[32].multiply(a[32])).
add(da[33].multiply(a[33])).add(da[34].multiply(a[34])).add(da[35].multiply(a[35])).add(da[36].multiply(a[36])).
add(da[37].multiply(a[37])).add(da[38].multiply(a[38])).add(da[39].multiply(a[39])).add(da[59].multiply(a[59])).
add(da[88].multiply(a88)) .add(da[89].multiply(a89) ).add(da[90].multiply(a90) ).add(da[91].multiply(a91) ).
add(da[92].multiply(a[92])).add(da[93].multiply(a[93]));
// termes d'activite magnetique
da[40] = p10.multiply(coste).multiply(dkp);
da[41] = p30.multiply(coste).multiply(dkp);
da[42] = p50.multiply(coste).multiply(dkp);
da[43] = p11.multiply(ch).multiply(dkp);
da[44] = p31.multiply(ch).multiply(dkp);
da[45] = p51.multiply(ch).multiply(dkp);
da[46] = p11.multiply(sh).multiply(dkp);
da[47] = p31.multiply(sh).multiply(dkp);
da[48] = p51.multiply(sh).multiply(dkp);
// fonction g[l] periodique supplementaire
fp = fp.
add(da[40].multiply(a[40])).
add(da[41].multiply(a[41])).
add(da[42].multiply(a[42])).
add(da[43].multiply(a[43])).
add(da[44].multiply(a[44])).
add(da[45].multiply(a[45])).
add(da[46].multiply(a[46])).
add(da[47].multiply(a[47])).
add(da[48].multiply(a[48]));
dakp = p10.multiply(a[40]).add(p30.multiply(a[41])).add(p50.multiply(a[42])).multiply(coste).
add(p11.multiply(a[40]).add(p31.multiply(a[44])).add(p51.multiply(a[45])).multiply(ch)).
add(p11.multiply(a[46]).add(p31.multiply(a[47])).add(p51.multiply(a[48])).multiply(sh));
da[ikp] = da[ikp].add(dakp.multiply(akp[2]));
da[ikp + 1] = da[ikp].add(dakp.multiply(c2fi).multiply(akp[2]));
// termes de longitude
da[49] = p11.multiply(clfl);
da[50] = p21.multiply(clfl);
da[51] = p31.multiply(clfl);
da[52] = p41.multiply(clfl);
da[53] = p51.multiply(clfl);
da[54] = p11.multiply(slfl);
da[55] = p21.multiply(slfl);
da[56] = p31.multiply(slfl);
da[57] = p41.multiply(slfl);
da[58] = p51.multiply(slfl);
// fonction g[l] periodique supplementaire
fp = fp.
add(da[49].multiply(a[49])).
add(da[50].multiply(a[50])).
add(da[51].multiply(a[51])).
add(da[52].multiply(a[52])).
add(da[53].multiply(a[53])).
add(da[54].multiply(a[54])).
add(da[55].multiply(a[55])).
add(da[56].multiply(a[56])).
add(da[57].multiply(a[57])).
add(da[58].multiply(a[58]));
// fonction g(l) totale (couplage avec le flux)
return f0.add(fp.multiply(f1f));
}
/** Apply a correction coefficient to the given parameter.
* @param param the parameter to correct
* @return the corrected parameter
*/
private double semestrialCorrection(final double param) {
final int debeq_pr = 59;
final int debeq_au = 244;
final double result;
if (day >= 100) {
final double xmult = (day - debeq_au) / 40.0;
result = param - 2.0 * param * xmult;
} else {
final double xmult = (day - debeq_pr) / 40.0;
result = 2.0 * param * xmult - param;
}
return result;
}
}
}