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.propagation.semianalytical.dsst.utilities;
18  
19  import org.hipparchus.analysis.interpolation.HermiteInterpolator;
20  import org.hipparchus.util.FastMath;
21  import org.orekit.time.AbsoluteDate;
22  
23  import java.util.ArrayList;
24  
25  /** Interpolated short periodics coefficients.
26   * <p>
27   * Representation of a coefficient that need to be interpolated over time.
28   * </p><p>
29   * The short periodics coefficients can be interpolated for faster computation.
30   * This class stores computed values of the coefficients through the method
31   * {@link #addGridPoint} and gives an interpolated result through the method
32   * {@link #value}.
33   * </p>
34   * @author Nicolas Bernard
35   *
36   */
37  public class ShortPeriodicsInterpolatedCoefficient {
38  
39      /**Values of the already computed coefficients.*/
40      private ArrayList<double[]> values;
41  
42      /**Grid points.*/
43      private ArrayList<AbsoluteDate> abscissae;
44  
45      /**Number of points used in the interpolation.*/
46      private int interpolationPoints;
47  
48      /**Index of the latest closest neighbor.*/
49      private int latestClosestNeighbor;
50  
51      /**Simple constructor.
52       * @param interpolationPoints number of points used in the interpolation
53       */
54      public ShortPeriodicsInterpolatedCoefficient(final int interpolationPoints) {
55          this.interpolationPoints = interpolationPoints;
56          this.abscissae = new ArrayList<>();
57          this.values = new ArrayList<>();
58          this.latestClosestNeighbor = 0;
59      }
60  
61      /**Compute the value of the coefficient.
62       * @param date date at which the coefficient should be computed
63       * @return value of the coefficient
64       */
65      public double[] value(final AbsoluteDate date) {
66          //Get the closest points from the input date
67          final int[] neighbors = getNeighborsIndices(date);
68  
69          //Creation and set up of the interpolator
70          final HermiteInterpolator interpolator = new HermiteInterpolator();
71          for (int i : neighbors) {
72              interpolator.addSamplePoint(abscissae.get(i).durationFrom(date), values.get(i));
73          }
74  
75          //interpolation
76          return interpolator.value(0.0);
77  
78      }
79  
80      /**Find the closest available points from the specified date.
81       * @param date date of interest
82       * @return indices corresponding to the closest points on the time scale
83       */
84      private int[] getNeighborsIndices(final AbsoluteDate date) {
85          final int sizeofNeighborhood = FastMath.min(interpolationPoints, abscissae.size());
86          final int[] neighborsIndices = new int[sizeofNeighborhood];
87  
88          //If the size of the complete sample is less than
89          //the desired number of interpolation points,
90          //then the entire sample is considered as the neighborhood
91          if (interpolationPoints >= abscissae.size()) {
92              for (int i = 0; i < sizeofNeighborhood; i++) {
93                  neighborsIndices[i] = i;
94              }
95          } else {
96              // get indices around closest neighbor
97              int inf = getClosestNeighbor(date);
98              int sup = inf + 1;
99  
100             while (sup - inf < interpolationPoints) {
101                 if (inf == 0) { //This means that we have reached the earliest date
102                     sup++;
103                 } else if (sup >= abscissae.size()) { //This means that we have reached the latest date
104                     inf--;
105                 } else { //the choice is made between the two next neighbors
106                     final double lowerNeighborDistance = FastMath.abs(abscissae.get(inf - 1).durationFrom(date));
107                     final double upperNeighborDistance = FastMath.abs(abscissae.get(sup).durationFrom(date));
108 
109                     if (lowerNeighborDistance <= upperNeighborDistance) {
110                         inf--;
111                     } else {
112                         sup++;
113                     }
114                 }
115             }
116 
117             for (int i = 0; i < interpolationPoints; ++i) {
118                 neighborsIndices[i] = inf + i;
119             }
120 
121         }
122 
123         return neighborsIndices;
124     }
125 
126     /**Find the closest point from a specific date amongst the available points.
127      * @param date date of interest
128      * @return index of the closest abscissa from the date of interest
129      */
130     private int getClosestNeighbor(final AbsoluteDate date) {
131         //the starting point is the latest result of a call to this method.
132         //Indeed, as this class is meant to be called during an integration process
133         //with an input date evolving often continuously in time, there is a high
134         //probability that the result will be the same as for last call of
135         //this method.
136         final int closestNeighbor;
137 
138         //case where the date is before the available points
139         if (date.compareTo(abscissae.get(0)) <= 0) {
140             closestNeighbor = 0;
141         }
142         //case where the date is after the available points
143         else if (date.compareTo(abscissae.get(abscissae.size() - 1)) >= 0) {
144             closestNeighbor = abscissae.size() - 1;
145         }
146         //general case: one is looking for the two consecutives entries that surround the input date
147         //then one choose the closest one
148         else {
149             int lowerBorder = latestClosestNeighbor;
150             int upperBorder = latestClosestNeighbor;
151 
152             final int searchDirection = date.compareTo(abscissae.get(latestClosestNeighbor));
153             if (searchDirection > 0) {
154                 upperBorder++;
155                 while (date.compareTo(abscissae.get(upperBorder)) > 0) {
156                     upperBorder++;
157                     lowerBorder++;
158                 }
159             }
160             else {
161                 lowerBorder--;
162                 while (date.compareTo(abscissae.get(lowerBorder)) < 0) {
163                     upperBorder--;
164                     lowerBorder--;
165                 }
166             }
167 
168             final double lowerDistance = FastMath.abs(date.durationFrom(abscissae.get(lowerBorder)));
169             final double upperDistance = FastMath.abs(date.durationFrom(abscissae.get(upperBorder)));
170 
171             closestNeighbor = (lowerDistance < upperDistance) ? lowerBorder : upperBorder;
172         }
173 
174         //The result is stored in order to speed up the next call to the function
175         //Indeed, it is highly likely that the requested result will be the same
176         this.latestClosestNeighbor = closestNeighbor;
177         return closestNeighbor;
178     }
179 
180     /** Clear the recorded values from the interpolation grid.
181      */
182     public void clearHistory() {
183         abscissae.clear();
184         values.clear();
185     }
186 
187     /** Add a point to the interpolation grid.
188      * @param date abscissa of the point
189      * @param value value of the element
190      */
191     public void addGridPoint(final AbsoluteDate date, final double[] value) {
192         //If the grid is empty, the value is directly added to both arrays
193         if (abscissae.isEmpty()) {
194             abscissae.add(date);
195             values.add(value);
196         }
197         //If the grid already contains this point, only its value is changed
198         else if (abscissae.contains(date)) {
199             values.set(abscissae.indexOf(date), value);
200         }
201         //If the grid does not contain this point, the position of the point
202         //in the grid is computed first
203         else {
204             final int closestNeighbor = getClosestNeighbor(date);
205             final int index = (date.compareTo(abscissae.get(closestNeighbor)) < 0) ? closestNeighbor : closestNeighbor + 1;
206             abscissae.add(index, date);
207             values.add(index, value);
208         }
209     }
210 }