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