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 > 0) AND when the ground point is sunlit (Sun
39 * elevation > 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 boolean dependsOnTimeOnly() {
215 return getDetectors().stream().allMatch(EventDetector::dependsOnTimeOnly);
216 }
217
218 @Override
219 public double g(final SpacecraftState s) {
220 // can't use stream/lambda here because g(s) throws a checked exception
221 // so write out and combine the map and reduce loops
222 double ret = Double.NaN; // return value
223 boolean first = true;
224 for (final EventDetector detector : detectors) {
225 if (first) {
226 ret = detector.g(s);
227 first = false;
228 } else {
229 ret = operator.combine(ret, detector.g(s));
230 }
231 }
232 // return the result of applying the operator to all operands
233 return ret;
234 }
235
236 @Override
237 protected BooleanDetector create(final EventDetectionSettings detectionSettings,
238 final EventHandler newHandler) {
239 return new BooleanDetector(detectors, operator, detectionSettings, newHandler);
240 }
241
242 @Override
243 public void init(final SpacecraftState s0, final AbsoluteDate t) {
244 super.init(s0, t);
245 for (final EventDetector detector : detectors) {
246 detector.init(s0, t);
247 }
248 }
249
250 @Override
251 public void reset(final SpacecraftState state, final AbsoluteDate target) {
252 super.init(state, target);
253 for (final EventDetector detector : detectors) {
254 detector.reset(state, target);
255 }
256 }
257
258 @Override
259 public void finish(final SpacecraftState state) {
260 super.finish(state);
261 for (final EventDetector detector : detectors) {
262 detector.finish(state);
263 }
264 }
265
266 /**
267 * Get the list of original detectors.
268 * @return the list of original detectors
269 * @since 10.2
270 */
271 public List<EventDetector> getDetectors() {
272 return new ArrayList<>(detectors);
273 }
274
275 /** Local class for operator. */
276 private enum Operator {
277
278 /** And operator. */
279 AND() {
280
281 @Override
282 /** {@inheritDoc} */
283 public double combine(final double g1, final double g2) {
284 return FastMath.min(g1, g2);
285 }
286
287 },
288
289 /** Or operator. */
290 OR() {
291
292 @Override
293 /** {@inheritDoc} */
294 public double combine(final double g1, final double g2) {
295 return FastMath.max(g1, g2);
296 }
297
298 };
299
300 /** Combine two g functions evaluations.
301 * @param g1 first evaluation
302 * @param g2 second evaluation
303 * @return combined evaluation
304 */
305 public abstract double combine(double g1, double g2);
306
307 }
308
309 }