1 /* Copyright 2020 Exotrail
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 * Exotrail 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.stream.Stream;
20
21 import org.hipparchus.Field;
22 import org.hipparchus.CalculusFieldElement;
23 import org.hipparchus.ode.events.Action;
24 import org.orekit.errors.OrekitException;
25 import org.orekit.errors.OrekitMessages;
26 import org.orekit.propagation.SpacecraftState;
27 import org.orekit.propagation.events.AbstractDetector;
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.time.AbsoluteDate;
32 import org.orekit.time.FieldAbsoluteDate;
33
34 /**
35 * Maneuver triggers based on start and stop detectors. This allow a succession
36 * of burn interval. The thruster starts firing when the start detector becomes
37 * positive. The thruster stops firing when the stop detector becomes positive.
38 * The 2 detectors should not be positive at the same time. A date detector is
39 * not suited as it does not delimit an interval. They can be both negative at
40 * the same time.
41 * @author Mikael Fillastre
42 * @author Andrea Fiorentino
43 * @since 10.2
44 */
45 public class EventBasedManeuverTriggers implements ManeuverTriggers, EventHandler<EventDetector> {
46
47 /** Detector to start firing, only detect increasing sign change. */
48 private final AbstractDetector<? extends EventDetector> startFiringDetector;
49 /**
50 * Detector to stop firing, only detect increasing sign change. e.g. it can be a
51 * negate detector of the start detector
52 */
53 private final AbstractDetector<? extends EventDetector> stopFiringDetector;
54
55 /** Flag for allowing backward propagation. */
56 private final boolean allowBackwardPropagation;
57
58 /**
59 * Flag for init method, called several times : force models + each detector.
60 */
61 private boolean initialized;
62
63 /** Triggered date of engine start. */
64 private AbsoluteDate triggeredStart;
65
66 /** Triggered date of engine stop. */
67 private AbsoluteDate triggeredEnd;
68
69 /** Propagation direction. */
70 private boolean forward;
71
72 /**
73 * Constructor.
74 * <p>
75 * This legacy constructor forbids backward propagation.
76 * </p>
77 * @param startFiringDetector Detector to start firing, only detect increasing
78 * sign change
79 * @param stopFiringDetector Detector to stop firing, only detect increasing
80 * sign change. e.g. it can be a negate detector of
81 * the start detector.
82 */
83 public EventBasedManeuverTriggers(final AbstractDetector<? extends EventDetector> startFiringDetector,
84 final AbstractDetector<? extends EventDetector> stopFiringDetector) {
85 this(startFiringDetector, stopFiringDetector, false);
86 }
87
88 /**
89 * Constructor.
90 * @param startFiringDetector Detector to start firing, only detect increasing
91 * sign change
92 * @param stopFiringDetector Detector to stop firing, only detect increasing
93 * sign change. e.g. it can be a negate detector of
94 * the start detector.
95 * @param allowBackwardPropagation if true, backward propagation is allowed
96 * @since 11.1
97 */
98 public EventBasedManeuverTriggers(final AbstractDetector<? extends EventDetector> startFiringDetector,
99 final AbstractDetector<? extends EventDetector> stopFiringDetector,
100 final boolean allowBackwardPropagation) {
101 if (startFiringDetector == null) {
102 throw new OrekitException(OrekitMessages.PARAMETER_NOT_SET, "stopFiringDetector",
103 EventBasedManeuverTriggers.class.getSimpleName());
104 }
105 if (stopFiringDetector == null) {
106 throw new OrekitException(OrekitMessages.PARAMETER_NOT_SET, "startFiringDetector",
107 EventBasedManeuverTriggers.class.getSimpleName());
108 }
109 this.startFiringDetector = startFiringDetector.withHandler(this);
110 this.stopFiringDetector = stopFiringDetector.withHandler(this);
111 this.allowBackwardPropagation = allowBackwardPropagation;
112 this.triggeredStart = null;
113 this.triggeredEnd = null;
114 this.initialized = false;
115 this.forward = true;
116
117 }
118
119 /**
120 * Getter for the start firing detector.
121 * @return Detectors to start firing,
122 */
123 public AbstractDetector<? extends EventDetector> getStartFiringDetector() {
124 return startFiringDetector;
125 }
126
127 /**
128 * Getter for the stop firing detector.
129 * @return Detectors to stop firing
130 */
131 public AbstractDetector<? extends EventDetector> getStopFiringDetector() {
132 return stopFiringDetector;
133 }
134
135 /** {@inheritDoc} */
136 @Override
137 public void init(final SpacecraftState initialState, final AbsoluteDate target) {
138
139 if (!initialized) {
140
141 initialized = true;
142 forward = target.isAfterOrEqualTo(initialState);
143 if (!forward && !allowBackwardPropagation) {
144 // backward propagation was forbidden
145 throw new OrekitException(OrekitMessages.BACKWARD_PROPAGATION_NOT_ALLOWED);
146 }
147 startFiringDetector.init(initialState, target);
148 stopFiringDetector.init(initialState, target);
149
150 checkInitialFiringState(initialState);
151
152 } // multiples calls to init : because it is a force model and by each detector
153 }
154
155 /**
156 * Method to set the firing state on initialization. can be overloaded by sub
157 * classes.
158 *
159 * @param initialState initial spacecraft state
160 */
161 protected void checkInitialFiringState(final SpacecraftState initialState) {
162 if (isFiringOnInitialState(initialState)) {
163 setFiring(true, initialState.getDate());
164 }
165 }
166
167 /**
168 * Method to check if the thruster is firing on initialization. can be called by
169 * sub classes
170 *
171 * @param initialState initial spacecraft state
172 * @return true if firing
173 */
174 protected boolean isFiringOnInitialState(final SpacecraftState initialState) {
175 // set the initial value of firing
176 final double insideThrustArcG = getStartFiringDetector().g(initialState);
177 boolean isInsideThrustArc = false;
178
179 if (insideThrustArcG == 0) {
180 // bound of arc
181 // check state for the next second (which can be forward or backward)
182 final double nextSecond = forward ? 1 : -1;
183 final double nextValue = getStartFiringDetector().g(initialState.shiftedBy(nextSecond));
184 isInsideThrustArc = nextValue > 0;
185 } else {
186 isInsideThrustArc = insideThrustArcG > 0;
187 }
188 return isInsideThrustArc;
189 }
190
191 /** {@inheritDoc} */
192 @Override
193 public Stream<EventDetector> getEventsDetectors() {
194 return Stream.of(getStartFiringDetector(), getStopFiringDetector());
195 }
196
197 /** {@inheritDoc} */
198 @Override
199 public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventsDetectors(final Field<T> field) {
200 // not implemented, it depends on the input detectors
201 throw new OrekitException(OrekitMessages.FUNCTION_NOT_IMPLEMENTED,
202 "EventBasedManeuverTriggers.getFieldEventsDetectors");
203 }
204
205 /**
206 * Set the firing start or end date depending on the firing flag. There is no
207 * effect if the firing state is not changing.
208 * @param firing true to start a maneuver, false to stop
209 * @param date date of event
210 */
211 public void setFiring(final boolean firing, final AbsoluteDate date) {
212 if (forward) {
213 if (firing) {
214 if (!date.equals(triggeredEnd)) {
215 triggeredStart = date;
216 triggeredEnd = null;
217 } // else no gap between stop and start, can not handle correctly : skip it
218 } else {
219 triggeredEnd = date;
220 }
221 } else { // backward propagation
222 if (firing) { // start firing by end date
223 if (!date.equals(triggeredStart)) {
224 triggeredEnd = date;
225 triggeredStart = null;
226 } // else no gap between stop and start, can not handle correctly : skip it
227 } else {
228 triggeredStart = date;
229 }
230 }
231 }
232
233 /** {@inheritDoc} */
234 @Override
235 public boolean isFiring(final AbsoluteDate date, final double[] parameters) {
236 // Firing state does not depend on a parameter driver here
237 return isFiring(date);
238 }
239
240 /** {@inheritDoc} */
241 @Override
242 public <T extends CalculusFieldElement<T>> boolean isFiring(final FieldAbsoluteDate<T> date, final T[] parameters) {
243 // Firing state does not depend on a parameter driver here
244 return isFiring(date.toAbsoluteDate());
245 }
246
247 /** {@inheritDoc} */
248 @Override
249 public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
250 Action action = Action.CONTINUE; // default not taken into account
251 final boolean detectorManaged = getEventsDetectors().anyMatch(managedDetector -> managedDetector.equals(detector));
252 if (detectorManaged) {
253 if (increasing) {
254 action = Action.RESET_EVENTS;
255 if (forward) {
256 if (detector.equals(startFiringDetector)) { // start of firing arc
257 setFiring(true, s.getDate());
258 action = Action.RESET_DERIVATIVES;
259 } else if (detector.equals(stopFiringDetector)) { // end of firing arc
260 setFiring(false, s.getDate());
261 action = Action.RESET_DERIVATIVES;
262 }
263 } else { // backward propagation. We could write a code on 3 lines but that would be
264 // harder to understand and debug. So we do prefer explicit code
265 if (detector.equals(startFiringDetector)) { // end of firing arc
266 setFiring(false, s.getDate());
267 action = Action.RESET_DERIVATIVES;
268 } else if (detector.equals(stopFiringDetector)) { // start of firing arc
269 setFiring(true, s.getDate());
270 action = Action.RESET_DERIVATIVES;
271 }
272 }
273 }
274 }
275 return action;
276 }
277
278 /**
279 * Check if maneuvering is on.
280 *
281 * @param date current date
282 * @return true if maneuver is on at this date
283 */
284 public boolean isFiring(final AbsoluteDate date) {
285 if (forward) {
286 if (triggeredStart == null) {
287 // explicitly ignores state date, as propagator did not allow us to introduce
288 // discontinuity
289 return false;
290 } else if (date.isBefore(triggeredStart)) {
291 // we are unambiguously before maneuver start
292 // robustness, we should not pass here
293 return false;
294 } else {
295 // after start date
296 if (triggeredEnd == null) {
297 // explicitly ignores state date, as propagator did not allow us to introduce
298 // discontinuity
299 return true;
300 } else if (date.isBefore(triggeredEnd)) {
301 // we are unambiguously before maneuver end
302 // robustness, we should not pass here
303 return true;
304 } else {
305 // we are at or after maneuver end
306 return false;
307 }
308 }
309 } else { // backward propagation, start firing by triggeredEnd
310 if (triggeredEnd == null) {
311 // explicitly ignores state date, as propagator did not allow us to introduce
312 // discontinuity
313 return false;
314 } else if (date.isAfter(triggeredEnd)) {
315 // we are unambiguously after maneuver end
316 return false;
317 } else {
318 if (triggeredStart == null) {
319 // explicitly ignores state date, as propagator did not allow us to introduce
320 // discontinuity
321 return true;
322 } else if (date.isAfter(triggeredStart)) {
323 // we are unambiguously after maneuver start
324 return true;
325 } else {
326 // we are at or before maneuver start
327 return false;
328 }
329 }
330 }
331 }
332
333 /**
334 * Getter for the triggered date of engine stop.
335 * @return Triggered date of engine stop
336 */
337 public AbsoluteDate getTriggeredEnd() {
338 return triggeredEnd;
339 }
340
341 /**
342 * Getter triggered date of engine start.
343 * @return Triggered date of engine start
344 */
345 public AbsoluteDate getTriggeredStart() {
346 return triggeredStart;
347 }
348
349 }