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 }