DateDetectionAdaptableIntervalFactory.java

  1. /* Copyright 2022-2025 Romain Serra
  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.propagation.events.intervals;

  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.util.FastMath;
  20. import org.orekit.time.AbsoluteDate;
  21. import org.orekit.time.ChronologicalComparator;
  22. import org.orekit.time.FieldTimeStamped;
  23. import org.orekit.time.TimeStamped;

  24. import java.util.Arrays;
  25. import java.util.ArrayList;
  26. import java.util.Collections;
  27. import java.util.List;
  28. import java.util.Optional;
  29. import java.util.SortedSet;
  30. import java.util.TreeSet;

  31. /**
  32.  * Factory for adaptable interval tuned for date(s) detection.
  33.  *
  34.  * @see org.orekit.propagation.events.DateDetector
  35.  * @see org.orekit.propagation.events.FieldDateDetector
  36.  * @author Romain Serra
  37.  * @since 13.0
  38.  */
  39. public class DateDetectionAdaptableIntervalFactory {

  40.     /** Default value for max check. */
  41.     public static final double DEFAULT_MAX_CHECK = 1.0e10;

  42.     /**
  43.      * Private constructor.
  44.      */
  45.     private DateDetectionAdaptableIntervalFactory() {
  46.         // factory class
  47.     }

  48.     /**
  49.      * Return a candidate {@link AdaptableInterval} for single date detection.
  50.      * @return adaptable interval
  51.      */
  52.     public static AdaptableInterval getSingleDateDetectionAdaptableInterval() {
  53.         return AdaptableInterval.of(DEFAULT_MAX_CHECK);
  54.     }

  55.     /**
  56.      * Return a candidate {@link AdaptableInterval} for multiple dates detection with a constant max. check.
  57.      * @param timeStampeds event dates
  58.      * @return adaptable interval
  59.      */
  60.     public static AdaptableInterval getDatesDetectionConstantInterval(final TimeStamped... timeStampeds) {
  61.         if (timeStampeds == null || timeStampeds.length < 2) {
  62.             return getSingleDateDetectionAdaptableInterval();
  63.         }
  64.         return AdaptableInterval.of(getMinGap(timeStampeds) * 0.5);
  65.     }

  66.     /**
  67.      * Return a candidate {@link AdaptableInterval} for multiple dates detection.
  68.      * @param timeStampeds event dates
  69.      * @return adaptable interval
  70.      */
  71.     public static AdaptableInterval getDatesDetectionInterval(final TimeStamped... timeStampeds) {
  72.         if (timeStampeds == null || timeStampeds.length < 2) {
  73.             return getSingleDateDetectionAdaptableInterval();
  74.         }
  75.         final double minGap = getMinGap(timeStampeds);
  76.         final SortedSet<TimeStamped> sortedSet = new TreeSet<>(new ChronologicalComparator());
  77.         sortedSet.addAll(Arrays.asList(timeStampeds));
  78.         return (state, isForward) -> {
  79.             final AbsoluteDate date = state.getDate();
  80.             double minDistance = Double.POSITIVE_INFINITY;
  81.             if (isForward) {
  82.                 for (final TimeStamped ts : sortedSet) {
  83.                     final AbsoluteDate nextDate = ts.getDate();
  84.                     if (date.isBefore(nextDate)) {
  85.                         minDistance = nextDate.durationFrom(date);
  86.                         break;
  87.                     }
  88.                 }
  89.             } else {
  90.                 final List<TimeStamped> inverted = new ArrayList<>(sortedSet);
  91.                 Collections.reverse(inverted);
  92.                 for (final TimeStamped ts : inverted) {
  93.                     final AbsoluteDate nextDate = ts.getDate();
  94.                     if (date.isAfter(nextDate)) {
  95.                         minDistance = date.durationFrom(nextDate);
  96.                         break;
  97.                     }
  98.                 }
  99.             }
  100.             return FastMath.abs(minDistance) + minGap / 2;
  101.         };
  102.     }

  103.     /**
  104.      * Return a candidate {@link FieldAdaptableInterval} for single date detection.
  105.      * @param <T> field type
  106.      * @return adaptable interval
  107.      */
  108.     public static <T extends CalculusFieldElement<T>> FieldAdaptableInterval<T> getSingleDateDetectionFieldAdaptableInterval() {
  109.         return FieldAdaptableInterval.of(DEFAULT_MAX_CHECK);
  110.     }

  111.     /**
  112.      * Return a candidate {@link FieldAdaptableInterval} for multiple dates detection with a constant max. check.
  113.      * @param timeStampeds event dates
  114.      * @param <T> field type
  115.      * @return adaptable interval
  116.      */
  117.     @SafeVarargs
  118.     public static <T extends CalculusFieldElement<T>> FieldAdaptableInterval<T> getDatesDetectionFieldConstantInterval(final FieldTimeStamped<T>... timeStampeds) {
  119.         if (timeStampeds == null || timeStampeds.length < 2) {
  120.             return getSingleDateDetectionFieldAdaptableInterval();
  121.         }
  122.         final double minGap = getMinGap(Arrays.stream(timeStampeds).map(t -> (TimeStamped) t.getDate().toAbsoluteDate())
  123.                 .toArray(TimeStamped[]::new));
  124.         return FieldAdaptableInterval.of(minGap * 0.5);
  125.     }

  126.     /**
  127.      * Return a candidate {@link FieldAdaptableInterval} for multiple dates detection.
  128.      * @param timeStampeds event dates
  129.      * @param <T> field type
  130.      * @return adaptable interval
  131.      */
  132.     @SafeVarargs
  133.     public static <T extends CalculusFieldElement<T>> FieldAdaptableInterval<T> getDatesDetectionFieldInterval(final FieldTimeStamped<T>... timeStampeds) {
  134.         if (timeStampeds == null || timeStampeds.length < 2) {
  135.             return getSingleDateDetectionFieldAdaptableInterval();
  136.         }
  137.         return FieldAdaptableInterval.of(getDatesDetectionInterval(Arrays.stream(timeStampeds)
  138.                 .map(t -> (TimeStamped) t.getDate().toAbsoluteDate()).toArray(TimeStamped[]::new)));
  139.     }

  140.     /**
  141.      * Compute min. gap between dated objects if applicable. It ignores duplicates.
  142.      * @param timeStampeds time stamped objects
  143.      * @return minimum gap
  144.      */
  145.     public static double getMinGap(final TimeStamped... timeStampeds) {
  146.         double minGap = DEFAULT_MAX_CHECK;
  147.         for (final TimeStamped timeStamped : timeStampeds) {
  148.             final Optional<Double> minDistance = Arrays.stream(timeStampeds)
  149.                     .map(t -> (!t.getDate().isEqualTo(timeStamped.getDate())) ? FastMath.abs(t.durationFrom(timeStamped)) : Double.POSITIVE_INFINITY)
  150.                     .min(Double::compareTo);
  151.             if (minDistance.isPresent()) {
  152.                 minGap = FastMath.min(minGap, minDistance.get());
  153.             }
  154.         }
  155.         return minGap;
  156.     }
  157. }