KnockeRediffusedForceModel.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.util.List;

  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.analysis.polynomials.PolynomialFunction;
  21. import org.hipparchus.analysis.polynomials.PolynomialsUtils;
  22. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  23. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  24. import org.hipparchus.geometry.euclidean.threed.Rotation;
  25. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  26. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  27. import org.hipparchus.util.FastMath;
  28. import org.hipparchus.util.FieldSinCos;
  29. import org.hipparchus.util.MathUtils;
  30. import org.hipparchus.util.SinCos;
  31. import org.orekit.annotation.DefaultDataContext;
  32. import org.orekit.data.DataContext;
  33. import org.orekit.forces.ForceModel;
  34. import org.orekit.frames.Frame;
  35. import org.orekit.propagation.FieldSpacecraftState;
  36. import org.orekit.propagation.SpacecraftState;
  37. import org.orekit.time.AbsoluteDate;
  38. import org.orekit.time.FieldAbsoluteDate;
  39. import org.orekit.time.TimeScale;
  40. import org.orekit.utils.Constants;
  41. import org.orekit.utils.ExtendedPositionProvider;
  42. import org.orekit.utils.ParameterDriver;

  43. /** The Knocke Earth Albedo and IR emission force model.
  44.  * <p>
  45.  * This model is based on "EARTH RADIATION PRESSURE EFFECTS ON SATELLITES", 1988, by P. C. Knocke, J. C. Ries, and B. D. Tapley.
  46.  * </p> <p>
  47.  * This model represents the effects of radiation pressure coming from the Earth.
  48.  * It considers Solar radiation which has been reflected by Earth (albedo) and Earth infrared emissions.
  49.  * The planet is considered as a sphere and is divided into elementary areas.
  50.  * Each elementary area is considered as a plane and emits radiation according to Lambert's law.
  51.  * The flux the satellite receives is then equal to the sum of the elementary fluxes coming from Earth.
  52.  * </p> <p>
  53.  * The radiative model of the satellite, and its ability to diffuse, reflect  or absorb radiation is handled
  54.  * by a {@link RadiationSensitive radiation sensitive model}.
  55.  * </p> <p>
  56.  * <b>Caution:</b> This model is only suitable for Earth. Using it with another central body is prone to error..
  57.  * </p>
  58.  *
  59.  * @author Thomas Paulet
  60.  * @since 10.3
  61.  */
  62. public class KnockeRediffusedForceModel implements ForceModel {

  63.     /** 7Earth rotation around Sun pulsation in rad/sec. */
  64.     private static final double EARTH_AROUND_SUN_PULSATION = MathUtils.TWO_PI / Constants.JULIAN_YEAR;

  65.     /** Coefficient for solar irradiance computation. */
  66.     private static final double ES_COEFF = 4.5606E-6;

  67.     /** First coefficient for albedo computation. */
  68.     private static final double A0 = 0.34;

  69.     /** Second coefficient for albedo computation. */
  70.     private static final double C0 = 0.;

  71.     /** Third coefficient for albedo computation. */
  72.     private static final double C1 = 0.10;

  73.     /** Fourth coefficient for albedo computation. */
  74.     private static final double C2 = 0.;

  75.     /** Fifth coefficient for albedo computation. */
  76.     private static final double A2 = 0.29;

  77.     /** First coefficient for Earth emissivity computation. */
  78.     private static final double E0 = 0.68;

  79.     /** Second coefficient for Earth emissivity computation. */
  80.     private static final double K0 = 0.;

  81.     /** Third coefficient for Earth emissivity computation. */
  82.     private static final double K1 = -0.07;

  83.     /** Fourth coefficient for Earth emissivity computation. */
  84.     private static final double K2 = 0.;

  85.     /** Fifth coefficient for Earth emissivity computation. */
  86.     private static final double E2 = -0.18;

  87.     /** Sun model. */
  88.     private final ExtendedPositionProvider sun;

  89.     /** Spacecraft. */
  90.     private final RadiationSensitive spacecraft;

  91.     /** Angular resolution for emissivity and albedo computation in rad. */
  92.     private final double angularResolution;

  93.     /** Earth equatorial radius in m. */
  94.     private final double equatorialRadius;

  95.     /** Reference date for periodic terms: December 22nd 1981.
  96.      * Without more precision, the choice is to set it at midnight, UTC. */
  97.     private final AbsoluteDate referenceEpoch;

  98.     /** Default Constructor.
  99.      * <p>This constructor uses the {@link DataContext#getDefault() default data context}</p>.
  100.      * @param sun Sun model
  101.      * @param spacecraft the object physical and geometrical information
  102.      * @param equatorialRadius the Earth equatorial radius in m
  103.      * @param angularResolution angular resolution in rad
  104.      */
  105.     @DefaultDataContext
  106.     public KnockeRediffusedForceModel (final ExtendedPositionProvider sun,
  107.                                        final RadiationSensitive spacecraft,
  108.                                        final double equatorialRadius,
  109.                                        final double angularResolution) {

  110.         this(sun, spacecraft, equatorialRadius, angularResolution, DataContext.getDefault().getTimeScales().getUTC());
  111.     }

  112.     /** General constructor.
  113.      * @param sun Sun model
  114.      * @param spacecraft the object physical and geometrical information
  115.      * @param equatorialRadius the Earth equatorial radius in m
  116.      * @param angularResolution angular resolution in rad
  117.      * @param utc the UTC time scale to define reference epoch
  118.      */
  119.     public KnockeRediffusedForceModel (final ExtendedPositionProvider sun,
  120.                                        final RadiationSensitive spacecraft,
  121.                                        final double equatorialRadius,
  122.                                        final double angularResolution,
  123.                                        final TimeScale utc) {
  124.         this.sun               = sun;
  125.         this.spacecraft        = spacecraft;
  126.         this.equatorialRadius  = equatorialRadius;
  127.         this.angularResolution = angularResolution;
  128.         this.referenceEpoch    = new AbsoluteDate(1981, 12, 22, 0, 0, 0.0, utc);
  129.     }


  130.     /** {@inheritDoc} */
  131.     @Override
  132.     public boolean dependsOnPositionOnly() {
  133.         return false;
  134.     }

  135.     /** {@inheritDoc} */
  136.     @Override
  137.     public Vector3D acceleration(final SpacecraftState s,
  138.                                  final double[] parameters) {

  139.         // Get date
  140.         final AbsoluteDate date = s.getDate();

  141.         // Get frame
  142.         final Frame frame = s.getFrame();

  143.         // Get satellite position
  144.         final Vector3D satellitePosition = s.getPosition();

  145.         // Get Sun position
  146.         final Vector3D sunPosition = sun.getPosition(date, frame);

  147.         // Project satellite on Earth as vector
  148.         final Vector3D projectedToGround = satellitePosition.normalize().scalarMultiply(equatorialRadius);

  149.         // Get elementary vector east for Earth browsing using rotations
  150.         final double q = 1.0 / FastMath.hypot(satellitePosition.getX(), satellitePosition.getY());
  151.         final Vector3D east = new Vector3D(-q * satellitePosition.getY(), q * satellitePosition.getX(), 0);

  152.         // Initialize rediffused flux with elementary flux coming from the circular area around the projected satellite
  153.         final double centerArea = MathUtils.TWO_PI * equatorialRadius * equatorialRadius *
  154.                                  (1.0 - FastMath.cos(angularResolution));
  155.         Vector3D rediffusedFlux = computeElementaryFlux(s, projectedToGround, sunPosition, centerArea);

  156.         // Sectorize the part of Earth which is seen by the satellite into crown sectors with constant angular resolution
  157.         for (double eastAxisOffset = 1.5 * angularResolution;
  158.              eastAxisOffset < FastMath.asin(equatorialRadius / satellitePosition.getNorm());
  159.              eastAxisOffset = eastAxisOffset + angularResolution) {

  160.             // Build rotation transformations to get first crown elementary sector center
  161.             final Rotation eastRotation = new Rotation(east, eastAxisOffset, RotationConvention.VECTOR_OPERATOR);

  162.             // Get first elementary crown sector center
  163.             final Vector3D firstCrownSectorCenter = eastRotation.applyTo(projectedToGround);

  164.             // Compute current elementary crown sector area, it results of the integration of an elementary crown sector
  165.             // over the angular resolution
  166.             final double sectorArea = equatorialRadius * equatorialRadius *
  167.                                       2.0 * angularResolution * FastMath.sin(0.5 * angularResolution) *
  168.                                       FastMath.sin(eastAxisOffset);

  169.             // Browse the entire crown
  170.             for (double radialAxisOffset = 0.5 * angularResolution;
  171.                  radialAxisOffset < MathUtils.TWO_PI;
  172.                  radialAxisOffset = radialAxisOffset + angularResolution) {

  173.                 // Build rotation transformations to get elementary area center
  174.                 final Rotation radialRotation  = new Rotation(projectedToGround, radialAxisOffset, RotationConvention.VECTOR_OPERATOR);

  175.                 // Get current elementary crown sector center
  176.                 final Vector3D currentCenter = radialRotation.applyTo(firstCrownSectorCenter);

  177.                 // Add current sector contribution to total rediffused flux
  178.                 rediffusedFlux = rediffusedFlux.add(computeElementaryFlux(s, currentCenter, sunPosition, sectorArea));
  179.             }
  180.         }

  181.         return spacecraft.radiationPressureAcceleration(s, rediffusedFlux, parameters);
  182.     }


  183.     /** {@inheritDoc} */
  184.     @Override
  185.     public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(final FieldSpacecraftState<T> s,
  186.                                                                              final T[] parameters) {
  187.         // Get date
  188.         final FieldAbsoluteDate<T> date = s.getDate();

  189.         // Get frame
  190.         final Frame frame = s.getFrame();

  191.         // Get zero
  192.         final T zero = date.getField().getZero();

  193.         // Get satellite position
  194.         final FieldVector3D<T> satellitePosition = s.getPosition();

  195.         // Get Sun position
  196.         final FieldVector3D<T> sunPosition = sun.getPosition(date, frame);

  197.         // Project satellite on Earth as vector
  198.         final FieldVector3D<T> projectedToGround = satellitePosition.normalize().scalarMultiply(equatorialRadius);

  199.         // Get elementary vector east for Earth browsing using rotations
  200.         final T q = FastMath.hypot(satellitePosition.getX(), satellitePosition.getY()).reciprocal();
  201.         final FieldVector3D<T> east = new FieldVector3D<>(q.negate().multiply(satellitePosition.getY()),
  202.                                                           q.multiply(satellitePosition.getX()),
  203.                                                           zero);

  204.         // Initialize rediffused flux with elementary flux coming from the circular area around the projected satellite
  205.         final T centerArea = zero.getPi().multiply(2.0).multiply(equatorialRadius).multiply(equatorialRadius).
  206.                         multiply(1.0 - FastMath.cos(angularResolution));
  207.         FieldVector3D<T> rediffusedFlux = computeElementaryFlux(s, projectedToGround, sunPosition, centerArea);

  208.         // Sectorize the part of Earth which is seen by the satellite into crown sectors with constant angular resolution
  209.         for (double eastAxisOffset = 1.5 * angularResolution;
  210.              eastAxisOffset < FastMath.asin(equatorialRadius / satellitePosition.getNorm().getReal());
  211.              eastAxisOffset = eastAxisOffset + angularResolution) {

  212.             // Build rotation transformations to get first crown elementary sector center
  213.             final FieldRotation<T> eastRotation = new FieldRotation<>(east, zero.newInstance(eastAxisOffset),
  214.                                                                       RotationConvention.VECTOR_OPERATOR);

  215.             // Get first elementary crown sector center
  216.             final FieldVector3D<T> firstCrownSectorCenter = eastRotation.applyTo(projectedToGround);

  217.             // Compute current elementary crown sector area, it results of the integration of an elementary crown sector
  218.             // over the angular resolution
  219.             final T sectorArea = zero.newInstance(equatorialRadius * equatorialRadius *
  220.                                                   2.0 * angularResolution * FastMath.sin(0.5 * angularResolution) *
  221.                                                   FastMath.sin(eastAxisOffset));

  222.             // Browse the entire crown
  223.             for (double radialAxisOffset = 0.5 * angularResolution;
  224.                  radialAxisOffset < MathUtils.TWO_PI;
  225.                  radialAxisOffset = radialAxisOffset + angularResolution) {

  226.                 // Build rotation transformations to get elementary area center
  227.                 final FieldRotation<T> radialRotation  = new FieldRotation<>(projectedToGround,
  228.                                                                              zero.newInstance(radialAxisOffset),
  229.                                                                              RotationConvention.VECTOR_OPERATOR);

  230.                 // Get current elementary crown sector center
  231.                 final FieldVector3D<T> currentCenter = radialRotation.applyTo(firstCrownSectorCenter);

  232.                 // Add current sector contribution to total rediffused flux
  233.                 rediffusedFlux = rediffusedFlux.add(computeElementaryFlux(s, currentCenter, sunPosition, sectorArea));
  234.             }
  235.         }

  236.         return spacecraft.radiationPressureAcceleration(s, rediffusedFlux, parameters);
  237.     }


  238.     /** {@inheritDoc} */
  239.     @Override
  240.     public List<ParameterDriver> getParametersDrivers() {
  241.         return spacecraft.getRadiationParametersDrivers();
  242.     }

  243.     /** Compute Earth albedo.
  244.      * Albedo value represents the fraction of solar radiative flux that is reflected by Earth.
  245.      * Its value is in [0;1].
  246.      * @param date the date
  247.      * @param phi the equatorial latitude in rad
  248.      * @return the albedo in [0;1]
  249.      */
  250.     public double computeAlbedo(final AbsoluteDate date, final double phi) {

  251.         // Get duration since coefficient reference epoch
  252.         final double deltaT = date.durationFrom(referenceEpoch);

  253.         // Compute 1rst Legendre polynomial coeficient
  254.         final SinCos sc = FastMath.sinCos(EARTH_AROUND_SUN_PULSATION * deltaT);
  255.         final double A1 = C0 +
  256.                           C1 * sc.cos() +
  257.                           C2 * sc.sin();

  258.         // Get 1rst and 2nd order Legendre polynomials
  259.         final PolynomialFunction firstLegendrePolynomial  = PolynomialsUtils.createLegendrePolynomial(1);
  260.         final PolynomialFunction secondLegendrePolynomial = PolynomialsUtils.createLegendrePolynomial(2);

  261.         // Get latitude sinus
  262.         final double sinPhi = FastMath.sin(phi);

  263.         // Compute albedo
  264.         return A0 +
  265.                A1 * firstLegendrePolynomial.value(sinPhi) +
  266.                A2 * secondLegendrePolynomial.value(sinPhi);

  267.     }


  268.     /** Compute Earth albedo.
  269.      * Albedo value represents the fraction of solar radiative flux that is reflected by Earth.
  270.      * Its value is in [0;1].
  271.      * @param date the date
  272.      * @param phi the equatorial latitude in rad
  273.      * @param <T> extends CalculusFieldElement
  274.      * @return the albedo in [0;1]
  275.      */
  276.     public <T extends CalculusFieldElement<T>> T computeAlbedo(final FieldAbsoluteDate<T> date, final T phi) {

  277.         // Get duration since coefficient reference epoch
  278.         final T deltaT = date.durationFrom(referenceEpoch);

  279.         // Compute 1rst Legendre polynomial coeficient
  280.         final FieldSinCos<T> sc = FastMath.sinCos(deltaT.multiply(EARTH_AROUND_SUN_PULSATION));
  281.         final T A1 = sc.cos().multiply(C1).add(
  282.                      sc.sin().multiply(C2)).add(C0);

  283.         // Get 1rst and 2nd order Legendre polynomials
  284.         final PolynomialFunction firstLegendrePolynomial  = PolynomialsUtils.createLegendrePolynomial(1);
  285.         final PolynomialFunction secondLegendrePolynomial = PolynomialsUtils.createLegendrePolynomial(2);

  286.         // Get latitude sinus
  287.         final T sinPhi = FastMath.sin(phi);

  288.         // Compute albedo
  289.         return firstLegendrePolynomial.value(sinPhi).multiply(A1).add(
  290.                secondLegendrePolynomial.value(sinPhi).multiply(A2)).add(A0);

  291.     }

  292.     /** Compute Earth emisivity.
  293.      * Emissivity is used to compute the infrared flux that is emitted by Earth.
  294.      * Its value is in [0;1].
  295.      * @param date the date
  296.      * @param phi the equatorial latitude in rad
  297.      * @return the emissivity in [0;1]
  298.      */
  299.     public double computeEmissivity(final AbsoluteDate date, final double phi) {

  300.         // Get duration since coefficient reference epoch
  301.         final double deltaT = date.durationFrom(referenceEpoch);

  302.         // Compute 1rst Legendre polynomial coefficient
  303.         final SinCos sc = FastMath.sinCos(EARTH_AROUND_SUN_PULSATION * deltaT);
  304.         final double E1 = K0 +
  305.                           K1 * sc.cos() +
  306.                           K2 * sc.sin();

  307.         // Get 1rst and 2nd order Legendre polynomials
  308.         final PolynomialFunction firstLegendrePolynomial  = PolynomialsUtils.createLegendrePolynomial(1);
  309.         final PolynomialFunction secondLegendrePolynomial = PolynomialsUtils.createLegendrePolynomial(2);

  310.         // Get latitude sinus
  311.         final double sinPhi = FastMath.sin(phi);

  312.         // Compute albedo
  313.         return E0 +
  314.                E1 * firstLegendrePolynomial.value(sinPhi) +
  315.                E2 * secondLegendrePolynomial.value(sinPhi);

  316.     }


  317.     /** Compute Earth emisivity.
  318.      * Emissivity is used to compute the infrared flux that is emitted by Earth.
  319.      * Its value is in [0;1].
  320.      * @param date the date
  321.      * @param phi the equatorial latitude in rad
  322.      * @param <T> extends CalculusFieldElement
  323.      * @return the emissivity in [0;1]
  324.      */
  325.     public <T extends CalculusFieldElement<T>> T computeEmissivity(final FieldAbsoluteDate<T> date, final T phi) {

  326.         // Get duration since coefficient reference epoch
  327.         final T deltaT = date.durationFrom(referenceEpoch);

  328.         // Compute 1rst Legendre polynomial coeficient
  329.         final FieldSinCos<T> sc = FastMath.sinCos(deltaT.multiply(EARTH_AROUND_SUN_PULSATION));
  330.         final T E1 = sc.cos().multiply(K1).add(
  331.                      sc.sin().multiply(K2)).add(K0);

  332.         // Get 1rst and 2nd order Legendre polynomials
  333.         final PolynomialFunction firstLegendrePolynomial  = PolynomialsUtils.createLegendrePolynomial(1);
  334.         final PolynomialFunction secondLegendrePolynomial = PolynomialsUtils.createLegendrePolynomial(2);

  335.         // Get latitude sinus
  336.         final T sinPhi = FastMath.sin(phi);

  337.         // Compute albedo
  338.         return firstLegendrePolynomial.value(sinPhi).multiply(E1).add(
  339.                secondLegendrePolynomial.value(sinPhi).multiply(E2)).add(E0);

  340.     }

  341.     /** Compute total solar flux impacting Earth.
  342.      * @param sunPosition the Sun position in an Earth centered frame
  343.      * @return the total solar flux impacting Earth in J/m^3
  344.      */
  345.     public double computeSolarFlux(final Vector3D sunPosition) {

  346.         // Compute Earth - Sun distance in UA
  347.         final double earthSunDistance = sunPosition.getNorm() / Constants.JPL_SSD_ASTRONOMICAL_UNIT;

  348.         // Compute Solar flux
  349.         return ES_COEFF * Constants.SPEED_OF_LIGHT / (earthSunDistance * earthSunDistance);
  350.     }


  351.     /** Compute total solar flux impacting Earth.
  352.      * @param sunPosition the Sun position in an Earth centered frame
  353.      * @param <T> extends CalculusFieldElement
  354.      * @return the total solar flux impacting Earth in J/m^3
  355.      */
  356.     public <T extends CalculusFieldElement<T>> T computeSolarFlux(final FieldVector3D<T> sunPosition) {

  357.         // Compute Earth - Sun distance in UA
  358.         final T earthSunDistance = sunPosition.getNorm().divide(Constants.JPL_SSD_ASTRONOMICAL_UNIT);

  359.         // Compute Solar flux
  360.         return earthSunDistance.multiply(earthSunDistance).reciprocal().multiply(ES_COEFF * Constants.SPEED_OF_LIGHT);
  361.     }


  362.     /** Compute elementary rediffused flux on satellite.
  363.      * @param state the current spacecraft state
  364.      * @param elementCenter the position of the considered area center
  365.      * @param sunPosition the position of the Sun in the spacecraft frame
  366.      * @param elementArea the area of the current element
  367.      * @return the rediffused flux from considered element on the spacecraft
  368.      */
  369.     public Vector3D computeElementaryFlux(final SpacecraftState state,
  370.                                           final Vector3D elementCenter,
  371.                                           final Vector3D sunPosition,
  372.                                           final double elementArea) {

  373.         // Get satellite position
  374.         final Vector3D satellitePosition = state.getPosition();

  375.         // Get current date
  376.         final AbsoluteDate date = state.getDate();

  377.         // Get solar flux impacting Earth
  378.         final double solarFlux = computeSolarFlux(sunPosition);

  379.         // Get satellite viewing angle as seen from current elementary area
  380.         final double centerNorm = elementCenter.getNorm();
  381.         final double cosAlpha   = Vector3D.dotProduct(elementCenter, satellitePosition) /
  382.                                   (centerNorm * satellitePosition.getNorm());

  383.         // Check that satellite sees the current area
  384.         if (cosAlpha > 0) {

  385.             // Get current elementary area center latitude
  386.             final double currentLatitude = elementCenter.getDelta();

  387.             // Compute Earth emissivity value
  388.             final double e = computeEmissivity(date, currentLatitude);

  389.             // Initialize albedo
  390.             double a = 0.0;

  391.             // Check if elementary area is in daylight
  392.             final double cosSunAngle = Vector3D.dotProduct(elementCenter, sunPosition) /
  393.                                        (centerNorm * sunPosition.getNorm());

  394.             if (cosSunAngle > 0) {
  395.                 // Elementary area is in daylight, compute albedo value
  396.                 a = computeAlbedo(date, currentLatitude);
  397.             }

  398.             // Compute elementary area contribution to rediffused flux
  399.             final double albedoAndIR = a * solarFlux * cosSunAngle + e * solarFlux * 0.25;

  400.             // Compute elementary area - satellite vector and distance
  401.             final Vector3D r = satellitePosition.subtract(elementCenter);
  402.             final double rNorm = r.getNorm();

  403.             // Compute attenuated projected elementary area vector
  404.             final Vector3D projectedAreaVector = r.scalarMultiply(elementArea * cosAlpha /
  405.                                                                  (FastMath.PI * rNorm * rNorm * rNorm));

  406.             // Compute elementary radiation flux from current elementary area
  407.             return projectedAreaVector.scalarMultiply(albedoAndIR / Constants.SPEED_OF_LIGHT);

  408.         } else {

  409.             // Spacecraft does not see the elementary area
  410.             return new Vector3D(0.0, 0.0, 0.0);
  411.         }

  412.     }


  413.     /** Compute elementary rediffused flux on satellite.
  414.      * @param state the current spacecraft state
  415.      * @param elementCenter the position of the considered area center
  416.      * @param sunPosition the position of the Sun in the spacecraft frame
  417.      * @param elementArea the area of the current element
  418.      * @param <T> extends CalculusFieldElement
  419.      * @return the rediffused flux from considered element on the spacecraft
  420.      */
  421.     public <T extends CalculusFieldElement<T>> FieldVector3D<T> computeElementaryFlux(final FieldSpacecraftState<T> state,
  422.                                                                                       final FieldVector3D<T> elementCenter,
  423.                                                                                       final FieldVector3D<T> sunPosition,
  424.                                                                                       final T elementArea) {

  425.         // Get satellite position
  426.         final FieldVector3D<T> satellitePosition = state.getPosition();

  427.         // Get current date
  428.         final FieldAbsoluteDate<T> date = state.getDate();

  429.         // Get zero
  430.         final T zero = date.getField().getZero();

  431.         // Get solar flux impacting Earth
  432.         final T solarFlux = computeSolarFlux(sunPosition);

  433.         // Get satellite viewing angle as seen from current elementary area
  434.         final T centerNorm = elementCenter.getNorm();
  435.         final T cosAlpha   = FieldVector3D.dotProduct(elementCenter, satellitePosition).
  436.                              divide(centerNorm.multiply(satellitePosition.getNorm()));

  437.         // Check that satellite sees the current area
  438.         if (cosAlpha.getReal() > 0) {

  439.             // Get current elementary area center latitude
  440.             final T currentLatitude = elementCenter.getDelta();

  441.             // Compute Earth emissivity value
  442.             final T e = computeEmissivity(date, currentLatitude);

  443.             // Initialize albedo
  444.             T a = zero;

  445.             // Check if elementary area is in daylight
  446.             final T cosSunAngle = FieldVector3D.dotProduct(elementCenter, sunPosition).
  447.                                   divide(centerNorm.multiply(sunPosition.getNorm()));

  448.             if (cosSunAngle.getReal() > 0) {
  449.                 // Elementary area is in daylight, compute albedo value
  450.                 a = computeAlbedo(date, currentLatitude);
  451.             }

  452.             // Compute elementary area contribution to rediffused flux
  453.             final T albedoAndIR = a.multiply(solarFlux).multiply(cosSunAngle).
  454.                                   add(e.multiply(solarFlux).multiply(0.25));

  455.             // Compute elementary area - satellite vector and distance
  456.             final FieldVector3D<T> r = satellitePosition.subtract(elementCenter);
  457.             final T rNorm = r.getNorm();

  458.             // Compute attenuated projected elementary area vector
  459.             final FieldVector3D<T> projectedAreaVector = r.scalarMultiply(elementArea.multiply(cosAlpha).
  460.                                                                           divide(rNorm.square().multiply(rNorm).multiply(zero.getPi())));

  461.             // Compute elementary radiation flux from current elementary area
  462.             return projectedAreaVector.scalarMultiply(albedoAndIR.divide(Constants.SPEED_OF_LIGHT));

  463.         } else {

  464.             // Spacecraft does not see the elementary area
  465.             return new FieldVector3D<>(zero, zero, zero);
  466.         }

  467.     }

  468. }