1 /* Copyright 2002-2022 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
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 * @param <T> type of the field elements
47 * @author Luc Maisonobe
48 * @since 11.1
49 */
50 public class FieldParameterDrivenDateIntervalDetector<T extends CalculusFieldElement<T>> extends FieldAbstractDetector<FieldParameterDrivenDateIntervalDetector<T>, T> {
51
52 /** Default suffix for start driver. */
53 public static final String START_SUFFIX = "_START";
54
55 /** Default suffix for stop driver. */
56 public static final String STOP_SUFFIX = "_STOP";
57
58 /** Default suffix for median driver. */
59 public static final String MEDIAN_SUFFIX = "_MEDIAN";
60
61 /** Default suffix for duration driver. */
62 public static final String DURATION_SUFFIX = "_DURATION";
63
64 /** Reference interval start driver. */
65 private DateDriver start;
66
67 /** Reference interval stop driver. */
68 private DateDriver stop;
69
70 /** Median date driver. */
71 private DateDriver median;
72
73 /** Duration driver. */
74 private ParameterDriver duration;
75
76 /** Build a new instance.
77 * @param field field to which the elements belong
78 * @param prefix prefix to use for parameter drivers names
79 * @param refMedian reference interval median date
80 * @param refDuration reference duration
81 */
82 public FieldParameterDrivenDateIntervalDetector(final Field<T> field, final String prefix,
83 final AbsoluteDate refMedian, final double refDuration) {
84 this(field, prefix,
85 refMedian.shiftedBy(-0.5 * refDuration),
86 refMedian.shiftedBy(+0.5 * refDuration));
87 }
88
89 /** Build a new instance.
90 * @param field field to which the elements belong
91 * @param prefix prefix to use for parameter drivers names
92 * @param refStart reference interval start date
93 * @param refStop reference interval stop date
94 */
95 public FieldParameterDrivenDateIntervalDetector(final Field<T> field, final String prefix,
96 final AbsoluteDate refStart, final AbsoluteDate refStop) {
97 this(field.getZero().newInstance(DEFAULT_MAXCHECK),
98 field.getZero().newInstance(DEFAULT_THRESHOLD),
99 DEFAULT_MAX_ITER,
100 new FieldStopOnEvent<FieldParameterDrivenDateIntervalDetector<T>, T>(),
101 new DateDriver(refStart, prefix + START_SUFFIX, true),
102 new DateDriver(refStop, prefix + STOP_SUFFIX, false),
103 new DateDriver(refStart.shiftedBy(0.5 * refStop.durationFrom(refStart)), prefix + MEDIAN_SUFFIX, true),
104 new ParameterDriver(prefix + DURATION_SUFFIX, refStop.durationFrom(refStart), 1.0, 0.0, Double.POSITIVE_INFINITY));
105 }
106
107 /** Private constructor with full parameters.
108 * <p>
109 * This constructor is private as users are expected to use the builder
110 * API with the various {@code withXxx()} methods to set up the instance
111 * in a readable manner without using a huge amount of parameters.
112 * </p>
113 * @param maxCheck maximum checking interval (s)
114 * @param threshold convergence threshold (s)
115 * @param maxIter maximum number of iterations in the event time search
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 */
122 private FieldParameterDrivenDateIntervalDetector(final T maxCheck, final T threshold, final int maxIter,
123 final FieldEventHandler<? super FieldParameterDrivenDateIntervalDetector<T>, T> handler,
124 final DateDriver start, final DateDriver stop,
125 final DateDriver median, final ParameterDriver duration) {
126 super(maxCheck, threshold, maxIter, handler);
127 this.start = start;
128 this.stop = stop;
129 this.median = median;
130 this.duration = duration;
131
132 // set up delegation between drivers
133 replaceBindingObserver(start, new StartObserver());
134 replaceBindingObserver(stop, new StopObserver());
135 replaceBindingObserver(median, new MedianObserver());
136 replaceBindingObserver(duration, new DurationObserver());
137
138 }
139
140 /** Replace binding observers.
141 * @param driver driver for whose binding observers should be replaced
142 * @param bindingObserver new binding observer
143 */
144 private void replaceBindingObserver(final ParameterDriver driver, final BindingObserver bindingObserver) {
145
146 // remove the previous binding observers
147 final List<ParameterObserver> original = driver.
148 getObservers().
149 stream().
150 filter(observer -> observer instanceof FieldParameterDrivenDateIntervalDetector.BindingObserver).
151 collect(Collectors.toList());
152 original.forEach(observer -> driver.removeObserver(observer));
153
154 driver.addObserver(bindingObserver);
155
156 }
157
158 /** {@inheritDoc} */
159 @Override
160 protected FieldParameterDrivenDateIntervalDetector<T> create(final T newMaxCheck, final T newThreshold, final int newMaxIter,
161 final FieldEventHandler<? super FieldParameterDrivenDateIntervalDetector<T>, T> newHandler) {
162 return new FieldParameterDrivenDateIntervalDetector<>(newMaxCheck, newThreshold, newMaxIter, newHandler,
163 start, stop, median, duration);
164 }
165
166 /** Get the driver for start date.
167 * <p>
168 * Note that the start date is automatically adjusted if either
169 * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
170 * are {@link ParameterDriver#isSelected() selected} and changed.
171 * </p>
172 * @return driver for start date
173 */
174 public DateDriver getStartDriver() {
175 return start;
176 }
177
178 /** Get the driver for stop date.
179 * <p>
180 * Note that the stop date is automatically adjusted if either
181 * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
182 * are {@link ParameterDriver#isSelected() selected} changed.
183 * </p>
184 * @return driver for stop date
185 */
186 public DateDriver getStopDriver() {
187 return stop;
188 }
189
190 /** Get the driver for median date.
191 * <p>
192 * Note that the median date is automatically adjusted if either
193 * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
194 * are {@link ParameterDriver#isSelected() selected} changed.
195 * </p>
196 * @return driver for median date
197 */
198 public DateDriver getMedianDriver() {
199 return median;
200 }
201
202 /** Get the driver for duration.
203 * <p>
204 * Note that the duration is automatically adjusted if either
205 * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
206 * are {@link ParameterDriver#isSelected() selected} changed.
207 * </p>
208 * @return driver for duration
209 */
210 public ParameterDriver getDurationDriver() {
211 return duration;
212 }
213
214 /** Compute the value of the switching function.
215 * <p>
216 * The function is positive for dates within the interval defined
217 * by applying the parameter drivers shifts to reference dates,
218 * and negative for dates outside of this interval. Note that
219 * if Δt_start - Δt_stop is less than ref_stop.durationFrom(ref_start),
220 * then the interval degenerates to empty and the function never
221 * reaches positive values.
222 * </p>
223 * @param s the current state information: date, kinematics, attitude
224 * @return value of the switching function
225 */
226 public T g(final FieldSpacecraftState<T> s) {
227 return FastMath.min(s.getDate().durationFrom(start.getDate()),
228 s.getDate().durationFrom(stop.getDate()).negate());
229 }
230
231 /** Base observer. */
232 private abstract class BindingObserver implements ParameterObserver {
233
234 /** {@inheritDoc} */
235 @Override
236 public void valueChanged(final double previousValue, final ParameterDriver driver) {
237 if (driver.isSelected()) {
238 setDelta(driver.getValue() - previousValue);
239 }
240 }
241
242 /** {@inheritDoc} */
243 @Override
244 public void selectionChanged(final boolean previousSelection, final ParameterDriver driver) {
245 if ((start.isSelected() || stop.isSelected()) &&
246 (median.isSelected() || duration.isSelected())) {
247 throw new OrekitException(OrekitMessages.INCONSISTENT_SELECTION,
248 start.getName(), stop.getName(),
249 median.getName(), duration.getName());
250 }
251 }
252
253 /** Change a value.
254 * @param delta change of value
255 */
256 protected abstract void setDelta(double delta);
257
258 }
259
260 /** Observer for start date. */
261 private class StartObserver extends BindingObserver {
262 /** {@inheritDoc} */
263 @Override
264 protected void setDelta(final double delta) {
265 median.setValue(median.getValue() + 0.5 * delta);
266 duration.setValue(duration.getValue() - delta);
267 }
268 }
269
270 /** Observer for stop date. */
271 private class StopObserver extends BindingObserver {
272 /** {@inheritDoc} */
273 @Override
274 protected void setDelta(final double delta) {
275 median.setValue(median.getValue() + 0.5 * delta);
276 duration.setValue(duration.getValue() + delta);
277 }
278 }
279
280 /** Observer for median date. */
281 private class MedianObserver extends BindingObserver {
282 /** {@inheritDoc} */
283 @Override
284 protected void setDelta(final double delta) {
285 start.setValue(start.getValue() + delta);
286 stop.setValue(stop.getValue() + delta);
287 }
288 }
289
290 /** Observer for duration. */
291 private class DurationObserver extends BindingObserver {
292 /** {@inheritDoc} */
293 @Override
294 protected void setDelta(final double delta) {
295 start.setValue(start.getValue() - 0.5 * delta);
296 stop.setValue(stop.getValue() + 0.5 * delta);
297 }
298 }
299
300 }