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 }