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 final 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 final 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     /** Get the mass.
235      * @return the mass (kg)
236      * @since 9.2
237      */
238     public double getMass()
239     {
240         return mass;
241     }
242 
243     /** Set the initial mass.
244      * @param mass the mass (kg)
245      */
246     public void setMass(final double mass) {
247         this.mass = mass;
248     }
249 
250     /** {@inheritDoc} */
251     public OrbitType getOrbitType() {
252         return orbitType;
253     }
254 
255     /** {@inheritDoc} */
256     public PositionAngleType getPositionAngleType() {
257         return positionAngleType;
258     }
259 
260     /** {@inheritDoc} */
261     public AbsoluteDate getInitialOrbitDate() {
262         return initialOrbitDate;
263     }
264 
265     /** {@inheritDoc} */
266     public Frame getFrame() {
267         return frame;
268     }
269 
270     /** {@inheritDoc} */
271     public ParameterDriversList getOrbitalParametersDrivers() {
272         return orbitalDrivers;
273     }
274 
275     /** {@inheritDoc} */
276     public ParameterDriversList getPropagationParametersDrivers() {
277         return propagationDrivers;
278     }
279 
280     /** {@inheritDoc}. */
281     @Override
282     @SuppressWarnings("unchecked")
283     public AbstractPropagatorBuilder<T> clone() {
284         try {
285             return (AbstractPropagatorBuilder<T>) super.clone();
286         } catch (CloneNotSupportedException cnse) {
287             throw new OrekitException(OrekitMessages.PROPAGATOR_BUILDER_NOT_CLONEABLE);
288         }
289     }
290 
291     /**
292      * Get the attitude provider.
293      *
294      * @return the attitude provider
295      * @since 10.1
296      */
297     public AttitudeProvider getAttitudeProvider() {
298         return attitudeProvider;
299     }
300 
301     /**
302      * Set the attitude provider.
303      *
304      * @param attitudeProvider attitude provider
305      * @since 10.1
306      */
307     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
308         this.attitudeProvider = attitudeProvider;
309     }
310 
311     /** Get the position scale.
312      * @return the position scale used to scale the orbital drivers
313      */
314     public double getPositionScale() {
315         return positionScale;
316     }
317 
318     /** {@inheritDoc} */
319     @Override
320     public double getMu() {
321         return mu;
322     }
323 
324     /** Get the number of estimated values for selected parameters.
325      * @return number of estimated values for selected parameters
326      */
327     private int getNbValuesForSelected() {
328 
329         int count = 0;
330 
331         // count orbital parameters
332         for (final ParameterDriver driver : orbitalDrivers.getDrivers()) {
333             if (driver.isSelected()) {
334                 count += driver.getNbOfValues();
335             }
336         }
337 
338         // count propagation parameters
339         for (final ParameterDriver driver : propagationDrivers.getDrivers()) {
340             if (driver.isSelected()) {
341                 count += driver.getNbOfValues();
342             }
343         }
344 
345         return count;
346 
347     }
348 
349     /** {@inheritDoc} */
350     public double[] getSelectedNormalizedParameters() {
351 
352         // allocate array
353         final double[] selected = new double[getNbValuesForSelected()];
354 
355         // fill data
356         int index = 0;
357         for (final ParameterDriver driver : orbitalDrivers.getDrivers()) {
358             if (driver.isSelected()) {
359                 for (int spanNumber = 0; spanNumber < driver.getNbOfValues(); ++spanNumber ) {
360                     selected[index++] = driver.getNormalizedValue(AbsoluteDate.ARBITRARY_EPOCH);
361                 }
362             }
363         }
364         for (final ParameterDriver driver : propagationDrivers.getDrivers()) {
365             if (driver.isSelected()) {
366                 for (int spanNumber = 0; spanNumber < driver.getNbOfValues(); ++spanNumber ) {
367                     selected[index++] = driver.getNormalizedValue(AbsoluteDate.ARBITRARY_EPOCH);
368                 }
369             }
370         }
371 
372         return selected;
373 
374     }
375 
376     /** {@inheritDoc} */
377     @Override
378     public abstract T buildPropagator(double[] normalizedParameters);
379 
380     /** {@inheritDoc} */
381     @Override
382     public T buildPropagator() {
383         return buildPropagator(getSelectedNormalizedParameters());
384     }
385 
386     /** Build an initial orbit using the current selected parameters.
387      * <p>
388      * This method is a stripped down version of {@link #buildPropagator(double[])}
389      * that only builds the initial orbit and not the full propagator.
390      * </p>
391      * @return an initial orbit
392      * @since 8.0
393      */
394     protected Orbit createInitialOrbit() {
395         final double[] unNormalized = new double[orbitalDrivers.getNbParams()];
396         for (int i = 0; i < unNormalized.length; ++i) {
397             unNormalized[i] = orbitalDrivers.getDrivers().get(i).getValue(initialOrbitDate);
398         }
399         return getOrbitType().mapArrayToOrbit(unNormalized, null, positionAngleType, initialOrbitDate, mu, frame);
400     }
401 
402     /** Set the selected parameters.
403      * @param normalizedParameters normalized values for the selected parameters
404      */
405     protected void setParameters(final double[] normalizedParameters) {
406 
407 
408         if (normalizedParameters.length != getNbValuesForSelected()) {
409             throw new OrekitIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
410                                                      normalizedParameters.length,
411                                                      getNbValuesForSelected());
412         }
413 
414         int index = 0;
415 
416         // manage orbital parameters
417         for (final ParameterDriver driver : orbitalDrivers.getDrivers()) {
418             if (driver.isSelected()) {
419                 // If the parameter driver contains only 1 value to estimate over the all time range, which
420                 // is normally always the case for orbital drivers
421                 if (driver.getNbOfValues() == 1) {
422                     driver.setNormalizedValue(normalizedParameters[index++], null);
423 
424                 } else {
425 
426                     for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
427                         driver.setNormalizedValue(normalizedParameters[index++], span.getStart());
428                     }
429                 }
430             }
431         }
432 
433         // manage propagation parameters
434         for (final ParameterDriver driver : propagationDrivers.getDrivers()) {
435 
436             if (driver.isSelected()) {
437 
438                 for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
439                     driver.setNormalizedValue(normalizedParameters[index++], span.getStart());
440                 }
441             }
442         }
443     }
444 
445     /**
446      * Add supported parameters.
447      *
448      * @param drivers drivers for the parameters
449      */
450     protected void addSupportedParameters(final List<ParameterDriver> drivers) {
451         drivers.forEach(propagationDrivers::add);
452         propagationDrivers.sort();
453     }
454 
455     /** Reset the orbit in the propagator builder.
456      * @param newOrbit New orbit to set in the propagator builder
457      */
458     public void resetOrbit(final Orbit newOrbit) {
459 
460         // Map the new orbit in an array of double
461         final double[] orbitArray = new double[6];
462         final Orbit orbitInCorrectFrame = (newOrbit.getFrame() == frame) ? newOrbit : newOrbit.inFrame(frame);
463         orbitType.mapOrbitToArray(orbitInCorrectFrame, getPositionAngleType(), orbitArray, null);
464 
465         // Update all the orbital drivers, selected or unselected
466         // Reset values and reference values
467         final List<DelegatingDriver> orbitalDriversList = getOrbitalParametersDrivers().getDrivers();
468         int i = 0;
469         for (DelegatingDriver driver : orbitalDriversList) {
470             driver.setReferenceValue(orbitArray[i]);
471             driver.setValue(orbitArray[i++], newOrbit.getDate());
472         }
473 
474         // Change the initial orbit date in the builder
475         this.initialOrbitDate = newOrbit.getDate();
476     }
477 
478     /** Add a set of user-specified equations to be integrated along with the orbit propagation (author Shiva Iyer).
479      * @param provider provider for additional derivatives
480      * @since 11.1
481      */
482     public void addAdditionalDerivativesProvider(final AdditionalDerivativesProvider provider) {
483         additionalDerivativesProviders.add(provider);
484     }
485 
486     /** Get the list of additional equations.
487      * @return the list of additional equations
488      * @since 11.1
489      */
490     protected List<AdditionalDerivativesProvider> getAdditionalDerivativesProviders() {
491         return additionalDerivativesProviders;
492     }
493 
494     /** Deselects orbital and propagation drivers. */
495     public void deselectDynamicParameters() {
496         for (ParameterDriver driver : getPropagationParametersDrivers().getDrivers()) {
497             driver.setSelected(false);
498         }
499         for (ParameterDriver driver : getOrbitalParametersDrivers().getDrivers()) {
500             driver.setSelected(false);
501         }
502     }
503 }