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.FieldEventHandler;
  29. import org.orekit.time.AbsoluteDate;

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

  46.     /** Start detector. */
  47.     private final ManeuverTriggerDetector<A> startDetector;

  48.     /** Stop detector. */
  49.     private final ManeuverTriggerDetector<O> stopDetector;

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

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

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

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

  76.     }

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

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

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

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

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

  138.     }

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

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

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

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

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

  162.     }

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

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

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

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

  253.     /** Local handler for start triggers. */
  254.     private class StartHandler extends TriggerHandler {

  255.         /** {@inheritDoc} */
  256.         @Override
  257.         public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
  258.             if (increasing) {
  259.                 // the event is meaningful for maneuver firing
  260.                 if (isForward()) {
  261.                     getFirings().addValidAfter(true, s.getDate(), false);
  262.                 } else {
  263.                     getFirings().addValidBefore(false, s.getDate(), false);
  264.                 }
  265.                 notifyResetters(s, true);
  266.                 return determineAction(detector, s);
  267.             } else {
  268.                 // the event is not meaningful for maneuver firing
  269.                 return Action.CONTINUE;
  270.             }
  271.         }

  272.     }

  273.     /** Local handler for stop triggers. */
  274.     private class StopHandler extends TriggerHandler {

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

  292.     }

  293.     /** Local handler for start triggers.
  294.      * @param <S> type of the field elements
  295.      */
  296.     private class FieldStartHandler<S extends CalculusFieldElement<S>> extends FieldTriggerHandler<S> {

  297.         /** {@inheritDoc} */
  298.         @Override
  299.         public Action eventOccurred(final FieldSpacecraftState<S> s, final FieldEventDetector<S> detector, final boolean increasing) {
  300.             if (increasing) {
  301.                 // the event is meaningful for maneuver firing
  302.                 if (isForward()) {
  303.                     getFirings().addValidAfter(true, s.getDate().toAbsoluteDate(), false);
  304.                 } else {
  305.                     getFirings().addValidBefore(false, s.getDate().toAbsoluteDate(), false);
  306.                 }
  307.                 notifyResetters(s, true);
  308.                 return determineAction(detector, s);
  309.             } else {
  310.                 // the event is not meaningful for maneuver firing
  311.                 return Action.CONTINUE;
  312.             }
  313.         }

  314.     }

  315.     /** Local handler for stop triggers.
  316.      * @param <S> type of the field elements
  317.      */
  318.     private class FieldStopHandler<S extends CalculusFieldElement<S>> extends FieldTriggerHandler<S> {

  319.         /** {@inheritDoc} */
  320.         @Override
  321.         public Action eventOccurred(final FieldSpacecraftState<S> s, final FieldEventDetector<S> detector, final boolean increasing) {
  322.             if (increasing) {
  323.                 // the event is meaningful for maneuver firing
  324.                 if (isForward()) {
  325.                     getFirings().addValidAfter(false, s.getDate().toAbsoluteDate(), false);
  326.                 } else {
  327.                     getFirings().addValidBefore(true, s.getDate().toAbsoluteDate(), false);
  328.                 }
  329.                 notifyResetters(s, false);
  330.                 return determineAction(detector, s);
  331.             } else {
  332.                 // the event is not meaningful for maneuver firing
  333.                 return Action.CONTINUE;
  334.             }
  335.         }

  336.     }

  337. }