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 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     public SpacecraftState getInitialState() {
104         return initialState;
105     }
106 
107     /** {@inheritDoc} */
108     public Frame getFrame() {
109         return initialState.getFrame();
110     }
111 
112     /** {@inheritDoc} */
113     public void resetInitialState(final SpacecraftState state) {
114         initialState = state;
115         setStartDate(state.getDate());
116     }
117 
118     /** {@inheritDoc} */
119     public StepHandlerMultiplexer getMultiplexer() {
120         return multiplexer;
121     }
122 
123     /** {@inheritDoc} */
124     @Override
125     public void addAdditionalDataProvider(final AdditionalDataProvider<?> provider) {
126 
127         // check if the name is already used
128         if (isAdditionalDataManaged(provider.getName())) {
129             // this additional state is already registered, complain
130             throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_NAME_ALREADY_IN_USE,
131                                       provider.getName());
132         }
133 
134         // this is really a new name, add it
135         additionalDataProviders.add(provider);
136 
137     }
138 
139     /** {@inheritDoc} */
140     @Override
141     public List<AdditionalDataProvider<?>> getAdditionalDataProviders() {
142         return Collections.unmodifiableList(additionalDataProviders);
143     }
144 
145     /**
146      * Remove an additional data provider.
147      * @param name data name
148      * @since 13.1
149      */
150     public void removeAdditionalDataProvider(final String name) {
151         additionalDataProviders.removeIf(provider -> provider.getName().equals(name));
152     }
153 
154     /** {@inheritDoc} */
155     @Override
156     public MatricesHarvester setupMatricesComputation(final String stmName, final RealMatrix initialStm,
157                                                       final DoubleArrayDictionary initialJacobianColumns) {
158         if (stmName == null) {
159             throw new OrekitException(OrekitMessages.NULL_ARGUMENT, "stmName");
160         }
161         harvester = createHarvester(stmName, initialStm, initialJacobianColumns);
162         return harvester;
163     }
164 
165     /**
166      * Erases the internal matrices harvester.
167      * @since 13.1
168      */
169     public void clearMatricesComputation() {
170         harvester = null;
171     }
172 
173     /** Create the harvester suitable for propagator.
174      * @param stmName State Transition Matrix state name
175      * @param initialStm initial State Transition Matrix ∂Y/∂Y₀,
176      * if null (which is the most frequent case), assumed to be 6x6 identity
177      * @param initialJacobianColumns initial columns of the Jacobians matrix with respect to parameters,
178      * if null or if some selected parameters are missing from the dictionary, the corresponding
179      * initial column is assumed to be 0
180      * @return harvester to retrieve computed matrices during and after propagation
181      * @since 11.1
182      */
183     protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
184                                                         final DoubleArrayDictionary initialJacobianColumns) {
185         // FIXME: not implemented as of 11.1
186         throw new UnsupportedOperationException();
187     }
188 
189     /** Get the harvester.
190      * @return harvester, or null if it was not created
191      * @since 11.1
192      */
193     protected AbstractMatricesHarvester getHarvester() {
194         return harvester;
195     }
196 
197     /** Update state by adding unmanaged states.
198      * @param original original state
199      * @return updated state, with unmanaged states included
200      * @see #updateAdditionalData(SpacecraftState)
201      */
202     protected SpacecraftState updateUnmanagedData(final SpacecraftState original) {
203 
204         // start with original state,
205         // which may already contain additional data, for example in interpolated ephemerides
206         SpacecraftState updated = original;
207 
208         // update the data providers not managed by providers
209         for (final Map.Entry<String, TimeSpanMap<Object>> entry : unmanagedStates.entrySet()) {
210             updated = updated.addAdditionalData(entry.getKey(),
211                                                 entry.getValue().get(original.getDate()));
212         }
213 
214         return updated;
215 
216     }
217 
218     /** Update state by adding all additional data.
219      * @param original original state
220      * @return updated state, with all additional data included
221      * (including {@link #updateUnmanagedData(SpacecraftState) unmanaged} data)
222      * @see #addAdditionalDataProvider(AdditionalDataProvider)
223      * @see #updateUnmanagedData(SpacecraftState)
224      */
225     public SpacecraftState updateAdditionalData(final SpacecraftState original) {
226 
227         // start with original state and unmanaged data
228         SpacecraftState updated = updateUnmanagedData(original);
229 
230         // set up queue for providers
231         final Queue<AdditionalDataProvider<?>> pending = new LinkedList<>(getAdditionalDataProviders());
232 
233         // update the additional data managed by providers, taking care of dependencies
234         int yieldCount = 0;
235         while (!pending.isEmpty()) {
236             final AdditionalDataProvider<?> provider = pending.remove();
237             if (provider.yields(updated)) {
238                 // this generator has to wait for another one,
239                 // we put it again in the pending queue
240                 pending.add(provider);
241                 if (++yieldCount >= pending.size()) {
242                     // all pending providers yielded!, they probably need data not yet initialized
243                     // we let the propagation proceed, if these data are really needed right now
244                     // an appropriate exception will be triggered when caller tries to access them
245                     break;
246                 }
247             } else {
248                 // we can use this provider right now
249                 updated    = provider.update(updated);
250                 yieldCount = 0;
251             }
252         }
253 
254         return updated;
255 
256     }
257 
258     /**
259      * Initialize the additional state providers at the start of propagation.
260      * @param target date of propagation. Not equal to {@code initialState.getDate()}.
261      * @since 11.2
262      */
263     protected void initializeAdditionalData(final AbsoluteDate target) {
264         for (final AdditionalDataProvider<?> provider : additionalDataProviders) {
265             provider.init(initialState, target);
266         }
267     }
268 
269     /** {@inheritDoc} */
270     public boolean isAdditionalDataManaged(final String name) {
271         for (final AdditionalDataProvider<?> provider : additionalDataProviders) {
272             if (provider.getName().equals(name)) {
273                 return true;
274             }
275         }
276         return false;
277     }
278 
279     /** {@inheritDoc} */
280     public String[] getManagedAdditionalData() {
281         final String[] managed = new String[additionalDataProviders.size()];
282         for (int i = 0; i < managed.length; ++i) {
283             managed[i] = additionalDataProviders.get(i).getName();
284         }
285         return managed;
286     }
287 
288     /** {@inheritDoc} */
289     public SpacecraftState propagate(final AbsoluteDate target) {
290         if (startDate == null) {
291             startDate = getInitialState().getDate();
292         }
293         return propagate(startDate, target);
294     }
295 
296     /** Initialize propagation.
297      * @since 10.1
298      */
299     protected void initializePropagation() {
300 
301         unmanagedStates.clear();
302 
303         if (initialState != null) {
304             // there is an initial state
305             // (null initial states occur for example in interpolated ephemerides)
306             // copy the additional data present in initialState but otherwise not managed
307             for (final DataDictionary.Entry initial : initialState.getAdditionalDataValues().getData()) {
308                 if (!isAdditionalDataManaged(initial.getKey())) {
309                     // this additional data is in the initial state, but is unknown to the propagator
310                     // we store it in a way event handlers may change it
311                     unmanagedStates.put(initial.getKey(), new TimeSpanMap<>(initial.getValue()));
312                 }
313             }
314         }
315     }
316 
317     /** Notify about a state change.
318      * @param state new state
319      */
320     protected void stateChanged(final SpacecraftState state) {
321         final AbsoluteDate date    = state.getDate();
322         final boolean      forward = date.durationFrom(getStartDate()) >= 0.0;
323         for (final DataDictionary.Entry changed : state.getAdditionalDataValues().getData()) {
324             final TimeSpanMap<Object> tsm = unmanagedStates.get(changed.getKey());
325             if (tsm != null) {
326                 // this is an unmanaged state
327                 if (forward) {
328                     tsm.addValidAfter(changed.getValue(), date, false);
329                 } else {
330                     tsm.addValidBefore(changed.getValue(), date, false);
331                 }
332             }
333         }
334     }
335 
336 }