1   /* Copyright 2002-2026 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.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.hipparchus.CalculusFieldElement;
25  import org.hipparchus.Field;
26  import org.hipparchus.ode.events.Action;
27  import org.orekit.propagation.FieldSpacecraftState;
28  import org.orekit.propagation.SpacecraftState;
29  import org.orekit.propagation.events.EventDetector;
30  import org.orekit.propagation.events.EventEnablingPredicateFilter;
31  import org.orekit.propagation.events.EventShifter;
32  import org.orekit.propagation.events.EventSlopeFilter;
33  import org.orekit.propagation.events.FieldEventDetector;
34  import org.orekit.propagation.events.FieldEventEnablingPredicateFilter;
35  import org.orekit.propagation.events.FieldEventShifter;
36  import org.orekit.propagation.events.FieldEventSlopeFilter;
37  import org.orekit.propagation.events.handlers.EventHandler;
38  import org.orekit.propagation.events.handlers.FieldContinueOnEvent;
39  import org.orekit.propagation.events.handlers.FieldEventHandler;
40  import org.orekit.time.AbsoluteDate;
41  import org.orekit.time.FieldAbsoluteDate;
42  import org.orekit.utils.TimeSpanMap;
43  
44  /** Base class for triggers.
45   * @author Luc Maisonobe
46   * @since 11.1
47   */
48  public abstract class AbstractManeuverTriggers implements ResettableManeuverTriggers {
49  
50      /** Firing time spans. */
51      private TimeSpanMap<Boolean> firings;
52  
53      /** Propagation direction. */
54      private boolean forward;
55  
56      /** Resetters for the maneuver triggers. */
57      private final List<ManeuverTriggersResetter> resetters;
58  
59      /** Cached field-based resetters. */
60      private final Map<Field<? extends CalculusFieldElement<?>>, List<FieldManeuverTriggersResetter<?>>> fieldResetters;
61  
62      /** Simple constructor.
63       */
64      protected AbstractManeuverTriggers() {
65          this.firings        = new TimeSpanMap<>(Boolean.FALSE);
66          this.resetters      = new ArrayList<>();
67          this.fieldResetters = new HashMap<>();
68      }
69  
70      /** {@inheritDoc} */
71      @Override
72      public void init(final SpacecraftState initialState, final AbsoluteDate target) {
73  
74          forward = target.isAfterOrEqualTo(initialState);
75          firings = new TimeSpanMap<>(Boolean.FALSE);
76          initializeResetters(initialState, target);
77  
78          if (isFiringOnInitialState(initialState, forward)) {
79              if (forward) {
80                  firings.addValidAfter(Boolean.TRUE, initialState.getDate(), false);
81              } else {
82                  firings.addValidBefore(Boolean.TRUE, initialState.getDate(), false);
83              }
84          }
85  
86      }
87  
88      /** {@inheritDoc} */
89      @SuppressWarnings("unchecked")
90      @Override
91      public <T extends CalculusFieldElement<T>> void init(final FieldSpacecraftState<T> initialState, final FieldAbsoluteDate<T> target) {
92  
93          forward = target.isAfterOrEqualTo(initialState);
94          firings = new TimeSpanMap<>(Boolean.FALSE);
95          // check if we already have resetters for this field
96          final List<FieldManeuverTriggersResetter<?>> list = fieldResetters.get(initialState.getDate().getField());
97          if (list != null) {
98              for (FieldManeuverTriggersResetter<?> r : list) {
99                  ((FieldManeuverTriggersResetter<T>) r).init(initialState, target);
100             }
101         }
102 
103         if (isFiringOnInitialState(initialState.toSpacecraftState(), forward)) {
104             if (forward) {
105                 firings.addValidAfter(Boolean.TRUE, initialState.getDate().toAbsoluteDate(), false);
106             } else {
107                 firings.addValidBefore(Boolean.TRUE, initialState.getDate().toAbsoluteDate(), false);
108             }
109         }
110 
111     }
112 
113     /**
114      * Method to check if the thruster is firing on initialization. can be called by
115      * sub classes
116      *
117      * @param initialState initial spacecraft state
118      * @param isForward if true, propagation will be in the forward direction
119      * @return true if firing in propagation direction
120      */
121     protected abstract boolean isFiringOnInitialState(SpacecraftState initialState, boolean isForward);
122 
123     /** {@inheritDoc} */
124     @Override
125     public boolean isFiring(final AbsoluteDate date, final double[] parameters) {
126         return firings.get(date);
127     }
128 
129     /** {@inheritDoc} */
130     @Override
131     public <S extends CalculusFieldElement<S>> boolean isFiring(final FieldAbsoluteDate<S> date, final S[] parameters) {
132         return firings.get(date.toAbsoluteDate());
133     }
134 
135     /** Get the firings detected during last propagation.
136      * @return firings detected during last propagation
137      */
138     public TimeSpanMap<Boolean> getFirings() {
139         return firings;
140     }
141 
142     /** {@inheritDoc} */
143     @Override
144     public void addResetter(final ManeuverTriggersResetter resetter) {
145         resetters.add(resetter);
146     }
147 
148     /** {@inheritDoc} */
149     @Override
150     public <T extends CalculusFieldElement<T>> void addResetter(final Field<T> field, final FieldManeuverTriggersResetter<T> resetter) {
151 
152         // check if we already have resetters for this field
153         final List<FieldManeuverTriggersResetter<?>> list = fieldResetters.computeIfAbsent(field, k -> new ArrayList<>());
154 
155         // add the resetter to the list
156         list.add(resetter);
157 
158     }
159 
160     /** Initialize resetters.
161      * @param initialState initial state
162      * @param target target date for the propagation
163      */
164     protected void initializeResetters(final SpacecraftState initialState, final AbsoluteDate target) {
165         for (final ManeuverTriggersResetter r : resetters) {
166             r.init(initialState, target);
167         }
168     }
169 
170     /** Notify resetters.
171      * @param state spacecraft state at trigger date (before applying the maneuver)
172      * @param start if true, the trigger is the start of the maneuver
173      */
174     protected void notifyResetters(final SpacecraftState state, final boolean start) {
175         for (final ManeuverTriggersResetter r : resetters) {
176             r.maneuverTriggered(state, start);
177         }
178     }
179 
180     /** Apply resetters.
181      * @param state spacecraft state at trigger date
182      * @return reset state
183      */
184     protected SpacecraftState applyResetters(final SpacecraftState state) {
185         SpacecraftState reset = state;
186         for (final ManeuverTriggersResetter r : resetters) {
187             reset = r.resetState(reset);
188         }
189         return reset;
190     }
191 
192     /** Initialize resetters.
193      * @param initialState initial state
194      * @param target target date for the propagation
195      * @param <T> type of the field elements
196      */
197     protected <T extends CalculusFieldElement<T>> void initializeResetters(final FieldSpacecraftState<T> initialState, final FieldAbsoluteDate<T> target) {
198         final List<FieldManeuverTriggersResetter<?>> list = fieldResetters.get(initialState.getDate().getField());
199         if (list != null) {
200             for (final FieldManeuverTriggersResetter<?> r : list) {
201                 @SuppressWarnings("unchecked")
202                 final FieldManeuverTriggersResetter<T> tr = (FieldManeuverTriggersResetter<T>) r;
203                 tr.init(initialState, target);
204             }
205         }
206     }
207 
208     /** Notify resetters.
209      * @param state spacecraft state at trigger date (before applying the maneuver)
210      * @param start if true, the trigger is the start of the maneuver
211      * @param <T> type of the field elements
212      */
213     protected <T extends CalculusFieldElement<T>> void notifyResetters(final FieldSpacecraftState<T> state, final boolean start) {
214         final List<FieldManeuverTriggersResetter<?>> list = fieldResetters.get(state.getDate().getField());
215         if (list != null) {
216             for (final FieldManeuverTriggersResetter<?> r : list) {
217                 @SuppressWarnings("unchecked")
218                 final FieldManeuverTriggersResetter<T> tr = (FieldManeuverTriggersResetter<T>) r;
219                 tr.maneuverTriggered(state, start);
220             }
221         }
222     }
223 
224     /** Apply resetters.
225      * @param state spacecraft state at trigger date
226      * @param <T> type of the field elements
227      * @return reset state
228      */
229     protected <T extends CalculusFieldElement<T>> FieldSpacecraftState<T>
230         applyResetters(final FieldSpacecraftState<T> state) {
231         FieldSpacecraftState<T> reset = state;
232         final List<FieldManeuverTriggersResetter<?>> list = fieldResetters.get(state.getDate().getField());
233         if (list != null) {
234             for (final FieldManeuverTriggersResetter<?> r : list) {
235                 @SuppressWarnings("unchecked")
236                 final FieldManeuverTriggersResetter<T> tr = (FieldManeuverTriggersResetter<T>) r;
237                 reset = tr.resetState(reset);
238             }
239         }
240         return reset;
241     }
242 
243     /**
244      * Attempts to create a working Fielded detector from a standard one.
245      * @param field field
246      * @param detector non-Field detector
247      * @return Field detector
248      * @param <T> field type
249      * @since 14.0
250      */
251     protected <T extends CalculusFieldElement<T>> FieldEventDetector<T> convertDetector(final Field<T> field,
252                                                                                         final EventDetector detector) {
253         if (detector instanceof EventEnablingPredicateFilter predicateFilter) {
254             final FieldEventDetector<T> fieldDetector = convertDetector(field, predicateFilter.getDetector());
255             return new FieldEventEnablingPredicateFilter<>(fieldDetector, (state, fieldEventDetector, g) -> predicateFilter
256                     .getPredicate().eventIsEnabled(state.toSpacecraftState(), null, g.getReal()));
257         } else if (detector instanceof EventSlopeFilter<?> eventSlopeFilter) {
258             final FieldEventDetector<T> fieldDetector = convertDetector(field, eventSlopeFilter.getDetector());
259             return new FieldEventSlopeFilter<>(fieldDetector, eventSlopeFilter.getFilterType());
260         } else if (detector instanceof EventShifter eventShifter) {
261             final FieldEventDetector<T> fieldDetector = convertDetector(field, eventShifter.getDetector());
262             final T zero = field.getZero();
263             return new FieldEventShifter<>(fieldDetector, eventShifter.isUseShiftedStates(),
264                     zero.newInstance(eventShifter.getIncreasingTimeShift()), zero.newInstance(eventShifter.getDecreasingTimeShift()));
265         } else {
266             return FieldEventDetector.of(field, new FieldContinueOnEvent<>(), detector);
267         }
268     }
269 
270     /** Local abstract handler for triggers, with a cache for the reset.
271      * @since 13.1
272      */
273     protected abstract class TriggerHandler implements EventHandler {
274 
275         /** Propagation direction. */
276         private boolean forward;
277 
278         /** Last evaluated state for cache. */
279         private SpacecraftState lastState;
280 
281         /** Last reset state for cache. */
282         private SpacecraftState lastResetState;
283 
284         /** {@inheritDoc} */
285         @Override
286         public void init(final SpacecraftState initialState, final AbsoluteDate target, final EventDetector detector) {
287             forward = target.isAfterOrEqualTo(initialState);
288             lastState = null;
289             lastResetState = null;
290             initializeResetters(initialState, target);
291         }
292 
293         /**
294          * Determines the action (reset state or derivatives only).
295          * @param detector event detector
296          * @param oldState state before reset if any
297          * @return action
298          */
299         protected Action determineAction(final EventDetector detector, final SpacecraftState oldState) {
300             final SpacecraftState resetState = resetState(detector, oldState);
301             if (resetState == oldState) {
302                 return Action.RESET_DERIVATIVES;
303             } else {
304                 return Action.RESET_STATE;
305             }
306         }
307 
308         /** {@inheritDoc} */
309         @Override
310         public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
311             if (lastState != oldState) {
312                 lastResetState = applyResetters(oldState);
313                 lastState = oldState;
314             }
315             return lastResetState;
316         }
317 
318         /**
319          * Getter for flag.
320          * @return flag on backward propagation
321          */
322         protected boolean isForward() {
323             return forward;
324         }
325     }
326 
327     /** Local abstract handler for triggers, with a cache for the reset.
328      * @param <S> type of the field elements
329      * @since 13.1
330      */
331     protected abstract class FieldTriggerHandler<S extends CalculusFieldElement<S>> implements FieldEventHandler<S> {
332 
333         /** Propagation direction. */
334         private boolean forward;
335 
336         /** Last evaluated state for cache. */
337         private FieldSpacecraftState<S> lastState;
338 
339         /** Last reset state for cache. */
340         private FieldSpacecraftState<S> lastResetState;
341 
342         /** {@inheritDoc} */
343         @Override
344         public void init(final FieldSpacecraftState<S> initialState,
345                          final FieldAbsoluteDate<S> target,
346                          final FieldEventDetector<S> detector) {
347             forward = target.isAfterOrEqualTo(initialState);
348             lastState = null;
349             lastResetState = null;
350             initializeResetters(initialState, target);
351         }
352 
353         /**
354          * Determines the action (reset state or derivatives only).
355          * @param detector event detector
356          * @param oldState state before reset if any
357          * @return action
358          */
359         protected Action determineAction(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
360             final FieldSpacecraftState<S> resetState = resetState(detector, oldState);
361             if (resetState == oldState) {
362                 return Action.RESET_DERIVATIVES;
363             } else {
364                 return Action.RESET_STATE;
365             }
366         }
367 
368         /** {@inheritDoc} */
369         @Override
370         public FieldSpacecraftState<S> resetState(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
371             if (lastState != oldState) {
372                 lastResetState = applyResetters(oldState);
373                 lastState = oldState;
374             }
375             return lastResetState;
376         }
377 
378         /**
379          * Getter for flag.
380          * @return flag on backward propagation
381          */
382         protected boolean isForward() {
383             return forward;
384         }
385     }
386 }