1   /* Contributed in the public domain.
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.propagation.events;
18  
19  
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.List;
24  import java.util.NoSuchElementException;
25  
26  import org.hipparchus.util.FastMath;
27  import org.orekit.propagation.SpacecraftState;
28  import org.orekit.propagation.events.handlers.ContinueOnEvent;
29  import org.orekit.propagation.events.handlers.EventHandler;
30  import org.orekit.time.AbsoluteDate;
31  
32  /**
33   * This class provides AND and OR operations for event detectors. This class treats
34   * positive values of the g function as true and negative values as false.
35   *
36   * <p> One example for an imaging satellite might be to only detect events when a
37   * satellite is overhead (elevation &gt; 0) AND when the ground point is sunlit (Sun
38   * elevation &gt; 0). Another slightly contrived example using the OR operator would be to
39   * detect access to a set of ground stations and only report events when the satellite
40   * enters or leaves the field of view of the set, but not hand-offs between the ground
41   * stations.
42   *
43   * <p> For the BooleanDetector is important that the sign of the g function of the
44   * underlying event detector is not arbitrary, but has a semantic meaning, e.g. in or out,
45   * true or false. This class works well with event detectors that detect entry to or exit
46   * from a region, e.g. {@link EclipseDetector}, {@link ElevationDetector}, {@link
47   * LatitudeCrossingDetector}. Using this detector with detectors that are not based on
48   * entry to or exit from a region, e.g. {@link DateDetector}, {@link
49   * LongitudeCrossingDetector}, will likely lead to unexpected results. To apply conditions
50   * to this latter type of event detectors a {@link EventEnablingPredicateFilter} is
51   * usually more appropriate.
52   *
53   * @author Evan Ward
54   * @see #andCombine(Collection)
55   * @see #orCombine(Collection)
56   * @see #notCombine(EventDetector)
57   * @see EventEnablingPredicateFilter
58   * @see EventSlopeFilter
59   */
60  public class BooleanDetector extends AbstractDetector<BooleanDetector> {
61  
62      /** Original detectors: the operands. */
63      private final List<EventDetector> detectors;
64  
65      /** The composition function. Should be associative for predictable behavior. */
66      private final Operator operator;
67  
68      /**
69       * Private constructor with all the parameters.
70       *
71       * @param detectors    the operands.
72       * @param operator     reduction operator to apply to value of the g function of the
73       *                     operands.
74       * @param newMaxCheck  max check interval in seconds.
75       * @param newThreshold convergence threshold in seconds.
76       * @param newMaxIter   max iterations.
77       * @param newHandler   event handler.
78       */
79      protected BooleanDetector(final List<EventDetector> detectors,
80                                final Operator operator,
81                                final AdaptableInterval newMaxCheck,
82                                final double newThreshold,
83                                final int newMaxIter,
84                                final EventHandler newHandler) {
85          super(newMaxCheck, newThreshold, newMaxIter, newHandler);
86          this.detectors = detectors;
87          this.operator = operator;
88      }
89  
90      /**
91       * Create a new event detector that is the logical AND of the given event detectors.
92       *
93       * <p> The created event detector's g function is positive if and only if the g
94       * functions of all detectors in {@code detectors} are positive.
95       *
96       * <p> The starting interval, threshold, and iteration count are set to the most
97       * stringent (minimum) of all the {@code detectors}. The event handlers of the
98       * underlying {@code detectors} are not used, instead the default handler is {@link
99       * ContinueOnEvent}.
100      *
101      * @param detectors the operands. Must contain at least one detector.
102      * @return a new event detector that is the logical AND of the operands.
103      * @throws NoSuchElementException if {@code detectors} is empty.
104      * @see BooleanDetector
105      * @see #andCombine(Collection)
106      * @see #orCombine(EventDetector...)
107      * @see #notCombine(EventDetector)
108      */
109     public static BooleanDetector andCombine(final EventDetector... detectors) {
110         return andCombine(Arrays.asList(detectors));
111     }
112 
113     /**
114      * Create a new event detector that is the logical AND of the given event detectors.
115      *
116      * <p> The created event detector's g function is positive if and only if the g
117      * functions of all detectors in {@code detectors} are positive.
118      *
119      * <p> The starting interval, threshold, and iteration count are set to the most
120      * stringent (minimum) of the {@code detectors}. The event handlers of the
121      * underlying {@code detectors} are not used, instead the default handler is {@link
122      * ContinueOnEvent}.
123      *
124      * @param detectors the operands. Must contain at least one detector.
125      * @return a new event detector that is the logical AND of the operands.
126      * @throws NoSuchElementException if {@code detectors} is empty.
127      * @see BooleanDetector
128      * @see #andCombine(EventDetector...)
129      * @see #orCombine(Collection)
130      * @see #notCombine(EventDetector)
131      */
132     public static BooleanDetector andCombine(final Collection<? extends EventDetector> detectors) {
133 
134         return new BooleanDetector(new ArrayList<>(detectors), // copy for immutability
135                 Operator.AND,
136                 s -> {
137                     double minInterval = Double.POSITIVE_INFINITY;
138                     for (final EventDetector detector : detectors) {
139                         minInterval = FastMath.min(minInterval, detector.getMaxCheckInterval().currentInterval(s));
140                     }
141                     return minInterval;
142                 },
143                 detectors.stream().map(EventDetector::getThreshold).min(Double::compareTo).get(),
144                 detectors.stream().map(EventDetector::getMaxIterationCount).min(Integer::compareTo).get(),
145                 new ContinueOnEvent());
146     }
147 
148     /**
149      * Create a new event detector that is the logical OR of the given event detectors.
150      *
151      * <p> The created event detector's g function is positive if and only if at least
152      * one of g functions of the event detectors in {@code detectors} is positive.
153      *
154      * <p> The starting interval, threshold, and iteration count are set to the most
155      * stringent (minimum) of the {@code detectors}. The event handlers of the
156      * underlying EventDetectors are not used, instead the default handler is {@link
157      * ContinueOnEvent}.
158      *
159      * @param detectors the operands. Must contain at least one detector.
160      * @return a new event detector that is the logical OR of the operands.
161      * @throws NoSuchElementException if {@code detectors} is empty.
162      * @see BooleanDetector
163      * @see #orCombine(Collection)
164      * @see #andCombine(EventDetector...)
165      * @see #notCombine(EventDetector)
166      */
167     public static BooleanDetector orCombine(final EventDetector... detectors) {
168         return orCombine(Arrays.asList(detectors));
169     }
170 
171     /**
172      * Create a new event detector that is the logical OR of the given event detectors.
173      *
174      * <p> The created event detector's g function is positive if and only if at least
175      * one of g functions of the event detectors in {@code detectors} is positive.
176      *
177      * <p> The starting interval, threshold, and iteration count are set to the most
178      * stringent (minimum) of the {@code detectors}. The event handlers of the
179      * underlying EventDetectors are not used, instead the default handler is {@link
180      * ContinueOnEvent}.
181      *
182      * @param detectors the operands. Must contain at least one detector.
183      * @return a new event detector that is the logical OR of the operands.
184      * @throws NoSuchElementException if {@code detectors} is empty.
185      * @see BooleanDetector
186      * @see #orCombine(EventDetector...)
187      * @see #andCombine(Collection)
188      * @see #notCombine(EventDetector)
189      */
190     public static BooleanDetector orCombine(final Collection<? extends EventDetector> detectors) {
191 
192         return new BooleanDetector(new ArrayList<>(detectors), // copy for immutability
193                 Operator.OR,
194                 s -> {
195                     double minInterval = Double.POSITIVE_INFINITY;
196                     for (final EventDetector detector : detectors) {
197                         minInterval = FastMath.min(minInterval, detector.getMaxCheckInterval().currentInterval(s));
198                     }
199                     return minInterval;
200                 },
201                 detectors.stream().map(EventDetector::getThreshold).min(Double::compareTo).get(),
202                 detectors.stream().map(EventDetector::getMaxIterationCount).min(Integer::compareTo).get(),
203                 new ContinueOnEvent());
204     }
205 
206     /**
207      * Create a new event detector that negates the g function of another detector.
208      *
209      * <p> This detector will be initialized with the same {@link
210      * EventDetector#getMaxCheckInterval()}, {@link EventDetector#getThreshold()}, and
211      * {@link EventDetector#getMaxIterationCount()} as {@code detector}. The event handler
212      * of the underlying detector is not used, instead the default handler is {@link
213      * ContinueOnEvent}.
214      *
215      * @param detector to negate.
216      * @return an new event detector whose g function is the same magnitude but opposite
217      * sign of {@code detector}.
218      * @see #andCombine(Collection)
219      * @see #orCombine(Collection)
220      * @see BooleanDetector
221      */
222     public static NegateDetector notCombine(final EventDetector detector) {
223         return new NegateDetector(detector);
224     }
225 
226     @Override
227     public double g(final SpacecraftState s) {
228         // can't use stream/lambda here because g(s) throws a checked exception
229         // so write out and combine the map and reduce loops
230         double ret = Double.NaN; // return value
231         boolean first = true;
232         for (final EventDetector detector : detectors) {
233             if (first) {
234                 ret = detector.g(s);
235                 first = false;
236             } else {
237                 ret = operator.combine(ret, detector.g(s));
238             }
239         }
240         // return the result of applying the operator to all operands
241         return ret;
242     }
243 
244     @Override
245     protected BooleanDetector create(final AdaptableInterval newMaxCheck,
246                                      final double newThreshold,
247                                      final int newMaxIter,
248                                      final EventHandler newHandler) {
249         return new BooleanDetector(detectors, operator, newMaxCheck, newThreshold,
250                 newMaxIter, newHandler);
251     }
252 
253     @Override
254     public void init(final SpacecraftState s0,
255                      final AbsoluteDate t) {
256         super.init(s0, t);
257         for (final EventDetector detector : detectors) {
258             detector.init(s0, t);
259         }
260     }
261 
262     /**
263      * Get the list of original detectors.
264      * @return the list of original detectors
265      * @since 10.2
266      */
267     public List<EventDetector> getDetectors() {
268         return new ArrayList<EventDetector>(detectors);
269     }
270 
271     /** Local class for operator. */
272     private enum Operator {
273 
274         /** And operator. */
275         AND() {
276 
277             @Override
278             /** {@inheritDoc} */
279             public double combine(final double g1, final double g2) {
280                 return FastMath.min(g1, g2);
281             }
282 
283         },
284 
285         /** Or operator. */
286         OR() {
287 
288             @Override
289             /** {@inheritDoc} */
290             public double combine(final double g1, final double g2) {
291                 return FastMath.max(g1, g2);
292             }
293 
294         };
295 
296         /** Combine two g functions evaluations.
297          * @param g1 first evaluation
298          * @param g2 second evaluation
299          * @return combined evaluation
300          */
301         public abstract double combine(double g1, double g2);
302 
303     };
304 
305 }