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