AbstractDragForceModel.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.drag;

  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.analysis.differentiation.DSFactory;
  20. import org.hipparchus.analysis.differentiation.DerivativeStructure;
  21. import org.hipparchus.analysis.differentiation.Gradient;
  22. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  23. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  24. import org.orekit.forces.ForceModel;
  25. import org.orekit.frames.Frame;
  26. import org.orekit.frames.StaticTransform;
  27. import org.orekit.models.earth.atmosphere.Atmosphere;
  28. import org.orekit.propagation.FieldSpacecraftState;
  29. import org.orekit.time.AbsoluteDate;
  30. import org.orekit.time.FieldAbsoluteDate;
  31. import org.orekit.utils.FieldPVCoordinates;

  32. import java.util.Arrays;

  33. /**
  34.  * Base class for drag force models.
  35.  * @see DragForce
  36.  * @author Bryan Cazabonne
  37.  * @since 10.2
  38.  */
  39. public abstract class AbstractDragForceModel implements ForceModel {

  40.     /** Atmospheric model. */
  41.     private final Atmosphere atmosphere;

  42.     /**
  43.      * Flag to use (first-order) finite differences instead of automatic differentiation when computing density derivatives w.r.t. position.
  44.      */
  45.     private final boolean useFiniteDifferencesOnDensityWrtPosition;

  46.     /**
  47.      * Constructor with default value for finite differences flag.
  48.      * @param atmosphere atmospheric model
  49.      */
  50.     protected AbstractDragForceModel(final Atmosphere atmosphere) {
  51.         this(atmosphere, true);
  52.     }

  53.     /**
  54.      * Constructor.
  55.      * @param atmosphere atmospheric model
  56.      * @param useFiniteDifferencesOnDensityWrtPosition flag to use finite differences to compute density derivatives w.r.t.
  57.      *                                                 position (is less accurate but can be faster depending on model)
  58.      * @since 12.1
  59.      */
  60.     protected AbstractDragForceModel(final Atmosphere atmosphere, final boolean useFiniteDifferencesOnDensityWrtPosition) {
  61.         this.atmosphere = atmosphere;
  62.         this.useFiniteDifferencesOnDensityWrtPosition = useFiniteDifferencesOnDensityWrtPosition;
  63.     }

  64.     /** Get the atmospheric model.
  65.      * @return atmosphere model
  66.      * @since 12.1
  67.      */
  68.     public Atmosphere getAtmosphere() {
  69.         return atmosphere;
  70.     }

  71.     /** {@inheritDoc} */
  72.     @Override
  73.     public boolean dependsOnPositionOnly() {
  74.         return false;
  75.     }

  76.     /** Check if a field state corresponds to derivatives with respect to state.
  77.      * @param state state to check
  78.      * @param <T> type of the field elements
  79.      * @return true if state corresponds to derivatives with respect to state
  80.      */
  81.     protected <T extends CalculusFieldElement<T>> boolean isDSStateDerivative(final FieldSpacecraftState<T> state) {
  82.         try {
  83.             final DerivativeStructure dsMass = (DerivativeStructure) state.getMass();
  84.             final int o = dsMass.getOrder();
  85.             final int p = dsMass.getFreeParameters();

  86.             // To be in the desired case:
  87.             // Order must be 1 (first order derivatives only)
  88.             // Number of parameters must be 6 (PV), 7 (PV + drag coefficient) or 8 (PV + drag coefficient + lift ratio)
  89.             if (o != 1 || p != 6 && p != 7 && p != 8) {
  90.                 return false;
  91.             }

  92.             // Check that the first 6 parameters are position and velocity
  93.             @SuppressWarnings("unchecked")
  94.             final FieldPVCoordinates<DerivativeStructure> pv = (FieldPVCoordinates<DerivativeStructure>) state.getPVCoordinates();
  95.             return isVariable(pv.getPosition().getX(), 0) &&
  96.                    isVariable(pv.getPosition().getY(), 1) &&
  97.                    isVariable(pv.getPosition().getZ(), 2) &&
  98.                    isVariable(pv.getVelocity().getX(), 3) &&
  99.                    isVariable(pv.getVelocity().getY(), 4) &&
  100.                    isVariable(pv.getVelocity().getZ(), 5);
  101.         } catch (ClassCastException cce) {
  102.             return false;
  103.         }
  104.     }

  105.     /** Check if a field state corresponds to derivatives with respect to state.
  106.      * @param state state to check
  107.      * @param <T> type of the field elements
  108.      * @return true if state corresponds to derivatives with respect to state
  109.      */
  110.     protected <T extends CalculusFieldElement<T>> boolean isGradientStateDerivative(final FieldSpacecraftState<T> state) {
  111.         try {
  112.             final Gradient gMass = (Gradient) state.getMass();
  113.             final int p = gMass.getFreeParameters();

  114.             // To be in the desired case:
  115.             // Number of parameters must be 6 (PV), 7 (PV + drag coefficient) or 8 (PV + drag coefficient + lift ratio)
  116.             if (p != 6 && p != 7 && p != 8) {
  117.                 return false;
  118.             }

  119.             // Check that the first 6 parameters are position and velocity
  120.             @SuppressWarnings("unchecked")
  121.             final FieldPVCoordinates<Gradient> pv = (FieldPVCoordinates<Gradient>) state.getPVCoordinates();
  122.             return isVariable(pv.getPosition().getX(), 0) &&
  123.                    isVariable(pv.getPosition().getY(), 1) &&
  124.                    isVariable(pv.getPosition().getZ(), 2) &&
  125.                    isVariable(pv.getVelocity().getX(), 3) &&
  126.                    isVariable(pv.getVelocity().getY(), 4) &&
  127.                    isVariable(pv.getVelocity().getZ(), 5);
  128.         } catch (ClassCastException cce) {
  129.             return false;
  130.         }
  131.     }

  132.     /**
  133.      * Evaluate the Field density.
  134.      * @param s spacecraft state
  135.      * @return atmospheric density
  136.      * @param <T> field type
  137.      * @since 12.1
  138.      */
  139.     @SuppressWarnings("unchecked")
  140.     protected <T extends CalculusFieldElement<T>> T getFieldDensity(final FieldSpacecraftState<T> s) {
  141.         final FieldAbsoluteDate<T> date     = s.getDate();
  142.         final Frame                frame    = s.getFrame();
  143.         final FieldVector3D<T>     position = s.getPosition();
  144.         if (isGradientStateDerivative(s)) {
  145.             if (useFiniteDifferencesOnDensityWrtPosition) {
  146.                 return (T) this.getGradientDensityWrtStateUsingFiniteDifferences(date.toAbsoluteDate(), frame, (FieldVector3D<Gradient>) position);
  147.             } else {
  148.                 return (T) this.getGradientDensityWrtState(date.toAbsoluteDate(), frame, (FieldVector3D<Gradient>) position);
  149.             }
  150.         } else if (isDSStateDerivative(s)) {
  151.             if (useFiniteDifferencesOnDensityWrtPosition) {
  152.                 return (T) this.getDSDensityWrtStateUsingFiniteDifferences(date.toAbsoluteDate(), frame, (FieldVector3D<DerivativeStructure>) position);
  153.             } else {
  154.                 return (T) this.getDSDensityWrtState(date.toAbsoluteDate(), frame, (FieldVector3D<DerivativeStructure>) position);
  155.             }
  156.         } else {
  157.             return atmosphere.getDensity(date, position, frame);
  158.         }
  159.     }

  160.     /** Check if a derivative represents a specified variable.
  161.      * @param ds derivative to check
  162.      * @param index index of the variable
  163.      * @return true if the derivative represents a specified variable
  164.      */
  165.     protected boolean isVariable(final DerivativeStructure ds, final int index) {
  166.         final double[] derivatives = ds.getAllDerivatives();
  167.         boolean check = true;
  168.         for (int i = 1; i < derivatives.length; ++i) {
  169.             check &= derivatives[i] == ((index + 1 == i) ? 1.0 : 0.0);
  170.         }
  171.         return check;
  172.     }

  173.     /** Check if a derivative represents a specified variable.
  174.      * @param g derivative to check
  175.      * @param index index of the variable
  176.      * @return true if the derivative represents a specified variable
  177.      */
  178.     protected boolean isVariable(final Gradient g, final int index) {
  179.         final double[] derivatives = g.getGradient();
  180.         boolean check = true;
  181.         for (int i = 0; i < derivatives.length; ++i) {
  182.             check &= derivatives[i] == ((index == i) ? 1.0 : 0.0);
  183.         }
  184.         return check;
  185.     }

  186.     /** Compute density and its derivatives.
  187.      * Using finite differences for the derivatives.
  188.      * And doing the actual computation only for the derivatives with respect to position (others are set to 0.).
  189.      * <p>
  190.      * From a theoretical point of view, this method computes the same values
  191.      * as {@link Atmosphere#getDensity(FieldAbsoluteDate, FieldVector3D, Frame)} in the
  192.      * specific case of {@link DerivativeStructure} with respect to state, so
  193.      * it is less general. However, it can be faster depending the Field implementation.
  194.      * </p>
  195.      * <p>
  196.      * The derivatives should be computed with respect to position. The input
  197.      * parameters already take into account the free parameters (6, 7 or 8 depending
  198.      * on derivation with respect to drag coefficient and lift ratio being considered or not)
  199.      * and order (always 1). Free parameters at indices 0, 1 and 2 correspond to derivatives
  200.      * with respect to position. Free parameters at indices 3, 4 and 5 correspond
  201.      * to derivatives with respect to velocity (these derivatives will remain zero
  202.      * as the atmospheric density does not depend on velocity). Free parameter
  203.      * at indexes 6 and 7 (if present) corresponds to derivatives with respect to drag coefficient
  204.      * and/or lift ratio (one of these or both).
  205.      * This 2 last derivatives will remain zero as atmospheric density does not depend on them.
  206.      * </p>
  207.      * @param date current date
  208.      * @param frame inertial reference frame for state (both orbit and attitude)
  209.      * @param position position of spacecraft in inertial frame
  210.      * @return the density and its derivatives
  211.      */
  212.     protected DerivativeStructure getDSDensityWrtStateUsingFiniteDifferences(final AbsoluteDate date,
  213.                                                                              final Frame frame,
  214.                                                                              final FieldVector3D<DerivativeStructure> position) {

  215.         // Retrieve derivation properties for parameter T
  216.         // It is implied here that T is a DerivativeStructure
  217.         // With order 1 and 6, 7 or 8 free parameters
  218.         // This is all checked before in method isStateDerivatives
  219.         final DSFactory factory = position.getX().getFactory();

  220.         // Build a DerivativeStructure using only derivatives with respect to position
  221.         final DSFactory factory3 = new DSFactory(3, 1);
  222.         final FieldVector3D<DerivativeStructure> position3 =
  223.                         new FieldVector3D<>(factory3.variable(0, position.getX().getReal()),
  224.                                             factory3.variable(1,  position.getY().getReal()),
  225.                                             factory3.variable(2,  position.getZ().getReal()));

  226.         // Get atmosphere properties in atmosphere own frame
  227.         final Frame      atmFrame  = atmosphere.getFrame();
  228.         final StaticTransform toBody = frame.getStaticTransformTo(atmFrame, date);
  229.         final FieldVector3D<DerivativeStructure> posBodyDS = toBody.transformPosition(position3);
  230.         final Vector3D   posBody   = posBodyDS.toVector3D();

  231.         // Estimate density model by finite differences and composition
  232.         // Using a delta of 1m
  233.         final double delta  = 1.0;
  234.         final double x      = posBody.getX();
  235.         final double y      = posBody.getY();
  236.         final double z      = posBody.getZ();
  237.         final double rho0   = atmosphere.getDensity(date, posBody, atmFrame);
  238.         final double dRhodX = (atmosphere.getDensity(date, new Vector3D(x + delta, y,         z),         atmFrame) - rho0) / delta;
  239.         final double dRhodY = (atmosphere.getDensity(date, new Vector3D(x,         y + delta, z),         atmFrame) - rho0) / delta;
  240.         final double dRhodZ = (atmosphere.getDensity(date, new Vector3D(x,         y,         z + delta), atmFrame) - rho0) / delta;
  241.         final double[] dXdQ = posBodyDS.getX().getAllDerivatives();
  242.         final double[] dYdQ = posBodyDS.getY().getAllDerivatives();
  243.         final double[] dZdQ = posBodyDS.getZ().getAllDerivatives();

  244.         // Density with derivatives:
  245.         // - The value and only the 3 first derivatives (those with respect to spacecraft position) are computed
  246.         // - Others are set to 0.
  247.         final int p = factory.getCompiler().getFreeParameters();
  248.         final double[] rhoAll = new double[p + 1];
  249.         rhoAll[0] = rho0;
  250.         for (int i = 1; i < 4; ++i) {
  251.             rhoAll[i] = dRhodX * dXdQ[i] + dRhodY * dYdQ[i] + dRhodZ * dZdQ[i];
  252.         }

  253.         return factory.build(rhoAll);
  254.     }

  255.     /** Compute density and its derivatives.
  256.      * And doing the actual computation only for the derivatives with respect to position (others are set to 0.).
  257.      * <p>
  258.      * The derivatives should be computed with respect to position. The input
  259.      * parameters already take into account the free parameters (6, 7 or 8 depending
  260.      * on derivation with respect to drag coefficient and lift ratio being considered or not)
  261.      * and order (always 1). Free parameters at indices 0, 1 and 2 correspond to derivatives
  262.      * with respect to position. Free parameters at indices 3, 4 and 5 correspond
  263.      * to derivatives with respect to velocity (these derivatives will remain zero
  264.      * as the atmospheric density does not depend on velocity). Free parameter
  265.      * at indexes 6 and 7 (if present) corresponds to derivatives with respect to drag coefficient
  266.      * and/or lift ratio (one of these or both).
  267.      * This 2 last derivatives will remain zero as atmospheric density does not depend on them.
  268.      * </p>
  269.      * @param date current date
  270.      * @param frame inertial reference frame for state (both orbit and attitude)
  271.      * @param position position of spacecraft in inertial frame
  272.      * @return the density and its derivatives
  273.      */
  274.     protected DerivativeStructure getDSDensityWrtState(final AbsoluteDate date, final Frame frame,
  275.                                                        final FieldVector3D<DerivativeStructure> position) {

  276.         // Retrieve derivation properties for parameter T
  277.         // It is implied here that T is a DerivativeStructure
  278.         // With order 1 and 6, 7 or 8 free parameters
  279.         // This is all checked before in method isStateDerivatives
  280.         final DSFactory factory = position.getX().getFactory();

  281.         // Build a DerivativeStructure using only derivatives with respect to position
  282.         final DSFactory factory3 = new DSFactory(3, 1);
  283.         final FieldVector3D<DerivativeStructure> position3 =
  284.                 new FieldVector3D<>(factory3.variable(0, position.getX().getReal()),
  285.                         factory3.variable(1,  position.getY().getReal()),
  286.                         factory3.variable(2,  position.getZ().getReal()));

  287.         // Get atmosphere properties in atmosphere own frame
  288.         final Frame      atmFrame  = atmosphere.getFrame();
  289.         final StaticTransform toBody = frame.getStaticTransformTo(atmFrame, date);
  290.         final FieldVector3D<DerivativeStructure> posBodyDS = toBody.transformPosition(position3);
  291.         final FieldAbsoluteDate<DerivativeStructure> fieldDate = new FieldAbsoluteDate<>(position3.getX().getField(), date);
  292.         final DerivativeStructure density = atmosphere.getDensity(fieldDate, posBodyDS, atmFrame);

  293.         // Density with derivatives:
  294.         // - The value and only the 3 first derivatives (those with respect to spacecraft position) are computed
  295.         // - Others are set to 0.
  296.         final double[] derivatives = Arrays.copyOf(density.getAllDerivatives(), factory.getCompiler().getSize());
  297.         return factory.build(derivatives);
  298.     }

  299.     /** Compute density and its derivatives.
  300.      * Using finite differences for the derivatives.
  301.      * And doing the actual computation only for the derivatives with respect to position (others are set to 0.).
  302.      * <p>
  303.      * From a theoretical point of view, this method computes the same values
  304.      * as {@link Atmosphere#getDensity(FieldAbsoluteDate, FieldVector3D, Frame)} in the
  305.      * specific case of {@link Gradient} with respect to state, so
  306.      * it is less general. However, it can be faster depending the Field implementation.
  307.      * </p>
  308.      * <p>
  309.      * The derivatives should be computed with respect to position. The input
  310.      * parameters already take into account the free parameters (6, 7 or 8 depending
  311.      * on derivation with respect to drag coefficient and lift ratio being considered or not)
  312.      * and order (always 1). Free parameters at indices 0, 1 and 2 correspond to derivatives
  313.      * with respect to position. Free parameters at indices 3, 4 and 5 correspond
  314.      * to derivatives with respect to velocity (these derivatives will remain zero
  315.      * as the atmospheric density does not depend on velocity). Free parameter
  316.      * at indexes 6 and 7 (if present) corresponds to derivatives with respect to drag coefficient
  317.      * and/or lift ratio (one of these or both).
  318.      * This 2 last derivatives will remain zero as atmospheric density does not depend on them.
  319.      * </p>
  320.      * @param date current date
  321.      * @param frame inertial reference frame for state (both orbit and attitude)
  322.      * @param position position of spacecraft in inertial frame
  323.      * @return the density and its derivatives
  324.      */
  325.     protected Gradient getGradientDensityWrtStateUsingFiniteDifferences(final AbsoluteDate date,
  326.                                                                         final Frame frame,
  327.                                                                         final FieldVector3D<Gradient> position) {

  328.         // Build a Gradient using only derivatives with respect to position
  329.         final FieldVector3D<Gradient> position3 =
  330.                         new FieldVector3D<>(Gradient.variable(3, 0, position.getX().getReal()),
  331.                                             Gradient.variable(3, 1,  position.getY().getReal()),
  332.                                             Gradient.variable(3, 2,  position.getZ().getReal()));

  333.         // Get atmosphere properties in atmosphere own frame
  334.         final Frame      atmFrame  = atmosphere.getFrame();
  335.         final StaticTransform toBody = frame.getStaticTransformTo(atmFrame, date);
  336.         final FieldVector3D<Gradient> posBodyDS = toBody.transformPosition(position3);
  337.         final Vector3D   posBody   = posBodyDS.toVector3D();

  338.         // Estimate density model by finite differences and composition
  339.         // Using a delta of 1m
  340.         final double delta  = 1.0;
  341.         final double x      = posBody.getX();
  342.         final double y      = posBody.getY();
  343.         final double z      = posBody.getZ();
  344.         final double rho0   = atmosphere.getDensity(date, posBody, atmFrame);
  345.         final double dRhodX = (atmosphere.getDensity(date, new Vector3D(x + delta, y,         z),         atmFrame) - rho0) / delta;
  346.         final double dRhodY = (atmosphere.getDensity(date, new Vector3D(x,         y + delta, z),         atmFrame) - rho0) / delta;
  347.         final double dRhodZ = (atmosphere.getDensity(date, new Vector3D(x,         y,         z + delta), atmFrame) - rho0) / delta;
  348.         final double[] dXdQ = posBodyDS.getX().getGradient();
  349.         final double[] dYdQ = posBodyDS.getY().getGradient();
  350.         final double[] dZdQ = posBodyDS.getZ().getGradient();

  351.         // Density with derivatives:
  352.         // - The value and only the 3 first derivatives (those with respect to spacecraft position) are computed
  353.         // - Others are set to 0.
  354.         final int p = position.getX().getFreeParameters();
  355.         final double[] rhoAll = new double[p];
  356.         for (int i = 0; i < 3; ++i) {
  357.             rhoAll[i] = dRhodX * dXdQ[i] + dRhodY * dYdQ[i] + dRhodZ * dZdQ[i];
  358.         }

  359.         return new Gradient(rho0, rhoAll);
  360.     }

  361.     /** Compute density and its derivatives.
  362.      * <p>
  363.      * The derivatives should be computed with respect to position. The input
  364.      * parameters already take into account the free parameters (6, 7 or 8 depending
  365.      * on derivation with respect to drag coefficient and lift ratio being considered or not)
  366.      * and order (always 1). Free parameters at indices 0, 1 and 2 correspond to derivatives
  367.      * with respect to position. Free parameters at indices 3, 4 and 5 correspond
  368.      * to derivatives with respect to velocity (these derivatives will remain zero
  369.      * as the atmospheric density does not depend on velocity). Free parameter
  370.      * at indexes 6 and 7 (if present) corresponds to derivatives with respect to drag coefficient
  371.      * and/or lift ratio (one of these or both).
  372.      * This 2 last derivatives will remain zero as atmospheric density does not depend on them.
  373.      * </p>
  374.      * @param date current date
  375.      * @param frame inertial reference frame for state (both orbit and attitude)
  376.      * @param position position of spacecraft in inertial frame
  377.      * @return the density and its derivatives
  378.      * @since 12.1
  379.      */
  380.     protected Gradient getGradientDensityWrtState(final AbsoluteDate date, final Frame frame,
  381.                                                   final FieldVector3D<Gradient> position) {

  382.         // Build a Gradient using only derivatives with respect to position
  383.         final int positionDimension = 3;
  384.         final FieldVector3D<Gradient> position3 =
  385.                 new FieldVector3D<>(Gradient.variable(positionDimension, 0, position.getX().getReal()),
  386.                         Gradient.variable(positionDimension, 1,  position.getY().getReal()),
  387.                         Gradient.variable(positionDimension, 2,  position.getZ().getReal()));

  388.         // Get atmosphere properties in atmosphere own frame
  389.         final Frame      atmFrame  = atmosphere.getFrame();
  390.         final StaticTransform toBody = frame.getStaticTransformTo(atmFrame, date);
  391.         final FieldVector3D<Gradient> posBodyGradient = toBody.transformPosition(position3);
  392.         final FieldAbsoluteDate<Gradient> fieldDate = new FieldAbsoluteDate<>(position3.getX().getField(), date);
  393.         final Gradient density = atmosphere.getDensity(fieldDate, posBodyGradient, atmFrame);

  394.         // Density with derivatives:
  395.         // - The value and only the 3 first derivatives (those with respect to spacecraft position) are computed
  396.         // - Others are set to 0.
  397.         final double[] derivatives = Arrays.copyOf(density.getGradient(), position.getX().getFreeParameters());
  398.         return new Gradient(density.getValue(), derivatives);
  399.     }
  400. }