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.CalculusFieldElement;
20  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
21  import org.hipparchus.util.FastMath;
22  import org.hipparchus.util.MathArrays;
23  import org.orekit.control.indirect.adjoint.cost.FieldCartesianCost;
24  import org.orekit.errors.OrekitException;
25  import org.orekit.errors.OrekitMessages;
26  import org.orekit.frames.Frame;
27  import org.orekit.orbits.OrbitType;
28  import org.orekit.propagation.FieldSpacecraftState;
29  import org.orekit.propagation.integration.FieldAdditionalDerivativesProvider;
30  import org.orekit.propagation.integration.FieldCombinedDerivatives;
31  import org.orekit.time.FieldAbsoluteDate;
32  import org.orekit.utils.FieldPVCoordinates;
33  
34  /**
35   * Class defining the Field version of the adjoint dynamics for Cartesian coordinates, as defined in the Pontryagin Maximum Principle.
36   * @author Romain Serra
37   * @see FieldAdditionalDerivativesProvider
38   * @see org.orekit.propagation.numerical.FieldNumericalPropagator
39   * @see CartesianAdjointDerivativesProvider
40   * @since 12.2
41   */
42  public class FieldCartesianAdjointDerivativesProvider<T extends CalculusFieldElement<T>> implements FieldAdditionalDerivativesProvider<T> {
43  
44      /** Contributing terms to the adjoint equation. */
45      private final CartesianAdjointEquationTerm[] adjointEquationTerms;
46  
47      /** Cost function. */
48      private final FieldCartesianCost<T> cost;
49  
50      /**
51       * Constructor.
52       * @param cost cost function
53       * @param adjointEquationTerms terms contributing to the adjoint equations
54       */
55      public FieldCartesianAdjointDerivativesProvider(final FieldCartesianCost<T> cost,
56                                                      final CartesianAdjointEquationTerm... adjointEquationTerms) {
57          this.cost = cost;
58          this.adjointEquationTerms = adjointEquationTerms;
59      }
60  
61      /**
62       * Getter for the cost.
63       * @return cost
64       */
65      public FieldCartesianCost<T> getCost() {
66          return cost;
67      }
68  
69      /** Getter for the name.
70       * @return name */
71      public String getName() {
72          return cost.getAdjointName();
73      }
74  
75      /** Getter for the dimension.
76       * @return dimension
77       */
78      public int getDimension() {
79          return cost.getAdjointDimension();
80      }
81  
82      /** {@inheritDoc} */
83      @Override
84      public void init(final FieldSpacecraftState<T> initialState, final FieldAbsoluteDate<T> target) {
85          FieldAdditionalDerivativesProvider.super.init(initialState, target);
86          if (initialState.isOrbitDefined() && initialState.getOrbit().getType() != OrbitType.CARTESIAN) {
87              throw new OrekitException(OrekitMessages.WRONG_COORDINATES_FOR_ADJOINT_EQUATION);
88          }
89      }
90  
91      /** {@inheritDoc} */
92      @Override
93      public FieldCombinedDerivatives<T> combinedDerivatives(final FieldSpacecraftState<T> state) {
94          // pre-computations
95          final T mass = state.getMass();
96          final T[] adjointVariables = state.getAdditionalState(getName());
97          final int adjointDimension = getDimension();
98          final T[] additionalDerivatives = MathArrays.buildArray(mass.getField(), adjointDimension);
99          final T[] cartesianVariablesAndMass = formCartesianAndMassVector(state);
100 
101         // mass flow rate and control acceleration
102         final T[] mainDerivativesIncrements = MathArrays.buildArray(mass.getField(), 7);
103         final FieldVector3D<T> thrustAccelerationVector = getCost().getFieldThrustAccelerationVector(adjointVariables, mass);
104         mainDerivativesIncrements[3] = thrustAccelerationVector.getX();
105         mainDerivativesIncrements[4] = thrustAccelerationVector.getY();
106         mainDerivativesIncrements[5] = thrustAccelerationVector.getZ();
107         final T thrustAccelerationNorm = thrustAccelerationVector.getNorm();
108         if (thrustAccelerationVector.getNorm().getReal() != 0.) {
109             final T thrustForceMagnitude = thrustAccelerationNorm.multiply(mass);
110             mainDerivativesIncrements[6] = thrustForceMagnitude.multiply(getCost().getMassFlowRateFactor().negate());
111         }
112 
113         // Cartesian position adjoint
114         additionalDerivatives[3] = adjointVariables[0].negate();
115         additionalDerivatives[4] = adjointVariables[1].negate();
116         additionalDerivatives[5] = adjointVariables[2].negate();
117 
118         // Cartesian velocity adjoint
119         final FieldAbsoluteDate<T> date = state.getDate();
120         final Frame propagationFrame = state.getFrame();
121         for (final CartesianAdjointEquationTerm equationTerm: adjointEquationTerms) {
122             final T[] contribution = equationTerm.getFieldRatesContribution(date, cartesianVariablesAndMass, adjointVariables,
123                     propagationFrame);
124             for (int i = 0; i < FastMath.min(adjointDimension, contribution.length); i++) {
125                 additionalDerivatives[i] = additionalDerivatives[i].add(contribution[i]);
126             }
127         }
128 
129         // other
130         getCost().updateFieldAdjointDerivatives(adjointVariables, mass, additionalDerivatives);
131 
132         return new FieldCombinedDerivatives<>(additionalDerivatives, mainDerivativesIncrements);
133     }
134 
135     /**
136      * Gather Cartesian variables and mass in same vector.
137      * @param state propagation state
138      * @return Cartesian variables and mass
139      */
140     private T[] formCartesianAndMassVector(final FieldSpacecraftState<T> state) {
141         final T mass = state.getMass();
142         final T[] cartesianVariablesAndMass = MathArrays.buildArray(mass.getField(), 7);
143         final FieldPVCoordinates<T> pvCoordinates = state.getPVCoordinates();
144         System.arraycopy(pvCoordinates.getPosition().toArray(), 0, cartesianVariablesAndMass, 0, 3);
145         System.arraycopy(pvCoordinates.getVelocity().toArray(), 0, cartesianVariablesAndMass, 3, 3);
146         cartesianVariablesAndMass[6] = mass;
147         return cartesianVariablesAndMass;
148     }
149 
150     /**
151      * Evaluate the Hamiltonian from Pontryagin's Maximum Principle.
152      * @param state state assumed to hold the adjoint variables
153      * @return Hamiltonian
154      */
155     public T evaluateHamiltonian(final FieldSpacecraftState<T> state) {
156         final T[] cartesianAndMassVector = formCartesianAndMassVector(state);
157         final T[] adjointVariables = state.getAdditionalState(getName());
158         T hamiltonian = adjointVariables[0].multiply(cartesianAndMassVector[3]).add(adjointVariables[1].multiply(cartesianAndMassVector[4]))
159                 .add(adjointVariables[2].multiply(cartesianAndMassVector[5]));
160         final FieldAbsoluteDate<T> date = state.getDate();
161         final Frame propagationFrame = state.getFrame();
162         for (final CartesianAdjointEquationTerm adjointEquationTerm : adjointEquationTerms) {
163             final T contribution = adjointEquationTerm.getFieldHamiltonianContribution(date, cartesianAndMassVector,
164                 adjointVariables, propagationFrame);
165             hamiltonian = hamiltonian.add(contribution);
166         }
167         final T mass = state.getMass();
168         if (adjointVariables.length != 6) {
169             final T thrustAccelerationNorm = getCost().getFieldThrustAccelerationVector(adjointVariables, mass).getNorm();
170             final T thrustForceNorm = thrustAccelerationNorm.multiply(mass);
171             hamiltonian = hamiltonian.subtract(adjointVariables[6].multiply(getCost().getMassFlowRateFactor()).multiply(thrustForceNorm));
172         }
173         hamiltonian = hamiltonian.add(getCost().getFieldHamiltonianContribution(adjointVariables, mass));
174         return hamiltonian;
175     }
176 }