1 /* Copyright 2002-2024 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.orbits.CartesianOrbit;
25 import org.orekit.propagation.SpacecraftState;
26 import org.orekit.propagation.events.AbstractDetector;
27 import org.orekit.propagation.events.AdaptableInterval;
28 import org.orekit.propagation.events.EventDetector;
29 import org.orekit.propagation.events.handlers.EventHandler;
30 import org.orekit.time.AbsoluteDate;
31 import org.orekit.utils.Constants;
32 import org.orekit.utils.DoubleArrayDictionary;
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 triggered when an underlying event generates a
40 * {@link Action#STOP STOP} event, in which case this class will generate a {@link
41 * Action#RESET_STATE RESET_STATE}
42 * event (the stop event from the underlying object is therefore filtered out).
43 * In the simple cases, the underlying event detector may be a basic
44 * {@link org.orekit.propagation.events.DateDetector date event}, but it
45 * can also be a more elaborate {@link
46 * org.orekit.propagation.events.ApsideDetector apside event} for apogee
47 * maneuvers for example.</p>
48 * <p>The maneuver is defined by a single velocity increment.
49 * If no AttitudeProvider is given, the current attitude of the spacecraft,
50 * defined by the current spacecraft state, will be used as the
51 * {@link AttitudeProvider} so the velocity increment should be given in
52 * the same pseudoinertial frame as the {@link SpacecraftState} used to
53 * construct the propagator that will handle the maneuver.
54 * If an AttitudeProvider is given, the velocity increment given should be
55 * defined appropriately in consideration of that provider. So, a typical
56 * case for tangential maneuvers is to provide a {@link org.orekit.attitudes.LofOffset LOF aligned}
57 * attitude provider along with a velocity increment defined in accordance with
58 * that LOF aligned attitude provider; e.g. if the LOF aligned attitude provider
59 * was constructed using LOFType.VNC the velocity increment should be
60 * provided in VNC coordinates.</p>
61 * <p>The norm through which the delta-V maps to the mass consumption is chosen via the
62 * enum {@link Control3DVectorCostType}. Default is Euclidean. </p>
63 * <p>Beware that the triggering event detector must behave properly both
64 * before and after maneuver. If for example a node detector is used to trigger
65 * an inclination maneuver and the maneuver change the orbit to an equatorial one,
66 * the node detector will fail just after the maneuver, being unable to find a
67 * node on an equatorial orbit! This is a real case that has been encountered
68 * during validation ...</p>
69 * @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
70 * @author Luc Maisonobe
71 */
72 public class ImpulseManeuver extends AbstractDetector<ImpulseManeuver> {
73
74 /** The attitude to override during the maneuver, if set. */
75 private final AttitudeProvider attitudeOverride;
76
77 /** Triggering event. */
78 private final EventDetector trigger;
79
80 /** Velocity increment in satellite frame. */
81 private final Vector3D deltaVSat;
82
83 /** Specific impulse. */
84 private final double isp;
85
86 /** Engine exhaust velocity. */
87 private final double vExhaust;
88
89 /** Indicator for forward propagation. */
90 private boolean forward;
91
92 /** Type of norm linking delta-V to mass consumption. */
93 private final Control3DVectorCostType control3DVectorCostType;
94
95 /** Build a new instance.
96 * @param trigger triggering event
97 * @param deltaVSat velocity increment in satellite frame
98 * @param isp engine specific impulse (s)
99 */
100 public ImpulseManeuver(final EventDetector trigger, final Vector3D deltaVSat, final double isp) {
101 this(trigger, null, deltaVSat, isp);
102 }
103
104
105 /** Build a new instance.
106 * @param trigger triggering event
107 * @param attitudeOverride the attitude provider to use for the maneuver
108 * @param deltaVSat velocity increment in satellite frame
109 * @param isp engine specific impulse (s)
110 */
111 public ImpulseManeuver(final EventDetector trigger, final AttitudeProvider attitudeOverride,
112 final Vector3D deltaVSat, final double isp) {
113 this(trigger.getMaxCheckInterval(), trigger.getThreshold(),
114 trigger.getMaxIterationCount(), new Handler(),
115 trigger, attitudeOverride, deltaVSat, isp, Control3DVectorCostType.TWO_NORM);
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 * @param control3DVectorCostType increment's norm for mass consumption
124 */
125 public ImpulseManeuver(final EventDetector trigger, final AttitudeProvider attitudeOverride,
126 final Vector3D deltaVSat, final double isp, final Control3DVectorCostType control3DVectorCostType) {
127 this(trigger.getMaxCheckInterval(), trigger.getThreshold(),
128 trigger.getMaxIterationCount(), new Handler(),
129 trigger, attitudeOverride, deltaVSat, isp, control3DVectorCostType);
130 }
131
132 /** Protected constructor with full parameters.
133 * <p>
134 * This constructor is not public as users are expected to use the builder
135 * API with the various {@code withXxx()} methods to set up the instance
136 * in a readable manner without using a huge amount of parameters.
137 * </p>
138 * @param maxCheck maximum checking interval
139 * @param threshold convergence threshold (s)
140 * @param maxIter maximum number of iterations in the event time search
141 * @param handler event handler to call at event occurrences
142 * @param trigger triggering event
143 * @param attitudeOverride the attitude provider to use for the maneuver
144 * @param deltaVSat velocity increment in satellite frame
145 * @param isp engine specific impulse (s)
146 * @param control3DVectorCostType increment's norm for mass consumption
147 * @since 6.1
148 */
149 protected ImpulseManeuver(final AdaptableInterval maxCheck, final double threshold,
150 final int maxIter, final EventHandler handler,
151 final EventDetector trigger, final AttitudeProvider attitudeOverride, final Vector3D deltaVSat,
152 final double isp, final Control3DVectorCostType control3DVectorCostType) {
153 super(maxCheck, threshold, maxIter, handler);
154 this.attitudeOverride = attitudeOverride;
155 this.trigger = trigger;
156 this.deltaVSat = deltaVSat;
157 this.isp = isp;
158 this.vExhaust = Constants.G0_STANDARD_GRAVITY * isp;
159 this.control3DVectorCostType = control3DVectorCostType;
160 }
161
162 /** {@inheritDoc} */
163 @Override
164 protected ImpulseManeuver create(final AdaptableInterval newMaxCheck, final double newThreshold,
165 final int newMaxIter, final EventHandler newHandler) {
166 return new ImpulseManeuver(newMaxCheck, newThreshold, newMaxIter, newHandler,
167 trigger, attitudeOverride, deltaVSat, isp, control3DVectorCostType);
168 }
169
170 /** {@inheritDoc} */
171 public void init(final SpacecraftState s0, final AbsoluteDate t) {
172 forward = t.durationFrom(s0.getDate()) >= 0;
173 // Initialize the triggering event
174 trigger.init(s0, t);
175 }
176
177 /** {@inheritDoc} */
178 public double g(final SpacecraftState s) {
179 return trigger.g(s);
180 }
181
182 /**
183 * Get the Attitude Provider to use during maneuver.
184 * @return the attitude provider
185 */
186 public AttitudeProvider getAttitudeOverride() {
187 return attitudeOverride;
188 }
189
190 /** Get the triggering event.
191 * @return triggering event
192 */
193 public EventDetector getTrigger() {
194 return trigger;
195 }
196
197 /** Get the velocity increment in satellite frame.
198 * @return velocity increment in satellite frame
199 */
200 public Vector3D getDeltaVSat() {
201 return deltaVSat;
202 }
203
204 /** Get the specific impulse.
205 * @return specific impulse
206 */
207 public double getIsp() {
208 return isp;
209 }
210
211 /** Get the control vector's cost type.
212 * @return control cost type
213 * @since 12.0
214 */
215 public Control3DVectorCostType getControl3DVectorCostType() {
216 return control3DVectorCostType;
217 }
218
219 /** Local handler. */
220 private static class Handler implements EventHandler {
221
222 /** {@inheritDoc} */
223 public Action eventOccurred(final SpacecraftState s, final EventDetector detector,
224 final boolean increasing) {
225
226 // filter underlying event
227 final ImpulseManeuver im = (ImpulseManeuver) detector;
228 final Action underlyingAction = im.trigger.getHandler().eventOccurred(s, im.trigger, increasing);
229
230 return (underlyingAction == Action.STOP) ? Action.RESET_STATE : Action.CONTINUE;
231
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 Rotation rotation;
242
243 if (override == null) {
244 rotation = oldState.getAttitude().getRotation();
245 } else {
246 rotation = override.getAttitudeRotation(oldState.getOrbit(), date, oldState.getFrame());
247 }
248
249 // convert velocity increment in inertial frame
250 final Vector3D deltaV = rotation.applyInverseTo(im.deltaVSat);
251 final double sign = im.forward ? +1 : -1;
252
253 // apply increment to position/velocity
254 final PVCoordinates oldPV = oldState.getPVCoordinates();
255 final PVCoordinates newPV =
256 new PVCoordinates(oldPV.getPosition(),
257 new Vector3D(1, oldPV.getVelocity(), sign, deltaV));
258 final CartesianOrbit newOrbit =
259 new CartesianOrbit(newPV, oldState.getFrame(), date, oldState.getMu());
260
261 // compute new mass
262 final double normDeltaV = im.control3DVectorCostType.evaluate(im.deltaVSat);
263 final double newMass = oldState.getMass() * FastMath.exp(-sign * normDeltaV / im.vExhaust);
264
265 // pack everything in a new state
266 SpacecraftState newState = new SpacecraftState(oldState.getOrbit().getType().normalize(newOrbit, oldState.getOrbit()),
267 oldState.getAttitude(), newMass);
268 for (final DoubleArrayDictionary.Entry entry : oldState.getAdditionalStatesValues().getData()) {
269 newState = newState.addAdditionalState(entry.getKey(), entry.getValue());
270 }
271 for (final DoubleArrayDictionary.Entry entry : oldState.getAdditionalStatesDerivatives().getData()) {
272 newState = newState.addAdditionalStateDerivative(entry.getKey(), entry.getValue());
273 }
274 return newState;
275
276 }
277
278 }
279
280 }