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