FieldEventState.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF 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 org.hipparchus.Field;
  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.analysis.UnivariateFunction;
  21. import org.hipparchus.analysis.solvers.BracketedUnivariateSolver;
  22. import org.hipparchus.analysis.solvers.BracketedUnivariateSolver.Interval;
  23. import org.hipparchus.analysis.solvers.BracketingNthOrderBrentSolver;
  24. import org.hipparchus.exception.MathRuntimeException;
  25. import org.hipparchus.ode.events.Action;
  26. import org.hipparchus.util.FastMath;
  27. import org.hipparchus.util.Precision;
  28. import org.orekit.errors.OrekitException;
  29. import org.orekit.errors.OrekitInternalError;
  30. import org.orekit.errors.OrekitMessages;
  31. import org.orekit.propagation.FieldSpacecraftState;
  32. import org.orekit.propagation.sampling.FieldOrekitStepInterpolator;
  33. import org.orekit.time.FieldAbsoluteDate;

  34. /** This class handles the state for one {@link FieldEventDetector
  35.  * event detector} during integration steps.
  36.  *
  37.  * <p>This class is heavily based on the class with the same name from the
  38.  * Hipparchus library. The changes performed consist in replacing
  39.  * raw types (double and double arrays) with space dynamics types
  40.  * ({@link FieldAbsoluteDate}, {@link FieldSpacecraftState}).</p>
  41.  * <p>Each time the propagator proposes a step, the event detector
  42.  * should be checked. This class handles the state of one detector
  43.  * during one propagation step, with references to the state at the
  44.  * end of the preceding step. This information is used to determine if
  45.  * the detector should trigger an event or not during the proposed
  46.  * step (and hence the step should be reduced to ensure the event
  47.  * occurs at a bound rather than inside the step).</p>
  48.  * @author Luc Maisonobe
  49.  * @param <D> class type for the generic version
  50.  */
  51. public class FieldEventState<D extends FieldEventDetector<T>, T extends CalculusFieldElement<T>> {

  52.     /** Event detector. */
  53.     private D detector;

  54.     /** Time of the previous call to g. */
  55.     private FieldAbsoluteDate<T> lastT;

  56.     /** Value from the previous call to g. */
  57.     private T lastG;

  58.     /** Time at the beginning of the step. */
  59.     private FieldAbsoluteDate<T> t0;

  60.     /** Value of the event detector at the beginning of the step. */
  61.     private T g0;

  62.     /** Simulated sign of g0 (we cheat when crossing events). */
  63.     private boolean g0Positive;

  64.     /** Indicator of event expected during the step. */
  65.     private boolean pendingEvent;

  66.     /** Occurrence time of the pending event. */
  67.     private FieldAbsoluteDate<T> pendingEventTime;

  68.     /**
  69.      * Time to stop propagation if the event is a stop event. Used to enable stopping at
  70.      * an event and then restarting after that event.
  71.      */
  72.     private FieldAbsoluteDate<T> stopTime;

  73.     /** Time after the current event. */
  74.     private FieldAbsoluteDate<T> afterEvent;

  75.     /** Value of the g function after the current event. */
  76.     private T afterG;

  77.     /** The earliest time considered for events. */
  78.     private FieldAbsoluteDate<T> earliestTimeConsidered;

  79.     /** Integration direction. */
  80.     private boolean forward;

  81.     /** Variation direction around pending event.
  82.      *  (this is considered with respect to the integration direction)
  83.      */
  84.     private boolean increasing;

  85.     /** Simple constructor.
  86.      * @param detector monitored event detector
  87.      */
  88.     public FieldEventState(final D detector) {

  89.         this.detector = detector;

  90.         // some dummy values ...
  91.         final Field<T> field   = detector.getMaxCheckInterval().getField();
  92.         final T nan            = field.getZero().add(Double.NaN);
  93.         lastT                  = FieldAbsoluteDate.getPastInfinity(field);
  94.         lastG                  = nan;
  95.         t0                     = null;
  96.         g0                     = nan;
  97.         g0Positive             = true;
  98.         pendingEvent           = false;
  99.         pendingEventTime       = null;
  100.         stopTime               = null;
  101.         increasing             = true;
  102.         earliestTimeConsidered = null;
  103.         afterEvent             = null;
  104.         afterG                 = nan;

  105.     }

  106.     /** Get the underlying event detector.
  107.      * @return underlying event detector
  108.      */
  109.     public D getEventDetector() {
  110.         return detector;
  111.     }

  112.     /** Initialize event handler at the start of a propagation.
  113.      * <p>
  114.      * This method is called once at the start of the propagation. It
  115.      * may be used by the event handler to initialize some internal data
  116.      * if needed.
  117.      * </p>
  118.      * @param s0 initial state
  119.      * @param t target time for the integration
  120.      *
  121.      */
  122.     public void init(final FieldSpacecraftState<T> s0,
  123.                      final FieldAbsoluteDate<T> t) {
  124.         detector.init(s0, t);
  125.         final Field<T> field = detector.getMaxCheckInterval().getField();
  126.         lastT = FieldAbsoluteDate.getPastInfinity(field);
  127.         lastG = field.getZero().add(Double.NaN);
  128.     }

  129.     /** Compute the value of the switching function.
  130.      * This function must be continuous (at least in its roots neighborhood),
  131.      * as the integrator will need to find its roots to locate the events.
  132.      * @param s the current state information: date, kinematics, attitude
  133.      * @return value of the switching function
  134.      */
  135.     private T g(final FieldSpacecraftState<T> s) {
  136.         if (!s.getDate().equals(lastT)) {
  137.             lastT = s.getDate();
  138.             lastG = detector.g(s);
  139.         }
  140.         return lastG;
  141.     }

  142.     /** Reinitialize the beginning of the step.
  143.      * @param interpolator interpolator valid for the current step
  144.      */
  145.     public void reinitializeBegin(final FieldOrekitStepInterpolator<T> interpolator) {
  146.         forward = interpolator.isForward();
  147.         final FieldSpacecraftState<T> s0 = interpolator.getPreviousState();
  148.         this.t0 = s0.getDate();
  149.         g0 = g(s0);
  150.         while (g0.getReal() == 0) {
  151.             // extremely rare case: there is a zero EXACTLY at interval start
  152.             // we will use the sign slightly after step beginning to force ignoring this zero
  153.             // try moving forward by half a convergence interval
  154.             final T dt = detector.getThreshold().multiply(forward ? 0.5 : -0.5);
  155.             FieldAbsoluteDate<T> startDate = t0.shiftedBy(dt);
  156.             // if convergence is too small move an ulp
  157.             if (t0.equals(startDate)) {
  158.                 startDate = nextAfter(startDate);
  159.             }
  160.             t0 = startDate;
  161.             g0 = g(interpolator.getInterpolatedState(t0));
  162.         }
  163.         g0Positive = g0.getReal() > 0;
  164.         // "last" event was increasing
  165.         increasing = g0Positive;
  166.     }

  167.     /** Evaluate the impact of the proposed step on the event detector.
  168.      * @param interpolator step interpolator for the proposed step
  169.      * @return true if the event detector triggers an event before
  170.      * the end of the proposed step (this implies the step should be
  171.      * rejected)
  172.      * @exception MathRuntimeException if an event cannot be located
  173.      */
  174.     public boolean evaluateStep(final FieldOrekitStepInterpolator<T> interpolator)
  175.         throws MathRuntimeException {
  176.         forward = interpolator.isForward();
  177.         final FieldSpacecraftState<T> s1 = interpolator.getCurrentState();
  178.         final FieldAbsoluteDate<T> t1 = s1.getDate();
  179.         final T dt = t1.durationFrom(t0);
  180.         if (FastMath.abs(dt.getReal()) < detector.getThreshold().getReal()) {
  181.             // we cannot do anything on such a small step, don't trigger any events
  182.             return false;
  183.         }
  184.         // number of points to check in the current step
  185.         final int n = FastMath.max(1, (int) FastMath.ceil(FastMath.abs(dt.getReal()) / detector.getMaxCheckInterval().getReal()));
  186.         final T h = dt.divide(n);


  187.         FieldAbsoluteDate<T> ta = t0;
  188.         T ga = g0;
  189.         for (int i = 0; i < n; ++i) {

  190.             // evaluate handler value at the end of the substep
  191.             final FieldAbsoluteDate<T> tb = (i == n - 1) ? t1 : t0.shiftedBy(h.multiply(i + 1));
  192.             final T gb = g(interpolator.getInterpolatedState(tb));

  193.             // check events occurrence
  194.             if (gb.getReal() == 0.0 || (g0Positive ^ (gb.getReal() > 0))) {
  195.                 // there is a sign change: an event is expected during this step
  196.                 if (findRoot(interpolator, ta, ga, tb, gb)) {
  197.                     return true;
  198.                 }
  199.             } else {
  200.                 // no sign change: there is no event for now
  201.                 ta = tb;
  202.                 ga = gb;
  203.             }
  204.         }

  205.         // no event during the whole step
  206.         pendingEvent     = false;
  207.         pendingEventTime = null;
  208.         return false;

  209.     }

  210.     /**
  211.      * Find a root in a bracketing interval.
  212.      *
  213.      * <p> When calling this method one of the following must be true. Either ga == 0, gb
  214.      * == 0, (ga < 0  and gb > 0), or (ga > 0 and gb < 0).
  215.      *
  216.      * @param interpolator that covers the interval.
  217.      * @param ta           earliest possible time for root.
  218.      * @param ga           g(ta).
  219.      * @param tb           latest possible time for root.
  220.      * @param gb           g(tb).
  221.      * @return if a zero crossing was found.
  222.      */
  223.     private boolean findRoot(final FieldOrekitStepInterpolator<T> interpolator,
  224.                              final FieldAbsoluteDate<T> ta, final T ga,
  225.                              final FieldAbsoluteDate<T> tb, final T gb) {

  226.         final T zero = ga.getField().getZero();

  227.         // check there appears to be a root in [ta, tb]
  228.         check(ga.getReal() == 0.0 || gb.getReal() == 0.0 || ga.getReal() > 0.0 && gb.getReal() < 0.0 || ga.getReal() < 0.0 && gb.getReal() > 0.0);
  229.         final T convergence = detector.getThreshold();
  230.         final int maxIterationCount = detector.getMaxIterationCount();
  231.         final BracketedUnivariateSolver<UnivariateFunction> solver =
  232.                 new BracketingNthOrderBrentSolver(0, convergence.getReal(), 0, 5);

  233.         // event time, just at or before the actual root.
  234.         FieldAbsoluteDate<T> beforeRootT = null;
  235.         T beforeRootG = zero.add(Double.NaN);
  236.         // time on the other side of the root.
  237.         // Initialized the the loop below executes once.
  238.         FieldAbsoluteDate<T> afterRootT = ta;
  239.         T afterRootG = zero;

  240.         // check for some conditions that the root finders don't like
  241.         // these conditions cannot not happen in the loop below
  242.         // the ga == 0.0 case is handled by the loop below
  243.         if (ta.equals(tb)) {
  244.             // both non-zero but times are the same. Probably due to reset state
  245.             beforeRootT = ta;
  246.             beforeRootG = ga;
  247.             afterRootT = shiftedBy(beforeRootT, convergence);
  248.             afterRootG = g(interpolator.getInterpolatedState(afterRootT));
  249.         } else if (ga.getReal() != 0.0 && gb.getReal() == 0.0) {
  250.             // hard: ga != 0.0 and gb == 0.0
  251.             // look past gb by up to convergence to find next sign
  252.             // throw an exception if g(t) = 0.0 in [tb, tb + convergence]
  253.             beforeRootT = tb;
  254.             beforeRootG = gb;
  255.             afterRootT = shiftedBy(beforeRootT, convergence);
  256.             afterRootG = g(interpolator.getInterpolatedState(afterRootT));
  257.         } else if (ga.getReal() != 0.0) {
  258.             final T newGa = g(interpolator.getInterpolatedState(ta));
  259.             if (ga.getReal() > 0 != newGa.getReal() > 0) {
  260.                 // both non-zero, step sign change at ta, possibly due to reset state
  261.                 beforeRootT = ta;
  262.                 beforeRootG = newGa;
  263.                 afterRootT = minTime(shiftedBy(beforeRootT, convergence), tb);
  264.                 afterRootG = g(interpolator.getInterpolatedState(afterRootT));
  265.             }
  266.         }
  267.         // loop to skip through "fake" roots, i.e. where g(t) = g'(t) = 0.0
  268.         // executed once if we didn't hit a special case above
  269.         FieldAbsoluteDate<T> loopT = ta;
  270.         T loopG = ga;
  271.         while ((afterRootG.getReal() == 0.0 || afterRootG.getReal() > 0.0 == g0Positive) &&
  272.                 strictlyAfter(afterRootT, tb)) {
  273.             if (loopG.getReal() == 0.0) {
  274.                 // ga == 0.0 and gb may or may not be 0.0
  275.                 // handle the root at ta first
  276.                 beforeRootT = loopT;
  277.                 beforeRootG = loopG;
  278.                 afterRootT = minTime(shiftedBy(beforeRootT, convergence), tb);
  279.                 afterRootG = g(interpolator.getInterpolatedState(afterRootT));
  280.             } else {
  281.                 // both non-zero, the usual case, use a root finder.
  282.                 // time zero for evaluating the function f. Needs to be final
  283.                 final FieldAbsoluteDate<T> fT0 = loopT;
  284.                 final UnivariateFunction f = dt -> {
  285.                     return g(interpolator.getInterpolatedState(fT0.shiftedBy(dt))).getReal();
  286.                 };
  287.                 // tb as a double for use in f
  288.                 final T tbDouble = tb.durationFrom(fT0);
  289.                 if (forward) {
  290.                     try {
  291.                         final Interval interval =
  292.                                 solver.solveInterval(maxIterationCount, f, 0, tbDouble.getReal());
  293.                         beforeRootT = fT0.shiftedBy(interval.getLeftAbscissa());
  294.                         beforeRootG = zero.add(interval.getLeftValue());
  295.                         afterRootT = fT0.shiftedBy(interval.getRightAbscissa());
  296.                         afterRootG = zero.add(interval.getRightValue());
  297.                         // CHECKSTYLE: stop IllegalCatch check
  298.                     } catch (RuntimeException e) {
  299.                         // CHECKSTYLE: resume IllegalCatch check
  300.                         throw new OrekitException(e, OrekitMessages.FIND_ROOT,
  301.                                 detector, loopT, loopG, tb, gb, lastT, lastG);
  302.                     }
  303.                 } else {
  304.                     try {
  305.                         final Interval interval =
  306.                                 solver.solveInterval(maxIterationCount, f, tbDouble.getReal(), 0);
  307.                         beforeRootT = fT0.shiftedBy(interval.getRightAbscissa());
  308.                         beforeRootG = zero.add(interval.getRightValue());
  309.                         afterRootT = fT0.shiftedBy(interval.getLeftAbscissa());
  310.                         afterRootG = zero.add(interval.getLeftValue());
  311.                         // CHECKSTYLE: stop IllegalCatch check
  312.                     } catch (RuntimeException e) {
  313.                         // CHECKSTYLE: resume IllegalCatch check
  314.                         throw new OrekitException(e, OrekitMessages.FIND_ROOT,
  315.                                 detector, tb, gb, loopT, loopG, lastT, lastG);
  316.                     }
  317.                 }
  318.             }
  319.             // tolerance is set to less than 1 ulp
  320.             // assume tolerance is 1 ulp
  321.             if (beforeRootT.equals(afterRootT)) {
  322.                 afterRootT = nextAfter(afterRootT);
  323.                 afterRootG = g(interpolator.getInterpolatedState(afterRootT));
  324.             }
  325.             // check loop is making some progress
  326.             check(forward && afterRootT.compareTo(beforeRootT) > 0 ||
  327.                   !forward && afterRootT.compareTo(beforeRootT) < 0);
  328.             // setup next iteration
  329.             loopT = afterRootT;
  330.             loopG = afterRootG;
  331.         }

  332.         // figure out the result of root finding, and return accordingly
  333.         if (afterRootG.getReal() == 0.0 || afterRootG.getReal() > 0.0 == g0Positive) {
  334.             // loop gave up and didn't find any crossing within this step
  335.             return false;
  336.         } else {
  337.             // real crossing
  338.             check(beforeRootT != null && !Double.isNaN(beforeRootG.getReal()));
  339.             // variation direction, with respect to the integration direction
  340.             increasing = !g0Positive;
  341.             pendingEventTime = beforeRootT;
  342.             stopTime = beforeRootG.getReal() == 0.0 ? beforeRootT : afterRootT;
  343.             pendingEvent = true;
  344.             afterEvent = afterRootT;
  345.             afterG = afterRootG;

  346.             // check increasing set correctly
  347.             check(afterG.getReal() > 0 == increasing);
  348.             check(increasing == gb.getReal() >= ga.getReal());

  349.             return true;
  350.         }

  351.     }

  352.     /**
  353.      * Get the next number after the given number in the current propagation direction.
  354.      *
  355.      * @param t input time
  356.      * @return t +/- 1 ulp depending on the direction.
  357.      */
  358.     private FieldAbsoluteDate<T> nextAfter(final FieldAbsoluteDate<T> t) {
  359.         return t.shiftedBy(forward ? +Precision.EPSILON : -Precision.EPSILON);
  360.     }


  361.     /** Get the occurrence time of the event triggered in the current
  362.      * step.
  363.      * @return occurrence time of the event triggered in the current
  364.      * step.
  365.      */
  366.     public FieldAbsoluteDate<T> getEventDate() {
  367.         return pendingEventTime;
  368.     }

  369.     /**
  370.      * Try to accept the current history up to the given time.
  371.      *
  372.      * <p> It is not necessary to call this method before calling {@link
  373.      * #doEvent(FieldSpacecraftState)} with the same state. It is necessary to call this
  374.      * method before you call {@link #doEvent(FieldSpacecraftState)} on some other event
  375.      * detector.
  376.      *
  377.      * @param state        to try to accept.
  378.      * @param interpolator to use to find the new root, if any.
  379.      * @return if the event detector has an event it has not detected before that is on or
  380.      * before the same time as {@code state}. In other words {@code false} means continue
  381.      * on while {@code true} means stop and handle my event first.
  382.      */
  383.     public boolean tryAdvance(final FieldSpacecraftState<T> state,
  384.                               final FieldOrekitStepInterpolator<T> interpolator) {
  385.         final FieldAbsoluteDate<T> t = state.getDate();
  386.         // check this is only called before a pending event.
  387.         check(!pendingEvent || !strictlyAfter(pendingEventTime, t));

  388.         final boolean meFirst;

  389.         if (strictlyAfter(t, earliestTimeConsidered)) {
  390.             // just found an event and we know the next time we want to search again
  391.             meFirst = false;
  392.         } else {
  393.             // check g function to see if there is a new event
  394.             final T g = g(state);
  395.             final boolean positive = g.getReal() > 0;

  396.             if (positive == g0Positive) {
  397.                 // g function has expected sign
  398.                 g0 = g; // g0Positive is the same
  399.                 meFirst = false;
  400.             } else {
  401.                 // found a root we didn't expect -> find precise location
  402.                 final FieldAbsoluteDate<T> oldPendingEventTime = pendingEventTime;
  403.                 final boolean foundRoot = findRoot(interpolator, t0, g0, t, g);
  404.                 // make sure the new root is not the same as the old root, if one exists
  405.                 meFirst = foundRoot && !pendingEventTime.equals(oldPendingEventTime);
  406.             }
  407.         }

  408.         if (!meFirst) {
  409.             // advance t0 to the current time so we can't find events that occur before t
  410.             t0 = t;
  411.         }

  412.         return meFirst;
  413.     }

  414.     /**
  415.      * Notify the user's listener of the event. The event occurs wholly within this method
  416.      * call including a call to {@link FieldEventDetector#resetState(FieldSpacecraftState)}
  417.      * if necessary.
  418.      *
  419.      * @param state the state at the time of the event. This must be at the same time as
  420.      *              the current value of {@link #getEventDate()}.
  421.      * @return the user's requested action and the new state if the action is {@link
  422.      * Action#RESET_STATE}. Otherwise
  423.      * the new state is {@code state}. The stop time indicates what time propagation should
  424.      * stop if the action is {@link Action#STOP}.
  425.      * This guarantees the integration will stop on or after the root, so that integration
  426.      * may be restarted safely.
  427.      */
  428.     public EventOccurrence<T> doEvent(final FieldSpacecraftState<T> state) {
  429.         // check event is pending and is at the same time
  430.         check(pendingEvent);
  431.         check(state.getDate().equals(this.pendingEventTime));

  432.         final Action action = detector.eventOccurred(state, increasing == forward);
  433.         final FieldSpacecraftState<T> newState;
  434.         if (action == Action.RESET_STATE) {
  435.             newState = detector.resetState(state);
  436.         } else {
  437.             newState = state;
  438.         }
  439.         // clear pending event
  440.         pendingEvent     = false;
  441.         pendingEventTime = null;
  442.         // setup for next search
  443.         earliestTimeConsidered = afterEvent;
  444.         t0 = afterEvent;
  445.         g0 = afterG;
  446.         g0Positive = increasing;
  447.         // check g0Positive set correctly
  448.         check(g0.getReal() == 0.0 || g0Positive == g0.getReal() > 0);
  449.         return new EventOccurrence<T>(action, newState, stopTime);
  450.     }

  451.     /**
  452.      * Shift a time value along the current integration direction: {@link #forward}.
  453.      *
  454.      * @param t     the time to shift.
  455.      * @param delta the amount to shift.
  456.      * @return t + delta if forward, else t - delta. If the result has to be rounded it
  457.      * will be rounded to be before the true value of t + delta.
  458.      */
  459.     private FieldAbsoluteDate<T> shiftedBy(final FieldAbsoluteDate<T> t, final T delta) {
  460.         if (forward) {
  461.             final FieldAbsoluteDate<T> ret = t.shiftedBy(delta);
  462.             if (ret.durationFrom(t).getReal() > delta.getReal()) {
  463.                 return ret.shiftedBy(-Precision.EPSILON);
  464.             } else {
  465.                 return ret;
  466.             }
  467.         } else {
  468.             final FieldAbsoluteDate<T> ret = t.shiftedBy(delta.negate());
  469.             if (t.durationFrom(ret).getReal() > delta.getReal()) {
  470.                 return ret.shiftedBy(+Precision.EPSILON);
  471.             } else {
  472.                 return ret;
  473.             }
  474.         }
  475.     }

  476.     /**
  477.      * Get the time that happens first along the current propagation direction: {@link
  478.      * #forward}.
  479.      *
  480.      * @param a first time
  481.      * @param b second time
  482.      * @return min(a, b) if forward, else max (a, b)
  483.      */
  484.     private FieldAbsoluteDate<T> minTime(final FieldAbsoluteDate<T> a, final FieldAbsoluteDate<T> b) {
  485.         return (forward ^ (a.compareTo(b) > 0)) ? a : b;
  486.     }

  487.     /**
  488.      * Check the ordering of two times.
  489.      *
  490.      * @param t1 the first time.
  491.      * @param t2 the second time.
  492.      * @return true if {@code t2} is strictly after {@code t1} in the propagation
  493.      * direction.
  494.      */
  495.     private boolean strictlyAfter(final FieldAbsoluteDate<T> t1, final FieldAbsoluteDate<T> t2) {
  496.         if (t1 == null || t2 == null) {
  497.             return false;
  498.         } else {
  499.             return forward ? t1.compareTo(t2) < 0 : t2.compareTo(t1) < 0;
  500.         }
  501.     }

  502.     /**
  503.      * Same as keyword assert, but throw a {@link MathRuntimeException}.
  504.      *
  505.      * @param condition to check
  506.      * @throws MathRuntimeException if {@code condition} is false.
  507.      */
  508.     private void check(final boolean condition) throws MathRuntimeException {
  509.         if (!condition) {
  510.             throw new OrekitInternalError(null);
  511.         }
  512.     }

  513.     /**
  514.      * Class to hold the data related to an event occurrence that is needed to decide how
  515.      * to modify integration.
  516.      */
  517.     public static class EventOccurrence<T extends CalculusFieldElement<T>> {

  518.         /** User requested action. */
  519.         private final Action action;
  520.         /** New state for a reset action. */
  521.         private final FieldSpacecraftState<T> newState;
  522.         /** The time to stop propagation if the action is a stop event. */
  523.         private final FieldAbsoluteDate<T> stopDate;

  524.         /**
  525.          * Create a new occurrence of an event.
  526.          *
  527.          * @param action   the user requested action.
  528.          * @param newState for a reset event. Should be the current state unless the
  529.          *                 action is {@link Action#RESET_STATE}.
  530.          * @param stopDate to stop propagation if the action is {@link Action#STOP}. Used
  531.          *                 to move the stop time to just after the root.
  532.          */
  533.         EventOccurrence(final Action action,
  534.                         final FieldSpacecraftState<T> newState,
  535.                         final FieldAbsoluteDate<T> stopDate) {
  536.             this.action = action;
  537.             this.newState = newState;
  538.             this.stopDate = stopDate;
  539.         }

  540.         /**
  541.          * Get the user requested action.
  542.          *
  543.          * @return the action.
  544.          */
  545.         public Action getAction() {
  546.             return action;
  547.         }

  548.         /**
  549.          * Get the new state for a reset action.
  550.          *
  551.          * @return the new state.
  552.          */
  553.         public FieldSpacecraftState<T> getNewState() {
  554.             return newState;
  555.         }

  556.         /**
  557.          * Get the new time for a stop action.
  558.          *
  559.          * @return when to stop propagation.
  560.          */
  561.         public FieldAbsoluteDate<T> getStopDate() {
  562.             return stopDate;
  563.         }

  564.     }

  565.     /**Get PendingEvent.
  566.      * @return if there is a pending event or not
  567.      * */

  568.     public boolean getPendingEvent() {
  569.         return pendingEvent;
  570.     }

  571. }