PropagatorsParallelizer.java

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

  97. public class PropagatorsParallelizer {

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

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

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

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

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

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

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

  131.         final double sign = FastMath.copySign(1.0, target.durationFrom(start));

  132.         // start all propagators in concurrent threads
  133.         final ExecutorService            executorService = Executors.newFixedThreadPool(propagators.size());
  134.         final List<PropagatorMonitoring> monitors        = new ArrayList<>(propagators.size());
  135.         for (final Propagator propagator : propagators) {
  136.             final PropagatorMonitoring monitor = new PropagatorMonitoring(propagator, start, target, executorService);
  137.             monitor.waitFirstStepCompletion();
  138.             monitors.add(monitor);
  139.         }

  140.         // main loop
  141.         AbsoluteDate previousDate = start;
  142.         final List<SpacecraftState> initialStates = new ArrayList<>(monitors.size());
  143.         for (final PropagatorMonitoring monitor : monitors) {
  144.             initialStates.add(monitor.parameters.initialState);
  145.         }
  146.         globalHandler.init(initialStates, target);
  147.         for (boolean isLast = false; !isLast;) {

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

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

  165.             // handle all states at once
  166.             final List<OrekitStepInterpolator> interpolators = new ArrayList<>(monitors.size());
  167.             for (final PropagatorMonitoring monitor : monitors) {
  168.                 interpolators.add(monitor.restricted);
  169.             }
  170.             globalHandler.handleStep(interpolators);

  171.             if (selected.parameters.finalState == null) {
  172.                 // step handler can still provide new results
  173.                 // this will wait until either handleStep or finish are called
  174.                 selected.retrieveNextParameters();
  175.             } else {
  176.                 // this was the last step
  177.                 isLast = true;
  178.                 /* For NumericalPropagators :
  179.                  * After reaching the finalState with the selected monitor,
  180.                  * we need to do the step with all remaining monitors to reach the target time.
  181.                  * This also triggers the StoringStepHandler, producing ephemeris.
  182.                  */
  183.                 for (PropagatorMonitoring monitor : monitors) {
  184.                     if (monitor != selected) {
  185.                         monitor.retrieveNextParameters();
  186.                     }
  187.                 }
  188.             }

  189.             previousDate = selectedStepEnd;

  190.         }

  191.         // stop all remaining propagators
  192.         executorService.shutdownNow();

  193.         // extract the final states
  194.         final List<SpacecraftState> finalStates = new ArrayList<>(monitors.size());
  195.         for (PropagatorMonitoring monitor : monitors) {
  196.             try {
  197.                 finalStates.add(monitor.future.get());
  198.             } catch (InterruptedException | ExecutionException e) {

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

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

  204.             }
  205.         }

  206.         globalHandler.finish(finalStates);

  207.         return finalStates;

  208.     }

  209.     /** Local exception to stop propagators. */
  210.     private static class PropagatorStoppingException extends OrekitException {

  211.         /** Serializable UID.*/
  212.         private static final long serialVersionUID = 20170629L;

  213.         /** Simple constructor.
  214.          * @param ie interruption exception
  215.          */
  216.         PropagatorStoppingException(final InterruptedException ie) {
  217.             super(ie, LocalizedCoreFormats.SIMPLE_MESSAGE, ie.getLocalizedMessage());
  218.         }

  219.     }

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

  222.         /** Global handler. */
  223.         private final MultiSatStepHandler globalHandler;

  224.         /** Simple constructor.
  225.          * @param globalHandler global handler to call
  226.          */
  227.         SinglePropagatorHandler(final MultiSatStepHandler globalHandler) {
  228.             this.globalHandler = globalHandler;
  229.         }


  230.         /** {@inheritDoc} */
  231.         @Override
  232.         public void init(final SpacecraftState s0, final AbsoluteDate t) {
  233.             globalHandler.init(Collections.singletonList(s0), t);
  234.         }

  235.         /** {@inheritDoc} */
  236.         @Override
  237.         public void handleStep(final OrekitStepInterpolator interpolator) {
  238.             globalHandler.handleStep(Collections.singletonList(interpolator));
  239.         }

  240.         /** {@inheritDoc} */
  241.         @Override
  242.         public void finish(final SpacecraftState finalState) {
  243.             globalHandler.finish(Collections.singletonList(finalState));
  244.         }

  245.     }

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

  248.         /** Previous container handed off. */
  249.         private ParametersContainer previous;

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

  252.         /** Simple constructor.
  253.          * @param queue queue for passing step handling parameters
  254.          */
  255.         MultiplePropagatorsHandler(final SynchronousQueue<ParametersContainer> queue) {
  256.             this.previous = new ParametersContainer(null, null, null);
  257.             this.queue    = queue;
  258.         }

  259.         /** Hand off container to parallelizer.
  260.          * @param container parameters container to hand-off
  261.          */
  262.         private void handOff(final ParametersContainer container) {
  263.             try {
  264.                 previous = container;
  265.                 queue.put(previous);
  266.             } catch (InterruptedException ie) {
  267.                 // use a dedicated exception to stop thread almost gracefully
  268.                 throw new PropagatorStoppingException(ie);
  269.             }
  270.         }

  271.         /** {@inheritDoc} */
  272.         @Override
  273.         public void init(final SpacecraftState s0, final AbsoluteDate t) {
  274.             handOff(new ParametersContainer(s0, null, null));
  275.         }

  276.         /** {@inheritDoc} */
  277.         @Override
  278.         public void handleStep(final OrekitStepInterpolator interpolator) {
  279.             handOff(new ParametersContainer(previous.initialState, interpolator, null));
  280.         }

  281.         /** {@inheritDoc} */
  282.         @Override
  283.         public void finish(final SpacecraftState finalState) {
  284.             handOff(new ParametersContainer(previous.initialState, previous.interpolator, finalState));
  285.         }

  286.     }

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

  289.         /** Initial state. */
  290.         private final SpacecraftState initialState;

  291.         /** Interpolator set up for last seen step. */
  292.         private final OrekitStepInterpolator interpolator;

  293.         /** Final state. */
  294.         private final SpacecraftState finalState;

  295.         /** Simple constructor.
  296.          * @param initialState initial state
  297.          * @param interpolator interpolator set up for last seen step
  298.          * @param finalState final state
  299.          */
  300.         ParametersContainer(final SpacecraftState initialState,
  301.                             final OrekitStepInterpolator interpolator,
  302.                             final SpacecraftState finalState) {
  303.             this.initialState = initialState;
  304.             this.interpolator = interpolator;
  305.             this.finalState   = finalState;
  306.         }

  307.     }

  308.     /** Container for propagator monitoring. */
  309.     private static class PropagatorMonitoring {

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

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

  314.         /** Last step handler parameters received. */
  315.         private ParametersContainer parameters;

  316.         /** Interpolator restricted to time range shared with other propagators. */
  317.         private OrekitStepInterpolator restricted;

  318.         /** Simple constructor.
  319.          * @param propagator managed propagator
  320.          * @param start start date from which orbit state should be propagated
  321.          * @param target target date to which orbit state should be propagated
  322.          * @param executorService service for running propagator
  323.          */
  324.         PropagatorMonitoring(final Propagator propagator, final AbsoluteDate start, final AbsoluteDate target,
  325.                              final ExecutorService executorService) {

  326.             // set up queue for handing off step handler parameters synchronization
  327.             // the main thread will let underlying propagators go forward
  328.             // by consuming the step handling parameters they will put at each step
  329.             queue = new SynchronousQueue<>();
  330.             propagator.getMultiplexer().add(new MultiplePropagatorsHandler(queue));

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

  333.         }

  334.         /** Wait completion of first step.
  335.          */
  336.         public void waitFirstStepCompletion() {

  337.             // wait until both the init method and the handleStep method
  338.             // of the current propagator step handler have been called,
  339.             // thus ensuring we have one step available to compare propagators
  340.             // progress with each other
  341.             while (parameters == null || parameters.initialState == null || parameters.interpolator == null) {
  342.                 retrieveNextParameters();
  343.             }

  344.         }

  345.         /** Retrieve next step handling parameters.
  346.          */
  347.         public void retrieveNextParameters() {
  348.             try {
  349.                 ParametersContainer params = null;
  350.                 while (params == null && !future.isDone()) {
  351.                     params = queue.poll(MAX_WAIT, TimeUnit.MILLISECONDS);
  352.                     // Check to avoid loop on future not done, in the case of reached finalState.
  353.                     if (parameters != null) {
  354.                         if (parameters.finalState != null) {
  355.                             break;
  356.                         }
  357.                     }
  358.                 }
  359.                 if (params == null) {
  360.                     // call Future.get just for the side effect of retrieving the exception
  361.                     // in case the propagator ended due to an exception
  362.                     future.get();
  363.                 }
  364.                 parameters = params;
  365.             } catch (InterruptedException | ExecutionException e) {
  366.                 manageException(e);
  367.                 parameters = null;
  368.             }
  369.         }

  370.         /** Convert exceptions.
  371.          * @param exception exception caught
  372.          */
  373.         private void manageException(final Exception exception) {
  374.             if (exception.getCause() instanceof PropagatorStoppingException) {
  375.                 // this was an expected exception, we deliberately shut down the propagators
  376.                 // we therefore explicitly ignore this exception
  377.                 return;
  378.             } else if (exception.getCause() instanceof OrekitException) {
  379.                 // unwrap the original exception
  380.                 throw (OrekitException) exception.getCause();
  381.             } else {
  382.                 throw new OrekitException(exception.getCause(),
  383.                                           LocalizedCoreFormats.SIMPLE_MESSAGE, exception.getLocalizedMessage());
  384.             }
  385.         }

  386.     }

  387. }