StartStopEventsTrigger.java

  1. /* Copyright 2002-2025 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.EventDetector;
  27. import org.orekit.propagation.events.FieldEventDetector;
  28. import org.orekit.propagation.events.handlers.EventHandler;
  29. import org.orekit.propagation.events.handlers.FieldEventHandler;
  30. import org.orekit.time.AbsoluteDate;
  31. import org.orekit.time.FieldAbsoluteDate;

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

  48.     /** Start detector. */
  49.     private final ManeuverTriggerDetector<A> startDetector;

  50.     /** Stop detector. */
  51.     private final ManeuverTriggerDetector<O> stopDetector;

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

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

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

  74.         this.startDetector = new ManeuverTriggerDetector<>(prototypeStartDetector, new StartHandler());
  75.         this.stopDetector  = new ManeuverTriggerDetector<>(prototypeStopDetector, new StopHandler());
  76.         this.cachedStart   = new HashMap<>();
  77.         this.cachedStop    = new HashMap<>();

  78.     }

  79.     /**
  80.      * Getter for the firing start detector.
  81.      * @return firing start detector
  82.      */
  83.     public A getStartDetector() {
  84.         return startDetector.getDetector();
  85.     }

  86.     /**
  87.      * Getter for the firing stop detector.
  88.      * @return firing stop detector
  89.      */
  90.     public O getStopDetector() {
  91.         return stopDetector.getDetector();
  92.     }

  93.     /** {@inheritDoc} */
  94.     @Override
  95.     public void init(final SpacecraftState initialState, final AbsoluteDate target) {
  96.         startDetector.init(initialState, target);
  97.         stopDetector.init(initialState, target);
  98.         super.init(initialState, target);
  99.     }

  100.     /** {@inheritDoc} */
  101.     @Override
  102.     protected boolean isFiringOnInitialState(final SpacecraftState initialState, final boolean isForward) {

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

  140.     }

  141.     /** {@inheritDoc} */
  142.     @Override
  143.     public Stream<EventDetector> getEventDetectors() {
  144.         return Stream.of(startDetector, stopDetector);
  145.     }

  146.     /** {@inheritDoc} */
  147.     @Override
  148.     public <S extends CalculusFieldElement<S>> Stream<FieldEventDetector<S>> getFieldEventDetectors(final Field<S> field) {

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

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

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

  164.     }

  165.     /** Convert a detector and set up new handler.
  166.      * <p>
  167.      * This method is not inlined in {@link #getFieldEventDetectors(Field)} because the
  168.      * parameterized types confuses the Java compiler.
  169.      * </p>
  170.      * @param field field to which the state belongs
  171.      * @param <D> type of the event detector
  172.      * @param <S> type of the field elements
  173.      * @return converted firing intervals detector
  174.      */
  175.     private <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>> FieldManeuverTriggerDetector<S, D> convertAndSetUpStartHandler(final Field<S> field) {
  176.         final D converted = convertStartDetector(field, startDetector.getDetector());
  177.         return new FieldManeuverTriggerDetector<>(converted, new FieldStartHandler<>());
  178.     }

  179.     /** Convert a detector and set up new handler.
  180.      * <p>
  181.      * This method is not inlined in {@link #getFieldEventDetectors(Field)} because the
  182.      * parameterized types confuses the Java compiler.
  183.      * </p>
  184.      * @param field field to which the state belongs
  185.      * @param <D> type of the event detector
  186.      * @param <S> type of the field elements
  187.      * @return converted firing intervals detector
  188.      */
  189.     private <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>> FieldManeuverTriggerDetector<S, D> convertAndSetUpStopHandler(final Field<S> field) {
  190.         final D converted = convertStopDetector(field, stopDetector.getDetector());
  191.         return new FieldManeuverTriggerDetector<>(converted, new FieldStopHandler<>());
  192.     }

  193.     /** Convert a primitive firing start detector into a field firing start detector.
  194.      * <p>
  195.      * The {@link org.orekit.propagation.events.FieldEventDetectionSettings} must be set up in conformance with the
  196.      * non-field detector.
  197.      * </p>
  198.      * <p>
  199.      * A skeleton implementation of this method to convert some {@code XyzDetector} into {@code FieldXyzDetector},
  200.      * considering these detectors have a withDetectionSettings method and are created from a date and a number parameter is:
  201.      * </p>
  202.      * <pre>{@code
  203.      *     protected <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>>
  204.      *         D convertStartDetector(final Field<S> field, final XyzDetector detector) {
  205.      *
  206.      *         final FieldAbsoluteDate<S> date  = new FieldAbsoluteDate<>(field, detector.getDate());
  207.      *         final S                    param = field.getZero().newInstance(detector.getParam());
  208.      *
  209.      *         final D converted = (D) new FieldXyzDetector<>(date, param)
  210.      *         .withDetectionSettings(field, detector.getDetectionSettings());
  211.      *         return converted;
  212.      *
  213.      *     }
  214.      * }
  215.      * </pre>
  216.      * @param field field to which the state belongs
  217.      * @param detector primitive firing start detector to convert
  218.      * @param <D> type of the event detector
  219.      * @param <S> type of the field elements
  220.      * @return converted firing start detector
  221.      */
  222.     protected abstract <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>> D
  223.         convertStartDetector(Field<S> field, A detector);

  224.     /** Convert a primitive firing stop detector into a field firing stop detector.
  225.      * <p>
  226.      * The {@link org.orekit.propagation.events.FieldEventDetectionSettings} must be set up in conformance with the
  227.      * non-field detector.
  228.      * </p>
  229.      * <p>
  230.      * A skeleton implementation of this method to convert some {@code XyzDetector} into {@code FieldXyzDetector},
  231.      * considering these detectors have a withDetectionSettings method and are created from a date and a number parameter is:
  232.      * </p>
  233.      * <pre>{@code
  234.      *     protected <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>>
  235.      *         D convertEndDetector(final Field<S> field, final XyzDetector detector) {
  236.      *
  237.      *         final FieldAbsoluteDate<S> date  = new FieldAbsoluteDate<>(field, detector.getDate());
  238.      *         final S                    param = field.getZero().newInstance(detector.getParam());
  239.      *
  240.      *         final D converted = (D) new FieldXyzDetector<>(date, param)
  241.      *         .withDetectionSettings(field, detector.getDetectionSettings());
  242.      *         return converted;
  243.      *
  244.      *     }
  245.      * }
  246.      * </pre>
  247.      * @param field field to which the state belongs
  248.      * @param detector primitive firing stop detector to convert
  249.      * @param <D> type of the event detector
  250.      * @param <S> type of the field elements
  251.      * @return converted firing stop detector
  252.      */
  253.     protected abstract <D extends FieldEventDetector<S>, S extends CalculusFieldElement<S>> D
  254.         convertStopDetector(Field<S> field, O detector);

  255.     /** Local handler for start triggers. */
  256.     private class StartHandler implements EventHandler {

  257.         /** Propagation direction. */
  258.         private boolean forward;

  259.         /** {@inheritDoc} */
  260.         @Override
  261.         public void init(final SpacecraftState initialState, final AbsoluteDate target, final EventDetector detector) {
  262.             forward = target.isAfterOrEqualTo(initialState);
  263.             initializeResetters(initialState, target);
  264.         }

  265.         /** {@inheritDoc} */
  266.         @Override
  267.         public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
  268.             if (increasing) {
  269.                 // the event is meaningful for maneuver firing
  270.                 if (forward) {
  271.                     getFirings().addValidAfter(true, s.getDate(), false);
  272.                 } else {
  273.                     getFirings().addValidBefore(false, s.getDate(), false);
  274.                 }
  275.                 notifyResetters(s, true);
  276.                 return Action.RESET_STATE;
  277.             } else {
  278.                 // the event is not meaningful for maneuver firing
  279.                 return Action.CONTINUE;
  280.             }
  281.         }

  282.         /** {@inheritDoc} */
  283.         @Override
  284.         public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
  285.             return applyResetters(oldState);
  286.         }

  287.     }

  288.     /** Local handler for stop triggers. */
  289.     private class StopHandler implements EventHandler {

  290.         /** Propagation direction. */
  291.         private boolean forward;

  292.         /** {@inheritDoc} */
  293.         @Override
  294.         public void init(final SpacecraftState initialState, final AbsoluteDate target, final EventDetector detector) {
  295.             forward = target.isAfterOrEqualTo(initialState);
  296.             initializeResetters(initialState, target);
  297.         }

  298.         /** {@inheritDoc} */
  299.         @Override
  300.         public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
  301.             if (increasing) {
  302.                 // the event is meaningful for maneuver firing
  303.                 if (forward) {
  304.                     getFirings().addValidAfter(false, s.getDate(), false);
  305.                 } else {
  306.                     getFirings().addValidBefore(true, s.getDate(), false);
  307.                 }
  308.                 notifyResetters(s, false);
  309.                 return Action.RESET_STATE;
  310.             } else {
  311.                 // the event is not meaningful for maneuver firing
  312.                 return Action.CONTINUE;
  313.             }
  314.         }

  315.         /** {@inheritDoc} */
  316.         @Override
  317.         public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
  318.             return applyResetters(oldState);
  319.         }

  320.     }

  321.     /** Local handler for start triggers.
  322.      * @param <S> type of the field elements
  323.      */
  324.     private class FieldStartHandler<S extends CalculusFieldElement<S>> implements FieldEventHandler<S> {

  325.         /** Propagation direction. */
  326.         private boolean forward;

  327.         /** {@inheritDoc} */
  328.         @Override
  329.         public void init(final FieldSpacecraftState<S> initialState,
  330.                          final FieldAbsoluteDate<S> target,
  331.                          final FieldEventDetector<S> detector) {
  332.             forward = target.isAfterOrEqualTo(initialState);
  333.             initializeResetters(initialState, target);
  334.         }

  335.         /** {@inheritDoc} */
  336.         @Override
  337.         public Action eventOccurred(final FieldSpacecraftState<S> s, final FieldEventDetector<S> detector, final boolean increasing) {
  338.             if (increasing) {
  339.                 // the event is meaningful for maneuver firing
  340.                 if (forward) {
  341.                     getFirings().addValidAfter(true, s.getDate().toAbsoluteDate(), false);
  342.                 } else {
  343.                     getFirings().addValidBefore(false, s.getDate().toAbsoluteDate(), false);
  344.                 }
  345.                 notifyResetters(s, true);
  346.                 return Action.RESET_STATE;
  347.             } else {
  348.                 // the event is not meaningful for maneuver firing
  349.                 return Action.CONTINUE;
  350.             }
  351.         }

  352.         /** {@inheritDoc} */
  353.         @Override
  354.         public FieldSpacecraftState<S> resetState(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
  355.             return applyResetters(oldState);
  356.         }

  357.     }

  358.     /** Local handler for stop triggers.
  359.      * @param <S> type of the field elements
  360.      */
  361.     private class FieldStopHandler<S extends CalculusFieldElement<S>> implements FieldEventHandler<S> {

  362.         /** Propagation direction. */
  363.         private boolean forward;

  364.         /** {@inheritDoc} */
  365.         @Override
  366.         public void init(final FieldSpacecraftState<S> initialState,
  367.                          final FieldAbsoluteDate<S> target,
  368.                          final FieldEventDetector<S> detector) {
  369.             forward = target.isAfterOrEqualTo(initialState);
  370.             initializeResetters(initialState, target);
  371.         }

  372.         /** {@inheritDoc} */
  373.         @Override
  374.         public Action eventOccurred(final FieldSpacecraftState<S> s, final FieldEventDetector<S> detector, final boolean increasing) {
  375.             if (increasing) {
  376.                 // the event is meaningful for maneuver firing
  377.                 if (forward) {
  378.                     getFirings().addValidAfter(false, s.getDate().toAbsoluteDate(), false);
  379.                 } else {
  380.                     getFirings().addValidBefore(true, s.getDate().toAbsoluteDate(), false);
  381.                 }
  382.                 notifyResetters(s, false);
  383.                 return Action.RESET_STATE;
  384.             } else {
  385.                 // the event is not meaningful for maneuver firing
  386.                 return Action.CONTINUE;
  387.             }
  388.         }

  389.         /** {@inheritDoc} */
  390.         @Override
  391.         public FieldSpacecraftState<S> resetState(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
  392.             return applyResetters(oldState);
  393.         }

  394.     }

  395. }