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;
18  
19  import org.hipparchus.linear.RealMatrix;
20  import org.orekit.attitudes.AttitudeProvider;
21  import org.orekit.errors.OrekitException;
22  import org.orekit.errors.OrekitMessages;
23  import org.orekit.frames.Frame;
24  import org.orekit.propagation.sampling.StepHandlerMultiplexer;
25  import org.orekit.time.AbsoluteDate;
26  import org.orekit.utils.DataDictionary;
27  import org.orekit.utils.DoubleArrayDictionary;
28  import org.orekit.utils.TimeSpanMap;
29  
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.HashMap;
33  import java.util.LinkedList;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Queue;
37  
38  /** Common handling of {@link Propagator} methods for 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   * @author Luc Maisonobe
45   */
46  public abstract class AbstractPropagator implements Propagator {
47  
48      /** Multiplexer for step handlers. */
49      private final StepHandlerMultiplexer multiplexer;
50  
51      /** Start date. */
52      private AbsoluteDate startDate;
53  
54      /** Attitude provider. */
55      private AttitudeProvider attitudeProvider;
56  
57      /** Providers for additional data. */
58      private final List<AdditionalDataProvider<?>> additionalDataProviders;
59  
60      /** States managed by no generators. */
61      private final Map<String, TimeSpanMap<Object>> unmanagedStates;
62  
63      /** Initial state. */
64      private SpacecraftState initialState;
65  
66      /** Harvester for State Transition Matrix and Jacobian matrix. */
67      private AbstractMatricesHarvester harvester;
68  
69      /** Build a new instance.
70       */
71      protected AbstractPropagator() {
72          multiplexer              = new StepHandlerMultiplexer();
73          additionalDataProviders  = new ArrayList<>();
74          unmanagedStates          = new HashMap<>();
75          harvester                = null;
76      }
77  
78      /** Set a start date.
79       * @param startDate start date
80       */
81      protected void setStartDate(final AbsoluteDate startDate) {
82          this.startDate = startDate;
83      }
84  
85      /** Get the start date.
86       * @return start date
87       */
88      protected AbsoluteDate getStartDate() {
89          return startDate;
90      }
91  
92      /**  {@inheritDoc} */
93      public AttitudeProvider getAttitudeProvider() {
94          return attitudeProvider;
95      }
96  
97      /**  {@inheritDoc} */
98      public void setAttitudeProvider(final AttitudeProvider attitudeProvider) {
99          this.attitudeProvider = attitudeProvider;
100     }
101 
102     /** {@inheritDoc} */
103     @Override
104     public SpacecraftState getBaseInitialState() {
105         return initialState;
106     }
107 
108     /** {@inheritDoc} */
109     @Override
110     public SpacecraftState getInitialState() {
111         return updateAdditionalData(getBaseInitialState());
112     }
113 
114     /** {@inheritDoc} */
115     public Frame getFrame() {
116         return initialState.getFrame();
117     }
118 
119     /** {@inheritDoc} */
120     public void resetInitialState(final SpacecraftState state) {
121         initialState = state;
122         setStartDate(state.getDate());
123     }
124 
125     /** {@inheritDoc} */
126     public StepHandlerMultiplexer getMultiplexer() {
127         return multiplexer;
128     }
129 
130     /** {@inheritDoc} */
131     @Override
132     public void addAdditionalDataProvider(final AdditionalDataProvider<?> provider) {
133 
134         // check if the name is already used
135         if (isAdditionalDataManaged(provider.getName())) {
136             // this additional state is already registered, complain
137             throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_NAME_ALREADY_IN_USE,
138                                       provider.getName());
139         }
140 
141         // this is really a new name, add it
142         additionalDataProviders.add(provider);
143 
144     }
145 
146     /** {@inheritDoc} */
147     @Override
148     public List<AdditionalDataProvider<?>> getAdditionalDataProviders() {
149         return Collections.unmodifiableList(additionalDataProviders);
150     }
151 
152     /**
153      * Remove an additional data provider.
154      * @param name data name
155      * @since 13.1
156      */
157     public void removeAdditionalDataProvider(final String name) {
158         additionalDataProviders.removeIf(provider -> provider.getName().equals(name));
159     }
160 
161     /** {@inheritDoc} */
162     @Override
163     public MatricesHarvester setupMatricesComputation(final String stmName, final RealMatrix initialStm,
164                                                       final DoubleArrayDictionary initialJacobianColumns) {
165         if (stmName == null) {
166             throw new OrekitException(OrekitMessages.NULL_ARGUMENT, "stmName");
167         }
168         harvester = createHarvester(stmName, initialStm, initialJacobianColumns);
169         return harvester;
170     }
171 
172     /**
173      * Erases the internal matrices harvester.
174      * @since 13.1
175      */
176     public void clearMatricesComputation() {
177         harvester = null;
178     }
179 
180     /** Create the harvester suitable for propagator.
181      * @param stmName State Transition Matrix state name
182      * @param initialStm initial State Transition Matrix ∂Y/∂Y₀,
183      * if null (which is the most frequent case), assumed to be 6x6 identity
184      * @param initialJacobianColumns initial columns of the Jacobians matrix with respect to parameters,
185      * if null or if some selected parameters are missing from the dictionary, the corresponding
186      * initial column is assumed to be 0
187      * @return harvester to retrieve computed matrices during and after propagation
188      * @since 11.1
189      */
190     protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
191                                                         final DoubleArrayDictionary initialJacobianColumns) {
192         // FIXME: not implemented as of 11.1
193         throw new UnsupportedOperationException();
194     }
195 
196     /** Get the harvester.
197      * @return harvester, or null if it was not created
198      * @since 11.1
199      */
200     protected AbstractMatricesHarvester getHarvester() {
201         return harvester;
202     }
203 
204     /** Update state by adding unmanaged states.
205      * @param original original state
206      * @return updated state, with unmanaged states included
207      * @see #updateAdditionalData(SpacecraftState)
208      */
209     protected SpacecraftState updateUnmanagedData(final SpacecraftState original) {
210 
211         // start with original state,
212         // which may already contain additional data, for example in interpolated ephemerides
213         SpacecraftState updated = original;
214 
215         // update the data providers not managed by providers
216         for (final Map.Entry<String, TimeSpanMap<Object>> entry : unmanagedStates.entrySet()) {
217             updated = updated.addAdditionalData(entry.getKey(),
218                                                 entry.getValue().get(original.getDate()));
219         }
220 
221         return updated;
222 
223     }
224 
225     /** Update state by adding all additional data.
226      * @param original original state
227      * @return updated state, with all additional data included
228      * (including {@link #updateUnmanagedData(SpacecraftState) unmanaged} data)
229      * @see #addAdditionalDataProvider(AdditionalDataProvider)
230      * @see #updateUnmanagedData(SpacecraftState)
231      */
232     public SpacecraftState updateAdditionalData(final SpacecraftState original) {
233 
234         // start with original state and unmanaged data
235         SpacecraftState updated = updateUnmanagedData(original);
236 
237         // set up queue for providers
238         final Queue<AdditionalDataProvider<?>> pending = new LinkedList<>(getAdditionalDataProviders());
239 
240         // update the additional data managed by providers, taking care of dependencies
241         int yieldCount = 0;
242         while (!pending.isEmpty()) {
243             final AdditionalDataProvider<?> provider = pending.remove();
244             if (provider.yields(updated)) {
245                 // this generator has to wait for another one,
246                 // we put it again in the pending queue
247                 pending.add(provider);
248                 if (++yieldCount >= pending.size()) {
249                     // all pending providers yielded!, they probably need data not yet initialized
250                     // we let the propagation proceed, if these data are really needed right now
251                     // an appropriate exception will be triggered when caller tries to access them
252                     break;
253                 }
254             } else {
255                 // we can use this provider right now
256                 updated    = provider.update(updated);
257                 yieldCount = 0;
258             }
259         }
260 
261         return updated;
262 
263     }
264 
265     /**
266      * Initialize the additional state providers at the start of propagation.
267      * @param target date of propagation. Not equal to {@code initialState.getDate()}.
268      * @since 11.2
269      */
270     protected void initializeAdditionalData(final AbsoluteDate target) {
271         for (final AdditionalDataProvider<?> provider : additionalDataProviders) {
272             provider.init(initialState, target);
273         }
274     }
275 
276     /** {@inheritDoc} */
277     public boolean isAdditionalDataManaged(final String name) {
278         for (final AdditionalDataProvider<?> provider : additionalDataProviders) {
279             if (provider.getName().equals(name)) {
280                 return true;
281             }
282         }
283         return false;
284     }
285 
286     /** {@inheritDoc} */
287     public String[] getManagedAdditionalData() {
288         final String[] managed = new String[additionalDataProviders.size()];
289         for (int i = 0; i < managed.length; ++i) {
290             managed[i] = additionalDataProviders.get(i).getName();
291         }
292         return managed;
293     }
294 
295     /** {@inheritDoc} */
296     public SpacecraftState propagate(final AbsoluteDate target) {
297         if (startDate == null) {
298             startDate = getInitialState().getDate();
299         }
300         return propagate(startDate, target);
301     }
302 
303     /** Initialize propagation.
304      * @since 10.1
305      */
306     protected void initializePropagation() {
307 
308         unmanagedStates.clear();
309 
310         if (initialState != null) {
311             // there is an initial state
312             // (null initial states occur for example in interpolated ephemerides)
313             // copy the additional data present in initialState but otherwise not managed
314             for (final DataDictionary.Entry initial : initialState.getAdditionalDataValues().getData()) {
315                 if (!isAdditionalDataManaged(initial.getKey())) {
316                     // this additional data is in the initial state, but is unknown to the propagator
317                     // we store it in a way event handlers may change it
318                     unmanagedStates.put(initial.getKey(), new TimeSpanMap<>(initial.getValue()));
319                 }
320             }
321         }
322     }
323 
324     /** Notify about a state change.
325      * @param state new state
326      */
327     protected void stateChanged(final SpacecraftState state) {
328         final AbsoluteDate date    = state.getDate();
329         final boolean      forward = date.durationFrom(getStartDate()) >= 0.0;
330         for (final DataDictionary.Entry changed : state.getAdditionalDataValues().getData()) {
331             final TimeSpanMap<Object> tsm = unmanagedStates.get(changed.getKey());
332             if (tsm != null) {
333                 // this is an unmanaged state
334                 if (forward) {
335                     tsm.addValidAfter(changed.getValue(), date, false);
336                 } else {
337                     tsm.addValidBefore(changed.getValue(), date, false);
338                 }
339             }
340         }
341     }
342 
343 }