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.cost;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
22  import org.hipparchus.util.FastMath;
23  import org.orekit.propagation.FieldSpacecraftState;
24  import org.orekit.propagation.events.EventDetectionSettings;
25  import org.orekit.propagation.events.FieldEventDetectionSettings;
26  import org.orekit.propagation.events.FieldEventDetector;
27  
28  import java.util.stream.Stream;
29  
30  /**
31   * Fuel cost penalized with a quadratic term. For epsilon equal to 1, one gets the bounded energy cost.
32   *
33   * @author Romain Serra
34   * @since 13.0
35   * @see BoundedCartesianEnergy
36   */
37  public class FieldQuadraticPenaltyCartesianFuel<T extends CalculusFieldElement<T>>
38          extends FieldPenalizedCartesianFuelCost<T> {
39  
40      /** Detection settings for singularity detection. */
41      private final FieldEventDetectionSettings<T> eventDetectionSettings;
42  
43      /**
44       * Constructor.
45       *
46       * @param name                   adjoint name
47       * @param massFlowRateFactor     mass flow rate factor
48       * @param maximumThrustMagnitude maximum thrust magnitude
49       * @param epsilon                penalty weight
50       * @param eventDetectionSettings detection settings
51       */
52      public FieldQuadraticPenaltyCartesianFuel(final String name, final T massFlowRateFactor,
53                                                final T maximumThrustMagnitude, final T epsilon,
54                                                final FieldEventDetectionSettings<T> eventDetectionSettings) {
55          super(name, massFlowRateFactor, maximumThrustMagnitude, epsilon);
56          this.eventDetectionSettings = eventDetectionSettings;
57      }
58  
59      /**
60       * Constructor with default event detection settings.
61       *
62       * @param name                   adjoint name
63       * @param massFlowRateFactor     mass flow rate factor
64       * @param maximumThrustMagnitude maximum thrust magnitude
65       * @param epsilon                penalty weight
66       */
67      public FieldQuadraticPenaltyCartesianFuel(final String name, final T massFlowRateFactor,
68                                                final T maximumThrustMagnitude, final T epsilon) {
69          this(name, massFlowRateFactor, maximumThrustMagnitude, epsilon, new FieldEventDetectionSettings<>(massFlowRateFactor.getField(),
70                  EventDetectionSettings.getDefaultEventDetectionSettings()));
71      }
72  
73      /**
74       * Getter for the event detection settings.
75       * @return detection settings
76       */
77      public FieldEventDetectionSettings<T> getEventDetectionSettings() {
78          return eventDetectionSettings;
79      }
80  
81      /** {@inheritDoc} */
82      @Override
83      public T evaluateFieldPenaltyFunction(final T controlNorm) {
84          return controlNorm.multiply(controlNorm.multiply(getMaximumThrustMagnitude()).divide(2).subtract(1.));
85      }
86  
87      /** {@inheritDoc} */
88      @Override
89      public FieldVector3D<T> getFieldThrustAccelerationVector(final T[] adjointVariables, final T mass) {
90          final T switchFunction = evaluateSwitchFunction(adjointVariables, mass);
91          if (switchFunction.getReal() > 0) {
92              final T thrustForceMagnitude = FastMath.min(switchFunction, getMaximumThrustMagnitude());
93              return getFieldThrustDirection(adjointVariables).scalarMultiply(thrustForceMagnitude.divide(mass));
94          } else {
95              return FieldVector3D.getZero(mass.getField());
96          }
97      }
98  
99      /** {@inheritDoc} */
100     @Override
101     public void updateFieldAdjointDerivatives(final T[] adjointVariables, final T mass, final T[] adjointDerivatives) {
102         if (getAdjointDimension() > 6) {
103             final T switchFunction = evaluateSwitchFunction(adjointVariables, mass);
104             if (switchFunction.getReal() > 0.) {
105                 final T minimum = FastMath.min(switchFunction, getMaximumThrustMagnitude());
106                 adjointDerivatives[6] = adjointDerivatives[6].add(getFieldAdjointVelocityNorm(adjointVariables)
107                         .multiply(minimum).divide(mass.square()));
108             }
109         }
110     }
111 
112     /**
113      * Evaluate switching function (whose value determines the control profile).
114      * @param adjointVariables adjoint vector
115      * @param mass mass
116      * @return value of switch function
117      */
118     private T evaluateSwitchFunction(final T[] adjointVariables, final T mass) {
119         T epsilonIndependentTerm = getFieldAdjointVelocityNorm(adjointVariables).divide(mass).subtract(1.);
120         if (getAdjointDimension() > 6) {
121             epsilonIndependentTerm = epsilonIndependentTerm.subtract(adjointVariables[6].multiply(getMassFlowRateFactor()));
122         }
123         return epsilonIndependentTerm.divide(getEpsilon()).add(1);
124     }
125 
126     /** {@inheritDoc} */
127     @Override
128     public Stream<FieldEventDetector<T>> getFieldEventDetectors(final Field<T> field) {
129         return Stream.of(new FieldQuadraticPenalizedSwitchDetector(getEventDetectionSettings(), field.getZero()),
130                 new FieldQuadraticPenalizedSwitchDetector(getEventDetectionSettings(), getMaximumThrustMagnitude()));
131     }
132 
133     /**
134      * Event detector for control non-differentiability.
135      */
136     private class FieldQuadraticPenalizedSwitchDetector extends FieldControlSwitchDetector<T> {
137 
138         /** Critical value at which the switching function has an event. */
139         private final T criticalValue;
140 
141         /**
142          * Constructor.
143          * @param detectionSettings detection settings.
144          * @param criticalValue switch function value to detect
145          */
146         FieldQuadraticPenalizedSwitchDetector(final FieldEventDetectionSettings<T> detectionSettings,
147                                               final T criticalValue) {
148             super(detectionSettings);
149             this.criticalValue = criticalValue;
150         }
151 
152         /** {@inheritDoc} */
153         @Override
154         public T g(final FieldSpacecraftState<T> state) {
155             final T[] adjoint = state.getAdditionalState(getAdjointName());
156             return evaluateSwitchFunction(adjoint, state.getMass()).subtract(criticalValue);
157         }
158     }
159 
160     /** {@inheritDoc} */
161     @Override
162     public QuadraticPenaltyCartesianFuel toCartesianCost() {
163         return new QuadraticPenaltyCartesianFuel(getAdjointName(), getMassFlowRateFactor().getReal(),
164                 getMaximumThrustMagnitude().getReal(), getEpsilon().getReal(),
165                 getEventDetectionSettings().toEventDetectionSettings());
166     }
167 }