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