StartStopEventsTrigger.java

  1. /* Copyright 2002-2024 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.forces.maneuvers.trigger;

  18. import java.util.HashMap;
  19. import java.util.Map;
  20. import java.util.stream.Stream;

  21. import org.hipparchus.CalculusFieldElement;
  22. import org.hipparchus.Field;
  23. import org.hipparchus.ode.events.Action;
  24. import org.orekit.propagation.FieldSpacecraftState;
  25. import org.orekit.propagation.SpacecraftState;
  26. import org.orekit.propagation.events.AbstractDetector;
  27. import org.orekit.propagation.events.EventDetector;
  28. import org.orekit.propagation.events.FieldAbstractDetector;
  29. import org.orekit.propagation.events.FieldAdaptableInterval;
  30. import org.orekit.propagation.events.FieldEventDetector;
  31. import org.orekit.propagation.events.handlers.EventHandler;
  32. import org.orekit.propagation.events.handlers.FieldEventHandler;
  33. import org.orekit.time.AbsoluteDate;
  34. import org.orekit.time.FieldAbsoluteDate;

  35. /**
  36.  * Maneuver triggers based on a pair of event detectors that defines firing start and stop.
  37.  * <p>
  38.  * The thruster starts firing when the start detector becomes
  39.  * positive. The thruster stops firing when the stop detector becomes positive.
  40.  * The 2 detectors should not be positive at the same time. A date detector is
  41.  * not suited as it does not delimit an interval. They can be both negative at
  42.  * the same time.
  43.  * </p>
  44.  * @param <A> type of the start detector
  45.  * @param <O> type of the stop detector
  46.  * @see IntervalEventTrigger
  47.  * @author Luc Maisonobe
  48.  * @since 11.1
  49.  */
  50. public abstract class StartStopEventsTrigger<A extends AbstractDetector<A>, O extends AbstractDetector<O>> extends AbstractManeuverTriggers {

  51.     /** Start detector. */
  52.     private final A startDetector;

  53.     /** Stop detector. */
  54.     private final O stopDetector;

  55.     /** Cached field-based start detectors. */
  56.     private final transient Map<Field<? extends CalculusFieldElement<?>>, FieldEventDetector<? extends CalculusFieldElement<?>>> cachedStart;

  57.     /** Cached field-based stop detectors. */
  58.     private final transient Map<Field<? extends CalculusFieldElement<?>>, FieldEventDetector<? extends CalculusFieldElement<?>>> cachedStop;

  59.     /** Simple constructor.
  60.      * <p>
  61.      * Note that the {@code startDetector} and {@code stopDetector} passed as an argument are used only
  62.      * as a <em>prototypes</em> from which new detectors will be built using their
  63.      * {@link AbstractDetector#withHandler(EventHandler) withHandler} methods to
  64.      * set up internal handlers. The original event handlers from the prototype
  65.      * will be <em>ignored</em> and never called.
  66.      * </p>
  67.      * <p>
  68.      * If the trigger is used in a {@link org.orekit.propagation.FieldPropagator field-based propagation},
  69.      * the detector will be automatically converted to a field equivalent. Beware however that the
  70.      * {@link FieldEventHandler#eventOccurred(FieldSpacecraftState, FieldEventDetector, boolean) eventOccurred}
  71.      * of the converted propagator <em>will</em> call the method with the same name in the prototype
  72.      * detector, in order to get the correct return value.
  73.      * </p>
  74.      * @param prototypeStartDetector prototype detector for firing start
  75.      * @param prototypeStopDetector prototype detector for firing stop
  76.      */
  77.     protected StartStopEventsTrigger(final A prototypeStartDetector, final O prototypeStopDetector) {

  78.         this.startDetector = prototypeStartDetector.withHandler(new StartHandler());
  79.         this.stopDetector  = prototypeStopDetector.withHandler(new StopHandler());
  80.         this.cachedStart   = new HashMap<>();
  81.         this.cachedStop    = new HashMap<>();

  82.     }

  83.     /**
  84.      * Getter for the firing start detector.
  85.      * @return firing start detector
  86.      */
  87.     public A getStartDetector() {
  88.         return startDetector;
  89.     }

  90.     /**
  91.      * Getter for the firing stop detector.
  92.      * @return firing stop detector
  93.      */
  94.     public O getStopDetector() {
  95.         return stopDetector;
  96.     }

  97.     /** {@inheritDoc} */
  98.     @Override
  99.     public void init(final SpacecraftState initialState, final AbsoluteDate target) {
  100.         startDetector.init(initialState, target);
  101.         stopDetector.init(initialState, target);
  102.         super.init(initialState, target);
  103.     }

  104.     /** {@inheritDoc} */
  105.     @Override
  106.     protected boolean isFiringOnInitialState(final SpacecraftState initialState, final boolean isForward) {

  107.         final double startG = startDetector.g(initialState);
  108.         if (startG == 0) {
  109.             final boolean increasing = startDetector.g(initialState.shiftedBy(2 * startDetector.getThreshold())) > 0;
  110.             if (increasing) {
  111.                 // we are at maneuver start
  112.                 notifyResetters(initialState, true);
  113.                 // if propagating forward, we start firing
  114.                 return isForward;
  115.             } else {
  116.                 // not a meaningful crossing
  117.                 return false;
  118.             }
  119.         } else if (startG < 0) {
  120.             // we are before start
  121.             return false;
  122.         } else {
  123.             // we are after start
  124.             final double stopG = stopDetector.g(initialState);
  125.             if (stopG == 0) {
  126.                 final boolean increasing = stopDetector.g(initialState.shiftedBy(2 * stopDetector.getThreshold())) > 0;
  127.                 if (increasing) {
  128.                     // we are at maneuver end
  129.                     notifyResetters(initialState, false);
  130.                     // if propagating backward, we start firing
  131.                     return !isForward;
  132.                 } else {
  133.                     // not a meaningful crossing
  134.                     return false;
  135.                 }
  136.             } else if (stopG > 0) {
  137.                 // we are after stop
  138.                 return false;
  139.             } else {
  140.                 // we are between start and stop
  141.                 return true;
  142.             }
  143.         }

  144.     }

  145.     /** {@inheritDoc} */
  146.     @Override
  147.     public Stream<EventDetector> getEventDetectors() {
  148.         return Stream.of(startDetector, stopDetector);
  149.     }

  150.     /** {@inheritDoc} */
  151.     @Override
  152.     public <S extends CalculusFieldElement<S>> Stream<FieldEventDetector<S>> getFieldEventDetectors(final Field<S> field) {

  153.         // get the field version of the start detector
  154.         @SuppressWarnings("unchecked")
  155.         FieldEventDetector<S> fStart = (FieldEventDetector<S>) cachedStart.get(field);
  156.         if (fStart == null) {
  157.             fStart = convertAndSetUpStartHandler(field);
  158.             cachedStart.put(field, fStart);
  159.         }

  160.         // get the field version of the stop detector
  161.         @SuppressWarnings("unchecked")
  162.         FieldEventDetector<S> fStop = (FieldEventDetector<S>) cachedStop.get(field);
  163.         if (fStop == null) {
  164.             fStop = convertAndSetUpStopHandler(field);
  165.             cachedStop.put(field, fStop);
  166.         }

  167.         return Stream.of(fStart, fStop);

  168.     }

  169.     /** Convert a detector and set up new handler.
  170.      * <p>
  171.      * This method is not inlined in {@link #getFieldEventDetectors(Field)} because the
  172.      * parameterized types confuses the Java compiler.
  173.      * </p>
  174.      * @param field field to which the state belongs
  175.      * @param <D> type of the event detector
  176.      * @param <S> type of the field elements
  177.      * @return converted firing intervals detector
  178.      */
  179.     private <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>> D convertAndSetUpStartHandler(final Field<S> field) {
  180.         final FieldAbstractDetector<D, S> converted = convertStartDetector(field, startDetector);
  181.         final FieldAdaptableInterval<S>   maxCheck  = s -> startDetector.getMaxCheckInterval().currentInterval(s.toSpacecraftState());
  182.         return converted.
  183.                withMaxCheck(maxCheck).
  184.                withThreshold(field.getZero().newInstance(startDetector.getThreshold())).
  185.                withHandler(new FieldStartHandler<>());
  186.     }

  187.     /** Convert a detector and set up new handler.
  188.      * <p>
  189.      * This method is not inlined in {@link #getFieldEventDetectors(Field)} because the
  190.      * parameterized types confuses the Java compiler.
  191.      * </p>
  192.      * @param field field to which the state belongs
  193.      * @param <D> type of the event detector
  194.      * @param <S> type of the field elements
  195.      * @return converted firing intervals detector
  196.      */
  197.     private <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>> D convertAndSetUpStopHandler(final Field<S> field) {
  198.         final FieldAbstractDetector<D, S> converted = convertStopDetector(field, stopDetector);
  199.         final FieldAdaptableInterval<S>   maxCheck  = s -> stopDetector.getMaxCheckInterval().currentInterval(s.toSpacecraftState());
  200.         return converted.
  201.                withMaxCheck(maxCheck).
  202.                withThreshold(field.getZero().newInstance(stopDetector.getThreshold())).
  203.                withHandler(new FieldStopHandler<>());
  204.     }

  205.     /** Convert a primitive firing start detector into a field firing start detector.
  206.      * <p>
  207.      * There is not need to set up {@link FieldAbstractDetector#withMaxCheck(FieldAdaptableInterval) withMaxCheck},
  208.      * {@link FieldAbstractDetector#withThreshold(CalculusFieldElement) withThreshold}, or
  209.      * {@link FieldAbstractDetector#withHandler(org.orekit.propagation.events.handlers.FieldEventHandler) withHandler}
  210.      * in the converted detector, this will be done by caller.
  211.      * </p>
  212.      * <p>
  213.      * A skeleton implementation of this method to convert some {@code XyzDetector} into {@code FieldXyzDetector},
  214.      * considering these detectors are created from a date and a number parameter is:
  215.      * </p>
  216.      * <pre>{@code
  217.      *     protected <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>>
  218.      *         FieldAbstractDetector<D, S> convertStartDetector(final Field<S> field, final XyzDetector detector) {
  219.      *
  220.      *         final FieldAbsoluteDate<S> date  = new FieldAbsoluteDate<>(field, detector.getDate());
  221.      *         final S                    param = field.getZero().newInstance(detector.getParam());
  222.      *
  223.      *         final FieldAbstractDetector<D, S> converted = (FieldAbstractDetector<D, S>) new FieldXyzDetector<>(date, param);
  224.      *         return converted;
  225.      *
  226.      *     }
  227.      * }
  228.      * </pre>
  229.      * @param field field to which the state belongs
  230.      * @param detector primitive firing start detector to convert
  231.      * @param <D> type of the event detector
  232.      * @param <S> type of the field elements
  233.      * @return converted firing start detector
  234.      */
  235.     protected abstract <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>> FieldAbstractDetector<D, S>
  236.         convertStartDetector(Field<S> field, A detector);

  237.     /** Convert a primitive firing stop detector into a field firing stop detector.
  238.      * <p>
  239.      * There is not need to set up {@link FieldAbstractDetector#withMaxCheck(FieldAdaptableInterval) withMaxCheck},
  240.      * {@link FieldAbstractDetector#withThreshold(CalculusFieldElement) withThreshold}, or
  241.      * {@link FieldAbstractDetector#withHandler(org.orekit.propagation.events.handlers.FieldEventHandler) withHandler}
  242.      * in the converted detector, this will be done by caller.
  243.      * </p>
  244.      * <p>
  245.      * A skeleton implementation of this method to convert some {@code XyzDetector} into {@code FieldXyzDetector},
  246.      * considering these detectors are created from a date and a number parameter is:
  247.      * </p>
  248.      * <pre>{@code
  249.      *     protected <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>>
  250.      *         FieldAbstractDetector<D, S> convertStopDetector(final Field<S> field, final XyzDetector detector) {
  251.      *
  252.      *         final FieldAbsoluteDate<S> date  = new FieldAbsoluteDate<>(field, detector.getDate());
  253.      *         final S                    param = field.getZero().newInstance(detector.getParam());
  254.      *
  255.      *         final FieldAbstractDetector<D, S> converted = (FieldAbstractDetector<D, S>) new FieldXyzDetector<>(date, param);
  256.      *         return converted;
  257.      *
  258.      *     }
  259.      * }
  260.      * </pre>
  261.      * @param field field to which the state belongs
  262.      * @param detector primitive firing stop detector to convert
  263.      * @param <D> type of the event detector
  264.      * @param <S> type of the field elements
  265.      * @return converted firing stop detector
  266.      */
  267.     protected abstract <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>> FieldAbstractDetector<D, S>
  268.         convertStopDetector(Field<S> field, O detector);

  269.     /** Local handler for start triggers. */
  270.     private class StartHandler implements EventHandler {

  271.         /** Propagation direction. */
  272.         private boolean forward;

  273.         /** {@inheritDoc} */
  274.         @Override
  275.         public void init(final SpacecraftState initialState, final AbsoluteDate target, final EventDetector detector) {
  276.             forward = target.isAfterOrEqualTo(initialState);
  277.             initializeResetters(initialState, target);
  278.         }

  279.         /** {@inheritDoc} */
  280.         @Override
  281.         public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
  282.             if (increasing) {
  283.                 // the event is meaningful for maneuver firing
  284.                 if (forward) {
  285.                     getFirings().addValidAfter(true, s.getDate(), false);
  286.                 } else {
  287.                     getFirings().addValidBefore(false, s.getDate(), false);
  288.                 }
  289.                 notifyResetters(s, true);
  290.                 return Action.RESET_STATE;
  291.             } else {
  292.                 // the event is not meaningful for maneuver firing
  293.                 return Action.CONTINUE;
  294.             }
  295.         }

  296.         /** {@inheritDoc} */
  297.         @Override
  298.         public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
  299.             return applyResetters(oldState);
  300.         }

  301.     }

  302.     /** Local handler for stop triggers. */
  303.     private class StopHandler implements EventHandler {

  304.         /** Propagation direction. */
  305.         private boolean forward;

  306.         /** {@inheritDoc} */
  307.         @Override
  308.         public void init(final SpacecraftState initialState, final AbsoluteDate target, final EventDetector detector) {
  309.             forward = target.isAfterOrEqualTo(initialState);
  310.             initializeResetters(initialState, target);
  311.         }

  312.         /** {@inheritDoc} */
  313.         @Override
  314.         public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
  315.             if (increasing) {
  316.                 // the event is meaningful for maneuver firing
  317.                 if (forward) {
  318.                     getFirings().addValidAfter(false, s.getDate(), false);
  319.                 } else {
  320.                     getFirings().addValidBefore(true, s.getDate(), false);
  321.                 }
  322.                 notifyResetters(s, false);
  323.                 return Action.RESET_STATE;
  324.             } else {
  325.                 // the event is not meaningful for maneuver firing
  326.                 return Action.CONTINUE;
  327.             }
  328.         }

  329.         /** {@inheritDoc} */
  330.         @Override
  331.         public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
  332.             return applyResetters(oldState);
  333.         }

  334.     }

  335.     /** Local handler for start triggers.
  336.      * @param <S> type of the field elements
  337.      */
  338.     private class FieldStartHandler<S extends CalculusFieldElement<S>> implements FieldEventHandler<S> {

  339.         /** Propagation direction. */
  340.         private boolean forward;

  341.         /** {@inheritDoc} */
  342.         @Override
  343.         public void init(final FieldSpacecraftState<S> initialState,
  344.                          final FieldAbsoluteDate<S> target,
  345.                          final FieldEventDetector<S> detector) {
  346.             forward = target.isAfterOrEqualTo(initialState);
  347.             initializeResetters(initialState, target);
  348.         }

  349.         /** {@inheritDoc} */
  350.         @Override
  351.         public Action eventOccurred(final FieldSpacecraftState<S> s, final FieldEventDetector<S> detector, final boolean increasing) {
  352.             if (increasing) {
  353.                 // the event is meaningful for maneuver firing
  354.                 if (forward) {
  355.                     getFirings().addValidAfter(true, s.getDate().toAbsoluteDate(), false);
  356.                 } else {
  357.                     getFirings().addValidBefore(false, s.getDate().toAbsoluteDate(), false);
  358.                 }
  359.                 notifyResetters(s, true);
  360.                 return Action.RESET_STATE;
  361.             } else {
  362.                 // the event is not meaningful for maneuver firing
  363.                 return Action.CONTINUE;
  364.             }
  365.         }

  366.         /** {@inheritDoc} */
  367.         @Override
  368.         public FieldSpacecraftState<S> resetState(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
  369.             return applyResetters(oldState);
  370.         }

  371.     }

  372.     /** Local handler for stop triggers.
  373.      * @param <S> type of the field elements
  374.      */
  375.     private class FieldStopHandler<S extends CalculusFieldElement<S>> implements FieldEventHandler<S> {

  376.         /** Propagation direction. */
  377.         private boolean forward;

  378.         /** {@inheritDoc} */
  379.         @Override
  380.         public void init(final FieldSpacecraftState<S> initialState,
  381.                          final FieldAbsoluteDate<S> target,
  382.                          final FieldEventDetector<S> detector) {
  383.             forward = target.isAfterOrEqualTo(initialState);
  384.             initializeResetters(initialState, target);
  385.         }

  386.         /** {@inheritDoc} */
  387.         @Override
  388.         public Action eventOccurred(final FieldSpacecraftState<S> s, final FieldEventDetector<S> detector, final boolean increasing) {
  389.             if (increasing) {
  390.                 // the event is meaningful for maneuver firing
  391.                 if (forward) {
  392.                     getFirings().addValidAfter(false, s.getDate().toAbsoluteDate(), false);
  393.                 } else {
  394.                     getFirings().addValidBefore(true, s.getDate().toAbsoluteDate(), false);
  395.                 }
  396.                 notifyResetters(s, false);
  397.                 return Action.RESET_STATE;
  398.             } else {
  399.                 // the event is not meaningful for maneuver firing
  400.                 return Action.CONTINUE;
  401.             }
  402.         }

  403.         /** {@inheritDoc} */
  404.         @Override
  405.         public FieldSpacecraftState<S> resetState(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
  406.             return applyResetters(oldState);
  407.         }

  408.     }

  409. }