FieldSortedListTrimmer.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.utils;

  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.exception.LocalizedCoreFormats;
  20. import org.hipparchus.util.FastMath;
  21. import org.orekit.errors.OrekitException;
  22. import org.orekit.errors.OrekitIllegalArgumentException;
  23. import org.orekit.errors.OrekitMessages;
  24. import org.orekit.errors.TimeStampedCacheException;
  25. import org.orekit.time.FieldAbsoluteDate;
  26. import org.orekit.time.FieldTimeStamped;

  27. import java.util.List;

  28. /** A trimmer for externally stored chronologically sorted lists.
  29.  * @author Evan Ward
  30.  * @author Vincent Cucchietti
  31.  * @since 12.1
  32.  */
  33. public class FieldSortedListTrimmer {

  34.     /** Size list to return from {@link #getNeighborsSubList(FieldAbsoluteDate, List)}. */
  35.     private final int neighborsSize;

  36.     /**
  37.      * Create a new cache with the given neighbors size and data.
  38.      *
  39.      * @param neighborsSize size of the list returned from {@link #getNeighborsSubList(FieldAbsoluteDate, List)}.
  40.      */
  41.     public FieldSortedListTrimmer(final int neighborsSize) {
  42.         if (neighborsSize < 1) {
  43.             throw new OrekitIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
  44.                                                      neighborsSize, 1);
  45.         }
  46.         // assign instance variables
  47.         this.neighborsSize = neighborsSize;
  48.     }

  49.     /** Get size of the list returned from {@link #getNeighborsSubList(FieldAbsoluteDate, List)}.
  50.      * @return size of the list returned from {@link #getNeighborsSubList(FieldAbsoluteDate, List)}
  51.      */
  52.     public int getNeighborsSize() {
  53.         return neighborsSize;
  54.     }

  55.     /** Get the entries surrounding a central date.
  56.      * <p>
  57.      * If the central date is well within covered range, the returned array will
  58.      * be balanced with half the points before central date and half the points
  59.      * after it (depending on n parity, of course). If the central date is near
  60.      * the boundary, then the returned array will be unbalanced and will contain
  61.      * only the n earliest (or latest) entries. A typical example of the later
  62.      * case is leap seconds cache, since the number of leap seconds cannot be
  63.      * arbitrarily increased.
  64.      * </p>
  65.      * @param <T>  the type of data
  66.      * @param <K>  the type of the field elements
  67.      * @param central central date
  68.      * @param data complete list of entries (must be chronologically sorted)
  69.      * @return entries surrounding the specified date (sublist of {@code data})
  70.      */
  71.     public <T extends FieldTimeStamped<K>, K extends CalculusFieldElement<K>>
  72.         List<T> getNeighborsSubList(final FieldAbsoluteDate<K> central, final List<T> data) {

  73.         if (neighborsSize > data.size()) {
  74.             throw new OrekitException(OrekitMessages.NOT_ENOUGH_DATA, data.size());
  75.         }

  76.         // Find central index
  77.         final int i = findIndex(central, data);

  78.         // Check index in the range of the data
  79.         if (i < 0) {
  80.             final FieldAbsoluteDate<K> earliest = data.get(0).getDate();
  81.             throw new TimeStampedCacheException(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_BEFORE,
  82.                                                 earliest, central, earliest.durationFrom(central).getReal());
  83.         }
  84.         else if (i >= data.size()) {
  85.             final FieldAbsoluteDate<K> latest = data.get(data.size() - 1).getDate();
  86.             throw new TimeStampedCacheException(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_AFTER,
  87.                                                 latest, central, central.durationFrom(latest).getReal());
  88.         }

  89.         // Force unbalanced range if necessary
  90.         int start = FastMath.max(0, i - (neighborsSize - 1) / 2);
  91.         final int end = FastMath.min(data.size(), start + neighborsSize);
  92.         start = end - neighborsSize;

  93.         // Return list without copying
  94.         return data.subList(start, end);
  95.     }

  96.     /**
  97.      * Find the index, i, to {@code data} such that {@code data[i] <= t} and
  98.      * {@code data[i+1] > t} if {@code data[i+1]} exists.
  99.      *
  100.      * @param <T>  the type of data
  101.      * @param <K>  the type of the field elements
  102.      * @param t the time
  103.      * @param data complete list of entries (must be chronologically sorted)
  104.      *
  105.      * @return the index of the data at or just before {@code t}, {@code -1} if {@code t} is before the first entry, or
  106.      * {@code data.size()} if {@code t} is after the last entry.
  107.      */
  108.     private <T extends FieldTimeStamped<K>, K extends CalculusFieldElement<K>>
  109.         int findIndex(final FieldAbsoluteDate<K> t, final List<T> data) {
  110.         // left bracket of search algorithm
  111.         int iInf  = 0;
  112.         K   dtInf = t.durationFrom(data.get(0));
  113.         if (dtInf.getReal() < 0) {
  114.             // before first entry
  115.             return -1;
  116.         }

  117.         // right bracket of search algorithm
  118.         int iSup  = data.size() - 1;
  119.         K   dtSup = t.durationFrom(data.get(data.size() - 1));
  120.         if (dtSup.getReal() > 0) {
  121.             // after last entry
  122.             return data.size();
  123.         }

  124.         // search entries, using linear interpolation
  125.         // this should take only 2 iterations for near linear entries (most frequent use case)
  126.         // regardless of the number of entries
  127.         // this is much faster than binary search for large number of entries
  128.         while (iSup - iInf > 1) {
  129.             final int iInterp = (int) FastMath.rint(dtSup.multiply(iInf).subtract(dtInf.multiply(iSup)).divide(dtSup.subtract(dtInf)).getReal());
  130.             final int iMed    = FastMath.max(iInf + 1, FastMath.min(iInterp, iSup - 1));
  131.             final K   dtMed   = t.durationFrom(data.get(iMed).getDate());
  132.             if (dtMed.getReal() < 0) {
  133.                 iSup  = iMed;
  134.                 dtSup = dtMed;
  135.             } else {
  136.                 iInf  = iMed;
  137.                 dtInf = dtMed;
  138.             }
  139.         }

  140.         // at this point data[iInf] <= t <= data[iSup], but the javadoc for this method
  141.         // says the upper bound is exclusive, so check for equality to make a half open
  142.         // interval.
  143.         if (dtSup.getReal() == 0.0) {
  144.             return iSup;
  145.         }

  146.         return iInf;

  147.     }

  148. }