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