NeQuickItu.java

  1. /* Copyright 2022-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.bodies.FieldGeodeticPoint;
  22. import org.orekit.bodies.GeodeticPoint;
  23. import org.orekit.data.DataSource;
  24. import org.orekit.time.DateTimeComponents;
  25. import org.orekit.time.TimeScale;
  26. import org.orekit.utils.units.Unit;

  27. /** Original model from Aeronomy and Radiopropagation Laboratory
  28.  * of the Abdus Salam International Centre for Theoretical Physics Trieste, Italy.
  29.  * <p>
  30.  * None of the code from Abdus Salam International Centre for Theoretical Physics Trieste
  31.  * has been used, the models have been reimplemented from scratch by the Orekit team.
  32.  * </p>
  33.  * @since 13.0
  34.  * @author Luc Maisonobe
  35.  */
  36. public class NeQuickItu extends NeQuickModel {

  37.     /** One thousand kilometer height. */
  38.     private static final double H_1000 = 1000000.0;

  39.     /** Two thousands kilometer height. */
  40.     private static final double H_2000 = 2000000.0;

  41.     /** H0 (km). */
  42.     private static final double H0 = 90.0;

  43.     /** HD (km). */
  44.     private static final double HD = 5.0;

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

  47.     /** Max number of points for integration. */
  48.     private static final int N_STOP = 1024;

  49.     /** Small convergence criterion. */
  50.     private static final double EPS_SMALL = 1.0e-3;

  51.     /** Medium convergence criterion. */
  52.     private static final double EPS_MEDIUM = 1.0e-2;

  53.     /** Solar flux. */
  54.     private final double f107;

  55.     /** Build a new instance.
  56.      * @param f107 solar flux
  57.      * @param utc UTC time scale
  58.      */
  59.     public NeQuickItu(final double f107, final TimeScale utc) {
  60.         super(utc);
  61.         this.f107 = f107;
  62.     }

  63.     /** Get solar flux.
  64.      * @return solar flux
  65.      */
  66.     public double getF107() {
  67.         return f107;
  68.     }

  69.     /** {@inheritDoc} */
  70.     @Override
  71.     double stec(final DateTimeComponents dateTime, final Ray ray) {
  72.         if (ray.getSatH() <= H_2000) {
  73.             if (ray.getRecH() >= H_1000) {
  74.                 // only one integration interval
  75.                 return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), ray.getS2());
  76.             } else {
  77.                 // two integration intervals, below and above 1000km
  78.                 final double h1000 = NeQuickModel.RE + H_1000;
  79.                 final double s1000 = FastMath.sqrt(h1000 * h1000 - ray.getRadius() * ray.getRadius());
  80.                 return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), s1000) +
  81.                        stecIntegration(dateTime, EPS_MEDIUM, ray, s1000, ray.getS2());
  82.             }
  83.         } else {
  84.             if (ray.getRecH() >= H_2000) {
  85.                 // only one integration interval
  86.                 return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), ray.getS2());
  87.             } else {
  88.                 final double h2000 = NeQuickModel.RE + H_2000;
  89.                 final double s2000 = FastMath.sqrt(h2000 * h2000 - ray.getRadius() * ray.getRadius());
  90.                 if (ray.getRecH() >= H_1000) {
  91.                     // two integration intervals, below and above 2000km
  92.                     return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), s2000) +
  93.                            stecIntegration(dateTime, EPS_SMALL, ray, s2000, ray.getS2());
  94.                 } else {
  95.                     // three integration intervals, below 1000km, between 1000km and 2000km, and above 2000km
  96.                     final double h1000 = NeQuickModel.RE + H_1000;
  97.                     final double s1000 = FastMath.sqrt(h1000 * h1000 - ray.getRadius() * ray.getRadius());
  98.                     return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), s1000) +
  99.                            stecIntegration(dateTime, EPS_MEDIUM, ray, s1000, s2000) +
  100.                            stecIntegration(dateTime, EPS_MEDIUM, ray, s2000, ray.getS2());
  101.                 }
  102.             }
  103.         }
  104.     }

  105.     /** {@inheritDoc} */
  106.     @Override
  107.     <T extends CalculusFieldElement<T>> T stec(final DateTimeComponents dateTime, final FieldRay<T> ray) {
  108.         if (ray.getSatH().getReal() <= H_2000) {
  109.             if (ray.getRecH().getReal() >= H_1000) {
  110.                 // only one integration interval
  111.                 return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), ray.getS2());
  112.             } else {
  113.                 // two integration intervals, below and above 1000km
  114.                 final double h1000 = NeQuickModel.RE + H_1000;
  115.                 final T s1000 = FastMath.sqrt(ray.getRadius().multiply(ray.getRadius()).negate().add(h1000 * h1000));
  116.                 return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), s1000).
  117.                        add(stecIntegration(dateTime, EPS_MEDIUM, ray, s1000, ray.getS2()));
  118.             }
  119.         } else {
  120.             if (ray.getRecH().getReal() >= H_2000) {
  121.                 // only one integration interval
  122.                 return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), ray.getS2());
  123.             } else {
  124.                 final double h2000 = NeQuickModel.RE + H_2000;
  125.                 final T s2000 = FastMath.sqrt(ray.getRadius().multiply(ray.getRadius()).negate().add(h2000 * h2000));
  126.                 if (ray.getRecH().getReal() >= H_1000) {
  127.                     // two integration intervals, below and above 2000km
  128.                     return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), s2000).
  129.                            add(stecIntegration(dateTime, EPS_SMALL, ray, s2000, ray.getS2()));
  130.                 } else {
  131.                     // three integration intervals, below 1000km, between 1000km and 2000km, and above 2000km
  132.                     final double h1000 = NeQuickModel.RE + H_1000;
  133.                     final T s1000 = FastMath.sqrt(ray.getRadius().multiply(ray.getRadius()).negate().add(h1000 * h1000));
  134.                     return stecIntegration(dateTime, EPS_SMALL, ray, ray.getS1(), s1000).
  135.                            add(stecIntegration(dateTime, EPS_MEDIUM, ray, s1000, s2000)).
  136.                            add(stecIntegration(dateTime, EPS_MEDIUM, ray, s2000, ray.getS2()));
  137.                 }
  138.             }
  139.         }
  140.     }

  141.     /**
  142.      * This method performs the STEC integration.
  143.      *
  144.      * @param dateTime current date and time components
  145.      * @param eps convergence criterion
  146.      * @param ray ray-perigee parameters
  147.      * @param s1  lower boundary of integration
  148.      * @param s2  upper boundary for integration
  149.      * @return result of the integration
  150.      */
  151.     private double stecIntegration(final DateTimeComponents dateTime, final double eps, final Ray ray, final double s1,
  152.                                    final double s2) {

  153.         double gInt1 = Double.NaN;
  154.         double gInt2 = Double.NaN;

  155.         for (int n = N_START; n <= N_STOP; n = 2 * n) {

  156.             // integrate with n intervals (2n points)
  157.             final Segment segment = new Segment(n, ray, s1, s2);
  158.             double sum = 0;
  159.             for (int i = 0; i < segment.getNbPoints(); ++i) {
  160.                 final GeodeticPoint gp = segment.getPoint(i);
  161.                 final double ed = electronDensity(dateTime, f107,
  162.                                                   gp.getLatitude(), gp.getLongitude(), gp.getAltitude());
  163.                 sum += ed;
  164.             }

  165.             gInt1 = gInt2;
  166.             gInt2 = sum * 0.5 * segment.getInterval();
  167.             if (FastMath.abs(gInt1 - gInt2) <= FastMath.abs(gInt1 * eps)) {
  168.                 // convergence reached
  169.                 break;
  170.             }

  171.         }

  172.         return Unit.TOTAL_ELECTRON_CONTENT_UNIT.fromSI(gInt2 + (gInt2 - gInt1) / 15.0);

  173.     }

  174.     /**
  175.      * This method performs the STEC integration.
  176.      *
  177.      * @param <T> type of the field elements
  178.      * @param dateTime current date and time components
  179.      * @param eps convergence criterion
  180.      * @param ray ray-perigee parameters
  181.      * @param s1  lower boundary of integration
  182.      * @param s2  upper boundary for integration
  183.      * @return result of the integration
  184.      */
  185.     private <T extends CalculusFieldElement<T>> T stecIntegration(final DateTimeComponents dateTime,
  186.                                                                   final double eps,
  187.                                                                   final FieldRay<T> ray, final T s1, final T s2) {

  188.         T gInt1 = s1.newInstance(Double.NaN);
  189.         T gInt2 = s1.newInstance(Double.NaN);
  190.         final T f107T = s1.newInstance(f107);

  191.         for (int n = N_START; n <= N_STOP; n = 2 * n) {

  192.             // integrate with n intervals (2n points)
  193.             final FieldSegment<T> segment = new FieldSegment<>(n, ray, s1, s2);
  194.             T sum = s1.getField().getZero();
  195.             for (int i = 0; i < segment.getNbPoints(); ++i) {
  196.                 final FieldGeodeticPoint<T> gp = segment.getPoint(i);
  197.                 final T ed =  electronDensity(dateTime, f107T,
  198.                                               gp.getLatitude(), gp.getLongitude(), gp.getAltitude());
  199.                 sum = sum.add(ed);
  200.             }

  201.             gInt1 = gInt2;
  202.             gInt2 = sum.multiply(0.5).multiply(segment.getInterval());
  203.             if (FastMath.abs(gInt1.subtract(gInt2).getReal()) <= FastMath.abs(gInt1.getReal() * eps)) {
  204.                 // convergence reached
  205.                 break;
  206.             }

  207.         }

  208.         return Unit.TOTAL_ELECTRON_CONTENT_UNIT.fromSI(gInt2.add(gInt2.subtract(gInt1).divide(15.0)));

  209.     }

  210.     /** {@inheritDoc} */
  211.     @Override
  212.     protected double computeMODIP(final double latitude, final double longitude) {
  213.         return ItuHolder.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 ItuHolder.INSTANCE.computeMODIP(latitude, longitude);
  219.     }

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

  225.     /** {@inheritDoc} */
  226.     @Override
  227.     double[] computeExponentialArguments(final double h, final NeQuickParameters parameters) {
  228.         final double   exp   = clipExp(10.0 / (1.0 + FastMath.abs(h - parameters.getHmF2())));
  229.         final double[] arguments = new double[3];
  230.         arguments[0] = fixLowHArg( (h - parameters.getHmF2()) / parameters.getB2Bot(), h);
  231.         arguments[1] = fixLowHArg(((h - parameters.getHmF1()) / parameters.getBF1(h)) * exp, h);
  232.         arguments[2] = fixLowHArg(((h - parameters.getHmE())  / parameters.getBE(h))  * exp, h);
  233.         return arguments;
  234.     }

  235.     /** {@inheritDoc} */
  236.     @Override
  237.     <T extends CalculusFieldElement<T>> T[] computeExponentialArguments(final T h,
  238.                                                                         final FieldNeQuickParameters<T> parameters) {
  239.         final T   exp   = clipExp(FastMath.abs(h.subtract(parameters.getHmF2())).add(1.0).reciprocal().multiply(10.0));
  240.         final T[] arguments = MathArrays.buildArray(h.getField(), 3);
  241.         arguments[0] = fixLowHArg(h.subtract(parameters.getHmF2()).divide(parameters.getB2Bot()), h);
  242.         arguments[1] = fixLowHArg(h.subtract(parameters.getHmF1()).divide(parameters.getBF1(h)).multiply(exp), h);
  243.         arguments[2] = fixLowHArg(h.subtract(parameters.getHmE()).divide(parameters.getBE(h)).multiply(exp), h);
  244.         return arguments;
  245.     }

  246.     /**
  247.      * Fix arguments for low altitudes.
  248.      * @param arg argument of the exponential
  249.      * @param h height in km
  250.      * @return fixed argument
  251.      * @since 13.0
  252.      */
  253.     private double fixLowHArg(final double arg, final double h) {
  254.         return h < H0 ? arg * (HD + H0 - h) / HD : arg;
  255.     }

  256.     /**
  257.      * Fix arguments for low altitudes.
  258.      * @param <T> type of the field elements
  259.      * @param arg argument of the exponential
  260.      * @param h height in km
  261.      * @return fixed argument
  262.      * @since 13.0
  263.      */
  264.     private <T extends CalculusFieldElement<T>> T fixLowHArg(final T arg, final T h) {
  265.         return h.getReal() < H0 ? arg.multiply(h.negate().add(HD + H0)).divide(HD) : arg;
  266.     }

  267.     /** {@inheritDoc} */
  268.     @Override
  269.     double computeH0(final NeQuickParameters parameters) {
  270.         final double b2k = -0.0538  * parameters.getFoF2() -
  271.                             0.00664 * parameters.getHmF2() +
  272.                             0.113   * parameters.getHmF2() / parameters.getB2Bot() +
  273.                             0.00257 * parameters.getAzr() +
  274.                             3.22;
  275.         return parameters.getB2Bot() * parameters.join(b2k, 1.0, 2.0, b2k - 1.0);
  276.     }

  277.     /** {@inheritDoc} */
  278.     @Override
  279.     <T extends CalculusFieldElement<T>> T computeH0(final FieldNeQuickParameters<T> parameters) {
  280.         final T b2k = parameters.getFoF2().multiply(-0.0538).
  281.                       subtract(parameters.getHmF2().multiply(0.00664)).
  282.                       add(parameters.getHmF2().multiply(0.113).divide(parameters.getB2Bot())).
  283.                       add(parameters.getAzr().multiply(0.00257)).
  284.                       add (3.22);
  285.         return parameters.getB2Bot().
  286.                multiply(parameters.join(b2k, b2k.newInstance(1.0), b2k.newInstance(2.0), b2k.subtract(1.0)));
  287.     }

  288.     /** Holder for the ITU modip singleton.
  289.      * <p>
  290.      * We use the initialization on demand holder idiom to store the singleton,
  291.      * as it is both thread-safe, efficient (no synchronization) and works with
  292.      * all versions of java.
  293.      * </p>
  294.      */
  295.     private static class ItuHolder {

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

  298.         /** Unique instance. */
  299.         private static final ModipGrid INSTANCE =
  300.             new ModipGrid(180, 180,
  301.                           new DataSource(MODIP_GRID, () -> ItuHolder.class.getResourceAsStream(MODIP_GRID)),
  302.                           false);

  303.         /** Private constructor.
  304.          * <p>This class is a utility class, it should neither have a public
  305.          * nor a default constructor. This private constructor prevents
  306.          * the compiler from generating one automatically.</p>
  307.          */
  308.         private ItuHolder() {
  309.         }

  310.     }

  311. }