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 }