SortedListTrimmer.java

  1. /* Contributed in the public domain.
  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.exception.LocalizedCoreFormats;
  19. import org.hipparchus.util.FastMath;
  20. import org.orekit.errors.OrekitException;
  21. import org.orekit.errors.OrekitIllegalArgumentException;
  22. import org.orekit.errors.OrekitMessages;
  23. import org.orekit.errors.TimeStampedCacheException;
  24. import org.orekit.time.AbsoluteDate;
  25. import org.orekit.time.TimeStamped;

  26. import java.util.List;

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

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

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

  46.     /** Get size of the list returned from {@link #getNeighborsSubList(AbsoluteDate, List)}.
  47.      * @return size of the list returned from {@link #getNeighborsSubList(AbsoluteDate, List)}
  48.      */
  49.     public int getNeighborsSize() {
  50.         return neighborsSize;
  51.     }

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

  68.         if (neighborsSize > data.size()) {
  69.             throw new OrekitException(OrekitMessages.NOT_ENOUGH_DATA, data.size());
  70.         }

  71.         // find central index
  72.         final int i = findIndex(central, data);

  73.         // check index in in the range of the data
  74.         if (i < 0) {
  75.             final AbsoluteDate earliest = data.get(0).getDate();
  76.             throw new TimeStampedCacheException(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_BEFORE,
  77.                                                 earliest, central, earliest.durationFrom(central));
  78.         } else if (i >= data.size()) {
  79.             final AbsoluteDate latest = data.get(data.size() - 1).getDate();
  80.             throw new TimeStampedCacheException(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_AFTER,
  81.                                                 latest, central, central.durationFrom(latest));
  82.         }

  83.         // force unbalanced range if necessary
  84.         int start = FastMath.max(0, i - (neighborsSize - 1) / 2);
  85.         final int end = FastMath.min(data.size(), start + neighborsSize);
  86.         start = end - neighborsSize;

  87.         // return list without copying
  88.         return data.subList(start, end);

  89.     }

  90.     /**
  91.      * Find the index, i, to {@code data} such that {@code data[i] <= t} and
  92.      * {@code data[i+1] > t} if {@code data[i+1]} exists.
  93.      *
  94.      * @param <T>  the type of data
  95.      * @param t the time
  96.      * @param data complete list of entries (must be chronologically sorted)
  97.      * @return the index of the data at or just before {@code t}, {@code -1} if
  98.      *         {@code t} is before the first entry, or {@code data.size()} if
  99.      *         {@code t} is after the last entry.
  100.      */
  101.     private <T extends TimeStamped> int findIndex(final AbsoluteDate t, final List<T> data) {

  102.         // left bracket of search algorithm
  103.         int    iInf  = 0;
  104.         double dtInf = t.durationFrom(data.get(0));
  105.         if (dtInf < 0) {
  106.             // before first entry
  107.             return -1;
  108.         }

  109.         // right bracket of search algorithm
  110.         int    iSup  = data.size() - 1;
  111.         double dtSup = t.durationFrom(data.get(data.size() - 1));
  112.         if (dtSup > 0) {
  113.             // after last entry
  114.             return data.size();
  115.         }

  116.         // search entries, using linear interpolation
  117.         // this should take only 2 iterations for near linear entries (most frequent use case)
  118.         // regardless of the number of entries
  119.         // this is much faster than binary search for large number of entries
  120.         while (iSup - iInf > 1) {
  121.             final int    iInterp = (int) FastMath.rint((iInf * dtSup - iSup * dtInf) / (dtSup - dtInf));
  122.             final int    iMed    = FastMath.max(iInf + 1, FastMath.min(iInterp, iSup - 1));
  123.             final double dtMed   = t.durationFrom(data.get(iMed).getDate());
  124.             if (dtMed < 0) {
  125.                 iSup  = iMed;
  126.                 dtSup = dtMed;
  127.             } else {
  128.                 iInf  = iMed;
  129.                 dtInf = dtMed;
  130.             }
  131.         }

  132.         // at this point data[iInf] <= t <= data[iSup], but the javadoc for this method
  133.         // says the upper bound is exclusive, so check for equality to make a half open
  134.         // interval.
  135.         if (dtSup == 0.0) {
  136.             return iSup;
  137.         }

  138.         return iInf;

  139.     }

  140. }