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++++]>
68 * |
69 * propagator 2 : ----[++++current step++++]--------->
70 * | |
71 * ... | |
72 * propagator n : ---------[++++current step++++]---->
73 * | |
74 * V V
75 * global handler : -------------[global step]--------->
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 }