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