1 /* Copyright 2002-2024 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.CalculusFieldElement;
23 import org.hipparchus.Field;
24 import org.hipparchus.util.FastMath;
25 import org.orekit.errors.OrekitException;
26 import org.orekit.errors.OrekitMessages;
27 import org.orekit.propagation.FieldSpacecraftState;
28 import org.orekit.propagation.events.handlers.FieldEventHandler;
29 import org.orekit.propagation.events.handlers.FieldStopOnEvent;
30 import org.orekit.time.AbsoluteDate;
31 import org.orekit.utils.DateDriver;
32 import org.orekit.utils.ParameterDriver;
33 import org.orekit.utils.ParameterObserver;
34 import org.orekit.utils.TimeSpanMap;
35 import org.orekit.utils.TimeSpanMap.Span;
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 DateDriver start;
69
70 /** Reference interval stop driver. */
71 private DateDriver stop;
72
73 /** Median date driver. */
74 private DateDriver median;
75
76 /** Duration driver. */
77 private 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(FieldAdaptableInterval.of(DEFAULT_MAXCHECK),
101 field.getZero().newInstance(DEFAULT_THRESHOLD),
102 DEFAULT_MAX_ITER,
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 maxCheck maximum checking interval
117 * @param threshold convergence threshold (s)
118 * @param maxIter maximum number of iterations in the event time search
119 * @param handler event handler to call at event occurrences
120 * @param start reference interval start driver
121 * @param stop reference interval stop driver
122 * @param median median date driver
123 * @param duration duration driver
124 */
125 protected FieldParameterDrivenDateIntervalDetector(final FieldAdaptableInterval<T> maxCheck, final T threshold, final int maxIter,
126 final FieldEventHandler<T> handler,
127 final DateDriver start, final DateDriver stop,
128 final DateDriver median, final ParameterDriver duration) {
129 super(maxCheck, threshold, maxIter, handler);
130 this.start = start;
131 this.stop = stop;
132 this.median = median;
133 this.duration = duration;
134
135 // set up delegation between drivers
136 replaceBindingObserver(start, new StartObserver());
137 replaceBindingObserver(stop, new StopObserver());
138 replaceBindingObserver(median, new MedianObserver());
139 replaceBindingObserver(duration, new DurationObserver());
140
141 }
142
143 /** Replace binding observers.
144 * @param driver driver for whose binding observers should be replaced
145 * @param bindingObserver new binding observer
146 */
147 private void replaceBindingObserver(final ParameterDriver driver, final BindingObserver bindingObserver) {
148
149 // remove the previous binding observers
150 final List<ParameterObserver> original = driver.
151 getObservers().
152 stream().
153 filter(observer -> observer instanceof FieldParameterDrivenDateIntervalDetector.BindingObserver).
154 collect(Collectors.toList());
155 original.forEach(observer -> driver.removeObserver(observer));
156
157 driver.addObserver(bindingObserver);
158
159 }
160
161 /** {@inheritDoc} */
162 @Override
163 protected FieldParameterDrivenDateIntervalDetector<T> create(final FieldAdaptableInterval<T> newMaxCheck, final T newThreshold, final int newMaxIter,
164 final FieldEventHandler<T> newHandler) {
165 return new FieldParameterDrivenDateIntervalDetector<>(newMaxCheck, newThreshold, newMaxIter, newHandler,
166 start, stop, median, duration);
167 }
168
169 /** Get the driver for start date.
170 * <p>
171 * Note that the start date is automatically adjusted if either
172 * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
173 * are {@link ParameterDriver#isSelected() selected} and changed.
174 * </p>
175 * @return driver for start date
176 */
177 public DateDriver getStartDriver() {
178 return start;
179 }
180
181 /** Get the driver for stop date.
182 * <p>
183 * Note that the stop date is automatically adjusted if either
184 * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
185 * are {@link ParameterDriver#isSelected() selected} changed.
186 * </p>
187 * @return driver for stop date
188 */
189 public DateDriver getStopDriver() {
190 return stop;
191 }
192
193 /** Get the driver for median date.
194 * <p>
195 * Note that the median date is automatically adjusted if either
196 * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
197 * are {@link ParameterDriver#isSelected() selected} changed.
198 * </p>
199 * @return driver for median date
200 */
201 public DateDriver getMedianDriver() {
202 return median;
203 }
204
205 /** Get the driver for duration.
206 * <p>
207 * Note that the duration is automatically adjusted if either
208 * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
209 * are {@link ParameterDriver#isSelected() selected} changed.
210 * </p>
211 * @return driver for duration
212 */
213 public ParameterDriver getDurationDriver() {
214 return duration;
215 }
216
217 /** Compute the value of the switching function.
218 * <p>
219 * The function is positive for dates within the interval defined
220 * by applying the parameter drivers shifts to reference dates,
221 * and negative for dates outside of this interval. Note that
222 * if Δt_start - Δt_stop is less than ref_stop.durationFrom(ref_start),
223 * then the interval degenerates to empty and the function never
224 * reaches positive values.
225 * </p>
226 * @param s the current state information: date, kinematics, attitude
227 * @return value of the switching function
228 */
229 public T g(final FieldSpacecraftState<T> s) {
230 return FastMath.min(s.getDate().durationFrom(start.getDate()),
231 s.getDate().durationFrom(stop.getDate()).negate());
232 }
233
234 /** Base observer. */
235 private abstract class BindingObserver implements ParameterObserver {
236
237 /** {@inheritDoc} */
238 @Override
239 public void valueChanged(final double previousValue, final ParameterDriver driver, final AbsoluteDate date) {
240 if (driver.isSelected()) {
241 setDelta(driver.getValue(date) - previousValue, date);
242 }
243 }
244
245 /** {@inheritDoc} */
246 @Override
247 public void valueSpanMapChanged(final TimeSpanMap<Double> previousValue, final ParameterDriver driver) {
248 if (driver.isSelected()) {
249 for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
250 setDelta(span.getData() - previousValue.get(span.getStart()), span.getStart());
251 }
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 date date for which the value wants to be change
268 * @param delta change of value
269 */
270 protected abstract void setDelta(double delta, AbsoluteDate date);
271
272 }
273
274 /** Observer for start date. */
275 private class StartObserver extends BindingObserver {
276 /** {@inheritDoc} */
277 @Override
278 protected void setDelta(final double delta, final AbsoluteDate date) {
279 median.setValue(median.getValue(date) + 0.5 * delta, date);
280 duration.setValue(duration.getValue(date) - delta, date);
281 }
282 }
283
284 /** Observer for stop date. */
285 private class StopObserver extends BindingObserver {
286 /** {@inheritDoc} */
287 @Override
288 protected void setDelta(final double delta, final AbsoluteDate date) {
289 median.setValue(median.getValue(date) + 0.5 * delta, date);
290 duration.setValue(duration.getValue(date) + delta, date);
291 }
292 }
293
294 /** Observer for median date. */
295 private class MedianObserver extends BindingObserver {
296 /** {@inheritDoc} */
297 @Override
298 protected void setDelta(final double delta, final AbsoluteDate date) {
299 start.setValue(start.getValue(date) + delta, date);
300 stop.setValue(stop.getValue(date) + delta, date);
301 }
302 }
303
304 /** Observer for duration. */
305 private class DurationObserver extends BindingObserver {
306 /** {@inheritDoc} */
307 @Override
308 protected void setDelta(final double delta, final AbsoluteDate date) {
309 start.setValue(start.getValue(date) - 0.5 * delta, date);
310 stop.setValue(stop.getValue(date) + 0.5 * delta, date);
311 }
312 }
313
314 }