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