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         /** Simple constructor. */
285         protected TriggerHandler() {
286             // nothing to do
287         }
288 
289         /** {@inheritDoc} */
290         @Override
291         public void init(final SpacecraftState initialState, final AbsoluteDate target, final EventDetector detector) {
292             forward = target.isAfterOrEqualTo(initialState);
293             lastState = null;
294             lastResetState = null;
295             initializeResetters(initialState, target);
296         }
297 
298         /**
299          * Determines the action (reset state or derivatives only).
300          * @param detector event detector
301          * @param oldState state before reset if any
302          * @return action
303          */
304         protected Action determineAction(final EventDetector detector, final SpacecraftState oldState) {
305             final SpacecraftState resetState = resetState(detector, oldState);
306             if (resetState == oldState) {
307                 return Action.RESET_DERIVATIVES;
308             } else {
309                 return Action.RESET_STATE;
310             }
311         }
312 
313         /** {@inheritDoc} */
314         @Override
315         public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
316             if (lastState != oldState) {
317                 lastResetState = applyResetters(oldState);
318                 lastState = oldState;
319             }
320             return lastResetState;
321         }
322 
323         /**
324          * Getter for flag.
325          * @return flag on backward propagation
326          */
327         protected boolean isForward() {
328             return forward;
329         }
330     }
331 
332     /** Local abstract handler for triggers, with a cache for the reset.
333      * @param <S> type of the field elements
334      * @since 13.1
335      */
336     protected abstract class FieldTriggerHandler<S extends CalculusFieldElement<S>> implements FieldEventHandler<S> {
337 
338         /** Propagation direction. */
339         private boolean forward;
340 
341         /** Last evaluated state for cache. */
342         private FieldSpacecraftState<S> lastState;
343 
344         /** Last reset state for cache. */
345         private FieldSpacecraftState<S> lastResetState;
346 
347         /** Simple constructor. */
348         protected FieldTriggerHandler() {
349             // nothing to do
350         }
351 
352         /** {@inheritDoc} */
353         @Override
354         public void init(final FieldSpacecraftState<S> initialState,
355                          final FieldAbsoluteDate<S> target,
356                          final FieldEventDetector<S> detector) {
357             forward = target.isAfterOrEqualTo(initialState);
358             lastState = null;
359             lastResetState = null;
360             initializeResetters(initialState, target);
361         }
362 
363         /**
364          * Determines the action (reset state or derivatives only).
365          * @param detector event detector
366          * @param oldState state before reset if any
367          * @return action
368          */
369         protected Action determineAction(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
370             final FieldSpacecraftState<S> resetState = resetState(detector, oldState);
371             if (resetState == oldState) {
372                 return Action.RESET_DERIVATIVES;
373             } else {
374                 return Action.RESET_STATE;
375             }
376         }
377 
378         /** {@inheritDoc} */
379         @Override
380         public FieldSpacecraftState<S> resetState(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
381             if (lastState != oldState) {
382                 lastResetState = applyResetters(oldState);
383                 lastState = oldState;
384             }
385             return lastResetState;
386         }
387 
388         /**
389          * Getter for flag.
390          * @return flag on backward propagation
391          */
392         protected boolean isForward() {
393             return forward;
394         }
395     }
396 }