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;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.LinkedList;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Queue;
26  
27  import org.hipparchus.CalculusFieldElement;
28  import org.hipparchus.Field;
29  import org.orekit.attitudes.AttitudeProvider;
30  import org.orekit.errors.OrekitException;
31  import org.orekit.errors.OrekitMessages;
32  import org.orekit.frames.Frame;
33  import org.orekit.propagation.sampling.FieldStepHandlerMultiplexer;
34  import org.orekit.time.FieldAbsoluteDate;
35  import org.orekit.utils.FieldDataDictionary;
36  import org.orekit.utils.FieldTimeSpanMap;
37  
38  /** Common handling of {@link Propagator} methods for analytical propagators.
39   * <p>
40   * This abstract class allows to provide easily the full set of {@link Propagator}
41   * methods, including all propagation modes support and discrete events support for
42   * any simple propagation method.
43   * </p>
44   * @param <T> the type of the field elements
45   * @author Luc Maisonobe
46   */
47  public abstract class FieldAbstractPropagator<T extends CalculusFieldElement<T>> implements FieldPropagator<T> {
48  
49      /** Multiplexer for step handlers. */
50      private final FieldStepHandlerMultiplexer<T> multiplexer;
51  
52      /** Start date. */
53      private FieldAbsoluteDate<T> startDate;
54  
55      /** Attitude provider. */
56      private AttitudeProvider attitudeProvider;
57  
58      /** Additional data providers. */
59      private final List<FieldAdditionalDataProvider<?, T>> additionalDataProviders;
60  
61      /** States managed by neither additional equations nor state providers. */
62      private final Map<String, FieldTimeSpanMap<Object, T>> unmanagedStates;
63  
64      /** Field used.*/
65      private final Field<T> field;
66  
67      /** Initial state. */
68      private FieldSpacecraftState<T> initialState;
69  
70      /** Build a new instance.
71       * @param field setting the field
72       */
73      protected FieldAbstractPropagator(final Field<T> field) {
74          this.field               = field;
75          multiplexer              = new FieldStepHandlerMultiplexer<>();
76          additionalDataProviders  = new ArrayList<>();
77          unmanagedStates          = new HashMap<>();
78      }
79  
80      /** Set a start date.
81       * @param startDate start date
82       */
83      protected void setStartDate(final FieldAbsoluteDate<T> startDate) {
84          this.startDate = startDate;
85      }
86  
87      /** Get the start date.
88       * @return start date
89       */
90      protected FieldAbsoluteDate<T> getStartDate() {
91          return startDate;
92      }
93  
94      /**  {@inheritDoc} */
95      public AttitudeProvider getAttitudeProvider() {
96          return attitudeProvider;
97      }
98  
99      /**  {@inheritDoc} */
100     public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
101         this.attitudeProvider = attitudeProvider;
102     }
103 
104     /** Field getter.
105      * @return field used*/
106     public Field<T> getField() {
107         return field;
108     }
109 
110     /** {@inheritDoc} */
111     public FieldSpacecraftState<T> getInitialState() {
112         return initialState;
113     }
114 
115     /** {@inheritDoc} */
116     public Frame getFrame() {
117         return initialState.getFrame();
118     }
119 
120     /** {@inheritDoc} */
121     public void resetInitialState(final FieldSpacecraftState<T> state) {
122         initialState = state;
123         setStartDate(state.getDate());
124     }
125 
126     /** {@inheritDoc} */
127     public FieldStepHandlerMultiplexer<T> getMultiplexer() {
128         return multiplexer;
129     }
130 
131     /** {@inheritDoc} */
132     public void addAdditionalDataProvider(final FieldAdditionalDataProvider<?, T> additionalDataProvider) {
133 
134         // check if the name is already used
135         if (isAdditionalDataManaged(additionalDataProvider.getName())) {
136             // this additional data is already registered, complain
137             throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_NAME_ALREADY_IN_USE,
138                                       additionalDataProvider.getName());
139         }
140 
141         // this is really a new name, add it
142         additionalDataProviders.add(additionalDataProvider);
143 
144     }
145 
146     /** {@inheritDoc} */
147     public List<FieldAdditionalDataProvider<?, T>> getAdditionalDataProviders() {
148         return Collections.unmodifiableList(additionalDataProviders);
149     }
150 
151     /**
152      * Remove an additional data provider.
153      * @param name data name
154      * @since 13.1
155      */
156     public void removeAdditionalDataProvider(final String name) {
157         additionalDataProviders.removeIf(provider -> provider.getName().equals(name));
158     }
159 
160     /** Update state by adding unmanaged states.
161      * @param original original state
162      * @return updated state, with unmanaged states included
163      * @see #updateAdditionalData(FieldSpacecraftState)
164      */
165     protected FieldSpacecraftState<T> updateUnmanagedData(final FieldSpacecraftState<T> original) {
166 
167         // start with original state,
168         // which may already contain additional states, for example in interpolated ephemerides
169         FieldSpacecraftState<T> updated = original;
170 
171         // update the states not managed by providers
172         for (final Map.Entry<String, FieldTimeSpanMap<Object, T>> entry : unmanagedStates.entrySet()) {
173             updated = updated.addAdditionalData(entry.getKey(),
174                                                  entry.getValue().get(original.getDate()));
175         }
176 
177         return updated;
178 
179     }
180 
181     /** Update state by adding all additional data.
182      * @param original original state
183      * @return updated state, with all additional data included
184      * @see #addAdditionalDataProvider(FieldAdditionalDataProvider)
185      */
186     public FieldSpacecraftState<T> updateAdditionalData(final FieldSpacecraftState<T> original) {
187 
188         // start with original state and unmanaged states
189         FieldSpacecraftState<T> updated = updateUnmanagedData(original);
190 
191         // set up queue for providers
192         final Queue<FieldAdditionalDataProvider<?, T>> pending = new LinkedList<>(getAdditionalDataProviders());
193 
194         // update the additional data managed by providers, taking care of dependencies
195         int yieldCount = 0;
196         while (!pending.isEmpty()) {
197             final FieldAdditionalDataProvider<?, T> provider = pending.remove();
198             if (provider.yields(updated)) {
199                 // this generator has to wait for another one,
200                 // we put it again in the pending queue
201                 pending.add(provider);
202                 if (++yieldCount >= pending.size()) {
203                     // all pending providers yielded!, they probably need data not yet initialized
204                     // we let the propagation proceed, if these data are really needed right now
205                     // an appropriate exception will be triggered when caller tries to access them
206                     break;
207                 }
208             } else {
209                 // we can use this provider right now
210                 updated    = provider.update(updated);
211                 yieldCount = 0;
212             }
213         }
214 
215         return updated;
216 
217     }
218 
219     /**
220      * Initialize the additional data providers at the start of propagation.
221      * @param target date of propagation. Not equal to {@code initialState.getDate()}.
222      * @since 11.2
223      */
224     protected void initializeAdditionalData(final FieldAbsoluteDate<T> target) {
225         for (final FieldAdditionalDataProvider<?, T> provider : additionalDataProviders) {
226             provider.init(initialState, target);
227         }
228     }
229 
230     /** {@inheritDoc} */
231     public boolean isAdditionalDataManaged(final String name) {
232         for (final FieldAdditionalDataProvider<?, T> provider : additionalDataProviders) {
233             if (provider.getName().equals(name)) {
234                 return true;
235             }
236         }
237         return false;
238     }
239 
240     /** {@inheritDoc} */
241     public String[] getManagedAdditionalData() {
242         final String[] managed = new String[additionalDataProviders.size()];
243         for (int i = 0; i < managed.length; ++i) {
244             managed[i] = additionalDataProviders.get(i).getName();
245         }
246         return managed;
247     }
248 
249     /** {@inheritDoc} */
250     public FieldSpacecraftState<T> propagate(final FieldAbsoluteDate<T> target) {
251         if (startDate == null) {
252             startDate = getInitialState().getDate();
253         }
254         return propagate(startDate, target);
255     }
256 
257     /** Initialize propagation.
258      * @since 10.1
259      */
260     protected void initializePropagation() {
261 
262         unmanagedStates.clear();
263 
264         if (initialState != null) {
265             // there is an initial state
266             // (null initial states occur for example in interpolated ephemerides)
267             // copy the additional data present in initialState but otherwise not managed
268             for (final FieldDataDictionary<T>.Entry initial : initialState.getAdditionalDataValues().getData()) {
269                 if (!isAdditionalDataManaged(initial.getKey())) {
270                     // this additional state is in the initial state, but is unknown to the propagator
271                     // we store it in a way event handlers may change it
272                     unmanagedStates.put(initial.getKey(),
273                                         new FieldTimeSpanMap<>(initial.getValue(),
274                                                                initialState.getDate().getField()));
275                 }
276             }
277         }
278     }
279 
280     /** Notify about a state change.
281      * @param state new state
282      */
283     protected void stateChanged(final FieldSpacecraftState<T> state) {
284         final FieldAbsoluteDate<T> date    = state.getDate();
285         final boolean              forward = date.durationFrom(getStartDate()).getReal() >= 0.0;
286         for (final  FieldDataDictionary<T>.Entry changed : state.getAdditionalDataValues().getData()) {
287             final FieldTimeSpanMap<Object, T> tsm = unmanagedStates.get(changed.getKey());
288             if (tsm != null) {
289                 // this is an unmanaged state
290                 if (forward) {
291                     tsm.addValidAfter(changed.getValue(), date, false);
292                 } else {
293                     tsm.addValidBefore(changed.getValue(), date, false);
294                 }
295             }
296         }
297     }
298 
299 }