AbstractRadiationForceModel.java

  1. /* Copyright 2002-2024 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.ode.events.Action;
  28. import org.hipparchus.util.FastMath;
  29. import org.hipparchus.util.MathArrays;
  30. import org.orekit.bodies.OneAxisEllipsoid;
  31. import org.orekit.frames.Frame;
  32. import org.orekit.propagation.events.EclipseDetector;
  33. import org.orekit.propagation.events.EventDetector;
  34. import org.orekit.propagation.events.FieldEclipseDetector;
  35. import org.orekit.propagation.events.FieldEventDetector;
  36. import org.orekit.utils.Constants;
  37. import org.orekit.utils.ExtendedPVCoordinatesProvider;
  38. import org.orekit.utils.ExtendedPVCoordinatesProviderAdapter;
  39. import org.orekit.utils.OccultationEngine;

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

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

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

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

  53.     /** Flatness for spherical bodies. */
  54.     private static final double SPHERICAL_BODY_FLATNESS = 0.0;

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

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

  61.     /**
  62.      * Default constructor.
  63.      * Only central body is considered.
  64.      * @param sun Sun model
  65.      * @param centralBody central body shape model (for umbra/penumbra computation)
  66.      * @since 12.0
  67.      */
  68.     protected AbstractRadiationForceModel(final ExtendedPVCoordinatesProvider sun, final OneAxisEllipsoid centralBody) {
  69.         // in most cases, there will be only Earth, sometimes also Moon so an initial capacity of 2 is appropriate
  70.         occultingBodies = new ArrayList<>(2);
  71.         occultingBodies.add(new OccultationEngine(sun, Constants.SUN_RADIUS, centralBody));
  72.     }

  73.     /** {@inheritDoc} */
  74.     @Override
  75.     public Stream<EventDetector> getEventDetectors() {
  76.         final EventDetector[] detectors = new EventDetector[2 * occultingBodies.size()];
  77.         for (int i = 0; i < occultingBodies.size(); ++i) {
  78.             final OccultationEngine occulting = occultingBodies.get(i);
  79.             detectors[2 * i]     = new EclipseDetector(occulting).
  80.                                    withUmbra().
  81.                                    withMargin(-ANGULAR_MARGIN).
  82.                                    withMaxCheck(ECLIPSE_MAX_CHECK).
  83.                                    withThreshold(ECLIPSE_THRESHOLD).
  84.                                    withHandler((state, detector, increasing) -> Action.RESET_DERIVATIVES);
  85.             detectors[2 * i + 1] = new EclipseDetector(occulting).
  86.                                    withPenumbra().
  87.                                    withMargin(ANGULAR_MARGIN).
  88.                                    withMaxCheck(ECLIPSE_MAX_CHECK).
  89.                                    withThreshold(ECLIPSE_THRESHOLD).
  90.                                    withHandler((state, detector, increasing) -> Action.RESET_DERIVATIVES);
  91.         }
  92.         // Fusion between Date detector for parameter driver span change and
  93.         // Detector for umbra / penumbra events
  94.         return Stream.concat(Stream.of(detectors), RadiationForceModel.super.getEventDetectors());
  95.     }

  96.     /** {@inheritDoc} */
  97.     @Override
  98.     public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(final Field<T> field) {
  99.         final T zero = field.getZero();
  100.         @SuppressWarnings("unchecked")
  101.         final FieldEventDetector<T>[] detectors = (FieldEventDetector<T>[]) Array.newInstance(FieldEventDetector.class,
  102.                                                                                               2 * occultingBodies.size());
  103.         for (int i = 0; i < occultingBodies.size(); ++i) {
  104.             final OccultationEngine occulting = occultingBodies.get(i);
  105.             detectors[2 * i]     = new FieldEclipseDetector<>(field, occulting).
  106.                                    withUmbra().
  107.                                    withMargin(zero.newInstance(-ANGULAR_MARGIN)).
  108.                                    withMaxCheck(ECLIPSE_MAX_CHECK).
  109.                                    withThreshold(zero.newInstance(ECLIPSE_THRESHOLD)).
  110.                                    withHandler((state, detector, increasing) -> Action.RESET_DERIVATIVES);
  111.             detectors[2 * i + 1] = new FieldEclipseDetector<>(field, occulting).
  112.                                    withPenumbra().
  113.                                    withMargin(zero.newInstance(ANGULAR_MARGIN)).
  114.                                    withMaxCheck(ECLIPSE_MAX_CHECK).
  115.                                    withThreshold(zero.newInstance(ECLIPSE_THRESHOLD)).
  116.                                    withHandler((state, detector, increasing) -> Action.RESET_DERIVATIVES);
  117.         }
  118.         return Stream.concat(Stream.of(detectors), RadiationForceModel.super.getFieldEventDetectors(field));
  119.     }

  120.     /**
  121.      * Get the useful angles for eclipse computation.
  122.      * @param position the satellite's position in the selected frame
  123.      * @param occultingPosition Oculting body position in the selected frame
  124.      * @param occultingRadius Occulting body mean radius
  125.      * @param occultedPosition Occulted body position in the selected frame
  126.      * @param occultedRadius Occulted body mean radius
  127.      * @return the 3 angles {(satOcculting, satOcculted), Occulting body apparent radius, Occulted body apparent radius}
  128.      */
  129.     protected double[] getGeneralEclipseAngles(final Vector3D position, final Vector3D occultingPosition, final double occultingRadius,
  130.                                                final Vector3D occultedPosition, final double occultedRadius) {
  131.         final double[] angle = new double[3];

  132.         final Vector3D satOccultedVector = occultedPosition.subtract(position);
  133.         final Vector3D satOccultingVector = occultingPosition.subtract(position);

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

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

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

  140.         return angle;
  141.     }

  142.     /**
  143.      * Get the useful angles for eclipse computation.
  144.      * @param occultingPosition Oculting body position in the selected frame
  145.      * @param occultingRadius Occulting body mean radius
  146.      * @param occultedPosition Occulted body position in the selected frame
  147.      * @param occultedRadius Occulted body mean radius
  148.      * @param position the satellite's position in the selected frame
  149.      * @param <T> extends RealFieldElement
  150.      * @return the 3 angles {(satOcculting, satOcculted), Occulting body apparent radius, Occulted body apparent radius}
  151.      */
  152.     protected <T extends CalculusFieldElement<T>> T[] getGeneralEclipseAngles(final FieldVector3D<T> position,
  153.                                                                               final FieldVector3D<T> occultingPosition, final T occultingRadius,
  154.                                                                               final FieldVector3D<T> occultedPosition, final T occultedRadius) {
  155.         final T[] angle = MathArrays.buildArray(position.getX().getField(), 3);

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

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

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

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

  164.         return angle;
  165.     }

  166.     /**
  167.      * Add a new occulting body.
  168.      * <p>
  169.      * Central body is already considered, it shall not be added this way.
  170.      * </p>
  171.      * @param provider body PV provider
  172.      * @param radius body mean radius
  173.      * @see #addOccultingBody(OneAxisEllipsoid)
  174.      */
  175.     public void addOccultingBody(final ExtendedPVCoordinatesProvider provider, final double radius) {

  176.         // as parent frame for occulting body frame,
  177.         // we select the first inertial frame in central body hierarchy
  178.         Frame parent = occultingBodies.get(0).getOcculting().getBodyFrame();
  179.         while (!parent.isPseudoInertial()) {
  180.             parent = parent.getParent();
  181.         }

  182.         // as the occulting body will be spherical, we can use an inertially oriented body frame
  183.         final Frame inertiallyOrientedBodyFrame =
  184.                         new ExtendedPVCoordinatesProviderAdapter(parent,
  185.                                                                  provider,
  186.                                                                  OCCULTING_PREFIX + occultingBodies.size());

  187.         // create the spherical occulting body
  188.         final OneAxisEllipsoid sphericalOccultingBody =
  189.                         new OneAxisEllipsoid(radius, SPHERICAL_BODY_FLATNESS, inertiallyOrientedBodyFrame);

  190.         addOccultingBody(sphericalOccultingBody);

  191.     }

  192.     /**
  193.      * Add a new occulting body.
  194.      * <p>
  195.      * Central body is already considered, it shall not be added this way.
  196.      * </p>
  197.      * @param occulting occulting body to add
  198.      * @see #addOccultingBody(ExtendedPVCoordinatesProvider, double)
  199.      * @since 12.0
  200.      */
  201.     public void addOccultingBody(final OneAxisEllipsoid occulting) {

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

  204.         // create a new occultation engine for the new occulting body
  205.         final OccultationEngine additional =
  206.                         new OccultationEngine(central.getOcculted(), central.getOccultedRadius(), occulting);

  207.         // add it to the list
  208.         occultingBodies.add(additional);

  209.     }

  210.     /**
  211.      * Get all occulting bodies to consider.
  212.      * <p>
  213.      * The list always contains at least one element: the central body
  214.      * which is always the first one in the list.
  215.      * </p>
  216.      * @return immutable list of all occulting bodies to consider, including the central body
  217.      * @since 12.0
  218.      */
  219.     public List<OccultationEngine> getOccultingBodies() {
  220.         return Collections.unmodifiableList(occultingBodies);
  221.     }

  222. }