1   /* Copyright 2002-2022 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.FieldEventDetector;
32  import org.orekit.propagation.events.handlers.EventHandler;
33  import org.orekit.propagation.events.handlers.FieldEventHandler;
34  import org.orekit.time.AbsoluteDate;
35  import org.orekit.time.FieldAbsoluteDate;
36  
37  /**
38   * Maneuver triggers based on a single event detector that defines firing intervals.
39   * <p>
40   * Firing intervals correspond to time spans with positive value of the event detector
41   * {@link EventDetector#g(SpacecraftState) g} function.
42   * </p>
43   * @param <T> type of the interval detector
44   * @see StartStopEventsTrigger
45   * @author Luc Maisonobe
46   * @since 11.1
47   */
48  public abstract class IntervalEventTrigger<T extends AbstractDetector<T>> extends AbstractManeuverTriggers {
49  
50      /** Intervals detector. */
51      private final T firingIntervalDetector;
52  
53      /** Cached field-based detectors. */
54      private final transient Map<Field<? extends CalculusFieldElement<?>>, FieldEventDetector<? extends CalculusFieldElement<?>>> cached;
55  
56      /** Simple constructor.
57       * <p>
58       * Note that the {@code intervalDetector} passed as an argument is used only
59       * as a <em>prototype</em> from which a new detector will be built using its
60       * {@link AbstractDetector#withHandler(EventHandler) withHandler} method to
61       * set up an internal handler. The original event handler from the prototype
62       * will be <em>ignored</em> and never called.
63       * </p>
64       * <p>
65       * If the trigger is used in a {@link org.orekit.propagation.FieldPropagator field-based propagation},
66       * the detector will be automatically converted to a field equivalent. Beware however that the
67       * {@link FieldEventHandler#eventOccurred(FieldSpacecraftState, FieldEventDetector, boolean) eventOccurred}
68       * of the converted propagator <em>will</em> call the method with the same name in the prototype
69       * detector, in order to get the correct return value.
70       * </p>
71       * @param prototypeFiringIntervalDetector prototype detector for firing interval
72       */
73      public IntervalEventTrigger(final T prototypeFiringIntervalDetector) {
74          this.firingIntervalDetector = prototypeFiringIntervalDetector.withHandler(new Handler());
75          this.cached                 = new HashMap<>();
76      }
77  
78      /**
79       * Getter for the firing interval detector.
80       * @return firing interval detector
81       */
82      public T getFiringIntervalDetector() {
83          return firingIntervalDetector;
84      }
85  
86      /** {@inheritDoc} */
87      @Override
88      protected boolean isFiringOnInitialState(final SpacecraftState initialState, final boolean isForward) {
89  
90          // set the initial value of firing
91          final double insideThrustArcG = firingIntervalDetector.g(initialState);
92          if (insideThrustArcG == 0) {
93              // bound of arc
94              // check state for the upcoming times
95              final double shift = (isForward ? 2 : -2) * firingIntervalDetector.getThreshold();
96              if (firingIntervalDetector.g(initialState.shiftedBy(shift)) > 0) {
97                  // we are entering the firing interval, from start if forward, from end if backward
98                  notifyResetters(initialState, isForward);
99                  return true;
100             } else {
101                 // we are leaving the firing interval, from end if forward, from start if backward
102                 notifyResetters(initialState, !isForward);
103                 return false;
104             }
105         } else {
106             return insideThrustArcG > 0;
107         }
108 
109     }
110 
111     /** {@inheritDoc} */
112     @Override
113     public Stream<EventDetector> getEventsDetectors() {
114         return Stream.of(firingIntervalDetector);
115     }
116 
117     /** {@inheritDoc} */
118     public <S extends CalculusFieldElement<S>> Stream<FieldEventDetector<S>> getFieldEventsDetectors(final Field<S> field) {
119 
120         @SuppressWarnings("unchecked")
121         FieldEventDetector<S> fd = (FieldEventDetector<S>) cached.get(field);
122         if (fd == null) {
123             fd = convertAndSetUpHandler(field);
124             cached.put(field, fd);
125         }
126 
127         return Stream.of(fd);
128 
129     }
130 
131     /** Convert a detector and set up check interval, threshold and new handler.
132      * <p>
133      * This method is not inlined in {@link #getFieldEventsDetectors(Field)} because the
134      * parameterized types confuses the Java compiler.
135      * </p>
136      * @param field field to which the state belongs
137      * @param <D> type of the event detector
138      * @param <S> type of the field elements
139      * @return converted firing intervals detector
140      */
141     private <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>> D convertAndSetUpHandler(final Field<S> field) {
142         final FieldAbstractDetector<D, S> converted = convertIntervalDetector(field, firingIntervalDetector);
143         return converted.
144                withMaxCheck(field.getZero().newInstance(firingIntervalDetector.getMaxCheckInterval())).
145                withThreshold(field.getZero().newInstance(firingIntervalDetector.getThreshold())).
146                withHandler(new FieldHandler<>());
147     }
148 
149     /** Convert a primitive firing intervals detector into a field firing intervals detector.
150      * <p>
151      * There is not need to set up {@link FieldAbstractDetector#withMaxCheck(CalculusFieldElement) withMaxCheck},
152      * {@link FieldAbstractDetector#withThreshold(CalculusFieldElement) withThreshold}, or
153      * {@link FieldAbstractDetector#withHandler(org.orekit.propagation.events.handlers.FieldEventHandler) withHandler}
154      * in the converted detector, this will be done by caller.
155      * </p>
156      * <p>
157      * A skeleton implementation of this method to convert some {@code XyzDetector} into {@code FieldXyzDetector},
158      * considering these detectors are created from a date and a number parameter is:
159      * </p>
160      * <pre>{@code
161      *     protected <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>>
162      *         FieldAbstractDetector<D, S> convertIntervalDetector(final Field<S> field, final XyzDetector detector) {
163      *
164      *         final FieldAbsoluteDate<S> date  = new FieldAbsoluteDate<>(field, detector.getDate());
165      *         final S                    param = field.getZero().newInstance(detector.getParam());
166      *
167      *         final FieldAbstractDetector<D, S> converted = (FieldAbstractDetector<D, S>) new FieldXyzDetector<>(date, param);
168      *         return converted;
169      *
170      *     }
171      * }
172      * </pre>
173      * @param field field to which the state belongs
174      * @param detector primitive firing intervals detector to convert
175      * @param <D> type of the event detector
176      * @param <S> type of the field elements
177      * @return converted firing intervals detector
178      */
179     protected abstract <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>>
180         FieldAbstractDetector<D, S> convertIntervalDetector(Field<S> field, T detector);
181 
182     /** Local handler for both start and stop triggers. */
183     private class Handler implements EventHandler<T> {
184 
185         /** Propagation direction. */
186         private boolean forward;
187 
188         /** {@inheritDoc} */
189         @Override
190         public void init(final SpacecraftState initialState, final AbsoluteDate target, final T detector) {
191             forward = target.isAfterOrEqualTo(initialState);
192             initializeResetters(initialState, target);
193         }
194 
195         /** {@inheritDoc} */
196         @Override
197         public Action eventOccurred(final SpacecraftState s, final T detector, final boolean increasing) {
198             if (forward) {
199                 getFirings().addValidAfter(increasing, s.getDate(), false);
200             } else {
201                 getFirings().addValidBefore(!increasing, s.getDate(), false);
202             }
203             notifyResetters(s, increasing);
204             return Action.RESET_STATE;
205         }
206 
207         /** {@inheritDoc} */
208         @Override
209         public SpacecraftState resetState(final T detector, final SpacecraftState oldState) {
210             return applyResetters(oldState);
211         }
212 
213     }
214 
215     /** Local handler for both start and stop triggers.
216      * @param <S> type of the field elements
217      */
218     private class FieldHandler<D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>> implements FieldEventHandler<D, S> {
219 
220         /** Propagation direction. */
221         private boolean forward;
222 
223         /** {@inheritDoc} */
224         @Override
225         public void init(final FieldSpacecraftState<S> initialState,
226                          final FieldAbsoluteDate<S> target,
227                          final D detector) {
228             forward = target.isAfterOrEqualTo(initialState);
229             initializeResetters(initialState, target);
230         }
231 
232         /** {@inheritDoc} */
233         @Override
234         public Action eventOccurred(final FieldSpacecraftState<S> s, final D detector, final boolean increasing) {
235             if (forward) {
236                 getFirings().addValidAfter(increasing, s.getDate().toAbsoluteDate(), false);
237             } else {
238                 getFirings().addValidBefore(!increasing, s.getDate().toAbsoluteDate(), false);
239             }
240             notifyResetters(s, increasing);
241             return Action.RESET_STATE;
242         }
243 
244         /** {@inheritDoc} */
245         @Override
246         public FieldSpacecraftState<S> resetState(final D detector, final FieldSpacecraftState<S> oldState) {
247             return applyResetters(oldState);
248         }
249 
250     }
251 
252 }