1   /* Copyright 2002-2025 CS GROUP
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  
18  package org.orekit.forces.maneuvers;
19  
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.List;
23  import java.util.stream.Stream;
24  
25  import org.hipparchus.CalculusFieldElement;
26  import org.hipparchus.Field;
27  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
28  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
29  import org.hipparchus.geometry.euclidean.threed.Rotation;
30  import org.hipparchus.geometry.euclidean.threed.Vector3D;
31  import org.orekit.attitudes.Attitude;
32  import org.orekit.attitudes.AttitudeRotationModel;
33  import org.orekit.attitudes.FieldAttitude;
34  import org.orekit.forces.ForceModel;
35  import org.orekit.forces.maneuvers.propulsion.PropulsionModel;
36  import org.orekit.forces.maneuvers.trigger.ManeuverTriggers;
37  import org.orekit.propagation.FieldSpacecraftState;
38  import org.orekit.propagation.SpacecraftState;
39  import org.orekit.propagation.events.EventDetector;
40  import org.orekit.propagation.events.FieldEventDetector;
41  import org.orekit.propagation.numerical.FieldTimeDerivativesEquations;
42  import org.orekit.propagation.numerical.TimeDerivativesEquations;
43  import org.orekit.time.AbsoluteDate;
44  import org.orekit.time.FieldAbsoluteDate;
45  import org.orekit.utils.ParameterDriver;
46  
47  
48  /** A generic model for maneuvers with finite-valued acceleration magnitude, as opposed to instantaneous changes
49   * in the velocity vector which are defined via detectors (in {@link org.orekit.forces.maneuvers.ImpulseManeuver} and
50   * {@link org.orekit.forces.maneuvers.FieldImpulseManeuver}).
51   * It contains:
52   *  - An attitude override, this is the attitude used during the maneuver, it can be different from the one
53   *    used for propagation;
54   *  - A maneuver triggers object from the trigger sub-package. It defines the triggers used to start and stop the maneuvers (dates or events for example).
55   *  - A propulsion model from sub-package propulsion. It defines the thrust or ΔV, isp, flow rate etc.
56   * Both the propulsion model and the maneuver triggers can contain parameter drivers (for estimation), as well as the attitude override if set.
57   * The convention here is the following: drivers from propulsion model first, then maneuver triggers and if any the attitude override when calling the
58   * method {@link #getParametersDrivers()}
59   * @author Maxime Journot
60   * @since 10.2
61   */
62  public class Maneuver implements ForceModel {
63  
64      /** The attitude to override during the maneuver, if set. */
65      private final AttitudeRotationModel attitudeOverride;
66  
67      /** Propulsion model to use for the thrust. */
68      private final PropulsionModel propulsionModel;
69  
70      /** Maneuver triggers. */
71      private final ManeuverTriggers maneuverTriggers;
72  
73      /** Generic maneuver constructor.
74       * @param attitudeOverride attitude provider for the attitude during the maneuver
75       * @param maneuverTriggers maneuver triggers
76       * @param propulsionModel propulsion model
77       */
78      public Maneuver(final AttitudeRotationModel attitudeOverride,
79                      final ManeuverTriggers maneuverTriggers,
80                      final PropulsionModel propulsionModel) {
81          this.maneuverTriggers = maneuverTriggers;
82          this.attitudeOverride = attitudeOverride;
83          this.propulsionModel = propulsionModel;
84      }
85  
86      /** Get the name of the maneuver.
87       * The name can be in the propulsion model, in the maneuver triggers or both.
88       * If it is in both it should be the same since it refers to the same maneuver.
89       * The name is inferred from the propulsion model first, then from the maneuver triggers if
90       * the propulsion model had an empty name.
91       * @return the name
92       */
93      public String getName() {
94  
95          //FIXME: Potentially, throw an exception if both propulsion model
96          // and maneuver triggers define a name but they are different
97          String name = propulsionModel.getName();
98  
99          if (name.isEmpty()) {
100             name = maneuverTriggers.getName();
101         }
102         return name;
103     }
104 
105     /** Get the attitude override used for the maneuver.
106      * @return the attitude override
107      * @since 13.0
108      */
109     public AttitudeRotationModel getAttitudeOverride() {
110         return attitudeOverride;
111     }
112 
113     /** Get the control vector's cost type.
114      * @return control cost type
115      * @since 12.0
116      */
117     public Control3DVectorCostType getControl3DVectorCostType() {
118         return propulsionModel.getControl3DVectorCostType();
119     }
120 
121     /** Get the propulsion model.
122      * @return the propulsion model
123      */
124     public PropulsionModel getPropulsionModel() {
125         return propulsionModel;
126     }
127 
128     /** Get the maneuver triggers.
129      * @return the maneuver triggers
130      */
131     public ManeuverTriggers getManeuverTriggers() {
132         return maneuverTriggers;
133     }
134 
135     /** {@inheritDoc} */
136     @Override
137     public boolean dependsOnPositionOnly() {
138         return false;
139     }
140 
141     /** {@inheritDoc} */
142     @Override
143     public void init(final SpacecraftState initialState, final AbsoluteDate target) {
144         propulsionModel.init(initialState, target);
145         maneuverTriggers.init(initialState, target);
146     }
147 
148     /** {@inheritDoc} */
149     @Override
150     public <T extends CalculusFieldElement<T>> void init(final FieldSpacecraftState<T> initialState, final FieldAbsoluteDate<T> target) {
151         propulsionModel.init(initialState, target);
152         maneuverTriggers.init(initialState, target);
153     }
154 
155     /** {@inheritDoc} */
156     @Override
157     public void addContribution(final SpacecraftState s, final TimeDerivativesEquations adder) {
158 
159         // Get the parameters associated to the maneuver (from ForceModel)
160         final double[] parameters = getParameters(s.getDate());
161 
162         // If the maneuver is active, compute and add its contribution
163         // Maneuver triggers are used to check if the maneuver is currently firing or not
164         // Specific drivers for the triggers are extracted from the array given by the ForceModel interface
165         if (maneuverTriggers.isFiring(s.getDate(), getManeuverTriggersParameters(parameters))) {
166 
167             // Compute thrust acceleration in inertial frame
168             adder.addNonKeplerianAcceleration(acceleration(s, parameters));
169 
170             // Compute flow rate using the propulsion model
171             // Specific drivers for the propulsion model are extracted from the array given by the ForceModel interface
172             adder.addMassDerivative(propulsionModel.getMassDerivatives(s, getPropulsionModelParameters(parameters)));
173         }
174     }
175 
176     /** {@inheritDoc} */
177     @Override
178     public <T extends CalculusFieldElement<T>> void addContribution(final FieldSpacecraftState<T> s,
179                         final FieldTimeDerivativesEquations<T> adder) {
180 
181         // Get the parameters associated to the maneuver (from ForceModel)
182         final T[] parameters = getParameters(s.getDate().getField(), s.getDate());
183 
184         // If the maneuver is active, compute and add its contribution
185         // Maneuver triggers are used to check if the maneuver is currently firing or not
186         // Specific drivers for the triggers are extracted from the array given by the ForceModel interface
187         if (maneuverTriggers.isFiring(s.getDate(), getManeuverTriggersParameters(parameters))) {
188 
189             // Compute thrust acceleration in inertial frame
190             // the acceleration method extracts the parameter in its core, that is why we call it with
191             // parameters and not extracted parameters
192             adder.addNonKeplerianAcceleration(acceleration(s, parameters));
193 
194             // Compute flow rate using the propulsion model
195             // Specific drivers for the propulsion model are extracted from the array given by the ForceModel interface
196             adder.addMassDerivative(propulsionModel.getMassDerivatives(s, getPropulsionModelParameters(parameters)));
197         }
198     }
199 
200     /** {@inheritDoc} */
201     @Override
202     public Vector3D acceleration(final SpacecraftState s, final double[] parameters) {
203 
204         // If the maneuver is active, compute and add its contribution
205         // Maneuver triggers are used to check if the maneuver is currently firing or not
206         // Specific drivers for the triggers are extracted from the array given by the ForceModel interface
207         if (maneuverTriggers.isFiring(s.getDate(), getManeuverTriggersParameters(parameters))) {
208 
209             // Attitude during maneuver
210             final Attitude maneuverAttitude;
211             if (attitudeOverride == null) {
212                 maneuverAttitude = s.getAttitude();
213             } else {
214                 final Rotation rotation = attitudeOverride.getAttitudeRotation(s, getAttitudeModelParameters(parameters));
215                 // use dummy rates to build full attitude as they should not be used
216                 maneuverAttitude = new Attitude(s.getDate(), s.getFrame(), rotation, Vector3D.ZERO, Vector3D.ZERO);
217             }
218 
219             // Compute acceleration from propulsion model
220             // Specific drivers for the propulsion model are extracted from the array given by the ForceModel interface
221             return propulsionModel.getAcceleration(s, maneuverAttitude, getPropulsionModelParameters(parameters));
222         } else {
223             // Constant (and null) acceleration when not firing
224             return Vector3D.ZERO;
225         }
226     }
227 
228     /** {@inheritDoc} */
229     @Override
230     public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(final FieldSpacecraftState<T> s, final T[] parameters) {
231 
232         // If the maneuver is active, compute and add its contribution
233         // Maneuver triggers are used to check if the maneuver is currently firing or not
234         // Specific drivers for the triggers are extracted from the array given by the ForceModel interface
235         if (maneuverTriggers.isFiring(s.getDate(), getManeuverTriggersParameters(parameters))) {
236 
237             // Attitude during maneuver
238             final FieldAttitude<T> maneuverAttitude;
239             if (attitudeOverride == null) {
240                 maneuverAttitude = s.getAttitude();
241             } else {
242                 final FieldRotation<T> rotation = attitudeOverride.getAttitudeRotation(s, getAttitudeModelParameters(parameters));
243                 // use dummy rates to build full attitude as they should not be used
244                 final FieldVector3D<T> zeroVector3D = FieldVector3D.getZero(s.getDate().getField());
245                 maneuverAttitude = new FieldAttitude<>(s.getDate(), s.getFrame(), rotation, zeroVector3D, zeroVector3D);
246             }
247 
248             // Compute acceleration from propulsion model
249             // Specific drivers for the propulsion model are extracted from the array given by the ForceModel interface
250             return propulsionModel.getAcceleration(s, maneuverAttitude, getPropulsionModelParameters(parameters));
251         } else {
252             // Constant (and null) acceleration when not firing
253             return FieldVector3D.getZero(s.getMass().getField());
254         }
255     }
256 
257     /** {@inheritDoc} */
258     @Override
259     public Stream<EventDetector> getEventDetectors() {
260         // Event detectors are extracted from both the maneuver triggers and the propulsion model
261         return Stream.concat(maneuverTriggers.getEventDetectors(),
262                              propulsionModel.getEventDetectors());
263     }
264 
265     /** {@inheritDoc} */
266     @Override
267     public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(final Field<T> field) {
268         // Event detectors are extracted from both the maneuver triggers and the propulsion model
269         return Stream.concat(maneuverTriggers.getFieldEventDetectors(field),
270                              propulsionModel.getFieldEventDetectors(field));
271     }
272 
273     /** {@inheritDoc} */
274     @Override
275     public List<ParameterDriver> getParametersDrivers() {
276         // Prepare final drivers' array
277         final List<ParameterDriver> drivers = new ArrayList<>();
278 
279         // Convention: Propulsion drivers are given before maneuver triggers drivers
280         // Add propulsion drivers first
281         drivers.addAll(0, propulsionModel.getParametersDrivers());
282 
283         // Then maneuver triggers' drivers
284         drivers.addAll(drivers.size(), maneuverTriggers.getParametersDrivers());
285 
286         // Then attitude override' drivers if defined
287         if (attitudeOverride != null) {
288             drivers.addAll(attitudeOverride.getParametersDrivers());
289         }
290 
291         // Return full drivers' array
292         return drivers;
293     }
294 
295     /** Extract propulsion model parameters from the parameters' array called in by the ForceModel interface.
296      *  Convention: Propulsion parameters are given before maneuver triggers parameters
297      * @param parameters parameters' array called in by ForceModel interface
298      * @return propulsion model parameters
299      */
300     public double[] getPropulsionModelParameters(final double[] parameters) {
301         return Arrays.copyOfRange(parameters, 0, propulsionModel.getParametersDrivers().size());
302     }
303 
304     /** Extract propulsion model parameters from the parameters' array called in by the ForceModel interface.
305      *  Convention: Propulsion parameters are given before maneuver triggers parameters
306      * @param parameters parameters' array called in by ForceModel interface
307      * @param <T> extends CalculusFieldElement&lt;T&gt;
308      * @return propulsion model parameters
309      */
310     public <T extends CalculusFieldElement<T>> T[] getPropulsionModelParameters(final T[] parameters) {
311         return Arrays.copyOfRange(parameters, 0, propulsionModel.getParametersDrivers().size());
312     }
313 
314     /** Extract maneuver triggers' parameters from the parameters' array called in by the ForceModel interface.
315      *  Convention: Propulsion parameters are given before maneuver triggers parameters
316      * @param parameters parameters' array called in by ForceModel interface
317      * @return maneuver triggers' parameters
318      */
319     public double[] getManeuverTriggersParameters(final double[] parameters) {
320         final int nbPropulsionModelDrivers = propulsionModel.getParametersDrivers().size();
321         return Arrays.copyOfRange(parameters, nbPropulsionModelDrivers,
322                                   nbPropulsionModelDrivers + maneuverTriggers.getParametersDrivers().size());
323     }
324 
325     /** Extract maneuver triggers' parameters from the parameters' array called in by the ForceModel interface.
326      *  Convention: Propulsion parameters are given before maneuver triggers parameters
327      * @param parameters parameters' array called in by ForceModel interface
328      * @param <T> extends CalculusFieldElement&lt;T&gt;
329      * @return maneuver triggers' parameters
330      */
331     public <T extends CalculusFieldElement<T>> T[] getManeuverTriggersParameters(final T[] parameters) {
332         final int nbPropulsionModelDrivers = propulsionModel.getParametersDrivers().size();
333         return Arrays.copyOfRange(parameters, nbPropulsionModelDrivers,
334                                   nbPropulsionModelDrivers + maneuverTriggers.getParametersDrivers().size());
335     }
336 
337     /** Extract attitude model' parameters from the parameters' array called in by the ForceModel interface.
338      *  Convention: Attitude model parameters are given last
339      * @param parameters parameters' array called in by ForceModel interface
340      * @return maneuver triggers' parameters
341      */
342     protected double[] getAttitudeModelParameters(final double[] parameters) {
343         final int nbAttitudeModelDrivers = (attitudeOverride == null) ? 0 : attitudeOverride.getParametersDrivers().size();
344         return Arrays.copyOfRange(parameters, parameters.length - nbAttitudeModelDrivers, parameters.length);
345     }
346 
347     /** Extract attitude model' parameters from the parameters' array called in by the ForceModel interface.
348      *  Convention: Attitude parameters are given last
349      * @param parameters parameters' array called in by ForceModel interface
350      * @param <T> extends CalculusFieldElement&lt;T&gt;
351      * @return maneuver triggers' parameters
352      */
353     protected <T extends CalculusFieldElement<T>> T[] getAttitudeModelParameters(final T[] parameters) {
354         final int nbAttitudeModelDrivers = (attitudeOverride == null) ? 0 : attitudeOverride.getParametersDrivers().size();
355         return Arrays.copyOfRange(parameters, parameters.length - nbAttitudeModelDrivers, parameters.length);
356     }
357 }