PropagatorsParallelizer.java

  1. /* Copyright 2002-2018 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.errors.OrekitInternalError;
  31. import org.orekit.propagation.sampling.MultiSatStepHandler;
  32. import org.orekit.propagation.sampling.OrekitStepHandler;
  33. import org.orekit.propagation.sampling.OrekitStepInterpolator;
  34. import org.orekit.time.AbsoluteDate;
  35. import org.orekit.time.TimeStamped;

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

  99. public class PropagatorsParallelizer {

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

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

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

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

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

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

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

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

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

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

  165.         // main loop
  166.         AbsoluteDate previousDate = start;
  167.         globalHandler.init(initialStates, target);
  168.         for (boolean isLast = false; !isLast;) {

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

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

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

  188.             // handle all states at once
  189.             globalHandler.handleStep(restricted, isLast);

  190.             if (!isLast) {
  191.                 // advance one step
  192.                 stepHandlingParameters.set(selected,
  193.                                            getParameters(selected, futures.get(selected), shpQueues.get(selected)));
  194.             }

  195.             previousDate = selectedStepEnd;

  196.         }

  197.         // stop all remaining propagators
  198.         executorService.shutdownNow();

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

  204.                 // sort out if exception was intentional or not
  205.                 manageException(e);

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

  209.             }
  210.         }

  211.         return finalStates;

  212.     }

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

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

  259.     /** Local exception to stop propagators. */
  260.     private static class PropagatorStoppingException extends OrekitException {

  261.         /** Serializable UID.*/
  262.         private static final long serialVersionUID = 20170629L;

  263.         /** Simple constructor.
  264.          * @param ie interruption exception
  265.          */
  266.         PropagatorStoppingException(final InterruptedException ie) {
  267.             super(ie, LocalizedCoreFormats.SIMPLE_MESSAGE, ie.getLocalizedMessage());
  268.         }

  269.     }

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

  272.         /** Global handler. */
  273.         private final MultiSatStepHandler globalHandler;

  274.         /** Simple constructor.
  275.          * @param globalHandler global handler to call
  276.          */
  277.         SinglePropagatorHandler(final MultiSatStepHandler globalHandler) {
  278.             this.globalHandler = globalHandler;
  279.         }


  280.         /** {@inheritDoc} */
  281.         @Override
  282.         public void init(final SpacecraftState s0, final AbsoluteDate t) throws OrekitException {
  283.             globalHandler.init(Collections.singletonList(s0), t);
  284.         }

  285.         /** {@inheritDoc} */
  286.         @Override
  287.         public void handleStep(final OrekitStepInterpolator interpolator, final boolean isLast)
  288.             throws OrekitException {
  289.             globalHandler.handleStep(Collections.singletonList(interpolator), isLast);
  290.         }

  291.     }

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

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

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

  298.         /** Simple constructor.
  299.          * @param initQueue queuefor passing initial state
  300.          * @param shpQueue queue for passing step handling parameters.
  301.          */
  302.         MultiplePropagatorsHandler(final SynchronousQueue<SpacecraftState> initQueue,
  303.                                    final SynchronousQueue<StepHandlingParameters> shpQueue) {
  304.             this.initQueue = initQueue;
  305.             this.shpQueue  = shpQueue;
  306.         }


  307.         /** {@inheritDoc} */
  308.         @Override
  309.         public void init(final SpacecraftState s0, final AbsoluteDate t) throws OrekitException {
  310.             try {
  311.                 initQueue.put(s0);
  312.             } catch (InterruptedException ie) {
  313.                 // use a dedicated exception to stop thread almost gracefully
  314.                 throw new PropagatorStoppingException(ie);
  315.             }
  316.         }

  317.         /** {@inheritDoc} */
  318.         @Override
  319.         public void handleStep(final OrekitStepInterpolator interpolator, final boolean isLast)
  320.                         throws OrekitException {
  321.             try {
  322.                 shpQueue.put(new StepHandlingParameters(interpolator, isLast));
  323.             } catch (InterruptedException ie) {
  324.                 // use a dedicated exception to stop thread almost gracefully
  325.                 throw new PropagatorStoppingException(ie);
  326.             }
  327.         }

  328.     }

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

  331.         /** Interpolator set up for the current step. */
  332.         private final OrekitStepInterpolator interpolator;

  333.         /** Indicator for last step. */
  334.         private final boolean isLast;

  335.         /** Simple constructor.
  336.          * @param interpolator interpolator set up for the current step
  337.          * @param isLast if true, this is the last integration step
  338.          */
  339.         StepHandlingParameters(final OrekitStepInterpolator interpolator, final boolean isLast) {
  340.             this.interpolator = interpolator;
  341.             this.isLast       = isLast;
  342.         }

  343.         /** {@inheritDoc} */
  344.         @Override
  345.         public AbsoluteDate getDate() {
  346.             try {
  347.                 return interpolator.getCurrentState().getDate();
  348.             } catch (OrekitException oe) {
  349.                 // this should never happen
  350.                 throw new OrekitInternalError(oe);
  351.             }
  352.         }

  353.     }

  354. }