NeQuickGalileo.java

  1. /* Copyright 2002-2025 Thales Alenia Space
  2.  * Licensed to CS Communication & Systèmes (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.models.earth.ionosphere.nequick;

  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.util.FastMath;
  20. import org.hipparchus.util.MathArrays;
  21. import org.orekit.annotation.DefaultDataContext;
  22. import org.orekit.bodies.FieldGeodeticPoint;
  23. import org.orekit.bodies.GeodeticPoint;
  24. import org.orekit.data.DataContext;
  25. import org.orekit.data.DataSource;
  26. import org.orekit.errors.OrekitException;
  27. import org.orekit.errors.OrekitMessages;
  28. import org.orekit.time.DateTimeComponents;
  29. import org.orekit.time.TimeScale;

  30. /** Galileo-specific version of NeQuick engine.
  31.  * @since 13.0
  32.  * @author Luc Maisonobe
  33.  */
  34. public class NeQuickGalileo extends NeQuickModel {

  35.     /** Starting number of points for integration. */
  36.     private static final int N_START = 8;

  37.     /** Broadcast ionization engine coefficients. */
  38.     private final double[] alpha;

  39.     /**
  40.      * Build a new instance.
  41.      *
  42.      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
  43.      *
  44.      * @param alpha effective ionisation level coefficients
  45.      * @see #NeQuickGalileo(double[], TimeScale)
  46.      */
  47.     @DefaultDataContext
  48.     public NeQuickGalileo(final double[] alpha) {
  49.         this(alpha, DataContext.getDefault().getTimeScales().getUTC());
  50.     }

  51.     /**
  52.      * Build a new instance of the Galileo version of the NeQuick-2 model.
  53.      * <p>
  54.      * The Galileo version uses a loose modip grid and 3 broadcast parameters to compute
  55.      * effective ionization level.
  56.      * </p>
  57.      * @param alpha broadcast effective ionisation level coefficients
  58.      * @param utc UTC time scale.
  59.      * @since 10.1
  60.      */
  61.     public NeQuickGalileo(final double[] alpha, final TimeScale utc) {
  62.         super(utc);
  63.         this.alpha = alpha.clone();
  64.     }

  65.     /** Get effective ionisation level coefficients.
  66.      * @return effective ionisation level coefficients
  67.      */
  68.     public double[] getAlpha() {
  69.         return alpha.clone();
  70.     }

  71.     /**
  72.      * Compute effective ionization level.
  73.      *
  74.      * @param modip modified dip latitude at receiver location
  75.      * @return effective ionization level (Az in Nequick Galileo, R12 in original Nequick ITU)
  76.      */
  77.     private double effectiveIonizationLevel(final double modip) {
  78.         // Particular condition (Eq. 17)
  79.         if (alpha[0] == 0.0 && alpha[1] == 0.0 && alpha[2] == 0.0) {
  80.             return 63.7;
  81.         } else {
  82.             // Az = a0 + modip * a1 + modip² * a2 (Eq. 18)
  83.             return FastMath.min(FastMath.max(alpha[0] + modip * (alpha[1] + modip * alpha[2]), 0.0), 400.0);
  84.         }
  85.     }

  86.     /**
  87.      * Compute effective ionization level.
  88.      *
  89.      * @param <T>   type of the field elements
  90.      * @param modip modified dip latitude at receiver location
  91.      * @return effective ionization level (Az in Nequick Galileo, R12 in original Nequick ITU)
  92.      */
  93.     private <T extends CalculusFieldElement<T>> T effectiveIonizationLevel(final T modip) {
  94.         // Particular condition (Eq. 17)
  95.         if (alpha[0] == 0.0 && alpha[1] == 0.0 && alpha[2] == 0.0) {
  96.             return modip.newInstance(63.7);
  97.         } else {
  98.             // Az = a0 + modip * a1 + modip² * a2 (Eq. 18)
  99.             return FastMath.min(FastMath.max(modip.multiply(alpha[2]).add(alpha[1]).multiply(modip).add(alpha[0]),
  100.                                              0.0),
  101.                                 400.0);
  102.         }
  103.     }

  104.     /** {@inheritDoc} */
  105.     @Override
  106.     double stec(final DateTimeComponents dateTime, final Ray ray) {

  107.         // Tolerance for the integration accuracy. Defined inside the reference document, section 2.5.8.1.
  108.         final double h1 = ray.getRecH();
  109.         final double tolerance;
  110.         if (h1 < 1000000.0) {
  111.             tolerance = 0.001;
  112.         } else {
  113.             tolerance = 0.01;
  114.         }

  115.         // Integration
  116.         int n = N_START;
  117.         final Segment seg1 = new Segment(n, ray, ray.getS1(), ray.getS2());
  118.         double gn1 = stecIntegration(dateTime, seg1);
  119.         n *= 2;
  120.         final Segment seg2 = new Segment(n, ray, ray.getS1(), ray.getS2());
  121.         double gn2 = stecIntegration(dateTime, seg2);

  122.         int count = 1;
  123.         while (FastMath.abs(gn2 - gn1) > tolerance * FastMath.abs(gn1) && count < 20) {
  124.             gn1 = gn2;
  125.             n *= 2;
  126.             final Segment seg = new Segment(n, ray, ray.getS1(), ray.getS2());
  127.             gn2 = stecIntegration(dateTime, seg);
  128.             count += 1;
  129.         }

  130.         // If count > 20 the integration did not converge
  131.         if (count == 20) {
  132.             throw new OrekitException(OrekitMessages.STEC_INTEGRATION_DID_NOT_CONVERGE);
  133.         }

  134.         // Eq. 202
  135.         return (gn2 + ((gn2 - gn1) / 15.0)) * 1.0e-16;

  136.     }

  137.     /** {@inheritDoc} */
  138.     @Override
  139.     <T extends CalculusFieldElement<T>> T stec(final DateTimeComponents dateTime, final FieldRay<T> ray) {

  140.         // Tolerance for the integration accuracy. Defined inside the reference document, section 2.5.8.1.
  141.         final T h1 = ray.getRecH();
  142.         final double tolerance;
  143.         if (h1.getReal() < 1000000.0) {
  144.             tolerance = 0.001;
  145.         } else {
  146.             tolerance = 0.01;
  147.         }

  148.         // Integration
  149.         int n = N_START;
  150.         final FieldSegment<T> seg1 = new FieldSegment<>(n, ray, ray.getS1(), ray.getS2());
  151.         T gn1 = stecIntegration(dateTime, seg1);
  152.         n *= 2;
  153.         final FieldSegment<T> seg2 = new FieldSegment<>(n, ray, ray.getS1(), ray.getS2());
  154.         T gn2 = stecIntegration(dateTime, seg2);

  155.         int count = 1;
  156.         while (FastMath.abs(gn2.subtract(gn1)).getReal() > FastMath.abs(gn1).multiply(tolerance).getReal() &&
  157.                count < 20) {
  158.             gn1 = gn2;
  159.             n *= 2;
  160.             final FieldSegment<T> seg = new FieldSegment<>(n, ray, ray.getS1(), ray.getS2());
  161.             gn2 = stecIntegration(dateTime, seg);
  162.             count += 1;
  163.         }

  164.         // If count > 20 the integration did not converge
  165.         if (count == 20) {
  166.             throw new OrekitException(OrekitMessages.STEC_INTEGRATION_DID_NOT_CONVERGE);
  167.         }

  168.         // Eq. 202
  169.         return gn2.add(gn2.subtract(gn1).divide(15.0)).multiply(1.0e-16);

  170.     }

  171.     /**
  172.      * This method performs the STEC integration.
  173.      *
  174.      * @param dateTime current date and time components
  175.      * @param seg      coordinates along the integration path
  176.      * @return result of the integration
  177.      */
  178.     private double stecIntegration(final DateTimeComponents dateTime, final Segment seg) {

  179.         // Compute electron density
  180.         double density = 0.0;
  181.         for (int i = 0; i < seg.getNbPoints(); i++) {
  182.             final GeodeticPoint gp = seg.getPoint(i);
  183.             final double modip = GalileoHolder.INSTANCE.computeMODIP(gp.getLatitude(), gp.getLongitude());
  184.             density += electronDensity(computeFourierTimeSeries(dateTime, effectiveIonizationLevel(modip)),
  185.                                        gp.getLatitude(), gp.getLongitude(), gp.getAltitude());
  186.         }

  187.         return 0.5 * seg.getInterval() * density;
  188.     }

  189.     /**
  190.      * This method performs the STEC integration.
  191.      *
  192.      * @param <T>      type of the elements
  193.      * @param dateTime current date and time components
  194.      * @param seg      coordinates along the integration path
  195.      * @return result of the integration
  196.      */
  197.     private <T extends CalculusFieldElement<T>> T stecIntegration(final DateTimeComponents dateTime,
  198.                                                               final FieldSegment<T> seg) {
  199.         // Compute electron density
  200.         T density = seg.getInterval().getField().getZero();
  201.         for (int i = 0; i < seg.getNbPoints(); i++) {
  202.             final FieldGeodeticPoint<T> gp = seg.getPoint(i);
  203.             final T modip = GalileoHolder.INSTANCE.computeMODIP(gp.getLatitude(), gp.getLongitude());
  204.             density = density.add(electronDensity(computeFourierTimeSeries(dateTime, effectiveIonizationLevel(modip)),
  205.                                                   gp.getLatitude(), gp.getLongitude(), gp.getAltitude()));
  206.         }

  207.         return seg.getInterval().multiply(density).multiply(0.5);
  208.     }

  209.     /** {@inheritDoc} */
  210.     @Override
  211.     protected double computeMODIP(final double latitude, final double longitude) {
  212.         return GalileoHolder.INSTANCE.computeMODIP(latitude, longitude);
  213.     }

  214.     /** {@inheritDoc} */
  215.     @Override
  216.     protected <T extends CalculusFieldElement<T>> T computeMODIP(final T latitude, final T longitude) {
  217.         return GalileoHolder.INSTANCE.computeMODIP(latitude, longitude);
  218.     }

  219.     /** {@inheritDoc} */
  220.     @Override
  221.     boolean applyChapmanParameters(final double hInKm) {
  222.         return hInKm < 100.0;
  223.     }

  224.     /** {@inheritDoc} */
  225.     @Override
  226.     double[] computeExponentialArguments(final double h, final NeQuickParameters parameters) {

  227.         // If h < 100km we use h = 100km as recommended in the reference document
  228.         // for equations 111 to 113
  229.         final double clippedH = FastMath.max(h, 100.0);

  230.         // Eq. 111 to 113
  231.         final double   exp       = clipExp(10.0 / (1.0 + FastMath.abs(clippedH - parameters.getHmF2())));
  232.         final double[] arguments = new double[3];
  233.         arguments[0] =  (clippedH - parameters.getHmF2()) / parameters.getB2Bot();
  234.         arguments[1] = ((clippedH - parameters.getHmF1()) / parameters.getBF1(h)) * exp;
  235.         arguments[2] = ((clippedH - parameters.getHmE())  / parameters.getBE(h))  * exp;
  236.         return arguments;

  237.     }

  238.     /** {@inheritDoc} */
  239.     @Override
  240.     <T extends CalculusFieldElement<T>> T[] computeExponentialArguments(final T h,
  241.                                                                         final FieldNeQuickParameters<T> parameters) {
  242.         // If h < 100km we use h = 100km as recommended in the reference document
  243.         // for equations 111 to 113
  244.         final T clippedH = FastMath.max(h, 100);

  245.         // Eq. 111 to 113
  246.         final T   exp   = clipExp(FastMath.abs(clippedH.subtract(parameters.getHmF2())).add(1.0).reciprocal().multiply(10.0));
  247.         final T[] arguments = MathArrays.buildArray(h.getField(), 3);
  248.         arguments[0] = clippedH.subtract(parameters.getHmF2()).divide(parameters.getB2Bot());
  249.         arguments[1] = clippedH.subtract(parameters.getHmF1()).divide(parameters.getBF1(h)).multiply(exp);
  250.         arguments[2] = clippedH.subtract(parameters.getHmE()).divide(parameters.getBE(h)).multiply(exp);
  251.         return arguments;

  252.     }

  253.     /** {@inheritDoc} */
  254.     @Override
  255.     double computeH0(final NeQuickParameters parameters) {

  256.         final int month = parameters.getDateTime().getDate().getMonth();

  257.         // Auxiliary parameter ka (Eq. 99 and 100)
  258.         final double ka;
  259.         if (month > 3 && month < 10) {
  260.             // month = 4,5,6,7,8,9
  261.             ka = 6.705 - 0.014 * parameters.getAzr() - 0.008 * parameters.getHmF2();
  262.         } else {
  263.             // month = 1,2,3,10,11,12
  264.             final double ratio = parameters.getHmF2() / parameters.getB2Bot();
  265.             ka = -7.77 + 0.097 * ratio * ratio + 0.153 * parameters.getNmF2();
  266.         }

  267.         // Auxiliary parameter kb (Eq. 101 and 102)
  268.         double kb = parameters.join(ka, 2.0, 1.0, ka - 2.0);
  269.         kb = parameters.join(8.0, kb, 1.0, kb - 8.0);

  270.         // Auxiliary parameter Ha (Eq. 103)
  271.         final double hA = kb * parameters.getB2Bot();

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

  275.         // Topside thickness parameter (Eq. 106)
  276.         return hA / v;

  277.     }

  278.     /** {@inheritDoc} */
  279.     @Override
  280.     <T extends CalculusFieldElement<T>> T computeH0(final FieldNeQuickParameters<T> parameters) {

  281.         final int month = parameters.getDateTime().getDate().getMonth();

  282.         // One
  283.         final T one = parameters.getAzr().getField().getOne();

  284.         // Auxiliary parameter ka (Eq. 99 and 100)
  285.         final T ka;
  286.         if (month > 3 && month < 10) {
  287.             // month = 4,5,6,7,8,9
  288.             ka = parameters.getAzr().multiply(0.014).add(parameters.getHmF2().multiply(0.008)).negate().add(6.705);
  289.         } else {
  290.             // month = 1,2,3,10,11,12
  291.             final T ratio = parameters.getHmF2().divide(parameters.getB2Bot());
  292.             ka = ratio.multiply(ratio).multiply(0.097).add(parameters.getNmF2().multiply(0.153)).add(-7.77);
  293.         }

  294.         // Auxiliary parameter kb (Eq. 101 and 102)
  295.         T kb = parameters.join(ka, one.newInstance(2.0), one, ka.subtract(2.0));
  296.         kb = parameters.join(one.newInstance(8.0), kb, one, kb.subtract(8.0));

  297.         // Auxiliary parameter Ha (Eq. 103)
  298.         final T hA = kb.multiply(parameters.getB2Bot());

  299.         // Auxiliary parameters x and v (Eq. 104 and 105)
  300.         final T x = hA.subtract(150.0).multiply(0.01);
  301.         final T v = x.multiply(0.041163).subtract(0.183981).multiply(x).add(1.424472);

  302.         // Topside thickness parameter (Eq. 106)
  303.         return hA.divide(v);

  304.     }

  305.     /** Holder for the Galileo-specific modip singleton.
  306.      * <p>
  307.      * We use the initialization on demand holder idiom to store the singleton,
  308.      * as it is both thread-safe, efficient (no synchronization) and works with
  309.      * all versions of java.
  310.      * </p>
  311.      */
  312.     private static class GalileoHolder {

  313.         /** Resource for modip grid. */
  314.         private static final String MODIP_GRID = "/assets/org/orekit/nequick/modipNeQG_wrapped.asc";

  315.         /** Unique instance. */
  316.         private static final ModipGrid INSTANCE =
  317.             new ModipGrid(36, 36,
  318.                           new DataSource(MODIP_GRID, () -> GalileoHolder.class.getResourceAsStream(MODIP_GRID)),
  319.                           true);

  320.         /** Private constructor.
  321.          * <p>This class is a utility class, it should neither have a public
  322.          * nor a default constructor. This private constructor prevents
  323.          * the compiler from generating one automatically.</p>
  324.          */
  325.         private GalileoHolder() {
  326.         }

  327.     }

  328. }