1   /* Copyright 2002-2021 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.radiation;
18  
19  import java.lang.reflect.Array;
20  import java.util.HashMap;
21  import java.util.Map;
22  import java.util.stream.Stream;
23  
24  import org.hipparchus.Field;
25  import org.hipparchus.CalculusFieldElement;
26  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
27  import org.hipparchus.geometry.euclidean.threed.Vector3D;
28  import org.hipparchus.ode.events.Action;
29  import org.hipparchus.util.FastMath;
30  import org.hipparchus.util.MathArrays;
31  import org.orekit.errors.OrekitException;
32  import org.orekit.errors.OrekitMessages;
33  import org.orekit.forces.AbstractForceModel;
34  import org.orekit.propagation.FieldSpacecraftState;
35  import org.orekit.propagation.SpacecraftState;
36  import org.orekit.propagation.events.AbstractDetector;
37  import org.orekit.propagation.events.EventDetector;
38  import org.orekit.propagation.events.FieldAbstractDetector;
39  import org.orekit.propagation.events.FieldEventDetector;
40  import org.orekit.propagation.events.handlers.EventHandler;
41  import org.orekit.propagation.events.handlers.FieldEventHandler;
42  import org.orekit.utils.Constants;
43  import org.orekit.utils.ExtendedPVCoordinatesProvider;
44  
45  /**
46   * Base class for radiation force models.
47   * @see SolarRadiationPressure
48   * @see ECOM2
49   * @since 10.2
50   */
51  public abstract class AbstractRadiationForceModel extends AbstractForceModel {
52  
53      /** Margin to force recompute lighting ratio derivatives when we are really inside penumbra. */
54      private static final double ANGULAR_MARGIN = 1.0e-10;
55  
56      /** Central body model. */
57      private final double equatorialRadius;
58  
59      /** Sun model. */
60      private final ExtendedPVCoordinatesProvider sun;
61  
62      /** Other occulting bodies to consider. The Moon for instance. */
63      private final Map<ExtendedPVCoordinatesProvider, Double> otherOccultingBodies;
64  
65      /**
66       * Default constructor.
67       * Only central body is considered.
68       * @param sun Sun model
69       * @param equatorialRadius central body spherical shape model (for umbra/penumbra computation)
70       */
71      protected AbstractRadiationForceModel(final ExtendedPVCoordinatesProvider sun, final double equatorialRadius) {
72          this.sun                  = sun;
73          this.equatorialRadius     = equatorialRadius;
74          this.otherOccultingBodies = new HashMap<>();
75      }
76  
77      /** {@inheritDoc} */
78      @Override
79      public boolean dependsOnPositionOnly() {
80          return false;
81      }
82  
83      /** {@inheritDoc} */
84      @Override
85      public Stream<EventDetector> getEventsDetectors() {
86          final EventDetector[] detectors = new EventDetector[2 + 2 * otherOccultingBodies.size()];
87          detectors[0] = new UmbraDetector();
88          detectors[1] = new PenumbraDetector();
89          int i = 2;
90          for (Map.Entry<ExtendedPVCoordinatesProvider, Double> entry : otherOccultingBodies.entrySet()) {
91              detectors[i]     = new GeneralUmbraDetector(entry.getKey(),    entry.getValue());
92              detectors[i + 1] = new GeneralPenumbraDetector(entry.getKey(), entry.getValue());
93              i = i + 2;
94          }
95          return Stream.of(detectors);
96      }
97  
98      /** {@inheritDoc} */
99      @Override
100     public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventsDetectors(final Field<T> field) {
101         final T zero = field.getZero();
102         @SuppressWarnings("unchecked")
103         final FieldEventDetector<T>[] detectors = (FieldEventDetector<T>[]) Array.newInstance(FieldEventDetector.class,
104                                                                                               2 + 2 * otherOccultingBodies.size());
105         detectors[0] = new FieldUmbraDetector<>(field);
106         detectors[1] = new FieldPenumbraDetector<>(field);
107         int i = 2;
108         for (Map.Entry<ExtendedPVCoordinatesProvider, Double> entry : otherOccultingBodies.entrySet()) {
109             detectors[i]     = new FieldGeneralUmbraDetector<>(field, entry.getKey(),    zero.newInstance(entry.getValue()));
110             detectors[i + 1] = new FieldGeneralPenumbraDetector<>(field, entry.getKey(), zero.newInstance(entry.getValue()));
111             i = i + 2;
112         }
113         return Stream.of(detectors);
114     }
115 
116     /**
117      * Get the useful angles for eclipse computation.
118      * @param sunPosition Sun position in the selected frame
119      * @param position the satellite's position in the selected frame
120      * @return the 3 angles {(satCentral, satSun), Central body apparent radius, Sun apparent radius}
121      */
122     protected double[] getEclipseAngles(final Vector3D sunPosition, final Vector3D position) {
123         final double[] angle = new double[3];
124 
125         final Vector3D satSunVector = sunPosition.subtract(position);
126 
127         // Sat-Sun / Sat-CentralBody angle
128         angle[0] = Vector3D.angle(satSunVector, position.negate());
129 
130         // Central body apparent radius
131         final double r = position.getNorm();
132         if (r <= equatorialRadius) {
133             throw new OrekitException(OrekitMessages.TRAJECTORY_INSIDE_BRILLOUIN_SPHERE, r);
134         }
135         angle[1] = FastMath.asin(equatorialRadius / r);
136 
137         // Sun apparent radius
138         angle[2] = FastMath.asin(Constants.SUN_RADIUS / satSunVector.getNorm());
139 
140         return angle;
141     }
142 
143 
144     /**
145      * Get the useful angles for eclipse computation.
146      * @param position the satellite's position in the selected frame
147      * @param occultingPosition Oculting body position in the selected frame
148      * @param occultingRadius Occulting body mean radius
149      * @param occultedPosition Occulted body position in the selected frame
150      * @param occultedRadius Occulted body mean radius
151      * @return the 3 angles {(satOcculting, satOcculted), Occulting body apparent radius, Occulted body apparent radius}
152      */
153     protected double[] getGeneralEclipseAngles(final Vector3D position, final Vector3D occultingPosition, final double occultingRadius,
154                                                final Vector3D occultedPosition, final double occultedRadius) {
155         final double[] angle = new double[3];
156 
157         final Vector3D satOccultedVector = occultedPosition.subtract(position);
158         final Vector3D satOccultingVector = occultingPosition.subtract(position);
159 
160         // Sat-Occulted / Sat-Occulting angle
161         angle[0] = Vector3D.angle(satOccultedVector, satOccultingVector);
162 
163         // Occulting body apparent radius
164         angle[1] = FastMath.asin(occultingRadius / satOccultingVector.getNorm());
165 
166         // Occulted body apparent radius
167         angle[2] = FastMath.asin(occultedRadius / satOccultedVector.getNorm());
168 
169         return angle;
170     }
171 
172     /**
173      * Get the useful angles for eclipse computation.
174      * @param sunPosition Sun position in the selected frame
175      * @param position the satellite's position in the selected frame.
176      * @param <T> extends CalculusFieldElement
177      * @return the 3 angles {(satCentral, satSun), Central body apparent radius, Sun apparent radius}
178      */
179     protected <T extends CalculusFieldElement<T>> T[] getEclipseAngles(final FieldVector3D<T> sunPosition, final FieldVector3D<T> position) {
180         final T[] angle = MathArrays.buildArray(position.getX().getField(), 3);
181 
182         final FieldVector3D<T> mP           = position.negate();
183         final FieldVector3D<T> satSunVector = mP.add(sunPosition);
184 
185         // Sat-Sun / Sat-CentralBody angle
186         angle[0] = FieldVector3D.angle(satSunVector, mP);
187 
188         // Central body apparent radius
189         final T r = position.getNorm();
190         if (r.getReal() <= equatorialRadius) {
191             throw new OrekitException(OrekitMessages.TRAJECTORY_INSIDE_BRILLOUIN_SPHERE, r);
192         }
193         angle[1] = r.reciprocal().multiply(equatorialRadius).asin();
194 
195         // Sun apparent radius
196         angle[2] = satSunVector.getNorm().reciprocal().multiply(Constants.SUN_RADIUS).asin();
197 
198         return angle;
199     }
200 
201     /**
202      * Get the useful angles for eclipse computation.
203      * @param occultingPosition Oculting body position in the selected frame
204      * @param occultingRadius Occulting body mean radius
205      * @param occultedPosition Occulted body position in the selected frame
206      * @param occultedRadius Occulted body mean radius
207      * @param position the satellite's position in the selected frame
208      * @param <T> extends RealFieldElement
209      * @return the 3 angles {(satOcculting, satOcculted), Occulting body apparent radius, Occulted body apparent radius}
210      */
211     protected <T extends CalculusFieldElement<T>> T[] getGeneralEclipseAngles(final FieldVector3D<T> position,
212                                                                               final FieldVector3D<T> occultingPosition, final T occultingRadius,
213                                                                               final FieldVector3D<T> occultedPosition, final T occultedRadius) {
214         final T[] angle = MathArrays.buildArray(position.getX().getField(), 3);
215 
216         final FieldVector3D<T> satOccultedVector = occultedPosition.subtract(position);
217         final FieldVector3D<T> satOccultingVector = occultingPosition.subtract(position);
218 
219         // Sat-Occulted / Sat-Occulting angle
220         angle[0] = FieldVector3D.angle(satOccultedVector, satOccultingVector);
221 
222         // Occulting body apparent radius
223         angle[1] = occultingRadius.divide(satOccultingVector.getNorm()).asin();
224 
225         // Occulted body apparent radius
226         angle[2] = occultedRadius.divide(satOccultedVector.getNorm()).asin();
227 
228         return angle;
229     }
230 
231     /**
232      * Add a new occulting body.
233      * Central body is already considered, it shall not be added this way.
234      * @param provider body PV provider
235      * @param radius body mean radius
236      */
237     public void addOccultingBody(final ExtendedPVCoordinatesProvider provider, final double radius) {
238         otherOccultingBodies.put(provider, radius);
239     }
240 
241     /**
242      * Getter for other occulting bodies to consider.
243      * @return the map of other occulting bodies and corresponding mean radiuses
244      */
245     public Map<ExtendedPVCoordinatesProvider, Double> getOtherOccultingBodies() {
246         return otherOccultingBodies;
247     }
248 
249     /**
250      * Getter for equatorial radius.
251      * @return central body equatorial radius
252      */
253     public double getEquatorialRadius() {
254         return equatorialRadius;
255     }
256 
257 
258     /** This class defines the umbra entry/exit detector. */
259     private class UmbraDetector extends AbstractDetector<UmbraDetector> {
260 
261         /** Build a new instance. */
262         UmbraDetector() {
263             super(60.0, 1.0e-3, DEFAULT_MAX_ITER, new EventHandler<UmbraDetector>() {
264 
265                 /** {@inheritDoc} */
266                 public Action eventOccurred(final SpacecraftState s, final UmbraDetector detector,
267                                             final boolean increasing) {
268                     return Action.RESET_DERIVATIVES;
269                 }
270 
271             });
272         }
273 
274         /** Private constructor with full parameters.
275          * <p>
276          * This constructor is private as users are expected to use the builder
277          * API with the various {@code withXxx()} methods to set up the instance
278          * in a readable manner without using a huge amount of parameters.
279          * </p>
280          * @param maxCheck maximum checking interval (s)
281          * @param threshold convergence threshold (s)
282          * @param maxIter maximum number of iterations in the event time search
283          * @param handler event handler to call at event occurrences
284          * @since 6.1
285          */
286         private UmbraDetector(final double maxCheck, final double threshold, final int maxIter,
287                               final EventHandler<? super UmbraDetector> handler) {
288             super(maxCheck, threshold, maxIter, handler);
289         }
290 
291         /** {@inheritDoc} */
292         @Override
293         protected UmbraDetector create(final double newMaxCheck, final double newThreshold, final int newMaxIter,
294                                        final EventHandler<? super UmbraDetector> newHandler) {
295             return new UmbraDetector(newMaxCheck, newThreshold, newMaxIter, newHandler);
296         }
297 
298         /** The G-function is the difference between the Sun-Sat-Central-Body angle and
299          * the central body apparent radius.
300          * @param s the current state information : date, kinematics, attitude
301          * @return value of the g function
302          */
303         public double g(final SpacecraftState s) {
304             final double[] angle = getEclipseAngles(sun.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(),
305                                                     s.getPVCoordinates().getPosition());
306             return angle[0] - angle[1] + angle[2] - ANGULAR_MARGIN;
307         }
308 
309     }
310 
311     /** This class defines the penumbra entry/exit detector. */
312     private class PenumbraDetector extends AbstractDetector<PenumbraDetector> {
313 
314         /** Build a new instance. */
315         PenumbraDetector() {
316             super(60.0, 1.0e-3, DEFAULT_MAX_ITER, new EventHandler<PenumbraDetector>() {
317 
318                 /** {@inheritDoc} */
319                 public Action eventOccurred(final SpacecraftState s, final PenumbraDetector detector,
320                                             final boolean increasing) {
321                     return Action.RESET_DERIVATIVES;
322                 }
323 
324             });
325         }
326 
327         /** Private constructor with full parameters.
328          * <p>
329          * This constructor is private as users are expected to use the builder
330          * API with the various {@code withXxx()} methods to set up the instance
331          * in a readable manner without using a huge amount of parameters.
332          * </p>
333          * @param maxCheck maximum checking interval (s)
334          * @param threshold convergence threshold (s)
335          * @param maxIter maximum number of iterations in the event time search
336          * @param handler event handler to call at event occurrences
337          * @since 6.1
338          */
339         private PenumbraDetector(final double maxCheck, final double threshold, final int maxIter,
340                                  final EventHandler<? super PenumbraDetector> handler) {
341             super(maxCheck, threshold, maxIter, handler);
342         }
343 
344         /** {@inheritDoc} */
345         @Override
346         protected PenumbraDetector create(final double newMaxCheck, final double newThreshold, final int newMaxIter,
347                                           final EventHandler<? super PenumbraDetector> newHandler) {
348             return new PenumbraDetector(newMaxCheck, newThreshold, newMaxIter, newHandler);
349         }
350 
351         /** The G-function is the difference between the Sun-Sat-Central-Body angle and
352          * the sum of the central body and Sun's apparent radius.
353          * @param s the current state information : date, kinematics, attitude
354          * @return value of the g function
355          */
356         public double g(final SpacecraftState s) {
357             final double[] angle = getEclipseAngles(sun.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(),
358                                                     s.getPVCoordinates().getPosition());
359             return angle[0] - angle[1] - angle[2] + ANGULAR_MARGIN;
360         }
361 
362     }
363 
364     /** This class defines the umbra entry/exit detector. */
365     private class FieldUmbraDetector<T extends CalculusFieldElement<T>>
366         extends FieldAbstractDetector<FieldUmbraDetector<T>, T> {
367 
368         /** Build a new instance.
369          * @param field field to which elements belong
370          */
371         FieldUmbraDetector(final Field<T> field) {
372             super(field.getZero().add(60.0), field.getZero().add(1.0e-3),
373                   DEFAULT_MAX_ITER, new FieldEventHandler<FieldUmbraDetector<T>, T>() {
374 
375                       /** {@inheritDoc} */
376                       public Action eventOccurred(final FieldSpacecraftState<T> s,
377                                                   final FieldUmbraDetector<T> detector,
378                                                   final boolean increasing) {
379                           return Action.RESET_DERIVATIVES;
380                       }
381 
382                   });
383         }
384 
385         /** Private constructor with full parameters.
386          * <p>
387          * This constructor is private as users are expected to use the builder
388          * API with the various {@code withXxx()} methods to set up the instance
389          * in a readable manner without using a huge amount of parameters.
390          * </p>
391          * @param maxCheck maximum checking interval (s)
392          * @param threshold convergence threshold (s)
393          * @param maxIter maximum number of iterations in the event time search
394          * @param handler event handler to call at event occurrences
395          */
396         private FieldUmbraDetector(final T maxCheck, final T threshold, final int maxIter,
397                                    final FieldEventHandler<? super FieldUmbraDetector<T>, T> handler) {
398             super(maxCheck, threshold, maxIter, handler);
399         }
400 
401         /** {@inheritDoc} */
402         @Override
403         protected FieldUmbraDetector<T> create(final T newMaxCheck, final T newThreshold, final int newMaxIter,
404                                                final FieldEventHandler<? super FieldUmbraDetector<T>, T> newHandler) {
405             return new FieldUmbraDetector<>(newMaxCheck, newThreshold, newMaxIter, newHandler);
406         }
407 
408         /** The G-function is the difference between the Sun-Sat-Central-Body angle and
409          * the central body apparent radius.
410          * @param s the current state information : date, kinematics, attitude
411          * @return value of the g function
412          */
413         public T g(final FieldSpacecraftState<T> s) {
414             final T[] angle = getEclipseAngles(sun.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(),
415                                                s.getPVCoordinates().getPosition());
416             return angle[0].subtract(angle[1]).add(angle[2]).subtract(ANGULAR_MARGIN);
417         }
418 
419     }
420 
421     /** This class defines the penumbra entry/exit detector. */
422     private class FieldPenumbraDetector<T extends CalculusFieldElement<T>>
423           extends FieldAbstractDetector<FieldPenumbraDetector<T>, T> {
424 
425         /** Build a new instance.
426          * @param field field to which elements belong
427          */
428         FieldPenumbraDetector(final Field<T> field) {
429             super(field.getZero().add(60.0), field.getZero().add(1.0e-3),
430                   DEFAULT_MAX_ITER, new FieldEventHandler<FieldPenumbraDetector<T>, T>() {
431 
432                       /** {@inheritDoc} */
433                       public Action eventOccurred(final FieldSpacecraftState<T> s,
434                                                   final FieldPenumbraDetector<T> detector,
435                                                   final boolean increasing) {
436                           return Action.RESET_DERIVATIVES;
437                       }
438 
439                   });
440         }
441 
442         /** Private constructor with full parameters.
443          * <p>
444          * This constructor is private as users are expected to use the builder
445          * API with the various {@code withXxx()} methods to set up the instance
446          * in a readable manner without using a huge amount of parameters.
447          * </p>
448          * @param maxCheck maximum checking interval (s)
449          * @param threshold convergence threshold (s)
450          * @param maxIter maximum number of iterations in the event time search
451          * @param handler event handler to call at event occurrences
452          */
453         private FieldPenumbraDetector(final T maxCheck, final T threshold, final int maxIter,
454                                       final FieldEventHandler<? super FieldPenumbraDetector<T>, T> handler) {
455             super(maxCheck, threshold, maxIter, handler);
456         }
457 
458         /** {@inheritDoc} */
459         @Override
460         protected FieldPenumbraDetector<T> create(final T newMaxCheck, final T newThreshold, final int newMaxIter,
461                                                   final FieldEventHandler<? super FieldPenumbraDetector<T>, T> newHandler) {
462             return new FieldPenumbraDetector<>(newMaxCheck, newThreshold, newMaxIter, newHandler);
463         }
464 
465         /** The G-function is the difference between the Sun-Sat-Central-Body angle and
466          * the sum of the central body and Sun's apparent radius.
467          * @param s the current state information : date, kinematics, attitude
468          * @return value of the g function
469          */
470         public T g(final FieldSpacecraftState<T> s) {
471             final T[] angle = getEclipseAngles(sun.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(),
472                                                s.getPVCoordinates().getPosition());
473             return angle[0].subtract(angle[1]).subtract(angle[2]).add(ANGULAR_MARGIN);
474         }
475 
476     }
477 
478     /** This class defines the umbra entry/exit detector. */
479     private class GeneralUmbraDetector extends AbstractDetector<GeneralUmbraDetector> {
480 
481         /** Occulting body PV provider. */
482         private ExtendedPVCoordinatesProvider provider;
483 
484         /** Occulting body mean radius. */
485         private double radius;
486 
487         /** Build a new instance.
488          * @param provider occulting body PV provider
489          * @param radius occulting body mean radius
490          */
491         GeneralUmbraDetector(final ExtendedPVCoordinatesProvider provider, final double radius) {
492             super(60.0, 1.0e-3, DEFAULT_MAX_ITER, new EventHandler<GeneralUmbraDetector>() {
493 
494                 /** {@inheritDoc} */
495                 public Action eventOccurred(final SpacecraftState s, final GeneralUmbraDetector detector,
496                                             final boolean increasing) {
497                     return Action.RESET_DERIVATIVES;
498                 }
499 
500             });
501             this.provider = provider;
502             this.radius   = radius;
503         }
504 
505         /** Private constructor with full parameters.
506          * <p>
507          * This constructor is private as users are expected to use the builder
508          * API with the various {@code withXxx()} methods to set up the instance
509          * in a readable manner without using a huge amount of parameters.
510          * </p>
511          * @param maxCheck maximum checking interval (s)
512          * @param threshold convergence threshold (s)
513          * @param maxIter maximum number of iterations in the event time search
514          * @param handler event handler to call at event occurrences
515          * @since 6.1
516          */
517         private GeneralUmbraDetector(final double maxCheck, final double threshold, final int maxIter,
518                                      final EventHandler<? super GeneralUmbraDetector> handler) {
519             super(maxCheck, threshold, maxIter, handler);
520         }
521 
522         /** {@inheritDoc} */
523         @Override
524         protected GeneralUmbraDetector create(final double newMaxCheck, final double newThreshold, final int newMaxIter,
525                                               final EventHandler<? super GeneralUmbraDetector> newHandler) {
526             return new GeneralUmbraDetector(newMaxCheck, newThreshold, newMaxIter, newHandler);
527         }
528 
529         /** The G-function is the difference between the Sun-Sat-Central-Body angle and
530          * the central body apparent radius.
531          * @param s the current state information : date, kinematics, attitude
532          * @return value of the g function
533          */
534         public double g(final SpacecraftState s) {
535             final double[] angle = getGeneralEclipseAngles(s.getPVCoordinates().getPosition(),
536                                                            provider.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(),
537                                                            radius, sun.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(),
538                                                            Constants.SUN_RADIUS);
539             return angle[0] - angle[1] + angle[2] - ANGULAR_MARGIN;
540         }
541 
542     }
543 
544     /** This class defines the umbra entry/exit detector. */
545     private class GeneralPenumbraDetector extends AbstractDetector<GeneralPenumbraDetector> {
546 
547         /** Occulting body PV provider. */
548         private ExtendedPVCoordinatesProvider provider;
549 
550         /** Occulting body mean radius. */
551         private double radius;
552 
553         /** Build a new instance.
554          * @param provider occulting body PV provider
555          * @param radius occulting body mean radius
556          */
557         GeneralPenumbraDetector(final ExtendedPVCoordinatesProvider provider, final double radius) {
558             super(60.0, 1.0e-3, DEFAULT_MAX_ITER, new EventHandler<GeneralPenumbraDetector>() {
559 
560                 /** {@inheritDoc} */
561                 public Action eventOccurred(final SpacecraftState s, final GeneralPenumbraDetector detector,
562                                             final boolean increasing) {
563                     return Action.RESET_DERIVATIVES;
564                 }
565 
566             });
567             this.provider = provider;
568             this.radius   = radius;
569         }
570 
571         /** Private constructor with full parameters.
572          * <p>
573          * This constructor is private as users are expected to use the builder
574          * API with the various {@code withXxx()} methods to set up the instance
575          * in a readable manner without using a huge amount of parameters.
576          * </p>
577          * @param maxCheck maximum checking interval (s)
578          * @param threshold convergence threshold (s)
579          * @param maxIter maximum number of iterations in the event time search
580          * @param handler event handler to call at event occurrences
581          * @since 6.1
582          */
583         private GeneralPenumbraDetector(final double maxCheck, final double threshold, final int maxIter,
584                                         final EventHandler<? super GeneralPenumbraDetector> handler) {
585             super(maxCheck, threshold, maxIter, handler);
586         }
587 
588         /** {@inheritDoc} */
589         @Override
590         protected GeneralPenumbraDetector create(final double newMaxCheck, final double newThreshold, final int newMaxIter,
591                                                  final EventHandler<? super GeneralPenumbraDetector> newHandler) {
592             return new GeneralPenumbraDetector(newMaxCheck, newThreshold, newMaxIter, newHandler);
593         }
594 
595         /** The G-function is the difference between the Sun-Sat-Central-Body angle and
596          * the central body apparent radius.
597          * @param s the current state information : date, kinematics, attitude
598          * @return value of the g function
599          */
600         public double g(final SpacecraftState s) {
601             final double[] angle = getGeneralEclipseAngles(s.getPVCoordinates().getPosition(),
602                                                            provider.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(),
603                                                            radius, sun.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(),
604                                                            Constants.SUN_RADIUS);
605             return angle[0] - angle[1] - angle[2] + ANGULAR_MARGIN;
606         }
607 
608     }
609 
610     /** This class defines the umbra entry/exit detector. */
611     private class FieldGeneralUmbraDetector<T extends CalculusFieldElement<T>>
612         extends FieldAbstractDetector<FieldGeneralUmbraDetector<T>, T> {
613 
614         /** Occulting body PV provider. */
615         private ExtendedPVCoordinatesProvider provider;
616 
617         /** Occulting body mean radius. */
618         private T radius;
619 
620         /** Build a new instance.
621          * @param field field to which elements belong
622          * @param provider occulting body PV provider
623          * @param radius occulting body mean radius
624          */
625         FieldGeneralUmbraDetector(final Field<T> field, final ExtendedPVCoordinatesProvider provider, final T radius) {
626             super(field.getZero().add(60.0), field.getZero().add(1.0e-3),
627                   DEFAULT_MAX_ITER, new FieldEventHandler<FieldGeneralUmbraDetector<T>, T>() {
628 
629                       /** {@inheritDoc} */
630                       public Action eventOccurred(final FieldSpacecraftState<T> s,
631                                                   final FieldGeneralUmbraDetector<T> detector,
632                                                   final boolean increasing) {
633                           return Action.RESET_DERIVATIVES;
634                       }
635 
636                   });
637             this.provider = provider;
638             this.radius   = radius;
639         }
640 
641         /** Private constructor with full parameters.
642          * <p>
643          * This constructor is private as users are expected to use the builder
644          * API with the various {@code withXxx()} methods to set up the instance
645          * in a readable manner without using a huge amount of parameters.
646          * </p>
647          * @param maxCheck maximum checking interval (s)
648          * @param threshold convergence threshold (s)
649          * @param maxIter maximum number of iterations in the event time search
650          * @param handler event handler to call at event occurrences
651          */
652         private FieldGeneralUmbraDetector(final T maxCheck, final T threshold,
653                                    final int maxIter,
654                                    final FieldEventHandler<? super FieldGeneralUmbraDetector<T>, T> handler) {
655             super(maxCheck, threshold, maxIter, handler);
656         }
657 
658         /** {@inheritDoc} */
659         @Override
660         protected FieldGeneralUmbraDetector<T> create(final T newMaxCheck, final T newThreshold, final int newMaxIter,
661                                                       final FieldEventHandler<? super FieldGeneralUmbraDetector<T>, T> newHandler) {
662             return new FieldGeneralUmbraDetector<>(newMaxCheck, newThreshold, newMaxIter, newHandler);
663         }
664 
665         /** The G-function is the difference between the Sun-Sat-Central-Body angle and
666          * the central body apparent radius.
667          * @param s the current state information : date, kinematics, attitude
668          * @return value of the g function
669          */
670         public T g(final FieldSpacecraftState<T> s) {
671             final T[] angle = getGeneralEclipseAngles(s.getPVCoordinates().getPosition(),
672                                                       provider.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(),
673                                                       radius, sun.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(),
674                                                       s.getA().getField().getZero().add(Constants.SUN_RADIUS));
675             return angle[0].subtract(angle[1]).add(angle[2]).subtract(ANGULAR_MARGIN);
676         }
677 
678     }
679 
680     /** This class defines the umbra entry/exit detector. */
681     private class FieldGeneralPenumbraDetector<T extends CalculusFieldElement<T>>
682         extends FieldAbstractDetector<FieldGeneralPenumbraDetector<T>, T> {
683 
684         /** Occulting body PV provider. */
685         private ExtendedPVCoordinatesProvider provider;
686 
687         /** Occulting body mean radius. */
688         private T radius;
689 
690         /** Build a new instance.
691          * @param field field to which elements belong
692          * @param provider occulting body PV provider
693          * @param radius occulting body mean radius
694          */
695         FieldGeneralPenumbraDetector(final Field<T> field, final ExtendedPVCoordinatesProvider provider, final T radius) {
696             super(field.getZero().add(60.0), field.getZero().add(1.0e-3),
697                   DEFAULT_MAX_ITER, new FieldEventHandler<FieldGeneralPenumbraDetector<T>, T>() {
698 
699                       /** {@inheritDoc} */
700                       public Action eventOccurred(final FieldSpacecraftState<T> s,
701                                                   final FieldGeneralPenumbraDetector<T> detector,
702                                                   final boolean increasing) {
703                           return Action.RESET_DERIVATIVES;
704                       }
705 
706                   });
707             this.provider = provider;
708             this.radius   = radius;
709         }
710 
711         /** Private constructor with full parameters.
712          * <p>
713          * This constructor is private as users are expected to use the builder
714          * API with the various {@code withXxx()} methods to set up the instance
715          * in a readable manner without using a huge amount of parameters.
716          * </p>
717          * @param maxCheck maximum checking interval (s)
718          * @param threshold convergence threshold (s)
719          * @param maxIter maximum number of iterations in the event time search
720          * @param handler event handler to call at event occurrences
721          */
722         private FieldGeneralPenumbraDetector(final T maxCheck, final T threshold, final int maxIter,
723                                              final FieldEventHandler<? super FieldGeneralPenumbraDetector<T>, T> handler) {
724             super(maxCheck, threshold, maxIter, handler);
725         }
726 
727         /** {@inheritDoc} */
728         @Override
729         protected FieldGeneralPenumbraDetector<T> create(final T newMaxCheck, final T newThreshold, final int newMaxIter,
730                                                          final FieldEventHandler<? super FieldGeneralPenumbraDetector<T>, T> newHandler) {
731             return new FieldGeneralPenumbraDetector<>(newMaxCheck, newThreshold, newMaxIter, newHandler);
732         }
733 
734         /** The G-function is the difference between the Sun-Sat-Central-Body angle and
735          * the central body apparent radius.
736          * @param s the current state information : date, kinematics, attitude
737          * @return value of the g function
738          */
739         public T g(final FieldSpacecraftState<T> s) {
740             final T[] angle = getGeneralEclipseAngles(s.getPVCoordinates().getPosition(),
741                                                       provider.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(),
742                                                       radius, sun.getPVCoordinates(s.getDate(), s.getFrame()).getPosition(),
743                                                       s.getA().getField().getZero().add(Constants.SUN_RADIUS));
744             return angle[0].subtract(angle[1]).subtract(angle[2]).add(ANGULAR_MARGIN);
745         }
746 
747     }
748 }