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