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