PropagatorsParallelizer.java

  1. /* Copyright 2002-2020 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. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.List;
  21. import java.util.concurrent.ExecutionException;
  22. import java.util.concurrent.ExecutorService;
  23. import java.util.concurrent.Executors;
  24. import java.util.concurrent.Future;
  25. import java.util.concurrent.SynchronousQueue;
  26. import java.util.concurrent.TimeUnit;

  27. import org.hipparchus.exception.LocalizedCoreFormats;
  28. import org.hipparchus.util.FastMath;
  29. import org.orekit.errors.OrekitException;
  30. import org.orekit.propagation.sampling.MultiSatStepHandler;
  31. import org.orekit.propagation.sampling.OrekitStepHandler;
  32. import org.orekit.propagation.sampling.OrekitStepInterpolator;
  33. import org.orekit.time.AbsoluteDate;
  34. import org.orekit.time.TimeStamped;

  35. /** This class provides a way to propagate simultaneously several orbits.
  36.  *
  37.  * <p>
  38.  * Multi-satellites propagation is based on multi-threading. Therefore,
  39.  * care must be taken so that all propagators can be run in a multi-thread
  40.  * context. This implies that all propagators are built independently and
  41.  * that they rely on force models that are also built independently. An
  42.  * obvious mistake would be to reuse a maneuver force model, as these models
  43.  * need to cache the firing/not-firing status. Objects used by force models
  44.  * like atmosphere models for drag force or others may also cache intermediate
  45.  * variables, so separate instances for each propagator must be set up.
  46.  * </p>
  47.  * <p>
  48.  * This class <em>will</em> create new threads for running the propagators
  49.  * and it <em>will</em> override the underlying propagators step handlers.
  50.  * The intent is anyway to manage the steps all at once using the global
  51.  * {@link MultiSatStepHandler handler} set up at construction.
  52.  * </p>
  53.  * <p>
  54.  * All propagators remain independent of each other (they don't even know
  55.  * they are managed by the parallelizer) and advance their simulation
  56.  * time following their own algorithm. The parallelizer will block them
  57.  * at the end of each step and allow them to continue in order to maintain
  58.  * synchronization. The {@link MultiSatStepHandler global handler} will
  59.  * experience perfectly synchronized steps, but some propagators may already
  60.  * be slightly ahead of time as depicted in the following rendering; were
  61.  * simulation times flows from left to right:
  62.  * </p>
  63.  * <pre>
  64.  *    propagator 1   : -------------[++++current step++++]&gt;
  65.  *                                  |
  66.  *    propagator 2   : ----[++++current step++++]---------&gt;
  67.  *                                  |           |
  68.  *    ...                           |           |
  69.  *    propagator n   : ---------[++++current step++++]----&gt;
  70.  *                                  |           |
  71.  *                                  V           V
  72.  *    global handler : -------------[global step]---------&gt;
  73.  * </pre>
  74.  * <p>
  75.  * The previous sketch shows that propagator 1 has already computed states
  76.  * up to the end of the propagation, but propagators 2 up to n are still late.
  77.  * The global step seen by the handler will be the common part between all
  78.  * propagators steps. Once this global step has been handled, the parallelizer
  79.  * will let the more late propagator (here propagator 2) to go one step further
  80.  * and a new global step will be computed and handled, until all propagators
  81.  * reach the end.
  82.  * </p>
  83.  * <p>
  84.  * This class does <em>not</em> provide multi-satellite events. As events
  85.  * may truncate steps and even reset state, all events (including multi-satellite
  86.  * events) are handled at a very low level within each propagators and cannot be
  87.  * managed from outside by the parallelizer. For accurate handling of multi-satellite
  88.  * events, the event detector should be registered <em>within</em> the propagator
  89.  * of one satellite and have access to an independent propagator (typically an
  90.  * analytical propagator or an ephemeris) of the other satellite. As the embedded
  91.  * propagator will be called by the detector which itself is called by the first
  92.  * propagator, it should really be a dedicated propagator and should not also
  93.  * appear as one of the parallelized propagators, otherwise conflicts will appear here.
  94.  * </p>
  95.  * @author Luc Maisonobe
  96.  * @since 9.0
  97.  */

  98. public class PropagatorsParallelizer {

  99.     /** Waiting time to avoid getting stuck waiting for interrupted threads (ms). */
  100.     private static long MAX_WAIT = 10;

  101.     /** Underlying propagators. */
  102.     private final List<Propagator> propagators;

  103.     /** Global step handler. */
  104.     private final MultiSatStepHandler globalHandler;

  105.     /** Simple constructor.
  106.      * @param propagators list of propagators to use
  107.      * @param globalHandler global handler for managing all spacecrafts
  108.      * simultaneously
  109.      */
  110.     public PropagatorsParallelizer(final List<Propagator> propagators,
  111.                                    final MultiSatStepHandler globalHandler) {
  112.         this.propagators = propagators;
  113.         this.globalHandler = globalHandler;
  114.     }

  115.     /** Get an unmodifiable list of the underlying mono-satellite propagators.
  116.      * @return unmodifiable list of the underlying mono-satellite propagators
  117.      */
  118.     public List<Propagator> getPropagators() {
  119.         return Collections.unmodifiableList(propagators);
  120.     }

  121.     /** Propagate from a start date towards a target date.
  122.      * @param start start date from which orbit state should be propagated
  123.      * @param target target date to which orbit state should be propagated
  124.      * @return propagated states
  125.      */
  126.     public List<SpacecraftState> propagate(final AbsoluteDate start, final AbsoluteDate target) {

  127.         if (propagators.size() == 1) {
  128.             // special handling when only one propagator is used
  129.             propagators.get(0).setMasterMode(new SinglePropagatorHandler(globalHandler));
  130.             return Collections.singletonList(propagators.get(0).propagate(start, target));
  131.         }

  132.         final double sign = FastMath.copySign(1.0, target.durationFrom(start));
  133.         final int n = propagators.size();

  134.         // set up queues for propagators synchronization
  135.         // the main thread will let underlying propagators go forward
  136.         // by consuming the step handling parameters they will put at each step
  137.         final List<SynchronousQueue<SpacecraftState>>        initQueues = new ArrayList<>(n);
  138.         final List<SynchronousQueue<StepHandlingParameters>> shpQueues  = new ArrayList<>(n);
  139.         for (final Propagator propagator : propagators) {
  140.             final SynchronousQueue<SpacecraftState>        initQueue = new SynchronousQueue<>();
  141.             initQueues.add(initQueue);
  142.             final SynchronousQueue<StepHandlingParameters> shpQueue  = new SynchronousQueue<>();
  143.             shpQueues.add(shpQueue);
  144.             propagator.setMasterMode(new MultiplePropagatorsHandler(initQueue, shpQueue));
  145.         }

  146.         // concurrently run all propagators
  147.         final ExecutorService               executorService        = Executors.newFixedThreadPool(n);
  148.         final List<Future<SpacecraftState>> futures                = new ArrayList<>(n);
  149.         final List<SpacecraftState>         initialStates          = new ArrayList<>(n);
  150.         final List<StepHandlingParameters>  stepHandlingParameters = new ArrayList<>(n);
  151.         final List<OrekitStepInterpolator>  restricted             = new ArrayList<>(n);
  152.         final List<SpacecraftState>         finalStates            = new ArrayList<>(n);
  153.         for (int i = 0; i < n; ++i) {
  154.             final Propagator propagator = propagators.get(i);
  155.             final Future<SpacecraftState> future = executorService.submit(() -> propagator.propagate(start, target));
  156.             futures.add(future);
  157.             initialStates.add(getParameters(i, future, initQueues.get(i)));
  158.             stepHandlingParameters.add(getParameters(i, future, shpQueues.get(i)));
  159.             restricted.add(null);
  160.             finalStates.add(null);
  161.         }

  162.         // main loop
  163.         AbsoluteDate previousDate = start;
  164.         globalHandler.init(initialStates, target);
  165.         for (boolean isLast = false; !isLast;) {

  166.             // select the earliest ending propagator, according to propagation direction
  167.             int selected = -1;
  168.             AbsoluteDate selectedStepEnd = null;
  169.             for (int i = 0; i < n; ++i) {
  170.                 final AbsoluteDate stepEnd = stepHandlingParameters.get(i).getDate();
  171.                 if (selected < 0 || sign * selectedStepEnd.durationFrom(stepEnd) > 0) {
  172.                     selected        = i;
  173.                     selectedStepEnd = stepEnd;
  174.                 }
  175.             }

  176.             // restrict steps to a common time range
  177.             for (int i = 0; i < n; ++i) {
  178.                 final OrekitStepInterpolator interpolator  = stepHandlingParameters.get(i).interpolator;
  179.                 final SpacecraftState        previousState = interpolator.getInterpolatedState(previousDate);
  180.                 final SpacecraftState        currentState  = interpolator.getInterpolatedState(selectedStepEnd);
  181.                 restricted.set(i, interpolator.restrictStep(previousState, currentState));
  182.             }

  183.             // will this be the last step?
  184.             isLast = stepHandlingParameters.get(selected).isLast;

  185.             // handle all states at once
  186.             globalHandler.handleStep(restricted, isLast);

  187.             if (!isLast) {
  188.                 // advance one step
  189.                 stepHandlingParameters.set(selected,
  190.                                            getParameters(selected, futures.get(selected), shpQueues.get(selected)));
  191.             }

  192.             previousDate = selectedStepEnd;

  193.         }

  194.         // stop all remaining propagators
  195.         executorService.shutdownNow();

  196.         // extract the final states
  197.         for (int i = 0; i < n; ++i) {
  198.             try {
  199.                 finalStates.set(i, futures.get(i).get());
  200.             } catch (InterruptedException | ExecutionException e) {

  201.                 // sort out if exception was intentional or not
  202.                 manageException(e);

  203.                 // this propagator was intentionally stopped,
  204.                 // we retrieve the final state from the last available interpolator
  205.                 finalStates.set(i, stepHandlingParameters.get(i).interpolator.getInterpolatedState(previousDate));

  206.             }
  207.         }

  208.         return finalStates;

  209.     }

  210.     /** Retrieve parameters.
  211.      * @param index index of the propagator
  212.      * @param future propagation task
  213.      * @param queue queue for transferring parameters
  214.      * @param <T> type of the parameters
  215.      * @return retrieved parameters
  216.      */
  217.     private <T> T getParameters(final int index,
  218.                                 final Future<SpacecraftState> future,
  219.                                 final SynchronousQueue<T> queue) {
  220.         try {
  221.             T params = null;
  222.             while (params == null && !future.isDone()) {
  223.                 params = queue.poll(MAX_WAIT, TimeUnit.MILLISECONDS);
  224.             }
  225.             if (params == null) {
  226.                 // call Future.get just for the side effect of retrieving the exception
  227.                 // in case the propagator ended due to an exception
  228.                 future.get();
  229.             }
  230.             return params;
  231.         } catch (InterruptedException | ExecutionException e) {
  232.             manageException(e);
  233.             return null;
  234.         }
  235.     }

  236.     /** Convert exceptions.
  237.      * @param exception exception caught
  238.      */
  239.     private void manageException(final Exception exception) {
  240.         if (exception.getCause() instanceof PropagatorStoppingException) {
  241.             // this was an expected exception, we deliberately shut down the propagators
  242.             // we therefore explicitly ignore this exception
  243.             return;
  244.         } else if (exception.getCause() instanceof OrekitException) {
  245.             // unwrap the original exception
  246.             throw (OrekitException) exception.getCause();
  247.         } else {
  248.             throw new OrekitException(exception.getCause(),
  249.                                       LocalizedCoreFormats.SIMPLE_MESSAGE, exception.getLocalizedMessage());
  250.         }
  251.     }

  252.     /** Local exception to stop propagators. */
  253.     private static class PropagatorStoppingException extends OrekitException {

  254.         /** Serializable UID.*/
  255.         private static final long serialVersionUID = 20170629L;

  256.         /** Simple constructor.
  257.          * @param ie interruption exception
  258.          */
  259.         PropagatorStoppingException(final InterruptedException ie) {
  260.             super(ie, LocalizedCoreFormats.SIMPLE_MESSAGE, ie.getLocalizedMessage());
  261.         }

  262.     }

  263.     /** Local class for handling single propagator steps. */
  264.     private static class SinglePropagatorHandler implements OrekitStepHandler {

  265.         /** Global handler. */
  266.         private final MultiSatStepHandler globalHandler;

  267.         /** Simple constructor.
  268.          * @param globalHandler global handler to call
  269.          */
  270.         SinglePropagatorHandler(final MultiSatStepHandler globalHandler) {
  271.             this.globalHandler = globalHandler;
  272.         }


  273.         /** {@inheritDoc} */
  274.         @Override
  275.         public void init(final SpacecraftState s0, final AbsoluteDate t) {
  276.             globalHandler.init(Collections.singletonList(s0), t);
  277.         }

  278.         /** {@inheritDoc} */
  279.         @Override
  280.         public void handleStep(final OrekitStepInterpolator interpolator, final boolean isLast) {
  281.             globalHandler.handleStep(Collections.singletonList(interpolator), isLast);
  282.         }

  283.     }

  284.     /** Local class for handling multiple propagator steps. */
  285.     private static class MultiplePropagatorsHandler implements OrekitStepHandler {

  286.         /** Queue for passing initial state. */
  287.         private final SynchronousQueue<SpacecraftState> initQueue;

  288.         /** Queue for passing step handling parameters. */
  289.         private final SynchronousQueue<StepHandlingParameters> shpQueue;

  290.         /** Simple constructor.
  291.          * @param initQueue queuefor passing initial state
  292.          * @param shpQueue queue for passing step handling parameters.
  293.          */
  294.         MultiplePropagatorsHandler(final SynchronousQueue<SpacecraftState> initQueue,
  295.                                    final SynchronousQueue<StepHandlingParameters> shpQueue) {
  296.             this.initQueue = initQueue;
  297.             this.shpQueue  = shpQueue;
  298.         }


  299.         /** {@inheritDoc} */
  300.         @Override
  301.         public void init(final SpacecraftState s0, final AbsoluteDate t) {
  302.             try {
  303.                 initQueue.put(s0);
  304.             } catch (InterruptedException ie) {
  305.                 // use a dedicated exception to stop thread almost gracefully
  306.                 throw new PropagatorStoppingException(ie);
  307.             }
  308.         }

  309.         /** {@inheritDoc} */
  310.         @Override
  311.         public void handleStep(final OrekitStepInterpolator interpolator, final boolean isLast) {
  312.             try {
  313.                 shpQueue.put(new StepHandlingParameters(interpolator, isLast));
  314.             } catch (InterruptedException ie) {
  315.                 // use a dedicated exception to stop thread almost gracefully
  316.                 throw new PropagatorStoppingException(ie);
  317.             }
  318.         }

  319.     }

  320.     /** Local class holding parameters for one step handling. */
  321.     private static class StepHandlingParameters implements TimeStamped {

  322.         /** Interpolator set up for the current step. */
  323.         private final OrekitStepInterpolator interpolator;

  324.         /** Indicator for last step. */
  325.         private final boolean isLast;

  326.         /** Simple constructor.
  327.          * @param interpolator interpolator set up for the current step
  328.          * @param isLast if true, this is the last integration step
  329.          */
  330.         StepHandlingParameters(final OrekitStepInterpolator interpolator, final boolean isLast) {
  331.             this.interpolator = interpolator;
  332.             this.isLast       = isLast;
  333.         }

  334.         /** {@inheritDoc} */
  335.         @Override
  336.         public AbsoluteDate getDate() {
  337.             return interpolator.getCurrentState().getDate();
  338.         }

  339.     }

  340. }