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éro
133 * @author Luc Maisonobe
134 * @author Guylaine Prat
135 * @author Fabien Maussion
136 * @author Vé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