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  package org.orekit.forces.maneuvers;
18  
19  import org.hipparchus.geometry.euclidean.threed.Rotation;
20  import org.hipparchus.geometry.euclidean.threed.Vector3D;
21  import org.hipparchus.ode.events.Action;
22  import org.hipparchus.util.FastMath;
23  import org.orekit.attitudes.AttitudeProvider;
24  import org.orekit.forces.maneuvers.propulsion.ThrustPropulsionModel;
25  import org.orekit.orbits.CartesianOrbit;
26  import org.orekit.propagation.SpacecraftState;
27  import org.orekit.propagation.events.DetectorModifier;
28  import org.orekit.propagation.events.EventDetectionSettings;
29  import org.orekit.propagation.events.EventDetector;
30  import org.orekit.propagation.events.handlers.EventHandler;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.utils.AbsolutePVCoordinates;
33  import org.orekit.utils.PVCoordinates;
34  
35  /** Impulse maneuver model.
36   * <p>This class implements an impulse maneuver as a discrete event
37   * that can be provided to any {@link org.orekit.propagation.Propagator
38   * Propagator}.</p>
39   * <p>The maneuver is executed when an underlying event is triggered and the handler returns anything but {@link Action#CONTINUE CONTINUE},
40   * in which case this class will generate a {@link Action#RESET_STATE RESET_STATE} event.
41   * By default, the detection settings are those of the trigger.
42   * In the simple cases, the underlying event detector may be a basic
43   * {@link org.orekit.propagation.events.DateDetector date event}, but it
44   * can also be a more elaborate {@link
45   * org.orekit.propagation.events.ApsideDetector apside event} for apogee
46   * maneuvers for example.</p>
47   * <p>The maneuver velocity increment is defined via {@link ImpulseProvider}.
48   * If no AttitudeProvider is given, the current attitude of the spacecraft,
49   * defined by the current spacecraft state, will be used as the
50   * {@link AttitudeProvider} so the velocity increment should be given in
51   * the same pseudoinertial frame as the {@link SpacecraftState} used to
52   * construct the propagator that will handle the maneuver.
53   * If an AttitudeProvider is given, the velocity increment given should be
54   * defined appropriately in consideration of that provider. So, a typical
55   * case for tangential maneuvers is to provide a {@link org.orekit.attitudes.LofOffset LOF aligned}
56   * attitude provider along with a velocity increment defined in accordance with
57   * that LOF aligned attitude provider; e.g. if the LOF aligned attitude provider
58   * was constructed using LOFType.VNC the velocity increment should be
59   * provided in VNC coordinates.</p>
60   * <p>The norm through which the delta-V maps to the mass consumption is chosen via the
61   * enum {@link Control3DVectorCostType}. Default is Euclidean. </p>
62   * <p>Beware that the triggering event detector must behave properly both
63   * before and after maneuver. If for example a node detector is used to trigger
64   * an inclination maneuver and the maneuver change the orbit to an equatorial one,
65   * the node detector will fail just after the maneuver, being unable to find a
66   * node on an equatorial orbit! This is a real case that has been encountered
67   * during validation ...</p>
68   * @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
69   * @author Luc Maisonobe
70   */
71  public class ImpulseManeuver extends AbstractImpulseManeuver implements DetectorModifier {
72  
73      /** Triggering event. */
74      private final EventDetector trigger;
75  
76      /** Specific impulse. */
77      private final double isp;
78  
79      /** Engine exhaust velocity. */
80      private final double vExhaust;
81  
82      /** Trigger's detection settings. */
83      private final EventDetectionSettings detectionSettings;
84  
85      /** Specific event handler. */
86      private final Handler handler;
87  
88      /** Impulse provider. */
89      private final ImpulseProvider impulseProvider;
90  
91      /** Indicator for forward propagation. */
92      private boolean forward;
93  
94      /** Build a new instance.
95       * @param trigger triggering event
96       * @param deltaVSat velocity increment in satellite frame
97       * @param isp engine specific impulse (s)
98       */
99      public ImpulseManeuver(final EventDetector trigger, final Vector3D deltaVSat, final double isp) {
100         this(trigger, null, deltaVSat, isp);
101     }
102 
103     /** Build a new instance.
104      * @param trigger triggering event
105      * @param attitudeOverride the attitude provider to use for the maneuver
106      * @param deltaVSat velocity increment in satellite frame
107      * @param isp engine specific impulse (s)
108      */
109     public ImpulseManeuver(final EventDetector trigger, final AttitudeProvider attitudeOverride,
110                            final Vector3D deltaVSat, final double isp) {
111         this(trigger, attitudeOverride, ImpulseProvider.of(deltaVSat), isp, Control3DVectorCostType.TWO_NORM);
112     }
113 
114     /** Build a new instance.
115      * @param trigger triggering event
116      * @param attitudeOverride the attitude provider to use for the maneuver
117      * @param impulseProvider impulse provider
118      * @param isp engine specific impulse (s)
119      * @param control3DVectorCostType increment's norm for mass consumption
120      * @since 13.0
121      */
122     public ImpulseManeuver(final EventDetector trigger, final AttitudeProvider attitudeOverride,
123                            final ImpulseProvider impulseProvider, final double isp, final Control3DVectorCostType control3DVectorCostType) {
124         this(trigger, trigger.getDetectionSettings(), attitudeOverride, impulseProvider, isp, control3DVectorCostType);
125     }
126 
127     /** Private constructor.
128      * @param trigger triggering event
129      * @param detectionSettings event detection settings
130      * @param attitudeOverride the attitude provider to use for the maneuver
131      * @param impulseProvider impulse provider
132      * @param isp engine specific impulse (s)
133      * @param control3DVectorCostType increment's norm for mass consumption
134      * @since 13.0
135      */
136     private ImpulseManeuver(final EventDetector trigger, final EventDetectionSettings detectionSettings,
137                             final AttitudeProvider attitudeOverride, final ImpulseProvider impulseProvider,
138                             final double isp, final Control3DVectorCostType control3DVectorCostType) {
139         super(attitudeOverride, control3DVectorCostType);
140         this.trigger   = trigger;
141         this.detectionSettings = detectionSettings;
142         this.impulseProvider = impulseProvider;
143         this.isp       = isp;
144         this.vExhaust  = ThrustPropulsionModel.getExhaustVelocity(isp);
145         this.handler = new Handler();
146     }
147 
148     /**
149      * Creates a copy with different event detection settings.
150      * @param eventDetectionSettings new detection settings
151      * @return a new detector with same properties except for the detection settings
152      */
153     public ImpulseManeuver withDetectionSettings(final EventDetectionSettings eventDetectionSettings) {
154         return new ImpulseManeuver(trigger, eventDetectionSettings, getAttitudeOverride(), impulseProvider, isp,
155                 getControl3DVectorCostType());
156     }
157 
158     /** {@inheritDoc} */
159     @Override
160     public void init(final SpacecraftState s0, final AbsoluteDate t) {
161         DetectorModifier.super.init(s0, t);
162         forward = t.durationFrom(s0.getDate()) >= 0;
163         impulseProvider.init(s0, t);
164     }
165 
166     /** {@inheritDoc} */
167     @Override
168     public void finish(final SpacecraftState state) {
169         DetectorModifier.super.finish(state);
170         impulseProvider.finish(state);
171     }
172 
173     /** {@inheritDoc} */
174     @Override
175     public EventDetector getDetector() {
176         return getTrigger();
177     }
178 
179     /** {@inheritDoc} */
180     @Override
181     public EventHandler getHandler() {
182         return handler;
183     }
184 
185     /** {@inheritDoc} */
186     @Override
187     public EventDetectionSettings getDetectionSettings() {
188         return detectionSettings;
189     }
190 
191     /** Get the triggering event.
192      * @return triggering event
193      */
194     public EventDetector getTrigger() {
195         return trigger;
196     }
197 
198     /**
199      * Getter for the impulse provider.
200      * @return impulse provider
201      * @since 13.0
202      */
203     public ImpulseProvider getImpulseProvider() {
204         return impulseProvider;
205     }
206 
207     /** Get the specific impulse.
208     * @return specific impulse
209     */
210     public double getIsp() {
211         return isp;
212     }
213 
214     /** Local handler. */
215     private class Handler implements EventHandler {
216 
217         @Override
218         public void init(final SpacecraftState initialState, final AbsoluteDate target, final EventDetector detector) {
219             getDetector().getHandler().init(initialState, target, getDetector());
220         }
221 
222         @Override
223         public void finish(final SpacecraftState finalState, final EventDetector detector) {
224             getDetector().getHandler().finish(finalState, getDetector());
225         }
226 
227         /** {@inheritDoc} */
228         public Action eventOccurred(final SpacecraftState s, final EventDetector detector,
229                                     final boolean increasing) {
230             final Action action = getDetector().getHandler().eventOccurred(s, getDetector(), increasing);
231             return action == Action.CONTINUE ? action : Action.RESET_STATE;
232         }
233 
234         /** {@inheritDoc} */
235         @Override
236         public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
237 
238             final ImpulseManeuver im = (ImpulseManeuver) detector;
239             final AbsoluteDate date = oldState.getDate();
240             final AttitudeProvider override = im.getAttitudeOverride();
241             final boolean isStateOrbitDefined = oldState.isOrbitDefined();
242 
243             final Rotation rotation;
244             if (override == null) {
245                 rotation = oldState.getAttitude().getRotation();
246             } else {
247                 rotation = override.getAttitudeRotation(isStateOrbitDefined ? oldState.getOrbit() : oldState.getAbsPVA(),
248                         date, oldState.getFrame());
249             }
250 
251             // convert velocity increment in inertial frame
252             final Vector3D deltaVSat = im.impulseProvider.getImpulse(oldState, im.forward);
253             final Vector3D deltaV = rotation.applyInverseTo(deltaVSat);
254 
255             // apply increment to position/velocity
256             final PVCoordinates oldPV = oldState.getPVCoordinates();
257             final Vector3D newVelocity = oldPV.getVelocity().add(deltaV);
258             final PVCoordinates newPV = new PVCoordinates(oldPV.getPosition(), newVelocity);
259 
260             // compute new mass
261             final double normDeltaV = im.getControl3DVectorCostType().evaluate(deltaVSat);
262             final double sign     = im.forward ? +1 : -1;
263             final double newMass = oldState.getMass() * FastMath.exp(-sign * normDeltaV / im.vExhaust);
264 
265             // pack everything in a new state
266             if (oldState.isOrbitDefined()) {
267                 final CartesianOrbit newOrbit = new CartesianOrbit(newPV, oldState.getFrame(), oldState.getDate(),
268                         oldState.getOrbit().getMu());
269                 return new SpacecraftState(oldState.getOrbit().getType().normalize(newOrbit, oldState.getOrbit()),
270                         oldState.getAttitude(), newMass, oldState.getAdditionalDataValues(), oldState.getAdditionalStatesDerivatives());
271             } else {
272                 final AbsolutePVCoordinates newAPV = new AbsolutePVCoordinates(oldState.getFrame(), oldState.getDate(),
273                         newPV);
274                 return new SpacecraftState(newAPV, oldState.getAttitude(), newMass,
275                         oldState.getAdditionalDataValues(), oldState.getAdditionalStatesDerivatives());
276             }
277         }
278 
279     }
280 
281 }