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.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.EventDetector;
29  import org.orekit.propagation.events.FieldEventDetector;
30  import org.orekit.propagation.events.handlers.EventHandler;
31  import org.orekit.propagation.events.handlers.FieldEventHandler;
32  import org.orekit.time.AbsoluteDate;
33  import org.orekit.time.FieldAbsoluteDate;
34  
35  /**
36   * Maneuver triggers based on a single event detector that defines firing intervals.
37   * <p>
38   * Firing intervals correspond to time spans with positive value of the event detector
39   * {@link EventDetector#g(SpacecraftState) g} function.
40   * </p>
41   * @param <T> type of the interval detector
42   * @see StartStopEventsTrigger
43   * @author Luc Maisonobe
44   * @since 11.1
45   */
46  public abstract class IntervalEventTrigger<T extends EventDetector> extends AbstractManeuverTriggers {
47  
48      /** Intervals detector. */
49      private final ManeuverTriggerDetector<T> firingIntervalDetector;
50  
51      /** Cached field-based detectors. */
52      private final transient Map<Field<? extends CalculusFieldElement<?>>, FieldEventDetector<? extends CalculusFieldElement<?>>> cached;
53  
54      /** Simple constructor.
55       * <p>
56       * Note that the {@code intervalDetector} passed as an argument is used only
57       * as a <em>prototype</em> from which a new detector will be built using
58       * {@link ManeuverTriggerDetector}. The original event handler from the prototype
59       * will be <em>ignored</em> and never called.
60       * </p>
61       * <p>
62       * If the trigger is used in a {@link org.orekit.propagation.FieldPropagator field-based propagation},
63       * the detector will be automatically converted to a field equivalent. Beware however that the
64       * {@link FieldEventHandler#eventOccurred(FieldSpacecraftState, FieldEventDetector, boolean) eventOccurred}
65       * of the converted propagator <em>will</em> call the method with the same name in the prototype
66       * detector, in order to get the correct return value.
67       * </p>
68       * @param prototypeFiringIntervalDetector prototype detector for firing interval
69       */
70      protected IntervalEventTrigger(final T prototypeFiringIntervalDetector) {
71          this.firingIntervalDetector = new ManeuverTriggerDetector<>(prototypeFiringIntervalDetector, new Handler());
72          this.cached                 = new HashMap<>();
73      }
74  
75      /** {@inheritDoc} */
76      @Override
77      public void init(final SpacecraftState initialState, final AbsoluteDate target) {
78          this.firingIntervalDetector.init(initialState, target);
79          super.init(initialState, target);
80      }
81  
82      /** {@inheritDoc} */
83      @Override
84      public <D extends CalculusFieldElement<D>> void init(final FieldSpacecraftState<D> initialState,
85                                                           final FieldAbsoluteDate<D> target) {
86          this.firingIntervalDetector.init(initialState.toSpacecraftState(), target.toAbsoluteDate());
87          super.init(initialState, target);
88      }
89  
90      /**
91       * Getter for the firing interval detector.
92       * @return firing interval detector
93       */
94      public T getFiringIntervalDetector() {
95          return firingIntervalDetector.getDetector();
96      }
97  
98      /** {@inheritDoc} */
99      @Override
100     protected boolean isFiringOnInitialState(final SpacecraftState initialState, final boolean isForward) {
101 
102         // set the initial value of firing
103         final double insideThrustArcG = firingIntervalDetector.g(initialState);
104         if (insideThrustArcG == 0) {
105             // bound of arc
106             // check state for the upcoming times
107             final double shift = (isForward ? 2 : -2) * firingIntervalDetector.getThreshold();
108             if (firingIntervalDetector.g(initialState.shiftedBy(shift)) > 0) {
109                 // we are entering the firing interval, from start if forward, from end if backward
110                 notifyResetters(initialState, isForward);
111                 return true;
112             } else {
113                 // we are leaving the firing interval, from end if forward, from start if backward
114                 notifyResetters(initialState, !isForward);
115                 return false;
116             }
117         } else {
118             return insideThrustArcG > 0;
119         }
120 
121     }
122 
123     /** {@inheritDoc} */
124     @Override
125     public Stream<EventDetector> getEventDetectors() {
126         return Stream.of(firingIntervalDetector);
127     }
128 
129     /** {@inheritDoc} */
130     @Override
131     public <S extends CalculusFieldElement<S>> Stream<FieldEventDetector<S>> getFieldEventDetectors(final Field<S> field) {
132 
133         @SuppressWarnings("unchecked")
134         FieldEventDetector<S> fd = (FieldEventDetector<S>) cached.get(field);
135         if (fd == null) {
136             fd = convertAndSetUpHandler(field);
137             cached.put(field, fd);
138         }
139 
140         return Stream.of(fd);
141 
142     }
143 
144     /** Convert a detector and set up check interval, threshold and new handler.
145      * <p>
146      * This method is not inlined in {@link #getFieldEventDetectors(Field)} because the
147      * parameterized types confuses the Java compiler.
148      * </p>
149      * @param field field to which the state belongs
150      * @param <D> type of the event detector
151      * @param <S> type of the field elements
152      * @return converted firing intervals detector
153      */
154     private <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>> FieldManeuverTriggerDetector<S, D> convertAndSetUpHandler(final Field<S> field) {
155         final D converted = convertIntervalDetector(field, firingIntervalDetector.getDetector());
156         return new FieldManeuverTriggerDetector<>(converted, new FieldHandler<>());
157     }
158 
159     /** Convert a primitive firing intervals detector into a field firing intervals detector.
160      * <p>
161      * The {@link org.orekit.propagation.events.FieldEventDetectionSettings} must be set up in conformance with the
162      * non-field detector.
163      * </p>
164      * <p>
165      * A skeleton implementation of this method to convert some {@code XyzDetector} into {@code FieldXyzDetector},
166      * considering these detectors are created from a date and a number parameter is:
167      * </p>
168      * <pre>{@code
169      *     protected <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>>
170      *         D convertIntervalDetector(final Field<S> field, final XyzDetector detector) {
171      *
172      *         final FieldAbsoluteDate<S> date  = new FieldAbsoluteDate<>(field, detector.getDate());
173      *         final S                    param = field.getZero().newInstance(detector.getParam());
174      *
175      *         D converted = (D) new FieldXyzDetector<>(date, param).withDetectionSettings(field, detector.getDetectionSettings());
176      *         return converted;
177      *
178      *     }
179      * }
180      * </pre>
181      * @param field field to which the state belongs
182      * @param detector primitive firing intervals detector to convert
183      * @param <D> type of the event detector
184      * @param <S> type of the field elements
185      * @return converted firing intervals detector
186      */
187     protected abstract <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>>
188         D convertIntervalDetector(Field<S> field, T detector);
189 
190     /** Local handler for both start and stop triggers. */
191     private class Handler implements EventHandler {
192 
193         /** Propagation direction. */
194         private boolean forward;
195 
196         /** {@inheritDoc} */
197         @Override
198         public void init(final SpacecraftState initialState, final AbsoluteDate target, final EventDetector detector) {
199             forward = target.isAfterOrEqualTo(initialState);
200             initializeResetters(initialState, target);
201         }
202 
203         /** {@inheritDoc} */
204         @Override
205         public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
206             if (forward) {
207                 getFirings().addValidAfter(increasing, s.getDate(), false);
208             } else {
209                 getFirings().addValidBefore(!increasing, s.getDate(), false);
210             }
211             notifyResetters(s, increasing);
212             return Action.RESET_STATE;
213         }
214 
215         /** {@inheritDoc} */
216         @Override
217         public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
218             return applyResetters(oldState);
219         }
220 
221     }
222 
223     /** Local handler for both start and stop triggers.
224      * @param <S> type of the field elements
225      */
226     private class FieldHandler<S extends CalculusFieldElement<S>> implements FieldEventHandler<S> {
227 
228         /** Propagation direction. */
229         private boolean forward;
230 
231         /** {@inheritDoc} */
232         @Override
233         public void init(final FieldSpacecraftState<S> initialState,
234                          final FieldAbsoluteDate<S> target,
235                          final FieldEventDetector<S> detector) {
236             forward = target.isAfterOrEqualTo(initialState);
237             initializeResetters(initialState, target);
238         }
239 
240         /** {@inheritDoc} */
241         @Override
242         public Action eventOccurred(final FieldSpacecraftState<S> s, final FieldEventDetector<S> detector, final boolean increasing) {
243             if (forward) {
244                 getFirings().addValidAfter(increasing, s.getDate().toAbsoluteDate(), false);
245             } else {
246                 getFirings().addValidBefore(!increasing, s.getDate().toAbsoluteDate(), false);
247             }
248             notifyResetters(s, increasing);
249             return Action.RESET_STATE;
250         }
251 
252         /** {@inheritDoc} */
253         @Override
254         public FieldSpacecraftState<S> resetState(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
255             return applyResetters(oldState);
256         }
257 
258     }
259 
260 }