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 }