1   /* Copyright 2022-2025 Romain Serra
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 org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
22  import org.hipparchus.geometry.euclidean.threed.Vector3D;
23  import org.hipparchus.util.FastMath;
24  import org.orekit.frames.Frame;
25  import org.orekit.propagation.FieldSpacecraftState;
26  import org.orekit.propagation.SpacecraftState;
27  import org.orekit.propagation.events.intervals.AdaptableInterval;
28  import org.orekit.propagation.events.EventDetector;
29  import org.orekit.propagation.events.EventDetectionSettings;
30  import org.orekit.propagation.events.intervals.FieldAdaptableInterval;
31  import org.orekit.propagation.events.FieldEventDetector;
32  import org.orekit.propagation.events.FieldEventDetectionSettings;
33  import org.orekit.propagation.events.handlers.EventHandler;
34  import org.orekit.propagation.events.handlers.FieldEventHandler;
35  import org.orekit.propagation.events.handlers.FieldResetDerivativesOnEvent;
36  import org.orekit.propagation.events.handlers.ResetDerivativesOnEvent;
37  import org.orekit.time.AbsoluteDate;
38  import org.orekit.time.FieldAbsoluteDate;
39  import org.orekit.utils.ExtendedPositionProvider;
40  
41  import java.util.ArrayList;
42  import java.util.List;
43  
44  /**
45   * Class defining a flux model from a single occulted body, casting a shadow on a spherical occulting body.
46   * It cannot model oblate bodies or multiple occulting objects (for this, see {@link SolarRadiationPressure}).
47   *
48   * @author Romain Serra
49   * @see AbstractSolarLightFluxModel
50   * @see LightFluxModel
51   * @see "Montenbruck, Oliver, and Gill, Eberhard. Satellite orbits : models, methods, and
52   *  * applications. Berlin New York: Springer, 2000."
53   * @since 12.2
54   */
55  public class ConicallyShadowedLightFluxModel extends AbstractSolarLightFluxModel {
56  
57      /**
58       * Default max. check interval for eclipse detection.
59       */
60      private static final double CONICAL_ECLIPSE_MAX_CHECK = 60;
61  
62      /**
63       * Default threshold for eclipse detection.
64       */
65      private static final double CONICAL_ECLIPSE_THRESHOLD = 1e-7;
66  
67      /** Occulted body radius. */
68      private final double occultedBodyRadius;
69  
70      /** Cached date. */
71      private AbsoluteDate lastDate;
72  
73      /** Cached frame. */
74      private Frame propagationFrame;
75  
76      /** Cached position. */
77      private Vector3D lastPosition;
78  
79      /**
80       * Constructor.
81       * @param kRef reference flux
82       * @param occultedBodyRadius radius of occulted body (light source)
83       * @param occultedBody position provider for light source
84       * @param occultingBodyRadius radius of central, occulting body
85       * @param eventDetectionSettings user-defined detection settings for eclipses (if ill-tuned, events might be missed or performance might drop)
86       */
87      public ConicallyShadowedLightFluxModel(final double kRef, final double occultedBodyRadius,
88                                             final ExtendedPositionProvider occultedBody,
89                                             final double occultingBodyRadius, final EventDetectionSettings eventDetectionSettings) {
90          super(kRef, occultedBody, occultingBodyRadius, eventDetectionSettings);
91          this.occultedBodyRadius = occultedBodyRadius;
92      }
93  
94      /**
95       * Constructor with default event detection settings.
96       * @param kRef reference flux
97       * @param occultedBodyRadius radius of occulted body (light source)
98       * @param occultedBody position provider for light source
99       * @param occultingBodyRadius radius of central, occulting body
100      */
101     public ConicallyShadowedLightFluxModel(final double kRef, final double occultedBodyRadius,
102                                            final ExtendedPositionProvider occultedBody, final double occultingBodyRadius) {
103         this(kRef, occultedBodyRadius, occultedBody, occultingBodyRadius, getDefaultEclipseDetectionSettings());
104     }
105 
106     /**
107      * Constructor with default value for reference flux.
108      * @param occultedBodyRadius radius of occulted body (light source)
109      * @param occultedBody position provider for light source
110      * @param occultingBodyRadius radius of central, occulting body
111      */
112     public ConicallyShadowedLightFluxModel(final double occultedBodyRadius, final ExtendedPositionProvider occultedBody,
113                                            final double occultingBodyRadius) {
114         super(occultedBody, occultingBodyRadius, getDefaultEclipseDetectionSettings());
115         this.occultedBodyRadius = occultedBodyRadius;
116     }
117 
118     /**
119      * Define default detection settings for eclipses.
120      * @return default settings
121      */
122     public static EventDetectionSettings getDefaultEclipseDetectionSettings() {
123         return new EventDetectionSettings(CONICAL_ECLIPSE_MAX_CHECK, CONICAL_ECLIPSE_THRESHOLD,
124                 EventDetectionSettings.DEFAULT_MAX_ITER);
125     }
126 
127     /** {@inheritDoc} */
128     @Override
129     public void init(final SpacecraftState initialState, final AbsoluteDate targetDate) {
130         super.init(initialState, targetDate);
131         lastDate = initialState.getDate();
132         propagationFrame = initialState.getFrame();
133         lastPosition = getOccultedBodyPosition(lastDate, propagationFrame);
134     }
135 
136     /** {@inheritDoc} */
137     @Override
138     public <T extends CalculusFieldElement<T>> void init(final FieldSpacecraftState<T> initialState,
139                                                          final FieldAbsoluteDate<T> targetDate) {
140         super.init(initialState, targetDate);
141         lastDate = initialState.getDate().toAbsoluteDate();
142         propagationFrame = initialState.getFrame();
143         lastPosition = getOccultedBodyPosition(initialState.getDate(), propagationFrame).toVector3D();
144     }
145 
146     /**
147      * Get occulted body position using cache.
148      * @param date date
149      * @return occulted body position
150      */
151     private Vector3D getOccultedBodyPosition(final AbsoluteDate date) {
152         if (!lastDate.isEqualTo(date)) {
153             lastPosition = getOccultedBodyPosition(date, propagationFrame);
154             lastDate = date;
155         }
156         return lastPosition;
157     }
158 
159     /**
160      * Get occulted body position using cache (non-Field and no derivatives case).
161      * @param fieldDate date
162      * @param <T> field type
163      * @return occulted body position
164      */
165     private <T extends CalculusFieldElement<T>> FieldVector3D<T> getOccultedBodyPosition(final FieldAbsoluteDate<T> fieldDate) {
166         if (fieldDate.hasZeroField()) {
167             final AbsoluteDate date = fieldDate.toAbsoluteDate();
168             if (!lastDate.isEqualTo(date)) {
169                 lastPosition = getOccultedBodyPosition(date, propagationFrame);
170                 lastDate = date;
171             }
172             return new FieldVector3D<>(fieldDate.getField(), lastPosition);
173         } else {
174             return getOccultedBodyPosition(fieldDate, propagationFrame);
175         }
176     }
177 
178     /** {@inheritDoc} */
179     @Override
180     protected double getLightingRatio(final Vector3D position, final Vector3D occultedBodyPosition) {
181         final double distanceSun = occultedBodyPosition.getNorm();
182         final double squaredDistance = position.getNormSq();
183         final Vector3D occultedBodyDirection = occultedBodyPosition.normalize();
184         final double s0 = -position.dotProduct(occultedBodyDirection);
185         if (s0 > 0.0) {
186             final double l = FastMath.sqrt(squaredDistance - s0 * s0);
187             final double sinf2 = (occultedBodyRadius - getOccultingBodyRadius()) / distanceSun;
188             final double l2 = (s0 * sinf2 - getOccultingBodyRadius()) / FastMath.sqrt(1.0 - sinf2 * sinf2);
189             if (FastMath.abs(l2) - l >= 0.0) { // umbra
190                 return 0.;
191             }
192             final double sinf1 = (occultedBodyRadius + getOccultingBodyRadius()) / distanceSun;
193             final double l1 = (s0 * sinf1 + getOccultingBodyRadius()) / FastMath.sqrt(1.0 - sinf1 * sinf1);
194             if (l1 - l > 0.0) { // penumbra
195                 final Vector3D relativePosition = occultedBodyPosition.subtract(position);
196                 final double relativeDistance = relativePosition.getNorm();
197                 final double a = FastMath.asin(occultedBodyRadius / relativeDistance);
198                 final double a2 = a * a;
199                 final double r = FastMath.sqrt(squaredDistance);
200                 final double b = FastMath.asin(getOccultingBodyRadius() / r);
201                 final double c = FastMath.acos(-(relativePosition.dotProduct(position)) / (r * relativeDistance));
202                 final double x = (c * c + a2 - b * b) / (2 * c);
203                 final double y = FastMath.sqrt(FastMath.max(0., a2 - x * x));
204                 final double arcCosXOverA = FastMath.acos(FastMath.max(-1, x / a));
205                 final double intermediate = (arcCosXOverA + (b * b * FastMath.acos((c - x) / b) - c * y) / a2) / FastMath.PI;
206                 return 1. - intermediate;
207             }
208         }
209         return 1.;
210     }
211 
212     /** {@inheritDoc} */
213     @Override
214     protected <T extends CalculusFieldElement<T>> T getLightingRatio(final FieldVector3D<T> position,
215                                                                      final FieldVector3D<T> occultedBodyPosition) {
216         final Field<T> field = position.getX().getField();
217         final T distanceSun = occultedBodyPosition.getNorm();
218         final T squaredDistance = position.getNormSq();
219         final FieldVector3D<T> occultedBodyDirection = occultedBodyPosition.normalize();
220         final T s0 = position.dotProduct(occultedBodyDirection).negate();
221         if (s0.getReal() > 0.0) {
222             final T reciprocalDistanceSun = distanceSun.reciprocal();
223             final T sinf2 = reciprocalDistanceSun.multiply(occultedBodyRadius - getOccultingBodyRadius());
224             final T l2 = (s0.multiply(sinf2).subtract(getOccultingBodyRadius())).divide(FastMath.sqrt(sinf2.square().negate().add(1)));
225             final T l = FastMath.sqrt(squaredDistance.subtract(s0.square()));
226             if (FastMath.abs(l2).subtract(l).getReal() >= 0.0) { // umbra
227                 return field.getZero();
228             }
229             final T sinf1 = reciprocalDistanceSun.multiply(occultedBodyRadius + getOccultingBodyRadius());
230             final T l1 = (s0.multiply(sinf1).add(getOccultingBodyRadius())).divide(FastMath.sqrt(sinf1.square().negate().add(1)));
231             if (l1.subtract(l).getReal() > 0.0) { // penumbra
232                 final FieldVector3D<T> relativePosition = occultedBodyPosition.subtract(position);
233                 final T relativeDistance = relativePosition.getNorm();
234                 final T a = FastMath.asin(relativeDistance.reciprocal().multiply(occultedBodyRadius));
235                 final T a2 = a.square();
236                 final T r = FastMath.sqrt(squaredDistance);
237                 final T b = FastMath.asin(r.reciprocal().multiply(getOccultingBodyRadius()));
238                 final T b2 = b.square();
239                 final T c = FastMath.acos((relativePosition.dotProduct(position).negate()).divide(r.multiply(relativeDistance)));
240                 final T x = (c.square().add(a2).subtract(b2)).divide(c.multiply(2));
241                 final T x2 = x.square();
242                 final T y = (a2.getReal() - x2.getReal() <= 0) ? s0.getField().getZero() : FastMath.sqrt(a2.subtract(x2));
243                 final T arcCosXOverA = (x.getReal() / a.getReal() < -1) ? s0.getPi().negate() : FastMath.acos(x.divide(a));
244                 final T intermediate = arcCosXOverA.add(((b2.multiply(FastMath.acos((c.subtract(x)).divide(b))))
245                         .subtract(c.multiply(y))).divide(a2));
246                 return intermediate.divide(-FastMath.PI).add(1);
247             }
248         }
249         return field.getOne();
250     }
251 
252     /** {@inheritDoc} */
253     @Override
254     public List<EventDetector> getEclipseConditionsDetector() {
255         final List<EventDetector> detectors = new ArrayList<>();
256         detectors.add(createUmbraEclipseDetector());
257         detectors.add(createPenumbraEclipseDetector());
258         return detectors;
259     }
260 
261     /**
262      * Method to create a new umbra detector.
263      * @return detector
264      */
265     private InternalEclipseDetector createUmbraEclipseDetector() {
266         return new InternalEclipseDetector() {
267             @Override
268             public double g(final SpacecraftState s) {
269                 final Vector3D position = s.getPosition();
270                 final Vector3D occultedBodyPosition = getOccultedBodyPosition(s.getDate());
271                 final Vector3D occultedBodyDirection = occultedBodyPosition.normalize();
272                 final double s0 = -position.dotProduct(occultedBodyDirection);
273                 final double distanceSun = occultedBodyPosition.getNorm();
274                 final double squaredDistance = position.getNormSq();
275                 final double sinf2 = (occultedBodyRadius - getOccultingBodyRadius()) / distanceSun;
276                 final double l = FastMath.sqrt(squaredDistance - s0 * s0);
277                 final double l2 = (s0 * sinf2 - getOccultingBodyRadius()) / FastMath.sqrt(1.0 - sinf2 * sinf2);
278                 return FastMath.abs(l2) / l - 1.;
279             }
280         };
281     }
282 
283     /**
284      * Method to create a new penumbra detector.
285      * @return detector
286      */
287     private InternalEclipseDetector createPenumbraEclipseDetector() {
288         return new InternalEclipseDetector() {
289             @Override
290             public double g(final SpacecraftState s) {
291                 final Vector3D position = s.getPosition();
292                 final Vector3D occultedBodyPosition = getOccultedBodyPosition(s.getDate());
293                 final Vector3D occultedBodyDirection = occultedBodyPosition.normalize();
294                 final double s0 = -position.dotProduct(occultedBodyDirection);
295                 final double distanceSun = occultedBodyPosition.getNorm();
296                 final double squaredDistance = position.getNormSq();
297                 final double l = FastMath.sqrt(squaredDistance - s0 * s0);
298                 final double sinf1 = (occultedBodyRadius + getOccultingBodyRadius()) / distanceSun;
299                 final double l1 = (s0 * sinf1 + getOccultingBodyRadius()) / FastMath.sqrt(1.0 - sinf1 * sinf1);
300                 return l1 / l - 1.;
301             }
302         };
303     }
304 
305     /**
306      * Internal class for event detector.
307      */
308     private abstract class InternalEclipseDetector implements EventDetector {
309         /** Event handler. */
310         private final ResetDerivativesOnEvent handler;
311 
312         /**
313          * Constructor.
314          */
315         InternalEclipseDetector() {
316             this.handler = new ResetDerivativesOnEvent();
317         }
318 
319         @Override
320         public EventDetectionSettings getDetectionSettings() {
321             return getEventDetectionSettings();
322         }
323 
324         @Override
325         public double getThreshold() {
326             return getDetectionSettings().getThreshold();
327         }
328 
329         @Override
330         public AdaptableInterval getMaxCheckInterval() {
331             return getDetectionSettings().getMaxCheckInterval();
332         }
333 
334         @Override
335         public int getMaxIterationCount() {
336             return getDetectionSettings().getMaxIterationCount();
337         }
338 
339         @Override
340         public EventHandler getHandler() {
341             return handler;
342         }
343     }
344 
345     /** {@inheritDoc} */
346     @Override
347     public <T extends CalculusFieldElement<T>> List<FieldEventDetector<T>> getFieldEclipseConditionsDetector(final Field<T> field) {
348         final List<FieldEventDetector<T>> detectors = new ArrayList<>();
349         final FieldEventDetectionSettings<T> detectionSettings = new FieldEventDetectionSettings<>(field,
350             getEventDetectionSettings());
351         detectors.add(createFieldUmbraEclipseDetector(detectionSettings));
352         detectors.add(createFieldPenumbraEclipseDetector(detectionSettings));
353         return detectors;
354     }
355 
356     /**
357      * Method to create a new umbra detector. Field version.
358      * @param detectionSettings non-Field detection settings
359      * @param <T> field type
360      * @return detector
361      */
362     private <T extends CalculusFieldElement<T>> FieldInternalEclipseDetector<T> createFieldUmbraEclipseDetector(final FieldEventDetectionSettings<T> detectionSettings) {
363         return new FieldInternalEclipseDetector<T>(detectionSettings) {
364             @Override
365             public T g(final FieldSpacecraftState<T> s) {
366                 final FieldVector3D<T> position = s.getPosition();
367                 final FieldVector3D<T> occultedBodyPosition = getOccultedBodyPosition(s.getDate());
368                 final FieldVector3D<T> occultedBodyDirection = occultedBodyPosition.normalize();
369                 final T s0 = position.dotProduct(occultedBodyDirection).negate();
370                 final T distanceSun = occultedBodyPosition.getNorm();
371                 final T squaredDistance = position.getNormSq();
372                 final T reciprocalDistanceSun = distanceSun.reciprocal();
373                 final T sinf2 = reciprocalDistanceSun.multiply(occultedBodyRadius - getOccultingBodyRadius());
374                 final T l2 = (s0.multiply(sinf2).subtract(getOccultingBodyRadius())).divide(FastMath.sqrt(sinf2.square().negate().add(1)));
375                 final T l = FastMath.sqrt(squaredDistance.subtract(s0.square()));
376                 return FastMath.abs(l2).divide(l).subtract(1);
377             }
378         };
379     }
380 
381     /**
382      * Method to create a new penumbra detector. Field version.
383      * @param detectionSettings non-Field detection settings
384      * @param <T> field type
385      * @return detector
386      */
387     private <T extends CalculusFieldElement<T>> FieldInternalEclipseDetector<T> createFieldPenumbraEclipseDetector(final FieldEventDetectionSettings<T> detectionSettings) {
388         return new FieldInternalEclipseDetector<T>(detectionSettings) {
389             @Override
390             public T g(final FieldSpacecraftState<T> s) {
391                 final FieldVector3D<T> position = s.getPosition();
392                 final FieldVector3D<T> occultedBodyPosition = getOccultedBodyPosition(s.getDate());
393                 final FieldVector3D<T> occultedBodyDirection = occultedBodyPosition.normalize();
394                 final T s0 = position.dotProduct(occultedBodyDirection).negate();
395                 final T distanceSun = occultedBodyPosition.getNorm();
396                 final T squaredDistance = position.getNormSq();
397                 final T reciprocalDistanceSun = distanceSun.reciprocal();
398                 final T sinf1 = reciprocalDistanceSun.multiply(occultedBodyRadius + getOccultingBodyRadius());
399                 final T l1 = (s0.multiply(sinf1).add(getOccultingBodyRadius())).divide(FastMath.sqrt(sinf1.square().negate().add(1)));
400                 final T l = FastMath.sqrt(squaredDistance.subtract(s0.square()));
401                 return l1.divide(l).subtract(1);
402             }
403         };
404     }
405 
406     /**
407      * Internal class for event detector.
408      */
409     private abstract static class FieldInternalEclipseDetector<T extends CalculusFieldElement<T>> implements FieldEventDetector<T> {
410         /** Event handler. */
411         private final FieldResetDerivativesOnEvent<T> handler;
412 
413         /** Detection settings. */
414         private final FieldEventDetectionSettings<T> fieldEventDetectionSettings;
415 
416         /**
417          * Constructor.
418          * @param fieldEventDetectionSettings detection settings
419          */
420         FieldInternalEclipseDetector(final FieldEventDetectionSettings<T> fieldEventDetectionSettings) {
421             this.handler = new FieldResetDerivativesOnEvent<>();
422             this.fieldEventDetectionSettings = fieldEventDetectionSettings;
423         }
424 
425         @Override
426         public FieldEventDetectionSettings<T> getDetectionSettings() {
427             return fieldEventDetectionSettings;
428         }
429 
430         @Override
431         public T getThreshold() {
432             return getDetectionSettings().getThreshold();
433         }
434 
435         @Override
436         public FieldAdaptableInterval<T> getMaxCheckInterval() {
437             return getDetectionSettings().getMaxCheckInterval();
438         }
439 
440         @Override
441         public int getMaxIterationCount() {
442             return getDetectionSettings().getMaxIterationCount();
443         }
444 
445         @Override
446         public FieldEventHandler<T> getHandler() {
447             return handler;
448         }
449     }
450 }