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         switch (detector) {
254             case EventEnablingPredicateFilter predicateFilter -> {
255                 final FieldEventDetector<T> fieldDetector = convertDetector(field, predicateFilter.getDetector());
256                 return new FieldEventEnablingPredicateFilter<>(fieldDetector,
257                                                                (state, fieldEventDetector, g) ->
258                                                                    predicateFilter.getPredicate().
259                                                                                    eventIsEnabled(state.toSpacecraftState(),
260                                                                                                   null,
261                                                                                                   g.getReal()));
262             }
263             case EventSlopeFilter<?> eventSlopeFilter -> {
264                 final FieldEventDetector<T> fieldDetector = convertDetector(field, eventSlopeFilter.getDetector());
265                 return new FieldEventSlopeFilter<>(fieldDetector, eventSlopeFilter.getFilterType());
266             }
267             case EventShifter eventShifter -> {
268                 final FieldEventDetector<T> fieldDetector = convertDetector(field, eventShifter.getDetector());
269                 final T zero = field.getZero();
270                 return new FieldEventShifter<>(fieldDetector, eventShifter.isUseShiftedStates(),
271                                                zero.newInstance(eventShifter.getIncreasingTimeShift()),
272                                                zero.newInstance(eventShifter.getDecreasingTimeShift()));
273             }
274             case null, default -> {
275                 return FieldEventDetector.of(field, new FieldContinueOnEvent<>(), detector);
276             }
277         }
278     }
279 
280     /** Local abstract handler for triggers, with a cache for the reset.
281      * @since 13.1
282      */
283     protected abstract class TriggerHandler implements EventHandler {
284 
285         /** Propagation direction. */
286         private boolean forward;
287 
288         /** Last evaluated state for cache. */
289         private SpacecraftState lastState;
290 
291         /** Last reset state for cache. */
292         private SpacecraftState lastResetState;
293 
294         /** Simple constructor. */
295         protected TriggerHandler() {
296             // nothing to do
297         }
298 
299         /** {@inheritDoc} */
300         @Override
301         public void init(final SpacecraftState initialState, final AbsoluteDate target, final EventDetector detector) {
302             forward = target.isAfterOrEqualTo(initialState);
303             lastState = null;
304             lastResetState = null;
305             initializeResetters(initialState, target);
306         }
307 
308         /**
309          * Determines the action (reset state or derivatives only).
310          * @param detector event detector
311          * @param oldState state before reset if any
312          * @return action
313          */
314         protected Action determineAction(final EventDetector detector, final SpacecraftState oldState) {
315             final SpacecraftState resetState = resetState(detector, oldState);
316             if (resetState == oldState) {
317                 return Action.RESET_DERIVATIVES;
318             } else {
319                 return Action.RESET_STATE;
320             }
321         }
322 
323         /** {@inheritDoc} */
324         @Override
325         public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
326             if (lastState != oldState) {
327                 lastResetState = applyResetters(oldState);
328                 lastState = oldState;
329             }
330             return lastResetState;
331         }
332 
333         /**
334          * Getter for flag.
335          * @return flag on backward propagation
336          */
337         protected boolean isForward() {
338             return forward;
339         }
340     }
341 
342     /** Local abstract handler for triggers, with a cache for the reset.
343      * @param <S> type of the field elements
344      * @since 13.1
345      */
346     protected abstract class FieldTriggerHandler<S extends CalculusFieldElement<S>> implements FieldEventHandler<S> {
347 
348         /** Propagation direction. */
349         private boolean forward;
350 
351         /** Last evaluated state for cache. */
352         private FieldSpacecraftState<S> lastState;
353 
354         /** Last reset state for cache. */
355         private FieldSpacecraftState<S> lastResetState;
356 
357         /** Simple constructor. */
358         protected FieldTriggerHandler() {
359             // nothing to do
360         }
361 
362         /** {@inheritDoc} */
363         @Override
364         public void init(final FieldSpacecraftState<S> initialState,
365                          final FieldAbsoluteDate<S> target,
366                          final FieldEventDetector<S> detector) {
367             forward = target.isAfterOrEqualTo(initialState);
368             lastState = null;
369             lastResetState = null;
370             initializeResetters(initialState, target);
371         }
372 
373         /**
374          * Determines the action (reset state or derivatives only).
375          * @param detector event detector
376          * @param oldState state before reset if any
377          * @return action
378          */
379         protected Action determineAction(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
380             final FieldSpacecraftState<S> resetState = resetState(detector, oldState);
381             if (resetState == oldState) {
382                 return Action.RESET_DERIVATIVES;
383             } else {
384                 return Action.RESET_STATE;
385             }
386         }
387 
388         /** {@inheritDoc} */
389         @Override
390         public FieldSpacecraftState<S> resetState(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
391             if (lastState != oldState) {
392                 lastResetState = applyResetters(oldState);
393                 lastState = oldState;
394             }
395             return lastResetState;
396         }
397 
398         /**
399          * Getter for flag.
400          * @return flag on backward propagation
401          */
402         protected boolean isForward() {
403             return forward;
404         }
405     }
406 }