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