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.util.ArrayList;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.hipparchus.CalculusFieldElement;
24  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
25  import org.hipparchus.geometry.euclidean.threed.Vector3D;
26  import org.hipparchus.util.FastMath;
27  import org.hipparchus.util.MathArrays;
28  import org.hipparchus.util.Precision;
29  import org.orekit.frames.Frame;
30  import org.orekit.propagation.FieldSpacecraftState;
31  import org.orekit.propagation.SpacecraftState;
32  import org.orekit.time.AbsoluteDate;
33  import org.orekit.time.FieldAbsoluteDate;
34  import org.orekit.utils.Constants;
35  import org.orekit.utils.ExtendedPVCoordinatesProvider;
36  import org.orekit.utils.ParameterDriver;
37  
38  /** Solar radiation pressure force model.
39   * <p>
40   * Since Orekit 11.0, it is possible to take into account
41   * the eclipses generated by Moon in the solar radiation
42   * pressure force model using the
43   * {@link #addOccultingBody(ExtendedPVCoordinatesProvider, double)}
44   * method.
45   * <p>
46   * Example:<br>
47   * <code> SolarRadiationPressure srp = </code>
48   * <code>                      new SolarRadiationPressure(CelestialBodyFactory.getSun(), Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS,</code>
49   * <code>                                     new IsotropicRadiationClassicalConvention(50.0, 0.5, 0.5));</code><br>
50   * <code> srp.addOccultingBody(CelestialBodyFactory.getMoon(), Constants.MOON_EQUATORIAL_RADIUS);</code><br>
51   *
52   * @author Fabien Maussion
53   * @author &Eacute;douard Delente
54   * @author V&eacute;ronique Pommier-Maurussane
55   * @author Pascal Parraud
56   */
57  public class SolarRadiationPressure extends AbstractRadiationForceModel {
58  
59      /** Reference distance for the solar radiation pressure (m). */
60      private static final double D_REF = 149597870000.0;
61  
62      /** Reference solar radiation pressure at D_REF (N/m²). */
63      private static final double P_REF = 4.56e-6;
64  
65      /** Margin to force recompute lighting ratio derivatives when we are really inside penumbra. */
66      private static final double ANGULAR_MARGIN = 1.0e-10;
67  
68      /** Reference flux normalized for a 1m distance (N). */
69      private final double kRef;
70  
71      /** Sun model. */
72      private final ExtendedPVCoordinatesProvider sun;
73  
74      /** Spacecraft. */
75      private final RadiationSensitive spacecraft;
76  
77      /** Simple constructor with default reference values.
78       * <p>When this constructor is used, the reference values are:</p>
79       * <ul>
80       *   <li>d<sub>ref</sub> = 149597870000.0 m</li>
81       *   <li>p<sub>ref</sub> = 4.56 10<sup>-6</sup> N/m²</li>
82       * </ul>
83       * @param sun Sun model
84       * @param equatorialRadius spherical shape model (for umbra/penumbra computation)
85       * @param spacecraft the object physical and geometrical information
86       * @since 9.2
87       */
88      public SolarRadiationPressure(final ExtendedPVCoordinatesProvider sun, final double equatorialRadius,
89                                    final RadiationSensitive spacecraft) {
90          this(D_REF, P_REF, sun, equatorialRadius, spacecraft);
91      }
92  
93      /** Complete constructor.
94       * <p>Note that reference solar radiation pressure <code>pRef</code> in
95       * N/m² is linked to solar flux SF in W/m² using
96       * formula pRef = SF/c where c is the speed of light (299792458 m/s). So
97       * at 1UA a 1367 W/m² solar flux is a 4.56 10<sup>-6</sup>
98       * N/m² solar radiation pressure.</p>
99       * @param dRef reference distance for the solar radiation pressure (m)
100      * @param pRef reference solar radiation pressure at dRef (N/m²)
101      * @param sun Sun model
102      * @param equatorialRadius spherical shape model (for umbra/penumbra computation)
103      * @param spacecraft the object physical and geometrical information
104      * @since 9.2
105      */
106     public SolarRadiationPressure(final double dRef, final double pRef,
107                                   final ExtendedPVCoordinatesProvider sun,
108                                   final double equatorialRadius,
109                                   final RadiationSensitive spacecraft) {
110         super(sun, equatorialRadius);
111         this.kRef = pRef * dRef * dRef;
112         this.sun  = sun;
113         this.spacecraft = spacecraft;
114     }
115 
116     /** {@inheritDoc} */
117     @Override
118     public Vector3D acceleration(final SpacecraftState s, final double[] parameters) {
119 
120         final AbsoluteDate date         = s.getDate();
121         final Frame        frame        = s.getFrame();
122         final Vector3D     position     = s.getPVCoordinates().getPosition();
123         final Vector3D     sunSatVector = position.subtract(sun.getPVCoordinates(date, frame).getPosition());
124         final double       r2           = sunSatVector.getNormSq();
125 
126         // compute flux
127         final double   ratio = getTotalLightingRatio(position, frame, date);
128         final double   rawP  = ratio  * kRef / r2;
129         final Vector3D flux  = new Vector3D(rawP / FastMath.sqrt(r2), sunSatVector);
130 
131         return spacecraft.radiationPressureAcceleration(date, frame, position, s.getAttitude().getRotation(),
132                                                         s.getMass(), flux, parameters);
133 
134     }
135 
136     /** {@inheritDoc} */
137     @Override
138     public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(final FieldSpacecraftState<T> s,
139                                                                          final T[] parameters) {
140 
141         final FieldAbsoluteDate<T> date         = s.getDate();
142         final Frame                frame        = s.getFrame();
143         final FieldVector3D<T>     position     = s.getPVCoordinates().getPosition();
144         final FieldVector3D<T>     sunSatVector = position.subtract(sun.getPVCoordinates(date, frame).getPosition());
145         final T                    r2           = sunSatVector.getNormSq();
146 
147         // compute flux
148         final T                ratio = getTotalLightingRatio(position, frame, date);
149         final T                rawP  = ratio.multiply(kRef).divide(r2);
150         final FieldVector3D<T> flux  = new FieldVector3D<>(rawP.divide(r2.sqrt()), sunSatVector);
151 
152         return spacecraft.radiationPressureAcceleration(date, frame, position, s.getAttitude().getRotation(),
153                                                         s.getMass(), flux, parameters);
154 
155     }
156 
157     /** Get the lighting ratio ([0-1]).
158      * Considers only central body as occulting body.
159      * @param position the satellite's position in the selected frame.
160      * @param frame in which is defined the position
161      * @param date the date
162      * @return lighting ratio
163           * @since 7.1
164      */
165     public double getLightingRatio(final Vector3D position, final Frame frame, final AbsoluteDate date) {
166 
167         final Vector3D sunPosition = sun.getPVCoordinates(date, frame).getPosition();
168         if (sunPosition.getNorm() < 2 * Constants.SUN_RADIUS) {
169             // we are in fact computing a trajectory around Sun (or solar system barycenter),
170             // not around a planet,we consider lighting ratio is always 1
171             return 1.0;
172         }
173 
174         // Compute useful angles
175         final double[] angle = getEclipseAngles(sunPosition, position);
176 
177         // Sat-Sun / Sat-CentralBody angle
178         final double sunSatCentralBodyAngle = angle[0];
179 
180         // Central Body apparent radius
181         final double alphaCentral = angle[1];
182 
183         // Sun apparent radius
184         final double alphaSun = angle[2];
185 
186         double result = 1.0;
187 
188         // Is the satellite in complete umbra ?
189         if (sunSatCentralBodyAngle - alphaCentral + alphaSun <= ANGULAR_MARGIN) {
190             result = 0.0;
191         } else if (sunSatCentralBodyAngle - alphaCentral - alphaSun < -ANGULAR_MARGIN) {
192             // Compute a lighting ratio in penumbra
193             final double sEA2    = sunSatCentralBodyAngle * sunSatCentralBodyAngle;
194             final double oo2sEA  = 1.0 / (2. * sunSatCentralBodyAngle);
195             final double aS2     = alphaSun * alphaSun;
196             final double aE2     = alphaCentral * alphaCentral;
197             final double aE2maS2 = aE2 - aS2;
198 
199             final double alpha1  = (sEA2 - aE2maS2) * oo2sEA;
200             final double alpha2  = (sEA2 + aE2maS2) * oo2sEA;
201 
202             // Protection against numerical inaccuracy at boundaries
203             final double almost0 = Precision.SAFE_MIN;
204             final double almost1 = FastMath.nextDown(1.0);
205             final double a1oaS   = FastMath.min(almost1, FastMath.max(-almost1, alpha1 / alphaSun));
206             final double aS2ma12 = FastMath.max(almost0, aS2 - alpha1 * alpha1);
207             final double a2oaE   = FastMath.min(almost1, FastMath.max(-almost1, alpha2 / alphaCentral));
208             final double aE2ma22 = FastMath.max(almost0, aE2 - alpha2 * alpha2);
209 
210             final double P1 = aS2 * FastMath.acos(a1oaS) - alpha1 * FastMath.sqrt(aS2ma12);
211             final double P2 = aE2 * FastMath.acos(a2oaE) - alpha2 * FastMath.sqrt(aE2ma22);
212 
213             result = 1. - (P1 + P2) / (FastMath.PI * aS2);
214         }
215 
216         return result;
217 
218     }
219 
220     /** Get eclipse ratio between to bodies seen from a specific object.
221      * Ratio is in [0-1].
222      * @param position the satellite's position in the selected frame
223      * @param occultingPosition the position of the occulting object
224      * @param occultingRadius the mean radius of the occulting object
225      * @param occultedPosition the position of the occulted object
226      * @param occultedRadius the mean radius of the occulted object
227      * @return eclipse ratio
228      */
229     public double getGeneralEclipseRatio(final Vector3D position,
230                                   final Vector3D occultingPosition,
231                                   final double occultingRadius,
232                                   final Vector3D occultedPosition,
233                                   final double occultedRadius) {
234 
235 
236         // Compute useful angles
237         final double[] angle = getGeneralEclipseAngles(position, occultingPosition, occultingRadius, occultedPosition, occultedRadius);
238 
239         // Sat-Occulted/ Sat-Occulting angle
240         final double occultedSatOcculting = angle[0];
241 
242         // Occulting apparent radius
243         final double alphaOcculting = angle[1];
244 
245         // Occulted apparent radius
246         final double alphaOcculted = angle[2];
247 
248         double result = 1.0;
249 
250         // Is the satellite in complete umbra ?
251         if (occultedSatOcculting - alphaOcculting + alphaOcculted <= ANGULAR_MARGIN) {
252             result = 0.0;
253         } else if (occultedSatOcculting - alphaOcculting - alphaOcculted < -ANGULAR_MARGIN) {
254             // Compute an eclipse ratio in penumbra
255             final double sEA2    = occultedSatOcculting * occultedSatOcculting;
256             final double oo2sEA  = 1.0 / (2. * occultedSatOcculting);
257             final double aS2     = alphaOcculted * alphaOcculted;
258             final double aE2     = alphaOcculting * alphaOcculting;
259             final double aE2maS2 = aE2 - aS2;
260 
261             final double alpha1  = (sEA2 - aE2maS2) * oo2sEA;
262             final double alpha2  = (sEA2 + aE2maS2) * oo2sEA;
263 
264             // Protection against numerical inaccuracy at boundaries
265             final double almost0 = Precision.SAFE_MIN;
266             final double almost1 = FastMath.nextDown(1.0);
267             final double a1oaS   = FastMath.min(almost1, FastMath.max(-almost1, alpha1 / alphaOcculted));
268             final double aS2ma12 = FastMath.max(almost0, aS2 - alpha1 * alpha1);
269             final double a2oaE   = FastMath.min(almost1, FastMath.max(-almost1, alpha2 / alphaOcculting));
270             final double aE2ma22 = FastMath.max(almost0, aE2 - alpha2 * alpha2);
271 
272             final double P1 = aS2 * FastMath.acos(a1oaS) - alpha1 * FastMath.sqrt(aS2ma12);
273             final double P2 = aE2 * FastMath.acos(a2oaE) - alpha2 * FastMath.sqrt(aE2ma22);
274 
275             result = 1. - (P1 + P2) / (FastMath.PI * aS2);
276         }
277 
278         return result;
279     }
280 
281     /** Get the total lighting ratio ([0-1]).
282      * This method considers every occulting bodies.
283      * @param position the satellite's position in the selected frame.
284      * @param frame in which is defined the position
285      * @param date the date
286      * @return lighting ratio
287      */
288     public double getTotalLightingRatio(final Vector3D position, final Frame frame, final AbsoluteDate date) {
289 
290         double result = 0.0;
291         final Map<ExtendedPVCoordinatesProvider, Double> otherOccultingBodies = getOtherOccultingBodies();
292         final Vector3D sunPosition = sun.getPVCoordinates(date, frame).getPosition();
293         final int n = otherOccultingBodies.size() + 1;
294 
295         if (n > 1) {
296 
297             final Vector3D[] occultingBodyPositions = new Vector3D[n];
298             final double[] occultingBodyRadiuses = new double[n];
299 
300             // Central body
301             occultingBodyPositions[0] = new Vector3D(0.0, 0.0, 0.0);
302             occultingBodyRadiuses[0] = getEquatorialRadius();
303 
304             // Other occulting bodies
305             int k = 1;
306             for (Map.Entry<ExtendedPVCoordinatesProvider, Double> entry : otherOccultingBodies.entrySet()) {
307                 occultingBodyPositions[k] = entry.getKey().getPVCoordinates(date, frame).getPosition();
308                 occultingBodyRadiuses[k]  = entry.getValue();
309                 ++k;
310             }
311             for (int i = 0; i < n; ++i) {
312 
313                 // Lighting ratio computations
314                 final double eclipseRatioI = getGeneralEclipseRatio(position,
315                                                                     occultingBodyPositions[i],
316                                                                     occultingBodyRadiuses[i],
317                                                                     sunPosition,
318                                                                     Constants.SUN_RADIUS);
319 
320                 // First body totaly occults Sun, full eclipse is occuring.
321                 if (eclipseRatioI == 0.0) {
322                     return 0.0;
323                 }
324 
325 
326                 result += eclipseRatioI;
327 
328 
329                 // Mutual occulting body eclipse ratio computations between first and secondary bodies
330                 for (int j = i + 1; j < n; ++j) {
331 
332                     final double eclipseRatioJ = getGeneralEclipseRatio(position,
333                                                                         occultingBodyPositions[j],
334                                                                         occultingBodyRadiuses[j],
335                                                                         sunPosition,
336                                                                         Constants.SUN_RADIUS);
337                     final double eclipseRatioIJ = getGeneralEclipseRatio(position,
338                                                                          occultingBodyPositions[i],
339                                                                          occultingBodyRadiuses[i],
340                                                                          occultingBodyPositions[j],
341                                                                          occultingBodyRadiuses[j]);
342 
343                     final double alphaJ = getGeneralEclipseAngles(position,
344                                                                   occultingBodyPositions[i],
345                                                                   occultingBodyRadiuses[i],
346                                                                   occultingBodyPositions[j],
347                                                                   occultingBodyRadiuses[j])[2];
348 
349                     final double alphaSun = getEclipseAngles(sunPosition, position)[2];
350                     final double alphaJSq = alphaJ * alphaJ;
351                     final double alphaSunSq = alphaSun * alphaSun;
352                     final double mutualEclipseCorrection = (1 - eclipseRatioIJ) * alphaJSq / alphaSunSq;
353 
354                     // Secondary body totally occults Sun, no more computations are required, full eclipse is occuring.
355                     if (eclipseRatioJ ==  0.0 ) {
356                         return 0.0;
357                     }
358 
359                     // Secondary body partially occults Sun
360                     else if (eclipseRatioJ != 1) {
361                         result -= mutualEclipseCorrection;
362                     }
363                 }
364             }
365             // Final term
366             result -= n - 1;
367         } else {
368             // only central body is considered
369             result = getLightingRatio(position, frame, date);
370         }
371         return result;
372     }
373 
374 
375     /** Get the lighting ratio ([0-1]).
376      * Considers only central body as occulting body.
377      * @param position the satellite's position in the selected frame.
378      * @param frame in which is defined the position
379      * @param date the date
380      * @param <T> extends CalculusFieldElement
381      * @return lighting ratio
382           * @since 7.1
383      */
384     public <T extends CalculusFieldElement<T>> T getLightingRatio(final FieldVector3D<T> position,
385                                                               final Frame frame,
386                                                               final FieldAbsoluteDate<T> date) {
387 
388         final T one = date.getField().getOne();
389 
390         final FieldVector3D<T> sunPosition = sun.getPVCoordinates(date, frame).getPosition();
391         if (sunPosition.getNorm().getReal() < 2 * Constants.SUN_RADIUS) {
392             // we are in fact computing a trajectory around Sun (or solar system barycenter),
393             // not around a planet,we consider lighting ratio is always 1
394             return one;
395         }
396 
397         // Compute useful angles
398         final T[] angle = getEclipseAngles(sunPosition, position);
399 
400         // Sat-Sun / Sat-CentralBody angle
401         final T sunsatCentralBodyAngle = angle[0];
402 
403         // Central Body apparent radius
404         final T alphaCentral = angle[1];
405 
406         // Sun apparent radius
407         final T alphaSun = angle[2];
408 
409         T result = one;
410         // Is the satellite in complete umbra ?
411         if (sunsatCentralBodyAngle.getReal() - alphaCentral.getReal() + alphaSun.getReal() <= ANGULAR_MARGIN) {
412             result = date.getField().getZero();
413         } else if (sunsatCentralBodyAngle.getReal() - alphaCentral.getReal() - alphaSun.getReal() < -ANGULAR_MARGIN) {
414             // Compute a lighting ratio in penumbra
415             final T sEA2    = sunsatCentralBodyAngle.multiply(sunsatCentralBodyAngle);
416             final T oo2sEA  = sunsatCentralBodyAngle.multiply(2).reciprocal();
417             final T aS2     = alphaSun.multiply(alphaSun);
418             final T aE2     = alphaCentral.multiply(alphaCentral);
419             final T aE2maS2 = aE2.subtract(aS2);
420 
421             final T alpha1  = sEA2.subtract(aE2maS2).multiply(oo2sEA);
422             final T alpha2  = sEA2.add(aE2maS2).multiply(oo2sEA);
423 
424             // Protection against numerical inaccuracy at boundaries
425             final double almost0 = Precision.SAFE_MIN;
426             final double almost1 = FastMath.nextDown(1.0);
427             final T a1oaS   = min(almost1, max(-almost1, alpha1.divide(alphaSun)));
428             final T aS2ma12 = max(almost0, aS2.subtract(alpha1.multiply(alpha1)));
429             final T a2oaE   = min(almost1, max(-almost1, alpha2.divide(alphaCentral)));
430             final T aE2ma22 = max(almost0, aE2.subtract(alpha2.multiply(alpha2)));
431 
432             final T P1 = aS2.multiply(a1oaS.acos()).subtract(alpha1.multiply(aS2ma12.sqrt()));
433             final T P2 = aE2.multiply(a2oaE.acos()).subtract(alpha2.multiply(aE2ma22.sqrt()));
434 
435             result = one.subtract(P1.add(P2).divide(aS2.multiply(one.getPi())));
436         }
437 
438         return result;
439     }
440 
441     /** Get eclipse ratio between to bodies seen from a specific object.
442      * Ratio is in [0-1].
443      * @param position the satellite's position in the selected frame
444      * @param occultingPosition the position of the occulting object
445      * @param occultingRadius the mean radius of the occulting object
446      * @param occultedPosition the position of the occulted object
447      * @param occultedRadius the mean radius of the occulted object
448      * @param <T> extends RealFieldElement
449      * @return eclipse ratio
450      */
451     public <T extends CalculusFieldElement<T>> T getGeneralEclipseRatio(final FieldVector3D<T> position,
452                                                                         final FieldVector3D<T> occultingPosition,
453                                                                         final T occultingRadius,
454                                                                         final FieldVector3D<T> occultedPosition,
455                                                                         final T occultedRadius) {
456 
457 
458         final T one = occultingRadius.getField().getOne();
459 
460         // Compute useful angles
461         final T[] angle = getGeneralEclipseAngles(position, occultingPosition, occultingRadius, occultedPosition, occultedRadius);
462 
463         // Sat-Occulted/ Sat-Occulting angle
464         final T occultedSatOcculting = angle[0];
465 
466         // Occulting apparent radius
467         final T alphaOcculting = angle[1];
468 
469         // Occulted apparent radius
470         final T alphaOcculted = angle[2];
471 
472         T result = one;
473 
474         // Is the satellite in complete umbra ?
475         if (occultedSatOcculting.getReal() - alphaOcculting.getReal() + alphaOcculted.getReal() <= ANGULAR_MARGIN) {
476             result = occultingRadius.getField().getZero();
477         } else if (occultedSatOcculting.getReal() - alphaOcculting.getReal() - alphaOcculted.getReal() < -ANGULAR_MARGIN) {
478             // Compute a lighting ratio in penumbra
479             final T sEA2    = occultedSatOcculting.multiply(occultedSatOcculting);
480             final T oo2sEA  = occultedSatOcculting.multiply(2).reciprocal();
481             final T aS2     = alphaOcculted.multiply(alphaOcculted);
482             final T aE2     = alphaOcculting.multiply(alphaOcculting);
483             final T aE2maS2 = aE2.subtract(aS2);
484 
485             final T alpha1  = sEA2.subtract(aE2maS2).multiply(oo2sEA);
486             final T alpha2  = sEA2.add(aE2maS2).multiply(oo2sEA);
487 
488             // Protection against numerical inaccuracy at boundaries
489             final double almost0 = Precision.SAFE_MIN;
490             final double almost1 = FastMath.nextDown(1.0);
491             final T a1oaS   = min(almost1, max(-almost1, alpha1.divide(alphaOcculted)));
492             final T aS2ma12 = max(almost0, aS2.subtract(alpha1.multiply(alpha1)));
493             final T a2oaE   = min(almost1, max(-almost1, alpha2.divide(alphaOcculting)));
494             final T aE2ma22 = max(almost0, aE2.subtract(alpha2.multiply(alpha2)));
495 
496             final T P1 = aS2.multiply(a1oaS.acos()).subtract(alpha1.multiply(aS2ma12.sqrt()));
497             final T P2 = aE2.multiply(a2oaE.acos()).subtract(alpha2.multiply(aE2ma22.sqrt()));
498 
499             result = one.subtract(P1.add(P2).divide(aS2.multiply(one.getPi())));
500         }
501 
502         return result;
503     }
504 
505     /** Get the total lighting ratio ([0-1]).
506      * This method considers every occulting bodies.
507      * @param position the satellite's position in the selected frame.
508      * @param frame in which is defined the position
509      * @param date the date
510      * @param <T> extends RealFieldElement
511      * @return lighting rati
512      */
513     public  <T extends CalculusFieldElement<T>> T getTotalLightingRatio(final FieldVector3D<T> position, final Frame frame, final FieldAbsoluteDate<T> date) {
514 
515         final T zero = position.getAlpha().getField().getZero();
516         T result = zero;
517         final Map<ExtendedPVCoordinatesProvider, Double> otherOccultingBodies = getOtherOccultingBodies();
518         final FieldVector3D<T> sunPosition = sun.getPVCoordinates(date, frame).getPosition();
519         final int n = otherOccultingBodies.size() + 1;
520 
521         if (n > 1) {
522 
523             final List<FieldVector3D<T>> occultingBodyPositions = new ArrayList<FieldVector3D<T>>(n);
524             final T[] occultingBodyRadiuses = MathArrays.buildArray(zero.getField(), n);
525 
526             // Central body
527             occultingBodyPositions.add(0, new FieldVector3D<>(zero, zero, zero));
528             occultingBodyRadiuses[0] = zero.add(getEquatorialRadius());
529 
530             // Other occulting bodies
531             int k = 1;
532             for (Map.Entry<ExtendedPVCoordinatesProvider, Double> entry: otherOccultingBodies.entrySet()) {
533                 occultingBodyPositions.add(k, entry.getKey().getPVCoordinates(date, frame).getPosition());
534                 occultingBodyRadiuses[k] = zero.newInstance(entry.getValue());
535                 ++k;
536             }
537             for (int i = 0; i < n; ++i) {
538 
539                 // Lighting ratio computations
540                 final T eclipseRatioI = getGeneralEclipseRatio(position,
541                                                                occultingBodyPositions.get(i),
542                                                                occultingBodyRadiuses[i],
543                                                                sunPosition,
544                                                                zero.add(Constants.SUN_RADIUS));
545 
546                 // First body totally occults Sun, full eclipse is occuring.
547                 if (eclipseRatioI.getReal() == 0.0) {
548                     return zero;
549                 }
550 
551 
552                 result = result.add(eclipseRatioI);
553 
554 
555                 // Mutual occulting body eclipse ratio computations between first and secondary bodies
556                 for (int j = i + 1; j < n; ++j) {
557 
558                     final T eclipseRatioJ = getGeneralEclipseRatio(position,
559                                                                    occultingBodyPositions.get(i),
560                                                                    occultingBodyRadiuses[j],
561                                                                    sunPosition,
562                                                                    zero.add(Constants.SUN_RADIUS));
563                     final T eclipseRatioIJ = getGeneralEclipseRatio(position,
564                                                                     occultingBodyPositions.get(i),
565                                                                     occultingBodyRadiuses[i],
566                                                                     occultingBodyPositions.get(j),
567                                                                     occultingBodyRadiuses[j]);
568 
569                     final T alphaJ = getGeneralEclipseAngles(position,
570                                                              occultingBodyPositions.get(i),
571                                                              occultingBodyRadiuses[i],
572                                                              occultingBodyPositions.get(j),
573                                                              occultingBodyRadiuses[j])[2];
574 
575                     final T alphaSun = getEclipseAngles(sunPosition, position)[2];
576                     final T alphaJSq = alphaJ.multiply(alphaJ);
577                     final T alphaSunSq = alphaSun.multiply(alphaSun);
578                     final T mutualEclipseCorrection = eclipseRatioIJ.negate().add(1).multiply(alphaJSq).divide(alphaSunSq);
579 
580                     // Secondary body totaly occults Sun, no more computations are required, full eclipse is occuring.
581                     if (eclipseRatioJ.getReal() ==  0.0 ) {
582                         return zero;
583                     }
584 
585                     // Secondary body partially occults Sun
586                     else if (eclipseRatioJ.getReal() != 1) {
587                         result = result.subtract(mutualEclipseCorrection);
588                     }
589                 }
590             }
591             // Final term
592             result = result.subtract(n - 1);
593         } else {
594             // only central body is considered
595             result = getLightingRatio(position, frame, date);
596         }
597         return result;
598     }
599 
600 
601     /** {@inheritDoc} */
602     @Override
603     public List<ParameterDriver> getParametersDrivers() {
604         return spacecraft.getRadiationParametersDrivers();
605     }
606 
607     /** Compute min of two values, one double and one field element.
608      * @param d double value
609      * @param f field element
610      * @param <T> type fo the field elements
611      * @return min value
612      */
613     private <T extends CalculusFieldElement<T>> T min(final double d, final T f) {
614         return (f.getReal() > d) ? f.getField().getZero().newInstance(d) : f;
615     }
616 
617     /** Compute max of two values, one double and one field element.
618      * @param d double value
619      * @param f field element
620      * @param <T> type fo the field elements
621      * @return max value
622      */
623     private <T extends CalculusFieldElement<T>> T max(final double d, final T f) {
624         return (f.getReal() <= d) ? f.getField().getZero().newInstance(d) : f;
625     }
626 
627 }