1 /* Copyright 2002-2025 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.orekit.errors.OrekitException;
23 import org.orekit.errors.OrekitMessages;
24 import org.orekit.propagation.SpacecraftState;
25 import org.orekit.propagation.events.functions.EventFunction;
26 import org.orekit.propagation.events.functions.TimeIntervalEventFunction;
27 import org.orekit.propagation.events.handlers.EventHandler;
28 import org.orekit.propagation.events.handlers.StopOnDecreasing;
29 import org.orekit.propagation.events.intervals.DateDetectionAdaptableIntervalFactory;
30 import org.orekit.time.AbsoluteDate;
31 import org.orekit.time.TimeInterval;
32 import org.orekit.utils.DateDriver;
33 import org.orekit.utils.ParameterDriver;
34 import org.orekit.utils.ParameterObserver;
35 import org.orekit.utils.TimeSpanMap;
36 import org.orekit.utils.TimeSpanMap.Span;
37
38 /** Detector for date intervals that may be offset thanks to parameter drivers.
39 * <p>
40 * Two dual views can be used for date intervals: either start date/stop date or
41 * median date/duration. {@link #getStartDriver() start}/{@link #getStopDriver() stop}
42 * drivers and {@link #getMedianDriver() median}/{@link #getDurationDriver() duration}
43 * drivers work in pair. Both drivers in one pair can be selected and their changes will
44 * be propagated to the other pair, but attempting to select drivers in both
45 * pairs at the same time will trigger an exception. Changing the value of a driver
46 * that is not selected should be avoided as it leads to inconsistencies between the pairs.
47 * </p>. Warning, startDate driver, stopDate driver, duration driver and medianDate driver
48 * must all have the same number of values to estimate (same number of span in valueSpanMap), that is is to
49 * say that the {@link org.orekit.utils.ParameterDriver#addSpans(AbsoluteDate, AbsoluteDate, double)}
50 * should be called with same arguments.
51 * @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
52 * @author Luc Maisonobe
53 * @since 11.1
54 */
55 public class ParameterDrivenDateIntervalDetector extends AbstractDetector<ParameterDrivenDateIntervalDetector> {
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 prefix prefix to use for parameter drivers names
83 * @param refMedian reference interval median date
84 * @param refDuration reference duration
85 */
86 public ParameterDrivenDateIntervalDetector(final String prefix,
87 final AbsoluteDate refMedian, final double refDuration) {
88 this(prefix,
89 refMedian.shiftedBy(-0.5 * refDuration),
90 refMedian.shiftedBy(0.5 * refDuration));
91 }
92
93 /** Build a new instance.
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 ParameterDrivenDateIntervalDetector(final String prefix,
99 final AbsoluteDate refStart, final AbsoluteDate refStop) {
100 this(getDefaultDetectionSettings(refStart, refStop),
101 new StopOnDecreasing(),
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, 0.0, Double.POSITIVE_INFINITY));
106 }
107
108 /** Protected constructor with full parameters.
109 * <p>
110 * This constructor is not public as users are expected to use the builder
111 * API with the various {@code withXxx()} methods to set up the instance
112 * in a readable manner without using a huge amount of parameters.
113 * </p>
114 * @param detectionSettings event detection settings
115 * @param handler event handler to call at event occurrences
116 * @param start reference interval start driver
117 * @param stop reference interval stop driver
118 * @param median median date driver
119 * @param duration duration driver
120 * @since 13.0
121 */
122 protected ParameterDrivenDateIntervalDetector(final EventDetectionSettings detectionSettings,
123 final EventHandler handler,
124 final DateDriver start, final DateDriver stop,
125 final DateDriver median, final ParameterDriver duration) {
126 super(detectionSettings, handler);
127 this.start = start;
128 this.stop = stop;
129 this.median = median;
130 this.duration = duration;
131
132 // set up delegation between drivers
133 replaceBindingObserver(start, new StartObserver());
134 replaceBindingObserver(stop, new StopObserver());
135 replaceBindingObserver(median, new MedianObserver());
136 replaceBindingObserver(duration, new DurationObserver());
137
138 }
139
140 /**
141 * Get default detection settings.
142 * @param refStart reference interval start date
143 * @param refStop reference interval stop date
144 * @return default detection settings
145 * @since 13.0
146 */
147 public static EventDetectionSettings getDefaultDetectionSettings(final AbsoluteDate refStart,
148 final AbsoluteDate refStop) {
149 return new EventDetectionSettings(DateDetectionAdaptableIntervalFactory.getDatesDetectionInterval(refStart, refStop),
150 DateDetector.DEFAULT_THRESHOLD, EventDetectionSettings.DEFAULT_MAX_ITER);
151 }
152
153 /** Replace binding observers.
154 * @param driver driver for whose binding observers should be replaced
155 * @param bindingObserver new binding observer
156 */
157 private void replaceBindingObserver(final ParameterDriver driver, final BindingObserver bindingObserver) {
158
159 // remove the previous binding observers
160 final List<ParameterObserver> original = driver.
161 getObservers().
162 stream().
163 filter(observer -> observer instanceof ParameterDrivenDateIntervalDetector.BindingObserver).
164 collect(Collectors.toList());
165 original.forEach(driver::removeObserver);
166
167 driver.addObserver(bindingObserver);
168
169 }
170
171 /** {@inheritDoc} */
172 @Override
173 protected ParameterDrivenDateIntervalDetector create(final EventDetectionSettings detectionSettings,
174 final EventHandler newHandler) {
175 return new ParameterDrivenDateIntervalDetector(detectionSettings, newHandler, start, stop, median, duration);
176 }
177
178 /** Get the driver for start date.
179 * <p>
180 * Note that the start date is automatically adjusted if either
181 * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
182 * are {@link ParameterDriver#isSelected() selected} and changed.
183 * </p>
184 * @return driver for start date
185 */
186 public DateDriver getStartDriver() {
187 return start;
188 }
189
190 /** Get the driver for stop date.
191 * <p>
192 * Note that the stop date is automatically adjusted if either
193 * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
194 * are {@link ParameterDriver#isSelected() selected} changed.
195 * </p>
196 * @return driver for stop date
197 */
198 public DateDriver getStopDriver() {
199 return stop;
200 }
201
202 /** Get the driver for median date.
203 * <p>
204 * Note that the median date is automatically adjusted if either
205 * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
206 * are {@link ParameterDriver#isSelected() selected} changed.
207 * </p>
208 * @return driver for median date
209 */
210 public DateDriver getMedianDriver() {
211 return median;
212 }
213
214 /** Get the driver for duration.
215 * <p>
216 * Note that the duration is automatically adjusted if either
217 * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
218 * are {@link ParameterDriver#isSelected() selected} changed.
219 * </p>
220 * @return driver for duration
221 */
222 public ParameterDriver getDurationDriver() {
223 return duration;
224 }
225
226 @Override
227 public EventFunction getEventFunction() {
228 return new ParameterDrivenDateIntervalEventFunction(start.getDate(), stop.getDate());
229 }
230
231 /** Compute the value of the switching function.
232 * <p>
233 * The function is positive for dates within the interval defined
234 * by applying the parameter drivers shifts to reference dates,
235 * and negative for dates outside of this interval. Note that
236 * if Δt_start - Δt_stop is less than ref_stop.durationFrom(ref_start),
237 * then the interval degenerates to empty and the function never
238 * reaches positive values.
239 * </p>
240 * @param s the current state information: date, kinematics, attitude
241 * @return value of the switching function
242 */
243 public double g(final SpacecraftState s) {
244 return getEventFunction().value(s);
245 }
246
247 /** Base observer. */
248 private abstract class BindingObserver implements ParameterObserver {
249
250 /** {@inheritDoc} */
251 @Override
252 public void valueChanged(final double previousValue, final ParameterDriver driver, final AbsoluteDate date) {
253 if (driver.isSelected()) {
254 setDelta(driver.getValue(date) - previousValue, date);
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 /** {@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 delta change of value
279 * @param date date at which the delta wants to be set
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 // date driver has no validity period, only 1 value is estimated
291 // over the all interval so there is no problem for calling getValue with null argument
292 // or any date, it would give the same result as there is only 1 span on the valueSpanMap
293 // of the driver
294 median.setValue(median.getValue(date) + 0.5 * delta, date);
295 duration.setValue(duration.getValue(date) - delta, date);
296 }
297 }
298
299 /** Observer for stop date. */
300 private class StopObserver extends BindingObserver {
301 /** {@inheritDoc} */
302 @Override
303 protected void setDelta(final double delta, final AbsoluteDate date) {
304 // date driver has no validity period, only 1 value is estimated
305 // over the all interval so there is no problem for calling getValue with null argument
306 // or any date, it would give the same result as there is only 1 span on the valueSpanMap
307 // of the driver
308 median.setValue(median.getValue(date) + 0.5 * delta, date);
309 duration.setValue(duration.getValue(date) + delta, date);
310 }
311 }
312
313 /** Observer for median date. */
314 private class MedianObserver extends BindingObserver {
315 /** {@inheritDoc} */
316 @Override
317 protected void setDelta(final double delta, final AbsoluteDate date) {
318 // date driver has no validity period, only 1 value is estimated
319 // over the all interval so there is no problem for calling getValue with null argument
320 // or any date, it would give the same result as there is only 1 span on the valueSpanMap
321 // of the driver
322 start.setValue(start.getValue(date) + delta, date);
323 stop.setValue(stop.getValue(date) + delta, date);
324 }
325 }
326
327 /** Observer for duration. */
328 private class DurationObserver extends BindingObserver {
329 /** {@inheritDoc} */
330 @Override
331 protected void setDelta(final double delta, final AbsoluteDate date) {
332 // date driver has no validity period, only 1 value is estimated
333 // over the all interval so there is no problem for calling getValue with null argument
334 // or any date, it would give the same result as there is only 1 span on the valueSpanMap
335 // of the driver
336 start.setValue(start.getValue(date) - 0.5 * delta, date);
337 stop.setValue(stop.getValue(date) + 0.5 * delta, date);
338 }
339 }
340
341 private static class ParameterDrivenDateIntervalEventFunction extends TimeIntervalEventFunction {
342
343 /**
344 * Constructor.
345 * @param startDate start date
346 * @param endDate end date
347 */
348 ParameterDrivenDateIntervalEventFunction(final AbsoluteDate startDate, final AbsoluteDate endDate) {
349 super(new TimeInterval() {
350 // unsafe implementation without sorting check
351 @Override
352 public AbsoluteDate getStartDate() {
353 return startDate;
354 }
355
356 @Override
357 public AbsoluteDate getEndDate() {
358 return endDate;
359 }
360 });
361 }
362 }
363 }