ParameterDrivenDateIntervalDetector.java
/* Copyright 2002-2024 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.propagation.events;
import java.util.List;
import java.util.stream.Collectors;
import org.hipparchus.util.FastMath;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.handlers.EventHandler;
import org.orekit.propagation.events.handlers.StopOnDecreasing;
import org.orekit.propagation.events.intervals.DateDetectionAdaptableIntervalFactory;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.DateDriver;
import org.orekit.utils.ParameterDriver;
import org.orekit.utils.ParameterObserver;
import org.orekit.utils.TimeSpanMap;
import org.orekit.utils.TimeSpanMap.Span;
/** Detector for date intervals that may be offset thanks to parameter drivers.
* <p>
* Two dual views can be used for date intervals: either start date/stop date or
* median date/duration. {@link #getStartDriver() start}/{@link #getStopDriver() stop}
* drivers and {@link #getMedianDriver() median}/{@link #getDurationDriver() duration}
* drivers work in pair. Both drivers in one pair can be selected and their changes will
* be propagated to the other pair, but attempting to select drivers in both
* pairs at the same time will trigger an exception. Changing the value of a driver
* that is not selected should be avoided as it leads to inconsistencies between the pairs.
* </p>. Warning, startDate driver, stopDate driver, duration driver and medianDate driver
* must all have the same number of values to estimate (same number of span in valueSpanMap), that is is to
* say that the {@link org.orekit.utils.ParameterDriver#addSpans(AbsoluteDate, AbsoluteDate, double)}
* should be called with same arguments.
* @see org.orekit.propagation.Propagator#addEventDetector(EventDetector)
* @author Luc Maisonobe
* @since 11.1
*/
public class ParameterDrivenDateIntervalDetector extends AbstractDetector<ParameterDrivenDateIntervalDetector> {
/** Default suffix for start driver. */
public static final String START_SUFFIX = "_START";
/** Default suffix for stop driver. */
public static final String STOP_SUFFIX = "_STOP";
/** Default suffix for median driver. */
public static final String MEDIAN_SUFFIX = "_MEDIAN";
/** Default suffix for duration driver. */
public static final String DURATION_SUFFIX = "_DURATION";
/** Reference interval start driver. */
private final DateDriver start;
/** Reference interval stop driver. */
private final DateDriver stop;
/** Median date driver. */
private final DateDriver median;
/** Duration driver. */
private final ParameterDriver duration;
/** Build a new instance.
* @param prefix prefix to use for parameter drivers names
* @param refMedian reference interval median date
* @param refDuration reference duration
*/
public ParameterDrivenDateIntervalDetector(final String prefix,
final AbsoluteDate refMedian, final double refDuration) {
this(prefix,
refMedian.shiftedBy(-0.5 * refDuration),
refMedian.shiftedBy(0.5 * refDuration));
}
/** Build a new instance.
* @param prefix prefix to use for parameter drivers names
* @param refStart reference interval start date
* @param refStop reference interval stop date
*/
public ParameterDrivenDateIntervalDetector(final String prefix,
final AbsoluteDate refStart, final AbsoluteDate refStop) {
this(getDefaultDetectionSettings(refStart, refStop),
new StopOnDecreasing(),
new DateDriver(refStart, prefix + START_SUFFIX, true),
new DateDriver(refStop, prefix + STOP_SUFFIX, false),
new DateDriver(refStart.shiftedBy(0.5 * refStop.durationFrom(refStart)), prefix + MEDIAN_SUFFIX, true),
new ParameterDriver(prefix + DURATION_SUFFIX, refStop.durationFrom(refStart), 1.0, 0.0, Double.POSITIVE_INFINITY));
}
/** Protected constructor with full parameters.
* <p>
* This constructor is not public as users are expected to use the builder
* API with the various {@code withXxx()} methods to set up the instance
* in a readable manner without using a huge amount of parameters.
* </p>
* @param detectionSettings event detection settings
* @param handler event handler to call at event occurrences
* @param start reference interval start driver
* @param stop reference interval stop driver
* @param median median date driver
* @param duration duration driver
* @since 13.0
*/
protected ParameterDrivenDateIntervalDetector(final EventDetectionSettings detectionSettings,
final EventHandler handler,
final DateDriver start, final DateDriver stop,
final DateDriver median, final ParameterDriver duration) {
super(detectionSettings, handler);
this.start = start;
this.stop = stop;
this.median = median;
this.duration = duration;
// set up delegation between drivers
replaceBindingObserver(start, new StartObserver());
replaceBindingObserver(stop, new StopObserver());
replaceBindingObserver(median, new MedianObserver());
replaceBindingObserver(duration, new DurationObserver());
}
/**
* Get default detection settings.
* @param refStart reference interval start date
* @param refStop reference interval stop date
* @return default detection settings
* @since 13.0
*/
public static EventDetectionSettings getDefaultDetectionSettings(final AbsoluteDate refStart,
final AbsoluteDate refStop) {
return new EventDetectionSettings(DateDetectionAdaptableIntervalFactory.getDatesDetectionInterval(refStart, refStop),
DateDetector.DEFAULT_THRESHOLD, EventDetectionSettings.DEFAULT_MAX_ITER);
}
/** Replace binding observers.
* @param driver driver for whose binding observers should be replaced
* @param bindingObserver new binding observer
*/
private void replaceBindingObserver(final ParameterDriver driver, final BindingObserver bindingObserver) {
// remove the previous binding observers
final List<ParameterObserver> original = driver.
getObservers().
stream().
filter(observer -> observer instanceof ParameterDrivenDateIntervalDetector.BindingObserver).
collect(Collectors.toList());
original.forEach(driver::removeObserver);
driver.addObserver(bindingObserver);
}
/** {@inheritDoc} */
@Override
protected ParameterDrivenDateIntervalDetector create(final EventDetectionSettings detectionSettings,
final EventHandler newHandler) {
return new ParameterDrivenDateIntervalDetector(detectionSettings, newHandler, start, stop, median, duration);
}
/** Get the driver for start date.
* <p>
* Note that the start date is automatically adjusted if either
* {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
* are {@link ParameterDriver#isSelected() selected} and changed.
* </p>
* @return driver for start date
*/
public DateDriver getStartDriver() {
return start;
}
/** Get the driver for stop date.
* <p>
* Note that the stop date is automatically adjusted if either
* {@link #getMedianDriver() median date} or {@link #getDurationDriver() duration}
* are {@link ParameterDriver#isSelected() selected} changed.
* </p>
* @return driver for stop date
*/
public DateDriver getStopDriver() {
return stop;
}
/** Get the driver for median date.
* <p>
* Note that the median date is automatically adjusted if either
* {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
* are {@link ParameterDriver#isSelected() selected} changed.
* </p>
* @return driver for median date
*/
public DateDriver getMedianDriver() {
return median;
}
/** Get the driver for duration.
* <p>
* Note that the duration is automatically adjusted if either
* {@link #getStartDriver()} start date or {@link #getStopDriver() stop date}
* are {@link ParameterDriver#isSelected() selected} changed.
* </p>
* @return driver for duration
*/
public ParameterDriver getDurationDriver() {
return duration;
}
/** Compute the value of the switching function.
* <p>
* The function is positive for dates within the interval defined
* by applying the parameter drivers shifts to reference dates,
* and negative for dates outside of this interval. Note that
* if Δt_start - Δt_stop is less than ref_stop.durationFrom(ref_start),
* then the interval degenerates to empty and the function never
* reaches positive values.
* </p>
* @param s the current state information: date, kinematics, attitude
* @return value of the switching function
*/
public double g(final SpacecraftState s) {
return FastMath.min(s.getDate().durationFrom(start.getDate()),
stop.getDate().durationFrom(s.getDate()));
}
/** Base observer. */
private abstract class BindingObserver implements ParameterObserver {
/** {@inheritDoc} */
@Override
public void valueChanged(final double previousValue, final ParameterDriver driver, final AbsoluteDate date) {
if (driver.isSelected()) {
setDelta(driver.getValue(date) - previousValue, date);
}
}
/** {@inheritDoc} */
@Override
public void valueSpanMapChanged(final TimeSpanMap<Double> previousValue, final ParameterDriver driver) {
if (driver.isSelected()) {
for (Span<Double> span = driver.getValueSpanMap().getFirstSpan(); span != null; span = span.next()) {
setDelta(span.getData() - previousValue.get(span.getStart()), span.getStart());
}
}
}
/** {@inheritDoc} */
@Override
public void selectionChanged(final boolean previousSelection, final ParameterDriver driver) {
if ((start.isSelected() || stop.isSelected()) &&
(median.isSelected() || duration.isSelected())) {
throw new OrekitException(OrekitMessages.INCONSISTENT_SELECTION,
start.getName(), stop.getName(),
median.getName(), duration.getName());
}
}
/** Change a value.
* @param delta change of value
* @param date date at which the delta wants to be set
*/
protected abstract void setDelta(double delta, AbsoluteDate date);
}
/** Observer for start date. */
private class StartObserver extends BindingObserver {
/** {@inheritDoc} */
@Override
protected void setDelta(final double delta, final AbsoluteDate date) {
// date driver has no validity period, only 1 value is estimated
// over the all interval so there is no problem for calling getValue with null argument
// or any date, it would give the same result as there is only 1 span on the valueSpanMap
// of the driver
median.setValue(median.getValue(date) + 0.5 * delta, date);
duration.setValue(duration.getValue(date) - delta, date);
}
}
/** Observer for stop date. */
private class StopObserver extends BindingObserver {
/** {@inheritDoc} */
@Override
protected void setDelta(final double delta, final AbsoluteDate date) {
// date driver has no validity period, only 1 value is estimated
// over the all interval so there is no problem for calling getValue with null argument
// or any date, it would give the same result as there is only 1 span on the valueSpanMap
// of the driver
median.setValue(median.getValue(date) + 0.5 * delta, date);
duration.setValue(duration.getValue(date) + delta, date);
}
}
/** Observer for median date. */
private class MedianObserver extends BindingObserver {
/** {@inheritDoc} */
@Override
protected void setDelta(final double delta, final AbsoluteDate date) {
// date driver has no validity period, only 1 value is estimated
// over the all interval so there is no problem for calling getValue with null argument
// or any date, it would give the same result as there is only 1 span on the valueSpanMap
// of the driver
start.setValue(start.getValue(date) + delta, date);
stop.setValue(stop.getValue(date) + delta, date);
}
}
/** Observer for duration. */
private class DurationObserver extends BindingObserver {
/** {@inheritDoc} */
@Override
protected void setDelta(final double delta, final AbsoluteDate date) {
// date driver has no validity period, only 1 value is estimated
// over the all interval so there is no problem for calling getValue with null argument
// or any date, it would give the same result as there is only 1 span on the valueSpanMap
// of the driver
start.setValue(start.getValue(date) - 0.5 * delta, date);
stop.setValue(stop.getValue(date) + 0.5 * delta, date);
}
}
}