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  
19  import org.hipparchus.geometry.euclidean.threed.Vector3D;
20  import org.hipparchus.util.FastMath;
21  import org.orekit.control.indirect.adjoint.cost.CartesianCost;
22  import org.orekit.errors.OrekitException;
23  import org.orekit.errors.OrekitMessages;
24  import org.orekit.frames.Frame;
25  import org.orekit.orbits.OrbitType;
26  import org.orekit.propagation.SpacecraftState;
27  import org.orekit.propagation.integration.AdditionalDerivativesProvider;
28  import org.orekit.propagation.integration.CombinedDerivatives;
29  import org.orekit.time.AbsoluteDate;
30  import org.orekit.utils.PVCoordinates;
31  
32  /**
33   * 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.
34   * The time derivatives of the adjoint variables are obtained by differentiating the so-called Hamiltonian.
35   * They depend on the force model and the cost being minimized.
36   * 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}.
37   * For the latter, the cost function is represented through the interface {@link CartesianCost}.
38   * @author Romain Serra
39   * @see AdditionalDerivativesProvider
40   * @see org.orekit.propagation.numerical.NumericalPropagator
41   * @since 12.2
42   */
43  public class CartesianAdjointDerivativesProvider implements AdditionalDerivativesProvider {
44  
45      /** Contributing terms to the adjoint equation. */
46      private final CartesianAdjointEquationTerm[] adjointEquationTerms;
47  
48      /** Cost function. */
49      private final CartesianCost cost;
50  
51      /**
52       * Constructor.
53       * @param cost cost function
54       * @param adjointEquationTerms terms contributing to the adjoint equations. If none, then the propagator should have no forces, not even a Newtonian attraction.
55       */
56      public CartesianAdjointDerivativesProvider(final CartesianCost cost,
57                                                 final CartesianAdjointEquationTerm... adjointEquationTerms) {
58          this.cost = cost;
59          this.adjointEquationTerms = adjointEquationTerms;
60      }
61  
62      /**
63       * Getter for the cost.
64       * @return cost
65       */
66      public CartesianCost getCost() {
67          return cost;
68      }
69  
70      /** Getter for the name.
71       * @return name */
72      public String getName() {
73          return cost.getAdjointName();
74      }
75  
76      /** Getter for the dimension.
77       * @return dimension
78       */
79      public int getDimension() {
80          return cost.getAdjointDimension();
81      }
82  
83      /** {@inheritDoc} */
84      @Override
85      public void init(final SpacecraftState initialState, final AbsoluteDate target) {
86          AdditionalDerivativesProvider.super.init(initialState, target);
87          if (initialState.isOrbitDefined() && initialState.getOrbit().getType() != OrbitType.CARTESIAN) {
88              throw new OrekitException(OrekitMessages.WRONG_COORDINATES_FOR_ADJOINT_EQUATION);
89          }
90      }
91  
92      /** {@inheritDoc} */
93      @Override
94      public CombinedDerivatives combinedDerivatives(final SpacecraftState state) {
95          // pre-computations
96          final double[] adjointVariables = state.getAdditionalState(getName());
97          final int adjointDimension = getDimension();
98          final double[] additionalDerivatives = new double[adjointDimension];
99          final double[] cartesianVariablesAndMass = formCartesianAndMassVector(state);
100         final double mass = state.getMass();
101 
102         // mass flow rate and control acceleration
103         final double[] mainDerivativesIncrements = new double[7];
104         final Vector3D thrustAccelerationVector = getCost().getThrustAccelerationVector(adjointVariables, mass);
105         mainDerivativesIncrements[3] = thrustAccelerationVector.getX();
106         mainDerivativesIncrements[4] = thrustAccelerationVector.getY();
107         mainDerivativesIncrements[5] = thrustAccelerationVector.getZ();
108         mainDerivativesIncrements[6] = -thrustAccelerationVector.getNorm() * mass * getCost().getMassFlowRateFactor();
109 
110         // Cartesian position adjoint
111         additionalDerivatives[3] = -adjointVariables[0];
112         additionalDerivatives[4] = -adjointVariables[1];
113         additionalDerivatives[5] = -adjointVariables[2];
114 
115         // update position and velocity adjoint derivatives
116         final AbsoluteDate date = state.getDate();
117         final Frame propagationFrame = state.getFrame();
118         for (final CartesianAdjointEquationTerm equationTerm: adjointEquationTerms) {
119             final double[] contribution = equationTerm.getRatesContribution(date, cartesianVariablesAndMass, adjointVariables,
120                     propagationFrame);
121             for (int i = 0; i < FastMath.min(adjointDimension, contribution.length); i++) {
122                 additionalDerivatives[i] += contribution[i];
123             }
124         }
125 
126         // other
127         getCost().updateAdjointDerivatives(adjointVariables, mass, additionalDerivatives);
128 
129         return new CombinedDerivatives(additionalDerivatives, mainDerivativesIncrements);
130     }
131 
132     /**
133      * Gather Cartesian variables and mass in same vector.
134      * @param state propagation state
135      * @return Cartesian variables and mass
136      */
137     private double[] formCartesianAndMassVector(final SpacecraftState state) {
138         final double[] cartesianVariablesAndMass = new double[7];
139         final PVCoordinates pvCoordinates = state.getPVCoordinates();
140         System.arraycopy(pvCoordinates.getPosition().toArray(), 0, cartesianVariablesAndMass, 0, 3);
141         System.arraycopy(pvCoordinates.getVelocity().toArray(), 0, cartesianVariablesAndMass, 3, 3);
142         final double mass = state.getMass();
143         cartesianVariablesAndMass[6] = mass;
144         return cartesianVariablesAndMass;
145     }
146 
147     /**
148      * Evaluate the Hamiltonian from Pontryagin's Maximum Principle.
149      * @param state state assumed to hold the adjoint variables
150      * @return Hamiltonian
151      */
152     public double evaluateHamiltonian(final SpacecraftState state) {
153         final double[] cartesianAndMassVector = formCartesianAndMassVector(state);
154         final double[] adjointVariables = state.getAdditionalState(getName());
155         double hamiltonian = adjointVariables[0] * cartesianAndMassVector[3] + adjointVariables[1] * cartesianAndMassVector[4] + adjointVariables[2] * cartesianAndMassVector[5];
156         final AbsoluteDate date = state.getDate();
157         final Frame propagationFrame = state.getFrame();
158         for (final CartesianAdjointEquationTerm adjointEquationTerm : adjointEquationTerms) {
159             hamiltonian += adjointEquationTerm.getHamiltonianContribution(date, adjointVariables, adjointVariables,
160                     propagationFrame);
161         }
162         final double mass = state.getMass();
163         if (adjointVariables.length != 6) {
164             final double thrustAccelerationNorm = getCost().getThrustAccelerationVector(adjointVariables, mass).getNorm();
165             hamiltonian -= adjointVariables[6] * getCost().getMassFlowRateFactor() * thrustAccelerationNorm * mass;
166         }
167         hamiltonian += getCost().getHamiltonianContribution(adjointVariables, mass);
168         return hamiltonian;
169     }
170 }