FieldEventEnablingPredicateFilter.java

  1. /* Copyright 2022-2025 Luc Maisonobe
  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.events;

  18. import java.lang.reflect.Array;
  19. import java.util.Arrays;

  20. import org.hipparchus.CalculusFieldElement;
  21. import org.hipparchus.ode.events.Action;
  22. import org.orekit.propagation.FieldSpacecraftState;
  23. import org.orekit.propagation.events.handlers.FieldEventHandler;
  24. import org.orekit.time.FieldAbsoluteDate;

  25. /** Wrapper used to detect events only when enabled by an external predicated function.
  26.  *
  27.  * <p>General {@link FieldEventDetector events} are defined implicitly
  28.  * by a {@link FieldEventDetector#g(FieldSpacecraftState) g function} crossing
  29.  * zero. This implies that during an orbit propagation, events are
  30.  * triggered at all zero crossings.
  31.  * </p>
  32.  *
  33.  * <p>Sometimes, users would like to enable or disable events by themselves,
  34.  * for example to trigger them only for certain orbits, or to check elevation
  35.  * maximums only when elevation itself is positive (i.e. they want to
  36.  * discard elevation maximums below ground). In these cases, looking precisely
  37.  * for all events location and triggering events that will later be ignored
  38.  * is a waste of computing time.</p>
  39.  *
  40.  * <p>Users can wrap a regular {@link FieldEventDetector event detector} in
  41.  * an instance of this class and provide this wrapping instance to
  42.  * a {@link org.orekit.propagation.FieldPropagator}
  43.  * in order to avoid wasting time looking for uninteresting events.
  44.  * The wrapper will intercept the calls to the {@link
  45.  * FieldEventDetector#g(FieldSpacecraftState) g function} and to the {@link
  46.  * FieldEventHandler#eventOccurred(FieldSpacecraftState, FieldEventDetector, boolean)
  47.  * eventOccurred} method in order to ignore uninteresting events. The
  48.  * wrapped regular {@link FieldEventDetector event detector} will the see only
  49.  * the interesting events, i.e. either only events that occur when a
  50.  * user-provided event enabling predicate function is true, ignoring all events
  51.  * that occur when the event enabling predicate function is false. The number of
  52.  * calls to the {@link FieldEventDetector#g(FieldSpacecraftState) g function} will also be
  53.  * reduced.</p>
  54.  * @param <T> type of the field elements
  55.  * @see FieldEventSlopeFilter
  56.  * @since 12.0
  57.  */
  58. public class FieldEventEnablingPredicateFilter<T extends CalculusFieldElement<T>> implements FieldDetectorModifier<T> {

  59.     /** Number of past transformers updates stored. */
  60.     private static final int HISTORY_SIZE = 100;

  61.     /** Wrapped event detector. */
  62.     private final FieldEventDetector<T> rawDetector;

  63.     /** Enabling predicate function. */
  64.     private final FieldEnablingPredicate<T> predicate;

  65.     /** Transformers of the g function. */
  66.     private final Transformer[] transformers;

  67.     /** Update time of the transformers. */
  68.     private final FieldAbsoluteDate<T>[] updates;

  69.     /** Event detection settings. */
  70.     private final FieldEventDetectionSettings<T> detectionSettings;

  71.     /** Specialized event handler. */
  72.     private final LocalHandler<T> handler;

  73.     /** Indicator for forward integration. */
  74.     private boolean forward;

  75.     /** Extreme time encountered so far. */
  76.     private FieldAbsoluteDate<T> extremeT;

  77.     /** Detector function value at extremeT. */
  78.     private T extremeG;

  79.     /** Wrap an {@link EventDetector event detector}.
  80.      * @param rawDetector event detector to wrap
  81.      * @param enabler event enabling predicate function to use
  82.      */
  83.     public FieldEventEnablingPredicateFilter(final FieldEventDetector<T> rawDetector,
  84.                                              final FieldEnablingPredicate<T> enabler) {
  85.         this(rawDetector.getDetectionSettings(), rawDetector, enabler);
  86.     }

  87.     /** Constructor with full parameters.
  88.      * @param detectionSettings event detection settings
  89.      * @param rawDetector event detector to wrap
  90.      * @param enabler event enabling function to use
  91.      * @since 13.0
  92.      */
  93.     @SuppressWarnings("unchecked")
  94.     public FieldEventEnablingPredicateFilter(final FieldEventDetectionSettings<T> detectionSettings,
  95.                                              final FieldEventDetector<T> rawDetector,
  96.                                              final FieldEnablingPredicate<T> enabler) {
  97.         this.detectionSettings = detectionSettings;
  98.         this.handler = new LocalHandler<>();
  99.         this.rawDetector  = rawDetector;
  100.         this.predicate = enabler;
  101.         this.transformers = new Transformer[HISTORY_SIZE];
  102.         this.updates      = (FieldAbsoluteDate<T>[]) Array.newInstance(FieldAbsoluteDate.class, HISTORY_SIZE);
  103.     }

  104.     /**
  105.      * Builds a new instance from the input detection settings.
  106.      * @param settings event detection settings to be used
  107.      * @return a new detector
  108.      */
  109.     public FieldEventEnablingPredicateFilter<T> withDetectionSettings(final FieldEventDetectionSettings<T> settings) {
  110.         return new FieldEventEnablingPredicateFilter<>(settings, rawDetector, predicate);
  111.     }

  112.     /**
  113.      * Get the wrapped raw detector.
  114.      * @return the wrapped raw detector
  115.      */
  116.     public FieldEventDetector<T> getDetector() {
  117.         return rawDetector;
  118.     }

  119.     /**  {@inheritDoc} */
  120.     @Override
  121.     public FieldEventHandler<T> getHandler() {
  122.         return handler;
  123.     }

  124.     /**  {@inheritDoc} */
  125.     @Override
  126.     public FieldEventDetectionSettings<T> getDetectionSettings() {
  127.         return detectionSettings;
  128.     }

  129.     /**
  130.      * Getter for the enabling predicate.
  131.      * @return predicate
  132.      * @since 13.1
  133.      */
  134.     public FieldEnablingPredicate<T> getPredicate() {
  135.         return predicate;
  136.     }

  137.     /**  {@inheritDoc} */
  138.     @Override
  139.     public boolean dependsOnTimeOnly() {
  140.         return false;  // cannot know what predicate needs
  141.     }

  142.     /**  {@inheritDoc} */
  143.     @Override
  144.     public void init(final FieldSpacecraftState<T> s0, final FieldAbsoluteDate<T> t) {
  145.         FieldDetectorModifier.super.init(s0, t);

  146.         // initialize events triggering logic
  147.         forward  = FieldAbstractDetector.checkIfForward(s0, t);
  148.         extremeT = forward ?
  149.                    FieldAbsoluteDate.getPastInfinity(t.getField()) :
  150.                    FieldAbsoluteDate.getFutureInfinity(t.getField());
  151.         extremeG = t.getField().getZero().newInstance(Double.NaN);
  152.         Arrays.fill(transformers, Transformer.UNINITIALIZED);
  153.         Arrays.fill(updates, extremeT);

  154.     }

  155.     @Override
  156.     public void reset(final FieldSpacecraftState<T> state, final FieldAbsoluteDate<T> target) {
  157.         FieldDetectorModifier.super.reset(state, target);
  158.         forward  = FieldAbstractDetector.checkIfForward(state, target);
  159.         extremeT = forward ?
  160.                 FieldAbsoluteDate.getPastInfinity(target.getField()) :
  161.                 FieldAbsoluteDate.getFutureInfinity(target.getField());
  162.         extremeG = target.getField().getZero().newInstance(Double.NaN);
  163.     }

  164.     /**  {@inheritDoc} */
  165.     @Override
  166.     public T g(final FieldSpacecraftState<T> s) {

  167.         final T       rawG      = rawDetector.g(s);
  168.         final boolean isEnabled = predicate.eventIsEnabled(s, rawDetector, rawG);
  169.         if (extremeG.isNaN()) {
  170.             extremeG = rawG;
  171.         }

  172.         // search which transformer should be applied to g
  173.         if (isForward()) {
  174.             final int last = transformers.length - 1;
  175.             if (extremeT.compareTo(s.getDate()) < 0) {
  176.                 // we are at the forward end of the history

  177.                 // check if enabled status has changed
  178.                 final Transformer previous = transformers[last];
  179.                 final Transformer next     = selectTransformer(previous, extremeG, isEnabled);
  180.                 if (next != previous) {
  181.                     // there is a status change somewhere between extremeT and t.
  182.                     // the new transformer is valid for t (this is how we have just computed
  183.                     // it above), but it is in fact valid on both sides of the change, so
  184.                     // it was already valid before t and even up to previous time. We store
  185.                     // the switch at extremeT for safety, to ensure the previous transformer
  186.                     // is not applied too close of the root
  187.                     System.arraycopy(updates,      1, updates,      0, last);
  188.                     System.arraycopy(transformers, 1, transformers, 0, last);
  189.                     updates[last]      = extremeT;
  190.                     transformers[last] = next;
  191.                 }

  192.                 extremeT = s.getDate();
  193.                 extremeG = rawG;

  194.                 // apply the transform
  195.                 return next.transformed(rawG);

  196.             } else {
  197.                 // we are in the middle of the history

  198.                 // select the transformer
  199.                 for (int i = last; i > 0; --i) {
  200.                     if (updates[i].compareTo(s.getDate()) <= 0) {
  201.                         // apply the transform
  202.                         return transformers[i].transformed(rawG);
  203.                     }
  204.                 }

  205.                 return transformers[0].transformed(rawG);

  206.             }
  207.         } else {
  208.             if (s.getDate().compareTo(extremeT) < 0) {
  209.                 // we are at the backward end of the history

  210.                 // check if a new rough root has been crossed
  211.                 final Transformer previous = transformers[0];
  212.                 final Transformer next     = selectTransformer(previous, extremeG, isEnabled);
  213.                 if (next != previous) {
  214.                     // there is a status change somewhere between extremeT and t.
  215.                     // the new transformer is valid for t (this is how we have just computed
  216.                     // it above), but it is in fact valid on both sides of the change, so
  217.                     // it was already valid before t and even up to previous time. We store
  218.                     // the switch at extremeT for safety, to ensure the previous transformer
  219.                     // is not applied too close of the root
  220.                     System.arraycopy(updates,      0, updates,      1, updates.length - 1);
  221.                     System.arraycopy(transformers, 0, transformers, 1, transformers.length - 1);
  222.                     updates[0]      = extremeT;
  223.                     transformers[0] = next;
  224.                 }

  225.                 extremeT = s.getDate();
  226.                 extremeG = rawG;

  227.                 // apply the transform
  228.                 return next.transformed(rawG);

  229.             } else {
  230.                 // we are in the middle of the history

  231.                 // select the transformer
  232.                 for (int i = 0; i < updates.length - 1; ++i) {
  233.                     if (s.getDate().compareTo(updates[i]) <= 0) {
  234.                         // apply the transform
  235.                         return transformers[i].transformed(rawG);
  236.                     }
  237.                 }

  238.                 return transformers[updates.length - 1].transformed(rawG);

  239.             }
  240.         }

  241.     }

  242.     /** Get next function transformer in the specified direction.
  243.      * @param previous transformer active on the previous point with respect
  244.      * to integration direction (may be null if no previous point is known)
  245.      * @param previousG value of the g function at the previous point
  246.      * @param isEnabled if true the event should be enabled now
  247.      * @return next transformer transformer
  248.      */
  249.     private Transformer selectTransformer(final Transformer previous, final T previousG, final boolean isEnabled) {
  250.         if (isEnabled) {
  251.             // we need to select a transformer that can produce zero crossings,
  252.             // so it is either Transformer.PLUS or Transformer.MINUS
  253.             switch (previous) {
  254.                 case UNINITIALIZED :
  255.                     return Transformer.PLUS; // this initial choice is arbitrary, it could have been Transformer.MINUS
  256.                 case MIN :
  257.                     return previousG.getReal() >= 0 ? Transformer.MINUS : Transformer.PLUS;
  258.                 case MAX :
  259.                     return previousG.getReal() >= 0 ? Transformer.PLUS : Transformer.MINUS;
  260.                 default :
  261.                     return previous;
  262.             }
  263.         } else {
  264.             // we need to select a transformer that cannot produce any zero crossings,
  265.             // so it is either Transformer.MAX or Transformer.MIN
  266.             switch (previous) {
  267.                 case UNINITIALIZED :
  268.                     return Transformer.MAX; // this initial choice is arbitrary, it could have been Transformer.MIN
  269.                 case PLUS :
  270.                     return previousG.getReal() >= 0 ? Transformer.MAX : Transformer.MIN;
  271.                 case MINUS :
  272.                     return previousG.getReal() >= 0 ? Transformer.MIN : Transformer.MAX;
  273.                 default :
  274.                     return previous;
  275.             }
  276.         }
  277.     }

  278.     /** Check if the current propagation is forward or backward.
  279.      * @return true if the current propagation is forward
  280.      */
  281.     public boolean isForward() {
  282.         return forward;
  283.     }

  284.     /** Local handler.
  285.      * @param <T> type of the field elements
  286.      */
  287.     private static class LocalHandler<T extends CalculusFieldElement<T>> implements FieldEventHandler<T> {

  288.         /** {@inheritDoc} */
  289.         public Action eventOccurred(final FieldSpacecraftState<T> s, final FieldEventDetector<T> detector, final boolean increasing) {
  290.             final FieldEventEnablingPredicateFilter<T> ef = (FieldEventEnablingPredicateFilter<T>) detector;
  291.             final Transformer transformer = ef.forward ? ef.transformers[ef.transformers.length - 1] : ef.transformers[0];
  292.             return ef.rawDetector.getHandler().eventOccurred(s, ef.rawDetector, transformer == Transformer.PLUS ? increasing : !increasing);
  293.         }

  294.         /** {@inheritDoc} */
  295.         @Override
  296.         public FieldSpacecraftState<T> resetState(final FieldEventDetector<T> detector, final FieldSpacecraftState<T> oldState) {
  297.             final FieldEventEnablingPredicateFilter<T> ef = (FieldEventEnablingPredicateFilter<T>) detector;
  298.             return ef.rawDetector.getHandler().resetState(ef.rawDetector, oldState);
  299.         }

  300.     }

  301. }