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