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