1   /* Copyright 2002-2013 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (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.propagation.numerical;
18  
19  import java.io.NotSerializableException;
20  import java.io.Serializable;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.List;
24  
25  import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
26  import org.apache.commons.math3.ode.AbstractIntegrator;
27  import org.apache.commons.math3.util.FastMath;
28  import org.orekit.attitudes.Attitude;
29  import org.orekit.attitudes.AttitudeProvider;
30  import org.orekit.errors.OrekitException;
31  import org.orekit.errors.OrekitMessages;
32  import org.orekit.errors.PropagationException;
33  import org.orekit.forces.ForceModel;
34  import org.orekit.forces.gravity.NewtonianAttraction;
35  import org.orekit.frames.Frame;
36  import org.orekit.frames.Transform;
37  import org.orekit.orbits.Orbit;
38  import org.orekit.orbits.OrbitType;
39  import org.orekit.orbits.PositionAngle;
40  import org.orekit.propagation.SpacecraftState;
41  import org.orekit.propagation.events.EventDetector;
42  import org.orekit.propagation.integration.AbstractIntegratedPropagator;
43  import org.orekit.propagation.integration.StateMapper;
44  import org.orekit.time.AbsoluteDate;
45  import org.orekit.utils.PVCoordinates;
46  
47  /** This class propagates {@link org.orekit.orbits.Orbit orbits} using
48   * numerical integration.
49   * <p>Numerical propagation is much more accurate than analytical propagation
50   * like for example {@link org.orekit.propagation.analytical.KeplerianPropagator
51   * keplerian} or {@link org.orekit.propagation.analytical.EcksteinHechlerPropagator
52   * Eckstein-Hechler}, but requires a few more steps to set up to be used properly.
53   * Whereas analytical propagators are configured only thanks to their various
54   * constructors and can be used immediately after construction, numerical propagators
55   * configuration involve setting several parameters between construction time
56   * and propagation time.</p>
57   * <p>The configuration parameters that can be set are:</p>
58   * <ul>
59   *   <li>the initial spacecraft state ({@link #setInitialState(SpacecraftState)})</li>
60   *   <li>the central attraction coefficient ({@link #setMu(double)})</li>
61   *   <li>the various force models ({@link #addForceModel(ForceModel)},
62   *   {@link #removeForceModels()})</li>
63   *   <li>the {@link OrbitType type} of orbital parameters to be used for propagation
64   *   ({@link #setOrbitType(OrbitType)}),
65   *   <li>the {@link PositionAngle type} of position angle to be used in orbital parameters
66   *   to be used for propagation where it is relevant ({@link
67   *   #setPositionAngleType(PositionAngle)}),
68   *   <li>whether {@link org.orekit.propagation.integration.AdditionalEquations additional equations}
69   *   (for example {@link PartialDerivativesEquations Jacobians}) should be propagated along with orbital state
70   *   ({@link #addAdditionalEquations(org.orekit.propagation.integration.AdditionalEquations)}),
71   *   <li>the discrete events that should be triggered during propagation
72   *   ({@link #addEventDetector(EventDetector)},
73   *   {@link #clearEventsDetectors()})</li>
74   *   <li>the binding logic with the rest of the application ({@link #setSlaveMode()},
75   *   {@link #setMasterMode(double, org.orekit.propagation.sampling.OrekitFixedStepHandler)},
76   *   {@link #setMasterMode(org.orekit.propagation.sampling.OrekitStepHandler)},
77   *   {@link #setEphemerisMode()}, {@link #getGeneratedEphemeris()})</li>
78   * </ul>
79   * <p>From these configuration parameters, only the initial state is mandatory. The default
80   * propagation settings are in {@link OrbitType#EQUINOCTIAL equinoctial} parameters with
81   * {@link PositionAngle#TRUE true} longitude argument. If the central attraction coefficient
82   * is not explicitly specified, the one used to define the initial orbit will be used.
83   * However, specifying only the initial state and perhaps the central attraction coefficient
84   * would mean the propagator would use only keplerian forces. In this case, the simpler {@link
85   * org.orekit.propagation.analytical.KeplerianPropagator KeplerianPropagator} class would
86   * perhaps be more effective.</p>
87   * <p>The underlying numerical integrator set up in the constructor may also have its own
88   * configuration parameters. Typical configuration parameters for adaptive stepsize integrators
89   * are the min, max and perhaps start step size as well as the absolute and/or relative errors
90   * thresholds.</p>
91   * <p>The state that is seen by the integrator is a simple seven elements double array.
92   * The six first elements are either:
93   * <ul>
94   *   <li>the {@link org.orekit.orbits.EquinoctialOrbit equinoctial orbit parameters} (a, e<sub>x</sub>,
95   *   e<sub>y</sub>, h<sub>x</sub>, h<sub>y</sub>, &lambda;<sub>M</sub> or &lambda;<sub>E</sub>
96   *   or &lambda;<sub>v</sub>) in meters and radians,</li>
97   *   <li>the {@link org.orekit.orbits.KeplerianOrbit Keplerian orbit parameters} (a, e, i, &omega;, &Omega;,
98   *   M or E or v) in meters and radians,</li>
99   *   <li>the {@link org.orekit.orbits.CircularOrbit circular orbit parameters} (a, e<sub>x</sub>, e<sub>y</sub>, i,
100  *   &Omega;, &alpha;<sub>M</sub> or &alpha;<sub>E</sub> or &alpha;<sub>v</sub>) in meters
101  *   and radians,</li>
102  *   <li>the {@link org.orekit.orbits.CartesianOrbit Cartesian orbit parameters} (x, y, z, v<sub>x</sub>,
103  *   v<sub>y</sub>, v<sub>z</sub>) in meters and meters per seconds.
104  * </ul>
105  * The last element is the mass in kilograms.
106  * </p>
107  * <p>The following code snippet shows a typical setting for Low Earth Orbit propagation in
108  * equinoctial parameters and true longitude argument:</p>
109  * <pre>
110  * final double dP       = 0.001;
111  * final double minStep  = 0.001;
112  * final double maxStep  = 500;
113  * final double initStep = 60;
114  * final double[][] tolerance = NumericalPropagator.tolerances(dP, orbit, OrbitType.EQUINOCTIAL);
115  * AdaptiveStepsizeIntegrator integrator = new DormandPrince853Integrator(minStep, maxStep, tolerance[0], tolerance[1]);
116  * integrator.setInitialStepSize(initStep);
117  * propagator = new NumericalPropagator(integrator);
118  * </pre>
119  * <p>The same propagator can be reused for several orbit extrapolations, by resetting
120  * the initial state without modifying the other configuration parameters. However, the
121  * same instance cannot be used simultaneously by different threads, the class is <em>not</em>
122  * thread-safe.</p>
123 
124  * @see SpacecraftState
125  * @see ForceModel
126  * @see org.orekit.propagation.sampling.OrekitStepHandler
127  * @see org.orekit.propagation.sampling.OrekitFixedStepHandler
128  * @see org.orekit.propagation.integration.IntegratedEphemeris
129  * @see TimeDerivativesEquations
130  *
131  * @author Mathieu Rom&eacute;ro
132  * @author Luc Maisonobe
133  * @author Guylaine Prat
134  * @author Fabien Maussion
135  * @author V&eacute;ronique Pommier-Maurussane
136  */
137 public class NumericalPropagator extends AbstractIntegratedPropagator {
138 
139     /** Central body attraction. */
140     private NewtonianAttraction newtonianAttraction;
141 
142     /** Force models used during the extrapolation of the Orbit, without jacobians. */
143     private final List<ForceModel> forceModels;
144 
145     /** Create a new instance of NumericalPropagator, based on orbit definition mu.
146      * After creation, the instance is empty, i.e. the attitude provider is set to an
147      * unspecified default law and there are no perturbing forces at all.
148      * This means that if {@link #addForceModel addForceModel} is not
149      * called after creation, the integrated orbit will follow a keplerian
150      * evolution only. The defaults are {@link OrbitType#EQUINOCTIAL}
151      * for {@link #setOrbitType(OrbitType) propagation
152      * orbit type} and {@link PositionAngle#TRUE} for {@link
153      * #setPositionAngleType(PositionAngle) position angle type}.
154      * @param integrator numerical integrator to use for propagation.
155      */
156     public NumericalPropagator(final AbstractIntegrator integrator) {
157         super(integrator);
158         forceModels = new ArrayList<ForceModel>();
159         initMapper();
160         setAttitudeProvider(DEFAULT_LAW);
161         setMu(Double.NaN);
162         setSlaveMode();
163         setOrbitType(OrbitType.EQUINOCTIAL);
164         setPositionAngleType(PositionAngle.TRUE);
165     }
166 
167      /** Set the central attraction coefficient &mu;.
168      * @param mu central attraction coefficient (m<sup>3</sup>/s<sup>2</sup>)
169      * @see #addForceModel(ForceModel)
170      */
171     public void setMu(final double mu) {
172         super.setMu(mu);
173         newtonianAttraction = new NewtonianAttraction(mu);
174     }
175 
176     /** Set the attitude provider.
177      * @param provider attitude provider
178      * @deprecated as of 6.0 replaced by {@link #setAttitudeProvider(AttitudeProvider)}
179      */
180     @Deprecated
181     public void setAttitudeLaw(final AttitudeProvider provider) {
182         setAttitudeProvider(provider);
183     }
184 
185     /** Add a force model to the global perturbation model.
186      * <p>If this method is not called at all, the integrated orbit will follow
187      * a keplerian evolution only.</p>
188      * @param model perturbing {@link ForceModel} to add
189      * @see #removeForceModels()
190      * @see #setMu(double)
191      */
192     public void addForceModel(final ForceModel model) {
193         forceModels.add(model);
194     }
195 
196     /** Remove all perturbing force models from the global perturbation model.
197      * <p>Once all perturbing forces have been removed (and as long as no new force
198      * model is added), the integrated orbit will follow a keplerian evolution
199      * only.</p>
200      * @see #addForceModel(ForceModel)
201      */
202     public void removeForceModels() {
203         forceModels.clear();
204     }
205 
206     /** Get perturbing force models list.
207      * @return list of perturbing force models
208      * @see #addForceModel(ForceModel)
209      * @see #getNewtonianAttractionForceModel()
210      */
211     public List<ForceModel> getForceModels() {
212         return forceModels;
213     }
214 
215     /** Get the Newtonian attraction from the central body force model.
216      * @return Newtonian attraction force model
217      * @see #setMu(double)
218      * @see #getForceModels()
219      */
220     public NewtonianAttraction getNewtonianAttractionForceModel() {
221         return newtonianAttraction;
222     }
223 
224     /** Set propagation orbit type.
225      * @param orbitType orbit type to use for propagation
226      */
227     public void setOrbitType(final OrbitType orbitType) {
228         super.setOrbitType(orbitType);
229     }
230 
231     /** Get propagation parameter type.
232      * @return orbit type used for propagation
233      */
234     public OrbitType getOrbitType() {
235         return super.getOrbitType();
236     }
237 
238     /** Set position angle type.
239      * <p>
240      * The position parameter type is meaningful only if {@link
241      * #getOrbitType() propagation orbit type}
242      * support it. As an example, it is not meaningful for propagation
243      * in {@link OrbitType#CARTESIAN Cartesian} parameters.
244      * </p>
245      * @param positionAngleType angle type to use for propagation
246      */
247     public void setPositionAngleType(final PositionAngle positionAngleType) {
248         super.setPositionAngleType(positionAngleType);
249     }
250 
251     /** Get propagation parameter type.
252      * @return angle type to use for propagation
253      */
254     public PositionAngle getPositionAngleType() {
255         return super.getPositionAngleType();
256     }
257 
258     /** Set the initial state.
259      * @param initialState initial state
260      * @exception PropagationException if initial state cannot be set
261      */
262     public void setInitialState(final SpacecraftState initialState) throws PropagationException {
263         resetInitialState(initialState);
264     }
265 
266     /** {@inheritDoc} */
267     public void resetInitialState(final SpacecraftState state) throws PropagationException {
268         super.resetInitialState(state);
269         if (newtonianAttraction == null) {
270             setMu(state.getMu());
271         }
272         setStartDate(null);
273     }
274 
275     /** {@inheritDoc} */
276     public PVCoordinates getPVCoordinates(final AbsoluteDate date, final Frame frame)
277         throws OrekitException {
278         return propagate(date).getPVCoordinates(frame);
279     }
280 
281     /** {@inheritDoc} */
282     protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
283                                        final OrbitType orbitType, final PositionAngle positionAngleType,
284                                        final AttitudeProvider attitudeProvider, final Frame frame) {
285         return new OsculatingMapper(referenceDate, mu, orbitType, positionAngleType, attitudeProvider, frame);
286     }
287 
288     /** Internal mapper using directly osculating parameters. */
289     private static class OsculatingMapper extends StateMapper implements Serializable {
290 
291         /** Serializable UID. */
292         private static final long serialVersionUID = 20130621L;
293 
294         /** Simple constructor.
295          * <p>
296          * The position parameter type is meaningful only if {@link
297          * #getOrbitType() propagation orbit type}
298          * support it. As an example, it is not meaningful for propagation
299          * in {@link OrbitType#CARTESIAN Cartesian} parameters.
300          * </p>
301          * @param referenceDate reference date
302          * @param mu central attraction coefficient (m<sup>3</sup>/s<sup>2</sup>)
303          * @param orbitType orbit type to use for mapping
304          * @param positionAngleType angle type to use for propagation
305          * @param attitudeProvider attitude provider
306          * @param frame inertial frame
307          */
308         public OsculatingMapper(final AbsoluteDate referenceDate, final double mu,
309                                 final OrbitType orbitType, final PositionAngle positionAngleType,
310                                 final AttitudeProvider attitudeProvider, final Frame frame) {
311             super(referenceDate, mu, orbitType, positionAngleType, attitudeProvider, frame);
312         }
313 
314         /** {@inheritDoc} */
315         public SpacecraftState mapArrayToState(final double t, final double[] y)
316             throws OrekitException {
317 
318             final double mass = y[6];
319             if (mass <= 0.0) {
320                 throw new PropagationException(OrekitMessages.SPACECRAFT_MASS_BECOMES_NEGATIVE, mass);
321             }
322 
323             final AbsoluteDate date = mapDoubleToDate(t);
324             final Orbit orbit       = getOrbitType().mapArrayToOrbit(y, getPositionAngleType(), date, getMu(), getFrame());
325             final Attitude attitude = getAttitudeProvider().getAttitude(orbit, date, getFrame());
326 
327             return new SpacecraftState(orbit, attitude, mass);
328 
329         }
330 
331         /** {@inheritDoc} */
332         public void mapStateToArray(final SpacecraftState state, final double[] y) {
333             getOrbitType().mapOrbitToArray(state.getOrbit(), getPositionAngleType(), y);
334             y[6] = state.getMass();
335         }
336 
337         /** Replace the instance with a data transfer object for serialization.
338          * @return data transfer object that will be serialized
339          * @exception NotSerializableException if the state mapper cannot be serialized (typically for DSST propagator)
340          */
341         private Object writeReplace() throws NotSerializableException {
342             return new DataTransferObject(getReferenceDate(), getMu(), getOrbitType(),
343                                           getPositionAngleType(), getAttitudeProvider(), getFrame());
344         }
345 
346         /** Internal class used only for serialization. */
347         private static class DataTransferObject implements Serializable {
348 
349             /** Serializable UID. */
350             private static final long serialVersionUID = 20130621L;
351 
352             /** Reference date. */
353             private final AbsoluteDate referenceDate;
354 
355             /** Central attraction coefficient (m<sup>3</sup>/s<sup>2</sup>). */
356             private final double mu;
357 
358             /** Orbit type to use for mapping. */
359             private final OrbitType orbitType;
360 
361             /** Angle type to use for propagation. */
362             private final PositionAngle positionAngleType;
363 
364             /** Attitude provider. */
365             private final AttitudeProvider attitudeProvider;
366 
367             /** Inertial frame. */
368             private final Frame frame;
369 
370             /** Simple constructor.
371              * @param referenceDate reference date
372              * @param mu central attraction coefficient (m<sup>3</sup>/s<sup>2</sup>)
373              * @param orbitType orbit type to use for mapping
374              * @param positionAngleType angle type to use for propagation
375              * @param attitudeProvider attitude provider
376              * @param frame inertial frame
377              */
378             public DataTransferObject(final AbsoluteDate referenceDate, final double mu,
379                                       final OrbitType orbitType, final PositionAngle positionAngleType,
380                                       final AttitudeProvider attitudeProvider, final Frame frame) {
381                 this.referenceDate     = referenceDate;
382                 this.mu                = mu;
383                 this.orbitType         = orbitType;
384                 this.positionAngleType = positionAngleType;
385                 this.attitudeProvider  = attitudeProvider;
386                 this.frame             = frame;
387             }
388 
389             /** Replace the deserialized data transfer object with a {@link OsculatingMapper}.
390              * @return replacement {@link OsculatingMapper}
391              */
392             private Object readResolve() {
393                 return new OsculatingMapper(referenceDate, mu, orbitType, positionAngleType, attitudeProvider, frame);
394             }
395         }
396 
397     }
398 
399     /** {@inheritDoc} */
400     protected MainStateEquations getMainStateEquations(final AbstractIntegrator integrator) {
401         return new Main(integrator);
402     }
403 
404     /** Internal class for osculating parameters integration. */
405     private class Main implements MainStateEquations, TimeDerivativesEquations {
406 
407         /** Derivatives array. */
408         private final double[] yDot;
409 
410         /** Current orbit. */
411         private Orbit orbit;
412 
413         /** Jacobian of the orbital parameters with respect to the cartesian parameters. */
414         private double[][] jacobian;
415 
416         /** Simple constructor.
417          * @param integrator numerical integrator to use for propagation.
418          */
419         public Main(final AbstractIntegrator integrator) {
420 
421             this.yDot     = new double[7];
422             this.jacobian = new double[6][6];
423 
424             for (final ForceModel forceModel : forceModels) {
425                 final EventDetector[] modelDetectors = forceModel.getEventsDetectors();
426                 if (modelDetectors != null) {
427                     for (final EventDetector detector : modelDetectors) {
428                         setUpEventDetector(integrator, detector);
429                     }
430                 }
431             }
432 
433         }
434 
435         /** {@inheritDoc} */
436         public double[] computeDerivatives(final SpacecraftState state) throws OrekitException {
437 
438             orbit = state.getOrbit();
439             Arrays.fill(yDot, 0.0);
440             orbit.getJacobianWrtCartesian(getPositionAngleType(), jacobian);
441 
442             // compute the contributions of all perturbing forces
443             for (final ForceModel forceModel : forceModels) {
444                 forceModel.addContribution(state, this);
445             }
446 
447             // finalize derivatives by adding the Kepler contribution
448             newtonianAttraction.addContribution(state, this);
449 
450             return yDot.clone();
451 
452         }
453 
454         /** {@inheritDoc} */
455         public void addKeplerContribution(final double mu) {
456             orbit.addKeplerContribution(getPositionAngleType(), mu, yDot);
457         }
458 
459         /** {@inheritDoc} */
460         public void addXYZAcceleration(final double x, final double y, final double z) {
461             for (int i = 0; i < 6; ++i) {
462                 final double[] jRow = jacobian[i];
463                 yDot[i] += jRow[3] * x + jRow[4] * y + jRow[5] * z;
464             }
465         }
466 
467         /** {@inheritDoc} */
468         public void addAcceleration(final Vector3D gamma, final Frame frame)
469             throws OrekitException {
470             final Transform t = frame.getTransformTo(orbit.getFrame(), orbit.getDate());
471             final Vector3D gammInRefFrame = t.transformVector(gamma);
472             addXYZAcceleration(gammInRefFrame.getX(), gammInRefFrame.getY(), gammInRefFrame.getZ());
473         }
474 
475         /** {@inheritDoc} */
476         public void addMassDerivative(final double q) {
477             if (q > 0) {
478                 throw OrekitException.createIllegalArgumentException(OrekitMessages.POSITIVE_FLOW_RATE, q);
479             }
480             yDot[6] += q;
481         }
482 
483     }
484 
485     /** Estimate tolerance vectors for integrators.
486      * <p>
487      * The errors are estimated from partial derivatives properties of orbits,
488      * starting from a scalar position error specified by the user.
489      * Considering the energy conservation equation V = sqrt(mu (2/r - 1/a)),
490      * we get at constant energy (i.e. on a Keplerian trajectory):
491      * <pre>
492      * V<sup>2</sup> r |dV| = mu |dr|
493      * </pre>
494      * So we deduce a scalar velocity error consistent with the position error.
495      * From here, we apply orbits Jacobians matrices to get consistent errors
496      * on orbital parameters.
497      * </p>
498      * <p>
499      * The tolerances are only <em>orders of magnitude</em>, and integrator tolerances
500      * are only local estimates, not global ones. So some care must be taken when using
501      * these tolerances. Setting 1mm as a position error does NOT mean the tolerances
502      * will guarantee a 1mm error position after several orbits integration.
503      * </p>
504      * @param dP user specified position error
505      * @param orbit reference orbit
506      * @param type propagation type for the meaning of the tolerance vectors elements
507      * (it may be different from {@code orbit.getType()})
508      * @return a two rows array, row 0 being the absolute tolerance error and row 1
509      * being the relative tolerance error
510      * @exception PropagationException if Jacobian is singular
511      */
512     public static double[][] tolerances(final double dP, final Orbit orbit, final OrbitType type)
513         throws PropagationException {
514 
515         // estimate the scalar velocity error
516         final PVCoordinates pv = orbit.getPVCoordinates();
517         final double r2 = pv.getPosition().getNormSq();
518         final double v  = pv.getVelocity().getNorm();
519         final double dV = orbit.getMu() * dP / (v * r2);
520 
521         final double[] absTol = new double[7];
522         final double[] relTol = new double[7];
523 
524         // we set the mass tolerance arbitrarily to 1.0e-6 kg, as mass evolves linearly
525         // with trust, this often has no influence at all on propagation
526         absTol[6] = 1.0e-6;
527 
528         if (type == OrbitType.CARTESIAN) {
529             absTol[0] = dP;
530             absTol[1] = dP;
531             absTol[2] = dP;
532             absTol[3] = dV;
533             absTol[4] = dV;
534             absTol[5] = dV;
535         } else {
536 
537             // convert the orbit to the desired type
538             final double[][] jacobian = new double[6][6];
539             final Orbit converted = type.convertType(orbit);
540             converted.getJacobianWrtCartesian(PositionAngle.TRUE, jacobian);
541 
542             for (int i = 0; i < 6; ++i) {
543                 final double[] row = jacobian[i];
544                 absTol[i] = FastMath.abs(row[0]) * dP +
545                             FastMath.abs(row[1]) * dP +
546                             FastMath.abs(row[2]) * dP +
547                             FastMath.abs(row[3]) * dV +
548                             FastMath.abs(row[4]) * dV +
549                             FastMath.abs(row[5]) * dV;
550                 if (Double.isNaN(absTol[i])) {
551                     throw new PropagationException(OrekitMessages.SINGULAR_JACOBIAN_FOR_ORBIT_TYPE, type);
552                 }
553             }
554 
555         }
556 
557         Arrays.fill(relTol, dP / FastMath.sqrt(r2));
558 
559         return new double[][] {
560             absTol, relTol
561         };
562 
563     }
564 
565 }
566