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