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>, λ<sub>M</sub> or λ<sub>E</sub>
96 * or λ<sub>v</sub>) in meters and radians,</li>
97 * <li>the {@link org.orekit.orbits.KeplerianOrbit Keplerian orbit parameters} (a, e, i, ω, Ω,
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 * Ω, α<sub>M</sub> or α<sub>E</sub> or α<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éro
132 * @author Luc Maisonobe
133 * @author Guylaine Prat
134 * @author Fabien Maussion
135 * @author Vé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 μ.
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