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