1   /* Copyright 2002-2022 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  
19  import java.util.List;
20  import java.util.stream.Collectors;
21  
22  import org.hipparchus.util.FastMath;
23  import org.orekit.errors.OrekitException;
24  import org.orekit.errors.OrekitMessages;
25  import org.orekit.propagation.SpacecraftState;
26  import org.orekit.propagation.events.handlers.EventHandler;
27  import org.orekit.propagation.events.handlers.StopOnDecreasing;
28  import org.orekit.time.AbsoluteDate;
29  import org.orekit.utils.DateDriver;
30  import org.orekit.utils.ParameterDriver;
31  import org.orekit.utils.ParameterObserver;
32  
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>
43   * @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
44   * @author Luc Maisonobe
45   * @since 11.1
46   */
47  public class ParameterDrivenDateIntervalDetector extends AbstractDetector<ParameterDrivenDateIntervalDetector> {
48  
49      /** Default suffix for start driver. */
50      public static final String START_SUFFIX = "_START";
51  
52      /** Default suffix for stop driver. */
53      public static final String STOP_SUFFIX = "_STOP";
54  
55      /** Default suffix for median driver. */
56      public static final String MEDIAN_SUFFIX = "_MEDIAN";
57  
58      /** Default suffix for duration driver. */
59      public static final String DURATION_SUFFIX = "_DURATION";
60  
61      /** Reference interval start driver. */
62      private DateDriver start;
63  
64      /** Reference interval stop driver. */
65      private DateDriver stop;
66  
67      /** Median date driver. */
68      private DateDriver median;
69  
70      /** Duration driver. */
71      private ParameterDriver duration;
72  
73      /** Build a new instance.
74       * @param prefix prefix to use for parameter drivers names
75       * @param refMedian reference interval median date
76       * @param refDuration reference duration
77       */
78      public ParameterDrivenDateIntervalDetector(final String prefix,
79                                                 final AbsoluteDate refMedian, final double refDuration) {
80          this(prefix,
81               refMedian.shiftedBy(-0.5 * refDuration),
82               refMedian.shiftedBy(+0.5 * refDuration));
83      }
84  
85      /** Build a new instance.
86       * @param prefix prefix to use for parameter drivers names
87       * @param refStart reference interval start date
88       * @param refStop reference interval stop date
89       */
90      public ParameterDrivenDateIntervalDetector(final String prefix,
91                                                 final AbsoluteDate refStart, final AbsoluteDate refStop) {
92          this(0.5 * refStop.durationFrom(refStart), 1.0e-10, DEFAULT_MAX_ITER,
93               new StopOnDecreasing<ParameterDrivenDateIntervalDetector>(),
94               new DateDriver(refStart, prefix + START_SUFFIX, true),
95               new DateDriver(refStop, prefix + STOP_SUFFIX, false),
96               new DateDriver(refStart.shiftedBy(0.5 * refStop.durationFrom(refStart)), prefix + MEDIAN_SUFFIX, true),
97               new ParameterDriver(prefix + DURATION_SUFFIX, refStop.durationFrom(refStart), 1.0, 0.0, Double.POSITIVE_INFINITY));
98      }
99  
100     /** Private constructor with full parameters.
101      * <p>
102      * This constructor is private as users are expected to use the builder
103      * API with the various {@code withXxx()} methods to set up the instance
104      * in a readable manner without using a huge amount of parameters.
105      * </p>
106      * @param maxCheck maximum checking interval (s)
107      * @param threshold convergence threshold (s)
108      * @param maxIter maximum number of iterations in the event time search
109      * @param handler event handler to call at event occurrences
110      * @param start reference interval start driver
111      * @param stop reference interval stop driver
112      * @param median median date driver
113      * @param duration duration driver
114      */
115     private ParameterDrivenDateIntervalDetector(final double maxCheck, final double threshold, final int maxIter,
116                                                 final EventHandler<? super ParameterDrivenDateIntervalDetector> handler,
117                                                 final DateDriver start, final DateDriver stop,
118                                                 final DateDriver median, final ParameterDriver duration) {
119         super(maxCheck, threshold, maxIter, handler);
120         this.start    = start;
121         this.stop     = stop;
122         this.median   = median;
123         this.duration = duration;
124 
125         // set up delegation between drivers
126         replaceBindingObserver(start,    new StartObserver());
127         replaceBindingObserver(stop,     new StopObserver());
128         replaceBindingObserver(median,   new MedianObserver());
129         replaceBindingObserver(duration, new DurationObserver());
130 
131     }
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 
139         // remove the previous binding observers
140         final List<ParameterObserver> original = driver.
141                                                  getObservers().
142                                                  stream().
143                                                  filter(observer -> observer instanceof ParameterDrivenDateIntervalDetector.BindingObserver).
144                                                  collect(Collectors.toList());
145         original.forEach(observer -> driver.removeObserver(observer));
146 
147         driver.addObserver(bindingObserver);
148 
149     }
150 
151     /** {@inheritDoc} */
152     @Override
153     protected ParameterDrivenDateIntervalDetector create(final double newMaxCheck, final double newThreshold, final int newMaxIter,
154                                                          final EventHandler<? super ParameterDrivenDateIntervalDetector> newHandler) {
155         return new ParameterDrivenDateIntervalDetector(newMaxCheck, newThreshold, newMaxIter, newHandler,
156                                                        start, stop, median, duration);
157     }
158 
159     /** Get the driver for start date.
160      * <p>
161      * Note that the start date is automatically adjusted if either
162      * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
163      * are {@link ParameterDriver#isSelected() selected} and changed.
164      * </p>
165      * @return driver for start date
166      */
167     public DateDriver getStartDriver() {
168         return start;
169     }
170 
171     /** Get the driver for stop date.
172      * <p>
173      * Note that the stop date is automatically adjusted if either
174      * {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
175      * are {@link ParameterDriver#isSelected() selected} changed.
176      * </p>
177      * @return driver for stop date
178      */
179     public DateDriver getStopDriver() {
180         return stop;
181     }
182 
183     /** Get the driver for median date.
184      * <p>
185      * Note that the median date is automatically adjusted if either
186      * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
187      * are {@link ParameterDriver#isSelected() selected} changed.
188      * </p>
189      * @return driver for median date
190      */
191     public DateDriver getMedianDriver() {
192         return median;
193     }
194 
195     /** Get the driver for duration.
196      * <p>
197      * Note that the duration is automatically adjusted if either
198      * {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
199      * are {@link ParameterDriver#isSelected() selected} changed.
200      * </p>
201      * @return driver for duration
202      */
203     public ParameterDriver getDurationDriver() {
204         return duration;
205     }
206 
207     /** Compute the value of the switching function.
208      * <p>
209      * The function is positive for dates within the interval defined
210      * by applying the parameter drivers shifts to reference dates,
211      * and negative for dates outside of this interval. Note that
212      * if Δt_start - Δt_stop is less than ref_stop.durationFrom(ref_start),
213      * then the interval degenerates to empty and the function never
214      * reaches positive values.
215      * </p>
216      * @param s the current state information: date, kinematics, attitude
217      * @return value of the switching function
218      */
219     public double g(final SpacecraftState s) {
220         return FastMath.min(s.getDate().durationFrom(start.getDate()),
221                             stop.getDate().durationFrom(s.getDate()));
222     }
223 
224     /** Base observer. */
225     private abstract class BindingObserver implements ParameterObserver {
226 
227         /** {@inheritDoc} */
228         @Override
229         public void valueChanged(final double previousValue, final ParameterDriver driver) {
230             if (driver.isSelected()) {
231                 setDelta(driver.getValue() - previousValue);
232             }
233         }
234 
235         /** {@inheritDoc} */
236         @Override
237         public void selectionChanged(final boolean previousSelection, final ParameterDriver driver) {
238             if ((start.isSelected()  || stop.isSelected()) &&
239                 (median.isSelected() || duration.isSelected())) {
240                 throw new OrekitException(OrekitMessages.INCONSISTENT_SELECTION,
241                                           start.getName(), stop.getName(),
242                                           median.getName(), duration.getName());
243             }
244         }
245 
246         /** Change a value.
247          * @param delta change of value
248          */
249         protected abstract void setDelta(double delta);
250 
251     }
252 
253     /** Observer for start date. */
254     private class StartObserver extends BindingObserver {
255         /** {@inheritDoc} */
256         @Override
257         protected void setDelta(final double delta) {
258             median.setValue(median.getValue() + 0.5 * delta);
259             duration.setValue(duration.getValue() - delta);
260         }
261     }
262 
263     /** Observer for stop date. */
264     private class StopObserver extends BindingObserver {
265         /** {@inheritDoc} */
266         @Override
267         protected void setDelta(final double delta) {
268             median.setValue(median.getValue() + 0.5 * delta);
269             duration.setValue(duration.getValue() + delta);
270         }
271     }
272 
273     /** Observer for median date. */
274     private class MedianObserver extends BindingObserver {
275         /** {@inheritDoc} */
276         @Override
277         protected void setDelta(final double delta) {
278             start.setValue(start.getValue() + delta);
279             stop.setValue(stop.getValue() + delta);
280         }
281     }
282 
283     /** Observer for duration. */
284     private class DurationObserver extends BindingObserver {
285         /** {@inheritDoc} */
286         @Override
287         protected void setDelta(final double delta) {
288             start.setValue(start.getValue() - 0.5 * delta);
289             stop.setValue(stop.getValue() + 0.5 * delta);
290         }
291     }
292 
293 }