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