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 }