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.forces.maneuvers.trigger;
18
19 import java.util.HashMap;
20 import java.util.Map;
21 import java.util.stream.Stream;
22
23 import org.hipparchus.CalculusFieldElement;
24 import org.hipparchus.Field;
25 import org.hipparchus.ode.events.Action;
26 import org.orekit.propagation.FieldSpacecraftState;
27 import org.orekit.propagation.SpacecraftState;
28 import org.orekit.propagation.events.AbstractDetector;
29 import org.orekit.propagation.events.EventDetector;
30 import org.orekit.propagation.events.FieldAbstractDetector;
31 import org.orekit.propagation.events.FieldEventDetector;
32 import org.orekit.propagation.events.handlers.EventHandler;
33 import org.orekit.propagation.events.handlers.FieldEventHandler;
34 import org.orekit.time.AbsoluteDate;
35 import org.orekit.time.FieldAbsoluteDate;
36
37 /**
38 * Maneuver triggers based on a pair of event detectors that defines firing start and stop.
39 * <p>
40 * The thruster starts firing when the start detector becomes
41 * positive. The thruster stops firing when the stop detector becomes positive.
42 * The 2 detectors should not be positive at the same time. A date detector is
43 * not suited as it does not delimit an interval. They can be both negative at
44 * the same time.
45 * </p>
46 * @param <A> type of the start detector
47 * @param <O> type of the stop detector
48 * @see IntervalEventTrigger
49 * @author Luc Maisonobe
50 * @since 11.1
51 */
52 public abstract class StartStopEventsTrigger<A extends AbstractDetector<A>, O extends AbstractDetector<O>> extends AbstractManeuverTriggers {
53
54 /** Start detector. */
55 private final A startDetector;
56
57 /** Stop detector. */
58 private final O stopDetector;
59
60 /** Cached field-based start detectors. */
61 private final transient Map<Field<? extends CalculusFieldElement<?>>, FieldEventDetector<? extends CalculusFieldElement<?>>> cachedStart;
62
63 /** Cached field-based stop detectors. */
64 private final transient Map<Field<? extends CalculusFieldElement<?>>, FieldEventDetector<? extends CalculusFieldElement<?>>> cachedStop;
65
66 /** Simple constructor.
67 * <p>
68 * Note that the {@code startDetector} and {@code stopDetector} passed as an argument are used only
69 * as a <em>prototypes</em> from which new detectors will be built using their
70 * {@link AbstractDetector#withHandler(EventHandler) withHandler} methods to
71 * set up internal handlers. The original event handlers from the prototype
72 * will be <em>ignored</em> and never called.
73 * </p>
74 * <p>
75 * If the trigger is used in a {@link org.orekit.propagation.FieldPropagator field-based propagation},
76 * the detector will be automatically converted to a field equivalent. Beware however that the
77 * {@link FieldEventHandler#eventOccurred(FieldSpacecraftState, FieldEventDetector, boolean) eventOccurred}
78 * of the converted propagator <em>will</em> call the method with the same name in the prototype
79 * detector, in order to get the correct return value.
80 * </p>
81 * @param prototypeStartDetector prototype detector for firing start
82 * @param prototypeStopDetector prototype detector for firing stop
83 */
84 protected StartStopEventsTrigger(final A prototypeStartDetector, final O prototypeStopDetector) {
85
86 this.startDetector = prototypeStartDetector.withHandler(new StartHandler());
87 this.stopDetector = prototypeStopDetector.withHandler(new StopHandler());
88 this.cachedStart = new HashMap<>();
89 this.cachedStop = new HashMap<>();
90
91 }
92
93 /**
94 * Getter for the firing start detector.
95 * @return firing start detector
96 */
97 public A getStartDetector() {
98 return startDetector;
99 }
100
101 /**
102 * Getter for the firing stop detector.
103 * @return firing stop detector
104 */
105 public O getStopDetector() {
106 return stopDetector;
107 }
108
109 /** {@inheritDoc} */
110 @Override
111 protected boolean isFiringOnInitialState(final SpacecraftState initialState, final boolean isForward) {
112
113 final double startG = startDetector.g(initialState);
114 if (startG == 0) {
115 final boolean increasing = startDetector.g(initialState.shiftedBy(2 * startDetector.getThreshold())) > 0;
116 if (increasing) {
117 // we are at maneuver start
118 notifyResetters(initialState, true);
119 // if propagating forward, we start firing
120 return isForward;
121 } else {
122 // not a meaningful crossing
123 return false;
124 }
125 } else if (startG < 0) {
126 // we are before start
127 return false;
128 } else {
129 // we are after start
130 final double stopG = stopDetector.g(initialState);
131 if (stopG == 0) {
132 final boolean increasing = stopDetector.g(initialState.shiftedBy(2 * stopDetector.getThreshold())) > 0;
133 if (increasing) {
134 // we are at maneuver end
135 notifyResetters(initialState, false);
136 // if propagating backward, we start firing
137 return !isForward;
138 } else {
139 // not a meaningful crossing
140 return false;
141 }
142 } else if (stopG > 0) {
143 // we are after stop
144 return false;
145 } else {
146 // we are between start and stop
147 return true;
148 }
149 }
150
151 }
152
153 /** {@inheritDoc} */
154 @Override
155 public Stream<EventDetector> getEventsDetectors() {
156 return Stream.of(startDetector, stopDetector);
157 }
158
159 /** {@inheritDoc} */
160 @Override
161 public <S extends CalculusFieldElement<S>> Stream<FieldEventDetector<S>> getFieldEventsDetectors(final Field<S> field) {
162
163 // get the field version of the start detector
164 @SuppressWarnings("unchecked")
165 FieldEventDetector<S> fStart = (FieldEventDetector<S>) cachedStart.get(field);
166 if (fStart == null) {
167 fStart = convertAndSetUpStartHandler(field);
168 cachedStart.put(field, fStart);
169 }
170
171 // get the field version of the stop detector
172 @SuppressWarnings("unchecked")
173 FieldEventDetector<S> fStop = (FieldEventDetector<S>) cachedStop.get(field);
174 if (fStop == null) {
175 fStop = convertAndSetUpStopHandler(field);
176 cachedStop.put(field, fStop);
177 }
178
179 return Stream.of(fStart, fStop);
180
181 }
182
183 /** Convert a detector and set up new handler.
184 * <p>
185 * This method is not inlined in {@link #getFieldEventsDetectors(Field)} because the
186 * parameterized types confuses the Java compiler.
187 * </p>
188 * @param field field to which the state belongs
189 * @param <D> type of the event detector
190 * @param <S> type of the field elements
191 * @return converted firing intervals detector
192 */
193 private <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>> D convertAndSetUpStartHandler(final Field<S> field) {
194 final FieldAbstractDetector<D, S> converted = convertStartDetector(field, startDetector);
195 return converted.
196 withMaxCheck(field.getZero().newInstance(startDetector.getMaxCheckInterval())).
197 withThreshold(field.getZero().newInstance(startDetector.getThreshold())).
198 withHandler(new FieldStartHandler<>());
199 }
200
201 /** Convert a detector and set up new handler.
202 * <p>
203 * This method is not inlined in {@link #getFieldEventsDetectors(Field)} because the
204 * parameterized types confuses the Java compiler.
205 * </p>
206 * @param field field to which the state belongs
207 * @param <D> type of the event detector
208 * @param <S> type of the field elements
209 * @return converted firing intervals detector
210 */
211 private <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>> D convertAndSetUpStopHandler(final Field<S> field) {
212 final FieldAbstractDetector<D, S> converted = convertStopDetector(field, stopDetector);
213 return converted.
214 withMaxCheck(field.getZero().newInstance(stopDetector.getMaxCheckInterval())).
215 withThreshold(field.getZero().newInstance(stopDetector.getThreshold())).
216 withHandler(new FieldStopHandler<>());
217 }
218
219 /** Convert a primitive firing start detector into a field firing start detector.
220 * <p>
221 * There is not need to set up {@link FieldAbstractDetector#withMaxCheck(CalculusFieldElement) withMaxCheck},
222 * {@link FieldAbstractDetector#withThreshold(CalculusFieldElement) withThreshold}, or
223 * {@link FieldAbstractDetector#withHandler(org.orekit.propagation.events.handlers.FieldEventHandler) withHandler}
224 * in the converted detector, this will be done by caller.
225 * </p>
226 * <p>
227 * A skeleton implementation of this method to convert some {@code XyzDetector} into {@code FieldXyzDetector},
228 * considering these detectors are created from a date and a number parameter is:
229 * </p>
230 * <pre>{@code
231 * protected <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>>
232 * FieldAbstractDetector<D, S> convertStartDetector(final Field<S> field, final XyzDetector detector) {
233 *
234 * final FieldAbsoluteDate<S> date = new FieldAbsoluteDate<>(field, detector.getDate());
235 * final S param = field.getZero().newInstance(detector.getParam());
236 *
237 * final FieldAbstractDetector<D, S> converted = (FieldAbstractDetector<D, S>) new FieldXyzDetector<>(date, param);
238 * return converted;
239 *
240 * }
241 * }
242 * </pre>
243 * @param field field to which the state belongs
244 * @param detector primitive firing start detector to convert
245 * @param <D> type of the event detector
246 * @param <S> type of the field elements
247 * @return converted firing start detector
248 */
249 protected abstract <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>> FieldAbstractDetector<D, S>
250 convertStartDetector(Field<S> field, A detector);
251
252 /** Convert a primitive firing stop detector into a field firing stop detector.
253 * <p>
254 * There is not need to set up {@link FieldAbstractDetector#withMaxCheck(CalculusFieldElement) withMaxCheck},
255 * {@link FieldAbstractDetector#withThreshold(CalculusFieldElement) withThreshold}, or
256 * {@link FieldAbstractDetector#withHandler(org.orekit.propagation.events.handlers.FieldEventHandler) withHandler}
257 * in the converted detector, this will be done by caller.
258 * </p>
259 * <p>
260 * A skeleton implementation of this method to convert some {@code XyzDetector} into {@code FieldXyzDetector},
261 * considering these detectors are created from a date and a number parameter is:
262 * </p>
263 * <pre>{@code
264 * protected <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>>
265 * FieldAbstractDetector<D, S> convertStopDetector(final Field<S> field, final XyzDetector detector) {
266 *
267 * final FieldAbsoluteDate<S> date = new FieldAbsoluteDate<>(field, detector.getDate());
268 * final S param = field.getZero().newInstance(detector.getParam());
269 *
270 * final FieldAbstractDetector<D, S> converted = (FieldAbstractDetector<D, S>) new FieldXyzDetector<>(date, param);
271 * return converted;
272 *
273 * }
274 * }
275 * </pre>
276 * @param field field to which the state belongs
277 * @param detector primitive firing stop detector to convert
278 * @param <D> type of the event detector
279 * @param <S> type of the field elements
280 * @return converted firing stop detector
281 */
282 protected abstract <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>> FieldAbstractDetector<D, S>
283 convertStopDetector(Field<S> field, O detector);
284
285 /** Local handler for start triggers. */
286 private class StartHandler implements EventHandler<A> {
287
288 /** Propagation direction. */
289 private boolean forward;
290
291 /** {@inheritDoc} */
292 @Override
293 public void init(final SpacecraftState initialState, final AbsoluteDate target, final A detector) {
294 forward = target.isAfterOrEqualTo(initialState);
295 initializeResetters(initialState, target);
296 }
297
298 /** {@inheritDoc} */
299 @Override
300 public Action eventOccurred(final SpacecraftState s, final A detector, final boolean increasing) {
301 if (increasing) {
302 // the event is meaningful for maneuver firing
303 if (forward) {
304 getFirings().addValidAfter(true, s.getDate(), false);
305 } else {
306 getFirings().addValidBefore(false, s.getDate(), false);
307 }
308 notifyResetters(s, true);
309 return Action.RESET_STATE;
310 } else {
311 // the event is not meaningful for maneuver firing
312 return Action.CONTINUE;
313 }
314 }
315
316 /** {@inheritDoc} */
317 @Override
318 public SpacecraftState resetState(final A detector, final SpacecraftState oldState) {
319 return applyResetters(oldState);
320 }
321
322 }
323
324 /** Local handler for stop triggers. */
325 private class StopHandler implements EventHandler<O> {
326
327 /** Propagation direction. */
328 private boolean forward;
329
330 /** {@inheritDoc} */
331 @Override
332 public void init(final SpacecraftState initialState, final AbsoluteDate target, final O detector) {
333 forward = target.isAfterOrEqualTo(initialState);
334 initializeResetters(initialState, target);
335 }
336
337 /** {@inheritDoc} */
338 @Override
339 public Action eventOccurred(final SpacecraftState s, final O detector, final boolean increasing) {
340 if (increasing) {
341 // the event is meaningful for maneuver firing
342 if (forward) {
343 getFirings().addValidAfter(false, s.getDate(), false);
344 } else {
345 getFirings().addValidBefore(true, s.getDate(), false);
346 }
347 notifyResetters(s, false);
348 return Action.RESET_STATE;
349 } else {
350 // the event is not meaningful for maneuver firing
351 return Action.CONTINUE;
352 }
353 }
354
355 /** {@inheritDoc} */
356 @Override
357 public SpacecraftState resetState(final O detector, final SpacecraftState oldState) {
358 return applyResetters(oldState);
359 }
360
361 }
362
363 /** Local handler for start triggers.
364 * @param <S> type of the field elements
365 */
366 private class FieldStartHandler<D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>> implements FieldEventHandler<D, S> {
367
368 /** Propagation direction. */
369 private boolean forward;
370
371 /** {@inheritDoc} */
372 @Override
373 public void init(final FieldSpacecraftState<S> initialState,
374 final FieldAbsoluteDate<S> target,
375 final D detector) {
376 forward = target.isAfterOrEqualTo(initialState);
377 initializeResetters(initialState, target);
378 }
379
380 /** {@inheritDoc} */
381 @Override
382 public Action eventOccurred(final FieldSpacecraftState<S> s, final D detector, final boolean increasing) {
383 if (increasing) {
384 // the event is meaningful for maneuver firing
385 if (forward) {
386 getFirings().addValidAfter(true, s.getDate().toAbsoluteDate(), false);
387 } else {
388 getFirings().addValidBefore(false, s.getDate().toAbsoluteDate(), false);
389 }
390 notifyResetters(s, true);
391 return Action.RESET_STATE;
392 } else {
393 // the event is not meaningful for maneuver firing
394 return Action.CONTINUE;
395 }
396 }
397
398 /** {@inheritDoc} */
399 @Override
400 public FieldSpacecraftState<S> resetState(final D detector, final FieldSpacecraftState<S> oldState) {
401 return applyResetters(oldState);
402 }
403
404 }
405
406 /** Local handler for stop triggers.
407 * @param <S> type of the field elements
408 */
409 private class FieldStopHandler<D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>> implements FieldEventHandler<D, S> {
410
411 /** Propagation direction. */
412 private boolean forward;
413
414 /** {@inheritDoc} */
415 @Override
416 public void init(final FieldSpacecraftState<S> initialState,
417 final FieldAbsoluteDate<S> target,
418 final D detector) {
419 forward = target.isAfterOrEqualTo(initialState);
420 initializeResetters(initialState, target);
421 }
422
423 /** {@inheritDoc} */
424 @Override
425 public Action eventOccurred(final FieldSpacecraftState<S> s, final D detector, final boolean increasing) {
426 if (increasing) {
427 // the event is meaningful for maneuver firing
428 if (forward) {
429 getFirings().addValidAfter(false, s.getDate().toAbsoluteDate(), false);
430 } else {
431 getFirings().addValidBefore(true, s.getDate().toAbsoluteDate(), false);
432 }
433 notifyResetters(s, false);
434 return Action.RESET_STATE;
435 } else {
436 // the event is not meaningful for maneuver firing
437 return Action.CONTINUE;
438 }
439 }
440
441 /** {@inheritDoc} */
442 @Override
443 public FieldSpacecraftState<S> resetState(final D detector, final FieldSpacecraftState<S> oldState) {
444 return applyResetters(oldState);
445 }
446
447 }
448
449 }