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 }