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.conversion;
18  
19  import org.hipparchus.exception.LocalizedCoreFormats;
20  import org.hipparchus.util.FastMath;
21  import org.orekit.attitudes.AttitudeProvider;
22  import org.orekit.attitudes.FrameAlignedProvider;
23  import org.orekit.errors.OrekitException;
24  import org.orekit.errors.OrekitIllegalArgumentException;
25  import org.orekit.errors.OrekitMessages;
26  import org.orekit.forces.gravity.NewtonianAttraction;
27  import org.orekit.frames.Frame;
28  import org.orekit.orbits.Orbit;
29  import org.orekit.orbits.OrbitType;
30  import org.orekit.orbits.PositionAngleType;
31  import org.orekit.propagation.AbstractPropagator;
32  import org.orekit.propagation.Propagator;
33  import org.orekit.propagation.integration.AdditionalDerivativesProvider;
34  import org.orekit.time.AbsoluteDate;
35  import org.orekit.utils.ParameterDriver;
36  import org.orekit.utils.ParameterDriversList;
37  import org.orekit.utils.ParameterDriversList.DelegatingDriver;
38  import org.orekit.utils.ParameterObserver;
39  import org.orekit.utils.TimeSpanMap;
40  import org.orekit.utils.TimeSpanMap.Span;
41  
42  import java.util.ArrayList;
43  import java.util.List;
44  
45  /** Base class for propagator builders.
46   * @param <T> type of the propagator
47   * @author Pascal Parraud
48   * @since 7.1
49   */
50  public abstract class AbstractPropagatorBuilder<T extends AbstractPropagator> implements PropagatorBuilder {
51  
52      /** Central attraction scaling factor.
53       * <p>
54       * We use a power of 2 to avoid numeric noise introduction
55       * in the multiplications/divisions sequences.
56       * </p>
57       */
58      private static final double MU_SCALE = FastMath.scalb(1.0, 32);
59  
60      /** Date of the initial orbit. */
61      private AbsoluteDate initialOrbitDate;
62  
63      /** Frame in which the orbit is propagated. */
64      private final Frame frame;
65  
66      /** Central attraction coefficient (m³/s²). */
67      private double mu;
68  
69      /** Initial mass. */
70      private double mass;
71  
72      /** Drivers for orbital parameters. */
73      private final ParameterDriversList orbitalDrivers;
74  
75      /** List of the supported parameters. */
76      private final ParameterDriversList propagationDrivers;
77  
78      /** Orbit type to use. */
79      private final OrbitType orbitType;
80  
81      /** Position angle type to use. */
82      private final PositionAngleType positionAngleType;
83  
84      /** Position scale to use for the orbital drivers. */
85      private final double positionScale;
86  
87      /** Attitude provider for the propagator. */
88      private AttitudeProvider attitudeProvider;
89  
90      /** Additional derivatives providers.
91       * @since 11.1
92       */
93      private final List<AdditionalDerivativesProvider> additionalDerivativesProviders;
94  
95      /** Build a new instance.
96       * <p>
97       * The template orbit is used as a model to {@link
98       * #createInitialOrbit() create initial orbit}. It defines the
99       * inertial frame, the central attraction coefficient, the orbit type, and is also
100      * used together with the {@code positionScale} to convert from the {@link
101      * ParameterDriver#setNormalizedValue(double) normalized} parameters used by the
102      * callers of this builder to the real orbital parameters. The default attitude
103      * provider is aligned with the orbit's inertial frame.
104      * </p>
105      * <p>
106      * By default, all the {@link #getOrbitalParametersDrivers() orbital parameters drivers}
107      * are selected, which means that if the builder is used for orbit determination or
108      * propagator conversion, all orbital parameters will be estimated. If only a subset
109      * of the orbital parameters must be estimated, caller must retrieve the orbital
110      * parameters by calling {@link #getOrbitalParametersDrivers()} and then call
111      * {@link ParameterDriver#setSelected(boolean) setSelected(false)}.
112      * </p>
113      * @param templateOrbit reference orbit from which real orbits will be built
114      * @param positionAngleType position angle type to use
115      * @param positionScale scaling factor used for orbital parameters normalization
116      * (typically set to the expected standard deviation of the position)
117      * @param addDriverForCentralAttraction if true, a {@link ParameterDriver} should
118      * be set up for central attraction coefficient
119      * @since 8.0
120      * @see #AbstractPropagatorBuilder(Orbit, PositionAngleType, double, boolean,
121      * AttitudeProvider)
122      */
123     protected AbstractPropagatorBuilder(final Orbit templateOrbit, final PositionAngleType positionAngleType,
124                                         final double positionScale, final boolean addDriverForCentralAttraction) {
125         this(templateOrbit, positionAngleType, positionScale, addDriverForCentralAttraction,
126              new FrameAlignedProvider(templateOrbit.getFrame()), Propagator.DEFAULT_MASS);
127     }
128     /** Build a new instance.
129      * <p>
130      * The template orbit is used as a model to {@link
131      * #createInitialOrbit() create initial orbit}. It defines the
132      * inertial frame, the central attraction coefficient, the orbit type, and is also
133      * used together with the {@code positionScale} to convert from the {@link
134      * ParameterDriver#setNormalizedValue(double) normalized} parameters used by the
135      * callers of this builder to the real orbital parameters.
136      * </p>
137      * <p>
138      * By default, all the {@link #getOrbitalParametersDrivers() orbital parameters drivers}
139      * are selected, which means that if the builder is used for orbit determination or
140      * propagator conversion, all orbital parameters will be estimated. If only a subset
141      * of the orbital parameters must be estimated, caller must retrieve the orbital
142      * parameters by calling {@link #getOrbitalParametersDrivers()} and then call
143      * {@link ParameterDriver#setSelected(boolean) setSelected(false)}.
144      * </p>
145      * @param templateOrbit reference orbit from which real orbits will be built
146      * @param positionAngleType position angle type to use
147      * @param positionScale scaling factor used for orbital parameters normalization
148      * (typically set to the expected standard deviation of the position)
149      * @param addDriverForCentralAttraction if true, a {@link ParameterDriver} should
150      * be set up for central attraction coefficient
151      * @param attitudeProvider for the propagator.
152      * @since 10.1
153      * @see #AbstractPropagatorBuilder(Orbit, PositionAngleType, double, boolean)
154      */
155     protected AbstractPropagatorBuilder(final Orbit templateOrbit,
156                                         final PositionAngleType positionAngleType,
157                                         final double positionScale,
158                                         final boolean addDriverForCentralAttraction,
159                                         final AttitudeProvider attitudeProvider) {
160         this(templateOrbit, positionAngleType, positionScale, addDriverForCentralAttraction, attitudeProvider,
161                 Propagator.DEFAULT_MASS);
162     }
163 
164     /** Build a new instance.
165      * <p>
166      * The template orbit is used as a model to {@link
167      * #createInitialOrbit() create initial orbit}. It defines the
168      * inertial frame, the central attraction coefficient, the orbit type, and is also
169      * used together with the {@code positionScale} to convert from the {@link
170      * ParameterDriver#setNormalizedValue(double) normalized} parameters used by the
171      * callers of this builder to the real orbital parameters.
172      * </p>
173      * <p>
174      * By default, all the {@link #getOrbitalParametersDrivers() orbital parameters drivers}
175      * are selected, which means that if the builder is used for orbit determination or
176      * propagator conversion, all orbital parameters will be estimated. If only a subset
177      * of the orbital parameters must be estimated, caller must retrieve the orbital
178      * parameters by calling {@link #getOrbitalParametersDrivers()} and then call
179      * {@link ParameterDriver#setSelected(boolean) setSelected(false)}.
180      * </p>
181      * @param templateOrbit reference orbit from which real orbits will be built
182      * @param positionAngleType position angle type to use
183      * @param positionScale scaling factor used for orbital parameters normalization
184      * (typically set to the expected standard deviation of the position)
185      * @param addDriverForCentralAttraction if true, a {@link ParameterDriver} should
186      * be set up for central attraction coefficient
187      * @param attitudeProvider for the propagator.
188      * @param initialMass mass
189      * @since 12.2
190      * @see #AbstractPropagatorBuilder(Orbit, PositionAngleType, double, boolean)
191      */
192     protected AbstractPropagatorBuilder(final Orbit templateOrbit,
193                                         final PositionAngleType positionAngleType,
194                                         final double positionScale,
195                                         final boolean addDriverForCentralAttraction,
196                                         final AttitudeProvider attitudeProvider, final double initialMass) {
197 
198         this.initialOrbitDate    = templateOrbit.getDate();
199         this.frame               = templateOrbit.getFrame();
200         this.mu                  = templateOrbit.getMu();
201         this.propagationDrivers  = new ParameterDriversList();
202         this.orbitType           = templateOrbit.getType();
203         this.positionAngleType = positionAngleType;
204         this.positionScale       = positionScale;
205         this.orbitalDrivers      = orbitType.getDrivers(positionScale, templateOrbit, positionAngleType);
206         this.attitudeProvider = attitudeProvider;
207         this.mass         = initialMass;
208         for (final DelegatingDriver driver : orbitalDrivers.getDrivers()) {
209             driver.setSelected(true);
210         }
211 
212         this.additionalDerivativesProviders  = new ArrayList<>();
213 
214         if (addDriverForCentralAttraction) {
215             final ParameterDriver muDriver = new ParameterDriver(NewtonianAttraction.CENTRAL_ATTRACTION_COEFFICIENT,
216                                                                  mu, MU_SCALE, 0, Double.POSITIVE_INFINITY);
217             muDriver.addObserver(new ParameterObserver() {
218                 /** {@inheritDoc} */
219                 @Override
220                 public void valueChanged(final double previousValue, final ParameterDriver driver, final AbsoluteDate date) {
221                     // getValue(), can be called without argument as mu driver should have only one span
222                     AbstractPropagatorBuilder.this.mu = driver.getValue();
223                 }
224 
225                 @Override
226                 public void valueSpanMapChanged(final TimeSpanMap<Double> previousValueSpanMap, final ParameterDriver driver) {
227                     // getValue(), can be called without argument as mu driver should have only one span
228                     AbstractPropagatorBuilder.this.mu = driver.getValue();
229                 }
230             });
231             propagationDrivers.add(muDriver);
232         }
233     }
234 
235     /** Get the mass.
236      * @return the mass (kg)
237      * @since 9.2
238      */
239     public double getMass()
240     {
241         return mass;
242     }
243 
244     /** Set the initial mass.
245      * @param mass the mass (kg)
246      */
247     public void setMass(final double mass) {
248         this.mass = mass;
249     }
250 
251     /** {@inheritDoc} */
252     public OrbitType getOrbitType() {
253         return orbitType;
254     }
255 
256     /** {@inheritDoc} */
257     public PositionAngleType getPositionAngleType() {
258         return positionAngleType;
259     }
260 
261     /** {@inheritDoc} */
262     public AbsoluteDate getInitialOrbitDate() {
263         return initialOrbitDate;
264     }
265 
266     /** {@inheritDoc} */
267     public Frame getFrame() {
268         return frame;
269     }
270 
271     /** {@inheritDoc} */
272     public ParameterDriversList getOrbitalParametersDrivers() {
273         return orbitalDrivers;
274     }
275 
276     /** {@inheritDoc} */
277     public ParameterDriversList getPropagationParametersDrivers() {
278         return propagationDrivers;
279     }
280 
281     /** {@inheritDoc}. */
282     @Override
283     @SuppressWarnings("unchecked")
284     public AbstractPropagatorBuilder<T> clone() {
285         try {
286             return (AbstractPropagatorBuilder<T>) super.clone();
287         } catch (CloneNotSupportedException cnse) {
288             throw new OrekitException(OrekitMessages.PROPAGATOR_BUILDER_NOT_CLONEABLE);
289         }
290     }
291 
292     /**
293      * Get the attitude provider.
294      *
295      * @return the attitude provider
296      * @since 10.1
297      */
298     public AttitudeProvider getAttitudeProvider() {
299         return attitudeProvider;
300     }
301 
302     /**
303      * Set the attitude provider.
304      *
305      * @param attitudeProvider attitude provider
306      * @since 10.1
307      */
308     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
309         this.attitudeProvider = attitudeProvider;
310     }
311 
312     /** Get the position scale.
313      * @return the position scale used to scale the orbital drivers
314      */
315     public double getPositionScale() {
316         return positionScale;
317     }
318 
319     /** {@inheritDoc} */
320     @Override
321     public double getMu() {
322         return mu;
323     }
324 
325     /** Get the number of estimated values for selected parameters.
326      * @return number of estimated values for selected parameters
327      */
328     private int getNbValuesForSelected() {
329 
330         int count = 0;
331 
332         // count orbital parameters
333         for (final ParameterDriver driver : orbitalDrivers.getDrivers()) {
334             if (driver.isSelected()) {
335                 count += driver.getNbOfValues();
336             }
337         }
338 
339         // count propagation parameters
340         for (final ParameterDriver driver : propagationDrivers.getDrivers()) {
341             if (driver.isSelected()) {
342                 count += driver.getNbOfValues();
343             }
344         }
345 
346         return count;
347 
348     }
349 
350     /** {@inheritDoc} */
351     public double[] getSelectedNormalizedParameters() {
352 
353         // allocate array
354         final double[] selected = new double[getNbValuesForSelected()];
355 
356         // fill data
357         int index = 0;
358         for (final ParameterDriver driver : orbitalDrivers.getDrivers()) {
359             if (driver.isSelected()) {
360                 for (int spanNumber = 0; spanNumber < driver.getNbOfValues(); ++spanNumber ) {
361                     selected[index++] = driver.getNormalizedValue(AbsoluteDate.ARBITRARY_EPOCH);
362                 }
363             }
364         }
365         for (final ParameterDriver driver : propagationDrivers.getDrivers()) {
366             if (driver.isSelected()) {
367                 for (int spanNumber = 0; spanNumber < driver.getNbOfValues(); ++spanNumber ) {
368                     selected[index++] = driver.getNormalizedValue(AbsoluteDate.ARBITRARY_EPOCH);
369                 }
370             }
371         }
372 
373         return selected;
374 
375     }
376 
377     /** {@inheritDoc} */
378     @Override
379     public abstract T buildPropagator(double[] normalizedParameters);
380 
381     /** {@inheritDoc} */
382     @Override
383     public T buildPropagator() {
384         return buildPropagator(getSelectedNormalizedParameters());
385     }
386 
387     /** Build an initial orbit using the current selected parameters.
388      * <p>
389      * This method is a stripped down version of {@link #buildPropagator(double[])}
390      * that only builds the initial orbit and not the full propagator.
391      * </p>
392      * @return an initial orbit
393      * @since 8.0
394      */
395     protected Orbit createInitialOrbit() {
396         final double[] unNormalized = new double[orbitalDrivers.getNbParams()];
397         for (int i = 0; i < unNormalized.length; ++i) {
398             unNormalized[i] = orbitalDrivers.getDrivers().get(i).getValue(initialOrbitDate);
399         }
400         return getOrbitType().mapArrayToOrbit(unNormalized, null, positionAngleType, initialOrbitDate, mu, frame);
401     }
402 
403     /** Set the selected parameters.
404      * @param normalizedParameters normalized values for the selected parameters
405      */
406     protected void setParameters(final double[] normalizedParameters) {
407 
408 
409         if (normalizedParameters.length != getNbValuesForSelected()) {
410             throw new OrekitIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
411                                                      normalizedParameters.length,
412                                                      getNbValuesForSelected());
413         }
414 
415         int index = 0;
416 
417         // manage orbital parameters
418         for (final ParameterDriver driver : orbitalDrivers.getDrivers()) {
419             if (driver.isSelected()) {
420                 // If the parameter driver contains only 1 value to estimate over the all time range, which
421                 // is normally always the case for orbital drivers
422                 if (driver.getNbOfValues() == 1) {
423                     driver.setNormalizedValue(normalizedParameters[index++], null);
424 
425                 } else {
426 
427                     for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
428                         driver.setNormalizedValue(normalizedParameters[index++], span.getStart());
429                     }
430                 }
431             }
432         }
433 
434         // manage propagation parameters
435         for (final ParameterDriver driver : propagationDrivers.getDrivers()) {
436 
437             if (driver.isSelected()) {
438 
439                 for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
440                     driver.setNormalizedValue(normalizedParameters[index++], span.getStart());
441                 }
442             }
443         }
444     }
445 
446     /**
447      * Add supported parameters.
448      *
449      * @param drivers drivers for the parameters
450      */
451     protected void addSupportedParameters(final List<ParameterDriver> drivers) {
452         drivers.forEach(propagationDrivers::add);
453         propagationDrivers.sort();
454     }
455 
456     /** Reset the orbit in the propagator builder.
457      * @param newOrbit New orbit to set in the propagator builder
458      */
459     public void resetOrbit(final Orbit newOrbit) {
460 
461         // Map the new orbit in an array of double
462         final double[] orbitArray = new double[6];
463         final Orbit orbitInCorrectFrame = (newOrbit.getFrame() == frame) ? newOrbit : newOrbit.inFrame(frame);
464         orbitType.mapOrbitToArray(orbitInCorrectFrame, getPositionAngleType(), orbitArray, null);
465 
466         // Update all the orbital drivers, selected or unselected
467         // Reset values and reference values
468         final List<DelegatingDriver> orbitalDriversList = getOrbitalParametersDrivers().getDrivers();
469         int i = 0;
470         for (DelegatingDriver driver : orbitalDriversList) {
471             driver.setReferenceValue(orbitArray[i]);
472             driver.setValue(orbitArray[i++], newOrbit.getDate());
473         }
474 
475         // Change the initial orbit date in the builder
476         this.initialOrbitDate = newOrbit.getDate();
477     }
478 
479     /** Add a set of user-specified equations to be integrated along with the orbit propagation (author Shiva Iyer).
480      * @param provider provider for additional derivatives
481      * @since 11.1
482      */
483     public void addAdditionalDerivativesProvider(final AdditionalDerivativesProvider provider) {
484         additionalDerivativesProviders.add(provider);
485     }
486 
487     /** Get the list of additional equations.
488      * @return the list of additional equations
489      * @since 11.1
490      */
491     protected List<AdditionalDerivativesProvider> getAdditionalDerivativesProviders() {
492         return additionalDerivativesProviders;
493     }
494 
495     /** Deselects orbital and propagation drivers. */
496     public void deselectDynamicParameters() {
497         for (ParameterDriver driver : getPropagationParametersDrivers().getDrivers()) {
498             driver.setSelected(false);
499         }
500         for (ParameterDriver driver : getOrbitalParametersDrivers().getDrivers()) {
501             driver.setSelected(false);
502         }
503     }
504 }