ImmutableFieldTimeStampedCache.java

  1. /* Copyright 2002-2023 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 java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.List;
  22. import java.util.stream.Stream;

  23. import org.hipparchus.CalculusFieldElement;
  24. import org.hipparchus.Field;
  25. import org.hipparchus.exception.LocalizedCoreFormats;
  26. import org.hipparchus.util.FastMath;
  27. import org.orekit.errors.OrekitIllegalArgumentException;
  28. import org.orekit.errors.OrekitIllegalStateException;
  29. import org.orekit.errors.OrekitMessages;
  30. import org.orekit.errors.TimeStampedCacheException;
  31. import org.orekit.time.FieldAbsoluteDate;
  32. import org.orekit.time.FieldChronologicalComparator;
  33. import org.orekit.time.FieldTimeStamped;
  34. import org.orekit.time.TimeStamped;

  35. /**
  36.  * A cache of {@link TimeStamped} data that provides concurrency through immutability. This strategy is suitable when all the
  37.  * cached data is stored in memory. (For example, {@link org.orekit.time.UTCScale UTCScale}) This class then provides
  38.  * convenient methods for accessing the data.
  39.  *
  40.  * @param <T> the type of data
  41.  * @param <KK> the type the field element
  42.  *
  43.  * @author Evan Ward
  44.  * @author Vincent Cucchietti
  45.  */
  46. public class ImmutableFieldTimeStampedCache<T extends FieldTimeStamped<KK>, KK extends CalculusFieldElement<KK>>
  47.         implements FieldTimeStampedCache<T, KK> {

  48.     /**
  49.      * the cached data. Be careful not to modify it after the constructor, or return a reference that allows mutating this
  50.      * list.
  51.      */
  52.     private final List<T> data;

  53.     /** the size list to return from {@link #getNeighbors(FieldAbsoluteDate)}. */
  54.     private final int neighborsSize;

  55.     /** Earliest date.
  56.      * @since 12.0
  57.      */
  58.     private final FieldAbsoluteDate<KK> earliestDate;

  59.     /** Latest date.
  60.      * @since 12.0
  61.      */
  62.     private final FieldAbsoluteDate<KK> latestDate;

  63.     /**
  64.      * Create a new cache with the given neighbors size and data.
  65.      *
  66.      * @param neighborsSize the size of the list returned from {@link #getNeighbors(FieldAbsoluteDate)}. Must be less than or
  67.      * equal to {@code data.size()}.
  68.      * @param data the backing data for this cache. The list will be copied to ensure immutability. To guarantee immutability
  69.      * the entries in {@code data} must be immutable themselves. There must be more data than {@code neighborsSize}.
  70.      *
  71.      * @throws IllegalArgumentException if {@code neighborsSize > data.size()} or if {@code neighborsSize} is negative
  72.      */
  73.     public ImmutableFieldTimeStampedCache(final int neighborsSize,
  74.                                           final Collection<? extends T> data) {
  75.         // Parameter check
  76.         if (neighborsSize > data.size()) {
  77.             throw new OrekitIllegalArgumentException(OrekitMessages.NOT_ENOUGH_CACHED_NEIGHBORS,
  78.                                                      data.size(), neighborsSize);
  79.         }
  80.         if (neighborsSize < 1) {
  81.             throw new OrekitIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
  82.                                                      neighborsSize, 0);
  83.         }

  84.         // Assign instance variables
  85.         this.neighborsSize = neighborsSize;

  86.         // Sort and copy data first
  87.         this.data = new ArrayList<>(data);
  88.         Collections.sort(this.data, new FieldChronologicalComparator<>());

  89.         this.earliestDate = this.data.get(0).getDate();
  90.         this.latestDate   = this.data.get(this.data.size() - 1).getDate();

  91.     }

  92.     /**
  93.      * private constructor for {@link #EMPTY_CACHE}.
  94.      * @param field field to which the elements belong
  95.      */
  96.     private ImmutableFieldTimeStampedCache(final Field<KK> field) {
  97.         this.data          = null;
  98.         this.neighborsSize = 0;
  99.         this.earliestDate  = FieldAbsoluteDate.getArbitraryEpoch(field);
  100.         this.latestDate    = FieldAbsoluteDate.getArbitraryEpoch(field);
  101.     }

  102.     /**
  103.      * Get an empty immutable cache, cast to the correct type.
  104.      *
  105.      * @param <TS> the type of data
  106.      * @param <CFE> the type of the calculus field element
  107.      * @param field field to which the elements belong
  108.      * @return an empty {@link ImmutableTimeStampedCache}.
  109.      */
  110.     public static <TS extends FieldTimeStamped<CFE>, CFE extends CalculusFieldElement<CFE>>
  111.         ImmutableFieldTimeStampedCache<TS, CFE> emptyCache(final Field<CFE> field) {
  112.         return new EmptyFieldTimeStampedCache<>(field);
  113.     }

  114.     /** {@inheritDoc} */
  115.     public Stream<T> getNeighbors(final FieldAbsoluteDate<KK> central) {

  116.         // Find central index
  117.         final int i = findIndex(central);

  118.         // Check index in the range of the data
  119.         if (i < 0) {
  120.             final FieldAbsoluteDate<KK> earliest = this.getEarliest().getDate();
  121.             throw new TimeStampedCacheException(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_BEFORE,
  122.                                                 earliest, central, earliest.durationFrom(central).getReal());
  123.         }
  124.         else if (i >= this.data.size()) {
  125.             final FieldAbsoluteDate<KK> latest = this.getLatest().getDate();
  126.             throw new TimeStampedCacheException(OrekitMessages.UNABLE_TO_GENERATE_NEW_DATA_AFTER,
  127.                                                 latest, central, central.durationFrom(latest).getReal());
  128.         }

  129.         // Force unbalanced range if necessary
  130.         int start = FastMath.max(0, i - (this.neighborsSize - 1) / 2);
  131.         final int end = FastMath.min(this.data.size(), start +
  132.                 this.neighborsSize);
  133.         start = end - this.neighborsSize;

  134.         // Return list without copying
  135.         return this.data.subList(start, end).stream();
  136.     }

  137.     /** {@inheritDoc} */
  138.     public int getNeighborsSize() {
  139.         return this.neighborsSize;
  140.     }

  141.     /** {@inheritDoc} */
  142.     public T getEarliest() {
  143.         return this.data.get(0);
  144.     }

  145.     /** {@inheritDoc} */
  146.     public T getLatest() {
  147.         return this.data.get(this.data.size() - 1);
  148.     }

  149.     /**
  150.      * Get all the data in this cache.
  151.      *
  152.      * @return a sorted collection of all data passed in the
  153.      * {@link #ImmutableFieldTimeStampedCache(int, Collection) constructor}.
  154.      */
  155.     public List<T> getAll() {
  156.         return Collections.unmodifiableList(this.data);
  157.     }

  158.     /** {@inheritDoc} */
  159.     @Override
  160.     public String toString() {
  161.         return "Immutable cache with " + this.data.size() + " entries";
  162.     }

  163.     /**
  164.      * Find the index, i, to {@link #data} such that {@code data[i] <= t} and {@code data[i+1] > t} if {@code data[i+1]}
  165.      * exists.
  166.      *
  167.      * @param t the time
  168.      *
  169.      * @return the index of the data at or just before {@code t}, {@code -1} if {@code t} is before the first entry, or
  170.      * {@code data.size()} if {@code t} is after the last entry.
  171.      */
  172.     private int findIndex(final FieldAbsoluteDate<KK> t) {
  173.         // left bracket of search algorithm
  174.         int iInf  = 0;
  175.         KK  dtInf = t.durationFrom(earliestDate);
  176.         if (dtInf.getReal() < 0) {
  177.             // before first entry
  178.             return -1;
  179.         }

  180.         // right bracket of search algorithm
  181.         int iSup  = data.size() - 1;
  182.         KK  dtSup = t.durationFrom(latestDate);
  183.         if (dtSup.getReal() > 0) {
  184.             // after last entry
  185.             return data.size();
  186.         }

  187.         // search entries, using linear interpolation
  188.         // this should take only 2 iterations for near linear entries (most frequent use case)
  189.         // regardless of the number of entries
  190.         // this is much faster than binary search for large number of entries
  191.         while (iSup - iInf > 1) {
  192.             final int iInterp = (int) FastMath.rint(dtSup.multiply(iInf).subtract(dtInf.multiply(iSup)).divide(dtSup.subtract(dtInf)).getReal());
  193.             final int iMed    = FastMath.max(iInf + 1, FastMath.min(iInterp, iSup - 1));
  194.             final KK  dtMed   = t.durationFrom(data.get(iMed).getDate());
  195.             if (dtMed.getReal() < 0) {
  196.                 iSup  = iMed;
  197.                 dtSup = dtMed;
  198.             } else {
  199.                 iInf  = iMed;
  200.                 dtInf = dtMed;
  201.             }
  202.         }

  203.         return iInf;
  204.     }

  205.     /** An empty immutable cache that always throws an exception on attempted access. */
  206.     private static class EmptyFieldTimeStampedCache<T extends FieldTimeStamped<KK>, KK extends CalculusFieldElement<KK>>
  207.             extends ImmutableFieldTimeStampedCache<T, KK> {

  208.         /** Simple constructor.
  209.          * @param field field to which elements belong
  210.          */
  211.         EmptyFieldTimeStampedCache(final Field<KK> field) {
  212.             super(field);
  213.         }

  214.         /** {@inheritDoc} */
  215.         @Override
  216.         public Stream<T> getNeighbors(final FieldAbsoluteDate<KK> central) {
  217.             throw new TimeStampedCacheException(OrekitMessages.NO_CACHED_ENTRIES);
  218.         }

  219.         /** {@inheritDoc} */
  220.         @Override
  221.         public int getNeighborsSize() {
  222.             return 0;
  223.         }

  224.         /** {@inheritDoc} */
  225.         @Override
  226.         public T getEarliest() {
  227.             throw new OrekitIllegalStateException(OrekitMessages.NO_CACHED_ENTRIES);
  228.         }

  229.         /** {@inheritDoc} */
  230.         @Override
  231.         public T getLatest() {
  232.             throw new OrekitIllegalStateException(OrekitMessages.NO_CACHED_ENTRIES);
  233.         }

  234.         /** {@inheritDoc} */
  235.         @Override
  236.         public List<T> getAll() {
  237.             return Collections.emptyList();
  238.         }

  239.         /** {@inheritDoc} */
  240.         @Override
  241.         public String toString() {
  242.             return "Empty immutable cache";
  243.         }

  244.     }

  245. }