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