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     /** Get all event field dates currently managed, in chronological order.
153      * @return all event field dates currently managed, in chronological order
154      * @since 12.0
155      */
156     public List<FieldTimeStamped<T>> getDates() {
157         return Collections.unmodifiableList(eventDateList);
158     }
159 
160     /** Compute the value of the switching function.
161      * This function measures the difference between the current and the target date.
162      * @param s the current state information: date, kinematics, attitude
163      * @return value of the switching function
164      */
165     public T g(final FieldSpacecraftState<T> s) {
166         gDate = s.getDate();
167         if (currentIndex < 0) {
168             return s.getMass().getField().getZero().newInstance(-1);
169         } else {
170             final FieldEventDate<T> event = getClosest(gDate);
171             return event.isgIncrease() ? gDate.durationFrom(event.getDate()) : event.getDate().durationFrom(gDate);
172         }
173     }
174 
175     /** Get the current event date according to the propagator.
176      * @return event date
177      */
178     public FieldAbsoluteDate<T> getDate() {
179         return currentIndex < 0 ? null : eventDateList.get(currentIndex).getDate();
180     }
181 
182     /** Add an event date.
183      * <p>The date to add must be:</p>
184      * <ul>
185      *   <li>less than the smallest already registered event date minus the maxCheck</li>
186      *   <li>or more than the largest already registered event date plus the maxCheck</li>
187      * </ul>
188      * @param target target date
189      * @throws IllegalArgumentException if the date is too close from already defined interval
190      * @see #FieldDateDetector(Field, FieldTimeStamped...)
191      */
192     public void addEventDate(final FieldAbsoluteDate<T> target) throws IllegalArgumentException {
193         final boolean increasing;
194         if (currentIndex < 0) {
195             increasing = (gDate == null) ? true : target.durationFrom(gDate).getReal() > 0.0;
196             currentIndex = 0;
197             eventDateList.add(new FieldEventDate<>(target, increasing));
198         } else {
199             final                      int lastIndex = eventDateList.size() - 1;
200             final FieldAbsoluteDate<T> firstDate     = eventDateList.get(0).getDate();
201             final FieldAbsoluteDate<T> lastDate      = eventDateList.get(lastIndex).getDate();
202             if (firstDate.durationFrom(target).getReal() > minGap) {
203                 increasing = !eventDateList.get(0).isgIncrease();
204                 eventDateList.add(0, new FieldEventDate<>(target, increasing));
205                 currentIndex++;
206             } else if (target.durationFrom(lastDate).getReal() > minGap) {
207                 increasing = !eventDateList.get(lastIndex).isgIncrease();
208                 eventDateList.add(new FieldEventDate<>(target, increasing));
209             } else {
210                 throw new OrekitIllegalArgumentException(OrekitMessages.EVENT_DATE_TOO_CLOSE,
211                                                          target,
212                                                          firstDate,
213                                                          lastDate,
214                                                          minGap,
215                                                          firstDate.durationFrom(target),
216                                                          target.durationFrom(lastDate));
217             }
218         }
219     }
220 
221     /** Get the closest EventDate to the target date.
222      * @param target target date
223      * @return current EventDate
224      */
225     private FieldEventDate<T> getClosest(final FieldAbsoluteDate<T> target) {
226         final T dt = target.durationFrom(eventDateList.get(currentIndex).getDate());
227         if (dt.getReal() < 0.0 && currentIndex > 0) {
228             boolean found = false;
229             while (currentIndex > 0 && !found) {
230                 if (target.durationFrom(eventDateList.get(currentIndex - 1).getDate()).getReal() < eventDateList.get(currentIndex).getDate().durationFrom(target).getReal()) {
231                     currentIndex--;
232                 } else {
233                     found = true;
234                 }
235             }
236         } else if (dt.getReal() > 0.0 && currentIndex < eventDateList.size() - 1) {
237             final int maxIndex = eventDateList.size() - 1;
238             boolean found = false;
239             while (currentIndex < maxIndex && !found) {
240                 if (target.durationFrom(eventDateList.get(currentIndex + 1).getDate()).getReal() > eventDateList.get(currentIndex).getDate().durationFrom(target).getReal()) {
241                     currentIndex++;
242                 } else {
243                     found = true;
244                 }
245             }
246         }
247         return eventDateList.get(currentIndex);
248     }
249 
250     /** Event date specification. */
251     private static class FieldEventDate<T extends CalculusFieldElement<T>> implements FieldTimeStamped<T> {
252 
253         /** Event date. */
254         private final FieldAbsoluteDate<T> eventDate;
255 
256         /** Flag for g function way around event date. */
257         private final boolean gIncrease;
258 
259         /** Simple constructor.
260          * @param date date
261          * @param increase if true, g function increases around event date
262          */
263         FieldEventDate(final FieldAbsoluteDate<T> date, final boolean increase) {
264             this.eventDate = date;
265             this.gIncrease = increase;
266         }
267 
268         /** Getter for event date.
269          * @return event date
270          */
271         public FieldAbsoluteDate<T> getDate() {
272             return eventDate;
273         }
274 
275         /** Getter for g function way at event date.
276          * @return g function increasing flag
277          */
278         public boolean isgIncrease() {
279             return gIncrease;
280         }
281 
282     }
283 
284 }