SolarRadiationPressure.java

  1. /* Copyright 2002-2025 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. import java.lang.reflect.Array;
  19. import java.util.List;

  20. import org.hipparchus.CalculusFieldElement;
  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.hipparchus.util.Precision;
  25. import org.orekit.bodies.OneAxisEllipsoid;
  26. import org.orekit.frames.Frame;
  27. import org.orekit.propagation.FieldSpacecraftState;
  28. import org.orekit.propagation.SpacecraftState;
  29. import org.orekit.propagation.events.EventDetectionSettings;
  30. import org.orekit.time.AbsoluteDate;
  31. import org.orekit.time.FieldAbsoluteDate;
  32. import org.orekit.utils.Constants;
  33. import org.orekit.utils.ExtendedPositionProvider;
  34. import org.orekit.utils.FrameAdapter;
  35. import org.orekit.utils.OccultationEngine;
  36. import org.orekit.utils.ParameterDriver;

  37. /** Solar radiation pressure force model.
  38.  * <p>
  39.  * Since Orekit 11.0, it is possible to take into account
  40.  * the eclipses generated by Moon in the solar radiation
  41.  * pressure force model using the
  42.  * {@link #addOccultingBody(ExtendedPositionProvider, double)}
  43.  * method.
  44.  * <p>
  45.  * Example:<br>
  46.  * <code> SolarRadiationPressure srp = </code>
  47.  * <code>                      new SolarRadiationPressure(CelestialBodyFactory.getSun(), Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS,</code>
  48.  * <code>                                     new IsotropicRadiationClassicalConvention(50.0, 0.5, 0.5));</code><br>
  49.  * <code> srp.addOccultingBody(CelestialBodyFactory.getMoon(), Constants.MOON_EQUATORIAL_RADIUS);</code><br>
  50.  *
  51.  * @author Fabien Maussion
  52.  * @author &Eacute;douard Delente
  53.  * @author V&eacute;ronique Pommier-Maurussane
  54.  * @author Pascal Parraud
  55.  */
  56. public class SolarRadiationPressure extends AbstractRadiationForceModel {

  57.     /** Reference distance for the solar radiation pressure (m). */
  58.     private static final double D_REF = 149597870000.0;

  59.     /** Reference solar radiation pressure at D_REF (N/m²). */
  60.     private static final double P_REF = 4.56e-6;

  61.     /** Margin to force recompute lighting ratio derivatives when we are really inside penumbra. */
  62.     private static final double ANGULAR_MARGIN = 1.0e-10;

  63.     /** Threshold to decide whether the S/C frame is Sun-centered. */
  64.     private static final double SUN_CENTERED_FRAME_THRESHOLD = 2. * Constants.SUN_RADIUS;

  65.     /** Reference flux normalized for a 1m distance (N). */
  66.     private final double kRef;

  67.     /** Sun model. */
  68.     private final ExtendedPositionProvider sun;

  69.     /** Spacecraft. */
  70.     private final RadiationSensitive spacecraft;

  71.     /** Simple constructor with default reference values.
  72.      * <p>When this constructor is used, the reference values are:</p>
  73.      * <ul>
  74.      *   <li>d<sub>ref</sub> = 149597870000.0 m</li>
  75.      *   <li>p<sub>ref</sub> = 4.56 10<sup>-6</sup> N/m²</li>
  76.      * </ul>
  77.      * @param sun Sun model
  78.      * @param centralBody central body shape model (for umbra/penumbra computation)
  79.      * @param spacecraft the object physical and geometrical information
  80.      * @since 12.0
  81.      */
  82.     public SolarRadiationPressure(final ExtendedPositionProvider sun,
  83.                                   final OneAxisEllipsoid centralBody,
  84.                                   final RadiationSensitive spacecraft) {
  85.         this(D_REF, P_REF, sun, centralBody, spacecraft);
  86.     }

  87.     /** Constructor with default eclipse detection settings.
  88.      * <p>Note that reference solar radiation pressure <code>pRef</code> in
  89.      * N/m² is linked to solar flux SF in W/m² using
  90.      * formula pRef = SF/c where c is the speed of light (299792458 m/s). So
  91.      * at 1UA a 1367 W/m² solar flux is a 4.56 10<sup>-6</sup>
  92.      * N/m² solar radiation pressure.</p>
  93.      * @param dRef reference distance for the solar radiation pressure (m)
  94.      * @param pRef reference solar radiation pressure at dRef (N/m²)
  95.      * @param sun Sun model
  96.      * @param centralBody central body shape model (for umbra/penumbra computation)
  97.      * @param spacecraft the object physical and geometrical information
  98.      * @since 12.0
  99.      */
  100.     public SolarRadiationPressure(final double dRef, final double pRef,
  101.                                   final ExtendedPositionProvider sun,
  102.                                   final OneAxisEllipsoid centralBody,
  103.                                   final RadiationSensitive spacecraft) {
  104.         this(dRef, pRef, sun, centralBody, spacecraft, getDefaultEclipseDetectionSettings());
  105.     }

  106.     /** Complete constructor.
  107.      * <p>Note that reference solar radiation pressure <code>pRef</code> in
  108.      * N/m² is linked to solar flux SF in W/m² using
  109.      * formula pRef = SF/c where c is the speed of light (299792458 m/s). So
  110.      * at 1UA a 1367 W/m² solar flux is a 4.56 10<sup>-6</sup>
  111.      * N/m² solar radiation pressure.</p>
  112.      * @param dRef reference distance for the solar radiation pressure (m)
  113.      * @param pRef reference solar radiation pressure at dRef (N/m²)
  114.      * @param sun Sun model
  115.      * @param centralBody central body shape model (for umbra/penumbra computation)
  116.      * @param spacecraft the object physical and geometrical information
  117.      * @param eclipseDetectionSettings event detection settings for penumbra and umbra
  118.      * @since 13.0
  119.      */
  120.     public SolarRadiationPressure(final double dRef, final double pRef,
  121.                                   final ExtendedPositionProvider sun,
  122.                                   final OneAxisEllipsoid centralBody,
  123.                                   final RadiationSensitive spacecraft,
  124.                                   final EventDetectionSettings eclipseDetectionSettings) {
  125.         super(sun, centralBody, eclipseDetectionSettings);
  126.         this.kRef = pRef * dRef * dRef;
  127.         this.sun  = sun;
  128.         this.spacecraft = spacecraft;
  129.     }

  130.     /**
  131.      * Getter for radiation-sensitive spacecraft.
  132.      * @return radiation-sensitive model
  133.      * @since 12.1
  134.      */
  135.     public RadiationSensitive getRadiationSensitiveSpacecraft() {
  136.         return spacecraft;
  137.     }

  138.     /** {@inheritDoc} */
  139.     @Override
  140.     public boolean dependsOnPositionOnly() {
  141.         return spacecraft instanceof IsotropicRadiationClassicalConvention || spacecraft instanceof IsotropicRadiationCNES95Convention || spacecraft instanceof IsotropicRadiationSingleCoefficient;
  142.     }

  143.     /** {@inheritDoc} */
  144.     @Override
  145.     public Vector3D acceleration(final SpacecraftState s, final double[] parameters) {

  146.         final AbsoluteDate date         = s.getDate();
  147.         final Frame        frame        = s.getFrame();
  148.         final Vector3D     position     = s.getPosition();
  149.         final Vector3D     sunPosition  = sun.getPosition(date, frame);
  150.         final Vector3D     sunSatVector = position.subtract(sunPosition);
  151.         final double       r2           = sunSatVector.getNormSq();

  152.         // compute flux
  153.         final double   ratio = getLightingRatio(s, sunPosition);
  154.         final double   rawP  = ratio  * kRef / r2;
  155.         final Vector3D flux  = new Vector3D(rawP / FastMath.sqrt(r2), sunSatVector);

  156.         return spacecraft.radiationPressureAcceleration(s, flux, parameters);

  157.     }

  158.     /** {@inheritDoc} */
  159.     @Override
  160.     public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(final FieldSpacecraftState<T> s,
  161.                                                                              final T[] parameters) {

  162.         final FieldAbsoluteDate<T> date         = s.getDate();
  163.         final Frame                frame        = s.getFrame();
  164.         final FieldVector3D<T>     position     = s.getPosition();
  165.         final FieldVector3D<T>     sunPosition  = sun.getPosition(date, frame);
  166.         final FieldVector3D<T>     sunSatVector = position.subtract(sunPosition);
  167.         final T                    r2           = sunSatVector.getNormSq();

  168.         // compute flux
  169.         final T                ratio = getLightingRatio(s, sunPosition);
  170.         final T                rawP  = ratio.multiply(kRef).divide(r2);
  171.         final FieldVector3D<T> flux  = new FieldVector3D<>(rawP.divide(r2.sqrt()), sunSatVector);

  172.         return spacecraft.radiationPressureAcceleration(s, flux, parameters);

  173.     }

  174.     /** Check whether the frame is considerer Sun-centered.
  175.      *
  176.      * @param sunPositionInFrame Sun position in frame to test
  177.      * @return true if frame is considered Sun-centered
  178.      * @since 12.0
  179.      */
  180.     private boolean isSunCenteredFrame(final Vector3D sunPositionInFrame) {
  181.         // Frame is considered Sun-centered if Sun (or Solar System barycenter) position
  182.         // in that frame is smaller than SUN_CENTERED_FRAME_THRESHOLD
  183.         return sunPositionInFrame.getNorm() < SUN_CENTERED_FRAME_THRESHOLD;
  184.     }


  185.     /** Get the lighting ratio ([0-1]).
  186.      * @param state spacecraft state
  187.      * @param sunPosition Sun position in S/C frame at S/C date
  188.      * @return lighting ratio
  189.      * @since 12.0 added to avoid numerous call to sun.getPosition(...)
  190.      */
  191.     private double getLightingRatio(final SpacecraftState state, final Vector3D sunPosition) {

  192.         // Check if S/C frame is Sun-centered
  193.         if (isSunCenteredFrame(sunPosition)) {
  194.             // We are in fact computing a trajectory around Sun (or solar system barycenter),
  195.             // not around a planet, we consider lighting ratio will always be 1
  196.             return 1.0;
  197.         }

  198.         final List<OccultationEngine> occultingBodies = getOccultingBodies();
  199.         final int n = occultingBodies.size();

  200.         final OccultationEngine.OccultationAngles[] angles = new OccultationEngine.OccultationAngles[n];
  201.         for (int i = 0; i < n; ++i) {
  202.             angles[i] = occultingBodies.get(i).angles(state);
  203.         }
  204.         final double alphaSunSq = angles[0].getOccultedApparentRadius() * angles[0].getOccultedApparentRadius();

  205.         double result = 0.0;
  206.         for (int i = 0; i < n; ++i) {

  207.             // compute lighting ratio considering one occulting body only
  208.             final OccultationEngine oi  = occultingBodies.get(i);
  209.             final double lightingRatioI = maskingRatio(angles[i]);
  210.             if (lightingRatioI == 0.0) {
  211.                 // body totally occults Sun, total eclipse is occurring.
  212.                 return 0.0;
  213.             }
  214.             result += lightingRatioI;

  215.             // Mutual occulting body eclipse ratio computations between first and secondary bodies
  216.             for (int j = i + 1; j < n; ++j) {

  217.                 final OccultationEngine oj = occultingBodies.get(j);
  218.                 final double lightingRatioJ = maskingRatio(angles[j]);
  219.                 if (lightingRatioJ == 0.0) {
  220.                     // Secondary body totally occults Sun, no more computations are required, total eclipse is occurring.
  221.                     return 0.0;
  222.                 } else if (lightingRatioJ != 1) {
  223.                     // Secondary body partially occults Sun

  224.                     final OccultationEngine oij = new OccultationEngine(new FrameAdapter(oi.getOcculting().getBodyFrame()),
  225.                                                                         oi.getOcculting().getEquatorialRadius(),
  226.                                                                         oj.getOcculting());
  227.                     final OccultationEngine.OccultationAngles aij = oij.angles(state);
  228.                     final double maskingRatioIJ = maskingRatio(aij);
  229.                     final double alphaJSq       = aij.getOccultedApparentRadius() * aij.getOccultedApparentRadius();

  230.                     final double mutualEclipseCorrection = (1 - maskingRatioIJ) * alphaJSq / alphaSunSq;
  231.                     result -= mutualEclipseCorrection;

  232.                 }

  233.             }
  234.         }

  235.         // Final term
  236.         result -= n - 1;

  237.         return result;
  238.     }

  239.     /** Get the lighting ratio ([0-1]).
  240.      * @param state spacecraft state
  241.      * @return lighting ratio
  242.      * @since 7.1
  243.      */
  244.     public double getLightingRatio(final SpacecraftState state) {
  245.         return getLightingRatio(state, sun.getPosition(state.getDate(), state.getFrame()));
  246.     }

  247.     /** Get the masking ratio ([0-1]) considering one pair of bodies.
  248.      * @param angles occultation angles
  249.      * @return masking ratio: 0.0 body fully masked, 1.0 body fully visible
  250.      * @since 12.0
  251.      */
  252.     private double maskingRatio(final OccultationEngine.OccultationAngles angles) {

  253.         // Sat-Occulted/ Sat-Occulting angle
  254.         final double sunSatCentralBodyAngle = angles.getSeparation();

  255.         // Occulting apparent radius
  256.         final double alphaCentral = angles.getLimbRadius();

  257.         // Occulted apparent radius
  258.         final double alphaSun = angles.getOccultedApparentRadius();

  259.         // Is the satellite in complete umbra ?
  260.         if (sunSatCentralBodyAngle - alphaCentral + alphaSun <= ANGULAR_MARGIN) {
  261.             return 0.0;
  262.         } else if (sunSatCentralBodyAngle - alphaCentral - alphaSun < -ANGULAR_MARGIN) {
  263.             // Compute a masking ratio in penumbra
  264.             final double sEA2    = sunSatCentralBodyAngle * sunSatCentralBodyAngle;
  265.             final double oo2sEA  = 1.0 / (2. * sunSatCentralBodyAngle);
  266.             final double aS2     = alphaSun * alphaSun;
  267.             final double aE2     = alphaCentral * alphaCentral;
  268.             final double aE2maS2 = aE2 - aS2;

  269.             final double alpha1  = (sEA2 - aE2maS2) * oo2sEA;
  270.             final double alpha2  = (sEA2 + aE2maS2) * oo2sEA;

  271.             // Protection against numerical inaccuracy at boundaries
  272.             final double almost0 = Precision.SAFE_MIN;
  273.             final double almost1 = FastMath.nextDown(1.0);
  274.             final double a1oaS   = FastMath.min(almost1, FastMath.max(-almost1, alpha1 / alphaSun));
  275.             final double aS2ma12 = FastMath.max(almost0, aS2 - alpha1 * alpha1);
  276.             final double a2oaE   = FastMath.min(almost1, FastMath.max(-almost1, alpha2 / alphaCentral));
  277.             final double aE2ma22 = FastMath.max(almost0, aE2 - alpha2 * alpha2);

  278.             final double P1 = aS2 * FastMath.acos(a1oaS) - alpha1 * FastMath.sqrt(aS2ma12);
  279.             final double P2 = aE2 * FastMath.acos(a2oaE) - alpha2 * FastMath.sqrt(aE2ma22);

  280.             return 1. - (P1 + P2) / (FastMath.PI * aS2);
  281.         } else {
  282.             return 1.0;
  283.         }

  284.     }

  285.     /** Get the lighting ratio ([0-1]).
  286.      * @param state spacecraft state
  287.      * @param sunPosition Sun position in S/C frame at S/C date
  288.      * @param <T> extends CalculusFieldElement
  289.      * @return lighting ratio
  290.      * @since 12.0 added to avoid numerous call to sun.getPosition(...)
  291.      */
  292.     private <T extends CalculusFieldElement<T>> T getLightingRatio(final FieldSpacecraftState<T> state, final FieldVector3D<T> sunPosition) {

  293.         final T one  = state.getDate().getField().getOne();
  294.         if (isSunCenteredFrame(sunPosition.toVector3D())) {
  295.             // We are in fact computing a trajectory around Sun (or solar system barycenter),
  296.             // not around a planet, we consider lighting ratio will always be 1
  297.             return one;
  298.         }
  299.         final T zero = state.getDate().getField().getZero();
  300.         final List<OccultationEngine> occultingBodies = getOccultingBodies();
  301.         final int n = occultingBodies.size();

  302.         @SuppressWarnings("unchecked")
  303.         final OccultationEngine.FieldOccultationAngles<T>[] angles =
  304.             (OccultationEngine.FieldOccultationAngles<T>[]) Array.newInstance(OccultationEngine.FieldOccultationAngles.class, n);
  305.         for (int i = 0; i < n; ++i) {
  306.             angles[i] = occultingBodies.get(i).angles(state);
  307.         }
  308.         final T alphaSunSq = angles[0].getOccultedApparentRadius().multiply(angles[0].getOccultedApparentRadius());

  309.         T result = state.getDate().getField().getZero();
  310.         for (int i = 0; i < n; ++i) {

  311.             // compute lighting ratio considering one occulting body only
  312.             final OccultationEngine oi  = occultingBodies.get(i);
  313.             final T lightingRatioI = maskingRatio(angles[i]);
  314.             if (lightingRatioI.isZero()) {
  315.                 // body totally occults Sun, total eclipse is occurring.
  316.                 return zero;
  317.             }
  318.             result = result.add(lightingRatioI);

  319.             // Mutual occulting body eclipse ratio computations between first and secondary bodies
  320.             for (int j = i + 1; j < n; ++j) {

  321.                 final OccultationEngine oj = occultingBodies.get(j);
  322.                 final T lightingRatioJ = maskingRatio(angles[j]);
  323.                 if (lightingRatioJ.isZero()) {
  324.                     // Secondary body totally occults Sun, no more computations are required, total eclipse is occurring.
  325.                     return zero;
  326.                 } else if (lightingRatioJ.getReal() != 1) {
  327.                     // Secondary body partially occults Sun

  328.                     final OccultationEngine oij = new OccultationEngine(new FrameAdapter(oi.getOcculting().getBodyFrame()),
  329.                                                                         oi.getOcculting().getEquatorialRadius(),
  330.                                                                         oj.getOcculting());
  331.                     final OccultationEngine.FieldOccultationAngles<T> aij = oij.angles(state);
  332.                     final T maskingRatioIJ = maskingRatio(aij);
  333.                     final T alphaJSq       = aij.getOccultedApparentRadius().multiply(aij.getOccultedApparentRadius());

  334.                     final T mutualEclipseCorrection = one.subtract(maskingRatioIJ).multiply(alphaJSq).divide(alphaSunSq);
  335.                     result = result.subtract(mutualEclipseCorrection);

  336.                 }

  337.             }
  338.         }

  339.         // Final term
  340.         result = result.subtract(n - 1);

  341.         return result;
  342.     }

  343.     /** Get the lighting ratio ([0-1]).
  344.      * @param state spacecraft state
  345.      * @param <T> extends CalculusFieldElement
  346.      * @return lighting ratio
  347.      * @since 7.1
  348.      */
  349.     public <T extends CalculusFieldElement<T>> T getLightingRatio(final FieldSpacecraftState<T> state) {
  350.         return getLightingRatio(state, sun.getPosition(state.getDate(), state.getFrame()));
  351.     }

  352.     /** Get the masking ratio ([0-1]) considering one pair of bodies.
  353.      * @param angles occultation angles
  354.      * @param <T> type of the field elements
  355.      * @return masking ratio: 0.0 body fully masked, 1.0 body fully visible
  356.      * @since 12.0
  357.      */
  358.     private <T extends CalculusFieldElement<T>> T maskingRatio(final OccultationEngine.FieldOccultationAngles<T> angles) {


  359.         // Sat-Occulted/ Sat-Occulting angle
  360.         final T occultedSatOcculting = angles.getSeparation();

  361.         // Occulting apparent radius
  362.         final T alphaOcculting = angles.getLimbRadius();

  363.         // Occulted apparent radius
  364.         final T alphaOcculted = angles.getOccultedApparentRadius();

  365.         // Is the satellite in complete umbra ?
  366.         if (occultedSatOcculting.getReal() - alphaOcculting.getReal() + alphaOcculted.getReal() <= ANGULAR_MARGIN) {
  367.             return occultedSatOcculting.getField().getZero();
  368.         } else if (occultedSatOcculting.getReal() - alphaOcculting.getReal() - alphaOcculted.getReal() < -ANGULAR_MARGIN) {
  369.             // Compute a masking ratio in penumbra
  370.             final T sEA2    = occultedSatOcculting.multiply(occultedSatOcculting);
  371.             final T oo2sEA  = occultedSatOcculting.multiply(2).reciprocal();
  372.             final T aS2     = alphaOcculted.multiply(alphaOcculted);
  373.             final T aE2     = alphaOcculting.multiply(alphaOcculting);
  374.             final T aE2maS2 = aE2.subtract(aS2);

  375.             final T alpha1  = sEA2.subtract(aE2maS2).multiply(oo2sEA);
  376.             final T alpha2  = sEA2.add(aE2maS2).multiply(oo2sEA);

  377.             // Protection against numerical inaccuracy at boundaries
  378.             final double almost0 = Precision.SAFE_MIN;
  379.             final double almost1 = FastMath.nextDown(1.0);
  380.             final T a1oaS   = min(almost1, max(-almost1, alpha1.divide(alphaOcculted)));
  381.             final T aS2ma12 = max(almost0, aS2.subtract(alpha1.multiply(alpha1)));
  382.             final T a2oaE   = min(almost1, max(-almost1, alpha2.divide(alphaOcculting)));
  383.             final T aE2ma22 = max(almost0, aE2.subtract(alpha2.multiply(alpha2)));

  384.             final T P1 = aS2.multiply(a1oaS.acos()).subtract(alpha1.multiply(aS2ma12.sqrt()));
  385.             final T P2 = aE2.multiply(a2oaE.acos()).subtract(alpha2.multiply(aE2ma22.sqrt()));

  386.             return occultedSatOcculting.getField().getOne().subtract(P1.add(P2).divide(aS2.multiply(occultedSatOcculting.getPi())));
  387.         } else {
  388.             return occultedSatOcculting.getField().getOne();
  389.         }

  390.     }

  391.     /** {@inheritDoc} */
  392.     @Override
  393.     public List<ParameterDriver> getParametersDrivers() {
  394.         return spacecraft.getRadiationParametersDrivers();
  395.     }

  396.     /** Compute min of two values, one double and one field element.
  397.      * @param d double value
  398.      * @param f field element
  399.      * @param <T> type of the field elements
  400.      * @return min value
  401.      */
  402.     private <T extends CalculusFieldElement<T>> T min(final double d, final T f) {
  403.         return (f.getReal() > d) ? f.getField().getZero().newInstance(d) : f;
  404.     }

  405.     /** Compute max of two values, one double and one field element.
  406.      * @param d double value
  407.      * @param f field element
  408.      * @param <T> type of the field elements
  409.      * @return max value
  410.      */
  411.     private <T extends CalculusFieldElement<T>> T max(final double d, final T f) {
  412.         return (f.getReal() <= d) ? f.getField().getZero().newInstance(d) : f;
  413.     }

  414. }