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  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import org.hipparchus.CalculusFieldElement;
24  import org.hipparchus.Field;
25  import org.hipparchus.ode.events.Action;
26  import org.orekit.errors.OrekitIllegalArgumentException;
27  import org.orekit.errors.OrekitMessages;
28  import org.orekit.propagation.FieldSpacecraftState;
29  import org.orekit.propagation.events.handlers.FieldEventHandler;
30  import org.orekit.propagation.events.handlers.FieldStopOnEvent;
31  import org.orekit.propagation.events.intervals.DateDetectionAdaptableIntervalFactory;
32  import org.orekit.time.FieldAbsoluteDate;
33  import org.orekit.time.FieldTimeStamped;
34  
35  /** Finder for date events.
36   * <p>This class finds date events (i.e. occurrence of some predefined dates).</p>
37   * <p>As of version 5.1, it is an enhanced date detector:</p>
38   * <ul>
39   *   <li>it can be defined without prior date ({@link #FieldDateDetector(Field, FieldTimeStamped...)})</li>
40   *   <li>several dates can be added ({@link #addEventDate(FieldAbsoluteDate)})</li>
41   * </ul>
42   * <p>The gap between the added dates must be more than the minGap.</p>
43   * <p>The default implementation behavior is to {@link Action#STOP stop}
44   * propagation at the first event date occurrence. This can be changed by calling
45   * {@link #withHandler(FieldEventHandler)} after construction.</p>
46   * @see org.orekit.propagation.FieldPropagator#addEventDetector(FieldEventDetector)
47   * @author Luc Maisonobe
48   * @author Pascal Parraud
49   * @param <T> type of the field elements
50   */
51  public class FieldDateDetector<T extends CalculusFieldElement<T>> extends FieldAbstractDetector<FieldDateDetector<T>, T>
52      implements FieldTimeStamped<T> {
53  
54      /** Default value for max check.
55       * @since 12.0
56       */
57      public static final double DEFAULT_MAX_CHECK = DateDetector.DEFAULT_MAX_CHECK;
58  
59      /** Default value for minimum gap between added dates.
60       * @since 12.0
61       */
62      public static final double DEFAULT_MIN_GAP = DateDetector.DEFAULT_MIN_GAP;
63  
64      /** Default value for convergence threshold.
65       * @since 12.0
66       */
67      public static final double DEFAULT_THRESHOLD = DateDetector.DEFAULT_THRESHOLD;
68  
69      /** Minimum gap between added dates.
70       * @since 12.0
71       */
72      private final double minGap;
73  
74      /** Last date for g computation. */
75      private FieldAbsoluteDate<T> gDate;
76  
77      /** List of event dates. */
78      private final ArrayList<FieldEventDate<T>> eventDateList;
79  
80      /** Current event date. */
81      private int currentIndex;
82  
83      /** Build a new instance from a fielded date.
84       * @param fieldAbsoluteDate fielded date
85       * @since 13.0
86       */
87      public FieldDateDetector(final FieldAbsoluteDate<T> fieldAbsoluteDate) {
88          this(new FieldEventDetectionSettings<>(DEFAULT_MAX_CHECK, fieldAbsoluteDate.getField().getZero().newInstance(DEFAULT_THRESHOLD),
89                          DEFAULT_MAX_ITER), new FieldStopOnEvent<>(), DEFAULT_MIN_GAP, fieldAbsoluteDate);
90      }
91  
92      /** Build a new instance.
93       * <p>First event dates are set here, but others can be
94       * added later with {@link #addEventDate(FieldAbsoluteDate)}.</p>
95       * @param field field to which dates belong
96       * @param dates list of event dates
97       * @see #addEventDate(FieldAbsoluteDate)
98       * @since 12.0
99       */
100     @SafeVarargs
101     public FieldDateDetector(final Field<T> field, final FieldTimeStamped<T>... dates) {
102         this(new FieldEventDetectionSettings<>(DateDetectionAdaptableIntervalFactory.getDatesDetectionFieldConstantInterval(dates),
103                 field.getZero().newInstance(DEFAULT_THRESHOLD), DEFAULT_MAX_ITER), new FieldStopOnEvent<>(), DEFAULT_MIN_GAP, dates);
104     }
105 
106     /** Protected constructor with full parameters.
107      * <p>
108      * This constructor is not public as users are expected to use the builder
109      * API with the various {@code withXxx()} methods to set up the instance
110      * in a readable manner without using a huge amount of parameters.
111      * </p>
112      * @param detectionSettings event detection settings
113      * @param handler event handler to call at event occurrences
114      * @param minGap minimum gap between added dates (s)
115      * @param dates list of event dates
116      */
117     @SafeVarargs
118     protected FieldDateDetector(final FieldEventDetectionSettings<T> detectionSettings,
119                                 final FieldEventHandler<T> handler, final double minGap,
120                                 final FieldTimeStamped<T>... dates) {
121         super(detectionSettings, handler);
122         this.currentIndex  = -1;
123         this.gDate         = null;
124         this.eventDateList = new ArrayList<>(dates.length);
125         for (final FieldTimeStamped<T> ts : dates) {
126             addEventDate(ts.getDate());
127         }
128         this.minGap        = minGap;
129     }
130 
131     /**
132      * Setup minimum gap between added dates.
133      * @param newMinGap new minimum gap between added dates
134      * @return a new detector with updated configuration (the instance is not changed)
135      * @since 12.0
136      */
137     public FieldDateDetector<T> withMinGap(final double newMinGap) {
138         @SuppressWarnings("unchecked")
139         final FieldTimeStamped<T>[] dates = eventDateList.toArray(new FieldEventDate[0]);
140         return new FieldDateDetector<>(getDetectionSettings(), getHandler(), newMinGap, dates);
141     }
142 
143     /** {@inheritDoc} */
144     @Override
145     protected FieldDateDetector<T> create(final FieldEventDetectionSettings<T> detectionSettings,
146                                           final FieldEventHandler<T> newHandler) {
147         @SuppressWarnings("unchecked")
148         final FieldTimeStamped<T>[] dates = eventDateList.toArray(new FieldEventDate[0]);
149         return new FieldDateDetector<>(detectionSettings, newHandler, minGap, dates);
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     public boolean dependsOnTimeOnly() {
155         return true;
156     }
157 
158     /** Get all event field dates currently managed, in chronological order.
159      * @return all event field dates currently managed, in chronological order
160      * @since 12.0
161      */
162     public List<FieldTimeStamped<T>> getDates() {
163         return Collections.unmodifiableList(eventDateList);
164     }
165 
166     /** Compute the value of the switching function.
167      * This function measures the difference between the current and the target date.
168      * @param s the current state information: date, kinematics, attitude
169      * @return value of the switching function
170      */
171     public T g(final FieldSpacecraftState<T> s) {
172         gDate = s.getDate();
173         if (currentIndex < 0) {
174             return s.getMass().getField().getZero().newInstance(-1);
175         } else {
176             final FieldEventDate<T> event = getClosest(gDate);
177             return event.isgIncrease() ? gDate.durationFrom(event.getDate()) : event.getDate().durationFrom(gDate);
178         }
179     }
180 
181     /** Get the current event date according to the propagator.
182      * @return event date
183      */
184     public FieldAbsoluteDate<T> getDate() {
185         return currentIndex < 0 ? null : eventDateList.get(currentIndex).getDate();
186     }
187 
188     /** Add an event date.
189      * <p>The date to add must be:</p>
190      * <ul>
191      *   <li>less than the smallest already registered event date minus the maxCheck</li>
192      *   <li>or more than the largest already registered event date plus the maxCheck</li>
193      * </ul>
194      * @param target target date
195      * @throws IllegalArgumentException if the date is too close from already defined interval
196      * @see #FieldDateDetector(Field, FieldTimeStamped...)
197      */
198     public void addEventDate(final FieldAbsoluteDate<T> target) throws IllegalArgumentException {
199         final boolean increasing;
200         if (currentIndex < 0) {
201             increasing = (gDate == null) ? true : target.durationFrom(gDate).getReal() > 0.0;
202             currentIndex = 0;
203             eventDateList.add(new FieldEventDate<>(target, increasing));
204         } else {
205             final                      int lastIndex = eventDateList.size() - 1;
206             final FieldAbsoluteDate<T> firstDate     = eventDateList.get(0).getDate();
207             final FieldAbsoluteDate<T> lastDate      = eventDateList.get(lastIndex).getDate();
208             if (firstDate.durationFrom(target).getReal() > minGap) {
209                 increasing = !eventDateList.get(0).isgIncrease();
210                 eventDateList.add(0, new FieldEventDate<>(target, increasing));
211                 currentIndex++;
212             } else if (target.durationFrom(lastDate).getReal() > minGap) {
213                 increasing = !eventDateList.get(lastIndex).isgIncrease();
214                 eventDateList.add(new FieldEventDate<>(target, increasing));
215             } else {
216                 throw new OrekitIllegalArgumentException(OrekitMessages.EVENT_DATE_TOO_CLOSE,
217                                                          target.toAbsoluteDate(),
218                                                          firstDate.toAbsoluteDate(),
219                                                          lastDate.toAbsoluteDate(),
220                                                          minGap,
221                                                          firstDate.durationFrom(target).getReal(),
222                                                          target.durationFrom(lastDate).getReal());
223             }
224         }
225     }
226 
227     /** Get the closest EventDate to the target date.
228      * @param target target date
229      * @return current EventDate
230      */
231     private FieldEventDate<T> getClosest(final FieldAbsoluteDate<T> target) {
232         final T dt = target.durationFrom(eventDateList.get(currentIndex).getDate());
233         if (dt.getReal() < 0.0 && currentIndex > 0) {
234             boolean found = false;
235             while (currentIndex > 0 && !found) {
236                 if (target.durationFrom(eventDateList.get(currentIndex - 1).getDate()).getReal() < eventDateList.get(currentIndex).getDate().durationFrom(target).getReal()) {
237                     currentIndex--;
238                 } else {
239                     found = true;
240                 }
241             }
242         } else if (dt.getReal() > 0.0 && currentIndex < eventDateList.size() - 1) {
243             final int maxIndex = eventDateList.size() - 1;
244             boolean found = false;
245             while (currentIndex < maxIndex && !found) {
246                 if (target.durationFrom(eventDateList.get(currentIndex + 1).getDate()).getReal() > eventDateList.get(currentIndex).getDate().durationFrom(target).getReal()) {
247                     currentIndex++;
248                 } else {
249                     found = true;
250                 }
251             }
252         }
253         return eventDateList.get(currentIndex);
254     }
255 
256     /** Event date specification. */
257     private static class FieldEventDate<T extends CalculusFieldElement<T>> implements FieldTimeStamped<T> {
258 
259         /** Event date. */
260         private final FieldAbsoluteDate<T> eventDate;
261 
262         /** Flag for g function way around event date. */
263         private final boolean gIncrease;
264 
265         /** Simple constructor.
266          * @param date date
267          * @param increase if true, g function increases around event date
268          */
269         FieldEventDate(final FieldAbsoluteDate<T> date, final boolean increase) {
270             this.eventDate = date;
271             this.gIncrease = increase;
272         }
273 
274         /** Getter for event date.
275          * @return event date
276          */
277         public FieldAbsoluteDate<T> getDate() {
278             return eventDate;
279         }
280 
281         /** Getter for g function way at event date.
282          * @return g function increasing flag
283          */
284         public boolean isgIncrease() {
285             return gIncrease;
286         }
287 
288     }
289 
290 }