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