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      /**
56       * Flag for init method, called several times : force models + each detector.
57       */
58      private boolean initialized;
59  
60      /** Triggered date of engine start. */
61      private AbsoluteDate triggeredStart;
62  
63      /** Triggered date of engine stop. */
64      private AbsoluteDate triggeredEnd;
65  
66      /**
67       * Constructor.
68       * @param startFiringDetector Detector to start firing, only detect increasing
69       *                            sign change
70       * @param stopFiringDetector  Detector to stop firing, only detect increasing
71       *                            sign change. e.g. it can be a negate detector of
72       *                            the start detector.
73       */
74      public EventBasedManeuverTriggers(final AbstractDetector<? extends EventDetector> startFiringDetector,
75              final AbstractDetector<? extends EventDetector> stopFiringDetector) {
76          if (startFiringDetector == null) {
77              throw new OrekitException(OrekitMessages.PARAMETER_NOT_SET, "stopFiringDetector",
78                      EventBasedManeuverTriggers.class.getSimpleName());
79          }
80          if (stopFiringDetector == null) {
81              throw new OrekitException(OrekitMessages.PARAMETER_NOT_SET, "startFiringDetector",
82                      EventBasedManeuverTriggers.class.getSimpleName());
83          }
84          this.startFiringDetector = startFiringDetector.withHandler(this);
85          this.stopFiringDetector = stopFiringDetector.withHandler(this);
86          this.triggeredStart = null;
87          this.triggeredEnd = null;
88          initialized = false;
89      }
90  
91      /**
92       * Getter for the start firing detector.
93       * @return Detectors to start firing,
94       */
95      public AbstractDetector<? extends EventDetector> getStartFiringDetector() {
96          return startFiringDetector;
97      }
98  
99      /**
100      * Getter for the stop firing detector.
101      * @return Detectors to stop firing
102      */
103     public AbstractDetector<? extends EventDetector> getStopFiringDetector() {
104         return stopFiringDetector;
105     }
106 
107     /** {@inheritDoc} */
108     @Override
109     public void init(final SpacecraftState initialState, final AbsoluteDate target) {
110 
111         if (!initialized) {
112 
113             initialized = true;
114             final AbsoluteDate sDate = initialState.getDate();
115             if (sDate.compareTo(target) > 0) {
116                 // backward propagation not managed because events on detectors can not be
117                 // reversed :
118                 // the stop event of the maneuver in forward direction won't be the start in the
119                 // backward.
120                 // e.g. if a stop detector is combination of orbit position and system
121                 // constraint
122                 throw new OrekitException(OrekitMessages.FUNCTION_NOT_IMPLEMENTED,
123                         "EventBasedManeuverTriggers in backward propagation");
124             }
125 
126             checkInitialFiringState(initialState);
127 
128         } // multiples calls to init : because it is a force model and by each detector
129     }
130 
131     /**
132      * Method to set the firing state on initialization. can be overloaded by sub
133      * classes.
134      *
135      * @param initialState initial spacecraft state
136      */
137     protected void checkInitialFiringState(final SpacecraftState initialState) {
138         if (isFiringOnInitialState(initialState)) {
139             setFiring(true, initialState.getDate());
140         }
141     }
142 
143     /**
144      * Method to check if the thruster is firing on initialization. can be called by
145      * sub classes
146      *
147      * @param initialState initial spacecraft state
148      * @return true if firing
149      */
150     protected boolean isFiringOnInitialState(final SpacecraftState initialState) {
151         // set the initial value of firing
152         final double insideThrustArcG = getStartFiringDetector().g(initialState);
153         boolean isInsideThrustArc = false;
154 
155         if (insideThrustArcG == 0) {
156             // bound of arc
157             // check state for the next second (which can be forward or backward)
158             final double nextSecond = 1;
159             final double nextValue = getStartFiringDetector().g(initialState.shiftedBy(nextSecond));
160             isInsideThrustArc = nextValue > 0;
161         } else {
162             isInsideThrustArc = insideThrustArcG > 0;
163         }
164         return isInsideThrustArc;
165     }
166 
167     /** {@inheritDoc} */
168     @Override
169     public Stream<EventDetector> getEventsDetectors() {
170         return Stream.of(getStartFiringDetector(), getStopFiringDetector());
171     }
172 
173     /** {@inheritDoc} */
174     @Override
175     public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventsDetectors(final Field<T> field) {
176         // not implemented, it depends on the input detectors
177         throw new OrekitException(OrekitMessages.FUNCTION_NOT_IMPLEMENTED,
178                 "EventBasedManeuverTriggers.getFieldEventsDetectors");
179     }
180 
181     /**
182      * Set the firing start or end date depending on the firing flag. There is no
183      * effect if the firing state is not changing.
184      * @param firing true to start a maneuver, false to stop
185      * @param date   date of event
186      */
187     public void setFiring(final boolean firing, final AbsoluteDate date) {
188         if (firing != isFiring(date)) {
189             if (firing) {
190                 if (!date.equals(triggeredEnd)) {
191                     triggeredStart = date;
192                 } // else no gap between stop and start, can not handle correctly : skip it
193             } else {
194                 triggeredEnd = date;
195             }
196         }
197     }
198 
199     /** {@inheritDoc} */
200     @Override
201     public boolean isFiring(final AbsoluteDate date, final double[] parameters) {
202         // Firing state does not depend on a parameter driver here
203         return isFiring(date);
204     }
205 
206     /** {@inheritDoc} */
207     @Override
208     public <T extends CalculusFieldElement<T>> boolean isFiring(final FieldAbsoluteDate<T> date, final T[] parameters) {
209         // Firing state does not depend on a parameter driver here
210         return isFiring(date.toAbsoluteDate());
211     }
212 
213     /** {@inheritDoc} */
214     @Override
215     public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
216         Action action = Action.CONTINUE; // default not taken into account
217         final boolean detectorManaged = getEventsDetectors()
218                 .anyMatch(managedDetector -> managedDetector.equals(detector));
219         if (detectorManaged) {
220             action = Action.RESET_EVENTS;
221             if (increasing) {
222                 if (detector.equals(getStartFiringDetector())) { // start of firing arc
223                     setFiring(true, s.getDate());
224                     action = Action.RESET_DERIVATIVES;
225                 } else if (detector.equals(getStopFiringDetector())) { // end of firing arc
226                     setFiring(false, s.getDate());
227                     action = Action.RESET_DERIVATIVES;
228                 }
229             }
230         }
231         return action;
232     }
233 
234     /**
235      * Check if maneuvering is on.
236      *
237      * @param date current date
238      * @return true if maneuver is on at this date
239      */
240     public boolean isFiring(final AbsoluteDate date) {
241         if (triggeredStart == null) {
242             // explicitly ignores state date, as propagator did not allow us to introduce
243             // discontinuity
244             return false;
245         } else if (date.isBefore(triggeredStart)) {
246             // we are unambiguously before maneuver start
247             // robustness, we should not pass here
248             return false;
249         } else {
250             // after start date
251             if (getTriggeredEnd() == null) {
252                 // explicitly ignores state date, as propagator did not allow us to introduce
253                 // discontinuity
254                 return true;
255             } else if (getTriggeredStart().isAfter(getTriggeredEnd())) {
256                 // last event is a start of maneuver, end not set yet
257                 // we are unambiguously before maneuver end
258                 return true;
259             } else if (date.isBefore(getTriggeredEnd())) {
260                 // we are unambiguously before maneuver end
261                 // robustness, we should not pass here
262                 return true;
263             } else {
264                 // we are at or after maneuver end
265                 return false;
266             }
267         }
268     }
269 
270     /**
271      * Getter for the triggered date of engine stop.
272      * @return Triggered date of engine stop
273      */
274     public AbsoluteDate getTriggeredEnd() {
275         return triggeredEnd;
276     }
277 
278     /**
279      * Getter triggered date of engine start.
280      * @return Triggered date of engine start
281      */
282     public AbsoluteDate getTriggeredStart() {
283         return triggeredStart;
284     }
285 
286 }