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