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