MarshallSolarActivityFutureEstimation.java

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

  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.stream.Collectors;

  21. import org.hipparchus.analysis.UnivariateFunction;
  22. import org.hipparchus.analysis.interpolation.LinearInterpolator;
  23. import org.hipparchus.util.FastMath;
  24. import org.orekit.annotation.DefaultDataContext;
  25. import org.orekit.data.DataContext;
  26. import org.orekit.data.DataProvidersManager;
  27. import org.orekit.data.DataSource;
  28. import org.orekit.models.earth.atmosphere.data.MarshallSolarActivityFutureEstimationLoader.LineParameters;
  29. import org.orekit.time.AbsoluteDate;
  30. import org.orekit.time.DateComponents;
  31. import org.orekit.time.DateTimeComponents;
  32. import org.orekit.time.TimeComponents;
  33. import org.orekit.time.TimeScale;
  34. import org.orekit.time.TimeStampedDouble;
  35. import org.orekit.utils.Constants;
  36. import org.orekit.utils.GenericTimeStampedCache;
  37. import org.orekit.utils.OrekitConfiguration;
  38. import org.orekit.utils.TimeStampedGenerator;

  39. /**
  40.  * This class provides solar activity data needed by atmospheric models: F107 solar flux, Ap and Kp indexes.
  41.  * <p>
  42.  * Data comes from the NASA Marshall Solar Activity Future Estimation (MSAFE) as estimates of monthly F10.7
  43.  * Mean solar flux and Ap geomagnetic parameter
  44.  * (see <a href="https://www.nasa.gov/solar-cycle-progression-and-forecast"> Marshall Solar Activity website</a>).
  45.  *
  46.  * <p>Data can be retrieved at the NASA
  47.  * <a href="https://www.nasa.gov/solar-cycle-progression-and-forecast/archived-forecast/"> Marshall Solar Activity archived forecast</a>.
  48.  * Here Kp indices are deduced from Ap indexes, which in turn are tabulated equivalent of retrieved Ap values.
  49.  *
  50.  * <p> If several MSAFE files are available, some dates may appear in several files (for example August 2007 is in all files from
  51.  * the first one published in March 1999 to the February 2008 file). In this case, the data from the most recent file is used
  52.  * and the older ones are discarded. The date of the file is assumed to be 6 months after its first entry (which explains why
  53.  * the file having August 2007 as its first entry is the February 2008 file). This implies that MSAFE files must <em>not</em>
  54.  * be edited to change their time span, otherwise this would break the old entries overriding mechanism.
  55.  *
  56.  * <p>With these data, the {@link #getInstantFlux(AbsoluteDate)} and {@link #getMeanFlux(AbsoluteDate)} methods return the same
  57.  * values and the {@link #get24HoursKp(AbsoluteDate)} and {@link #getThreeHourlyKP(AbsoluteDate)} methods return the same
  58.  * values.
  59.  *
  60.  * <p>Conversion from Ap index values in the MSAFE file to Kp values used by atmosphere models is done using Jacchia's equation
  61.  * in [1].
  62.  *
  63.  * <p>With these data, the {@link #getAp(AbsoluteDate date)} method returns an array of seven times the same daily Ap value,
  64.  * i.e. it is suited to be used only with the {@link org.orekit.models.earth.atmosphere.NRLMSISE00 NRLMSISE00} atmospheric
  65.  * model where the switch #9 is set to 1.
  66.  *
  67.  * <h2>References</h2>
  68.  *
  69.  * <ol> <li> Jacchia, L. G. "CIRA 1972, recent atmospheric models, and improvements in
  70.  * progress." COSPAR, 21st Plenary Meeting. Vol. 1. 1978. </li> </ol>
  71.  *
  72.  * @author Bruno Revelin
  73.  * @author Luc Maisonobe
  74.  * @author Evan Ward
  75.  * @author Pascal Parraud
  76.  * @author Vincent Cucchietti
  77.  */
  78. public class MarshallSolarActivityFutureEstimation
  79.         extends AbstractSolarActivityData<LineParameters, MarshallSolarActivityFutureEstimationLoader> {

  80.     /**
  81.      * Default regular expression for the supported name that work with all officially published files.
  82.      *
  83.      * @since 10.0
  84.      */
  85.     public static final String DEFAULT_SUPPORTED_NAMES =
  86.             "\\p{Alpha}\\p{Lower}\\p{Lower}\\p{Digit}\\p{Digit}\\p{Digit}\\p{Digit}(?:f|F)10(?:[-_]prd)?\\.(?:txt|TXT)";

  87.     /** Serializable UID. */
  88.     private static final long serialVersionUID = -5212198874900835369L;

  89.     /** Selected strength level of activity. */
  90.     private final StrengthLevel strengthLevel;

  91.     /** Cache dedicated to average flux. */
  92.     private final transient GenericTimeStampedCache<TimeStampedDouble> averageFluxCache;

  93.     /**
  94.      * Simple constructor. This constructor uses the {@link DataContext#getDefault() default data context}.
  95.      * <p>
  96.      * The original file names used by NASA Marshall space center are of the form: may2019f10_prd.txt or Oct1999F10.TXT. So a
  97.      * recommended regular expression for the supported name that work with all published files is:
  98.      * {@link #DEFAULT_SUPPORTED_NAMES}.
  99.      * <p>
  100.      * It provides a default configuration for the thread safe cache :
  101.      * <ul>
  102.      *     <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
  103.      *     <li>Max span : {@code Constants.JULIAN_YEAR}</li>
  104.      *     <li>Max span interval : {@code 0}</li>
  105.      * </ul>
  106.      *
  107.      * @param supportedNames regular expression for supported files names
  108.      * @param strengthLevel selected strength level of activity
  109.      *
  110.      * @see #MarshallSolarActivityFutureEstimation(String, StrengthLevel, DataProvidersManager, TimeScale)
  111.      */
  112.     @DefaultDataContext
  113.     public MarshallSolarActivityFutureEstimation(final String supportedNames,
  114.                                                  final StrengthLevel strengthLevel) {
  115.         this(supportedNames, strengthLevel,
  116.              DataContext.getDefault().getDataProvidersManager(),
  117.              DataContext.getDefault().getTimeScales().getUTC());
  118.     }

  119.     /**
  120.      * Constructor that allows specifying the source of the MSAFE auxiliary data files.
  121.      * <p>
  122.      * It provides a default configuration for the thread safe cache :
  123.      * <ul>
  124.      *     <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
  125.      *     <li>Max span : {@code 31 * Constants.JULIAN_DAY}</li>
  126.      *     <li>Max interval : {@code 0}</li>
  127.      *     <li>Minimum step: {@code 27 * Constants.JULIAN_DAY}</li>
  128.      * </ul>
  129.      *
  130.      * @param supportedNames regular expression for supported files names
  131.      * @param strengthLevel selected strength level of activity
  132.      * @param dataProvidersManager provides access to auxiliary data files.
  133.      * @param utc UTC time scale.
  134.      *
  135.      * @since 10.1
  136.      */
  137.     public MarshallSolarActivityFutureEstimation(final String supportedNames,
  138.                                                  final StrengthLevel strengthLevel,
  139.                                                  final DataProvidersManager dataProvidersManager,
  140.                                                  final TimeScale utc) {
  141.         this(supportedNames, strengthLevel, dataProvidersManager, utc, OrekitConfiguration.getCacheSlotsNumber(),
  142.              Constants.JULIAN_DAY * 31, 0, Constants.JULIAN_DAY * 27);
  143.     }

  144.     /**
  145.      * Constructor that allows specifying the source of the MSAFE auxiliary data files and customizable thread safe cache
  146.      * configuration.
  147.      *
  148.      * @param supportedNames regular expression for supported files names
  149.      * @param strengthLevel selected strength level of activity
  150.      * @param dataProvidersManager provides access to auxiliary data files.
  151.      * @param utc UTC time scale.
  152.      * @param maxSlots maximum number of independent cached time slots in the
  153.      * {@link GenericTimeStampedCache time-stamped cache}
  154.      * @param maxSpan maximum duration span in seconds of one slot in the {@link GenericTimeStampedCache time-stamped cache}
  155.      * @param maxInterval time interval above which a new slot is created in the
  156.      * {@link GenericTimeStampedCache time-stamped cache}
  157.      * @param minimumStep overriding minimum step designed for non-homogeneous tabulated values. To be used for example when
  158.      * caching monthly tabulated values. May be null.
  159.      *
  160.      * @since 10.1
  161.      */
  162.     public MarshallSolarActivityFutureEstimation(final String supportedNames,
  163.                                                  final StrengthLevel strengthLevel,
  164.                                                  final DataProvidersManager dataProvidersManager,
  165.                                                  final TimeScale utc,
  166.                                                  final int maxSlots,
  167.                                                  final double maxSpan,
  168.                                                  final double maxInterval,
  169.                                                  final double minimumStep) {
  170.         super(supportedNames, new MarshallSolarActivityFutureEstimationLoader(strengthLevel, utc),
  171.               dataProvidersManager, utc, maxSlots, maxSpan, maxInterval, minimumStep);

  172.         // Initialise fields
  173.         this.strengthLevel    = strengthLevel;
  174.         this.averageFluxCache = new GenericTimeStampedCache<>(N_NEIGHBORS, OrekitConfiguration.getCacheSlotsNumber(),
  175.                                                               Constants.JULIAN_DAY, 0, new AverageFluxGenerator());
  176.     }

  177.     /**
  178.      * Simple constructor which use the {@link DataContext#getDefault() default data context}.
  179.      * <p>
  180.      * It provides a default configuration for the thread safe cache :
  181.      * <ul>
  182.      *     <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
  183.      *     <li>Max span : {@code 31 * Constants.JULIAN_DAY}</li>
  184.      *     <li>Max interval : {@code 0}</li>
  185.      *     <li>Minimum step: {@code 27 * Constants.JULIAN_DAY}</li>
  186.      * </ul>
  187.      *
  188.      * @param source source for the data
  189.      * @param strengthLevel selected strength level of activity
  190.      *
  191.      * @since 12.0
  192.      */
  193.     @DefaultDataContext
  194.     public MarshallSolarActivityFutureEstimation(final DataSource source,
  195.                                                  final StrengthLevel strengthLevel) {
  196.         this(source, strengthLevel, DataContext.getDefault().getTimeScales().getUTC());
  197.     }

  198.     /**
  199.      * Simple constructor.
  200.      * <p>
  201.      * It provides a default configuration for the thread safe cache :
  202.      * <ul>
  203.      *     <li>Number of slots : {@code OrekitConfiguration.getCacheSlotsNumber()}</li>
  204.      *     <li>Max span : {@code 31 * Constants.JULIAN_DAY}</li>
  205.      *     <li>Max interval : {@code 0}</li>
  206.      *     <li>Minimum step: {@code 27 * Constants.JULIAN_DAY}</li>
  207.      * </ul>
  208.      *
  209.      * @param source source for the data
  210.      * @param strengthLevel selected strength level of activity
  211.      * @param utc UTC time scale
  212.      *
  213.      * @since 12.0
  214.      */
  215.     public MarshallSolarActivityFutureEstimation(final DataSource source,
  216.                                                  final StrengthLevel strengthLevel,
  217.                                                  final TimeScale utc) {
  218.         this(source, strengthLevel, utc, OrekitConfiguration.getCacheSlotsNumber(),
  219.              Constants.JULIAN_DAY * 31, 0, Constants.JULIAN_DAY * 27);
  220.     }

  221.     /**
  222.      * Constructor with customizable thread safe cache configuration.
  223.      *
  224.      * @param source source for the data
  225.      * @param strengthLevel selected strength level of activity
  226.      * @param utc UTC time scale
  227.      * @param maxSlots maximum number of independent cached time slots in the
  228.      * {@link GenericTimeStampedCache time-stamped cache}
  229.      * @param maxSpan maximum duration span in seconds of one slot in the {@link GenericTimeStampedCache time-stamped cache}
  230.      * @param maxInterval time interval above which a new slot is created in the
  231.      * {@link GenericTimeStampedCache time-stamped cache}
  232.      * @param minimumStep overriding minimum step designed for non-homogeneous tabulated values. To be used for example when
  233.      * caching monthly tabulated values. Use {@code Double.NaN} otherwise.
  234.      *
  235.      * @since 12.0
  236.      */
  237.     public MarshallSolarActivityFutureEstimation(final DataSource source,
  238.                                                  final StrengthLevel strengthLevel,
  239.                                                  final TimeScale utc,
  240.                                                  final int maxSlots,
  241.                                                  final double maxSpan,
  242.                                                  final double maxInterval,
  243.                                                  final double minimumStep) {
  244.         super(source, new MarshallSolarActivityFutureEstimationLoader(strengthLevel, utc), utc,
  245.               maxSlots, maxSpan, maxInterval, minimumStep);
  246.         this.strengthLevel    = strengthLevel;
  247.         this.averageFluxCache = new GenericTimeStampedCache<>(N_NEIGHBORS, OrekitConfiguration.getCacheSlotsNumber(),
  248.                                                               Constants.JULIAN_DAY, 0, new AverageFluxGenerator());
  249.     }

  250.     /** {@inheritDoc} */
  251.     public double getInstantFlux(final AbsoluteDate date) {
  252.         return getMeanFlux(date);
  253.     }

  254.     /** {@inheritDoc} */
  255.     public double getMeanFlux(final AbsoluteDate date) {
  256.         return getLinearInterpolation(date, LineParameters::getF107);
  257.     }

  258.     /** {@inheritDoc} */
  259.     public double getThreeHourlyKP(final AbsoluteDate date) {
  260.         return get24HoursKp(date);
  261.     }

  262.     /**
  263.      * Get the date of the file from which data at the specified date comes from.
  264.      * <p>
  265.      * If several MSAFE files are available, some dates may appear in several files (for example August 2007 is in all files
  266.      * from the first one published in March 1999 to the February 2008 file). In this case, the data from the most recent
  267.      * file is used and the older ones are discarded. The date of the file is assumed to be 6 months after its first entry
  268.      * (which explains why the file having August 2007 as its first entry is the February 2008 file). This implies that MSAFE
  269.      * files must <em>not</em> be edited to change their time span, otherwise this would break the old entries overriding
  270.      * mechanism.
  271.      * </p>
  272.      *
  273.      * @param date date of the solar activity data
  274.      *
  275.      * @return date of the file
  276.      */
  277.     public DateComponents getFileDate(final AbsoluteDate date) {
  278.         // Get the neighboring solar activity
  279.         final LocalSolarActivity localSolarActivity = new LocalSolarActivity(date);
  280.         final LineParameters     previousParam      = localSolarActivity.getPreviousParam();
  281.         final LineParameters     currentParam       = localSolarActivity.getNextParam();

  282.         // Choose which file date to return
  283.         final double dtP = date.durationFrom(previousParam.getDate());
  284.         final double dtC = currentParam.getDate().durationFrom(date);
  285.         return (dtP < dtC) ? previousParam.getFileDate() : currentParam.getFileDate();
  286.     }

  287.     /**
  288.      * The Kp index is derived from the Ap index.
  289.      * <p>The method used is explained on <a
  290.      * href="http://www.ngdc.noaa.gov/stp/GEOMAG/kp_ap.html"> NOAA website.</a> as follows:</p>
  291.      * <p>The scale is 0 to 9 expressed in thirds of a unit, e.g. 5- is 4 2/3,
  292.      * 5 is 5 and 5+ is 5 1/3. The ap (equivalent range) index is derived from the Kp index as follows:</p>
  293.      * <table border="1">
  294.      * <caption>Kp / Ap Conversion Table</caption>
  295.      * <tbody>
  296.      * <tr>
  297.      * <td>Kp</td><td>0o</td><td>0+</td><td>1-</td><td>1o</td><td>1+</td><td>2-</td><td>2o</td><td>2+</td><td>3-</td><td>3o</td><td>3+</td><td>4-</td><td>4o</td><td>4+</td>
  298.      * </tr>
  299.      * <tr>
  300.      * <td>ap</td><td>0</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>9</td><td>12</td><td>15</td><td>18</td><td>22</td><td>27</td><td>32</td>
  301.      * </tr>
  302.      * <tr>
  303.      * <td>Kp</td><td>5-</td><td>5o</td><td>5+</td><td>6-</td><td>6o</td><td>6+</td><td>7-</td><td>7o</td><td>7+</td><td>8-</td><td>8o</td><td>8+</td><td>9-</td><td>9o</td>
  304.      * </tr>
  305.      * <tr>
  306.      * <td>ap</td><td>39</td><td>48</td><td>56</td><td>67</td><td>80</td><td>94</td><td>111</td><td>132</td><td>154</td><td>179</td><td>207</td><td>236</td><td>300</td><td>400</td>
  307.      * </tr>
  308.      * </tbody>
  309.      * </table>
  310.      *
  311.      * @param date date of the Kp data
  312.      *
  313.      * @return the 24H geomagnetic index
  314.      */
  315.     public double get24HoursKp(final AbsoluteDate date) {
  316.         // get the daily Ap
  317.         final double ap = getDailyAp(date);

  318.         // get the corresponding Kp index from
  319.         // equation 4 in [1] for Ap to Kp conversion
  320.         return 1.89 * FastMath.asinh(0.154 * ap);
  321.     }

  322.     /** {@inheritDoc} */
  323.     public double getDailyFlux(final AbsoluteDate date) {
  324.         return getMeanFlux(date.shiftedBy(-Constants.JULIAN_DAY));
  325.     }

  326.     public double getAverageFlux(final AbsoluteDate date) {
  327.         // Extract closest neighbours average
  328.         final List<TimeStampedDouble> neighbors = averageFluxCache.getNeighbors(date).collect(Collectors.toList());

  329.         // Create linear interpolating function
  330.         final double[] x = new double[] { 0, 1 };
  331.         final double[] y = neighbors.stream().map(TimeStampedDouble::getValue).mapToDouble(Double::doubleValue).toArray();

  332.         final LinearInterpolator interpolator          = new LinearInterpolator();
  333.         final UnivariateFunction interpolatingFunction = interpolator.interpolate(x, y);

  334.         // Interpolate
  335.         final AbsoluteDate previousDate = neighbors.get(0).getDate();
  336.         final AbsoluteDate nextDate     = neighbors.get(1).getDate();
  337.         return interpolatingFunction.value(date.durationFrom(previousDate) / nextDate.durationFrom(previousDate));
  338.     }

  339.     /** {@inheritDoc} */
  340.     public double[] getAp(final AbsoluteDate date) {
  341.         // Gets the AP for the current date
  342.         final double ap = getDailyAp(date);

  343.         // Retuns an array of Ap filled in with the daily Ap only
  344.         return new double[] { ap, ap, ap, ap, ap, ap, ap };
  345.     }

  346.     /**
  347.      * Gets the daily Ap index.
  348.      *
  349.      * @param date the current date
  350.      *
  351.      * @return the daily Ap index
  352.      */
  353.     private double getDailyAp(final AbsoluteDate date) {
  354.         return getLinearInterpolation(date, LineParameters::getAp);
  355.     }

  356.     /**
  357.      * Get the strength level for activity.
  358.      *
  359.      * @return strength level to set
  360.      */
  361.     public StrengthLevel getStrengthLevel() {
  362.         return strengthLevel;
  363.     }

  364.     /** Strength level of activity. */
  365.     public enum StrengthLevel {

  366.         /** Strong level of activity. */
  367.         STRONG,

  368.         /** Average level of activity. */
  369.         AVERAGE,

  370.         /** Weak level of activity. */
  371.         WEAK

  372.     }

  373.     /** Generator generating average flux data between given dates. */
  374.     private class AverageFluxGenerator implements TimeStampedGenerator<TimeStampedDouble> {

  375.         /** {@inheritDoc} */
  376.         @Override
  377.         public List<TimeStampedDouble> generate(final AbsoluteDate existingDate, final AbsoluteDate date) {
  378.             // No prior data in the cache
  379.             if (existingDate == null) {
  380.                 return generateDataFromEarliestToLatestDates(getCurrentDay(date), getNextDay(date));
  381.             }
  382.             // Prior data in the cache, fill with data between date and existing date
  383.             if (date.isBefore(existingDate)) {
  384.                 return generateDataFromEarliestToLatestDates(date, existingDate);
  385.             }
  386.             return generateDataFromEarliestToLatestDates(existingDate, date);
  387.         }

  388.         /**
  389.          * Generate data from earliest to latest date.
  390.          *
  391.          * @param earliest earliest date
  392.          * @param latest latest date
  393.          *
  394.          * @return data generated from earliest to latest date
  395.          */
  396.         private List<TimeStampedDouble> generateDataFromEarliestToLatestDates(final AbsoluteDate earliest,
  397.                                                                               final AbsoluteDate latest) {
  398.             final List<TimeStampedDouble> generated = new ArrayList<>();

  399.             // Add next computed average until it brackets the latest date
  400.             AbsoluteDate latestNeighbourDate = getCurrentDay(earliest);
  401.             while (latestNeighbourDate.isBeforeOrEqualTo(latest)) {
  402.                 generated.add(computeAverageFlux(latestNeighbourDate));
  403.                 latestNeighbourDate = getNextDay(latestNeighbourDate);
  404.             }
  405.             return generated;
  406.         }

  407.         /**
  408.          * Get the current day at midnight.
  409.          *
  410.          * @param date date
  411.          *
  412.          * @return current day at midnight.
  413.          */
  414.         private AbsoluteDate getCurrentDay(final AbsoluteDate date) {
  415.             // Find previous day date time components
  416.             final TimeScale      utc            = getUTC();
  417.             final DateComponents dateComponents = date.getComponents(utc).getDate();

  418.             // Create absolute date for previous day
  419.             return new AbsoluteDate(new DateTimeComponents(dateComponents, TimeComponents.H00), utc);
  420.         }

  421.         /**
  422.          * Get the next day at midnight.
  423.          *
  424.          * @param date date
  425.          *
  426.          * @return next day at midnight.
  427.          */
  428.         private AbsoluteDate getNextDay(final AbsoluteDate date) {
  429.             // Find previous day date time components
  430.             final TimeScale      utc               = getUTC();
  431.             final DateComponents dateComponents    = date.getComponents(utc).getDate();
  432.             final DateComponents shiftedComponents = new DateComponents(dateComponents, 1);

  433.             // Create absolute date for previous day
  434.             return new AbsoluteDate(new DateTimeComponents(shiftedComponents, TimeComponents.H00), utc);
  435.         }

  436.         /**
  437.          * Compute the average flux for given absolute date.
  438.          *
  439.          * @param date date at which the average flux is desired
  440.          *
  441.          * @return average flux
  442.          */
  443.         private TimeStampedDouble computeAverageFlux(final AbsoluteDate date) {
  444.             // Extract list of neighbors to compute average
  445.             final TimeStampedGenerator<LineParameters> generator   = getCache().getGenerator();
  446.             final AbsoluteDate                         initialDate = date.shiftedBy(-40 * Constants.JULIAN_DAY);
  447.             final AbsoluteDate                         finalDate   = date.shiftedBy(40 * Constants.JULIAN_DAY);
  448.             final List<LineParameters>                 monthlyData = generator.generate(initialDate, finalDate);

  449.             // Create interpolator for given data
  450.             final LinearInterpolator interpolator = new LinearInterpolator();

  451.             final double[] x = monthlyData.stream().map(param -> param.getDate().durationFrom(initialDate))
  452.                                           .mapToDouble(Double::doubleValue).toArray();
  453.             final double[] y = monthlyData.stream().map(LineParameters::getF107).mapToDouble(Double::doubleValue).toArray();

  454.             final UnivariateFunction interpolatingFunction = interpolator.interpolate(x, y);

  455.             // Loops over the 81 days centered on the given date
  456.             double average = 0;
  457.             for (int i = -40; i < 41; i++) {
  458.                 average += interpolatingFunction.value(date.shiftedBy(i * Constants.JULIAN_DAY).durationFrom(initialDate));
  459.             }

  460.             // Returns the 81 day average flux
  461.             return new TimeStampedDouble(average / 81, date);
  462.         }
  463.     }

  464. }