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