FieldDateDetector.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.propagation.events;

  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.List;

  21. import org.hipparchus.CalculusFieldElement;
  22. import org.hipparchus.Field;
  23. import org.hipparchus.ode.events.Action;
  24. import org.orekit.errors.OrekitIllegalArgumentException;
  25. import org.orekit.errors.OrekitMessages;
  26. import org.orekit.propagation.FieldSpacecraftState;
  27. import org.orekit.propagation.events.handlers.FieldEventHandler;
  28. import org.orekit.propagation.events.handlers.FieldStopOnEvent;
  29. import org.orekit.propagation.events.intervals.DateDetectionAdaptableIntervalFactory;
  30. import org.orekit.time.FieldAbsoluteDate;
  31. import org.orekit.time.FieldTimeStamped;

  32. /** Finder for date events.
  33.  * <p>This class finds date events (i.e. occurrence of some predefined dates).</p>
  34.  * <p>As of version 5.1, it is an enhanced date detector:</p>
  35.  * <ul>
  36.  *   <li>it can be defined without prior date ({@link #FieldDateDetector(Field, FieldTimeStamped...)})</li>
  37.  *   <li>several dates can be added ({@link #addEventDate(FieldAbsoluteDate)})</li>
  38.  * </ul>
  39.  * <p>The gap between the added dates must be more than the minGap.</p>
  40.  * <p>The default implementation behavior is to {@link Action#STOP stop}
  41.  * propagation at the first event date occurrence. This can be changed by calling
  42.  * {@link #withHandler(FieldEventHandler)} after construction.</p>
  43.  * @see org.orekit.propagation.FieldPropagator#addEventDetector(FieldEventDetector)
  44.  * @author Luc Maisonobe
  45.  * @author Pascal Parraud
  46.  * @param <T> type of the field elements
  47.  */
  48. public class FieldDateDetector<T extends CalculusFieldElement<T>> extends FieldAbstractDetector<FieldDateDetector<T>, T>
  49.     implements FieldTimeStamped<T> {

  50.     /** Default value for max check.
  51.      * @since 12.0
  52.      */
  53.     public static final double DEFAULT_MAX_CHECK = DateDetector.DEFAULT_MAX_CHECK;

  54.     /** Default value for minimum gap between added dates.
  55.      * @since 12.0
  56.      */
  57.     public static final double DEFAULT_MIN_GAP = DateDetector.DEFAULT_MIN_GAP;

  58.     /** Default value for convergence threshold.
  59.      * @since 12.0
  60.      */
  61.     public static final double DEFAULT_THRESHOLD = DateDetector.DEFAULT_THRESHOLD;

  62.     /** Minimum gap between added dates.
  63.      * @since 12.0
  64.      */
  65.     private final double minGap;

  66.     /** Last date for g computation. */
  67.     private FieldAbsoluteDate<T> gDate;

  68.     /** List of event dates. */
  69.     private final ArrayList<FieldEventDate<T>> eventDateList;

  70.     /** Current event date. */
  71.     private int currentIndex;

  72.     /** Build a new instance from a fielded date.
  73.      * @param fieldAbsoluteDate fielded date
  74.      * @since 13.0
  75.      */
  76.     public FieldDateDetector(final FieldAbsoluteDate<T> fieldAbsoluteDate) {
  77.         this(new FieldEventDetectionSettings<>(DEFAULT_MAX_CHECK, fieldAbsoluteDate.getField().getZero().newInstance(DEFAULT_THRESHOLD),
  78.                         DEFAULT_MAX_ITER), new FieldStopOnEvent<>(), DEFAULT_MIN_GAP, fieldAbsoluteDate);
  79.     }

  80.     /** Build a new instance.
  81.      * <p>First event dates are set here, but others can be
  82.      * added later with {@link #addEventDate(FieldAbsoluteDate)}.</p>
  83.      * @param field field to which dates belong
  84.      * @param dates list of event dates
  85.      * @see #addEventDate(FieldAbsoluteDate)
  86.      * @since 12.0
  87.      */
  88.     @SafeVarargs
  89.     public FieldDateDetector(final Field<T> field, final FieldTimeStamped<T>... dates) {
  90.         this(new FieldEventDetectionSettings<>(DateDetectionAdaptableIntervalFactory.getDatesDetectionFieldConstantInterval(dates),
  91.                 field.getZero().newInstance(DEFAULT_THRESHOLD), DEFAULT_MAX_ITER), new FieldStopOnEvent<>(), DEFAULT_MIN_GAP, dates);
  92.     }

  93.     /** Protected constructor with full parameters.
  94.      * <p>
  95.      * This constructor is not public as users are expected to use the builder
  96.      * API with the various {@code withXxx()} methods to set up the instance
  97.      * in a readable manner without using a huge amount of parameters.
  98.      * </p>
  99.      * @param detectionSettings event detection settings
  100.      * @param handler event handler to call at event occurrences
  101.      * @param minGap minimum gap between added dates (s)
  102.      * @param dates list of event dates
  103.      */
  104.     @SafeVarargs
  105.     protected FieldDateDetector(final FieldEventDetectionSettings<T> detectionSettings,
  106.                                 final FieldEventHandler<T> handler, final double minGap,
  107.                                 final FieldTimeStamped<T>... dates) {
  108.         super(detectionSettings, handler);
  109.         this.currentIndex  = -1;
  110.         this.gDate         = null;
  111.         this.eventDateList = new ArrayList<>(dates.length);
  112.         for (final FieldTimeStamped<T> ts : dates) {
  113.             addEventDate(ts.getDate());
  114.         }
  115.         this.minGap        = minGap;
  116.     }

  117.     /**
  118.      * Setup minimum gap between added dates.
  119.      * @param newMinGap new minimum gap between added dates
  120.      * @return a new detector with updated configuration (the instance is not changed)
  121.      * @since 12.0
  122.      */
  123.     public FieldDateDetector<T> withMinGap(final double newMinGap) {
  124.         @SuppressWarnings("unchecked")
  125.         final FieldTimeStamped<T>[] dates = eventDateList.toArray(new FieldEventDate[0]);
  126.         return new FieldDateDetector<>(getDetectionSettings(), getHandler(), newMinGap, dates);
  127.     }

  128.     /** {@inheritDoc} */
  129.     @Override
  130.     protected FieldDateDetector<T> create(final FieldEventDetectionSettings<T> detectionSettings,
  131.                                           final FieldEventHandler<T> newHandler) {
  132.         @SuppressWarnings("unchecked")
  133.         final FieldTimeStamped<T>[] dates = eventDateList.toArray(new FieldEventDate[0]);
  134.         return new FieldDateDetector<>(detectionSettings, newHandler, minGap, dates);
  135.     }

  136.     /** Get all event field dates currently managed, in chronological order.
  137.      * @return all event field dates currently managed, in chronological order
  138.      * @since 12.0
  139.      */
  140.     public List<FieldTimeStamped<T>> getDates() {
  141.         return Collections.unmodifiableList(eventDateList);
  142.     }

  143.     /** Compute the value of the switching function.
  144.      * This function measures the difference between the current and the target date.
  145.      * @param s the current state information: date, kinematics, attitude
  146.      * @return value of the switching function
  147.      */
  148.     public T g(final FieldSpacecraftState<T> s) {
  149.         gDate = s.getDate();
  150.         if (currentIndex < 0) {
  151.             return s.getMass().getField().getZero().newInstance(-1);
  152.         } else {
  153.             final FieldEventDate<T> event = getClosest(gDate);
  154.             return event.isgIncrease() ? gDate.durationFrom(event.getDate()) : event.getDate().durationFrom(gDate);
  155.         }
  156.     }

  157.     /** Get the current event date according to the propagator.
  158.      * @return event date
  159.      */
  160.     public FieldAbsoluteDate<T> getDate() {
  161.         return currentIndex < 0 ? null : eventDateList.get(currentIndex).getDate();
  162.     }

  163.     /** Add an event date.
  164.      * <p>The date to add must be:</p>
  165.      * <ul>
  166.      *   <li>less than the smallest already registered event date minus the maxCheck</li>
  167.      *   <li>or more than the largest already registered event date plus the maxCheck</li>
  168.      * </ul>
  169.      * @param target target date
  170.      * @throws IllegalArgumentException if the date is too close from already defined interval
  171.      * @see #FieldDateDetector(Field, FieldTimeStamped...)
  172.      */
  173.     public void addEventDate(final FieldAbsoluteDate<T> target) throws IllegalArgumentException {
  174.         final boolean increasing;
  175.         if (currentIndex < 0) {
  176.             increasing = (gDate == null) ? true : target.durationFrom(gDate).getReal() > 0.0;
  177.             currentIndex = 0;
  178.             eventDateList.add(new FieldEventDate<>(target, increasing));
  179.         } else {
  180.             final                      int lastIndex = eventDateList.size() - 1;
  181.             final FieldAbsoluteDate<T> firstDate     = eventDateList.get(0).getDate();
  182.             final FieldAbsoluteDate<T> lastDate      = eventDateList.get(lastIndex).getDate();
  183.             if (firstDate.durationFrom(target).getReal() > minGap) {
  184.                 increasing = !eventDateList.get(0).isgIncrease();
  185.                 eventDateList.add(0, new FieldEventDate<>(target, increasing));
  186.                 currentIndex++;
  187.             } else if (target.durationFrom(lastDate).getReal() > minGap) {
  188.                 increasing = !eventDateList.get(lastIndex).isgIncrease();
  189.                 eventDateList.add(new FieldEventDate<>(target, increasing));
  190.             } else {
  191.                 throw new OrekitIllegalArgumentException(OrekitMessages.EVENT_DATE_TOO_CLOSE,
  192.                                                          target,
  193.                                                          firstDate,
  194.                                                          lastDate,
  195.                                                          minGap,
  196.                                                          firstDate.durationFrom(target),
  197.                                                          target.durationFrom(lastDate));
  198.             }
  199.         }
  200.     }

  201.     /** Get the closest EventDate to the target date.
  202.      * @param target target date
  203.      * @return current EventDate
  204.      */
  205.     private FieldEventDate<T> getClosest(final FieldAbsoluteDate<T> target) {
  206.         final T dt = target.durationFrom(eventDateList.get(currentIndex).getDate());
  207.         if (dt.getReal() < 0.0 && currentIndex > 0) {
  208.             boolean found = false;
  209.             while (currentIndex > 0 && !found) {
  210.                 if (target.durationFrom(eventDateList.get(currentIndex - 1).getDate()).getReal() < eventDateList.get(currentIndex).getDate().durationFrom(target).getReal()) {
  211.                     currentIndex--;
  212.                 } else {
  213.                     found = true;
  214.                 }
  215.             }
  216.         } else if (dt.getReal() > 0.0 && currentIndex < eventDateList.size() - 1) {
  217.             final int maxIndex = eventDateList.size() - 1;
  218.             boolean found = false;
  219.             while (currentIndex < maxIndex && !found) {
  220.                 if (target.durationFrom(eventDateList.get(currentIndex + 1).getDate()).getReal() > eventDateList.get(currentIndex).getDate().durationFrom(target).getReal()) {
  221.                     currentIndex++;
  222.                 } else {
  223.                     found = true;
  224.                 }
  225.             }
  226.         }
  227.         return eventDateList.get(currentIndex);
  228.     }

  229.     /** Event date specification. */
  230.     private static class FieldEventDate<T extends CalculusFieldElement<T>> implements FieldTimeStamped<T> {

  231.         /** Event date. */
  232.         private final FieldAbsoluteDate<T> eventDate;

  233.         /** Flag for g function way around event date. */
  234.         private final boolean gIncrease;

  235.         /** Simple constructor.
  236.          * @param date date
  237.          * @param increase if true, g function increases around event date
  238.          */
  239.         FieldEventDate(final FieldAbsoluteDate<T> date, final boolean increase) {
  240.             this.eventDate = date;
  241.             this.gIncrease = increase;
  242.         }

  243.         /** Getter for event date.
  244.          * @return event date
  245.          */
  246.         public FieldAbsoluteDate<T> getDate() {
  247.             return eventDate;
  248.         }

  249.         /** Getter for g function way at event date.
  250.          * @return g function increasing flag
  251.          */
  252.         public boolean isgIncrease() {
  253.             return gIncrease;
  254.         }

  255.     }

  256. }