1   /* Copyright 2002-2022 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.List;
22  import java.util.concurrent.ExecutionException;
23  import java.util.concurrent.ExecutorService;
24  import java.util.concurrent.Executors;
25  import java.util.concurrent.Future;
26  import java.util.concurrent.SynchronousQueue;
27  import java.util.concurrent.TimeUnit;
28  
29  import org.hipparchus.exception.LocalizedCoreFormats;
30  import org.hipparchus.util.FastMath;
31  import org.orekit.errors.OrekitException;
32  import org.orekit.propagation.sampling.MultiSatStepHandler;
33  import org.orekit.propagation.sampling.OrekitStepHandler;
34  import org.orekit.propagation.sampling.OrekitStepInterpolator;
35  import org.orekit.time.AbsoluteDate;
36  
37  /** This class provides a way to propagate simultaneously several orbits.
38   *
39   * <p>
40   * Multi-satellites propagation is based on multi-threading. Therefore,
41   * care must be taken so that all propagators can be run in a multi-thread
42   * context. This implies that all propagators are built independently and
43   * that they rely on force models that are also built independently. An
44   * obvious mistake would be to reuse a maneuver force model, as these models
45   * need to cache the firing/not-firing status. Objects used by force models
46   * like atmosphere models for drag force or others may also cache intermediate
47   * variables, so separate instances for each propagator must be set up.
48   * </p>
49   * <p>
50   * This class <em>will</em> create new threads for running the propagators.
51   * It adds a new {@link MultiSatStepHandler global step handler} to manage
52   * the steps all at once, in addition to the existing individual step
53   * handlers that are preserved.
54   * </p>
55   * <p>
56   * All propagators remain independent of each other (they don't even know
57   * they are managed by the parallelizer) and advance their simulation
58   * time following their own algorithm. The parallelizer will block them
59   * at the end of each step and allow them to continue in order to maintain
60   * synchronization. The {@link MultiSatStepHandler global handler} will
61   * experience perfectly synchronized steps, but some propagators may already
62   * be slightly ahead of time as depicted in the following rendering; were
63   * simulation times flows from left to right:
64   * </p>
65   * <pre>
66   *    propagator 1   : -------------[++++current step++++]&gt;
67   *                                  |
68   *    propagator 2   : ----[++++current step++++]---------&gt;
69   *                                  |           |
70   *    ...                           |           |
71   *    propagator n   : ---------[++++current step++++]----&gt;
72   *                                  |           |
73   *                                  V           V
74   *    global handler : -------------[global step]---------&gt;
75   * </pre>
76   * <p>
77   * The previous sketch shows that propagator 1 has already computed states
78   * up to the end of the propagation, but propagators 2 up to n are still late.
79   * The global step seen by the handler will be the common part between all
80   * propagators steps. Once this global step has been handled, the parallelizer
81   * will let the more late propagator (here propagator 2) to go one step further
82   * and a new global step will be computed and handled, until all propagators
83   * reach the end.
84   * </p>
85   * <p>
86   * This class does <em>not</em> provide multi-satellite events. As events
87   * may truncate steps and even reset state, all events (including multi-satellite
88   * events) are handled at a very low level within each propagators and cannot be
89   * managed from outside by the parallelizer. For accurate handling of multi-satellite
90   * events, the event detector should be registered <em>within</em> the propagator
91   * of one satellite and have access to an independent propagator (typically an
92   * analytical propagator or an ephemeris) of the other satellite. As the embedded
93   * propagator will be called by the detector which itself is called by the first
94   * propagator, it should really be a dedicated propagator and should not also
95   * appear as one of the parallelized propagators, otherwise conflicts will appear here.
96   * </p>
97   * @author Luc Maisonobe
98   * @since 9.0
99   */
100 
101 public class PropagatorsParallelizer {
102 
103     /** Waiting time to avoid getting stuck waiting for interrupted threads (ms). */
104     private static long MAX_WAIT = 10;
105 
106     /** Underlying propagators. */
107     private final List<Propagator> propagators;
108 
109     /** Global step handler. */
110     private final MultiSatStepHandler globalHandler;
111 
112     /** Simple constructor.
113      * @param propagators list of propagators to use
114      * @param globalHandler global handler for managing all spacecrafts
115      * simultaneously
116      */
117     public PropagatorsParallelizer(final List<Propagator> propagators,
118                                    final MultiSatStepHandler globalHandler) {
119         this.propagators = propagators;
120         this.globalHandler = globalHandler;
121     }
122 
123     /** Get an unmodifiable list of the underlying mono-satellite propagators.
124      * @return unmodifiable list of the underlying mono-satellite propagators
125      */
126     public List<Propagator> getPropagators() {
127         return Collections.unmodifiableList(propagators);
128     }
129 
130     /** Propagate from a start date towards a target date.
131      * @param start start date from which orbit state should be propagated
132      * @param target target date to which orbit state should be propagated
133      * @return propagated states
134      */
135     public List<SpacecraftState> propagate(final AbsoluteDate start, final AbsoluteDate target) {
136 
137         if (propagators.size() == 1) {
138             // special handling when only one propagator is used
139             propagators.get(0).getMultiplexer().add(new SinglePropagatorHandler(globalHandler));
140             return Collections.singletonList(propagators.get(0).propagate(start, target));
141         }
142 
143         final double sign = FastMath.copySign(1.0, target.durationFrom(start));
144 
145         // start all propagators in concurrent threads
146         final ExecutorService            executorService = Executors.newFixedThreadPool(propagators.size());
147         final List<PropagatorMonitoring> monitors        = new ArrayList<>(propagators.size());
148         for (final Propagator propagator : propagators) {
149             final PropagatorMonitoring monitor = new PropagatorMonitoring(propagator, start, target, executorService);
150             monitor.waitFirstStepCompletion();
151             monitors.add(monitor);
152         }
153 
154         // main loop
155         AbsoluteDate previousDate = start;
156         final List<SpacecraftState> initialStates = new ArrayList<>(monitors.size());
157         for (final PropagatorMonitoring monitor : monitors) {
158             initialStates.add(monitor.parameters.initialState);
159         }
160         globalHandler.init(initialStates, target);
161         for (boolean isLast = false; !isLast;) {
162 
163             // select the earliest ending propagator, according to propagation direction
164             PropagatorMonitoring selected = null;
165             AbsoluteDate selectedStepEnd  = null;
166             for (PropagatorMonitoring monitor : monitors) {
167                 final AbsoluteDate stepEnd = monitor.parameters.interpolator.getCurrentState().getDate();
168                 if (selected == null || sign * selectedStepEnd.durationFrom(stepEnd) > 0) {
169                     selected        = monitor;
170                     selectedStepEnd = stepEnd;
171                 }
172             }
173 
174             // restrict steps to a common time range
175             for (PropagatorMonitoring monitor : monitors) {
176                 final OrekitStepInterpolator interpolator  = monitor.parameters.interpolator;
177                 final SpacecraftState        previousState = interpolator.getInterpolatedState(previousDate);
178                 final SpacecraftState        currentState  = interpolator.getInterpolatedState(selectedStepEnd);
179                 monitor.restricted                         = interpolator.restrictStep(previousState, currentState);
180             }
181 
182             // handle all states at once
183             final List<OrekitStepInterpolator> interpolators = new ArrayList<>(monitors.size());
184             for (final PropagatorMonitoring monitor : monitors) {
185                 interpolators.add(monitor.restricted);
186             }
187             globalHandler.handleStep(interpolators);
188 
189             if (selected.parameters.finalState == null) {
190                 // step handler can still provide new results
191                 // this will wait until either handleStep or finish are called
192                 selected.retrieveNextParameters();
193             } else {
194                 // this was the last step
195                 isLast = true;
196                 /* For NumericalPropagators :
197                  * After reaching the finalState with the selected monitor,
198                  * we need to do the step with all remaining monitors to reach the target time.
199                  * This also triggers the StoringStepHandler, producing ephemeris.
200                  */
201                 for (PropagatorMonitoring monitor : monitors) {
202                     if (monitor != selected) {
203                         monitor.retrieveNextParameters();
204                     }
205                 }
206             }
207 
208             previousDate = selectedStepEnd;
209 
210         }
211 
212         // stop all remaining propagators
213         executorService.shutdownNow();
214 
215         // extract the final states
216         final List<SpacecraftState> finalStates = new ArrayList<>(monitors.size());
217         for (PropagatorMonitoring monitor : monitors) {
218             try {
219                 finalStates.add(monitor.future.get());
220             } catch (InterruptedException | ExecutionException e) {
221 
222                 // sort out if exception was intentional or not
223                 monitor.manageException(e);
224 
225                 // this propagator was intentionally stopped,
226                 // we retrieve the final state from the last available interpolator
227                 finalStates.add(monitor.parameters.interpolator.getInterpolatedState(previousDate));
228 
229             }
230         }
231 
232         globalHandler.finish(finalStates);
233 
234         return finalStates;
235 
236     }
237 
238     /** Local exception to stop propagators. */
239     private static class PropagatorStoppingException extends OrekitException {
240 
241         /** Serializable UID.*/
242         private static final long serialVersionUID = 20170629L;
243 
244         /** Simple constructor.
245          * @param ie interruption exception
246          */
247         PropagatorStoppingException(final InterruptedException ie) {
248             super(ie, LocalizedCoreFormats.SIMPLE_MESSAGE, ie.getLocalizedMessage());
249         }
250 
251     }
252 
253     /** Local class for handling single propagator steps. */
254     private static class SinglePropagatorHandler implements OrekitStepHandler {
255 
256         /** Global handler. */
257         private final MultiSatStepHandler globalHandler;
258 
259         /** Simple constructor.
260          * @param globalHandler global handler to call
261          */
262         SinglePropagatorHandler(final MultiSatStepHandler globalHandler) {
263             this.globalHandler = globalHandler;
264         }
265 
266 
267         /** {@inheritDoc} */
268         @Override
269         public void init(final SpacecraftState s0, final AbsoluteDate t) {
270             globalHandler.init(Collections.singletonList(s0), t);
271         }
272 
273         /** {@inheritDoc} */
274         @Override
275         public void handleStep(final OrekitStepInterpolator interpolator) {
276             globalHandler.handleStep(Collections.singletonList(interpolator));
277         }
278 
279         /** {@inheritDoc} */
280         @Override
281         public void finish(final SpacecraftState finalState) {
282             globalHandler.finish(Collections.singletonList(finalState));
283         }
284 
285     }
286 
287     /** Local class for handling multiple propagator steps. */
288     private static class MultiplePropagatorsHandler implements OrekitStepHandler {
289 
290         /** Previous container handed off. */
291         private ParametersContainer previous;
292 
293         /** Queue for passing step handling parameters. */
294         private final SynchronousQueue<ParametersContainer> queue;
295 
296         /** Simple constructor.
297          * @param queue queue for passing step handling parameters
298          */
299         MultiplePropagatorsHandler(final SynchronousQueue<ParametersContainer> queue) {
300             this.previous = new ParametersContainer(null, null, null);
301             this.queue    = queue;
302         }
303 
304         /** Hand off container to parallelizer.
305          * @param container parameters container to hand-off
306          */
307         private void handOff(final ParametersContainer container) {
308             try {
309                 previous = container;
310                 queue.put(previous);
311             } catch (InterruptedException ie) {
312                 // use a dedicated exception to stop thread almost gracefully
313                 throw new PropagatorStoppingException(ie);
314             }
315         }
316 
317         /** {@inheritDoc} */
318         @Override
319         public void init(final SpacecraftState s0, final AbsoluteDate t) {
320             handOff(new ParametersContainer(s0, null, null));
321         }
322 
323         /** {@inheritDoc} */
324         @Override
325         public void handleStep(final OrekitStepInterpolator interpolator) {
326             handOff(new ParametersContainer(previous.initialState, interpolator, null));
327         }
328 
329         /** {@inheritDoc} */
330         @Override
331         public void finish(final SpacecraftState finalState) {
332             handOff(new ParametersContainer(previous.initialState, previous.interpolator, finalState));
333         }
334 
335     }
336 
337     /** Container for parameters passed by propagators to step handlers. */
338     private static class ParametersContainer {
339 
340         /** Initial state. */
341         private final SpacecraftState initialState;
342 
343         /** Interpolator set up for last seen step. */
344         private final OrekitStepInterpolator interpolator;
345 
346         /** Final state. */
347         private final SpacecraftState finalState;
348 
349         /** Simple constructor.
350          * @param initialState initial state
351          * @param interpolator interpolator set up for last seen step
352          * @param finalState final state
353          */
354         ParametersContainer(final SpacecraftState initialState,
355                             final OrekitStepInterpolator interpolator,
356                             final SpacecraftState finalState) {
357             this.initialState = initialState;
358             this.interpolator = interpolator;
359             this.finalState   = finalState;
360         }
361 
362     }
363 
364     /** Container for propagator monitoring. */
365     private static class PropagatorMonitoring {
366 
367         /** Queue for handing off step handler parameters. */
368         private final SynchronousQueue<ParametersContainer> queue;
369 
370         /** Future for retrieving propagation return value. */
371         private final Future<SpacecraftState> future;
372 
373         /** Last step handler parameters received. */
374         private ParametersContainer parameters;
375 
376         /** Interpolator restricted to time range shared with other propagators. */
377         private OrekitStepInterpolator restricted;
378 
379         /** Simple constructor.
380          * @param propagator managed propagator
381          * @param start start date from which orbit state should be propagated
382          * @param target target date to which orbit state should be propagated
383          * @param executorService service for running propagator
384          */
385         PropagatorMonitoring(final Propagator propagator, final AbsoluteDate start, final AbsoluteDate target,
386                              final ExecutorService executorService) {
387 
388             // set up queue for handing off step handler parameters synchronization
389             // the main thread will let underlying propagators go forward
390             // by consuming the step handling parameters they will put at each step
391             queue = new SynchronousQueue<>();
392             propagator.getMultiplexer().add(new MultiplePropagatorsHandler(queue));
393 
394             // start the propagator
395             future = executorService.submit(() -> propagator.propagate(start, target));
396 
397         }
398 
399         /** Wait completion of first step.
400          */
401         public void waitFirstStepCompletion() {
402 
403             // wait until both the init method and the handleStep method
404             // of the current propagator step handler have been called,
405             // thus ensuring we have one step available to compare propagators
406             // progress with each other
407             while (parameters == null || parameters.initialState == null || parameters.interpolator == null) {
408                 retrieveNextParameters();
409             }
410 
411         }
412 
413         /** Retrieve next step handling parameters.
414          */
415         public void retrieveNextParameters() {
416             try {
417                 ParametersContainer params = null;
418                 while (params == null && !future.isDone()) {
419                     params = queue.poll(MAX_WAIT, TimeUnit.MILLISECONDS);
420                     // Check to avoid loop on future not done, in the case of reached finalState.
421                     if (parameters != null) {
422                         if (parameters.finalState != null) {
423                             break;
424                         }
425                     }
426                 }
427                 if (params == null) {
428                     // call Future.get just for the side effect of retrieving the exception
429                     // in case the propagator ended due to an exception
430                     future.get();
431                 }
432                 parameters = params;
433             } catch (InterruptedException | ExecutionException e) {
434                 manageException(e);
435                 parameters = null;
436             }
437         }
438 
439         /** Convert exceptions.
440          * @param exception exception caught
441          */
442         private void manageException(final Exception exception) {
443             if (exception.getCause() instanceof PropagatorStoppingException) {
444                 // this was an expected exception, we deliberately shut down the propagators
445                 // we therefore explicitly ignore this exception
446                 return;
447             } else if (exception.getCause() instanceof OrekitException) {
448                 // unwrap the original exception
449                 throw (OrekitException) exception.getCause();
450             } else {
451                 throw new OrekitException(exception.getCause(),
452                                           LocalizedCoreFormats.SIMPLE_MESSAGE, exception.getLocalizedMessage());
453             }
454         }
455 
456     }
457 
458 }