1   /* Copyright 2002-2024 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.forces.empirical;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  import java.util.stream.Stream;
23  
24  import org.hipparchus.CalculusFieldElement;
25  import org.hipparchus.Field;
26  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
27  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
28  import org.hipparchus.geometry.euclidean.threed.Rotation;
29  import org.hipparchus.geometry.euclidean.threed.Vector3D;
30  import org.hipparchus.util.MathArrays;
31  import org.orekit.attitudes.AttitudeProvider;
32  import org.orekit.forces.ForceModel;
33  import org.orekit.propagation.FieldSpacecraftState;
34  import org.orekit.propagation.SpacecraftState;
35  import org.orekit.propagation.events.EventDetector;
36  import org.orekit.propagation.events.FieldEventDetector;
37  import org.orekit.time.AbsoluteDate;
38  import org.orekit.time.FieldAbsoluteDate;
39  import org.orekit.utils.ParameterDriver;
40  import org.orekit.utils.TimeSpanMap;
41  import org.orekit.utils.TimeSpanMap.Span;
42  
43  /** Time span parametric acceleration model.
44   *  <p>
45   *  This class is closely related to {@link org.orekit.forces.empirical.ParametricAcceleration ParametricAcceleration} class.<br>
46   *  The difference is that it has a {@link TimeSpanMap} of {@link AccelerationModel} objects as attribute
47   *  instead of a single {@link AccelerationModel} object. <br>
48   *  The idea behind this model is to allow the user to design a parametric acceleration model that can see its physical parameters
49   *  change with time, at dates chosen by the user. <br>
50   *  </p>
51   *  <p>
52   *  This is a behavior that can be sought in precise orbit determination.<br>
53   *  Indeed for this type of application, the empirical parameters must be revalued at
54   *  each new orbit.
55   *  </p>
56   *  <b>Usage</b>:<ul>
57   *  <li><u>Construction</u>: constructor takes an acceleration direction, an attitude mode (or an inertial flag) and
58   *  an AccelerationModel model.<br>
59   *  This last model will be your initial AccelerationModel model and it will be initially valid for the whole time line.<br>
60   *  The real validity of this first entry will be truncated as other AccelerationModel models are added.
61   *  <li><u>Time spans</u>: AccelerationModel models are added using methods {@link #addAccelerationModelValidAfter(AccelerationModel, AbsoluteDate)}
62   *   or {@link #addAccelerationModelValidBefore(AccelerationModel, AbsoluteDate)}.<br>
63   *   Recommendations are the same than the ones in {@link TimeSpanMap}, meaning: <ul>
64   *   <li>As an entry is added, it truncates the validity of the neighboring entries already present in the map;
65   *   <li><b>The transition dates should be entered only once</b>. Repeating a transition date will lead to unexpected result and is not supported;
66   *   <li>It is advised to order your AccelerationModel models chronologically when adding them to avoid any confusion.
67   *   </ul>
68   *   <li><u>Naming the parameter drivers</u>: It is strongly advised to give a custom name to the {@link ParameterDriver}(s)
69   *   of each AccelerationModel model that is added to the object. This will allow you keeping track of the evolution of your models.<br>
70   *   Different names are mandatory to differentiate the different drivers.<br>
71   *   Since there is no default name for acceleration model parameters, you must handle the driver names to consider
72   *   different names when adding a new acceleration model.
73   *   </ul>
74   * @author Bryan Cazabonne
75   * @since 10.3
76   */
77  public class TimeSpanParametricAcceleration implements ForceModel {
78  
79      /** Prefix for dates before in the parameter drivers' name. */
80      public static final String DATE_BEFORE = " - Before ";
81  
82      /** Prefix for dates after in the parameter drivers' name. */
83      public static final String DATE_AFTER = " - After ";
84  
85      /** Direction of the acceleration in defining frame. */
86      private final Vector3D direction;
87  
88      /** Flag for inertial acceleration direction. */
89      private final boolean isInertial;
90  
91      /** The attitude to override, if set. */
92      private final AttitudeProvider attitudeOverride;
93  
94      /** TimeSpanMap of AccelerationModel objects. */
95      private final TimeSpanMap<AccelerationModel> accelerationModelTimeSpanMap;
96  
97      /** Simple constructor.
98       * @param direction acceleration direction in overridden spacecraft frame
99       * @param isInertial if true, direction is defined in the same inertial
100      * frame used for propagation (i.e. {@link SpacecraftState#getFrame()}),
101      * otherwise direction is defined in spacecraft frame (i.e. using the
102      * propagation {@link
103      * org.orekit.propagation.Propagator#setAttitudeProvider(AttitudeProvider)
104      * attitude law})
105      * @param accelerationModel acceleration model used to compute the contribution of the empirical acceleration
106      */
107     public TimeSpanParametricAcceleration(final Vector3D direction,
108                                           final boolean isInertial,
109                                           final AccelerationModel accelerationModel) {
110         this(direction, isInertial, null, accelerationModel);
111     }
112 
113     /** Simple constructor.
114      * @param direction acceleration direction in overridden spacecraft frame
115      * frame used for propagation (i.e. {@link SpacecraftState#getFrame()}),
116      * otherwise direction is defined in spacecraft frame (i.e. using the
117      * propagation {@link
118      * org.orekit.propagation.Propagator#setAttitudeProvider(AttitudeProvider)
119      * attitude law})
120      * @param attitudeOverride provider for attitude used to compute acceleration
121      * @param accelerationModel acceleration model used to compute the contribution of the empirical acceleration
122      */
123     public TimeSpanParametricAcceleration(final Vector3D direction,
124                                           final AttitudeProvider attitudeOverride,
125                                           final AccelerationModel accelerationModel) {
126         this(direction, false, attitudeOverride, accelerationModel);
127     }
128 
129     /** Simple constructor.
130      * @param direction acceleration direction in overridden spacecraft frame
131      * @param isInertial if true, direction is defined in the same inertial
132      * frame used for propagation (i.e. {@link SpacecraftState#getFrame()}),
133      * otherwise direction is defined in spacecraft frame (i.e. using the
134      * propagation {@link
135      * org.orekit.propagation.Propagator#setAttitudeProvider(AttitudeProvider)
136      * attitude law})
137      * @param attitudeOverride provider for attitude used to compute acceleration
138      * @param accelerationModel acceleration model used to compute the contribution of the empirical acceleration
139      */
140     private TimeSpanParametricAcceleration(final Vector3D direction,
141                                            final boolean isInertial,
142                                            final AttitudeProvider attitudeOverride,
143                                            final AccelerationModel accelerationModel) {
144         this.direction                    = direction;
145         this.isInertial                   = isInertial;
146         this.attitudeOverride             = attitudeOverride;
147         this.accelerationModelTimeSpanMap = new TimeSpanMap<>(accelerationModel);
148     }
149 
150     /** {@inheritDoc} */
151     @Override
152     public void init(final SpacecraftState initialState, final AbsoluteDate target) {
153         accelerationModelTimeSpanMap.forEach(accelerationModel -> accelerationModel.init(initialState, target));
154     }
155 
156     /** Add an AccelerationModel entry valid before a limit date.<br>
157      * <p>
158      * Using <code>addAccelerationModelValidBefore(entry, t)</code> will make <code>entry</code>
159      * valid in ]-∞, t[ (note the open bracket).
160      * <p>
161      * <b>WARNING</b>: Since there is no default name for acceleration model parameters,
162      * the user must handle itself the driver names to consider different names
163      * (i.e. different parameters) when adding a new acceleration model.
164      * @param accelerationModel AccelerationModel entry
165      * @param latestValidityDate date before which the entry is valid
166      * (must be different from <b>all</b> dates already used for transitions)
167      */
168     public void addAccelerationModelValidBefore(final AccelerationModel accelerationModel, final AbsoluteDate latestValidityDate) {
169         accelerationModelTimeSpanMap.addValidBefore(accelerationModel, latestValidityDate, false);
170     }
171 
172     /** Add a AccelerationModel entry valid after a limit date.<br>
173      * <p>
174      * Using <code>addAccelerationModelValidAfter(entry, t)</code> will make <code>entry</code>
175      * valid in [t, +∞[ (note the closed bracket).
176      * <p>
177      * <b>WARNING</b>: Since there is no default name for acceleration model parameters,
178      * the user must handle itself the driver names to consider different names
179      * (i.e. different parameters) when adding a new acceleration model.
180      * @param accelerationModel AccelerationModel entry
181      * @param earliestValidityDate date after which the entry is valid
182      * (must be different from <b>all</b> dates already used for transitions)
183      */
184     public void addAccelerationModelValidAfter(final AccelerationModel accelerationModel, final AbsoluteDate earliestValidityDate) {
185         accelerationModelTimeSpanMap.addValidAfter(accelerationModel, earliestValidityDate, false);
186     }
187 
188     /** Get the {@link AccelerationModel} model valid at a date.
189      * @param date the date of validity
190      * @return the AccelerationModel model valid at date
191      */
192     public AccelerationModel getAccelerationModel(final AbsoluteDate date) {
193         return accelerationModelTimeSpanMap.get(date);
194     }
195 
196     /** Get the {@link AccelerationModel} {@link Span} containing a specified date.
197      * @param date date belonging to the desired time span
198      * @return the AccelerationModel time span containing the specified date
199      */
200     public Span<AccelerationModel> getAccelerationModelSpan(final AbsoluteDate date) {
201         return accelerationModelTimeSpanMap.getSpan(date);
202     }
203 
204     /** Extract a range of the {@link AccelerationModel} map.
205      * <p>
206      * The object returned will be a new independent instance that will contain
207      * only the transitions that lie in the specified range.
208      * </p>
209      * See the {@link TimeSpanMap#extractRange TimeSpanMap.extractRange method} for more.
210      * @param start earliest date at which a transition is included in the range
211      * (may be set to {@link AbsoluteDate#PAST_INFINITY} to keep all early transitions)
212      * @param end latest date at which a transition is included in the r
213      * (may be set to {@link AbsoluteDate#FUTURE_INFINITY} to keep all late transitions)
214      * @return a new TimeSpanMap instance of AccelerationModel with all transitions restricted to the specified range
215      */
216     public TimeSpanMap<AccelerationModel> extractAccelerationModelRange(final AbsoluteDate start, final AbsoluteDate end) {
217         return accelerationModelTimeSpanMap.extractRange(start, end);
218     }
219 
220     /** Get the first {@link Span time span} of the acceleration model time span map.
221      * @return the first {@link Span time span} of the acceleration model time span map
222      * @since 11.1
223      */
224     public Span<AccelerationModel> getFirstSpan() {
225         return accelerationModelTimeSpanMap.getFirstSpan();
226     }
227 
228     /** {@inheritDoc} */
229     @Override
230     public boolean dependsOnPositionOnly() {
231         return isInertial;
232     }
233 
234     /** {@inheritDoc} */
235     @Override
236     public Vector3D acceleration(final SpacecraftState state,
237                                  final double[] parameters) {
238 
239         // Date
240         final AbsoluteDate date = state.getDate();
241 
242         // Compute inertial direction
243         final Vector3D inertialDirection;
244         if (isInertial) {
245             // the acceleration direction is already defined in the inertial frame
246             inertialDirection = direction;
247         } else {
248             final Rotation rotation;
249             if (attitudeOverride == null) {
250                 // the acceleration direction is defined in spacecraft frame as set by the propagator
251                 rotation = state.getAttitude().getRotation();
252             } else {
253                 // the acceleration direction is defined in a dedicated frame
254                 rotation = attitudeOverride.getAttitudeRotation(state.getOrbit(), date, state.getFrame());
255             }
256             inertialDirection = rotation.applyInverseTo(direction);
257         }
258 
259         // Extract the proper parameters valid at date from the input array
260         final double[] extractedParameters = extractParameters(parameters, date);
261 
262         // Compute and return the parametric acceleration
263         return new Vector3D(getAccelerationModel(date).signedAmplitude(state, extractedParameters), inertialDirection);
264 
265     }
266 
267     /** {@inheritDoc} */
268     @Override
269     public <T extends CalculusFieldElement<T>> FieldVector3D<T> acceleration(final FieldSpacecraftState<T> state,
270                                                                          final T[] parameters) {
271 
272         // Date
273         final FieldAbsoluteDate<T> date = state.getDate();
274 
275         // Compute inertial direction
276         final FieldVector3D<T> inertialDirection;
277         if (isInertial) {
278             // the acceleration direction is already defined in the inertial frame
279             inertialDirection = new FieldVector3D<>(state.getDate().getField(), direction);
280         } else {
281             final FieldRotation<T> rotation;
282             if (attitudeOverride == null) {
283                 // the acceleration direction is defined in spacecraft frame as set by the propagator
284                 rotation = state.getAttitude().getRotation();
285             } else {
286                 // the acceleration direction is defined in a dedicated frame
287                 rotation = attitudeOverride.getAttitudeRotation(state.getOrbit(), date, state.getFrame());
288             }
289             inertialDirection = rotation.applyInverseTo(direction);
290         }
291 
292         // Extract the proper parameters valid at date from the input array
293         final T[] extractedParameters = extractParameters(parameters, date);
294 
295         // Compute and return the parametric acceleration
296         return new FieldVector3D<>(getAccelerationModel(date.toAbsoluteDate()).signedAmplitude(state, extractedParameters), inertialDirection);
297 
298     }
299 
300     /** {@inheritDoc} */
301     @Override
302     public Stream<EventDetector> getEventDetectors() {
303         return Stream.empty();
304     }
305 
306     /** {@inheritDoc} */
307     @Override
308     public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(final Field<T> field) {
309         return Stream.empty();
310     }
311 
312     /** {@inheritDoc}
313      * <p>
314      * All the parameter drivers of all AccelerationModel models are returned in an array.
315      * Models are ordered chronologically.
316      * </p>
317      */
318     @Override
319     public List<ParameterDriver> getParametersDrivers() {
320 
321         // Get all transitions from the TimeSpanMap
322         final List<ParameterDriver> listParameterDrivers = new ArrayList<>();
323 
324         // Loop on the spans
325         for (Span<AccelerationModel> span = getFirstSpan(); span != null; span = span.next()) {
326             // Add all the parameter drivers of the time span
327             for (ParameterDriver driver : span.getData().getParametersDrivers()) {
328                 // Add the driver only if the name does not exist already
329                 if (!findByName(listParameterDrivers, driver.getName())) {
330                     listParameterDrivers.add(driver);
331                 }
332             }
333         }
334 
335         // Return an array of parameter drivers with no duplicated name
336         return Collections.unmodifiableList(listParameterDrivers);
337 
338     }
339 
340     /** Extract the proper parameter drivers' values from the array in input of the
341      * {@link #acceleration(SpacecraftState, double[]) acceleration} method.
342      *  Parameters are filtered given an input date.
343      * @param parameters the input parameters array
344      * @param date the date
345      * @return the parameters given the date
346      */
347     public double[] extractParameters(final double[] parameters, final AbsoluteDate date) {
348 
349         // Get the acceleration model parameter drivers of the date
350         final List<ParameterDriver> empiricalParameterDriver = getAccelerationModel(date).getParametersDrivers();
351 
352         // Find out the indexes of the parameters in the whole array of parameters
353         final List<ParameterDriver> allParameters = getParametersDrivers();
354         final double[] outParameters = new double[empiricalParameterDriver.size()];
355         int index = 0;
356         for (int i = 0; i < allParameters.size(); i++) {
357             final String driverName = allParameters.get(i).getName();
358             for (ParameterDriver accDriver : empiricalParameterDriver) {
359                 if (accDriver.getName().equals(driverName)) {
360                     outParameters[index++] = parameters[i];
361                 }
362             }
363         }
364         return outParameters;
365     }
366 
367     /** Extract the proper parameter drivers' values from the array in input of the
368      * {@link #acceleration(FieldSpacecraftState, CalculusFieldElement[]) acceleration} method.
369      *  Parameters are filtered given an input date.
370      * @param parameters the input parameters array
371      * @param date the date
372      * @param <T> extends CalculusFieldElement
373      * @return the parameters given the date
374      */
375     public <T extends CalculusFieldElement<T>> T[] extractParameters(final T[] parameters,
376                                                                  final FieldAbsoluteDate<T> date) {
377 
378         // Get the acceleration parameter drivers of the date
379         final List<ParameterDriver> empiricalParameterDriver = getAccelerationModel(date.toAbsoluteDate()).getParametersDrivers();
380 
381         // Find out the indexes of the parameters in the whole array of parameters
382         final List<ParameterDriver> allParameters = getParametersDrivers();
383         final T[] outParameters = MathArrays.buildArray(date.getField(), empiricalParameterDriver.size());
384         int index = 0;
385         for (int i = 0; i < allParameters.size(); i++) {
386             final String driverName = allParameters.get(i).getName();
387             for (ParameterDriver accDriver : empiricalParameterDriver) {
388                 if (accDriver.getName().equals(driverName)) {
389                     outParameters[index++] = parameters[i];
390                 }
391             }
392         }
393         return outParameters;
394     }
395 
396     /** Find if a parameter driver with a given name already exists in a list of parameter drivers.
397      * @param driversList the list of parameter drivers
398      * @param name the parameter driver's name to filter with
399      * @return true if the name was found, false otherwise
400      */
401     private boolean findByName(final List<ParameterDriver> driversList, final String name) {
402         for (final ParameterDriver d : driversList) {
403             if (d.getName().equals(name)) {
404                 return true;
405             }
406         }
407         return false;
408     }
409 
410 }