CartesianAdjointDerivativesProvider.java

  1. /* Copyright 2022-2025 Romain Serra
  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.control.indirect.adjoint;

  18. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  19. import org.hipparchus.util.FastMath;
  20. import org.orekit.control.indirect.adjoint.cost.CartesianCost;
  21. import org.orekit.errors.OrekitException;
  22. import org.orekit.errors.OrekitMessages;
  23. import org.orekit.frames.Frame;
  24. import org.orekit.orbits.OrbitType;
  25. import org.orekit.propagation.SpacecraftState;
  26. import org.orekit.propagation.integration.AdditionalDerivativesProvider;
  27. import org.orekit.propagation.integration.CombinedDerivatives;
  28. import org.orekit.time.AbsoluteDate;
  29. import org.orekit.utils.PVCoordinates;

  30. /**
  31.  * Class defining the adjoint dynamics, as defined in the Pontryagin Maximum Principle, in the case where Cartesian coordinates in an inertial frame are the dependent variable.
  32.  * The time derivatives of the adjoint variables are obtained by differentiating the so-called Hamiltonian.
  33.  * They depend on the force model and the cost being minimized.
  34.  * For the former, it is the user's responsibility to make sure the provided {@link CartesianAdjointEquationTerm} are consistent with the {@link org.orekit.forces.ForceModel}.
  35.  * For the latter, the cost function is represented through the interface {@link CartesianCost}.
  36.  * @author Romain Serra
  37.  * @see AdditionalDerivativesProvider
  38.  * @see org.orekit.propagation.numerical.NumericalPropagator
  39.  * @since 12.2
  40.  */
  41. public class CartesianAdjointDerivativesProvider implements AdditionalDerivativesProvider {

  42.     /** Contributing terms to the adjoint equation. */
  43.     private final CartesianAdjointEquationTerm[] adjointEquationTerms;

  44.     /** Cost function. */
  45.     private final CartesianCost cost;

  46.     /**
  47.      * Constructor.
  48.      * @param cost cost function
  49.      * @param adjointEquationTerms terms contributing to the adjoint equations. If none, then the propagator should have no forces, not even a Newtonian attraction.
  50.      */
  51.     public CartesianAdjointDerivativesProvider(final CartesianCost cost,
  52.                                                final CartesianAdjointEquationTerm... adjointEquationTerms) {
  53.         this.cost = cost;
  54.         this.adjointEquationTerms = adjointEquationTerms;
  55.     }

  56.     /**
  57.      * Getter for the cost.
  58.      * @return cost
  59.      */
  60.     public CartesianCost getCost() {
  61.         return cost;
  62.     }

  63.     /** Getter for the name.
  64.      * @return name */
  65.     public String getName() {
  66.         return cost.getAdjointName();
  67.     }

  68.     /** Getter for the dimension.
  69.      * @return dimension
  70.      */
  71.     public int getDimension() {
  72.         return cost.getAdjointDimension();
  73.     }

  74.     /** {@inheritDoc} */
  75.     @Override
  76.     public void init(final SpacecraftState initialState, final AbsoluteDate target) {
  77.         AdditionalDerivativesProvider.super.init(initialState, target);
  78.         if (initialState.isOrbitDefined() && initialState.getOrbit().getType() != OrbitType.CARTESIAN) {
  79.             throw new OrekitException(OrekitMessages.WRONG_COORDINATES_FOR_ADJOINT_EQUATION);
  80.         }
  81.     }

  82.     /** {@inheritDoc} */
  83.     @Override
  84.     public CombinedDerivatives combinedDerivatives(final SpacecraftState state) {
  85.         // pre-computations
  86.         final double[] adjointVariables = state.getAdditionalState(getName());
  87.         final int adjointDimension = getDimension();
  88.         final double[] additionalDerivatives = new double[adjointDimension];
  89.         final double[] cartesianVariablesAndMass = formCartesianAndMassVector(state);
  90.         final double mass = state.getMass();

  91.         // mass flow rate and control acceleration
  92.         final double[] mainDerivativesIncrements = new double[7];
  93.         final Vector3D thrustAccelerationVector = getCost().getThrustAccelerationVector(adjointVariables, mass);
  94.         mainDerivativesIncrements[3] = thrustAccelerationVector.getX();
  95.         mainDerivativesIncrements[4] = thrustAccelerationVector.getY();
  96.         mainDerivativesIncrements[5] = thrustAccelerationVector.getZ();
  97.         mainDerivativesIncrements[6] = -thrustAccelerationVector.getNorm() * mass * getCost().getMassFlowRateFactor();

  98.         // Cartesian position adjoint
  99.         additionalDerivatives[3] = -adjointVariables[0];
  100.         additionalDerivatives[4] = -adjointVariables[1];
  101.         additionalDerivatives[5] = -adjointVariables[2];

  102.         // update position and velocity adjoint derivatives
  103.         final AbsoluteDate date = state.getDate();
  104.         final Frame propagationFrame = state.getFrame();
  105.         for (final CartesianAdjointEquationTerm equationTerm: adjointEquationTerms) {
  106.             final double[] contribution = equationTerm.getRatesContribution(date, cartesianVariablesAndMass, adjointVariables,
  107.                     propagationFrame);
  108.             for (int i = 0; i < FastMath.min(adjointDimension, contribution.length); i++) {
  109.                 additionalDerivatives[i] += contribution[i];
  110.             }
  111.         }

  112.         // other
  113.         getCost().updateAdjointDerivatives(adjointVariables, mass, additionalDerivatives);

  114.         return new CombinedDerivatives(additionalDerivatives, mainDerivativesIncrements);
  115.     }

  116.     /**
  117.      * Gather Cartesian variables and mass in same vector.
  118.      * @param state propagation state
  119.      * @return Cartesian variables and mass
  120.      */
  121.     private double[] formCartesianAndMassVector(final SpacecraftState state) {
  122.         final double[] cartesianVariablesAndMass = new double[7];
  123.         final PVCoordinates pvCoordinates = state.getPVCoordinates();
  124.         System.arraycopy(pvCoordinates.getPosition().toArray(), 0, cartesianVariablesAndMass, 0, 3);
  125.         System.arraycopy(pvCoordinates.getVelocity().toArray(), 0, cartesianVariablesAndMass, 3, 3);
  126.         final double mass = state.getMass();
  127.         cartesianVariablesAndMass[6] = mass;
  128.         return cartesianVariablesAndMass;
  129.     }

  130.     /**
  131.      * Evaluate the Hamiltonian from Pontryagin's Maximum Principle.
  132.      * @param state state assumed to hold the adjoint variables
  133.      * @return Hamiltonian
  134.      */
  135.     public double evaluateHamiltonian(final SpacecraftState state) {
  136.         final double[] cartesianAndMassVector = formCartesianAndMassVector(state);
  137.         final double[] adjointVariables = state.getAdditionalState(getName());
  138.         double hamiltonian = adjointVariables[0] * cartesianAndMassVector[3] + adjointVariables[1] * cartesianAndMassVector[4] + adjointVariables[2] * cartesianAndMassVector[5];
  139.         final AbsoluteDate date = state.getDate();
  140.         final Frame propagationFrame = state.getFrame();
  141.         for (final CartesianAdjointEquationTerm adjointEquationTerm : adjointEquationTerms) {
  142.             hamiltonian += adjointEquationTerm.getHamiltonianContribution(date, adjointVariables, adjointVariables,
  143.                     propagationFrame);
  144.         }
  145.         final double mass = state.getMass();
  146.         if (adjointVariables.length != 6) {
  147.             final double thrustAccelerationNorm = getCost().getThrustAccelerationVector(adjointVariables, mass).getNorm();
  148.             hamiltonian -= adjointVariables[6] * getCost().getMassFlowRateFactor() * thrustAccelerationNorm * mass;
  149.         }
  150.         hamiltonian += getCost().getHamiltonianContribution(adjointVariables, mass);
  151.         return hamiltonian;
  152.     }
  153. }