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