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.List;
20  
21  import org.hipparchus.CalculusFieldElement;
22  import org.hipparchus.Field;
23  import org.orekit.errors.OrekitException;
24  import org.orekit.errors.OrekitMessages;
25  import org.orekit.propagation.FieldSpacecraftState;
26  import org.orekit.propagation.events.functions.EventFunction;
27  import org.orekit.propagation.events.functions.TimeIntervalEventFunction;
28  import org.orekit.propagation.events.handlers.EventHandler;
29  import org.orekit.propagation.events.handlers.FieldEventHandler;
30  import org.orekit.propagation.events.handlers.FieldStopOnEvent;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.time.TimeInterval;
33  import org.orekit.utils.DateDriver;
34  import org.orekit.utils.ParameterDriver;
35  import org.orekit.utils.ParameterObserver;
36  import org.orekit.utils.TimeSpanMap;
37  import org.orekit.utils.TimeSpanMap.Span;
38  
39  /** Detector for date intervals that may be offset thanks to parameter drivers.
40   * <p>
41   * Two dual views can be used for date intervals: either start date/stop date or
42   * median date/duration. {@link #getStartDriver() start}/{@link #getStopDriver() stop}
43   * drivers and {@link #getMedianDriver() median}/{@link #getDurationDriver() duration}
44   * drivers work in pair. Both drivers in one pair can be selected and their changes will
45   * be propagated to the other pair, but attempting to select drivers in both
46   * pairs at the same time will trigger an exception. Changing the value of a driver
47   * that is not selected should be avoided as it leads to inconsistencies between the pairs.
48   * </p>
49   * @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
50   * @param <T> type of the field elements
51   * @author Luc Maisonobe
52   * @since 11.1
53   */
54  public class FieldParameterDrivenDateIntervalDetector<T extends CalculusFieldElement<T>>
55      extends FieldAbstractDetector<FieldParameterDrivenDateIntervalDetector<T>, T> {
56  
57      /** Default suffix for start driver. */
58      public static final String START_SUFFIX = "_START";
59  
60      /** Default suffix for stop driver. */
61      public static final String STOP_SUFFIX = "_STOP";
62  
63      /** Default suffix for median driver. */
64      public static final String MEDIAN_SUFFIX = "_MEDIAN";
65  
66      /** Default suffix for duration driver. */
67      public static final String DURATION_SUFFIX = "_DURATION";
68  
69      /** Reference interval start driver. */
70      private final DateDriver start;
71  
72      /** Reference interval stop driver. */
73      private final DateDriver stop;
74  
75      /** Median date driver. */
76      private final DateDriver median;
77  
78      /** Duration driver. */
79      private final ParameterDriver duration;
80  
81      /** Build a new instance.
82       * @param field field to which the elements belong
83       * @param prefix prefix to use for parameter drivers names
84       * @param refMedian reference interval median date
85       * @param refDuration reference duration
86       */
87      public FieldParameterDrivenDateIntervalDetector(final Field<T> field, final String prefix,
88                                                      final AbsoluteDate refMedian, final double refDuration) {
89          this(field, prefix,
90               refMedian.shiftedBy(-0.5 * refDuration),
91               refMedian.shiftedBy(+0.5 * refDuration));
92      }
93  
94      /** Build a new instance.
95       * @param field field to which the elements belong
96       * @param prefix prefix to use for parameter drivers names
97       * @param refStart reference interval start date
98       * @param refStop reference interval stop date
99       */
100     public FieldParameterDrivenDateIntervalDetector(final Field<T> field, final String prefix,
101                                                     final AbsoluteDate refStart, final AbsoluteDate refStop) {
102         this(new FieldEventDetectionSettings<>(field, EventDetectionSettings.getDefaultEventDetectionSettings()),
103              new FieldStopOnEvent<>(),
104              new DateDriver(refStart, prefix + START_SUFFIX, true),
105              new DateDriver(refStop, prefix + STOP_SUFFIX, false),
106              new DateDriver(refStart.shiftedBy(0.5 * refStop.durationFrom(refStart)), prefix + MEDIAN_SUFFIX, true),
107              new ParameterDriver(prefix + DURATION_SUFFIX, refStop.durationFrom(refStart), 1.0, 0.0, Double.POSITIVE_INFINITY));
108     }
109 
110     /** Protected constructor with full parameters.
111      * <p>
112      * This constructor is not public as users are expected to use the builder
113      * API with the various {@code withXxx()} methods to set up the instance
114      * in a readable manner without using a huge amount of parameters.
115      * </p>
116      * @param detectionSettings event detection settings
117      * @param handler event handler to call at event occurrences
118      * @param start reference interval start driver
119      * @param stop reference interval stop driver
120      * @param median median date driver
121      * @param duration duration driver
122      * @since 13.0
123      */
124     protected FieldParameterDrivenDateIntervalDetector(final FieldEventDetectionSettings<T> detectionSettings,
125                                                        final FieldEventHandler<T> handler,
126                                                        final DateDriver start, final DateDriver stop,
127                                                        final DateDriver median, final ParameterDriver duration) {
128         super(detectionSettings, handler);
129         this.start    = start;
130         this.stop     = stop;
131         this.median   = median;
132         this.duration = duration;
133 
134         // set up delegation between drivers
135         replaceBindingObserver(start,    new StartObserver());
136         replaceBindingObserver(stop,     new StopObserver());
137         replaceBindingObserver(median,   new MedianObserver());
138         replaceBindingObserver(duration, new DurationObserver());
139 
140     }
141 
142     /** Replace binding observers.
143      * @param driver driver for whose binding observers should be replaced
144      * @param bindingObserver new binding observer
145      */
146     private void replaceBindingObserver(final ParameterDriver driver, final BindingObserver bindingObserver) {
147 
148         // remove the previous binding observers
149         final List<ParameterObserver> original = driver.
150                                                  getObservers().
151                                                  stream().
152                                                  filter(observer -> observer instanceof FieldParameterDrivenDateIntervalDetector.BindingObserver).
153                 toList();
154         original.forEach(driver::removeObserver);
155 
156         driver.addObserver(bindingObserver);
157 
158     }
159 
160     /** {@inheritDoc} */
161     @Override
162     protected FieldParameterDrivenDateIntervalDetector<T> create(final FieldEventDetectionSettings<T> detectionSettings,
163                                                                  final FieldEventHandler<T> newHandler) {
164         return new FieldParameterDrivenDateIntervalDetector<>(detectionSettings, newHandler,
165                                                               start, stop, median, duration);
166     }
167 
168     /** Get the driver for start date.
169      * <p>
170      * Note that the start date is automatically adjusted if either
171      * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
172      * are {@link ParameterDriver#isSelected() selected} and changed.
173      * </p>
174      * @return driver for start date
175      */
176     public DateDriver getStartDriver() {
177         return start;
178     }
179 
180     /** Get the driver for stop date.
181      * <p>
182      * Note that the stop date is automatically adjusted if either
183      * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
184      * are {@link ParameterDriver#isSelected() selected} changed.
185      * </p>
186      * @return driver for stop date
187      */
188     public DateDriver getStopDriver() {
189         return stop;
190     }
191 
192     /** Get the driver for median date.
193      * <p>
194      * Note that the median date is automatically adjusted if either
195      * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
196      * are {@link ParameterDriver#isSelected() selected} changed.
197      * </p>
198      * @return driver for median date
199      */
200     public DateDriver getMedianDriver() {
201         return median;
202     }
203 
204     /** Get the driver for duration.
205      * <p>
206      * Note that the duration is automatically adjusted if either
207      * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
208      * are {@link ParameterDriver#isSelected() selected} changed.
209      * </p>
210      * @return driver for duration
211      */
212     public ParameterDriver getDurationDriver() {
213         return duration;
214     }
215 
216     @Override
217     public EventFunction getEventFunction() {
218         return new ParameterDrivenDateIntervalEventFunction(start.getDate(), stop.getDate());
219     }
220 
221     /** Compute the value of the switching function.
222      * <p>
223      * The function is positive for dates within the interval defined
224      * by applying the parameter drivers shifts to reference dates,
225      * and negative for dates outside of this interval. Note that
226      * if Δt_start - Δt_stop is less than ref_stop.durationFrom(ref_start),
227      * then the interval degenerates to empty and the function never
228      * reaches positive values.
229      * </p>
230      * @param s the current state information: date, kinematics, attitude
231      * @return value of the switching function
232      */
233     @Override
234     public T g(final FieldSpacecraftState<T> s) {
235         return getEventFunction().value(s);
236     }
237 
238     /** {@inheritDoc} */
239     @Override
240     public ParameterDrivenDateIntervalDetector toEventDetector(final EventHandler eventHandler) {
241         return new ParameterDrivenDateIntervalDetector(getDetectionSettings().toEventDetectionSettings(), eventHandler,
242                 getStartDriver(), getStopDriver(), getMedianDriver(), getDurationDriver());
243     }
244 
245     /** Base observer. */
246     private abstract class BindingObserver implements ParameterObserver {
247 
248         /** {@inheritDoc} */
249         @Override
250         public void valueChanged(final double previousValue, final ParameterDriver driver, final AbsoluteDate date) {
251             if (driver.isSelected()) {
252                 setDelta(driver.getValue(date) - previousValue, date);
253             }
254         }
255 
256         /** {@inheritDoc} */
257         @Override
258         public void valueSpanMapChanged(final TimeSpanMap<Double> previousValue, final ParameterDriver driver) {
259             if (driver.isSelected()) {
260                 for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
261                     setDelta(span.getData() - previousValue.get(span.getStart()), span.getStart());
262                 }
263             }
264         }
265 
266         /** {@inheritDoc} */
267         @Override
268         public void selectionChanged(final boolean previousSelection, final ParameterDriver driver) {
269             if ((start.isSelected()  || stop.isSelected()) &&
270                 (median.isSelected() || duration.isSelected())) {
271                 throw new OrekitException(OrekitMessages.INCONSISTENT_SELECTION,
272                                           start.getName(), stop.getName(),
273                                           median.getName(), duration.getName());
274             }
275         }
276 
277         /** Change a value.
278          * @param date date for which the value wants to be change
279          * @param delta change of value
280          */
281         protected abstract void setDelta(double delta, AbsoluteDate date);
282 
283     }
284 
285     /** Observer for start date. */
286     private class StartObserver extends BindingObserver {
287         /** {@inheritDoc} */
288         @Override
289         protected void setDelta(final double delta, final AbsoluteDate date) {
290             median.setValue(median.getValue(date) + 0.5 * delta, date);
291             duration.setValue(duration.getValue(date) - delta, date);
292         }
293     }
294 
295     /** Observer for stop date. */
296     private class StopObserver extends BindingObserver {
297         /** {@inheritDoc} */
298         @Override
299         protected void setDelta(final double delta, final AbsoluteDate date) {
300             median.setValue(median.getValue(date) + 0.5 * delta, date);
301             duration.setValue(duration.getValue(date) + delta, date);
302         }
303     }
304 
305     /** Observer for median date. */
306     private class MedianObserver extends BindingObserver {
307         /** {@inheritDoc} */
308         @Override
309         protected void setDelta(final double delta, final AbsoluteDate date) {
310             start.setValue(start.getValue(date) + delta, date);
311             stop.setValue(stop.getValue(date) + delta, date);
312         }
313     }
314 
315     /** Observer for duration. */
316     private class DurationObserver extends BindingObserver {
317         /** {@inheritDoc} */
318         @Override
319         protected void setDelta(final double delta, final AbsoluteDate date) {
320             start.setValue(start.getValue(date) - 0.5 * delta, date);
321             stop.setValue(stop.getValue(date) + 0.5 * delta, date);
322         }
323     }
324 
325     private static class ParameterDrivenDateIntervalEventFunction extends TimeIntervalEventFunction {
326 
327         /**
328          * Constructor.
329          * @param startDate start date
330          * @param endDate end date
331          */
332         ParameterDrivenDateIntervalEventFunction(final AbsoluteDate startDate, final AbsoluteDate endDate) {
333             super(new TimeInterval() {
334                 // unsafe implementation without sorting check
335                 @Override
336                 public AbsoluteDate getStartDate() {
337                     return startDate;
338                 }
339 
340                 @Override
341                 public AbsoluteDate getEndDate() {
342                     return endDate;
343                 }
344             });
345         }
346     }
347 }