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