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.drivers.DateDriver;
34 import org.orekit.utils.drivers.ParameterDriver;
35 import org.orekit.utils.drivers.ParameterObserver;
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>
47 * @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
48 * @param <T> type of the field elements
49 * @author Luc Maisonobe
50 * @since 11.1
51 */
52 public class FieldParameterDrivenDateIntervalDetector<T extends CalculusFieldElement<T>>
53 extends FieldAbstractDetector<FieldParameterDrivenDateIntervalDetector<T>, T> {
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 field field to which the elements belong
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 FieldParameterDrivenDateIntervalDetector(final Field<T> field, final String prefix,
86 final AbsoluteDate refMedian, final double refDuration) {
87 this(field, prefix,
88 refMedian.shiftedBy(-0.5 * refDuration),
89 refMedian.shiftedBy(+0.5 * refDuration));
90 }
91
92 /** Build a new instance.
93 * @param field field to which the elements belong
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 FieldParameterDrivenDateIntervalDetector(final Field<T> field, final String prefix,
99 final AbsoluteDate refStart, final AbsoluteDate refStop) {
100 this(new FieldEventDetectionSettings<>(field, EventDetectionSettings.getDefaultEventDetectionSettings()),
101 new FieldStopOnEvent<>(),
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,
106 0.0, Double.POSITIVE_INFINITY, TimeInterval.UNLIMITED));
107 }
108
109 /** Protected constructor with full parameters.
110 * <p>
111 * This constructor is not public as users are expected to use the builder
112 * API with the various {@code withXxx()} methods to set up the instance
113 * in a readable manner without using a huge amount of parameters.
114 * </p>
115 * @param detectionSettings event detection settings
116 * @param handler event handler to call at event occurrences
117 * @param start reference interval start driver
118 * @param stop reference interval stop driver
119 * @param median median date driver
120 * @param duration duration driver
121 * @since 13.0
122 */
123 protected FieldParameterDrivenDateIntervalDetector(final FieldEventDetectionSettings<T> detectionSettings,
124 final FieldEventHandler<T> handler,
125 final DateDriver start, final DateDriver stop,
126 final DateDriver median, final ParameterDriver duration) {
127 super(detectionSettings, handler);
128 this.start = start;
129 this.stop = stop;
130 this.median = median;
131 this.duration = duration;
132
133 // set up delegation between drivers
134 replaceBindingObserver(start, new StartObserver());
135 replaceBindingObserver(stop, new StopObserver());
136 replaceBindingObserver(median, new MedianObserver());
137 replaceBindingObserver(duration, new DurationObserver());
138
139 }
140
141 /** Replace binding observers.
142 * @param driver driver for whose binding observers should be replaced
143 * @param bindingObserver new binding observer
144 */
145 private void replaceBindingObserver(final ParameterDriver driver, final BindingObserver bindingObserver) {
146
147 // remove the previous binding observers
148 final List<ParameterObserver> original = driver.
149 getObservers().
150 stream().
151 filter(observer -> observer instanceof FieldParameterDrivenDateIntervalDetector.BindingObserver).
152 toList();
153 original.forEach(driver::removeObserver);
154
155 driver.addObserver(bindingObserver);
156
157 }
158
159 /** {@inheritDoc} */
160 @Override
161 protected FieldParameterDrivenDateIntervalDetector<T> create(final FieldEventDetectionSettings<T> detectionSettings,
162 final FieldEventHandler<T> newHandler) {
163 return new FieldParameterDrivenDateIntervalDetector<>(detectionSettings, newHandler,
164 start, stop, median, duration);
165 }
166
167 /** Get the driver for start date.
168 * <p>
169 * Note that the start date is automatically adjusted if either
170 * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
171 * are {@link ParameterDriver#isSelected() selected} and changed.
172 * </p>
173 * @return driver for start date
174 */
175 public DateDriver getStartDriver() {
176 return start;
177 }
178
179 /** Get the driver for stop date.
180 * <p>
181 * Note that the stop date is automatically adjusted if either
182 * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
183 * are {@link ParameterDriver#isSelected() selected} changed.
184 * </p>
185 * @return driver for stop date
186 */
187 public DateDriver getStopDriver() {
188 return stop;
189 }
190
191 /** Get the driver for median date.
192 * <p>
193 * Note that the median date is automatically adjusted if either
194 * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
195 * are {@link ParameterDriver#isSelected() selected} changed.
196 * </p>
197 * @return driver for median date
198 */
199 public DateDriver getMedianDriver() {
200 return median;
201 }
202
203 /** Get the driver for duration.
204 * <p>
205 * Note that the duration is automatically adjusted if either
206 * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
207 * are {@link ParameterDriver#isSelected() selected} changed.
208 * </p>
209 * @return driver for duration
210 */
211 public ParameterDriver getDurationDriver() {
212 return duration;
213 }
214
215 @Override
216 public EventFunction getEventFunction() {
217 return new ParameterDrivenDateIntervalEventFunction(start.getDate(), stop.getDate());
218 }
219
220 /** Compute the value of the switching function.
221 * <p>
222 * The function is positive for dates within the interval defined
223 * by applying the parameter drivers shifts to reference dates,
224 * and negative for dates outside of this interval. Note that
225 * if Δt_start - Δt_stop is less than ref_stop.durationFrom(ref_start),
226 * then the interval degenerates to empty and the function never
227 * reaches positive values.
228 * </p>
229 * @param s the current state information: date, kinematics, attitude
230 * @return value of the switching function
231 */
232 @Override
233 public T g(final FieldSpacecraftState<T> s) {
234 return getEventFunction().value(s);
235 }
236
237 /** {@inheritDoc} */
238 @Override
239 public ParameterDrivenDateIntervalDetector toEventDetector(final EventHandler eventHandler) {
240 return new ParameterDrivenDateIntervalDetector(getDetectionSettings().toEventDetectionSettings(), eventHandler,
241 getStartDriver(), getStopDriver(), getMedianDriver(), getDurationDriver());
242 }
243
244 /** Base observer. */
245 private abstract class BindingObserver implements ParameterObserver {
246
247 /** {@inheritDoc} */
248 @Override
249 public void valueChanged(final double previousValue, final ParameterDriver driver) {
250 if (driver.isSelected()) {
251 setDelta(driver.getValue() - previousValue);
252 }
253 }
254
255 /** {@inheritDoc} */
256 @Override
257 public void selectionChanged(final boolean previousSelection, final ParameterDriver driver) {
258 if ((start.isSelected() || stop.isSelected()) &&
259 (median.isSelected() || duration.isSelected())) {
260 throw new OrekitException(OrekitMessages.INCONSISTENT_SELECTION,
261 start.getName(), stop.getName(),
262 median.getName(), duration.getName());
263 }
264 }
265
266 /** Change a value.
267 * @param delta change of value
268 */
269 protected abstract void setDelta(double delta);
270
271 }
272
273 /** Observer for start date. */
274 private class StartObserver extends BindingObserver {
275 /** {@inheritDoc} */
276 @Override
277 protected void setDelta(final double delta) {
278 median.setValue(median.getValue() + 0.5 * delta);
279 duration.setValue(duration.getValue() - delta);
280 }
281 }
282
283 /** Observer for stop date. */
284 private class StopObserver extends BindingObserver {
285 /** {@inheritDoc} */
286 @Override
287 protected void setDelta(final double delta) {
288 median.setValue(median.getValue() + 0.5 * delta);
289 duration.setValue(duration.getValue() + delta);
290 }
291 }
292
293 /** Observer for median date. */
294 private class MedianObserver extends BindingObserver {
295 /** {@inheritDoc} */
296 @Override
297 protected void setDelta(final double delta) {
298 start.setValue(start.getValue() + delta);
299 stop.setValue(stop.getValue() + delta);
300 }
301 }
302
303 /** Observer for duration. */
304 private class DurationObserver extends BindingObserver {
305 /** {@inheritDoc} */
306 @Override
307 protected void setDelta(final double delta) {
308 start.setValue(start.getValue() - 0.5 * delta);
309 stop.setValue(stop.getValue() + 0.5 * delta);
310 }
311 }
312
313 private static class ParameterDrivenDateIntervalEventFunction extends TimeIntervalEventFunction {
314
315 /**
316 * Constructor.
317 * @param startDate start date
318 * @param endDate end date
319 */
320 ParameterDrivenDateIntervalEventFunction(final AbsoluteDate startDate, final AbsoluteDate endDate) {
321 super(new TimeInterval() {
322 // unsafe implementation without sorting check
323 @Override
324 public AbsoluteDate getStartDate() {
325 return startDate;
326 }
327
328 @Override
329 public AbsoluteDate getEndDate() {
330 return endDate;
331 }
332 });
333 }
334 }
335 }