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