AbstractRadiationForceModel.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.ArrayList;
  20. import java.util.Collections;
  21. import java.util.List;
  22. import java.util.stream.Stream;

  23. import org.hipparchus.CalculusFieldElement;
  24. import org.hipparchus.Field;
  25. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  26. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  27. import org.hipparchus.util.FastMath;
  28. import org.hipparchus.util.MathArrays;
  29. import org.orekit.bodies.OneAxisEllipsoid;
  30. import org.orekit.frames.Frame;
  31. import org.orekit.propagation.events.EclipseDetector;
  32. import org.orekit.propagation.events.EventDetector;
  33. import org.orekit.propagation.events.EventDetectionSettings;
  34. import org.orekit.propagation.events.FieldEclipseDetector;
  35. import org.orekit.propagation.events.FieldEventDetector;
  36. import org.orekit.propagation.events.FieldEventDetectionSettings;
  37. import org.orekit.propagation.events.handlers.FieldResetDerivativesOnEvent;
  38. import org.orekit.propagation.events.handlers.ResetDerivativesOnEvent;
  39. import org.orekit.utils.Constants;
  40. import org.orekit.utils.ExtendedPositionProviderAdapter;
  41. import org.orekit.utils.ExtendedPositionProvider;
  42. import org.orekit.utils.OccultationEngine;

  43. /**
  44.  * Base class for radiation force models.
  45.  * @see SolarRadiationPressure
  46.  * @see ECOM2
  47.  * @since 10.2
  48.  */
  49. public abstract class AbstractRadiationForceModel implements RadiationForceModel {

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

  52.     /** Max check interval for eclipse detectors. */
  53.     private static final double ECLIPSE_MAX_CHECK = 60.0;

  54.     /** Threshold for eclipse detectors. */
  55.     private static final double ECLIPSE_THRESHOLD = 1.0e-7; // this is consistent with ANGULAR_MARGIN = 10⁻¹⁰ rad for LEO

  56.     /** Flatness for spherical bodies. */
  57.     private static final double SPHERICAL_BODY_FLATNESS = 0.0;

  58.     /** Prefix for occulting bodies frames names. */
  59.     private static final String OCCULTING_PREFIX = "occulting-";

  60.     /** Occulting bodies (central body is always the first one).
  61.      * @since 12.0
  62.      */
  63.     private final List<OccultationEngine> occultingBodies;

  64.     /** Eclipse detection settings. */
  65.     private final EventDetectionSettings eclipseDetectionSettings;

  66.     /**
  67.      * Default constructor.
  68.      * Only central body is considered.
  69.      * @param sun Sun model
  70.      * @param centralBody central body shape model (for umbra/penumbra computation)
  71.      * @param eclipseDetectionSettings eclipse detection settings (warning: poor settings will miss eclipses)
  72.      * @since 13.0
  73.      */
  74.     protected AbstractRadiationForceModel(final ExtendedPositionProvider sun, final OneAxisEllipsoid centralBody,
  75.                                           final EventDetectionSettings eclipseDetectionSettings) {
  76.         // in most cases, there will be only Earth, sometimes also Moon so an initial capacity of 2 is appropriate
  77.         occultingBodies = new ArrayList<>(2);
  78.         occultingBodies.add(new OccultationEngine(sun, Constants.SUN_RADIUS, centralBody));
  79.         this.eclipseDetectionSettings = eclipseDetectionSettings;
  80.     }

  81.     /** {@inheritDoc} */
  82.     @Override
  83.     public Stream<EventDetector> getEventDetectors() {
  84.         final EventDetector[] detectors = new EventDetector[2 * occultingBodies.size()];
  85.         for (int i = 0; i < occultingBodies.size(); ++i) {
  86.             final OccultationEngine occulting = occultingBodies.get(i);
  87.             detectors[2 * i]     = new EclipseDetector(occulting).
  88.                                    withUmbra().
  89.                                    withMargin(-ANGULAR_MARGIN).
  90.                                    withDetectionSettings(eclipseDetectionSettings).
  91.                                    withHandler(new ResetDerivativesOnEvent());
  92.             detectors[2 * i + 1] = new EclipseDetector(occulting).
  93.                                    withPenumbra().
  94.                                    withMargin(ANGULAR_MARGIN).
  95.                                    withDetectionSettings(eclipseDetectionSettings).
  96.                                    withHandler(new ResetDerivativesOnEvent());
  97.         }
  98.         // Fusion between Date detector for parameter driver span change and
  99.         // Detector for umbra / penumbra events
  100.         return Stream.concat(Stream.of(detectors), RadiationForceModel.super.getEventDetectors());
  101.     }

  102.     /**
  103.      * Get the default eclipse detection settings.
  104.      * @return detection settings
  105.      * @since 13.0
  106.      */
  107.     public static EventDetectionSettings getDefaultEclipseDetectionSettings() {
  108.         return new EventDetectionSettings(ECLIPSE_MAX_CHECK, ECLIPSE_THRESHOLD, EventDetectionSettings.DEFAULT_MAX_ITER);
  109.     }

  110.     /** {@inheritDoc} */
  111.     @Override
  112.     public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(final Field<T> field) {
  113.         final T zero = field.getZero();
  114.         @SuppressWarnings("unchecked")
  115.         final FieldEventDetector<T>[] detectors = (FieldEventDetector<T>[]) Array.newInstance(FieldEventDetector.class,
  116.                                                                                               2 * occultingBodies.size());
  117.         for (int i = 0; i < occultingBodies.size(); ++i) {
  118.             final OccultationEngine occulting = occultingBodies.get(i);
  119.             detectors[2 * i]     = new FieldEclipseDetector<>(field, occulting).
  120.                                    withUmbra().
  121.                                    withMargin(zero.newInstance(-ANGULAR_MARGIN)).
  122.                                    withDetectionSettings(new FieldEventDetectionSettings<>(field, eclipseDetectionSettings)).
  123.                                    withHandler(new FieldResetDerivativesOnEvent<>());
  124.             detectors[2 * i + 1] = new FieldEclipseDetector<>(field, occulting).
  125.                                    withPenumbra().
  126.                                    withMargin(zero.newInstance(ANGULAR_MARGIN)).
  127.                                    withDetectionSettings(new FieldEventDetectionSettings<>(field, eclipseDetectionSettings)).
  128.                                    withHandler(new FieldResetDerivativesOnEvent<>());
  129.         }
  130.         return Stream.concat(Stream.of(detectors), RadiationForceModel.super.getFieldEventDetectors(field));
  131.     }

  132.     /**
  133.      * Get the useful angles for eclipse computation.
  134.      * @param position the satellite's position in the selected frame
  135.      * @param occultingPosition Oculting body position in the selected frame
  136.      * @param occultingRadius Occulting body mean radius
  137.      * @param occultedPosition Occulted body position in the selected frame
  138.      * @param occultedRadius Occulted body mean radius
  139.      * @return the 3 angles {(satOcculting, satOcculted), Occulting body apparent radius, Occulted body apparent radius}
  140.      */
  141.     protected double[] getGeneralEclipseAngles(final Vector3D position, final Vector3D occultingPosition, final double occultingRadius,
  142.                                                final Vector3D occultedPosition, final double occultedRadius) {
  143.         final double[] angle = new double[3];

  144.         final Vector3D satOccultedVector = occultedPosition.subtract(position);
  145.         final Vector3D satOccultingVector = occultingPosition.subtract(position);

  146.         // Sat-Occulted / Sat-Occulting angle
  147.         angle[0] = Vector3D.angle(satOccultedVector, satOccultingVector);

  148.         // Occulting body apparent radius
  149.         angle[1] = FastMath.asin(occultingRadius / satOccultingVector.getNorm());

  150.         // Occulted body apparent radius
  151.         angle[2] = FastMath.asin(occultedRadius / satOccultedVector.getNorm());

  152.         return angle;
  153.     }

  154.     /**
  155.      * Get the useful angles for eclipse computation.
  156.      * @param occultingPosition Oculting body position in the selected frame
  157.      * @param occultingRadius Occulting body mean radius
  158.      * @param occultedPosition Occulted body position in the selected frame
  159.      * @param occultedRadius Occulted body mean radius
  160.      * @param position the satellite's position in the selected frame
  161.      * @param <T> extends RealFieldElement
  162.      * @return the 3 angles {(satOcculting, satOcculted), Occulting body apparent radius, Occulted body apparent radius}
  163.      */
  164.     protected <T extends CalculusFieldElement<T>> T[] getGeneralEclipseAngles(final FieldVector3D<T> position,
  165.                                                                               final FieldVector3D<T> occultingPosition, final T occultingRadius,
  166.                                                                               final FieldVector3D<T> occultedPosition, final T occultedRadius) {
  167.         final T[] angle = MathArrays.buildArray(position.getX().getField(), 3);

  168.         final FieldVector3D<T> satOccultedVector = occultedPosition.subtract(position);
  169.         final FieldVector3D<T> satOccultingVector = occultingPosition.subtract(position);

  170.         // Sat-Occulted / Sat-Occulting angle
  171.         angle[0] = FieldVector3D.angle(satOccultedVector, satOccultingVector);

  172.         // Occulting body apparent radius
  173.         angle[1] = occultingRadius.divide(satOccultingVector.getNorm()).asin();

  174.         // Occulted body apparent radius
  175.         angle[2] = occultedRadius.divide(satOccultedVector.getNorm()).asin();

  176.         return angle;
  177.     }

  178.     /**
  179.      * Add a new occulting body.
  180.      * <p>
  181.      * Central body is already considered, it shall not be added this way.
  182.      * </p>
  183.      * @param provider body PV provider
  184.      * @param radius body mean radius
  185.      * @see #addOccultingBody(OneAxisEllipsoid)
  186.      */
  187.     public void addOccultingBody(final ExtendedPositionProvider provider, final double radius) {

  188.         // as parent frame for occulting body frame,
  189.         // we select the first inertial frame in central body hierarchy
  190.         Frame parent = occultingBodies.get(0).getOcculting().getBodyFrame();
  191.         while (!parent.isPseudoInertial()) {
  192.             parent = parent.getParent();
  193.         }

  194.         // as the occulting body will be spherical, we can use an inertially oriented body frame
  195.         final Frame inertiallyOrientedBodyFrame =
  196.                         new ExtendedPositionProviderAdapter(parent,
  197.                                                                  provider,
  198.                                                                  OCCULTING_PREFIX + occultingBodies.size());

  199.         // create the spherical occulting body
  200.         final OneAxisEllipsoid sphericalOccultingBody =
  201.                         new OneAxisEllipsoid(radius, SPHERICAL_BODY_FLATNESS, inertiallyOrientedBodyFrame);

  202.         addOccultingBody(sphericalOccultingBody);

  203.     }

  204.     /**
  205.      * Add a new occulting body.
  206.      * <p>
  207.      * Central body is already considered, it shall not be added this way.
  208.      * </p>
  209.      * @param occulting occulting body to add
  210.      * @see #addOccultingBody(ExtendedPositionProvider, double)
  211.      * @since 12.0
  212.      */
  213.     public void addOccultingBody(final OneAxisEllipsoid occulting) {

  214.         // retrieve Sun from the central occulting body engine
  215.         final OccultationEngine central = occultingBodies.get(0);

  216.         // create a new occultation engine for the new occulting body
  217.         final OccultationEngine additional =
  218.                         new OccultationEngine(central.getOcculted(), central.getOccultedRadius(), occulting);

  219.         // add it to the list
  220.         occultingBodies.add(additional);

  221.     }

  222.     /**
  223.      * Get all occulting bodies to consider.
  224.      * <p>
  225.      * The list always contains at least one element: the central body
  226.      * which is always the first one in the list.
  227.      * </p>
  228.      * @return immutable list of all occulting bodies to consider, including the central body
  229.      * @since 12.0
  230.      */
  231.     public List<OccultationEngine> getOccultingBodies() {
  232.         return Collections.unmodifiableList(occultingBodies);
  233.     }

  234. }