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 }