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 java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import org.orekit.attitudes.Attitude;
24  import org.orekit.attitudes.AttitudeProvider;
25  import org.orekit.attitudes.FrameAlignedProvider;
26  import org.orekit.estimation.leastsquares.BatchLSModel;
27  import org.orekit.estimation.leastsquares.ModelObserver;
28  import org.orekit.estimation.measurements.ObservedMeasurement;
29  import org.orekit.forces.ForceModel;
30  import org.orekit.forces.gravity.NewtonianAttraction;
31  import org.orekit.forces.maneuvers.ImpulseManeuver;
32  import org.orekit.orbits.Orbit;
33  import org.orekit.orbits.PositionAngleType;
34  import org.orekit.propagation.PropagationType;
35  import org.orekit.propagation.Propagator;
36  import org.orekit.propagation.SpacecraftState;
37  import org.orekit.propagation.integration.AdditionalDerivativesProvider;
38  import org.orekit.propagation.numerical.NumericalPropagator;
39  import org.orekit.utils.ParameterDriver;
40  import org.orekit.utils.ParameterDriversList;
41  
42  /** Builder for numerical propagator.
43   * @author Pascal Parraud
44   * @since 6.0
45   */
46  public class NumericalPropagatorBuilder extends AbstractIntegratedPropagatorBuilder<NumericalPropagator> {
47  
48      /** Force models used during the extrapolation of the orbit. */
49      private final List<ForceModel> forceModels;
50  
51      /** Impulse maneuvers. */
52      private final List<ImpulseManeuver> impulseManeuvers;
53  
54      /** Build a new instance.
55       * <p>
56       * The reference orbit is used as a model to {@link
57       * #createInitialOrbit() create initial orbit}. It defines the
58       * inertial frame, the central attraction coefficient, and is also used together
59       * with the {@code positionScale} to convert from the {@link
60       * ParameterDriver#setNormalizedValue(double) normalized} parameters used by the
61       * callers of this builder to the real orbital parameters.
62       * The default attitude provider is aligned with the orbit's inertial frame.
63       * </p>
64       *
65       * @param referenceOrbit reference orbit from which real orbits will be built
66       * @param builder first order integrator builder
67       * @param positionAngleType position angle type to use
68       * @param positionScale scaling factor used for orbital parameters normalization
69       * (typically set to the expected standard deviation of the position)
70       * @since 8.0
71       * @see #NumericalPropagatorBuilder(Orbit, ODEIntegratorBuilder, PositionAngleType,
72       * double, AttitudeProvider)
73       */
74      public NumericalPropagatorBuilder(final Orbit referenceOrbit,
75                                        final ODEIntegratorBuilder builder,
76                                        final PositionAngleType positionAngleType,
77                                        final double positionScale) {
78          this(referenceOrbit, builder, positionAngleType, positionScale,
79               FrameAlignedProvider.of(referenceOrbit.getFrame()));
80      }
81  
82      /** Build a new instance.
83       * <p>
84       * The reference orbit is used as a model to {@link
85       * #createInitialOrbit() create initial orbit}. It defines the
86       * inertial frame, the central attraction coefficient, and is also used together
87       * with the {@code positionScale} to convert from the {@link
88       * ParameterDriver#setNormalizedValue(double) normalized} parameters used by the
89       * callers of this builder to the real orbital parameters.
90       * </p>
91       * @param referenceOrbit reference orbit from which real orbits will be built
92       * @param builder first order integrator builder
93       * @param positionAngleType position angle type to use
94       * @param positionScale scaling factor used for orbital parameters normalization
95       * (typically set to the expected standard deviation of the position)
96       * @param attitudeProvider attitude law.
97       * @since 10.1
98       */
99      public NumericalPropagatorBuilder(final Orbit referenceOrbit,
100                                       final ODEIntegratorBuilder builder,
101                                       final PositionAngleType positionAngleType,
102                                       final double positionScale,
103                                       final AttitudeProvider attitudeProvider) {
104         super(referenceOrbit, builder, positionAngleType, positionScale, PropagationType.OSCULATING, attitudeProvider, Propagator.DEFAULT_MASS);
105         this.forceModels = new ArrayList<>();
106         this.impulseManeuvers = new ArrayList<>();
107     }
108 
109     /** Copy constructor.
110      *
111      * @param builder builder to copy
112      */
113     private NumericalPropagatorBuilder(final NumericalPropagatorBuilder builder) {
114         this(builder.createInitialOrbit(),
115              builder.getIntegratorBuilder(),
116              builder.getPositionAngleType(),
117              builder.getPositionScale(),
118              builder.getAttitudeProvider());
119     }
120 
121     /** {@inheritDoc}. */
122     @Override
123     public NumericalPropagatorBuilder clone() {
124         // Call to super clone() method to avoid warning
125         final NumericalPropagatorBuilder clonedBuilder = (NumericalPropagatorBuilder) super.clone();
126 
127         // Use copy constructor to unlink orbital drivers
128         final NumericalPropagatorBuilder builder =  new NumericalPropagatorBuilder(clonedBuilder);
129 
130         // Set mass and force models
131         builder.setMass(getMass());
132         for (ForceModel model : forceModels) {
133             builder.addForceModel(model);
134         }
135 
136         // Add impulse maneuvers
137         impulseManeuvers.forEach(builder::addImpulseManeuver);
138 
139         return builder;
140     }
141 
142 
143     /**
144      * Add impulse maneuver.
145      * @param impulseManeuver impulse maneuver
146      * @since 12.2
147      */
148     public void addImpulseManeuver(final ImpulseManeuver impulseManeuver) {
149         impulseManeuvers.add(impulseManeuver);
150     }
151 
152     /**
153      * Remove all impulse maneuvers.
154      * @since 12.2
155      */
156     public void clearImpulseManeuvers() {
157         impulseManeuvers.clear();
158     }
159 
160     /** Get the list of all force models.
161      * @return the list of all force models
162      * @since 9.2
163      */
164     public List<ForceModel> getAllForceModels()
165     {
166         return Collections.unmodifiableList(forceModels);
167     }
168 
169     /** Add a force model to the global perturbation model.
170      * <p>If this method is not called at all, the integrated orbit will follow
171      * a Keplerian evolution only.</p>
172      * @param model perturbing {@link ForceModel} to add
173      */
174     public void addForceModel(final ForceModel model) {
175         if (model instanceof NewtonianAttraction) {
176             // we want to add the central attraction force model
177             if (hasNewtonianAttraction()) {
178                 // there is already a central attraction model, replace it
179                 forceModels.set(forceModels.size() - 1, model);
180             } else {
181                 // there are no central attraction model yet, add it at the end of the list
182                 forceModels.add(model);
183             }
184         } else {
185             // we want to add a perturbing force model
186             if (hasNewtonianAttraction()) {
187                 // insert the new force model before Newtonian attraction,
188                 // which should always be the last one in the list
189                 forceModels.add(forceModels.size() - 1, model);
190             } else {
191                 // we only have perturbing force models up to now, just append at the end of the list
192                 forceModels.add(model);
193             }
194         }
195 
196         addSupportedParameters(model.getParametersDrivers());
197     }
198 
199     /** {@inheritDoc} */
200     public NumericalPropagator buildPropagator(final double[] normalizedParameters) {
201 
202         setParameters(normalizedParameters);
203         final Orbit           orbit    = createInitialOrbit();
204         final Attitude        attitude =
205                 getAttitudeProvider().getAttitude(orbit, orbit.getDate(), getFrame());
206         final SpacecraftState state    = new SpacecraftState(orbit, attitude).withMass(getMass());
207 
208         final NumericalPropagator propagator = new NumericalPropagator(
209                 getIntegratorBuilder().buildIntegrator(orbit, getOrbitType(), getPositionAngleType()),
210                 getAttitudeProvider());
211         propagator.setOrbitType(getOrbitType());
212         propagator.setPositionAngleType(getPositionAngleType());
213 
214         // Configure force models
215         if (!hasNewtonianAttraction()) {
216             // There are no central attraction model yet, add it at the end of the list
217             addForceModel(new NewtonianAttraction(orbit.getMu()));
218         }
219         for (ForceModel model : forceModels) {
220             propagator.addForceModel(model);
221         }
222         impulseManeuvers.forEach(propagator::addEventDetector);
223 
224         propagator.resetInitialState(state);
225 
226         // Add additional derivatives providers to the propagator
227         for (AdditionalDerivativesProvider provider: getAdditionalDerivativesProviders()) {
228             propagator.addAdditionalDerivativesProvider(provider);
229         }
230 
231         return propagator;
232 
233     }
234 
235     /** {@inheritDoc} */
236     @Override
237     public BatchLSModel buildLeastSquaresModel(final PropagatorBuilder[] builders,
238                                                final List<ObservedMeasurement<?>> measurements,
239                                                final ParameterDriversList estimatedMeasurementsParameters,
240                                                final ModelObserver observer) {
241         return new BatchLSModel(builders, measurements, estimatedMeasurementsParameters, observer);
242     }
243 
244     /** Check if Newtonian attraction force model is available.
245      * <p>
246      * Newtonian attraction is always the last force model in the list.
247      * </p>
248      * @return true if Newtonian attraction force model is available
249      */
250     private boolean hasNewtonianAttraction() {
251         final int last = forceModels.size() - 1;
252         return last >= 0 && forceModels.get(last) instanceof NewtonianAttraction;
253     }
254 
255 }