PropagatorsParallelizer.java

  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. 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 java.util.stream.Collectors;

  28. import org.hipparchus.exception.LocalizedCoreFormats;
  29. import org.hipparchus.util.FastMath;
  30. import org.orekit.errors.OrekitException;
  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. /** 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).setStepHandler(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.         // start all propagators in concurrent threads
  134.         final ExecutorService            executorService = Executors.newFixedThreadPool(propagators.size());
  135.         final List<PropagatorMonitoring> monitors        = new ArrayList<>(propagators.size());
  136.         for (final Propagator propagator : propagators) {
  137.             final PropagatorMonitoring monitor = new PropagatorMonitoring(propagator, start, target, executorService);
  138.             monitor.waitFirstStepCompletion();
  139.             monitors.add(monitor);
  140.         }

  141.         // main loop
  142.         AbsoluteDate previousDate = start;
  143.         globalHandler.init(monitors.stream().map(m -> m.parameters.initialState).collect(Collectors.toList()), target);
  144.         for (boolean isLast = false; !isLast;) {

  145.             // select the earliest ending propagator, according to propagation direction
  146.             PropagatorMonitoring selected = null;
  147.             AbsoluteDate selectedStepEnd  = null;
  148.             for (PropagatorMonitoring monitor : monitors) {
  149.                 final AbsoluteDate stepEnd = monitor.parameters.interpolator.getCurrentState().getDate();
  150.                 if (selected == null || sign * selectedStepEnd.durationFrom(stepEnd) > 0) {
  151.                     selected        = monitor;
  152.                     selectedStepEnd = stepEnd;
  153.                 }
  154.             }

  155.             // restrict steps to a common time range
  156.             for (PropagatorMonitoring monitor : monitors) {
  157.                 final OrekitStepInterpolator interpolator  = monitor.parameters.interpolator;
  158.                 final SpacecraftState        previousState = interpolator.getInterpolatedState(previousDate);
  159.                 final SpacecraftState        currentState  = interpolator.getInterpolatedState(selectedStepEnd);
  160.                 monitor.restricted                         = interpolator.restrictStep(previousState, currentState);
  161.             }

  162.             // handle all states at once
  163.             globalHandler.handleStep(monitors.stream().map(m -> m.restricted).collect(Collectors.toList()));

  164.             if (selected.parameters.finalState == null) {
  165.                 // step handler can still provide new results
  166.                 // this will wait until either handleStep or finish are called
  167.                 selected.retrieveNextParameters();
  168.             } else {
  169.                 // this was the last step
  170.                 isLast = true;
  171.             }

  172.             previousDate = selectedStepEnd;

  173.         }

  174.         // stop all remaining propagators
  175.         executorService.shutdownNow();

  176.         // extract the final states
  177.         final List<SpacecraftState> finalStates = new ArrayList<>(monitors.size());
  178.         for (PropagatorMonitoring monitor : monitors) {
  179.             try {
  180.                 finalStates.add(monitor.future.get());
  181.             } catch (InterruptedException | ExecutionException e) {

  182.                 // sort out if exception was intentional or not
  183.                 monitor.manageException(e);

  184.                 // this propagator was intentionally stopped,
  185.                 // we retrieve the final state from the last available interpolator
  186.                 finalStates.add(monitor.parameters.interpolator.getInterpolatedState(previousDate));

  187.             }
  188.         }

  189.         globalHandler.finish(finalStates);

  190.         return finalStates;

  191.     }

  192.     /** Local exception to stop propagators. */
  193.     private static class PropagatorStoppingException extends OrekitException {

  194.         /** Serializable UID.*/
  195.         private static final long serialVersionUID = 20170629L;

  196.         /** Simple constructor.
  197.          * @param ie interruption exception
  198.          */
  199.         PropagatorStoppingException(final InterruptedException ie) {
  200.             super(ie, LocalizedCoreFormats.SIMPLE_MESSAGE, ie.getLocalizedMessage());
  201.         }

  202.     }

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

  205.         /** Global handler. */
  206.         private final MultiSatStepHandler globalHandler;

  207.         /** Simple constructor.
  208.          * @param globalHandler global handler to call
  209.          */
  210.         SinglePropagatorHandler(final MultiSatStepHandler globalHandler) {
  211.             this.globalHandler = globalHandler;
  212.         }


  213.         /** {@inheritDoc} */
  214.         @Override
  215.         public void init(final SpacecraftState s0, final AbsoluteDate t) {
  216.             globalHandler.init(Collections.singletonList(s0), t);
  217.         }

  218.         /** {@inheritDoc} */
  219.         @Override
  220.         public void handleStep(final OrekitStepInterpolator interpolator) {
  221.             globalHandler.handleStep(Collections.singletonList(interpolator));
  222.         }

  223.         /** {@inheritDoc} */
  224.         @Override
  225.         public void finish(final SpacecraftState finalState) {
  226.             globalHandler.finish(Collections.singletonList(finalState));
  227.         }

  228.     }

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

  231.         /** Previous container handed off. */
  232.         private ParametersContainer previous;

  233.         /** Queue for passing step handling parameters. */
  234.         private final SynchronousQueue<ParametersContainer> queue;

  235.         /** Simple constructor.
  236.          * @param queue queue for passing step handling parameters
  237.          */
  238.         MultiplePropagatorsHandler(final SynchronousQueue<ParametersContainer> queue) {
  239.             this.previous = new ParametersContainer(null, null, null);
  240.             this.queue    = queue;
  241.         }

  242.         /** Hand off container to parallelizer.
  243.          * @param container parameters container to hand-off
  244.          */
  245.         private void handOff(final ParametersContainer container) {
  246.             try {
  247.                 previous = container;
  248.                 queue.put(previous);
  249.             } catch (InterruptedException ie) {
  250.                 // use a dedicated exception to stop thread almost gracefully
  251.                 throw new PropagatorStoppingException(ie);
  252.             }
  253.         }

  254.         /** {@inheritDoc} */
  255.         @Override
  256.         public void init(final SpacecraftState s0, final AbsoluteDate t) {
  257.             handOff(new ParametersContainer(s0, null, null));
  258.         }

  259.         /** {@inheritDoc} */
  260.         @Override
  261.         public void handleStep(final OrekitStepInterpolator interpolator) {
  262.             handOff(new ParametersContainer(previous.initialState, interpolator, null));
  263.         }

  264.         /** {@inheritDoc} */
  265.         @Override
  266.         public void finish(final SpacecraftState finalState) {
  267.             handOff(new ParametersContainer(previous.initialState, previous.interpolator, finalState));
  268.         }

  269.     }

  270.     /** Container for parameters passed by propagators to step handlers. */
  271.     private static class ParametersContainer {

  272.         /** Initial state. */
  273.         private final SpacecraftState initialState;

  274.         /** Interpolator set up for last seen step. */
  275.         private final OrekitStepInterpolator interpolator;

  276.         /** Final state. */
  277.         private final SpacecraftState finalState;

  278.         /** Simple constructor.
  279.          * @param initialState initial state
  280.          * @param interpolator interpolator set up for last seen step
  281.          * @param finalState final state
  282.          */
  283.         ParametersContainer(final SpacecraftState initialState,
  284.                             final OrekitStepInterpolator interpolator,
  285.                             final SpacecraftState finalState) {
  286.             this.initialState = initialState;
  287.             this.interpolator = interpolator;
  288.             this.finalState   = finalState;
  289.         }

  290.     }

  291.     /** Container for propagator monitoring. */
  292.     private static class PropagatorMonitoring {

  293.         /** Queue for handing off step handler parameters. */
  294.         private final SynchronousQueue<ParametersContainer> queue;

  295.         /** Future for retrieving propagation return value. */
  296.         private final Future<SpacecraftState> future;

  297.         /** Last step handler parameters received. */
  298.         private ParametersContainer parameters;

  299.         /** Interpolator restricted to time range shared with other propagators. */
  300.         private OrekitStepInterpolator restricted;

  301.         /** Simple constructor.
  302.          * @param propagator managed propagator
  303.          * @param start start date from which orbit state should be propagated
  304.          * @param target target date to which orbit state should be propagated
  305.          * @param executorService service for running propagator
  306.          */
  307.         PropagatorMonitoring(final Propagator propagator, final AbsoluteDate start, final AbsoluteDate target,
  308.                              final ExecutorService executorService) {

  309.             // set up queue for handing off step handler parameters synchronization
  310.             // the main thread will let underlying propagators go forward
  311.             // by consuming the step handling parameters they will put at each step
  312.             queue = new SynchronousQueue<>();
  313.             propagator.setStepHandler(new MultiplePropagatorsHandler(queue));

  314.             // start the propagator
  315.             future = executorService.submit(() -> propagator.propagate(start, target));

  316.         }

  317.         /** Wait completion of first step.
  318.          */
  319.         public void waitFirstStepCompletion() {

  320.             // wait until both the init method and the handleStep method
  321.             // of the current propagator step handler have been called,
  322.             // thus ensuring we have one step available to compare propagators
  323.             // progress with each other
  324.             while (parameters == null || parameters.initialState == null || parameters.interpolator == null) {
  325.                 retrieveNextParameters();
  326.             }

  327.         }

  328.         /** Retrieve next step handling parameters.
  329.          */
  330.         public void retrieveNextParameters() {
  331.             try {
  332.                 ParametersContainer params = null;
  333.                 while (params == null && !future.isDone()) {
  334.                     params = queue.poll(MAX_WAIT, TimeUnit.MILLISECONDS);
  335.                 }
  336.                 if (params == null) {
  337.                     // call Future.get just for the side effect of retrieving the exception
  338.                     // in case the propagator ended due to an exception
  339.                     future.get();
  340.                 }
  341.                 parameters = params;
  342.             } catch (InterruptedException | ExecutionException e) {
  343.                 manageException(e);
  344.                 parameters = null;
  345.             }
  346.         }

  347.         /** Convert exceptions.
  348.          * @param exception exception caught
  349.          */
  350.         private void manageException(final Exception exception) {
  351.             if (exception.getCause() instanceof PropagatorStoppingException) {
  352.                 // this was an expected exception, we deliberately shut down the propagators
  353.                 // we therefore explicitly ignore this exception
  354.                 return;
  355.             } else if (exception.getCause() instanceof OrekitException) {
  356.                 // unwrap the original exception
  357.                 throw (OrekitException) exception.getCause();
  358.             } else {
  359.                 throw new OrekitException(exception.getCause(),
  360.                                           LocalizedCoreFormats.SIMPLE_MESSAGE, exception.getLocalizedMessage());
  361.             }
  362.         }

  363.     }

  364. }