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 java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import org.hipparchus.ode.ODEIntegrator;
24  import org.orekit.attitudes.Attitude;
25  import org.orekit.attitudes.AttitudeProvider;
26  import org.orekit.attitudes.FrameAlignedProvider;
27  import org.orekit.estimation.leastsquares.BatchLSModel;
28  import org.orekit.estimation.leastsquares.ModelObserver;
29  import org.orekit.estimation.measurements.ObservedMeasurement;
30  import org.orekit.forces.ForceModel;
31  import org.orekit.forces.gravity.NewtonianAttraction;
32  import org.orekit.forces.maneuvers.ImpulseManeuver;
33  import org.orekit.orbits.AbstractOrbitFactory;
34  import org.orekit.orbits.Orbit;
35  import org.orekit.propagation.PropagationType;
36  import org.orekit.propagation.Propagator;
37  import org.orekit.propagation.SpacecraftState;
38  import org.orekit.propagation.integration.AdditionalDerivativesProvider;
39  import org.orekit.propagation.numerical.NumericalPropagator;
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
47      extends AbstractIntegratedPropagatorBuilder<NumericalPropagator, Orbit, AbstractOrbitFactory<Orbit>> {
48  
49      /** Force models used during the extrapolation of the orbit. */
50      private final List<ForceModel> forceModels;
51  
52      /** Impulse maneuvers. */
53      private final List<ImpulseManeuver> impulseManeuvers;
54  
55      /** Build a new instance.
56       * @param factory factory for initial orbit
57       * @param builder first order integrator builder
58       * @since 14.0
59       * @see #NumericalPropagatorBuilder(AbstractOrbitFactory, ODEIntegratorBuilder, AttitudeProvider)
60       */
61      public NumericalPropagatorBuilder(final AbstractOrbitFactory<? extends Orbit> factory,
62                                        final ODEIntegratorBuilder builder) {
63          this(factory, builder, FrameAlignedProvider.of(factory.getFrame()));
64      }
65  
66      /** Build a new instance.
67       * @param factory factory for initial orbit
68       * @param builder first order integrator builder
69       * @param attitudeProvider attitude law.
70       * @since 14.0
71       */
72      public NumericalPropagatorBuilder(final AbstractOrbitFactory<? extends Orbit> factory,
73                                        final ODEIntegratorBuilder builder,
74                                        final AttitudeProvider attitudeProvider) {
75          super((AbstractOrbitFactory<Orbit>) factory, builder,
76                PropagationType.OSCULATING, attitudeProvider, Propagator.DEFAULT_MASS);
77          this.forceModels = new ArrayList<>();
78          this.impulseManeuvers = new ArrayList<>();
79      }
80  
81      /** {@inheritDoc}. */
82      @Override
83      public NumericalPropagatorBuilder clone() {
84          // Call to super clone() method to avoid warning
85          final NumericalPropagatorBuilder clonedBuilder = (NumericalPropagatorBuilder) super.clone();
86  
87          // Use cloned builder to unlink orbital drivers
88          final NumericalPropagatorBuilder builder =
89              new NumericalPropagatorBuilder((AbstractOrbitFactory<Orbit>) clonedBuilder.getOrbitalParameterFactory().clone(),
90                                             clonedBuilder.getIntegratorBuilder(), clonedBuilder.getAttitudeProvider());
91  
92          // Set mass and force models
93          builder.setMass(getMass());
94          for (ForceModel model : forceModels) {
95              builder.addForceModel(model);
96          }
97  
98          // Add impulse maneuvers
99          impulseManeuvers.forEach(builder::addImpulseManeuver);
100 
101         return builder;
102     }
103 
104 
105     /**
106      * Add impulse maneuver.
107      * @param impulseManeuver impulse maneuver
108      * @since 12.2
109      */
110     public void addImpulseManeuver(final ImpulseManeuver impulseManeuver) {
111         impulseManeuvers.add(impulseManeuver);
112     }
113 
114     /**
115      * Remove all impulse maneuvers.
116      * @since 12.2
117      */
118     public void clearImpulseManeuvers() {
119         impulseManeuvers.clear();
120     }
121 
122     /** Get the list of all force models.
123      * @return the list of all force models
124      * @since 9.2
125      */
126     public List<ForceModel> getAllForceModels()
127     {
128         return Collections.unmodifiableList(forceModels);
129     }
130 
131     /** Add a force model to the global perturbation model.
132      * <p>If this method is not called at all, the integrated orbit will follow
133      * a Keplerian evolution only.</p>
134      * @param model perturbing {@link ForceModel} to add
135      */
136     public void addForceModel(final ForceModel model) {
137         if (model instanceof NewtonianAttraction) {
138             // we want to add the central attraction force model
139             if (hasNewtonianAttraction()) {
140                 // there is already a central attraction model, replace it
141                 forceModels.set(forceModels.size() - 1, model);
142             } else {
143                 // there are no central attraction model yet, add it at the end of the list
144                 forceModels.add(model);
145             }
146         } else {
147             // we want to add a perturbing force model
148             if (hasNewtonianAttraction()) {
149                 // insert the new force model before Newtonian attraction,
150                 // which should always be the last one in the list
151                 forceModels.add(forceModels.size() - 1, model);
152             } else {
153                 // we only have perturbing force models up to now, just append at the end of the list
154                 forceModels.add(model);
155             }
156         }
157 
158         addPropagationParameters(model.getParametersDrivers());
159     }
160 
161     /** {@inheritDoc} */
162     public NumericalPropagator buildPropagator(final double[] normalizedParameters) {
163 
164         final AbstractOrbitFactory<Orbit> factory = getOrbitalParameterFactory();
165         setParameters(normalizedParameters);
166         final Orbit           orbit    = factory.createFromDrivers();
167         final Attitude        attitude = getAttitudeProvider().
168                                          getAttitude(orbit, orbit.getDate(), factory.getFrame());
169         final SpacecraftState state    = new SpacecraftState(orbit, attitude).withMass(getMass());
170 
171         final ODEIntegrator integrator = getIntegratorBuilder().
172                                          buildIntegrator(orbit,
173                                                          factory.getOrbitType(),
174                                                          factory.getPositionAngleType());
175         final NumericalPropagator propagator = new NumericalPropagator(integrator, getAttitudeProvider());
176         propagator.setOrbitType(factory.getOrbitType());
177         propagator.setPositionAngleType(factory.getPositionAngleType());
178 
179         // Configure force models
180         if (!hasNewtonianAttraction()) {
181             // There are no central attraction model yet, add it at the end of the list
182             addForceModel(new NewtonianAttraction(orbit.getMu()));
183         }
184         for (ForceModel model : forceModels) {
185             propagator.addForceModel(model);
186         }
187         impulseManeuvers.forEach(propagator::addEventDetector);
188 
189         propagator.resetInitialState(state);
190 
191         // Add additional derivatives providers to the propagator
192         for (AdditionalDerivativesProvider provider: getAdditionalDerivativesProviders()) {
193             propagator.addAdditionalDerivativesProvider(provider);
194         }
195 
196         return propagator;
197 
198     }
199 
200     /** {@inheritDoc} */
201     @Override
202     public BatchLSModel buildLeastSquaresModel(final PropagatorBuilder[] builders,
203                                                final List<ObservedMeasurement<?>> measurements,
204                                                final ParameterDriversList estimatedMeasurementsParameters,
205                                                final ModelObserver observer) {
206         return new BatchLSModel(builders, measurements, estimatedMeasurementsParameters, observer);
207     }
208 
209     /** Check if Newtonian attraction force model is available.
210      * <p>
211      * Newtonian attraction is always the last force model in the list.
212      * </p>
213      * @return true if Newtonian attraction force model is available
214      */
215     private boolean hasNewtonianAttraction() {
216         final int last = forceModels.size() - 1;
217         return last >= 0 && forceModels.get(last) instanceof NewtonianAttraction;
218     }
219 
220 }