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 }