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