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