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.utils;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.exception.LocalizedCoreFormats;
21  import org.hipparchus.util.FastMath;
22  import org.orekit.errors.OrekitException;
23  import org.orekit.errors.OrekitIllegalArgumentException;
24  import org.orekit.errors.OrekitMessages;
25  import org.orekit.errors.TimeStampedCacheException;
26  import org.orekit.time.FieldAbsoluteDate;
27  import org.orekit.time.FieldTimeStamped;
28  
29  import java.util.List;
30  
31  /** A trimmer for externally stored chronologically sorted lists.
32   * @author Evan Ward
33   * @author Vincent Cucchietti
34   * @since 12.1
35   */
36  public class FieldSortedListTrimmer {
37  
38      /** Size list to return from {@link #getNeighborsSubList(FieldAbsoluteDate, List)}. */
39      private final int neighborsSize;
40  
41      /**
42       * Create a new cache with the given neighbors size and data.
43       *
44       * @param neighborsSize size of the list returned from {@link #getNeighborsSubList(FieldAbsoluteDate, List)}.
45       */
46      public FieldSortedListTrimmer(final int neighborsSize) {
47          if (neighborsSize < 1) {
48              throw new OrekitIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
49                                                       neighborsSize, 1);
50          }
51          // assign instance variables
52          this.neighborsSize = neighborsSize;
53      }
54  
55      /** Get size of the list returned from {@link #getNeighborsSubList(FieldAbsoluteDate, List)}.
56       * @return size of the list returned from {@link #getNeighborsSubList(FieldAbsoluteDate, List)}
57       */
58      public int getNeighborsSize() {
59          return neighborsSize;
60      }
61  
62      /** Get the entries surrounding a central date.
63       * <p>
64       * If the central date is well within covered range, the returned array will
65       * be balanced with half the points before central date and half the points
66       * after it (depending on n parity, of course). If the central date is near
67       * the boundary, then the returned array will be unbalanced and will contain
68       * only the n earliest (or latest) entries. A typical example of the later
69       * case is leap seconds cache, since the number of leap seconds cannot be
70       * arbitrarily increased.
71       * </p>
72       * @param <T>  the type of data
73       * @param <K>  the type of the field elements
74       * @param central central date
75       * @param data complete list of entries (must be chronologically sorted)
76       * @return entries surrounding the specified date (sublist of {@code data})
77       */
78      public <T extends FieldTimeStamped<K>, K extends CalculusFieldElement<K>>
79          List<T> getNeighborsSubList(final FieldAbsoluteDate<K> central, final List<T> data) {
80  
81          if (neighborsSize > data.size()) {
82              throw new OrekitException(OrekitMessages.NOT_ENOUGH_DATA, data.size());
83          }
84  
85          // Find central index
86          final int i = findIndex(central, data);
87  
88          // Check index in the range of the data
89          if (i < 0) {
90              final FieldAbsoluteDate<K> earliest = data.get(0).getDate();
91              throw new TimeStampedCacheException(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_BEFORE,
92                                                  earliest, central, earliest.durationFrom(central).getReal());
93          }
94          else if (i >= data.size()) {
95              final FieldAbsoluteDate<K> latest = data.get(data.size() - 1).getDate();
96              throw new TimeStampedCacheException(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_AFTER,
97                                                  latest, central, central.durationFrom(latest).getReal());
98          }
99  
100         // Force unbalanced range if necessary
101         int start = FastMath.max(0, i - (neighborsSize - 1) / 2);
102         final int end = FastMath.min(data.size(), start + neighborsSize);
103         start = end - neighborsSize;
104 
105         // Return list without copying
106         return data.subList(start, end);
107     }
108 
109     /**
110      * Find the index, i, to {@code data} such that {@code data[i] <= t} and
111      * {@code data[i+1] > t} if {@code data[i+1]} exists.
112      *
113      * @param <T>  the type of data
114      * @param <K>  the type of the field elements
115      * @param t the time
116      * @param data complete list of entries (must be chronologically sorted)
117      *
118      * @return the index of the data at or just before {@code t}, {@code -1} if {@code t} is before the first entry, or
119      * {@code data.size()} if {@code t} is after the last entry.
120      */
121     private <T extends FieldTimeStamped<K>, K extends CalculusFieldElement<K>>
122         int findIndex(final FieldAbsoluteDate<K> t, final List<T> data) {
123         // left bracket of search algorithm
124         int iInf  = 0;
125         K   dtInf = t.durationFrom(data.get(0));
126         if (dtInf.getReal() < 0) {
127             // before first entry
128             return -1;
129         }
130 
131         // right bracket of search algorithm
132         int iSup  = data.size() - 1;
133         K   dtSup = t.durationFrom(data.get(data.size() - 1));
134         if (dtSup.getReal() > 0) {
135             // after last entry
136             return data.size();
137         }
138 
139         // search entries, using linear interpolation
140         // this should take only 2 iterations for near linear entries (most frequent use case)
141         // regardless of the number of entries
142         // this is much faster than binary search for large number of entries
143         while (iSup - iInf > 1) {
144             final int iInterp = (int) FastMath.rint(dtSup.multiply(iInf).subtract(dtInf.multiply(iSup)).divide(dtSup.subtract(dtInf)).getReal());
145             final int iMed    = FastMath.max(iInf + 1, FastMath.min(iInterp, iSup - 1));
146             final K   dtMed   = t.durationFrom(data.get(iMed).getDate());
147             if (dtMed.getReal() < 0) {
148                 iSup  = iMed;
149                 dtSup = dtMed;
150             } else {
151                 iInf  = iMed;
152                 dtInf = dtMed;
153             }
154         }
155 
156         // at this point data[iInf] <= t <= data[iSup], but the javadoc for this method
157         // says the upper bound is exclusive, so check for equality to make a half open
158         // interval.
159         if (dtSup.getReal() == 0.0) {
160             return iSup;
161         }
162 
163         return iInf;
164 
165     }
166 
167 }