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.             final double az = effectiveIonizationLevel(modip);
  185.             density += electronDensity(dateTime, az, 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.             final T az = effectiveIonizationLevel(modip);
  205.             density = density.add(electronDensity(dateTime, az,
  206.                                                   gp.getLatitude(), gp.getLongitude(), gp.getAltitude()));
  207.         }

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

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

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

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

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

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

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

  238.     }

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

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

  253.     }

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

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

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

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

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

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

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

  278.     }

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

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

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

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

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

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

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

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

  305.     }

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

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

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

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

  328.     }

  329. }