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(getMassDerivative(s, getPropulsionModelParameters(parameters)));
173         }
174     }
175 
176     /** {@inheritDoc} */
177     @Override
178     public double getMassDerivative(final SpacecraftState state, final double[] parameters) {
179         if (maneuverTriggers.isFiring(state.getDate(), getManeuverTriggersParameters(parameters))) {
180             return propulsionModel.getMassDerivatives(state, getPropulsionModelParameters(parameters));
181         } else {
182             return 0.;
183         }
184     }
185 
186     /** {@inheritDoc} */
187     @Override
188     public <T extends CalculusFieldElement<T>> void addContribution(final FieldSpacecraftState<T> s,
189                         final FieldTimeDerivativesEquations<T> adder) {
190 
191         // Get the parameters associated to the maneuver (from ForceModel)
192         final T[] parameters = getParameters(s.getDate().getField(), s.getDate());
193 
194         // If the maneuver is active, compute and add its contribution
195         // Maneuver triggers are used to check if the maneuver is currently firing or not
196         // Specific drivers for the triggers are extracted from the array given by the ForceModel interface
197         if (maneuverTriggers.isFiring(s.getDate(), getManeuverTriggersParameters(parameters))) {
198 
199             // Compute thrust acceleration in inertial frame
200             // the acceleration method extracts the parameter in its core, that is why we call it with
201             // parameters and not extracted parameters
202             adder.addNonKeplerianAcceleration(acceleration(s, parameters));
203 
204             // Compute flow rate using the propulsion model
205             // Specific drivers for the propulsion model are extracted from the array given by the ForceModel interface
206             adder.addMassDerivative(getMassDerivative(s, parameters));
207         }
208     }
209 
210     /** {@inheritDoc} */
211     @Override
212     public <T extends CalculusFieldElement<T>> T getMassDerivative(final FieldSpacecraftState<T> state,
213                                                                    final T[] parameters) {
214         if (maneuverTriggers.isFiring(state.getDate(), getManeuverTriggersParameters(parameters))) {
215             return propulsionModel.getMassDerivatives(state, getPropulsionModelParameters(parameters));
216         } else {
217             return state.getMass().getField().getZero();
218         }
219     }
220 
221     /** {@inheritDoc} */
222     @Override
223     public Vector3D acceleration(final SpacecraftState s, final double[] parameters) {
224 
225         // If the maneuver is active, compute and add its contribution
226         // Maneuver triggers are used to check if the maneuver is currently firing or not
227         // Specific drivers for the triggers are extracted from the array given by the ForceModel interface
228         if (maneuverTriggers.isFiring(s.getDate(), getManeuverTriggersParameters(parameters))) {
229 
230             // Attitude during maneuver
231             final Attitude maneuverAttitude;
232             if (attitudeOverride == null) {
233                 maneuverAttitude = s.getAttitude();
234             } else {
235                 final Rotation rotation = attitudeOverride.getAttitudeRotation(s, getAttitudeModelParameters(parameters));
236                 // use dummy rates to build full attitude as they should not be used
237                 maneuverAttitude = new Attitude(s.getDate(), s.getFrame(), rotation, Vector3D.ZERO, Vector3D.ZERO);
238             }
239 
240             // Compute acceleration from propulsion model
241             // Specific drivers for the propulsion model are extracted from the array given by the ForceModel interface
242             return propulsionModel.getAcceleration(s, maneuverAttitude, getPropulsionModelParameters(parameters));
243         } else {
244             // Constant (and null) acceleration when not firing
245             return Vector3D.ZERO;
246         }
247     }
248 
249     /** {@inheritDoc} */
250     @Override
251     public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(final FieldSpacecraftState<T> s, final T[] parameters) {
252 
253         // If the maneuver is active, compute and add its contribution
254         // Maneuver triggers are used to check if the maneuver is currently firing or not
255         // Specific drivers for the triggers are extracted from the array given by the ForceModel interface
256         if (maneuverTriggers.isFiring(s.getDate(), getManeuverTriggersParameters(parameters))) {
257 
258             // Attitude during maneuver
259             final FieldAttitude<T> maneuverAttitude;
260             if (attitudeOverride == null) {
261                 maneuverAttitude = s.getAttitude();
262             } else {
263                 final FieldRotation<T> rotation = attitudeOverride.getAttitudeRotation(s, getAttitudeModelParameters(parameters));
264                 // use dummy rates to build full attitude as they should not be used
265                 final FieldVector3D<T> zeroVector3D = FieldVector3D.getZero(s.getDate().getField());
266                 maneuverAttitude = new FieldAttitude<>(s.getDate(), s.getFrame(), rotation, zeroVector3D, zeroVector3D);
267             }
268 
269             // Compute acceleration from propulsion model
270             // Specific drivers for the propulsion model are extracted from the array given by the ForceModel interface
271             return propulsionModel.getAcceleration(s, maneuverAttitude, getPropulsionModelParameters(parameters));
272         } else {
273             // Constant (and null) acceleration when not firing
274             return FieldVector3D.getZero(s.getMass().getField());
275         }
276     }
277 
278     /** {@inheritDoc} */
279     @Override
280     public Stream<EventDetector> getEventDetectors() {
281         // Event detectors are extracted from both the maneuver triggers and the propulsion model
282         return Stream.concat(maneuverTriggers.getEventDetectors(),
283                              propulsionModel.getEventDetectors());
284     }
285 
286     /** {@inheritDoc} */
287     @Override
288     public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(final Field<T> field) {
289         // Event detectors are extracted from both the maneuver triggers and the propulsion model
290         return Stream.concat(maneuverTriggers.getFieldEventDetectors(field),
291                              propulsionModel.getFieldEventDetectors(field));
292     }
293 
294     /** {@inheritDoc} */
295     @Override
296     public List<ParameterDriver> getParametersDrivers() {
297         // Prepare final drivers' array
298         final List<ParameterDriver> drivers = new ArrayList<>();
299 
300         // Convention: Propulsion drivers are given before maneuver triggers drivers
301         // Add propulsion drivers first
302         drivers.addAll(0, propulsionModel.getParametersDrivers());
303 
304         // Then maneuver triggers' drivers
305         drivers.addAll(drivers.size(), maneuverTriggers.getParametersDrivers());
306 
307         // Then attitude override' drivers if defined
308         if (attitudeOverride != null) {
309             drivers.addAll(drivers.size(), attitudeOverride.getParametersDrivers());
310         }
311 
312         // Return full drivers' array
313         return drivers;
314     }
315 
316     /** Extract propulsion model parameters from the parameters' array called in by the ForceModel interface.
317      *  Convention: Propulsion parameters are given before maneuver triggers parameters
318      * @param parameters parameters' array called in by ForceModel interface
319      * @return propulsion model parameters
320      */
321     public double[] getPropulsionModelParameters(final double[] parameters) {
322         return Arrays.copyOfRange(parameters, 0, propulsionModel.getParametersDrivers().size());
323     }
324 
325     /** Extract propulsion model 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 propulsion model parameters
330      */
331     public <T extends CalculusFieldElement<T>> T[] getPropulsionModelParameters(final T[] parameters) {
332         return Arrays.copyOfRange(parameters, 0, propulsionModel.getParametersDrivers().size());
333     }
334 
335     /** Extract maneuver triggers' parameters from the parameters' array called in by the ForceModel interface.
336      *  Convention: Propulsion parameters are given before maneuver triggers parameters
337      * @param parameters parameters' array called in by ForceModel interface
338      * @return maneuver triggers' parameters
339      */
340     public double[] getManeuverTriggersParameters(final double[] parameters) {
341         final int nbPropulsionModelDrivers = propulsionModel.getParametersDrivers().size();
342         return Arrays.copyOfRange(parameters, nbPropulsionModelDrivers,
343                                   nbPropulsionModelDrivers + maneuverTriggers.getParametersDrivers().size());
344     }
345 
346     /** Extract maneuver triggers' parameters from the parameters' array called in by the ForceModel interface.
347      *  Convention: Propulsion parameters are given before maneuver triggers parameters
348      * @param parameters parameters' array called in by ForceModel interface
349      * @param <T> extends CalculusFieldElement&lt;T&gt;
350      * @return maneuver triggers' parameters
351      */
352     public <T extends CalculusFieldElement<T>> T[] getManeuverTriggersParameters(final T[] parameters) {
353         final int nbPropulsionModelDrivers = propulsionModel.getParametersDrivers().size();
354         return Arrays.copyOfRange(parameters, nbPropulsionModelDrivers,
355                                   nbPropulsionModelDrivers + maneuverTriggers.getParametersDrivers().size());
356     }
357 
358     /** Extract attitude model' parameters from the parameters' array called in by the ForceModel interface.
359      *  Convention: Attitude model parameters are given last
360      * @param parameters parameters' array called in by ForceModel interface
361      * @return maneuver triggers' parameters
362      */
363     protected double[] getAttitudeModelParameters(final double[] parameters) {
364         final int nbAttitudeModelDrivers = (attitudeOverride == null) ? 0 : attitudeOverride.getParametersDrivers().size();
365         return Arrays.copyOfRange(parameters, parameters.length - nbAttitudeModelDrivers, parameters.length);
366     }
367 
368     /** Extract attitude model' parameters from the parameters' array called in by the ForceModel interface.
369      *  Convention: Attitude parameters are given last
370      * @param parameters parameters' array called in by ForceModel interface
371      * @param <T> extends CalculusFieldElement&lt;T&gt;
372      * @return maneuver triggers' parameters
373      */
374     protected <T extends CalculusFieldElement<T>> T[] getAttitudeModelParameters(final T[] parameters) {
375         final int nbAttitudeModelDrivers = (attitudeOverride == null) ? 0 : attitudeOverride.getParametersDrivers().size();
376         return Arrays.copyOfRange(parameters, parameters.length - nbAttitudeModelDrivers, parameters.length);
377     }
378 }