BoxAndSolarArraySpacecraft.java

  1. /* Copyright 2002-2019 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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;

  18. import java.util.ArrayList;
  19. import java.util.List;

  20. import org.hipparchus.Field;
  21. import org.hipparchus.RealFieldElement;
  22. import org.hipparchus.analysis.differentiation.DSFactory;
  23. import org.hipparchus.analysis.differentiation.DerivativeStructure;
  24. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  25. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  26. import org.hipparchus.geometry.euclidean.threed.Rotation;
  27. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  28. import org.hipparchus.util.FastMath;
  29. import org.hipparchus.util.Precision;
  30. import org.orekit.errors.OrekitException;
  31. import org.orekit.errors.OrekitInternalError;
  32. import org.orekit.errors.OrekitMessages;
  33. import org.orekit.forces.drag.DragSensitive;
  34. import org.orekit.forces.radiation.RadiationSensitive;
  35. import org.orekit.frames.Frame;
  36. import org.orekit.time.AbsoluteDate;
  37. import org.orekit.time.FieldAbsoluteDate;
  38. import org.orekit.utils.PVCoordinatesProvider;
  39. import org.orekit.utils.ParameterDriver;

  40. /** Class representing the features of a classical satellite
  41.  * with a convex body shape and rotating flat solar arrays.
  42.  * <p>
  43.  * The body can be either a simple parallelepipedic box aligned with
  44.  * spacecraft axes or a set of facets defined by their area and normal vector.
  45.  * This should handle accurately most spacecraft shapes.
  46.  * </p>
  47.  * <p>
  48.  * The solar array rotation with respect to satellite body can be either
  49.  * the best lighting orientation (i.e. Sun exactly in solar array meridian
  50.  * plane defined by solar array rotation axis and solar array normal vector)
  51.  * or a rotation evolving linearly according to a start position and an
  52.  * angular rate (which can be set to 0 for non-rotating panels, which may
  53.  * occur in special modes or during contingencies).
  54.  * </p>
  55.  * <p>
  56.  * The lift component of the drag force can be optionally considered. It should
  57.  * probably only be used for reentry computation, with much denser atmosphere
  58.  * than in regular orbit propagation. The lift component is computed using a
  59.  * ratio of molecules that experience specular reflection instead of diffuse
  60.  * reflection (absorption followed by outgassing at negligible velocity).
  61.  * Without lift (i.e. when the lift ratio is set to 0), drag force is along
  62.  * atmosphere relative velocity. With lift (i.e. when the lift ratio is set to any
  63.  * value between 0 and 1), the drag force depends on both relative velocity direction
  64.  * and facets normal orientation. For a single panel, if the relative velocity is
  65.  * head-on (i.e. aligned with the panel normal), the force will be in the same
  66.  * direction with and without lift, but the magnitude with lift ratio set to 1.0 will
  67.  * be twice the magnitude with lift ratio set to 0.0 (because atmosphere molecules
  68.  * bounces backward at same velocity in case of specular reflection).
  69.  * </p>
  70.  * <p>
  71.  * This model does not take cast shadow between body and solar array into account.
  72.  * </p>
  73.  *
  74.  * @author Luc Maisonobe
  75.  * @author Pascal Parraud
  76.  */
  77. public class BoxAndSolarArraySpacecraft implements RadiationSensitive, DragSensitive {

  78.     /** Parameters scaling factor.
  79.      * <p>
  80.      * We use a power of 2 to avoid numeric noise introduction
  81.      * in the multiplications/divisions sequences.
  82.      * </p>
  83.      */
  84.     private final double SCALE = FastMath.scalb(1.0, -3);

  85.     /** Driver for drag coefficient parameter. */
  86.     private final ParameterDriver dragParameterDriver;

  87.     /** Driver for lift ratio parameter (may be null is lift is ignored). */
  88.     private final ParameterDriver liftParameterDriver;

  89.     /** Driver for radiation pressure absorption coefficient parameter. */
  90.     private final ParameterDriver absorptionParameterDriver;

  91.     /** Driver for radiation pressure reflection coefficient parameter. */
  92.     private final ParameterDriver reflectionParameterDriver;

  93.     /** Surface vectors for body facets. */
  94.     private final List<Facet> facets;

  95.     /** Solar array area (m²). */
  96.     private final double solarArrayArea;

  97.     /** Reference date for linear rotation angle (may be null). */
  98.     private final AbsoluteDate referenceDate;

  99.     /** Rotation rate for linear rotation angle. */
  100.     private final double rotationRate;

  101.     /** Solar array reference axis in spacecraft frame (may be null). */
  102.     private final Vector3D saX;

  103.     /** Solar array third axis in spacecraft frame (may be null). */
  104.     private final Vector3D saY;

  105.     /** Solar array rotation axis in spacecraft frame. */
  106.     private final Vector3D saZ;

  107.     /** Sun model. */
  108.     private final PVCoordinatesProvider sun;

  109.     /** Factory for the DerivativeStructure instances. */
  110.     private final DSFactory factory;

  111.     /** Build a spacecraft model with best lighting of solar array.
  112.      * <p>
  113.      * This constructor builds an instance that completely ignores lift
  114.      * in atmospheric drag (the value of lift coefficient is set to zero,
  115.      * and there are no {@link ParameterDriver drivers} to change it).
  116.      * </p>
  117.      * <p>
  118.      * Solar arrays orientation will be such that at each time the Sun direction
  119.      * will always be in the solar array meridian plane defined by solar array
  120.      * rotation axis and solar array normal vector.
  121.      * </p>
  122.      * @param xLength length of the body along its X axis (m)
  123.      * @param yLength length of the body along its Y axis (m)
  124.      * @param zLength length of the body along its Z axis (m)
  125.      * @param sun sun model
  126.      * @param solarArrayArea area of the solar array (m²)
  127.      * @param solarArrayAxis solar array rotation axis in satellite frame
  128.      * @param dragCoeff drag coefficient (used only for drag)
  129.      * @param absorptionCoeff absorption coefficient between 0.0 an 1.0
  130.      * (used only for radiation pressure)
  131.      * @param reflectionCoeff specular reflection coefficient between 0.0 an 1.0
  132.      * (used only for radiation pressure)
  133.      */
  134.     public BoxAndSolarArraySpacecraft(final double xLength, final double yLength,
  135.                                       final double zLength,
  136.                                       final PVCoordinatesProvider sun, final double solarArrayArea,
  137.                                       final Vector3D solarArrayAxis,
  138.                                       final double dragCoeff,
  139.                                       final double absorptionCoeff,
  140.                                       final double reflectionCoeff) {
  141.         this(simpleBoxFacets(xLength, yLength, zLength), sun, solarArrayArea, solarArrayAxis,
  142.              dragCoeff, 0.0, false,
  143.              absorptionCoeff, reflectionCoeff);
  144.     }

  145.     /** Build a spacecraft model with best lighting of solar array.
  146.      * <p>
  147.      * Solar arrays orientation will be such that at each time the Sun direction
  148.      * will always be in the solar array meridian plane defined by solar array
  149.      * rotation axis and solar array normal vector.
  150.      * </p>
  151.      * @param xLength length of the body along its X axis (m)
  152.      * @param yLength length of the body along its Y axis (m)
  153.      * @param zLength length of the body along its Z axis (m)
  154.      * @param sun sun model
  155.      * @param solarArrayArea area of the solar array (m²)
  156.      * @param solarArrayAxis solar array rotation axis in satellite frame
  157.      * @param dragCoeff drag coefficient (used only for drag)
  158.      * @param liftRatio lift ratio (proportion between 0 and 1 of atmosphere modecules
  159.      * that will experience specular reflection when hitting spacecraft instead
  160.      * of experiencing diffuse reflection, hence producing lift)
  161.      * @param absorptionCoeff absorption coefficient between 0.0 an 1.0
  162.      * (used only for radiation pressure)
  163.      * @param reflectionCoeff specular reflection coefficient between 0.0 an 1.0
  164.      * (used only for radiation pressure)
  165.      * @since 9.0
  166.      */
  167.     public BoxAndSolarArraySpacecraft(final double xLength, final double yLength,
  168.                                       final double zLength,
  169.                                       final PVCoordinatesProvider sun, final double solarArrayArea,
  170.                                       final Vector3D solarArrayAxis,
  171.                                       final double dragCoeff, final double liftRatio,
  172.                                       final double absorptionCoeff,
  173.                                       final double reflectionCoeff) {
  174.         this(simpleBoxFacets(xLength, yLength, zLength), sun, solarArrayArea, solarArrayAxis,
  175.              dragCoeff, liftRatio,
  176.              absorptionCoeff, reflectionCoeff);
  177.     }

  178.     /** Build a spacecraft model with best lighting of solar array.
  179.      * <p>
  180.      * The spacecraft body is described by an array of surface vectors. Each facet of
  181.      * the body is described by a vector normal to the facet (pointing outward of the spacecraft)
  182.      * and whose norm is the surface area in m².
  183.      * </p>
  184.      * <p>
  185.      * Solar arrays orientation will be such that at each time the Sun direction
  186.      * will always be in the solar array meridian plane defined by solar array
  187.      * rotation axis and solar array normal vector.
  188.      * </p>
  189.      * @param facets body facets (only the facets with strictly positive area will be stored)
  190.      * @param sun sun model
  191.      * @param solarArrayArea area of the solar array (m²)
  192.      * @param solarArrayAxis solar array rotation axis in satellite frame
  193.      * @param dragCoeff drag coefficient (used only for drag)
  194.      * @param absorptionCoeff absorption coefficient between 0.0 an 1.0
  195.      * (used only for radiation pressure)
  196.      * @param reflectionCoeff specular reflection coefficient between 0.0 an 1.0
  197.      * (used only for radiation pressure)
  198.      */
  199.     public BoxAndSolarArraySpacecraft(final Facet[] facets,
  200.                                       final PVCoordinatesProvider sun, final double solarArrayArea,
  201.                                       final Vector3D solarArrayAxis,
  202.                                       final double dragCoeff,
  203.                                       final double absorptionCoeff,
  204.                                       final double reflectionCoeff) {
  205.         this(facets, sun, solarArrayArea, solarArrayAxis,
  206.              dragCoeff, 0.0, false,
  207.              absorptionCoeff, reflectionCoeff);
  208.     }

  209.     /** Build a spacecraft model with best lighting of solar array.
  210.      * <p>
  211.      * The spacecraft body is described by an array of surface vectors. Each facet of
  212.      * the body is described by a vector normal to the facet (pointing outward of the spacecraft)
  213.      * and whose norm is the surface area in m².
  214.      * </p>
  215.      * <p>
  216.      * Solar arrays orientation will be such that at each time the Sun direction
  217.      * will always be in the solar array meridian plane defined by solar array
  218.      * rotation axis and solar array normal vector.
  219.      * </p>
  220.      * @param facets body facets (only the facets with strictly positive area will be stored)
  221.      * @param sun sun model
  222.      * @param solarArrayArea area of the solar array (m²)
  223.      * @param solarArrayAxis solar array rotation axis in satellite frame
  224.      * @param dragCoeff drag coefficient (used only for drag)
  225.      * @param liftRatio lift ratio (proportion between 0 and 1 of atmosphere modecules
  226.      * that will experience specular reflection when hitting spacecraft instead
  227.      * of experiencing diffuse reflection, hence producing lift)
  228.      * @param absorptionCoeff absorption coefficient between 0.0 an 1.0
  229.      * (used only for radiation pressure)
  230.      * @param reflectionCoeff specular reflection coefficient between 0.0 an 1.0
  231.      * (used only for radiation pressure)
  232.      * @since 9.0
  233.      */
  234.     public BoxAndSolarArraySpacecraft(final Facet[] facets,
  235.                                       final PVCoordinatesProvider sun, final double solarArrayArea,
  236.                                       final Vector3D solarArrayAxis,
  237.                                       final double dragCoeff, final double liftRatio,
  238.                                       final double absorptionCoeff,
  239.                                       final double reflectionCoeff) {
  240.         this(facets, sun, solarArrayArea, solarArrayAxis,
  241.              dragCoeff, liftRatio, true,
  242.              absorptionCoeff, reflectionCoeff);
  243.     }

  244.     /** Build a spacecraft model with best lighting of solar array.
  245.      * <p>
  246.      * The spacecraft body is described by an array of surface vectors. Each facet of
  247.      * the body is described by a vector normal to the facet (pointing outward of the spacecraft)
  248.      * and whose norm is the surface area in m².
  249.      * </p>
  250.      * <p>
  251.      * Solar arrays orientation will be such that at each time the Sun direction
  252.      * will always be in the solar array meridian plane defined by solar array
  253.      * rotation axis and solar array normal vector.
  254.      * </p>
  255.      * @param facets body facets (only the facets with strictly positive area will be stored)
  256.      * @param sun sun model
  257.      * @param solarArrayArea area of the solar array (m²)
  258.      * @param solarArrayAxis solar array rotation axis in satellite frame
  259.      * @param dragCoeff drag coefficient (used only for drag)
  260.      * @param liftRatio lift ratio (proportion between 0 and 1 of atmosphere modecules
  261.      * that will experience specular reflection when hitting spacecraft instead
  262.      * of experiencing diffuse reflection, hence producing lift)
  263.      * @param useLift if true, lift should be used, otherwise it is completely ignored
  264.      * @param absorptionCoeff absorption coefficient between 0.0 an 1.0
  265.      * (used only for radiation pressure)
  266.      * @param reflectionCoeff specular reflection coefficient between 0.0 an 1.0
  267.      * (used only for radiation pressure)
  268.      * @since 9.0
  269.      */
  270.     private BoxAndSolarArraySpacecraft(final Facet[] facets,
  271.                                        final PVCoordinatesProvider sun, final double solarArrayArea,
  272.                                        final Vector3D solarArrayAxis,
  273.                                        final double dragCoeff, final double liftRatio, final boolean useLift,
  274.                                        final double absorptionCoeff,
  275.                                        final double reflectionCoeff) {

  276.         // drag
  277.         dragParameterDriver = buildDragParameterDriver(dragCoeff);
  278.         liftParameterDriver = useLift ? buildLiftParameterDriver(liftRatio) : null;

  279.         // radiation pressure
  280.         absorptionParameterDriver = buildAbsorptionParameterDriver(absorptionCoeff);
  281.         reflectionParameterDriver = buildReflectionParameterDriver(reflectionCoeff);

  282.         factory = new DSFactory(1, 1);

  283.         this.facets = filter(facets);

  284.         this.sun            = sun;
  285.         this.solarArrayArea = solarArrayArea;
  286.         this.referenceDate  = null;
  287.         this.rotationRate   = 0;

  288.         this.saZ = solarArrayAxis.normalize();
  289.         this.saY = null;
  290.         this.saX = null;

  291.     }

  292.     /** Build a spacecraft model with linear rotation of solar array.
  293.      * <p>
  294.      * Solar arrays orientation will be a regular rotation from the
  295.      * reference orientation at reference date and using a constant
  296.      * rotation rate.
  297.      * </p>
  298.      * @param xLength length of the body along its X axis (m)
  299.      * @param yLength length of the body along its Y axis (m)
  300.      * @param zLength length of the body along its Z axis (m)
  301.      * @param sun sun model
  302.      * @param solarArrayArea area of the solar array (m²)
  303.      * @param solarArrayAxis solar array rotation axis in satellite frame
  304.      * @param referenceDate reference date for the solar array rotation
  305.      * @param referenceNormal direction of the solar array normal at reference date
  306.      * in spacecraft frame
  307.      * @param rotationRate rotation rate of the solar array, may be 0 (rad/s)
  308.      * @param dragCoeff drag coefficient (used only for drag)
  309.      * @param absorptionCoeff absorption coefficient between 0.0 an 1.0
  310.      * (used only for radiation pressure)
  311.      * @param reflectionCoeff specular reflection coefficient between 0.0 an 1.0
  312.      * (used only for radiation pressure)
  313.      */
  314.     public BoxAndSolarArraySpacecraft(final double xLength, final double yLength,
  315.                                       final double zLength,
  316.                                       final PVCoordinatesProvider sun, final double solarArrayArea,
  317.                                       final Vector3D solarArrayAxis,
  318.                                       final AbsoluteDate referenceDate,
  319.                                       final Vector3D referenceNormal,
  320.                                       final double rotationRate,
  321.                                       final double dragCoeff,
  322.                                       final double absorptionCoeff,
  323.                                       final double reflectionCoeff) {
  324.         this(simpleBoxFacets(xLength, yLength, zLength), sun, solarArrayArea, solarArrayAxis,
  325.              referenceDate, referenceNormal, rotationRate,
  326.              dragCoeff, 0.0, false,
  327.              absorptionCoeff, reflectionCoeff);
  328.     }

  329.     /** Build a spacecraft model with linear rotation of solar array.
  330.      * <p>
  331.      * Solar arrays orientation will be a regular rotation from the
  332.      * reference orientation at reference date and using a constant
  333.      * rotation rate.
  334.      * </p>
  335.      * @param xLength length of the body along its X axis (m)
  336.      * @param yLength length of the body along its Y axis (m)
  337.      * @param zLength length of the body along its Z axis (m)
  338.      * @param sun sun model
  339.      * @param solarArrayArea area of the solar array (m²)
  340.      * @param solarArrayAxis solar array rotation axis in satellite frame
  341.      * @param referenceDate reference date for the solar array rotation
  342.      * @param referenceNormal direction of the solar array normal at reference date
  343.      * in spacecraft frame
  344.      * @param rotationRate rotation rate of the solar array, may be 0 (rad/s)
  345.      * @param dragCoeff drag coefficient (used only for drag)
  346.      * @param liftRatio lift ratio (proportion between 0 and 1 of atmosphere modecules
  347.      * that will experience specular reflection when hitting spacecraft instead
  348.      * of experiencing diffuse reflection, hence producing lift)
  349.      * @param absorptionCoeff absorption coefficient between 0.0 an 1.0
  350.      * (used only for radiation pressure)
  351.      * @param reflectionCoeff specular reflection coefficient between 0.0 an 1.0
  352.      * (used only for radiation pressure)
  353.      * @since 9.0
  354.      */
  355.     public BoxAndSolarArraySpacecraft(final double xLength, final double yLength,
  356.                                       final double zLength,
  357.                                       final PVCoordinatesProvider sun, final double solarArrayArea,
  358.                                       final Vector3D solarArrayAxis,
  359.                                       final AbsoluteDate referenceDate,
  360.                                       final Vector3D referenceNormal,
  361.                                       final double rotationRate,
  362.                                       final double dragCoeff, final double liftRatio,
  363.                                       final double absorptionCoeff,
  364.                                       final double reflectionCoeff) {
  365.         this(simpleBoxFacets(xLength, yLength, zLength), sun, solarArrayArea, solarArrayAxis,
  366.              referenceDate, referenceNormal, rotationRate,
  367.              dragCoeff, liftRatio, true,
  368.              absorptionCoeff, reflectionCoeff);
  369.     }

  370.     /** Build a spacecraft model with linear rotation of solar array.
  371.      * <p>
  372.      * The spacecraft body is described by an array of surface vectors. Each facet of
  373.      * the body is described by a vector normal to the facet (pointing outward of the spacecraft)
  374.      * and whose norm is the surface area in m².
  375.      * </p>
  376.      * <p>
  377.      * Solar arrays orientation will be a regular rotation from the
  378.      * reference orientation at reference date and using a constant
  379.      * rotation rate.
  380.      * </p>
  381.      * @param facets body facets (only the facets with strictly positive area will be stored)
  382.      * @param sun sun model
  383.      * @param solarArrayArea area of the solar array (m²)
  384.      * @param solarArrayAxis solar array rotation axis in satellite frame
  385.      * @param referenceDate reference date for the solar array rotation
  386.      * @param referenceNormal direction of the solar array normal at reference date
  387.      * in spacecraft frame
  388.      * @param rotationRate rotation rate of the solar array, may be 0 (rad/s)
  389.      * @param dragCoeff drag coefficient (used only for drag)
  390.      * @param absorptionCoeff absorption coefficient between 0.0 an 1.0
  391.      * (used only for radiation pressure)
  392.      * @param reflectionCoeff specular reflection coefficient between 0.0 an 1.0
  393.      * (used only for radiation pressure)
  394.      */
  395.     public BoxAndSolarArraySpacecraft(final Facet[] facets,
  396.                                       final PVCoordinatesProvider sun, final double solarArrayArea,
  397.                                       final Vector3D solarArrayAxis,
  398.                                       final AbsoluteDate referenceDate,
  399.                                       final Vector3D referenceNormal,
  400.                                       final double rotationRate,
  401.                                       final double dragCoeff,
  402.                                       final double absorptionCoeff,
  403.                                       final double reflectionCoeff) {
  404.         this(facets, sun, solarArrayArea, solarArrayAxis, referenceDate, referenceNormal, rotationRate,
  405.              dragCoeff, 0.0, false,
  406.              absorptionCoeff, reflectionCoeff);
  407.     }

  408.     /** Build a spacecraft model with linear rotation of solar array.
  409.      * <p>
  410.      * The spacecraft body is described by an array of surface vectors. Each facet of
  411.      * the body is described by a vector normal to the facet (pointing outward of the spacecraft)
  412.      * and whose norm is the surface area in m².
  413.      * </p>
  414.      * <p>
  415.      * Solar arrays orientation will be a regular rotation from the
  416.      * reference orientation at reference date and using a constant
  417.      * rotation rate.
  418.      * </p>
  419.      * @param facets body facets (only the facets with strictly positive area will be stored)
  420.      * @param sun sun model
  421.      * @param solarArrayArea area of the solar array (m²)
  422.      * @param solarArrayAxis solar array rotation axis in satellite frame
  423.      * @param referenceDate reference date for the solar array rotation
  424.      * @param referenceNormal direction of the solar array normal at reference date
  425.      * in spacecraft frame
  426.      * @param rotationRate rotation rate of the solar array, may be 0 (rad/s)
  427.      * @param dragCoeff drag coefficient (used only for drag)
  428.      * @param liftRatio lift ratio (proportion between 0 and 1 of atmosphere modecules
  429.      * that will experience specular reflection when hitting spacecraft instead
  430.      * of experiencing diffuse reflection, hence producing lift)
  431.      * @param absorptionCoeff absorption coefficient between 0.0 an 1.0
  432.      * (used only for radiation pressure)
  433.      * @param reflectionCoeff specular reflection coefficient between 0.0 an 1.0
  434.      * (used only for radiation pressure)
  435.      * @since 9.0
  436.      */
  437.     public BoxAndSolarArraySpacecraft(final Facet[] facets,
  438.                                       final PVCoordinatesProvider sun, final double solarArrayArea,
  439.                                       final Vector3D solarArrayAxis,
  440.                                       final AbsoluteDate referenceDate,
  441.                                       final Vector3D referenceNormal,
  442.                                       final double rotationRate,
  443.                                       final double dragCoeff, final double liftRatio,
  444.                                       final double absorptionCoeff,
  445.                                       final double reflectionCoeff) {
  446.         this(facets, sun, solarArrayArea, solarArrayAxis, referenceDate, referenceNormal, rotationRate,
  447.              dragCoeff, liftRatio, true,
  448.              absorptionCoeff, reflectionCoeff);
  449.     }

  450.     /** Build a spacecraft model with linear rotation of solar array.
  451.      * <p>
  452.      * The spacecraft body is described by an array of surface vectors. Each facet of
  453.      * the body is described by a vector normal to the facet (pointing outward of the spacecraft)
  454.      * and whose norm is the surface area in m².
  455.      * </p>
  456.      * <p>
  457.      * Solar arrays orientation will be a regular rotation from the
  458.      * reference orientation at reference date and using a constant
  459.      * rotation rate.
  460.      * </p>
  461.      * @param facets body facets (only the facets with strictly positive area will be stored)
  462.      * @param sun sun model
  463.      * @param solarArrayArea area of the solar array (m²)
  464.      * @param solarArrayAxis solar array rotation axis in satellite frame
  465.      * @param referenceDate reference date for the solar array rotation
  466.      * @param referenceNormal direction of the solar array normal at reference date
  467.      * in spacecraft frame
  468.      * @param rotationRate rotation rate of the solar array, may be 0 (rad/s)
  469.      * @param dragCoeff drag coefficient (used only for drag)
  470.      * @param liftRatio lift ratio (proportion between 0 and 1 of atmosphere modecules
  471.      * that will experience specular reflection when hitting spacecraft instead
  472.      * of experiencing diffuse reflection, hence producing lift)
  473.      * @param useLift if true, lift should be used, otherwise it is completely ignored
  474.      * @param absorptionCoeff absorption coefficient between 0.0 an 1.0
  475.      * (used only for radiation pressure)
  476.      * @param reflectionCoeff specular reflection coefficient between 0.0 an 1.0
  477.      * (used only for radiation pressure)
  478.      * @since 9.0
  479.      */
  480.     private BoxAndSolarArraySpacecraft(final Facet[] facets,
  481.                                        final PVCoordinatesProvider sun, final double solarArrayArea,
  482.                                        final Vector3D solarArrayAxis,
  483.                                        final AbsoluteDate referenceDate,
  484.                                        final Vector3D referenceNormal,
  485.                                        final double rotationRate,
  486.                                        final double dragCoeff, final double liftRatio, final boolean useLift,
  487.                                        final double absorptionCoeff,
  488.                                        final double reflectionCoeff) {

  489.         // drag
  490.         dragParameterDriver = buildDragParameterDriver(dragCoeff);
  491.         liftParameterDriver = useLift ? buildLiftParameterDriver(liftRatio) : null;

  492.         // radiation pressure
  493.         absorptionParameterDriver = buildAbsorptionParameterDriver(absorptionCoeff);
  494.         reflectionParameterDriver = buildReflectionParameterDriver(reflectionCoeff);

  495.         factory = new DSFactory(1, 1);


  496.         this.facets = filter(facets.clone());

  497.         this.sun            = sun;
  498.         this.solarArrayArea = solarArrayArea;
  499.         this.referenceDate  = referenceDate;
  500.         this.rotationRate   = rotationRate;

  501.         this.saZ = solarArrayAxis.normalize();
  502.         this.saY = Vector3D.crossProduct(saZ, referenceNormal).normalize();
  503.         this.saX = Vector3D.crossProduct(saY, saZ);

  504.     }

  505.     /** Build the parameter driver for drag coefficient.
  506.      * @param coeff drag coefficient
  507.      * @return parameter driver for drag coefficient
  508.      * @since 9.0
  509.      */
  510.     private ParameterDriver buildDragParameterDriver(final double coeff) {
  511.         try {
  512.             return new ParameterDriver(DragSensitive.DRAG_COEFFICIENT,
  513.                                        coeff, SCALE, 0.0, Double.POSITIVE_INFINITY);
  514.         } catch (OrekitException oe) {
  515.             // this should never happen
  516.             throw new OrekitInternalError(oe);
  517.         }
  518.     }

  519.     /** Build the parameter driver for lift coefficient.
  520.      * @param coeff lift coefficient
  521.      * @return parameter driver for lift coefficient
  522.      * @since 9.0
  523.      */
  524.     private ParameterDriver buildLiftParameterDriver(final double coeff) {
  525.         try {
  526.             return new ParameterDriver(DragSensitive.LIFT_RATIO, coeff, SCALE, 0.0, 1.0);
  527.         } catch (OrekitException oe) {
  528.             // this should never happen
  529.             throw new OrekitInternalError(oe);
  530.         }
  531.     }

  532.     /** Build the parameter driver for absorption coefficient.
  533.      * @param coeff absorption coefficient
  534.      * @return parameter driver for absorption coefficient
  535.      * @since 9.0
  536.      */
  537.     private ParameterDriver buildAbsorptionParameterDriver(final double coeff) {
  538.         try {
  539.             return new ParameterDriver(RadiationSensitive.ABSORPTION_COEFFICIENT, coeff, SCALE, 0.0, 1.0);
  540.         } catch (OrekitException oe) {
  541.             // this should never happen
  542.             throw new OrekitInternalError(oe);
  543.         }
  544.     }

  545.     /** Build the parameter driver for reflection coefficient.
  546.      * @param coeff absorption coefficient
  547.      * @return parameter driver for reflection coefficient
  548.      * @since 9.0
  549.      */
  550.     private ParameterDriver buildReflectionParameterDriver(final double coeff) {
  551.         try {
  552.             return new ParameterDriver(RadiationSensitive.REFLECTION_COEFFICIENT, coeff, SCALE, 0.0, 1.0);
  553.         } catch (OrekitException oe) {
  554.             // this should never happen
  555.             throw new OrekitInternalError(oe);
  556.         }
  557.     }

  558.     /** {@inheritDoc} */
  559.     @Override
  560.     public ParameterDriver[] getDragParametersDrivers() {
  561.         return liftParameterDriver == null ?
  562.                new ParameterDriver[] {
  563.                    dragParameterDriver
  564.                } : new ParameterDriver[] {
  565.                    dragParameterDriver, liftParameterDriver
  566.                };
  567.     }

  568.     /** {@inheritDoc} */
  569.     @Override
  570.     public ParameterDriver[] getRadiationParametersDrivers() {
  571.         return new ParameterDriver[] {
  572.             absorptionParameterDriver, reflectionParameterDriver
  573.         };
  574.     }

  575.     /** Get solar array normal in spacecraft frame.
  576.      * @param date current date
  577.      * @param frame inertial reference frame for state (both orbit and attitude)
  578.      * @param position position of spacecraft in reference frame
  579.      * @param rotation orientation (attitude) of the spacecraft with respect to reference frame
  580.      * @return solar array normal in spacecraft frame
  581.      */
  582.     public synchronized Vector3D getNormal(final AbsoluteDate date, final Frame frame,
  583.                                            final Vector3D position, final Rotation rotation) {

  584.         if (referenceDate != null) {
  585.             // use a simple rotation at fixed rate
  586.             final double alpha = rotationRate * date.durationFrom(referenceDate);
  587.             return new Vector3D(FastMath.cos(alpha), saX, FastMath.sin(alpha), saY);
  588.         }

  589.         // compute orientation for best lighting
  590.         final Vector3D sunInert = sun.getPVCoordinates(date, frame).getPosition().subtract(position).normalize();
  591.         final Vector3D sunSpacecraft = rotation.applyTo(sunInert);
  592.         final double d = Vector3D.dotProduct(sunSpacecraft, saZ);
  593.         final double f = 1 - d * d;
  594.         if (f < Precision.EPSILON) {
  595.             // extremely rare case: the sun is along solar array rotation axis
  596.             // (there will not be much output power ...)
  597.             // we set up an arbitrary normal
  598.             return saZ.orthogonal();
  599.         }

  600.         final double s = 1.0 / FastMath.sqrt(f);
  601.         return new Vector3D(s, sunSpacecraft, -s * d, saZ);

  602.     }

  603.     /** Get solar array normal in spacecraft frame.
  604.      * @param date current date
  605.      * @param frame inertial reference frame for state (both orbit and attitude)
  606.      * @param position position of spacecraft in reference frame
  607.      * @param rotation orientation (attitude) of the spacecraft with respect to reference frame
  608.      * @return solar array normal in spacecraft frame
  609.      * @param <T> type of the field elements
  610.      */
  611.     public synchronized <T extends RealFieldElement<T>> FieldVector3D<T> getNormal(final FieldAbsoluteDate<T> date,
  612.                                                                                    final Frame frame,
  613.                                                                                    final FieldVector3D<T> position,
  614.                                                                                    final FieldRotation<T> rotation) {

  615.         if (referenceDate != null) {
  616.             // use a simple rotation at fixed rate
  617.             final T alpha = date.durationFrom(referenceDate).multiply(rotationRate);
  618.             return new FieldVector3D<>(alpha.cos(), saX, alpha.sin(), saY);
  619.         }

  620.         // compute orientation for best lighting
  621.         final FieldVector3D<T> sunInert = position.subtract(sun.getPVCoordinates(date.toAbsoluteDate(), frame).getPosition()).negate().normalize();
  622.         final FieldVector3D<T> sunSpacecraft = rotation.applyTo(sunInert);
  623.         final T d = FieldVector3D.dotProduct(sunSpacecraft, saZ);
  624.         final T f = d.multiply(d).subtract(1).negate();
  625.         if (f.getReal() < Precision.EPSILON) {
  626.             // extremely rare case: the sun is along solar array rotation axis
  627.             // (there will not be much output power ...)
  628.             // we set up an arbitrary normal
  629.             return new FieldVector3D<>(f.getField(), saZ.orthogonal());
  630.         }

  631.         final T s = f.sqrt().reciprocal();
  632.         return new FieldVector3D<>(s, sunSpacecraft,
  633.                                    s.multiply(d).negate(), new FieldVector3D<>(date.getField(), saZ));

  634.     }

  635.     /** Get solar array normal in spacecraft frame.
  636.      * @param date current date
  637.      * @param frame inertial reference frame for state (both orbit and attitude)
  638.      * @param position position of spacecraft in reference frame
  639.      * @param rotation orientation (attitude) of the spacecraft with respect to reference frame
  640.      * @return solar array normal in spacecraft frame
  641.      */
  642.     public synchronized FieldVector3D<DerivativeStructure> getNormal(final AbsoluteDate date, final Frame frame,
  643.                                                                      final FieldVector3D<DerivativeStructure> position,
  644.                                                                      final FieldRotation<DerivativeStructure> rotation) {

  645.         final DerivativeStructure zero = position.getX().getField().getZero();

  646.         if (referenceDate != null) {
  647.             // use a simple rotation at fixed rate
  648.             final DerivativeStructure alpha = zero.add(rotationRate * date.durationFrom(referenceDate));
  649.             return new FieldVector3D<>(alpha.cos(), saX, alpha.sin(), saY);
  650.         }

  651.         // compute orientation for best lighting
  652.         final FieldVector3D<DerivativeStructure> sunInert =
  653.                 position.subtract(sun.getPVCoordinates(date, frame).getPosition()).negate().normalize();
  654.         final FieldVector3D<DerivativeStructure> sunSpacecraft = rotation.applyTo(sunInert);
  655.         final DerivativeStructure d = FieldVector3D.dotProduct(sunSpacecraft, saZ);
  656.         final DerivativeStructure f = d.multiply(d).subtract(1).negate();
  657.         if (f.getValue() < Precision.EPSILON) {
  658.             // extremely rare case: the sun is along solar array rotation axis
  659.             // (there will not be much output power ...)
  660.             // we set up an arbitrary normal
  661.             return new FieldVector3D<>(position.getX().getField(), saZ.orthogonal());
  662.         }

  663.         final DerivativeStructure s = f.sqrt().reciprocal();
  664.         return new FieldVector3D<>(s, sunSpacecraft,
  665.                                    s.multiply(d).negate(), new FieldVector3D<>(zero.getField(), saZ));

  666.     }


  667.     /** {@inheritDoc} */
  668.     @Override
  669.     public Vector3D dragAcceleration(final AbsoluteDate date, final Frame frame, final Vector3D position,
  670.                                      final Rotation rotation, final double mass,
  671.                                      final double density, final Vector3D relativeVelocity,
  672.                                      final double[] parameters) {

  673.         final double dragCoeff = parameters[0];
  674.         final double liftRatio = liftParameterDriver == null ? 0.0 : parameters[1];

  675.         // relative velocity in spacecraft frame
  676.         final double   vNorm2 = relativeVelocity.getNormSq();
  677.         final double   vNorm  = FastMath.sqrt(vNorm2);
  678.         final Vector3D vDir   = rotation.applyTo(relativeVelocity.scalarMultiply(1.0 / vNorm));
  679.         final double   coeff  = density * dragCoeff * vNorm2 / (2.0 * mass);
  680.         final double   oMr    = 1 - liftRatio;

  681.         // solar array contribution
  682.         final Vector3D frontNormal = getNormal(date, frame, position, rotation);
  683.         final double   s           = coeff * solarArrayArea * Vector3D.dotProduct(frontNormal, vDir);
  684.         Vector3D acceleration = new Vector3D(oMr * FastMath.abs(s), vDir,
  685.                                              liftRatio * s * 2,     frontNormal);

  686.         // body facets contribution
  687.         for (final Facet facet : facets) {
  688.             final double dot = Vector3D.dotProduct(facet.getNormal(), vDir);
  689.             if (dot < 0) {
  690.                 // the facet intercepts the incoming flux
  691.                 final double f = coeff * facet.getArea() * dot;
  692.                 acceleration = new Vector3D(1,                     acceleration,
  693.                                             oMr * FastMath.abs(f), vDir,
  694.                                             liftRatio * f * 2,     facet.getNormal());
  695.             }
  696.         }

  697.         // convert back to inertial frame
  698.         return rotation.applyInverseTo(acceleration);

  699.     }

  700.     /** {@inheritDoc} */
  701.     @Override
  702.     public FieldVector3D<DerivativeStructure> dragAcceleration(final AbsoluteDate date, final Frame frame,
  703.                                                                final Vector3D position, final Rotation rotation,
  704.                                                                final double mass, final  double density,
  705.                                                                final Vector3D relativeVelocity,
  706.                                                                final double[] parameters,
  707.                                                                final String paramName) {

  708.         final DerivativeStructure dragCoeffDS;
  709.         final DerivativeStructure liftRatioDS;
  710.         final DerivativeStructure oMrDS;
  711.         final Field<DerivativeStructure> field = factory.getDerivativeField();
  712.         if (dragParameterDriver.getName().equals(paramName)) {
  713.             final double liftRatio = liftParameterDriver == null ? 0.0 : parameters[1];
  714.             dragCoeffDS = factory.variable(0, parameters[0]);
  715.             liftRatioDS = factory.constant(liftRatio);
  716.             oMrDS       = factory.constant(1 - liftRatio);
  717.         } else if (liftParameterDriver != null && liftParameterDriver.getName().equals(paramName)) {
  718.             dragCoeffDS = factory.constant(parameters[0]);
  719.             liftRatioDS = factory.variable(0, parameters[1]);
  720.             oMrDS       = liftRatioDS.negate().add(1);
  721.         } else {
  722.             if (liftParameterDriver == null) {
  723.                 throw new OrekitException(OrekitMessages.UNSUPPORTED_PARAMETER_NAME, paramName,
  724.                                           dragParameterDriver.getName());
  725.             } else {
  726.                 throw new OrekitException(OrekitMessages.UNSUPPORTED_PARAMETER_NAME, paramName,
  727.                                           dragParameterDriver.getName(), liftParameterDriver.getName());
  728.             }
  729.         }

  730.         // relative velocity in spacecraft frame
  731.         final double                             vNorm2 = relativeVelocity.getNormSq();
  732.         final double                             vNorm  = FastMath.sqrt(vNorm2);
  733.         final FieldVector3D<DerivativeStructure> vDir   = new FieldVector3D<>(field,
  734.                                                                               rotation.applyTo(relativeVelocity.scalarMultiply(1.0 / vNorm)));
  735.         final DerivativeStructure                coeff  = dragCoeffDS.multiply(0.5 * density * vNorm2 / mass);

  736.         // solar array facet contribution
  737.         final FieldVector3D<DerivativeStructure> frontNormal = new FieldVector3D<>(field,
  738.                                                                                    getNormal(date, frame, position, rotation));
  739.         final DerivativeStructure                s           = coeff.
  740.                                                                multiply(solarArrayArea).
  741.                                                                multiply(FieldVector3D.dotProduct(frontNormal, vDir));
  742.         FieldVector3D<DerivativeStructure> acceleration = new FieldVector3D<>(s.abs().multiply(oMrDS), vDir,
  743.                                                                               s.multiply(liftRatioDS).multiply(2), frontNormal);

  744.         // body facets contribution
  745.         for (final Facet facet : facets) {
  746.             final DerivativeStructure dot = FieldVector3D.dotProduct(facet.getNormal(), vDir);
  747.             if (dot.getValue() < 0) {
  748.                 // the facet intercepts the incoming flux
  749.                 final DerivativeStructure f = coeff.multiply(facet.getArea()).multiply(dot);
  750.                 acceleration = new FieldVector3D<>(field.getOne(),          acceleration,
  751.                                                    f.abs().multiply(oMrDS), vDir,
  752.                                                    f.multiply(liftRatioDS).multiply(2), new FieldVector3D<>(field, facet.getNormal()));
  753.             }
  754.         }

  755.         // convert back to inertial frame
  756.         return new FieldRotation<>(field, rotation).applyInverseTo(acceleration);

  757.     }

  758.     /** {@inheritDoc} */
  759.     @Override
  760.     public Vector3D radiationPressureAcceleration(final AbsoluteDate date, final Frame frame, final Vector3D position,
  761.                                                   final Rotation rotation, final double mass, final Vector3D flux,
  762.                                                   final double[] parameters) {

  763.         if (flux.getNormSq() < Precision.SAFE_MIN) {
  764.             // null illumination (we are probably in umbra)
  765.             return Vector3D.ZERO;
  766.         }

  767.         // radiation flux in spacecraft frame
  768.         final Vector3D fluxSat = rotation.applyTo(flux);

  769.         // solar array contribution
  770.         Vector3D normal = getNormal(date, frame, position, rotation);
  771.         double dot = Vector3D.dotProduct(normal, fluxSat);
  772.         if (dot > 0) {
  773.             // the solar array is illuminated backward,
  774.             // fix signs to compute contribution correctly
  775.             dot   = -dot;
  776.             normal = normal.negate();
  777.         }
  778.         Vector3D force = facetRadiationAcceleration(normal, solarArrayArea, fluxSat, dot, parameters);

  779.         // body facets contribution
  780.         for (final Facet bodyFacet : facets) {
  781.             normal = bodyFacet.getNormal();
  782.             dot = Vector3D.dotProduct(normal, fluxSat);
  783.             if (dot < 0) {
  784.                 // the facet intercepts the incoming flux
  785.                 force = force.add(facetRadiationAcceleration(normal, bodyFacet.getArea(), fluxSat, dot, parameters));
  786.             }
  787.         }

  788.         // convert to inertial frame
  789.         return rotation.applyInverseTo(new Vector3D(1.0 / mass, force));

  790.     }

  791.     /** {@inheritDoc} */
  792.     @Override
  793.     public FieldVector3D<DerivativeStructure> radiationPressureAcceleration(final AbsoluteDate date, final Frame frame,
  794.                                                                             final Vector3D position, final Rotation rotation,
  795.                                                                             final double mass, final Vector3D flux,
  796.                                                                             final double[] parameters,
  797.                                                                             final String paramName) {

  798.         if (flux.getNormSq() < Precision.SAFE_MIN) {
  799.             // null illumination (we are probably in umbra)
  800.             return FieldVector3D.getZero(factory.getDerivativeField());
  801.         }

  802.         final DerivativeStructure absorptionCoeffDS;
  803.         final DerivativeStructure specularReflectionCoeffDS;
  804.         if (ABSORPTION_COEFFICIENT.equals(paramName)) {
  805.             absorptionCoeffDS         = factory.variable(0, parameters[0]);
  806.             specularReflectionCoeffDS = factory.constant(parameters[1]);
  807.         } else if (REFLECTION_COEFFICIENT.equals(paramName)) {
  808.             absorptionCoeffDS         = factory.constant(parameters[0]);
  809.             specularReflectionCoeffDS = factory.variable(0, parameters[1]);
  810.         } else {
  811.             throw new OrekitException(OrekitMessages.UNSUPPORTED_PARAMETER_NAME, paramName,
  812.                                       ABSORPTION_COEFFICIENT + ", " + REFLECTION_COEFFICIENT);
  813.         }
  814.         final DerivativeStructure diffuseReflectionCoeffDS =
  815.                 absorptionCoeffDS.add(specularReflectionCoeffDS).subtract(1).negate();

  816.         // radiation flux in spacecraft frame
  817.         final Vector3D fluxSat = rotation.applyTo(flux);

  818.         // solar array contribution
  819.         Vector3D normal = getNormal(date, frame, position, rotation);
  820.         double dot = Vector3D.dotProduct(normal, fluxSat);
  821.         if (dot > 0) {
  822.             // the solar array is illuminated backward,
  823.             // fix signs to compute contribution correctly
  824.             dot   = -dot;
  825.             normal = normal.negate();
  826.         }
  827.         FieldVector3D<DerivativeStructure> force =
  828.                 facetRadiationAcceleration(normal, solarArrayArea, fluxSat, dot,
  829.                                            specularReflectionCoeffDS, diffuseReflectionCoeffDS);

  830.         // body facets contribution
  831.         for (final Facet bodyFacet : facets) {
  832.             normal = bodyFacet.getNormal();
  833.             dot = Vector3D.dotProduct(normal, fluxSat);
  834.             if (dot < 0) {
  835.                 // the facet intercepts the incoming flux
  836.                 force = force.add(facetRadiationAcceleration(normal, bodyFacet.getArea(), fluxSat, dot,
  837.                                                              specularReflectionCoeffDS, diffuseReflectionCoeffDS));
  838.             }
  839.         }

  840.         // convert to inertial
  841.         return FieldRotation.applyInverseTo(rotation, new FieldVector3D<>(1.0 / mass, force));

  842.     }

  843.     /** {@inheritDoc} */
  844.     @Override
  845.     public <T extends RealFieldElement<T>> FieldVector3D<T>
  846.         dragAcceleration(final FieldAbsoluteDate<T> date, final Frame frame,
  847.                          final FieldVector3D<T> position, final FieldRotation<T> rotation,
  848.                          final T mass, final  T density, final FieldVector3D<T> relativeVelocity,
  849.                          final T[] parameters) {

  850.         final T dragCoeff = parameters[0];
  851.         final T liftRatio = liftParameterDriver == null ? dragCoeff.getField().getZero() : parameters[1];

  852.         // relative velocity in spacecraft frame
  853.         final T                vNorm2 = relativeVelocity.getNormSq();
  854.         final T                vNorm  = vNorm2.sqrt();
  855.         final FieldVector3D<T> vDir   = rotation.applyTo(relativeVelocity.scalarMultiply(vNorm.reciprocal()));
  856.         final T                coeff  = density.multiply(0.5).multiply(dragCoeff).multiply(vNorm2).divide(mass);
  857.         final T                oMr    = liftRatio.negate().add(1);

  858.         // solar array facet contribution
  859.         final FieldVector3D<T> frontNormal = getNormal(date, frame, position, rotation);
  860.         final T                s           = coeff.
  861.                                              multiply(solarArrayArea).
  862.                                              multiply(FieldVector3D.dotProduct(frontNormal, vDir));
  863.         FieldVector3D<T> acceleration = new FieldVector3D<>(s.abs().multiply(oMr), vDir,
  864.                                                             s.multiply(liftRatio).multiply(2), frontNormal);

  865.         // body facets contribution
  866.         final Field<T> field = coeff.getField();
  867.         for (final Facet facet : facets) {
  868.             final T dot = FieldVector3D.dotProduct(facet.getNormal(), vDir);
  869.             if (dot.getReal() < 0) {
  870.                 // the facet intercepts the incoming flux
  871.                 final T f = coeff.multiply(facet.getArea()).multiply(dot);
  872.                 acceleration = new FieldVector3D<>(field.getOne(),        acceleration,
  873.                                                    f.abs().multiply(oMr), vDir,
  874.                                                    f.multiply(liftRatio).multiply(2), new FieldVector3D<>(field, facet.getNormal()));
  875.             }
  876.         }

  877.         // convert back to inertial frame
  878.         return rotation.applyInverseTo(acceleration);

  879.     }

  880.     /** {@inheritDoc} */
  881.     @Override
  882.     public <T extends RealFieldElement<T>> FieldVector3D<T>
  883.         radiationPressureAcceleration(final FieldAbsoluteDate<T> date, final Frame frame,
  884.                                       final FieldVector3D<T> position,
  885.                                       final FieldRotation<T> rotation, final T mass,
  886.                                       final FieldVector3D<T> flux,
  887.                                       final T[] parameters) {

  888.         if (flux.getNormSq().getReal() < Precision.SAFE_MIN) {
  889.             // null illumination (we are probably in umbra)
  890.             return FieldVector3D.getZero(date.getField());
  891.         }

  892.         // radiation flux in spacecraft frame
  893.         final FieldVector3D<T> fluxSat = rotation.applyTo(flux);

  894.         // solar array contribution
  895.         FieldVector3D<T> normal = getNormal(date, frame, position, rotation);
  896.         T dot = FieldVector3D.dotProduct(normal, fluxSat);
  897.         if (dot.getReal() > 0) {
  898.             // the solar array is illuminated backward,
  899.             // fix signs to compute contribution correctly
  900.             dot    = dot.negate();
  901.             normal = normal.negate();
  902.         }
  903.         FieldVector3D<T> force = facetRadiationAcceleration(normal, solarArrayArea, fluxSat, dot, parameters);

  904.         // body facets contribution
  905.         for (final Facet bodyFacet : facets) {
  906.             normal = new FieldVector3D<>(date.getField(), bodyFacet.getNormal());
  907.             dot = FieldVector3D.dotProduct(fluxSat, normal);
  908.             if (dot.getReal() < 0) {
  909.                 // the facet intercepts the incoming flux
  910.                 force = force.add(facetRadiationAcceleration(normal, bodyFacet.getArea(), fluxSat, dot, parameters));
  911.             }
  912.         }

  913.         // convert to inertial frame
  914.         return rotation.applyInverseTo(new FieldVector3D<>(mass.reciprocal(), force));

  915.     }

  916.     /** Compute contribution of one facet to force.
  917.      * <p>This method implements equation 8-44 from David A. Vallado's
  918.      * Fundamentals of Astrodynamics and Applications, third edition,
  919.      * 2007, Microcosm Press.</p>
  920.      * @param normal facet normal
  921.      * @param area facet area
  922.      * @param fluxSat radiation pressure flux in spacecraft frame
  923.      * @param dot dot product of facet and fluxSat (must be negative)
  924.      * @param parameters values of the force model parameters
  925.      * @return contribution of the facet to force in spacecraft frame
  926.      */
  927.     private Vector3D facetRadiationAcceleration(final Vector3D normal, final double area, final Vector3D fluxSat,
  928.                                                 final double dot, final double[] parameters) {

  929.         final double absorptionCoeff         = parameters[0];
  930.         final double specularReflectionCoeff = parameters[1];
  931.         final double diffuseReflectionCoeff  = 1 - (absorptionCoeff + specularReflectionCoeff);

  932.         final double psr  = fluxSat.getNorm();

  933.         // Vallado's equation 8-44 uses different parameters which are related to our parameters as:
  934.         // cos (phi) = -dot / (psr * area)
  935.         // n         = facet / area
  936.         // s         = -fluxSat / psr
  937.         final double cN = 2 * area * dot * (diffuseReflectionCoeff / 3 - specularReflectionCoeff * dot / psr);
  938.         final double cS = (area * dot / psr) * (specularReflectionCoeff - 1);
  939.         return new Vector3D(cN, normal, cS, fluxSat);

  940.     }

  941.     /** Compute contribution of one facet to force.
  942.      * <p>This method implements equation 8-44 from David A. Vallado's
  943.      * Fundamentals of Astrodynamics and Applications, third edition,
  944.      * 2007, Microcosm Press.</p>
  945.      * @param normal facet normal
  946.      * @param area facet area
  947.      * @param fluxSat radiation pressure flux in spacecraft frame
  948.      * @param dot dot product of facet and fluxSat (must be negative)
  949.      * @param parameters values of the force model parameters
  950.      * @param <T> type of the field elements
  951.      * @return contribution of the facet to force in spacecraft frame
  952.      */
  953.     private <T extends RealFieldElement<T>> FieldVector3D<T>
  954.         facetRadiationAcceleration(final FieldVector3D<T> normal, final double area, final FieldVector3D<T> fluxSat,
  955.                                    final T dot, final T[] parameters) {

  956.         final T absorptionCoeff         = parameters[0];
  957.         final T specularReflectionCoeff = parameters[1];
  958.         final T diffuseReflectionCoeff  = absorptionCoeff.add(specularReflectionCoeff).negate().add(1);

  959.         final T psr  = fluxSat.getNorm();

  960.         // Vallado's equation 8-44 uses different parameters which are related to our parameters as:
  961.         // cos (phi) = -dot / (psr * area)
  962.         // n         = facet / area
  963.         // s         = -fluxSat / psr
  964.         final T cN = dot.multiply(-2 * area).multiply(dot.multiply(specularReflectionCoeff).divide(psr).subtract(diffuseReflectionCoeff.divide(3)));
  965.         final T cS = dot.multiply(area).multiply(specularReflectionCoeff.subtract(1)).divide(psr);
  966.         return new FieldVector3D<>(cN, normal, cS, fluxSat);

  967.     }

  968.     /** Compute contribution of one facet to force.
  969.      * <p>This method implements equation 8-44 from David A. Vallado's
  970.      * Fundamentals of Astrodynamics and Applications, third edition,
  971.      * 2007, Microcosm Press.</p>
  972.      * @param normal facet normal
  973.      * @param area facet area
  974.      * @param fluxSat radiation pressure flux in spacecraft frame
  975.      * @param dot dot product of facet and fluxSat (must be negative)
  976.      * @param specularReflectionCoeffDS specular reflection coefficient
  977.      * @param diffuseReflectionCoeffDS diffuse reflection coefficient
  978.      * @return contribution of the facet to force in spacecraft frame
  979.      */
  980.     private FieldVector3D<DerivativeStructure> facetRadiationAcceleration(final Vector3D normal, final double area,
  981.                                                                           final Vector3D fluxSat, final double dot,
  982.                                                                           final DerivativeStructure specularReflectionCoeffDS,
  983.                                                                           final DerivativeStructure diffuseReflectionCoeffDS) {
  984.         final double psr  = fluxSat.getNorm();

  985.         // Vallado's equation 8-44 uses different parameters which are related to our parameters as:
  986.         // cos (phi) = -dot / (psr * area)
  987.         // n         = facet / area
  988.         // s         = -fluxSat / psr
  989.         final DerivativeStructure cN =
  990.                 diffuseReflectionCoeffDS.divide(3).subtract(specularReflectionCoeffDS.multiply(dot / psr)).multiply(2 * area * dot);
  991.         final DerivativeStructure cS = specularReflectionCoeffDS.subtract(1).multiply(area * dot / psr);

  992.         return new FieldVector3D<>(cN, normal, cS, fluxSat);

  993.     }

  994.     /** Class representing a single facet of a convex spacecraft body.
  995.      * <p>Instance of this class are guaranteed to be immutable.</p>
  996.      * @author Luc Maisonobe
  997.      */
  998.     public static class Facet {

  999.         /** Unit Normal vector, pointing outward. */
  1000.         private final Vector3D normal;

  1001.         /** Area in m². */
  1002.         private final double area;

  1003.         /** Simple constructor.
  1004.          * @param normal vector normal to the facet, pointing outward (will be normalized)
  1005.          * @param area facet area in m²
  1006.          */
  1007.         public Facet(final Vector3D normal, final double area) {
  1008.             this.normal = normal.normalize();
  1009.             this.area   = area;
  1010.         }

  1011.         /** Get unit normal vector.
  1012.          * @return unit normal vector
  1013.          */
  1014.         public Vector3D getNormal() {
  1015.             return normal;
  1016.         }

  1017.         /** Get facet area.
  1018.          * @return facet area
  1019.          */
  1020.         public double getArea() {
  1021.             return area;
  1022.         }

  1023.     }

  1024.     /** Build the surface vectors for body facets of a simple parallelepipedic box.
  1025.      * @param xLength length of the body along its X axis (m)
  1026.      * @param yLength length of the body along its Y axis (m)
  1027.      * @param zLength length of the body along its Z axis (m)
  1028.      * @return surface vectors array
  1029.      */
  1030.     private static Facet[] simpleBoxFacets(final double xLength, final double yLength, final double zLength) {
  1031.         return new Facet[] {
  1032.             new Facet(Vector3D.MINUS_I, yLength * zLength),
  1033.             new Facet(Vector3D.PLUS_I,  yLength * zLength),
  1034.             new Facet(Vector3D.MINUS_J, xLength * zLength),
  1035.             new Facet(Vector3D.PLUS_J,  xLength * zLength),
  1036.             new Facet(Vector3D.MINUS_K, xLength * yLength),
  1037.             new Facet(Vector3D.PLUS_K,  xLength * yLength)
  1038.         };
  1039.     }

  1040.     /** Filter out zero area facets.
  1041.      * @param facets original facets (may include zero area facets)
  1042.      * @return filtered array
  1043.      */
  1044.     private static List<Facet> filter(final Facet[] facets) {
  1045.         final List<Facet> filtered = new ArrayList<Facet>(facets.length);
  1046.         for (Facet facet : facets) {
  1047.             if (facet.getArea() > 0) {
  1048.                 filtered.add(facet);
  1049.             }
  1050.         }
  1051.         return filtered;
  1052.     }

  1053. }