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