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