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     /** Update state by adding unmanaged states.
152      * @param original original state
153      * @return updated state, with unmanaged states included
154      * @see #updateAdditionalData(FieldSpacecraftState)
155      */
156     protected FieldSpacecraftState<T> updateUnmanagedData(final FieldSpacecraftState<T> original) {
157 
158         // start with original state,
159         // which may already contain additional states, for example in interpolated ephemerides
160         FieldSpacecraftState<T> updated = original;
161 
162         // update the states not managed by providers
163         for (final Map.Entry<String, FieldTimeSpanMap<Object, T>> entry : unmanagedStates.entrySet()) {
164             updated = updated.addAdditionalData(entry.getKey(),
165                                                  entry.getValue().get(original.getDate()));
166         }
167 
168         return updated;
169 
170     }
171 
172     /** Update state by adding all additional data.
173      * @param original original state
174      * @return updated state, with all additional data included
175      * @see #addAdditionalDataProvider(FieldAdditionalDataProvider)
176      */
177     public FieldSpacecraftState<T> updateAdditionalData(final FieldSpacecraftState<T> original) {
178 
179         // start with original state and unmanaged states
180         FieldSpacecraftState<T> updated = updateUnmanagedData(original);
181 
182         // set up queue for providers
183         final Queue<FieldAdditionalDataProvider<?, T>> pending = new LinkedList<>(getAdditionalDataProviders());
184 
185         // update the additional data managed by providers, taking care of dependencies
186         int yieldCount = 0;
187         while (!pending.isEmpty()) {
188             final FieldAdditionalDataProvider<?, T> provider = pending.remove();
189             if (provider.yields(updated)) {
190                 // this generator has to wait for another one,
191                 // we put it again in the pending queue
192                 pending.add(provider);
193                 if (++yieldCount >= pending.size()) {
194                     // all pending providers yielded!, they probably need data not yet initialized
195                     // we let the propagation proceed, if these data are really needed right now
196                     // an appropriate exception will be triggered when caller tries to access them
197                     break;
198                 }
199             } else {
200                 // we can use this provider right now
201                 updated    = provider.update(updated);
202                 yieldCount = 0;
203             }
204         }
205 
206         return updated;
207 
208     }
209 
210     /**
211      * Initialize the additional data providers at the start of propagation.
212      * @param target date of propagation. Not equal to {@code initialState.getDate()}.
213      * @since 11.2
214      */
215     protected void initializeAdditionalData(final FieldAbsoluteDate<T> target) {
216         for (final FieldAdditionalDataProvider<?, T> provider : additionalDataProviders) {
217             provider.init(initialState, target);
218         }
219     }
220 
221     /** {@inheritDoc} */
222     public boolean isAdditionalDataManaged(final String name) {
223         for (final FieldAdditionalDataProvider<?, T> provider : additionalDataProviders) {
224             if (provider.getName().equals(name)) {
225                 return true;
226             }
227         }
228         return false;
229     }
230 
231     /** {@inheritDoc} */
232     public String[] getManagedAdditionalData() {
233         final String[] managed = new String[additionalDataProviders.size()];
234         for (int i = 0; i < managed.length; ++i) {
235             managed[i] = additionalDataProviders.get(i).getName();
236         }
237         return managed;
238     }
239 
240     /** {@inheritDoc} */
241     public FieldSpacecraftState<T> propagate(final FieldAbsoluteDate<T> target) {
242         if (startDate == null) {
243             startDate = getInitialState().getDate();
244         }
245         return propagate(startDate, target);
246     }
247 
248     /** Initialize propagation.
249      * @since 10.1
250      */
251     protected void initializePropagation() {
252 
253         unmanagedStates.clear();
254 
255         if (initialState != null) {
256             // there is an initial state
257             // (null initial states occur for example in interpolated ephemerides)
258             // copy the additional data present in initialState but otherwise not managed
259             for (final FieldDataDictionary<T>.Entry initial : initialState.getAdditionalDataValues().getData()) {
260                 if (!isAdditionalDataManaged(initial.getKey())) {
261                     // this additional state is in the initial state, but is unknown to the propagator
262                     // we store it in a way event handlers may change it
263                     unmanagedStates.put(initial.getKey(),
264                                         new FieldTimeSpanMap<>(initial.getValue(),
265                                                                initialState.getDate().getField()));
266                 }
267             }
268         }
269     }
270 
271     /** Notify about a state change.
272      * @param state new state
273      */
274     protected void stateChanged(final FieldSpacecraftState<T> state) {
275         final FieldAbsoluteDate<T> date    = state.getDate();
276         final boolean              forward = date.durationFrom(getStartDate()).getReal() >= 0.0;
277         for (final  FieldDataDictionary<T>.Entry changed : state.getAdditionalDataValues().getData()) {
278             final FieldTimeSpanMap<Object, T> tsm = unmanagedStates.get(changed.getKey());
279             if (tsm != null) {
280                 // this is an unmanaged state
281                 if (forward) {
282                     tsm.addValidAfter(changed.getValue(), date);
283                 } else {
284                     tsm.addValidBefore(changed.getValue(), date);
285                 }
286             }
287         }
288     }
289 
290 }