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