ParameterDrivenDateIntervalDetector.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.propagation.events;

  18. import java.util.List;
  19. import java.util.stream.Collectors;

  20. import org.hipparchus.util.FastMath;
  21. import org.orekit.errors.OrekitException;
  22. import org.orekit.errors.OrekitMessages;
  23. import org.orekit.propagation.SpacecraftState;
  24. import org.orekit.propagation.events.handlers.EventHandler;
  25. import org.orekit.propagation.events.handlers.StopOnDecreasing;
  26. import org.orekit.propagation.events.intervals.DateDetectionAdaptableIntervalFactory;
  27. import org.orekit.time.AbsoluteDate;
  28. import org.orekit.utils.DateDriver;
  29. import org.orekit.utils.ParameterDriver;
  30. import org.orekit.utils.ParameterObserver;
  31. import org.orekit.utils.TimeSpanMap;
  32. import org.orekit.utils.TimeSpanMap.Span;

  33. /** Detector for date intervals that may be offset thanks to parameter drivers.
  34.  * <p>
  35.  * Two dual views can be used for date intervals: either start date/stop date or
  36.  * median date/duration. {@link #getStartDriver() start}/{@link #getStopDriver() stop}
  37.  * drivers and {@link #getMedianDriver() median}/{@link #getDurationDriver() duration}
  38.  * drivers work in pair. Both drivers in one pair can be selected and their changes will
  39.  * be propagated to the other pair, but attempting to select drivers in both
  40.  * pairs at the same time will trigger an exception. Changing the value of a driver
  41.  * that is not selected should be avoided as it leads to inconsistencies between the pairs.
  42.  * </p>. Warning, startDate driver, stopDate driver, duration driver and medianDate driver
  43.  * must all have the same number of values to estimate (same number of span in valueSpanMap), that is is to
  44.  * say that the {@link org.orekit.utils.ParameterDriver#addSpans(AbsoluteDate, AbsoluteDate, double)}
  45.  * should be called with same arguments.
  46.  * @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
  47.  * @author Luc Maisonobe
  48.  * @since 11.1
  49.  */
  50. public class ParameterDrivenDateIntervalDetector extends AbstractDetector<ParameterDrivenDateIntervalDetector> {

  51.     /** Default suffix for start driver. */
  52.     public static final String START_SUFFIX = "_START";

  53.     /** Default suffix for stop driver. */
  54.     public static final String STOP_SUFFIX = "_STOP";

  55.     /** Default suffix for median driver. */
  56.     public static final String MEDIAN_SUFFIX = "_MEDIAN";

  57.     /** Default suffix for duration driver. */
  58.     public static final String DURATION_SUFFIX = "_DURATION";

  59.     /** Reference interval start driver. */
  60.     private final DateDriver start;

  61.     /** Reference interval stop driver. */
  62.     private final DateDriver stop;

  63.     /** Median date driver. */
  64.     private final DateDriver median;

  65.     /** Duration driver. */
  66.     private final ParameterDriver duration;

  67.     /** Build a new instance.
  68.      * @param prefix prefix to use for parameter drivers names
  69.      * @param refMedian reference interval median date
  70.      * @param refDuration reference duration
  71.      */
  72.     public ParameterDrivenDateIntervalDetector(final String prefix,
  73.                                                final AbsoluteDate refMedian, final double refDuration) {
  74.         this(prefix,
  75.              refMedian.shiftedBy(-0.5 * refDuration),
  76.              refMedian.shiftedBy(0.5 * refDuration));
  77.     }

  78.     /** Build a new instance.
  79.      * @param prefix prefix to use for parameter drivers names
  80.      * @param refStart reference interval start date
  81.      * @param refStop reference interval stop date
  82.      */
  83.     public ParameterDrivenDateIntervalDetector(final String prefix,
  84.                                                final AbsoluteDate refStart, final AbsoluteDate refStop) {
  85.         this(getDefaultDetectionSettings(refStart, refStop),
  86.              new StopOnDecreasing(),
  87.              new DateDriver(refStart, prefix + START_SUFFIX, true),
  88.              new DateDriver(refStop, prefix + STOP_SUFFIX, false),
  89.              new DateDriver(refStart.shiftedBy(0.5 * refStop.durationFrom(refStart)), prefix + MEDIAN_SUFFIX, true),
  90.              new ParameterDriver(prefix + DURATION_SUFFIX, refStop.durationFrom(refStart), 1.0, 0.0, Double.POSITIVE_INFINITY));
  91.     }

  92.     /** Protected constructor with full parameters.
  93.      * <p>
  94.      * This constructor is not public as users are expected to use the builder
  95.      * API with the various {@code withXxx()} methods to set up the instance
  96.      * in a readable manner without using a huge amount of parameters.
  97.      * </p>
  98.      * @param detectionSettings event detection settings
  99.      * @param handler event handler to call at event occurrences
  100.      * @param start reference interval start driver
  101.      * @param stop reference interval stop driver
  102.      * @param median median date driver
  103.      * @param duration duration driver
  104.      * @since 13.0
  105.      */
  106.     protected ParameterDrivenDateIntervalDetector(final EventDetectionSettings detectionSettings,
  107.                                                   final EventHandler handler,
  108.                                                   final DateDriver start, final DateDriver stop,
  109.                                                   final DateDriver median, final ParameterDriver duration) {
  110.         super(detectionSettings, handler);
  111.         this.start    = start;
  112.         this.stop     = stop;
  113.         this.median   = median;
  114.         this.duration = duration;

  115.         // set up delegation between drivers
  116.         replaceBindingObserver(start,    new StartObserver());
  117.         replaceBindingObserver(stop,     new StopObserver());
  118.         replaceBindingObserver(median,   new MedianObserver());
  119.         replaceBindingObserver(duration, new DurationObserver());

  120.     }

  121.     /**
  122.      * Get default detection settings.
  123.      * @param refStart reference interval start date
  124.      * @param refStop reference interval stop date
  125.      * @return default detection settings
  126.      * @since 13.0
  127.      */
  128.     public static EventDetectionSettings getDefaultDetectionSettings(final AbsoluteDate refStart,
  129.                                                                      final AbsoluteDate refStop) {
  130.         return new EventDetectionSettings(DateDetectionAdaptableIntervalFactory.getDatesDetectionInterval(refStart, refStop),
  131.                 DateDetector.DEFAULT_THRESHOLD, EventDetectionSettings.DEFAULT_MAX_ITER);
  132.     }

  133.     /** Replace binding observers.
  134.      * @param driver driver for whose binding observers should be replaced
  135.      * @param bindingObserver new binding observer
  136.      */
  137.     private void replaceBindingObserver(final ParameterDriver driver, final BindingObserver bindingObserver) {

  138.         // remove the previous binding observers
  139.         final List<ParameterObserver> original = driver.
  140.                                                  getObservers().
  141.                                                  stream().
  142.                                                  filter(observer -> observer instanceof ParameterDrivenDateIntervalDetector.BindingObserver).
  143.                                                  collect(Collectors.toList());
  144.         original.forEach(driver::removeObserver);

  145.         driver.addObserver(bindingObserver);

  146.     }

  147.     /** {@inheritDoc} */
  148.     @Override
  149.     protected ParameterDrivenDateIntervalDetector create(final EventDetectionSettings detectionSettings,
  150.                                                          final EventHandler newHandler) {
  151.         return new ParameterDrivenDateIntervalDetector(detectionSettings, newHandler, start, stop, median, duration);
  152.     }

  153.     /** Get the driver for start date.
  154.      * <p>
  155.      * Note that the start date is automatically adjusted if either
  156.      * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
  157.      * are {@link ParameterDriver#isSelected() selected} and changed.
  158.      * </p>
  159.      * @return driver for start date
  160.      */
  161.     public DateDriver getStartDriver() {
  162.         return start;
  163.     }

  164.     /** Get the driver for stop date.
  165.      * <p>
  166.      * Note that the stop date is automatically adjusted if either
  167.      * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
  168.      * are {@link ParameterDriver#isSelected() selected} changed.
  169.      * </p>
  170.      * @return driver for stop date
  171.      */
  172.     public DateDriver getStopDriver() {
  173.         return stop;
  174.     }

  175.     /** Get the driver for median date.
  176.      * <p>
  177.      * Note that the median date is automatically adjusted if either
  178.      * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
  179.      * are {@link ParameterDriver#isSelected() selected} changed.
  180.      * </p>
  181.      * @return driver for median date
  182.      */
  183.     public DateDriver getMedianDriver() {
  184.         return median;
  185.     }

  186.     /** Get the driver for duration.
  187.      * <p>
  188.      * Note that the duration is automatically adjusted if either
  189.      * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
  190.      * are {@link ParameterDriver#isSelected() selected} changed.
  191.      * </p>
  192.      * @return driver for duration
  193.      */
  194.     public ParameterDriver getDurationDriver() {
  195.         return duration;
  196.     }

  197.     /** Compute the value of the switching function.
  198.      * <p>
  199.      * The function is positive for dates within the interval defined
  200.      * by applying the parameter drivers shifts to reference dates,
  201.      * and negative for dates outside of this interval. Note that
  202.      * if Δt_start - Δt_stop is less than ref_stop.durationFrom(ref_start),
  203.      * then the interval degenerates to empty and the function never
  204.      * reaches positive values.
  205.      * </p>
  206.      * @param s the current state information: date, kinematics, attitude
  207.      * @return value of the switching function
  208.      */
  209.     public double g(final SpacecraftState s) {
  210.         return FastMath.min(s.getDate().durationFrom(start.getDate()),
  211.                             stop.getDate().durationFrom(s.getDate()));
  212.     }

  213.     /** Base observer. */
  214.     private abstract class BindingObserver implements ParameterObserver {

  215.         /** {@inheritDoc} */
  216.         @Override
  217.         public void valueChanged(final double previousValue, final ParameterDriver driver, final AbsoluteDate date) {
  218.             if (driver.isSelected()) {
  219.                 setDelta(driver.getValue(date) - previousValue, date);
  220.             }
  221.         }
  222.         /** {@inheritDoc} */
  223.         @Override
  224.         public void valueSpanMapChanged(final TimeSpanMap<Double> previousValue, final ParameterDriver driver) {
  225.             if (driver.isSelected()) {
  226.                 for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
  227.                     setDelta(span.getData() - previousValue.get(span.getStart()), span.getStart());
  228.                 }
  229.             }
  230.         }
  231.         /** {@inheritDoc} */
  232.         @Override
  233.         public void selectionChanged(final boolean previousSelection, final ParameterDriver driver) {
  234.             if ((start.isSelected()  || stop.isSelected()) &&
  235.                 (median.isSelected() || duration.isSelected())) {
  236.                 throw new OrekitException(OrekitMessages.INCONSISTENT_SELECTION,
  237.                                           start.getName(), stop.getName(),
  238.                                           median.getName(), duration.getName());
  239.             }
  240.         }

  241.         /** Change a value.
  242.          * @param delta change of value
  243.          * @param date date at which the delta wants to be set
  244.          */
  245.         protected abstract void setDelta(double delta, AbsoluteDate date);

  246.     }

  247.     /** Observer for start date. */
  248.     private class StartObserver extends BindingObserver {
  249.         /** {@inheritDoc} */
  250.         @Override
  251.         protected void setDelta(final double delta, final AbsoluteDate date) {
  252.             // date driver has no validity period, only 1 value is estimated
  253.             // over the all interval so there is no problem for calling getValue with null argument
  254.             // or any date, it would give the same result as there is only 1 span on the valueSpanMap
  255.             // of the driver
  256.             median.setValue(median.getValue(date) + 0.5 * delta, date);
  257.             duration.setValue(duration.getValue(date) - delta, date);
  258.         }
  259.     }

  260.     /** Observer for stop date. */
  261.     private class StopObserver extends BindingObserver {
  262.         /** {@inheritDoc} */
  263.         @Override
  264.         protected void setDelta(final double delta, final AbsoluteDate date) {
  265.             // date driver has no validity period, only 1 value is estimated
  266.             // over the all interval so there is no problem for calling getValue with null argument
  267.             // or any date, it would give the same result as there is only 1 span on the valueSpanMap
  268.             // of the driver
  269.             median.setValue(median.getValue(date) + 0.5 * delta, date);
  270.             duration.setValue(duration.getValue(date) + delta, date);
  271.         }
  272.     }

  273.     /** Observer for median date. */
  274.     private class MedianObserver extends BindingObserver {
  275.         /** {@inheritDoc} */
  276.         @Override
  277.         protected void setDelta(final double delta, final AbsoluteDate date) {
  278.             // date driver has no validity period, only 1 value is estimated
  279.             // over the all interval so there is no problem for calling getValue with null argument
  280.             // or any date, it would give the same result as there is only 1 span on the valueSpanMap
  281.             // of the driver
  282.             start.setValue(start.getValue(date) + delta, date);
  283.             stop.setValue(stop.getValue(date) + delta, date);
  284.         }
  285.     }

  286.     /** Observer for duration. */
  287.     private class DurationObserver extends BindingObserver {
  288.         /** {@inheritDoc} */
  289.         @Override
  290.         protected void setDelta(final double delta, final AbsoluteDate date) {
  291.             // date driver has no validity period, only 1 value is estimated
  292.             // over the all interval so there is no problem for calling getValue with null argument
  293.             // or any date, it would give the same result as there is only 1 span on the valueSpanMap
  294.             // of the driver
  295.             start.setValue(start.getValue(date) - 0.5 * delta, date);
  296.             stop.setValue(stop.getValue(date) + 0.5 * delta, date);
  297.         }
  298.     }

  299. }