1 /* Copyright 2002-2022 CS GROUP
2 * Licensed to CS GROUP (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.semianalytical.dsst;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Set;
26
27 import org.hipparchus.linear.RealMatrix;
28 import org.hipparchus.ode.ODEIntegrator;
29 import org.hipparchus.ode.ODEStateAndDerivative;
30 import org.hipparchus.ode.sampling.ODEStateInterpolator;
31 import org.hipparchus.ode.sampling.ODEStepHandler;
32 import org.hipparchus.util.FastMath;
33 import org.hipparchus.util.MathUtils;
34 import org.orekit.annotation.DefaultDataContext;
35 import org.orekit.attitudes.Attitude;
36 import org.orekit.attitudes.AttitudeProvider;
37 import org.orekit.data.DataContext;
38 import org.orekit.errors.OrekitException;
39 import org.orekit.errors.OrekitInternalError;
40 import org.orekit.errors.OrekitMessages;
41 import org.orekit.frames.Frame;
42 import org.orekit.orbits.EquinoctialOrbit;
43 import org.orekit.orbits.Orbit;
44 import org.orekit.orbits.OrbitType;
45 import org.orekit.orbits.PositionAngle;
46 import org.orekit.propagation.AbstractMatricesHarvester;
47 import org.orekit.propagation.MatricesHarvester;
48 import org.orekit.propagation.PropagationType;
49 import org.orekit.propagation.Propagator;
50 import org.orekit.propagation.SpacecraftState;
51 import org.orekit.propagation.events.EventDetector;
52 import org.orekit.propagation.integration.AbstractIntegratedPropagator;
53 import org.orekit.propagation.integration.AdditionalDerivativesProvider;
54 import org.orekit.propagation.integration.StateMapper;
55 import org.orekit.propagation.numerical.NumericalPropagator;
56 import org.orekit.propagation.semianalytical.dsst.forces.DSSTForceModel;
57 import org.orekit.propagation.semianalytical.dsst.forces.DSSTNewtonianAttraction;
58 import org.orekit.propagation.semianalytical.dsst.forces.ShortPeriodTerms;
59 import org.orekit.propagation.semianalytical.dsst.utilities.AuxiliaryElements;
60 import org.orekit.propagation.semianalytical.dsst.utilities.FixedNumberInterpolationGrid;
61 import org.orekit.propagation.semianalytical.dsst.utilities.InterpolationGrid;
62 import org.orekit.propagation.semianalytical.dsst.utilities.MaxGapInterpolationGrid;
63 import org.orekit.time.AbsoluteDate;
64 import org.orekit.utils.DoubleArrayDictionary;
65 import org.orekit.utils.ParameterDriver;
66 import org.orekit.utils.ParameterDriversList;
67 import org.orekit.utils.ParameterDriversList.DelegatingDriver;
68 import org.orekit.utils.ParameterObserver;
69
70 /**
71 * This class propagates {@link org.orekit.orbits.Orbit orbits} using the DSST theory.
72 * <p>
73 * Whereas analytical propagators are configured only thanks to their various
74 * constructors and can be used immediately after construction, such a semianalytical
75 * propagator configuration involves setting several parameters between construction
76 * time and propagation time, just as numerical propagators.
77 * </p>
78 * <p>
79 * The configuration parameters that can be set are:
80 * </p>
81 * <ul>
82 * <li>the initial spacecraft state ({@link #setInitialState(SpacecraftState)})</li>
83 * <li>the various force models ({@link #addForceModel(DSSTForceModel)},
84 * {@link #removeForceModels()})</li>
85 * <li>the discrete events that should be triggered during propagation (
86 * {@link #addEventDetector(org.orekit.propagation.events.EventDetector)},
87 * {@link #clearEventsDetectors()})</li>
88 * <li>the binding logic with the rest of the application ({@link #getMultiplexer()})</li>
89 * </ul>
90 * <p>
91 * From these configuration parameters, only the initial state is mandatory.
92 * The default propagation settings are in {@link OrbitType#EQUINOCTIAL equinoctial}
93 * parameters with {@link PositionAngle#TRUE true} longitude argument.
94 * The central attraction coefficient used to define the initial orbit will be used.
95 * However, specifying only the initial state would mean the propagator would use
96 * only Keplerian forces. In this case, the simpler
97 * {@link org.orekit.propagation.analytical.KeplerianPropagator KeplerianPropagator}
98 * class would be more effective.
99 * </p>
100 * <p>
101 * The underlying numerical integrator set up in the constructor may also have
102 * its own configuration parameters. Typical configuration parameters for adaptive
103 * stepsize integrators are the min, max and perhaps start step size as well as
104 * the absolute and/or relative errors thresholds.
105 * </p>
106 * <p>
107 * The state that is seen by the integrator is a simple six elements double array.
108 * These six elements are:
109 * <ul>
110 * <li>the {@link org.orekit.orbits.EquinoctialOrbit equinoctial orbit parameters}
111 * (a, e<sub>x</sub>, e<sub>y</sub>, h<sub>x</sub>, h<sub>y</sub>, λ<sub>m</sub>)
112 * in meters and radians,</li>
113 * </ul>
114 *
115 * <p>By default, at the end of the propagation, the propagator resets the initial state to the final state,
116 * thus allowing a new propagation to be started from there without recomputing the part already performed.
117 * This behaviour can be chenged by calling {@link #setResetAtEnd(boolean)}.
118 * </p>
119 * <p>Beware the same instance cannot be used simultaneously by different threads, the class is <em>not</em>
120 * thread-safe.</p>
121 *
122 * @see SpacecraftState
123 * @see DSSTForceModel
124 * @author Romain Di Costanzo
125 * @author Pascal Parraud
126 */
127 public class DSSTPropagator extends AbstractIntegratedPropagator {
128
129 /** Retrograde factor I.
130 * <p>
131 * DSST model needs equinoctial orbit as internal representation.
132 * Classical equinoctial elements have discontinuities when inclination
133 * is close to zero. In this representation, I = +1. <br>
134 * To avoid this discontinuity, another representation exists and equinoctial
135 * elements can be expressed in a different way, called "retrograde" orbit.
136 * This implies I = -1. <br>
137 * As Orekit doesn't implement the retrograde orbit, I is always set to +1.
138 * But for the sake of consistency with the theory, the retrograde factor
139 * has been kept in the formulas.
140 * </p>
141 */
142 private static final int I = 1;
143
144 /** Default value for epsilon. */
145 private static final double EPSILON_DEFAULT = 1.0e-13;
146
147 /** Default value for maxIterations. */
148 private static final int MAX_ITERATIONS_DEFAULT = 200;
149
150 /** Number of grid points per integration step to be used in interpolation of short periodics coefficients.*/
151 private static final int INTERPOLATION_POINTS_PER_STEP = 3;
152
153 /** Flag specifying whether the initial orbital state is given with osculating elements. */
154 private boolean initialIsOsculating;
155
156 /** Force models used to compute short periodic terms. */
157 private final transient List<DSSTForceModel> forceModels;
158
159 /** State mapper holding the force models. */
160 private MeanPlusShortPeriodicMapper mapper;
161
162 /** Generator for the interpolation grid. */
163 private InterpolationGrid interpolationgrid;
164
165 /** Create a new instance of DSSTPropagator.
166 * <p>
167 * After creation, there are no perturbing forces at all.
168 * This means that if {@link #addForceModel addForceModel}
169 * is not called after creation, the integrated orbit will
170 * follow a Keplerian evolution only.
171 * </p>
172 *
173 * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
174 *
175 * @param integrator numerical integrator to use for propagation.
176 * @param propagationType type of orbit to output (mean or osculating).
177 * @see #DSSTPropagator(ODEIntegrator, PropagationType, AttitudeProvider)
178 */
179 @DefaultDataContext
180 public DSSTPropagator(final ODEIntegrator integrator, final PropagationType propagationType) {
181 this(integrator, propagationType,
182 Propagator.getDefaultLaw(DataContext.getDefault().getFrames()));
183 }
184
185 /** Create a new instance of DSSTPropagator.
186 * <p>
187 * After creation, there are no perturbing forces at all.
188 * This means that if {@link #addForceModel addForceModel}
189 * is not called after creation, the integrated orbit will
190 * follow a Keplerian evolution only.
191 * </p>
192 * @param integrator numerical integrator to use for propagation.
193 * @param propagationType type of orbit to output (mean or osculating).
194 * @param attitudeProvider the attitude law.
195 * @since 10.1
196 */
197 public DSSTPropagator(final ODEIntegrator integrator,
198 final PropagationType propagationType,
199 final AttitudeProvider attitudeProvider) {
200 super(integrator, propagationType);
201 forceModels = new ArrayList<DSSTForceModel>();
202 initMapper();
203 // DSST uses only equinoctial orbits and mean longitude argument
204 setOrbitType(OrbitType.EQUINOCTIAL);
205 setPositionAngleType(PositionAngle.MEAN);
206 setAttitudeProvider(attitudeProvider);
207 setInterpolationGridToFixedNumberOfPoints(INTERPOLATION_POINTS_PER_STEP);
208 }
209
210
211 /** Create a new instance of DSSTPropagator.
212 * <p>
213 * After creation, there are no perturbing forces at all.
214 * This means that if {@link #addForceModel addForceModel}
215 * is not called after creation, the integrated orbit will
216 * follow a Keplerian evolution only. Only the mean orbits
217 * will be generated.
218 * </p>
219 *
220 * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
221 *
222 * @param integrator numerical integrator to use for propagation.
223 * @see #DSSTPropagator(ODEIntegrator, PropagationType, AttitudeProvider)
224 */
225 @DefaultDataContext
226 public DSSTPropagator(final ODEIntegrator integrator) {
227 this(integrator, PropagationType.MEAN);
228 }
229
230 /** Set the central attraction coefficient μ.
231 * <p>
232 * Setting the central attraction coefficient is
233 * equivalent to {@link #addForceModel(DSSTForceModel) add}
234 * a {@link DSSTNewtonianAttraction} force model.
235 * </p>
236 * @param mu central attraction coefficient (m³/s²)
237 * @see #addForceModel(DSSTForceModel)
238 * @see #getAllForceModels()
239 */
240 public void setMu(final double mu) {
241 addForceModel(new DSSTNewtonianAttraction(mu));
242 }
243
244 /** Set the central attraction coefficient μ only in upper class.
245 * @param mu central attraction coefficient (m³/s²)
246 */
247 private void superSetMu(final double mu) {
248 super.setMu(mu);
249 }
250
251 /** Check if Newtonian attraction force model is available.
252 * <p>
253 * Newtonian attraction is always the last force model in the list.
254 * </p>
255 * @return true if Newtonian attraction force model is available
256 */
257 private boolean hasNewtonianAttraction() {
258 final int last = forceModels.size() - 1;
259 return last >= 0 && forceModels.get(last) instanceof DSSTNewtonianAttraction;
260 }
261
262 /** Set the initial state with osculating orbital elements.
263 * @param initialState initial state (defined with osculating elements)
264 */
265 public void setInitialState(final SpacecraftState initialState) {
266 setInitialState(initialState, PropagationType.OSCULATING);
267 }
268
269 /** Set the initial state.
270 * @param initialState initial state
271 * @param stateType defined if the orbital state is defined with osculating or mean elements
272 */
273 public void setInitialState(final SpacecraftState initialState,
274 final PropagationType stateType) {
275 switch (stateType) {
276 case MEAN:
277 initialIsOsculating = false;
278 break;
279 case OSCULATING:
280 initialIsOsculating = true;
281 break;
282 default:
283 throw new OrekitInternalError(null);
284 }
285 resetInitialState(initialState);
286 }
287
288 /** Reset the initial state.
289 *
290 * @param state new initial state
291 */
292 @Override
293 public void resetInitialState(final SpacecraftState state) {
294 super.resetInitialState(state);
295 if (!hasNewtonianAttraction()) {
296 // use the state to define central attraction
297 setMu(state.getMu());
298 }
299 super.setStartDate(state.getDate());
300 }
301
302 /** Set the selected short periodic coefficients that must be stored as additional states.
303 * @param selectedCoefficients short periodic coefficients that must be stored as additional states
304 * (null means no coefficients are selected, empty set means all coefficients are selected)
305 */
306 public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
307 mapper.setSelectedCoefficients(selectedCoefficients == null ?
308 null : new HashSet<String>(selectedCoefficients));
309 }
310
311 /** Get the selected short periodic coefficients that must be stored as additional states.
312 * @return short periodic coefficients that must be stored as additional states
313 * (null means no coefficients are selected, empty set means all coefficients are selected)
314 */
315 public Set<String> getSelectedCoefficients() {
316 final Set<String> set = mapper.getSelectedCoefficients();
317 return set == null ? null : Collections.unmodifiableSet(set);
318 }
319
320 /** Get the names of the parameters in the matrix returned by {@link MatricesHarvester#getParametersJacobian}.
321 * @return names of the parameters (i.e. columns) of the Jacobian matrix
322 */
323 protected List<String> getJacobiansColumnsNames() {
324 final List<String> columnsNames = new ArrayList<>();
325 for (final DSSTForceModel forceModel : getAllForceModels()) {
326 for (final ParameterDriver driver : forceModel.getParametersDrivers()) {
327 if (driver.isSelected() && !columnsNames.contains(driver.getName())) {
328 columnsNames.add(driver.getName());
329 }
330 }
331 }
332 Collections.sort(columnsNames);
333 return columnsNames;
334 }
335
336 /** {@inheritDoc} */
337 @Override
338 protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
339 final DoubleArrayDictionary initialJacobianColumns) {
340 return new DSSTHarvester(this, stmName, initialStm, initialJacobianColumns);
341 }
342
343 /** {@inheritDoc} */
344 @Override
345 protected void setUpStmAndJacobianGenerators() {
346
347 final AbstractMatricesHarvester harvester = getHarvester();
348 if (harvester != null) {
349
350 // set up the additional equations and additional state providers
351 final DSSTStateTransitionMatrixGenerator stmGenerator = setUpStmGenerator();
352 setUpRegularParametersJacobiansColumns(stmGenerator);
353
354 // as we are now starting the propagation, everything is configured
355 // we can freeze the names in the harvester
356 harvester.freezeColumnsNames();
357
358 }
359
360 }
361
362 /** Set up the State Transition Matrix Generator.
363 * @return State Transition Matrix Generator
364 * @since 11.1
365 */
366 private DSSTStateTransitionMatrixGenerator setUpStmGenerator() {
367
368 final AbstractMatricesHarvester harvester = getHarvester();
369
370 // add the STM generator corresponding to the current settings, and setup state accordingly
371 DSSTStateTransitionMatrixGenerator stmGenerator = null;
372 for (final AdditionalDerivativesProvider equations : getAdditionalDerivativesProviders()) {
373 if (equations instanceof DSSTStateTransitionMatrixGenerator &&
374 equations.getName().equals(harvester.getStmName())) {
375 // the STM generator has already been set up in a previous propagation
376 stmGenerator = (DSSTStateTransitionMatrixGenerator) equations;
377 break;
378 }
379 }
380 if (stmGenerator == null) {
381 // this is the first time we need the STM generate, create it
382 stmGenerator = new DSSTStateTransitionMatrixGenerator(harvester.getStmName(),
383 getAllForceModels(),
384 getAttitudeProvider());
385 addAdditionalDerivativesProvider(stmGenerator);
386 }
387
388 if (!getInitialIntegrationState().hasAdditionalState(harvester.getStmName())) {
389 // add the initial State Transition Matrix if it is not already there
390 // (perhaps due to a previous propagation)
391 setInitialState(stmGenerator.setInitialStateTransitionMatrix(getInitialState(),
392 harvester.getInitialStateTransitionMatrix()),
393 getPropagationType());
394 }
395
396 return stmGenerator;
397
398 }
399
400 /** Set up the Jacobians columns generator for regular parameters.
401 * @param stmGenerator generator for the State Transition Matrix
402 * @since 11.1
403 */
404 private void setUpRegularParametersJacobiansColumns(final DSSTStateTransitionMatrixGenerator stmGenerator) {
405
406 // first pass: gather all parameters (excluding trigger dates), binding similar names together
407 final ParameterDriversList selected = new ParameterDriversList();
408 for (final DSSTForceModel forceModel : getAllForceModels()) {
409 for (final ParameterDriver driver : forceModel.getParametersDrivers()) {
410 selected.add(driver);
411 }
412 }
413
414 // second pass: now that shared parameter names are bound together,
415 // their selections status have been synchronized, we can filter them
416 selected.filter(true);
417
418 // third pass: sort parameters lexicographically
419 selected.sort();
420
421 // add the Jacobians column generators corresponding to parameters, and setup state accordingly
422 for (final DelegatingDriver driver : selected.getDrivers()) {
423
424 DSSTIntegrableJacobianColumnGenerator generator = null;
425
426 // check if we already have set up the providers
427 for (final AdditionalDerivativesProvider provider : getAdditionalDerivativesProviders()) {
428 if (provider instanceof DSSTIntegrableJacobianColumnGenerator &&
429 provider.getName().equals(driver.getName())) {
430 // the Jacobian column generator has already been set up in a previous propagation
431 generator = (DSSTIntegrableJacobianColumnGenerator) provider;
432 break;
433 }
434 }
435
436 if (generator == null) {
437 // this is the first time we need the Jacobian column generator, create it
438 generator = new DSSTIntegrableJacobianColumnGenerator(stmGenerator, driver.getName());
439 addAdditionalDerivativesProvider(generator);
440 }
441
442 if (!getInitialIntegrationState().hasAdditionalState(driver.getName())) {
443 // add the initial Jacobian column if it is not already there
444 // (perhaps due to a previous propagation)
445 setInitialState(getInitialState().addAdditionalState(driver.getName(),
446 getHarvester().getInitialJacobianColumn(driver.getName())),
447 getPropagationType());
448 }
449
450 }
451
452 }
453
454 /** Check if the initial state is provided in osculating elements.
455 * @return true if initial state is provided in osculating elements
456 */
457 public boolean initialIsOsculating() {
458 return initialIsOsculating;
459 }
460
461 /** Set the interpolation grid generator.
462 * <p>
463 * The generator will create an interpolation grid with a fixed
464 * number of points for each mean element integration step.
465 * </p>
466 * <p>
467 * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
468 * nor {@link #setInterpolationGridToMaxTimeGap(double)} has been called,
469 * by default the propagator is set as to 3 interpolations points per step.
470 * </p>
471 * @param interpolationPoints number of interpolation points at
472 * each integration step
473 * @see #setInterpolationGridToMaxTimeGap(double)
474 * @since 7.1
475 */
476 public void setInterpolationGridToFixedNumberOfPoints(final int interpolationPoints) {
477 interpolationgrid = new FixedNumberInterpolationGrid(interpolationPoints);
478 }
479
480 /** Set the interpolation grid generator.
481 * <p>
482 * The generator will create an interpolation grid with a maximum
483 * time gap between interpolation points.
484 * </p>
485 * <p>
486 * If neither {@link #setInterpolationGridToFixedNumberOfPoints(int)}
487 * nor {@link #setInterpolationGridToMaxTimeGap(double)} has been called,
488 * by default the propagator is set as to 3 interpolations points per step.
489 * </p>
490 * @param maxGap maximum time gap between interpolation points (seconds)
491 * @see #setInterpolationGridToFixedNumberOfPoints(int)
492 * @since 7.1
493 */
494 public void setInterpolationGridToMaxTimeGap(final double maxGap) {
495 interpolationgrid = new MaxGapInterpolationGrid(maxGap);
496 }
497
498 /** Add a force model to the global perturbation model.
499 * <p>
500 * If this method is not called at all,
501 * the integrated orbit will follow a Keplerian evolution only.
502 * </p>
503 * @param force perturbing {@link DSSTForceModel force} to add
504 * @see #removeForceModels()
505 * @see #setMu(double)
506 */
507 public void addForceModel(final DSSTForceModel force) {
508
509 if (force instanceof DSSTNewtonianAttraction) {
510 // we want to add the central attraction force model
511
512 // ensure we are notified of any mu change
513 force.getParametersDrivers().get(0).addObserver(new ParameterObserver() {
514 /** {@inheritDoc} */
515 @Override
516 public void valueChanged(final double previousValue, final ParameterDriver driver) {
517 superSetMu(driver.getValue());
518 }
519 });
520
521 if (hasNewtonianAttraction()) {
522 // there is already a central attraction model, replace it
523 forceModels.set(forceModels.size() - 1, force);
524 } else {
525 // there are no central attraction model yet, add it at the end of the list
526 forceModels.add(force);
527 }
528 } else {
529 // we want to add a perturbing force model
530 if (hasNewtonianAttraction()) {
531 // insert the new force model before Newtonian attraction,
532 // which should always be the last one in the list
533 forceModels.add(forceModels.size() - 1, force);
534 } else {
535 // we only have perturbing force models up to now, just append at the end of the list
536 forceModels.add(force);
537 }
538 }
539
540 force.registerAttitudeProvider(getAttitudeProvider());
541
542 }
543
544 /** Remove all perturbing force models from the global perturbation model
545 * (except central attraction).
546 * <p>
547 * Once all perturbing forces have been removed (and as long as no new force model is added),
548 * the integrated orbit will follow a Keplerian evolution only.
549 * </p>
550 * @see #addForceModel(DSSTForceModel)
551 */
552 public void removeForceModels() {
553 final int last = forceModels.size() - 1;
554 if (hasNewtonianAttraction()) {
555 // preserve the Newtonian attraction model at the end
556 final DSSTForceModel newton = forceModels.get(last);
557 forceModels.clear();
558 forceModels.add(newton);
559 } else {
560 forceModels.clear();
561 }
562 }
563
564 /** Get all the force models, perturbing forces and Newtonian attraction included.
565 * @return list of perturbing force models, with Newtonian attraction being the
566 * last one
567 * @see #addForceModel(DSSTForceModel)
568 * @see #setMu(double)
569 */
570 public List<DSSTForceModel> getAllForceModels() {
571 return Collections.unmodifiableList(forceModels);
572 }
573
574 /** Get propagation parameter type.
575 * @return orbit type used for propagation
576 */
577 public OrbitType getOrbitType() {
578 return super.getOrbitType();
579 }
580
581 /** Get propagation parameter type.
582 * @return angle type to use for propagation
583 */
584 public PositionAngle getPositionAngleType() {
585 return super.getPositionAngleType();
586 }
587
588 /** Conversion from mean to osculating orbit.
589 * <p>
590 * Compute osculating state <b>in a DSST sense</b>, corresponding to the
591 * mean SpacecraftState in input, and according to the Force models taken
592 * into account.
593 * </p><p>
594 * Since the osculating state is obtained by adding short-periodic variation
595 * of each force model, the resulting output will depend on the
596 * force models parameterized in input.
597 * </p>
598 * @param mean Mean state to convert
599 * @param forces Forces to take into account
600 * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
601 * like atmospheric drag, radiation pressure or specific user-defined models)
602 * @return osculating state in a DSST sense
603 */
604 public static SpacecraftState computeOsculatingState(final SpacecraftState mean,
605 final AttitudeProvider attitudeProvider,
606 final Collection<DSSTForceModel> forces) {
607
608 //Create the auxiliary object
609 final AuxiliaryElements aux = new AuxiliaryElements(mean.getOrbit(), I);
610
611 // Set the force models
612 final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
613 for (final DSSTForceModel force : forces) {
614 force.registerAttitudeProvider(attitudeProvider);
615 shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, PropagationType.OSCULATING, force.getParameters()));
616 force.updateShortPeriodTerms(force.getParameters(), mean);
617 }
618
619 final EquinoctialOrbit osculatingOrbit = computeOsculatingOrbit(mean, shortPeriodTerms);
620
621 return new SpacecraftState(osculatingOrbit, mean.getAttitude(), mean.getMass(),
622 mean.getAdditionalStatesValues(), mean.getAdditionalStatesDerivatives());
623
624 }
625
626 /** Conversion from osculating to mean orbit.
627 * <p>
628 * Compute mean state <b>in a DSST sense</b>, corresponding to the
629 * osculating SpacecraftState in input, and according to the Force models
630 * taken into account.
631 * </p><p>
632 * Since the osculating state is obtained with the computation of
633 * short-periodic variation of each force model, the resulting output will
634 * depend on the force models parameterized in input.
635 * </p><p>
636 * The computation is done through a fixed-point iteration process.
637 * </p>
638 * @param osculating Osculating state to convert
639 * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
640 * like atmospheric drag, radiation pressure or specific user-defined models)
641 * @param forceModels Forces to take into account
642 * @return mean state in a DSST sense
643 */
644 public static SpacecraftState computeMeanState(final SpacecraftState osculating,
645 final AttitudeProvider attitudeProvider,
646 final Collection<DSSTForceModel> forceModels) {
647 return computeMeanState(osculating, attitudeProvider, forceModels, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
648 }
649
650 /** Conversion from osculating to mean orbit.
651 * <p>
652 * Compute mean state <b>in a DSST sense</b>, corresponding to the
653 * osculating SpacecraftState in input, and according to the Force models
654 * taken into account.
655 * </p><p>
656 * Since the osculating state is obtained with the computation of
657 * short-periodic variation of each force model, the resulting output will
658 * depend on the force models parameterized in input.
659 * </p><p>
660 * The computation is done through a fixed-point iteration process.
661 * </p>
662 * @param osculating Osculating state to convert
663 * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
664 * like atmospheric drag, radiation pressure or specific user-defined models)
665 * @param forceModels Forces to take into account
666 * @param epsilon convergence threshold for mean parameters conversion
667 * @param maxIterations maximum iterations for mean parameters conversion
668 * @return mean state in a DSST sense
669 * @since 10.1
670 */
671 public static SpacecraftState computeMeanState(final SpacecraftState osculating,
672 final AttitudeProvider attitudeProvider,
673 final Collection<DSSTForceModel> forceModels,
674 final double epsilon,
675 final int maxIterations) {
676 final Orbit meanOrbit = computeMeanOrbit(osculating, attitudeProvider, forceModels, epsilon, maxIterations);
677 return new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass(),
678 osculating.getAdditionalStatesValues(), osculating.getAdditionalStatesDerivatives());
679 }
680
681 /** Override the default value of the parameter.
682 * <p>
683 * By default, if the initial orbit is defined as osculating,
684 * it will be averaged over 2 satellite revolutions.
685 * This can be changed by using this method.
686 * </p>
687 * @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
688 * elements
689 */
690 public void setSatelliteRevolution(final int satelliteRevolution) {
691 mapper.setSatelliteRevolution(satelliteRevolution);
692 }
693
694 /** Get the number of satellite revolutions to use for converting osculating to mean elements.
695 * @return number of satellite revolutions to use for converting osculating to mean elements
696 */
697 public int getSatelliteRevolution() {
698 return mapper.getSatelliteRevolution();
699 }
700
701 /** Override the default value short periodic terms.
702 * <p>
703 * By default, short periodic terms are initialized before
704 * the numerical integration of the mean orbital elements.
705 * </p>
706 * @param shortPeriodTerms short periodic terms
707 */
708 public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
709 mapper.setShortPeriodTerms(shortPeriodTerms);
710 }
711
712 /** Get the short periodic terms.
713 * @return the short periodic terms
714 */
715 public List<ShortPeriodTerms> getShortPeriodTerms() {
716 return mapper.getShortPeriodTerms();
717 }
718
719 /** {@inheritDoc} */
720 @Override
721 public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
722 super.setAttitudeProvider(attitudeProvider);
723
724 //Register the attitude provider for each force model
725 for (final DSSTForceModel force : forceModels) {
726 force.registerAttitudeProvider(attitudeProvider);
727 }
728 }
729
730 /** Method called just before integration.
731 * <p>
732 * The default implementation does nothing, it may be specialized in subclasses.
733 * </p>
734 * @param initialState initial state
735 * @param tEnd target date at which state should be propagated
736 */
737 @Override
738 protected void beforeIntegration(final SpacecraftState initialState,
739 final AbsoluteDate tEnd) {
740
741 // check if only mean elements must be used
742 final PropagationType type = getPropagationType();
743
744 // compute common auxiliary elements
745 final AuxiliaryElements aux = new AuxiliaryElements(initialState.getOrbit(), I);
746
747 // initialize all perturbing forces
748 final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
749 for (final DSSTForceModel force : forceModels) {
750 shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, type, force.getParameters()));
751 }
752 mapper.setShortPeriodTerms(shortPeriodTerms);
753
754 // if required, insert the special short periodics step handler
755 if (type == PropagationType.OSCULATING) {
756 final ShortPeriodicsHandler spHandler = new ShortPeriodicsHandler(forceModels);
757 // Compute short periodic coefficients for this point
758 for (DSSTForceModel forceModel : forceModels) {
759 forceModel.updateShortPeriodTerms(forceModel.getParameters(), initialState);
760 }
761 final Collection<ODEStepHandler> stepHandlers = new ArrayList<ODEStepHandler>();
762 stepHandlers.add(spHandler);
763 final ODEIntegrator integrator = getIntegrator();
764 final Collection<ODEStepHandler> existing = integrator.getStepHandlers();
765 stepHandlers.addAll(existing);
766
767 integrator.clearStepHandlers();
768
769 // add back the existing handlers after the short periodics one
770 for (final ODEStepHandler sp : stepHandlers) {
771 integrator.addStepHandler(sp);
772 }
773 }
774 }
775
776 /** {@inheritDoc} */
777 @Override
778 protected void afterIntegration() {
779 // remove the special short periodics step handler if added before
780 if (getPropagationType() == PropagationType.OSCULATING) {
781 final List<ODEStepHandler> preserved = new ArrayList<ODEStepHandler>();
782 final ODEIntegrator integrator = getIntegrator();
783 for (final ODEStepHandler sp : integrator.getStepHandlers()) {
784 if (!(sp instanceof ShortPeriodicsHandler)) {
785 preserved.add(sp);
786 }
787 }
788
789 // clear the list
790 integrator.clearStepHandlers();
791
792 // add back the step handlers that were important for the user
793 for (final ODEStepHandler sp : preserved) {
794 integrator.addStepHandler(sp);
795 }
796 }
797 }
798
799 /** Compute mean state from osculating state.
800 * <p>
801 * Compute in a DSST sense the mean state corresponding to the input osculating state.
802 * </p><p>
803 * The computing is done through a fixed-point iteration process.
804 * </p>
805 * @param osculating initial osculating state
806 * @param attitudeProvider attitude provider (may be null if there are no Gaussian force models
807 * like atmospheric drag, radiation pressure or specific user-defined models)
808 * @param forceModels force models
809 * @param epsilon convergence threshold for mean parameters conversion
810 * @param maxIterations maximum iterations for mean parameters conversion
811 * @return mean state
812 */
813 private static Orbit computeMeanOrbit(final SpacecraftState osculating,
814 final AttitudeProvider attitudeProvider,
815 final Collection<DSSTForceModel> forceModels, final double epsilon, final int maxIterations) {
816
817 // rough initialization of the mean parameters
818 EquinoctialOrbit meanOrbit = (EquinoctialOrbit) OrbitType.EQUINOCTIAL.convertType(osculating.getOrbit());
819
820 // threshold for each parameter
821 final double thresholdA = epsilon * (1 + FastMath.abs(meanOrbit.getA()));
822 final double thresholdE = epsilon * (1 + meanOrbit.getE());
823 final double thresholdI = epsilon * (1 + meanOrbit.getI());
824 final double thresholdL = epsilon * FastMath.PI;
825
826 // ensure all Gaussian force models can rely on attitude
827 for (final DSSTForceModel force : forceModels) {
828 force.registerAttitudeProvider(attitudeProvider);
829 }
830
831 int i = 0;
832 while (i++ < maxIterations) {
833
834 final SpacecraftState meanState = new SpacecraftState(meanOrbit, osculating.getAttitude(), osculating.getMass());
835
836 //Create the auxiliary object
837 final AuxiliaryElements aux = new AuxiliaryElements(meanOrbit, I);
838
839 // Set the force models
840 final List<ShortPeriodTerms> shortPeriodTerms = new ArrayList<ShortPeriodTerms>();
841 for (final DSSTForceModel force : forceModels) {
842 shortPeriodTerms.addAll(force.initializeShortPeriodTerms(aux, PropagationType.OSCULATING, force.getParameters()));
843 force.updateShortPeriodTerms(force.getParameters(), meanState);
844 }
845
846 // recompute the osculating parameters from the current mean parameters
847 final EquinoctialOrbit rebuilt = computeOsculatingOrbit(meanState, shortPeriodTerms);
848
849 // adapted parameters residuals
850 final double deltaA = osculating.getA() - rebuilt.getA();
851 final double deltaEx = osculating.getEquinoctialEx() - rebuilt.getEquinoctialEx();
852 final double deltaEy = osculating.getEquinoctialEy() - rebuilt.getEquinoctialEy();
853 final double deltaHx = osculating.getHx() - rebuilt.getHx();
854 final double deltaHy = osculating.getHy() - rebuilt.getHy();
855 final double deltaLv = MathUtils.normalizeAngle(osculating.getLv() - rebuilt.getLv(), 0.0);
856
857 // check convergence
858 if (FastMath.abs(deltaA) < thresholdA &&
859 FastMath.abs(deltaEx) < thresholdE &&
860 FastMath.abs(deltaEy) < thresholdE &&
861 FastMath.abs(deltaHx) < thresholdI &&
862 FastMath.abs(deltaHy) < thresholdI &&
863 FastMath.abs(deltaLv) < thresholdL) {
864 return meanOrbit;
865 }
866
867 // update mean parameters
868 meanOrbit = new EquinoctialOrbit(meanOrbit.getA() + deltaA,
869 meanOrbit.getEquinoctialEx() + deltaEx,
870 meanOrbit.getEquinoctialEy() + deltaEy,
871 meanOrbit.getHx() + deltaHx,
872 meanOrbit.getHy() + deltaHy,
873 meanOrbit.getLv() + deltaLv,
874 PositionAngle.TRUE, meanOrbit.getFrame(),
875 meanOrbit.getDate(), meanOrbit.getMu());
876 }
877
878 throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_DSST_MEAN_PARAMETERS, i);
879
880 }
881
882 /** Compute osculating state from mean state.
883 * <p>
884 * Compute and add the short periodic variation to the mean {@link SpacecraftState}.
885 * </p>
886 * @param meanState initial mean state
887 * @param shortPeriodTerms short period terms
888 * @return osculating state
889 */
890 private static EquinoctialOrbit computeOsculatingOrbit(final SpacecraftState meanState,
891 final List<ShortPeriodTerms> shortPeriodTerms) {
892
893 final double[] mean = new double[6];
894 final double[] meanDot = new double[6];
895 OrbitType.EQUINOCTIAL.mapOrbitToArray(meanState.getOrbit(), PositionAngle.MEAN, mean, meanDot);
896 final double[] y = mean.clone();
897 for (final ShortPeriodTerms spt : shortPeriodTerms) {
898 final double[] shortPeriodic = spt.value(meanState.getOrbit());
899 for (int i = 0; i < shortPeriodic.length; i++) {
900 y[i] += shortPeriodic[i];
901 }
902 }
903 return (EquinoctialOrbit) OrbitType.EQUINOCTIAL.mapArrayToOrbit(y, meanDot,
904 PositionAngle.MEAN, meanState.getDate(),
905 meanState.getMu(), meanState.getFrame());
906 }
907
908 /** {@inheritDoc} */
909 @Override
910 protected SpacecraftState getInitialIntegrationState() {
911 if (initialIsOsculating) {
912 // the initial state is an osculating state,
913 // it must be converted to mean state
914 return computeMeanState(getInitialState(), getAttitudeProvider(), forceModels);
915 } else {
916 // the initial state is already a mean state
917 return getInitialState();
918 }
919 }
920
921 /** {@inheritDoc}
922 * <p>
923 * Note that for DSST, orbit type is hardcoded to {@link OrbitType#EQUINOCTIAL}
924 * and position angle type is hardcoded to {@link PositionAngle#MEAN}, so
925 * the corresponding parameters are ignored.
926 * </p>
927 */
928 @Override
929 protected StateMapper createMapper(final AbsoluteDate referenceDate, final double mu,
930 final OrbitType ignoredOrbitType, final PositionAngle ignoredPositionAngleType,
931 final AttitudeProvider attitudeProvider, final Frame frame) {
932
933 // create a mapper with the common settings provided as arguments
934 final MeanPlusShortPeriodicMapper newMapper =
935 new MeanPlusShortPeriodicMapper(referenceDate, mu, attitudeProvider, frame);
936
937 // copy the specific settings from the existing mapper
938 if (mapper != null) {
939 newMapper.setSatelliteRevolution(mapper.getSatelliteRevolution());
940 newMapper.setSelectedCoefficients(mapper.getSelectedCoefficients());
941 newMapper.setShortPeriodTerms(mapper.getShortPeriodTerms());
942 }
943
944 mapper = newMapper;
945 return mapper;
946
947 }
948
949
950 /** Get the short period terms value.
951 * @param meanState the mean state
952 * @return shortPeriodTerms short period terms
953 * @since 7.1
954 */
955 public double[] getShortPeriodTermsValue(final SpacecraftState meanState) {
956 final double[] sptValue = new double[6];
957
958 for (ShortPeriodTerms spt : mapper.getShortPeriodTerms()) {
959 final double[] shortPeriodic = spt.value(meanState.getOrbit());
960 for (int i = 0; i < shortPeriodic.length; i++) {
961 sptValue[i] += shortPeriodic[i];
962 }
963 }
964 return sptValue;
965 }
966
967
968 /** Internal mapper using mean parameters plus short periodic terms. */
969 private static class MeanPlusShortPeriodicMapper extends StateMapper {
970
971 /** Short periodic coefficients that must be stored as additional states. */
972 private Set<String> selectedCoefficients;
973
974 /** Number of satellite revolutions in the averaging interval. */
975 private int satelliteRevolution;
976
977 /** Short period terms. */
978 private List<ShortPeriodTerms> shortPeriodTerms;
979
980 /** Simple constructor.
981 * @param referenceDate reference date
982 * @param mu central attraction coefficient (m³/s²)
983 * @param attitudeProvider attitude provider
984 * @param frame inertial frame
985 */
986 MeanPlusShortPeriodicMapper(final AbsoluteDate referenceDate, final double mu,
987 final AttitudeProvider attitudeProvider, final Frame frame) {
988
989 super(referenceDate, mu, OrbitType.EQUINOCTIAL, PositionAngle.MEAN, attitudeProvider, frame);
990
991 this.selectedCoefficients = null;
992
993 // Default averaging period for conversion from osculating to mean elements
994 this.satelliteRevolution = 2;
995
996 this.shortPeriodTerms = Collections.emptyList();
997
998 }
999
1000 /** {@inheritDoc} */
1001 @Override
1002 public SpacecraftState mapArrayToState(final AbsoluteDate date,
1003 final double[] y, final double[] yDot,
1004 final PropagationType type) {
1005
1006 // add short periodic variations to mean elements to get osculating elements
1007 // (the loop may not be performed if there are no force models and in the
1008 // case we want to remain in mean parameters only)
1009 final double[] elements = y.clone();
1010 final DoubleArrayDictionary coefficients;
1011 if (type == PropagationType.MEAN) {
1012 coefficients = null;
1013 } else {
1014 final Orbit meanOrbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngle.MEAN, date, getMu(), getFrame());
1015 coefficients = selectedCoefficients == null ? null : new DoubleArrayDictionary();
1016 for (final ShortPeriodTerms spt : shortPeriodTerms) {
1017 final double[] shortPeriodic = spt.value(meanOrbit);
1018 for (int i = 0; i < shortPeriodic.length; i++) {
1019 elements[i] += shortPeriodic[i];
1020 }
1021 if (selectedCoefficients != null) {
1022 coefficients.putAll(spt.getCoefficients(date, selectedCoefficients));
1023 }
1024 }
1025 }
1026
1027 final double mass = elements[6];
1028 if (mass <= 0.0) {
1029 throw new OrekitException(OrekitMessages.SPACECRAFT_MASS_BECOMES_NEGATIVE, mass);
1030 }
1031
1032 final Orbit orbit = OrbitType.EQUINOCTIAL.mapArrayToOrbit(elements, yDot, PositionAngle.MEAN, date, getMu(), getFrame());
1033 final Attitude attitude = getAttitudeProvider().getAttitude(orbit, date, getFrame());
1034
1035 if (coefficients == null) {
1036 return new SpacecraftState(orbit, attitude, mass);
1037 } else {
1038 return new SpacecraftState(orbit, attitude, mass, coefficients);
1039 }
1040
1041 }
1042
1043 /** {@inheritDoc} */
1044 @Override
1045 public void mapStateToArray(final SpacecraftState state, final double[] y, final double[] yDot) {
1046
1047 OrbitType.EQUINOCTIAL.mapOrbitToArray(state.getOrbit(), PositionAngle.MEAN, y, yDot);
1048 y[6] = state.getMass();
1049
1050 }
1051
1052 /** Set the number of satellite revolutions to use for converting osculating to mean elements.
1053 * <p>
1054 * By default, if the initial orbit is defined as osculating,
1055 * it will be averaged over 2 satellite revolutions.
1056 * This can be changed by using this method.
1057 * </p>
1058 * @param satelliteRevolution number of satellite revolutions to use for converting osculating to mean
1059 * elements
1060 */
1061 public void setSatelliteRevolution(final int satelliteRevolution) {
1062 this.satelliteRevolution = satelliteRevolution;
1063 }
1064
1065 /** Get the number of satellite revolutions to use for converting osculating to mean elements.
1066 * @return number of satellite revolutions to use for converting osculating to mean elements
1067 */
1068 public int getSatelliteRevolution() {
1069 return satelliteRevolution;
1070 }
1071
1072 /** Set the selected short periodic coefficients that must be stored as additional states.
1073 * @param selectedCoefficients short periodic coefficients that must be stored as additional states
1074 * (null means no coefficients are selected, empty set means all coefficients are selected)
1075 */
1076 public void setSelectedCoefficients(final Set<String> selectedCoefficients) {
1077 this.selectedCoefficients = selectedCoefficients;
1078 }
1079
1080 /** Get the selected short periodic coefficients that must be stored as additional states.
1081 * @return short periodic coefficients that must be stored as additional states
1082 * (null means no coefficients are selected, empty set means all coefficients are selected)
1083 */
1084 public Set<String> getSelectedCoefficients() {
1085 return selectedCoefficients;
1086 }
1087
1088 /** Set the short period terms.
1089 * @param shortPeriodTerms short period terms
1090 * @since 7.1
1091 */
1092 public void setShortPeriodTerms(final List<ShortPeriodTerms> shortPeriodTerms) {
1093 this.shortPeriodTerms = shortPeriodTerms;
1094 }
1095
1096 /** Get the short period terms.
1097 * @return shortPeriodTerms short period terms
1098 * @since 7.1
1099 */
1100 public List<ShortPeriodTerms> getShortPeriodTerms() {
1101 return shortPeriodTerms;
1102 }
1103
1104 }
1105
1106 /** {@inheritDoc} */
1107 @Override
1108 protected MainStateEquations getMainStateEquations(final ODEIntegrator integrator) {
1109 return new Main(integrator);
1110 }
1111
1112 /** Internal class for mean parameters integration. */
1113 private class Main implements MainStateEquations {
1114
1115 /** Derivatives array. */
1116 private final double[] yDot;
1117
1118 /** Simple constructor.
1119 * @param integrator numerical integrator to use for propagation.
1120 */
1121 Main(final ODEIntegrator integrator) {
1122 yDot = new double[7];
1123
1124 for (final DSSTForceModel forceModel : forceModels) {
1125 final EventDetector[] modelDetectors = forceModel.getEventsDetectors();
1126 if (modelDetectors != null) {
1127 for (final EventDetector detector : modelDetectors) {
1128 setUpEventDetector(integrator, detector);
1129 }
1130 }
1131 }
1132
1133 }
1134
1135 /** {@inheritDoc} */
1136 @Override
1137 public void init(final SpacecraftState initialState, final AbsoluteDate target) {
1138 forceModels.forEach(fm -> fm.init(initialState, target));
1139 }
1140
1141 /** {@inheritDoc} */
1142 @Override
1143 public double[] computeDerivatives(final SpacecraftState state) {
1144
1145 Arrays.fill(yDot, 0.0);
1146
1147 // compute common auxiliary elements
1148 final AuxiliaryElements auxiliaryElements = new AuxiliaryElements(state.getOrbit(), I);
1149
1150 // compute the contributions of all perturbing forces
1151 for (final DSSTForceModel forceModel : forceModels) {
1152 final double[] daidt = elementRates(forceModel, state, auxiliaryElements, forceModel.getParameters());
1153 for (int i = 0; i < daidt.length; i++) {
1154 yDot[i] += daidt[i];
1155 }
1156 }
1157
1158 return yDot.clone();
1159 }
1160
1161 /** This method allows to compute the mean equinoctial elements rates da<sub>i</sub> / dt
1162 * for a specific force model.
1163 * @param forceModel force to take into account
1164 * @param state current state
1165 * @param auxiliaryElements auxiliary elements related to the current orbit
1166 * @param parameters force model parameters
1167 * @return the mean equinoctial elements rates da<sub>i</sub> / dt
1168 */
1169 private double[] elementRates(final DSSTForceModel forceModel,
1170 final SpacecraftState state,
1171 final AuxiliaryElements auxiliaryElements,
1172 final double[] parameters) {
1173 return forceModel.getMeanElementRate(state, auxiliaryElements, parameters);
1174 }
1175
1176 }
1177
1178 /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
1179 * <p>
1180 * The errors are estimated from partial derivatives properties of orbits,
1181 * starting from a scalar position error specified by the user.
1182 * Considering the energy conservation equation V = sqrt(mu (2/r - 1/a)),
1183 * we get at constant energy (i.e. on a Keplerian trajectory):
1184 *
1185 * <pre>
1186 * V r² |dV| = mu |dr|
1187 * </pre>
1188 *
1189 * <p> So we deduce a scalar velocity error consistent with the position error. From here, we apply
1190 * orbits Jacobians matrices to get consistent errors on orbital parameters.
1191 *
1192 * <p>
1193 * The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
1194 * local estimates, not global ones. So some care must be taken when using these tolerances.
1195 * Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
1196 * position after several orbits integration.
1197 * </p>
1198 *
1199 * @param dP user specified position error (m)
1200 * @param orbit reference orbit
1201 * @return a two rows array, row 0 being the absolute tolerance error
1202 * and row 1 being the relative tolerance error
1203 */
1204 public static double[][] tolerances(final double dP, final Orbit orbit) {
1205
1206 return NumericalPropagator.tolerances(dP, orbit, OrbitType.EQUINOCTIAL);
1207
1208 }
1209
1210 /** Estimate tolerance vectors for an AdaptativeStepsizeIntegrator.
1211 * <p>
1212 * The errors are estimated from partial derivatives properties of orbits,
1213 * starting from scalar position and velocity errors specified by the user.
1214 * <p>
1215 * The tolerances are only <em>orders of magnitude</em>, and integrator tolerances are only
1216 * local estimates, not global ones. So some care must be taken when using these tolerances.
1217 * Setting 1mm as a position error does NOT mean the tolerances will guarantee a 1mm error
1218 * position after several orbits integration.
1219 * </p>
1220 *
1221 * @param dP user specified position error (m)
1222 * @param dV user specified velocity error (m/s)
1223 * @param orbit reference orbit
1224 * @return a two rows array, row 0 being the absolute tolerance error
1225 * and row 1 being the relative tolerance error
1226 * @since 10.3
1227 */
1228 public static double[][] tolerances(final double dP, final double dV, final Orbit orbit) {
1229
1230 return NumericalPropagator.tolerances(dP, dV, orbit, OrbitType.EQUINOCTIAL);
1231
1232 }
1233
1234 /** Step handler used to compute the parameters for the short periodic contributions.
1235 * @author Lucian Barbulescu
1236 */
1237 private class ShortPeriodicsHandler implements ODEStepHandler {
1238
1239 /** Force models used to compute short periodic terms. */
1240 private final List<DSSTForceModel> forceModels;
1241
1242 /** Constructor.
1243 * @param forceModels force models
1244 */
1245 ShortPeriodicsHandler(final List<DSSTForceModel> forceModels) {
1246 this.forceModels = forceModels;
1247 }
1248
1249 /** {@inheritDoc} */
1250 @Override
1251 public void handleStep(final ODEStateInterpolator interpolator) {
1252
1253 // Get the grid points to compute
1254 final double[] interpolationPoints =
1255 interpolationgrid.getGridPoints(interpolator.getPreviousState().getTime(),
1256 interpolator.getCurrentState().getTime());
1257
1258 final SpacecraftState[] meanStates = new SpacecraftState[interpolationPoints.length];
1259 for (int i = 0; i < interpolationPoints.length; ++i) {
1260
1261 // Build the mean state interpolated at grid point
1262 final double time = interpolationPoints[i];
1263 final ODEStateAndDerivative sd = interpolator.getInterpolatedState(time);
1264 meanStates[i] = mapper.mapArrayToState(time,
1265 sd.getPrimaryState(),
1266 sd.getPrimaryDerivative(),
1267 PropagationType.MEAN);
1268 }
1269
1270 // Computate short periodic coefficients for this step
1271 for (DSSTForceModel forceModel : forceModels) {
1272 forceModel.updateShortPeriodTerms(forceModel.getParameters(), meanStates);
1273 }
1274
1275 }
1276 }
1277 }