1   /* Copyright 2002-2026 Exotrail
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.forces.maneuvers;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
22  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
23  import org.hipparchus.ode.events.Action;
24  import org.hipparchus.util.FastMath;
25  import org.orekit.attitudes.AttitudeProvider;
26  import org.orekit.orbits.FieldCartesianOrbit;
27  import org.orekit.orbits.FieldOrbit;
28  import org.orekit.propagation.FieldSpacecraftState;
29  import org.orekit.propagation.events.FieldDetectorModifier;
30  import org.orekit.propagation.events.FieldEventDetectionSettings;
31  import org.orekit.propagation.events.FieldEventDetector;
32  import org.orekit.propagation.events.handlers.EventHandler;
33  import org.orekit.propagation.events.handlers.FieldEventHandler;
34  import org.orekit.propagation.events.handlers.FieldResetDerivativesOnEvent;
35  import org.orekit.propagation.events.handlers.FieldStopOnDecreasing;
36  import org.orekit.propagation.events.handlers.FieldStopOnEvent;
37  import org.orekit.propagation.events.handlers.FieldStopOnIncreasing;
38  import org.orekit.propagation.events.handlers.ResetDerivativesOnEvent;
39  import org.orekit.propagation.events.handlers.StopOnDecreasing;
40  import org.orekit.propagation.events.handlers.StopOnIncreasing;
41  import org.orekit.time.FieldAbsoluteDate;
42  import org.orekit.utils.Constants;
43  import org.orekit.utils.FieldAbsolutePVCoordinates;
44  import org.orekit.utils.FieldPVCoordinates;
45  
46  /** Impulse maneuver model for propagators working with Fields.
47   * <p>This class implements an impulse maneuver as a discrete event
48   * that can be provided to any {@link org.orekit.propagation.FieldPropagator
49   * Propagator} and mirrors the standard version
50   * {@link org.orekit.forces.maneuvers.ImpulseManeuver}.</p>
51   * <p>The maneuver is executed when an underlying event is triggered and the handler returns anything but {@link Action#CONTINUE CONTINUE},
52   * in which case this class will generate a {@link Action#RESET_STATE RESET_STATE} event.
53   * In the simple cases, the underlying event detector may be a basic
54   * {@link org.orekit.propagation.events.FieldDateDetector date event}, but it
55   * can also be a more elaborate {@link
56   * org.orekit.propagation.events.FieldApsideDetector apside event} for apogee
57   * maneuvers for example.</p>
58   * <p>The maneuver velocity increment is defined via {@link FieldImpulseProvider}.
59   * If no AttitudeProvider is given, the current attitude of the spacecraft,
60   * defined by the current spacecraft state, will be used as the
61   * {@link AttitudeProvider} so the velocity increment should be given in
62   * the same pseudoinertial frame as the {@link FieldSpacecraftState} used to
63   * construct the propagator that will handle the maneuver.
64   * If an AttitudeProvider is given, the velocity increment given should be
65   * defined appropriately in consideration of that provider. So, a typical
66   * case for tangential maneuvers is to provide a {@link org.orekit.attitudes.LofOffset LOF aligned}
67   * attitude provider along with a velocity increment defined in accordance with
68   * that LOF aligned attitude provider; e.g. if the LOF aligned attitude provider
69   * was constructed using LOFType.VNC the velocity increment should be
70   * provided in VNC coordinates.</p>
71   * <p>The norm through which the delta-V maps to the mass consumption is chosen via the
72   * enum {@link Control3DVectorCostType}. Default is Euclidean. </p>
73   * <p>Beware that the triggering event detector must behave properly both
74   * before and after maneuver. If for example a node detector is used to trigger
75   * an inclination maneuver and the maneuver change the orbit to an equatorial one,
76   * the node detector will fail just after the maneuver, being unable to find a
77   * node on an equatorial orbit! This is a real case that has been encountered
78   * during validation ...</p>
79   * @see org.orekit.propagation.FieldPropagator#addEventDetector(FieldEventDetector)
80   * @see org.orekit.forces.maneuvers.ImpulseManeuver
81   * @author Romain Serra
82   * @since 12.0
83   * @param <T> type of the field elements
84   */
85  public class FieldImpulseManeuver<T extends CalculusFieldElement<T>> extends AbstractImpulseManeuver
86          implements FieldDetectorModifier<T> {
87  
88      /** Triggering event. */
89      private final FieldEventDetector<T> trigger;
90  
91      /** Specific impulse. */
92      private final T isp;
93  
94      /** Engine exhaust velocity. */
95      private final T vExhaust;
96  
97      /** Trigger's detection settings. */
98      private final FieldEventDetectionSettings<T> detectionSettings;
99  
100     /** Specific event handler. */
101     private final Handler handler;
102 
103     /** Field impulse provider. */
104     private final FieldImpulseProvider<T> fieldImpulseProvider;
105 
106     /** Indicator for forward propagation. */
107     private boolean forward;
108 
109     /** Build a new instance.
110      * @param trigger triggering event
111      * @param deltaVSat velocity increment in satellite frame
112      * @param isp engine specific impulse (s)
113      */
114     public FieldImpulseManeuver(final FieldEventDetector<T> trigger, final FieldVector3D<T> deltaVSat, final T isp) {
115         this(trigger, null, deltaVSat, isp);
116     }
117 
118     /** Build a new instance.
119      * @param trigger triggering event
120      * @param attitudeOverride the attitude provider to use for the maneuver
121      * @param deltaVSat velocity increment in satellite frame
122      * @param isp engine specific impulse (s)
123      */
124     public FieldImpulseManeuver(final FieldEventDetector<T> trigger, final AttitudeProvider attitudeOverride,
125                                 final FieldVector3D<T> deltaVSat, final T isp) {
126         this(trigger, attitudeOverride, FieldImpulseProvider.of(deltaVSat), isp, Control3DVectorCostType.TWO_NORM);
127     }
128 
129     /** Build a new instance.
130      * @param trigger triggering event
131      * @param attitudeOverride the attitude provider to use for the maneuver
132      * @param fieldImpulseProvider impulse provider
133      * @param isp engine specific impulse (s)
134      * @param control3DVectorCostType increment's norm for mass consumption
135      */
136     public FieldImpulseManeuver(final FieldEventDetector<T> trigger, final AttitudeProvider attitudeOverride,
137                                 final FieldImpulseProvider<T> fieldImpulseProvider, final T isp,
138                                 final Control3DVectorCostType control3DVectorCostType) {
139         this(trigger, trigger.getDetectionSettings(), attitudeOverride, fieldImpulseProvider, isp, control3DVectorCostType);
140     }
141 
142     /** Private constructor.
143      * @param trigger triggering event
144      * @param detectionSettings event detection settings
145      * @param attitudeOverride the attitude provider to use for the maneuver
146      * @param fieldImpulseProvider impulse provider
147      * @param isp engine specific impulse (s)
148      * @param control3DVectorCostType increment's norm for mass consumption
149      */
150     private FieldImpulseManeuver(final FieldEventDetector<T> trigger,
151                                  final FieldEventDetectionSettings<T> detectionSettings,
152                                  final AttitudeProvider attitudeOverride, final FieldImpulseProvider<T> fieldImpulseProvider,
153                                  final T isp, final Control3DVectorCostType control3DVectorCostType) {
154         super(attitudeOverride, control3DVectorCostType);
155         this.trigger = trigger;
156         this.detectionSettings = detectionSettings;
157         this.fieldImpulseProvider = fieldImpulseProvider;
158         this.isp = isp;
159         this.vExhaust = this.isp.multiply(Constants.G0_STANDARD_GRAVITY);
160         this.handler = new Handler();
161     }
162 
163     /**
164      * Constructor from non-Field impulse maneuver.
165      * The conversion of the trigger might not work as expected in specific cases and one might need to build it by hand.
166      * @param field field
167      * @param maneuver non-Field impulse maneuver
168      * @since 14.0
169      */
170     public FieldImpulseManeuver(final Field<T> field, final ImpulseManeuver maneuver) {
171         this(FieldEventDetector.of(field, convertHandler(maneuver.getDetector().getHandler()), maneuver.getDetector()),
172                 maneuver.getAttitudeOverride(), FieldImpulseProvider.of(maneuver.getImpulseProvider()),
173                 field.getZero().newInstance(maneuver.getIsp()), maneuver.getControl3DVectorCostType());
174     }
175 
176     private static <T extends CalculusFieldElement<T>> FieldEventHandler<T> convertHandler(final EventHandler eventHandler) {
177         if (eventHandler instanceof StopOnDecreasing) {
178             return new FieldStopOnDecreasing<>();
179         } else if (eventHandler instanceof StopOnIncreasing) {
180             return new FieldStopOnIncreasing<>();
181         } else if (eventHandler instanceof ResetDerivativesOnEvent) {
182             return new FieldResetDerivativesOnEvent<>();
183         } else {
184             return new FieldStopOnEvent<>();
185         }
186     }
187 
188     /**
189      * Creates a copy with different event detection settings.
190      * @param eventDetectionSettings new detection settings
191      * @return a new detector with same properties except for the detection settings
192      */
193     public FieldImpulseManeuver<T> withDetectionSettings(final FieldEventDetectionSettings<T> eventDetectionSettings) {
194         return new FieldImpulseManeuver<>(trigger, eventDetectionSettings, getAttitudeOverride(), fieldImpulseProvider, isp,
195                 getControl3DVectorCostType());
196     }
197 
198     /** {@inheritDoc} */
199     @Override
200     public void init(final FieldSpacecraftState<T> s0, final FieldAbsoluteDate<T> t) {
201         FieldDetectorModifier.super.init(s0, t);
202         forward = t.durationFrom(s0.getDate()).getReal() >= 0;
203         fieldImpulseProvider.init(s0, t);
204     }
205 
206     /** {@inheritDoc} */
207     @Override
208     public void finish(final FieldSpacecraftState<T> state) {
209         FieldDetectorModifier.super.finish(state);
210         fieldImpulseProvider.finish(state);
211     }
212 
213     /** {@inheritDoc} */
214     @Override
215     public FieldEventDetectionSettings<T> getDetectionSettings() {
216         return detectionSettings;
217     }
218 
219     /** {@inheritDoc} */
220     @Override
221     public FieldEventDetector<T> getDetector() {
222         return trigger;
223     }
224 
225     /** Get the triggering event.
226      * @return triggering event
227      */
228     public FieldEventDetector<T> getTrigger() {
229         return getDetector();
230     }
231 
232     /**
233      * Getter for the impulse provider.
234      * @return impulse provider
235      * @since 13.0
236      */
237     public FieldImpulseProvider<T> getFieldImpulseProvider() {
238         return fieldImpulseProvider;
239     }
240 
241     /** Get the specific impulse.
242      * @return specific impulse
243      */
244     public T getIsp() {
245         return isp;
246     }
247 
248     @Override
249     public FieldEventHandler<T> getHandler() {
250         return handler;
251     }
252 
253     /** Local handler. */
254     private class Handler implements FieldEventHandler<T> {
255 
256         @Override
257         public void init(final FieldSpacecraftState<T> initialState, final FieldAbsoluteDate<T> target,
258                          final FieldEventDetector<T> detector) {
259             getDetector().getHandler().init(initialState, target, getDetector());
260         }
261 
262         @Override
263         public void finish(final FieldSpacecraftState<T> finalState, final FieldEventDetector<T> detector) {
264             getDetector().getHandler().finish(finalState, getDetector());
265         }
266 
267         /** {@inheritDoc} */
268         @Override
269         public Action eventOccurred(final FieldSpacecraftState<T> s,
270                                     final FieldEventDetector<T> detector,
271                                     final boolean increasing) {
272             final Action action = getDetector().getHandler().eventOccurred(s, getDetector(), increasing);
273             return action == Action.CONTINUE ? action : Action.RESET_STATE;
274         }
275 
276         /** {@inheritDoc} */
277         @Override
278         public FieldSpacecraftState<T> resetState(final FieldEventDetector<T> detector,
279                                                   final FieldSpacecraftState<T> oldState) {
280 
281             final FieldImpulseManeuver<T> im = (FieldImpulseManeuver<T>) detector;
282             final FieldAbsoluteDate<T> date = oldState.getDate();
283             final boolean isStateOrbitDefined = oldState.isOrbitDefined();
284 
285             final FieldRotation<T> rotation;
286             if (im.getAttitudeOverride() == null) {
287                 rotation = oldState.getAttitude().getRotation();
288             } else {
289                 rotation = im.getAttitudeOverride().getAttitudeRotation(isStateOrbitDefined ? oldState.getOrbit() : oldState.getAbsPVA(),
290                         date, oldState.getFrame());
291             }
292 
293             // convert velocity increment in inertial frame
294             final FieldVector3D<T> deltaVSat = im.fieldImpulseProvider.getImpulse(oldState, im.forward);
295             final FieldVector3D<T> deltaV = rotation.applyInverseTo(deltaVSat);
296             final T one = oldState.getMass().getField().getOne();
297             final T sign = (im.forward) ? one : one.negate();
298 
299             // apply increment to position/velocity
300             final FieldPVCoordinates<T> oldPV = oldState.getPVCoordinates();
301             final FieldVector3D<T> newVelocity = oldPV.getVelocity().add(deltaV);
302             final FieldPVCoordinates<T> newPV = new FieldPVCoordinates<>(oldPV.getPosition(), newVelocity);
303 
304             // compute new mass
305             final T normDeltaV = im.getControl3DVectorCostType().evaluate(deltaVSat);
306             final T newMass = oldState.getMass().multiply(FastMath.exp(normDeltaV.multiply(sign.negate()).divide(im.vExhaust)));
307 
308             // pack everything in a new state
309             if (oldState.isOrbitDefined()) {
310                 final FieldOrbit<T> newOrbit = new FieldCartesianOrbit<>(newPV, oldState.getFrame(), oldState.getDate(),
311                         oldState.getOrbit().getMu());
312                 return new FieldSpacecraftState<>(oldState.getOrbit().getType().normalize(newOrbit, oldState.getOrbit()),
313                         oldState.getAttitude(), newMass, oldState.getAdditionalDataValues(),
314                         oldState.getAdditionalStatesDerivatives());
315             } else {
316                 final FieldAbsolutePVCoordinates<T> newAbsPVA = new FieldAbsolutePVCoordinates<>(oldState.getFrame(),
317                         oldState.getDate(), newPV);
318                 return new FieldSpacecraftState<>(newAbsPVA, oldState.getAttitude(), newMass,
319                         oldState.getAdditionalDataValues(), oldState.getAdditionalStatesDerivatives());
320             }
321         }
322 
323     }
324 }