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.DateDriver;
32 import org.orekit.utils.ParameterDriver;
33 import org.orekit.utils.ParameterObserver;
34 import org.orekit.utils.TimeSpanMap;
35 import org.orekit.utils.TimeSpanMap.Span;
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>. Warning, startDate driver, stopDate driver, duration driver and medianDate driver
47 * must all have the same number of values to estimate (same number of span in valueSpanMap), that is is to
48 * say that the {@link org.orekit.utils.ParameterDriver#addSpans(AbsoluteDate, AbsoluteDate, double)}
49 * should be called with same arguments.
50 * @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
51 * @author Luc Maisonobe
52 * @since 11.1
53 */
54 public class ParameterDrivenDateIntervalDetector extends AbstractDetector<ParameterDrivenDateIntervalDetector> {
55
56 /** Default suffix for start driver. */
57 public static final String START_SUFFIX = "_START";
58
59 /** Default suffix for stop driver. */
60 public static final String STOP_SUFFIX = "_STOP";
61
62 /** Default suffix for median driver. */
63 public static final String MEDIAN_SUFFIX = "_MEDIAN";
64
65 /** Default suffix for duration driver. */
66 public static final String DURATION_SUFFIX = "_DURATION";
67
68 /** Reference interval start driver. */
69 private final DateDriver start;
70
71 /** Reference interval stop driver. */
72 private final DateDriver stop;
73
74 /** Median date driver. */
75 private final DateDriver median;
76
77 /** Duration driver. */
78 private final ParameterDriver duration;
79
80 /** Build a new instance.
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 ParameterDrivenDateIntervalDetector(final String prefix,
86 final AbsoluteDate refMedian, final double refDuration) {
87 this(prefix,
88 refMedian.shiftedBy(-0.5 * refDuration),
89 refMedian.shiftedBy(0.5 * refDuration));
90 }
91
92 /** Build a new instance.
93 * @param prefix prefix to use for parameter drivers names
94 * @param refStart reference interval start date
95 * @param refStop reference interval stop date
96 */
97 public ParameterDrivenDateIntervalDetector(final String prefix,
98 final AbsoluteDate refStart, final AbsoluteDate refStop) {
99 this(getDefaultDetectionSettings(refStart, refStop),
100 new StopOnDecreasing(),
101 new DateDriver(refStart, prefix + START_SUFFIX, true),
102 new DateDriver(refStop, prefix + STOP_SUFFIX, false),
103 new DateDriver(refStart.shiftedBy(0.5 * refStop.durationFrom(refStart)), prefix + MEDIAN_SUFFIX, true),
104 new ParameterDriver(prefix + DURATION_SUFFIX, refStop.durationFrom(refStart), 1.0, 0.0, Double.POSITIVE_INFINITY));
105 }
106
107 /** Protected constructor with full parameters.
108 * <p>
109 * This constructor is not public as users are expected to use the builder
110 * API with the various {@code withXxx()} methods to set up the instance
111 * in a readable manner without using a huge amount of parameters.
112 * </p>
113 * @param detectionSettings event detection settings
114 * @param handler event handler to call at event occurrences
115 * @param start reference interval start driver
116 * @param stop reference interval stop driver
117 * @param median median date driver
118 * @param duration duration driver
119 * @since 13.0
120 */
121 protected ParameterDrivenDateIntervalDetector(final EventDetectionSettings detectionSettings,
122 final EventHandler handler,
123 final DateDriver start, final DateDriver stop,
124 final DateDriver median, final ParameterDriver duration) {
125 super(detectionSettings, handler);
126 this.start = start;
127 this.stop = stop;
128 this.median = median;
129 this.duration = duration;
130
131 // set up delegation between drivers
132 replaceBindingObserver(start, new StartObserver());
133 replaceBindingObserver(stop, new StopObserver());
134 replaceBindingObserver(median, new MedianObserver());
135 replaceBindingObserver(duration, new DurationObserver());
136
137 }
138
139 /**
140 * Get default detection settings.
141 * @param refStart reference interval start date
142 * @param refStop reference interval stop date
143 * @return default detection settings
144 * @since 13.0
145 */
146 public static EventDetectionSettings getDefaultDetectionSettings(final AbsoluteDate refStart,
147 final AbsoluteDate refStop) {
148 return new EventDetectionSettings(DateDetectionAdaptableIntervalFactory.getDatesDetectionInterval(refStart, refStop),
149 DateDetector.DEFAULT_THRESHOLD, EventDetectionSettings.DEFAULT_MAX_ITER);
150 }
151
152 /** Replace binding observers.
153 * @param driver driver for whose binding observers should be replaced
154 * @param bindingObserver new binding observer
155 */
156 private void replaceBindingObserver(final ParameterDriver driver, final BindingObserver bindingObserver) {
157
158 // remove the previous binding observers
159 final List<ParameterObserver> original = driver.
160 getObservers().
161 stream().
162 filter(observer -> observer instanceof ParameterDrivenDateIntervalDetector.BindingObserver).
163 toList();
164 original.forEach(driver::removeObserver);
165
166 driver.addObserver(bindingObserver);
167
168 }
169
170 /** {@inheritDoc} */
171 @Override
172 protected ParameterDrivenDateIntervalDetector create(final EventDetectionSettings detectionSettings,
173 final EventHandler newHandler) {
174 return new ParameterDrivenDateIntervalDetector(detectionSettings, newHandler, start, stop, median, duration);
175 }
176
177 /** Get the driver for start date.
178 * <p>
179 * Note that the start date is automatically adjusted if either
180 * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
181 * are {@link ParameterDriver#isSelected() selected} and changed.
182 * </p>
183 * @return driver for start date
184 */
185 public DateDriver getStartDriver() {
186 return start;
187 }
188
189 /** Get the driver for stop date.
190 * <p>
191 * Note that the stop date is automatically adjusted if either
192 * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
193 * are {@link ParameterDriver#isSelected() selected} changed.
194 * </p>
195 * @return driver for stop date
196 */
197 public DateDriver getStopDriver() {
198 return stop;
199 }
200
201 /** Get the driver for median date.
202 * <p>
203 * Note that the median date is automatically adjusted if either
204 * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
205 * are {@link ParameterDriver#isSelected() selected} changed.
206 * </p>
207 * @return driver for median date
208 */
209 public DateDriver getMedianDriver() {
210 return median;
211 }
212
213 /** Get the driver for duration.
214 * <p>
215 * Note that the duration is automatically adjusted if either
216 * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
217 * are {@link ParameterDriver#isSelected() selected} changed.
218 * </p>
219 * @return driver for duration
220 */
221 public ParameterDriver getDurationDriver() {
222 return duration;
223 }
224
225 @Override
226 public EventFunction getEventFunction() {
227 return new ParameterDrivenDateIntervalEventFunction(start.getDate(), stop.getDate());
228 }
229
230 /** Compute the value of the switching function.
231 * <p>
232 * The function is positive for dates within the interval defined
233 * by applying the parameter drivers shifts to reference dates,
234 * and negative for dates outside of this interval. Note that
235 * if Δt_start - Δt_stop is less than ref_stop.durationFrom(ref_start),
236 * then the interval degenerates to empty and the function never
237 * reaches positive values.
238 * </p>
239 * @param s the current state information: date, kinematics, attitude
240 * @return value of the switching function
241 */
242 public double g(final SpacecraftState s) {
243 return getEventFunction().value(s);
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 /** {@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 /** {@inheritDoc} */
266 @Override
267 public void selectionChanged(final boolean previousSelection, final ParameterDriver driver) {
268 if ((start.isSelected() || stop.isSelected()) &&
269 (median.isSelected() || duration.isSelected())) {
270 throw new OrekitException(OrekitMessages.INCONSISTENT_SELECTION,
271 start.getName(), stop.getName(),
272 median.getName(), duration.getName());
273 }
274 }
275
276 /** Change a value.
277 * @param delta change of value
278 * @param date date at which the delta wants to be set
279 */
280 protected abstract void setDelta(double delta, AbsoluteDate date);
281
282 }
283
284 /** Observer for start date. */
285 private class StartObserver extends BindingObserver {
286 /** {@inheritDoc} */
287 @Override
288 protected void setDelta(final double delta, final AbsoluteDate date) {
289 // date driver has no validity period, only 1 value is estimated
290 // over the all interval so there is no problem for calling getValue with null argument
291 // or any date, it would give the same result as there is only 1 span on the valueSpanMap
292 // of the driver
293 median.setValue(median.getValue(date) + 0.5 * delta, date);
294 duration.setValue(duration.getValue(date) - delta, date);
295 }
296 }
297
298 /** Observer for stop date. */
299 private class StopObserver extends BindingObserver {
300 /** {@inheritDoc} */
301 @Override
302 protected void setDelta(final double delta, final AbsoluteDate date) {
303 // date driver has no validity period, only 1 value is estimated
304 // over the all interval so there is no problem for calling getValue with null argument
305 // or any date, it would give the same result as there is only 1 span on the valueSpanMap
306 // of the driver
307 median.setValue(median.getValue(date) + 0.5 * delta, date);
308 duration.setValue(duration.getValue(date) + delta, date);
309 }
310 }
311
312 /** Observer for median date. */
313 private class MedianObserver extends BindingObserver {
314 /** {@inheritDoc} */
315 @Override
316 protected void setDelta(final double delta, final AbsoluteDate date) {
317 // date driver has no validity period, only 1 value is estimated
318 // over the all interval so there is no problem for calling getValue with null argument
319 // or any date, it would give the same result as there is only 1 span on the valueSpanMap
320 // of the driver
321 start.setValue(start.getValue(date) + delta, date);
322 stop.setValue(stop.getValue(date) + delta, date);
323 }
324 }
325
326 /** Observer for duration. */
327 private class DurationObserver extends BindingObserver {
328 /** {@inheritDoc} */
329 @Override
330 protected void setDelta(final double delta, final AbsoluteDate date) {
331 // date driver has no validity period, only 1 value is estimated
332 // over the all interval so there is no problem for calling getValue with null argument
333 // or any date, it would give the same result as there is only 1 span on the valueSpanMap
334 // of the driver
335 start.setValue(start.getValue(date) - 0.5 * delta, date);
336 stop.setValue(stop.getValue(date) + 0.5 * delta, date);
337 }
338 }
339
340 private static class ParameterDrivenDateIntervalEventFunction extends TimeIntervalEventFunction {
341
342 /**
343 * Constructor.
344 * @param startDate start date
345 * @param endDate end date
346 */
347 ParameterDrivenDateIntervalEventFunction(final AbsoluteDate startDate, final AbsoluteDate endDate) {
348 super(new TimeInterval() {
349 // unsafe implementation without sorting check
350 @Override
351 public AbsoluteDate getStartDate() {
352 return startDate;
353 }
354
355 @Override
356 public AbsoluteDate getEndDate() {
357 return endDate;
358 }
359 });
360 }
361 }
362 }