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 }