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     /**
110      * Add impulse maneuver.
111      * @param impulseManeuver impulse maneuver
112      * @since 12.2
113      */
114     public void addImpulseManeuver(final ImpulseManeuver impulseManeuver) {
115         impulseManeuvers.add(impulseManeuver);
116     }
117 
118     /**
119      * Remove all impulse maneuvers.
120      * @since 12.2
121      */
122     public void clearImpulseManeuvers() {
123         impulseManeuvers.clear();
124     }
125 
126     /** Get the list of all force models.
127      * @return the list of all force models
128      * @since 9.2
129      */
130     public List<ForceModel> getAllForceModels()
131     {
132         return Collections.unmodifiableList(forceModels);
133     }
134 
135     /** Add a force model to the global perturbation model.
136      * <p>If this method is not called at all, the integrated orbit will follow
137      * a Keplerian evolution only.</p>
138      * @param model perturbing {@link ForceModel} to add
139      */
140     public void addForceModel(final ForceModel model) {
141         if (model instanceof NewtonianAttraction) {
142             // we want to add the central attraction force model
143             if (hasNewtonianAttraction()) {
144                 // there is already a central attraction model, replace it
145                 forceModels.set(forceModels.size() - 1, model);
146             } else {
147                 // there are no central attraction model yet, add it at the end of the list
148                 forceModels.add(model);
149             }
150         } else {
151             // we want to add a perturbing force model
152             if (hasNewtonianAttraction()) {
153                 // insert the new force model before Newtonian attraction,
154                 // which should always be the last one in the list
155                 forceModels.add(forceModels.size() - 1, model);
156             } else {
157                 // we only have perturbing force models up to now, just append at the end of the list
158                 forceModels.add(model);
159             }
160         }
161 
162         addSupportedParameters(model.getParametersDrivers());
163     }
164 
165     /** {@inheritDoc} */
166     public NumericalPropagator buildPropagator(final double[] normalizedParameters) {
167 
168         setParameters(normalizedParameters);
169         final Orbit           orbit    = createInitialOrbit();
170         final Attitude        attitude =
171                 getAttitudeProvider().getAttitude(orbit, orbit.getDate(), getFrame());
172         final SpacecraftState state    = new SpacecraftState(orbit, attitude).withMass(getMass());
173 
174         final NumericalPropagator propagator = new NumericalPropagator(
175                 getIntegratorBuilder().buildIntegrator(orbit, getOrbitType(), getPositionAngleType()),
176                 getAttitudeProvider());
177         propagator.setOrbitType(getOrbitType());
178         propagator.setPositionAngleType(getPositionAngleType());
179 
180         // Configure force models
181         if (!hasNewtonianAttraction()) {
182             // There are no central attraction model yet, add it at the end of the list
183             addForceModel(new NewtonianAttraction(orbit.getMu()));
184         }
185         for (ForceModel model : forceModels) {
186             propagator.addForceModel(model);
187         }
188         impulseManeuvers.forEach(propagator::addEventDetector);
189 
190         propagator.resetInitialState(state);
191 
192         // Add additional derivatives providers to the propagator
193         for (AdditionalDerivativesProvider provider: getAdditionalDerivativesProviders()) {
194             propagator.addAdditionalDerivativesProvider(provider);
195         }
196 
197         return propagator;
198 
199     }
200 
201     /** {@inheritDoc} */
202     @Override
203     public BatchLSModel buildLeastSquaresModel(final PropagatorBuilder[] builders,
204                                                final List<ObservedMeasurement<?>> measurements,
205                                                final ParameterDriversList estimatedMeasurementsParameters,
206                                                final ModelObserver observer) {
207         return new BatchLSModel(builders, measurements, estimatedMeasurementsParameters, observer);
208     }
209 
210     /** Check if Newtonian attraction force model is available.
211      * <p>
212      * Newtonian attraction is always the last force model in the list.
213      * </p>
214      * @return true if Newtonian attraction force model is available
215      */
216     private boolean hasNewtonianAttraction() {
217         final int last = forceModels.size() - 1;
218         return last >= 0 && forceModels.get(last) instanceof NewtonianAttraction;
219     }
220 
221 }