ConicallyShadowedLightFluxModel.java

  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. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.Field;
  20. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  21. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  22. import org.hipparchus.util.FastMath;
  23. import org.orekit.frames.Frame;
  24. import org.orekit.propagation.FieldSpacecraftState;
  25. import org.orekit.propagation.SpacecraftState;
  26. import org.orekit.propagation.events.intervals.AdaptableInterval;
  27. import org.orekit.propagation.events.EventDetector;
  28. import org.orekit.propagation.events.EventDetectionSettings;
  29. import org.orekit.propagation.events.intervals.FieldAdaptableInterval;
  30. import org.orekit.propagation.events.FieldEventDetector;
  31. import org.orekit.propagation.events.FieldEventDetectionSettings;
  32. import org.orekit.propagation.events.handlers.EventHandler;
  33. import org.orekit.propagation.events.handlers.FieldEventHandler;
  34. import org.orekit.propagation.events.handlers.FieldResetDerivativesOnEvent;
  35. import org.orekit.propagation.events.handlers.ResetDerivativesOnEvent;
  36. import org.orekit.time.AbsoluteDate;
  37. import org.orekit.time.FieldAbsoluteDate;
  38. import org.orekit.utils.ExtendedPositionProvider;

  39. import java.util.ArrayList;
  40. import java.util.List;

  41. /**
  42.  * Class defining a flux model from a single occulted body, casting a shadow on a spherical occulting body.
  43.  * It cannot model oblate bodies or multiple occulting objects (for this, see {@link SolarRadiationPressure}).
  44.  *
  45.  * @author Romain Serra
  46.  * @see AbstractSolarLightFluxModel
  47.  * @see LightFluxModel
  48.  * @see "Montenbruck, Oliver, and Gill, Eberhard. Satellite orbits : models, methods, and
  49.  *  * applications. Berlin New York: Springer, 2000."
  50.  * @since 12.2
  51.  */
  52. public class ConicallyShadowedLightFluxModel extends AbstractSolarLightFluxModel {

  53.     /**
  54.      * Default max. check interval for eclipse detection.
  55.      */
  56.     private static final double CONICAL_ECLIPSE_MAX_CHECK = 60;

  57.     /**
  58.      * Default threshold for eclipse detection.
  59.      */
  60.     private static final double CONICAL_ECLIPSE_THRESHOLD = 1e-7;

  61.     /** Occulted body radius. */
  62.     private final double occultedBodyRadius;

  63.     /** Cached date. */
  64.     private AbsoluteDate lastDate;

  65.     /** Cached frame. */
  66.     private Frame propagationFrame;

  67.     /** Cached position. */
  68.     private Vector3D lastPosition;

  69.     /**
  70.      * Constructor.
  71.      * @param kRef reference flux
  72.      * @param occultedBodyRadius radius of occulted body (light source)
  73.      * @param occultedBody position provider for light source
  74.      * @param occultingBodyRadius radius of central, occulting body
  75.      * @param eventDetectionSettings user-defined detection settings for eclipses (if ill-tuned, events might be missed or performance might drop)
  76.      */
  77.     public ConicallyShadowedLightFluxModel(final double kRef, final double occultedBodyRadius,
  78.                                            final ExtendedPositionProvider occultedBody,
  79.                                            final double occultingBodyRadius, final EventDetectionSettings eventDetectionSettings) {
  80.         super(kRef, occultedBody, occultingBodyRadius, eventDetectionSettings);
  81.         this.occultedBodyRadius = occultedBodyRadius;
  82.     }

  83.     /**
  84.      * Constructor with default event detection settings.
  85.      * @param kRef reference flux
  86.      * @param occultedBodyRadius radius of occulted body (light source)
  87.      * @param occultedBody position provider for light source
  88.      * @param occultingBodyRadius radius of central, occulting body
  89.      */
  90.     public ConicallyShadowedLightFluxModel(final double kRef, final double occultedBodyRadius,
  91.                                            final ExtendedPositionProvider occultedBody, final double occultingBodyRadius) {
  92.         this(kRef, occultedBodyRadius, occultedBody, occultingBodyRadius, getDefaultEclipseDetectionSettings());
  93.     }

  94.     /**
  95.      * Constructor with default value for reference flux.
  96.      * @param occultedBodyRadius radius of occulted body (light source)
  97.      * @param occultedBody position provider for light source
  98.      * @param occultingBodyRadius radius of central, occulting body
  99.      */
  100.     public ConicallyShadowedLightFluxModel(final double occultedBodyRadius, final ExtendedPositionProvider occultedBody,
  101.                                            final double occultingBodyRadius) {
  102.         super(occultedBody, occultingBodyRadius, getDefaultEclipseDetectionSettings());
  103.         this.occultedBodyRadius = occultedBodyRadius;
  104.     }

  105.     /**
  106.      * Define default detection settings for eclipses.
  107.      * @return default settings
  108.      */
  109.     public static EventDetectionSettings getDefaultEclipseDetectionSettings() {
  110.         return new EventDetectionSettings(CONICAL_ECLIPSE_MAX_CHECK, CONICAL_ECLIPSE_THRESHOLD,
  111.                 EventDetectionSettings.DEFAULT_MAX_ITER);
  112.     }

  113.     /** {@inheritDoc} */
  114.     @Override
  115.     public void init(final SpacecraftState initialState, final AbsoluteDate targetDate) {
  116.         super.init(initialState, targetDate);
  117.         lastDate = initialState.getDate();
  118.         propagationFrame = initialState.getFrame();
  119.         lastPosition = getOccultedBodyPosition(lastDate, propagationFrame);
  120.     }

  121.     /** {@inheritDoc} */
  122.     @Override
  123.     public <T extends CalculusFieldElement<T>> void init(final FieldSpacecraftState<T> initialState,
  124.                                                          final FieldAbsoluteDate<T> targetDate) {
  125.         super.init(initialState, targetDate);
  126.         lastDate = initialState.getDate().toAbsoluteDate();
  127.         propagationFrame = initialState.getFrame();
  128.         lastPosition = getOccultedBodyPosition(initialState.getDate(), propagationFrame).toVector3D();
  129.     }

  130.     /**
  131.      * Get occulted body position using cache.
  132.      * @param date date
  133.      * @return occulted body position
  134.      */
  135.     private Vector3D getOccultedBodyPosition(final AbsoluteDate date) {
  136.         if (!lastDate.isEqualTo(date)) {
  137.             lastPosition = getOccultedBodyPosition(date, propagationFrame);
  138.             lastDate = date;
  139.         }
  140.         return lastPosition;
  141.     }

  142.     /**
  143.      * Get occulted body position using cache (non-Field and no derivatives case).
  144.      * @param fieldDate date
  145.      * @param <T> field type
  146.      * @return occulted body position
  147.      */
  148.     private <T extends CalculusFieldElement<T>> FieldVector3D<T> getOccultedBodyPosition(final FieldAbsoluteDate<T> fieldDate) {
  149.         if (fieldDate.hasZeroField()) {
  150.             final AbsoluteDate date = fieldDate.toAbsoluteDate();
  151.             if (!lastDate.isEqualTo(date)) {
  152.                 lastPosition = getOccultedBodyPosition(date, propagationFrame);
  153.                 lastDate = date;
  154.             }
  155.             return new FieldVector3D<>(fieldDate.getField(), lastPosition);
  156.         } else {
  157.             return getOccultedBodyPosition(fieldDate, propagationFrame);
  158.         }
  159.     }

  160.     /** {@inheritDoc} */
  161.     @Override
  162.     protected double getLightingRatio(final Vector3D position, final Vector3D occultedBodyPosition) {
  163.         final double distanceSun = occultedBodyPosition.getNorm();
  164.         final double squaredDistance = position.getNormSq();
  165.         final Vector3D occultedBodyDirection = occultedBodyPosition.normalize();
  166.         final double s0 = -position.dotProduct(occultedBodyDirection);
  167.         if (s0 > 0.0) {
  168.             final double l = FastMath.sqrt(squaredDistance - s0 * s0);
  169.             final double sinf2 = (occultedBodyRadius - getOccultingBodyRadius()) / distanceSun;
  170.             final double l2 = (s0 * sinf2 - getOccultingBodyRadius()) / FastMath.sqrt(1.0 - sinf2 * sinf2);
  171.             if (FastMath.abs(l2) - l >= 0.0) { // umbra
  172.                 return 0.;
  173.             }
  174.             final double sinf1 = (occultedBodyRadius + getOccultingBodyRadius()) / distanceSun;
  175.             final double l1 = (s0 * sinf1 + getOccultingBodyRadius()) / FastMath.sqrt(1.0 - sinf1 * sinf1);
  176.             if (l1 - l > 0.0) { // penumbra
  177.                 final Vector3D relativePosition = occultedBodyPosition.subtract(position);
  178.                 final double relativeDistance = relativePosition.getNorm();
  179.                 final double a = FastMath.asin(occultedBodyRadius / relativeDistance);
  180.                 final double a2 = a * a;
  181.                 final double r = FastMath.sqrt(squaredDistance);
  182.                 final double b = FastMath.asin(getOccultingBodyRadius() / r);
  183.                 final double c = FastMath.acos(-(relativePosition.dotProduct(position)) / (r * relativeDistance));
  184.                 final double x = (c * c + a2 - b * b) / (2 * c);
  185.                 final double y = FastMath.sqrt(FastMath.max(0., a2 - x * x));
  186.                 final double arcCosXOverA = FastMath.acos(FastMath.max(-1, x / a));
  187.                 final double intermediate = (arcCosXOverA + (b * b * FastMath.acos((c - x) / b) - c * y) / a2) / FastMath.PI;
  188.                 return 1. - intermediate;
  189.             }
  190.         }
  191.         return 1.;
  192.     }

  193.     /** {@inheritDoc} */
  194.     @Override
  195.     protected <T extends CalculusFieldElement<T>> T getLightingRatio(final FieldVector3D<T> position,
  196.                                                                      final FieldVector3D<T> occultedBodyPosition) {
  197.         final Field<T> field = position.getX().getField();
  198.         final T distanceSun = occultedBodyPosition.getNorm();
  199.         final T squaredDistance = position.getNormSq();
  200.         final FieldVector3D<T> occultedBodyDirection = occultedBodyPosition.normalize();
  201.         final T s0 = position.dotProduct(occultedBodyDirection).negate();
  202.         if (s0.getReal() > 0.0) {
  203.             final T reciprocalDistanceSun = distanceSun.reciprocal();
  204.             final T sinf2 = reciprocalDistanceSun.multiply(occultedBodyRadius - getOccultingBodyRadius());
  205.             final T l2 = (s0.multiply(sinf2).subtract(getOccultingBodyRadius())).divide(FastMath.sqrt(sinf2.square().negate().add(1)));
  206.             final T l = FastMath.sqrt(squaredDistance.subtract(s0.square()));
  207.             if (FastMath.abs(l2).subtract(l).getReal() >= 0.0) { // umbra
  208.                 return field.getZero();
  209.             }
  210.             final T sinf1 = reciprocalDistanceSun.multiply(occultedBodyRadius + getOccultingBodyRadius());
  211.             final T l1 = (s0.multiply(sinf1).add(getOccultingBodyRadius())).divide(FastMath.sqrt(sinf1.square().negate().add(1)));
  212.             if (l1.subtract(l).getReal() > 0.0) { // penumbra
  213.                 final FieldVector3D<T> relativePosition = occultedBodyPosition.subtract(position);
  214.                 final T relativeDistance = relativePosition.getNorm();
  215.                 final T a = FastMath.asin(relativeDistance.reciprocal().multiply(occultedBodyRadius));
  216.                 final T a2 = a.square();
  217.                 final T r = FastMath.sqrt(squaredDistance);
  218.                 final T b = FastMath.asin(r.reciprocal().multiply(getOccultingBodyRadius()));
  219.                 final T b2 = b.square();
  220.                 final T c = FastMath.acos((relativePosition.dotProduct(position).negate()).divide(r.multiply(relativeDistance)));
  221.                 final T x = (c.square().add(a2).subtract(b2)).divide(c.multiply(2));
  222.                 final T x2 = x.square();
  223.                 final T y = (a2.getReal() - x2.getReal() <= 0) ? s0.getField().getZero() : FastMath.sqrt(a2.subtract(x2));
  224.                 final T arcCosXOverA = (x.getReal() / a.getReal() < -1) ? s0.getPi().negate() : FastMath.acos(x.divide(a));
  225.                 final T intermediate = arcCosXOverA.add(((b2.multiply(FastMath.acos((c.subtract(x)).divide(b))))
  226.                         .subtract(c.multiply(y))).divide(a2));
  227.                 return intermediate.divide(-FastMath.PI).add(1);
  228.             }
  229.         }
  230.         return field.getOne();
  231.     }

  232.     /** {@inheritDoc} */
  233.     @Override
  234.     public List<EventDetector> getEclipseConditionsDetector() {
  235.         final List<EventDetector> detectors = new ArrayList<>();
  236.         detectors.add(createUmbraEclipseDetector());
  237.         detectors.add(createPenumbraEclipseDetector());
  238.         return detectors;
  239.     }

  240.     /**
  241.      * Method to create a new umbra detector.
  242.      * @return detector
  243.      */
  244.     private InternalEclipseDetector createUmbraEclipseDetector() {
  245.         return new InternalEclipseDetector() {
  246.             @Override
  247.             public double g(final SpacecraftState s) {
  248.                 final Vector3D position = s.getPosition();
  249.                 final Vector3D occultedBodyPosition = getOccultedBodyPosition(s.getDate());
  250.                 final Vector3D occultedBodyDirection = occultedBodyPosition.normalize();
  251.                 final double s0 = -position.dotProduct(occultedBodyDirection);
  252.                 final double distanceSun = occultedBodyPosition.getNorm();
  253.                 final double squaredDistance = position.getNormSq();
  254.                 final double sinf2 = (occultedBodyRadius - getOccultingBodyRadius()) / distanceSun;
  255.                 final double l = FastMath.sqrt(squaredDistance - s0 * s0);
  256.                 final double l2 = (s0 * sinf2 - getOccultingBodyRadius()) / FastMath.sqrt(1.0 - sinf2 * sinf2);
  257.                 return FastMath.abs(l2) / l - 1.;
  258.             }
  259.         };
  260.     }

  261.     /**
  262.      * Method to create a new penumbra detector.
  263.      * @return detector
  264.      */
  265.     private InternalEclipseDetector createPenumbraEclipseDetector() {
  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 l = FastMath.sqrt(squaredDistance - s0 * s0);
  276.                 final double sinf1 = (occultedBodyRadius + getOccultingBodyRadius()) / distanceSun;
  277.                 final double l1 = (s0 * sinf1 + getOccultingBodyRadius()) / FastMath.sqrt(1.0 - sinf1 * sinf1);
  278.                 return l1 / l - 1.;
  279.             }
  280.         };
  281.     }

  282.     /**
  283.      * Internal class for event detector.
  284.      */
  285.     private abstract class InternalEclipseDetector implements EventDetector {
  286.         /** Event handler. */
  287.         private final ResetDerivativesOnEvent handler;

  288.         /**
  289.          * Constructor.
  290.          */
  291.         InternalEclipseDetector() {
  292.             this.handler = new ResetDerivativesOnEvent();
  293.         }

  294.         @Override
  295.         public EventDetectionSettings getDetectionSettings() {
  296.             return getEventDetectionSettings();
  297.         }

  298.         @Override
  299.         public double getThreshold() {
  300.             return getDetectionSettings().getThreshold();
  301.         }

  302.         @Override
  303.         public AdaptableInterval getMaxCheckInterval() {
  304.             return getDetectionSettings().getMaxCheckInterval();
  305.         }

  306.         @Override
  307.         public int getMaxIterationCount() {
  308.             return getDetectionSettings().getMaxIterationCount();
  309.         }

  310.         @Override
  311.         public EventHandler getHandler() {
  312.             return handler;
  313.         }
  314.     }

  315.     /** {@inheritDoc} */
  316.     @Override
  317.     public <T extends CalculusFieldElement<T>> List<FieldEventDetector<T>> getFieldEclipseConditionsDetector(final Field<T> field) {
  318.         final List<FieldEventDetector<T>> detectors = new ArrayList<>();
  319.         final FieldEventDetectionSettings<T> detectionSettings = new FieldEventDetectionSettings<>(field,
  320.             getEventDetectionSettings());
  321.         detectors.add(createFieldUmbraEclipseDetector(detectionSettings));
  322.         detectors.add(createFieldPenumbraEclipseDetector(detectionSettings));
  323.         return detectors;
  324.     }

  325.     /**
  326.      * Method to create a new umbra detector. Field version.
  327.      * @param detectionSettings non-Field detection settings
  328.      * @param <T> field type
  329.      * @return detector
  330.      */
  331.     private <T extends CalculusFieldElement<T>> FieldInternalEclipseDetector<T> createFieldUmbraEclipseDetector(final FieldEventDetectionSettings<T> detectionSettings) {
  332.         return new FieldInternalEclipseDetector<T>(detectionSettings) {
  333.             @Override
  334.             public T g(final FieldSpacecraftState<T> s) {
  335.                 final FieldVector3D<T> position = s.getPosition();
  336.                 final FieldVector3D<T> occultedBodyPosition = getOccultedBodyPosition(s.getDate());
  337.                 final FieldVector3D<T> occultedBodyDirection = occultedBodyPosition.normalize();
  338.                 final T s0 = position.dotProduct(occultedBodyDirection).negate();
  339.                 final T distanceSun = occultedBodyPosition.getNorm();
  340.                 final T squaredDistance = position.getNormSq();
  341.                 final T reciprocalDistanceSun = distanceSun.reciprocal();
  342.                 final T sinf2 = reciprocalDistanceSun.multiply(occultedBodyRadius - getOccultingBodyRadius());
  343.                 final T l2 = (s0.multiply(sinf2).subtract(getOccultingBodyRadius())).divide(FastMath.sqrt(sinf2.square().negate().add(1)));
  344.                 final T l = FastMath.sqrt(squaredDistance.subtract(s0.square()));
  345.                 return FastMath.abs(l2).divide(l).subtract(1);
  346.             }
  347.         };
  348.     }

  349.     /**
  350.      * Method to create a new penumbra detector. Field version.
  351.      * @param detectionSettings non-Field detection settings
  352.      * @param <T> field type
  353.      * @return detector
  354.      */
  355.     private <T extends CalculusFieldElement<T>> FieldInternalEclipseDetector<T> createFieldPenumbraEclipseDetector(final FieldEventDetectionSettings<T> detectionSettings) {
  356.         return new FieldInternalEclipseDetector<T>(detectionSettings) {
  357.             @Override
  358.             public T g(final FieldSpacecraftState<T> s) {
  359.                 final FieldVector3D<T> position = s.getPosition();
  360.                 final FieldVector3D<T> occultedBodyPosition = getOccultedBodyPosition(s.getDate());
  361.                 final FieldVector3D<T> occultedBodyDirection = occultedBodyPosition.normalize();
  362.                 final T s0 = position.dotProduct(occultedBodyDirection).negate();
  363.                 final T distanceSun = occultedBodyPosition.getNorm();
  364.                 final T squaredDistance = position.getNormSq();
  365.                 final T reciprocalDistanceSun = distanceSun.reciprocal();
  366.                 final T sinf1 = reciprocalDistanceSun.multiply(occultedBodyRadius + getOccultingBodyRadius());
  367.                 final T l1 = (s0.multiply(sinf1).add(getOccultingBodyRadius())).divide(FastMath.sqrt(sinf1.square().negate().add(1)));
  368.                 final T l = FastMath.sqrt(squaredDistance.subtract(s0.square()));
  369.                 return l1.divide(l).subtract(1);
  370.             }
  371.         };
  372.     }

  373.     /**
  374.      * Internal class for event detector.
  375.      */
  376.     private abstract static class FieldInternalEclipseDetector<T extends CalculusFieldElement<T>> implements FieldEventDetector<T> {
  377.         /** Event handler. */
  378.         private final FieldResetDerivativesOnEvent<T> handler;

  379.         /** Detection settings. */
  380.         private final FieldEventDetectionSettings<T> fieldEventDetectionSettings;

  381.         /**
  382.          * Constructor.
  383.          * @param fieldEventDetectionSettings detection settings
  384.          */
  385.         FieldInternalEclipseDetector(final FieldEventDetectionSettings<T> fieldEventDetectionSettings) {
  386.             this.handler = new FieldResetDerivativesOnEvent<>();
  387.             this.fieldEventDetectionSettings = fieldEventDetectionSettings;
  388.         }

  389.         @Override
  390.         public FieldEventDetectionSettings<T> getDetectionSettings() {
  391.             return fieldEventDetectionSettings;
  392.         }

  393.         @Override
  394.         public T getThreshold() {
  395.             return getDetectionSettings().getThreshold();
  396.         }

  397.         @Override
  398.         public FieldAdaptableInterval<T> getMaxCheckInterval() {
  399.             return getDetectionSettings().getMaxCheckInterval();
  400.         }

  401.         @Override
  402.         public int getMaxIterationCount() {
  403.             return getDetectionSettings().getMaxIterationCount();
  404.         }

  405.         @Override
  406.         public FieldEventHandler<T> getHandler() {
  407.             return handler;
  408.         }
  409.     }
  410. }