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