DateDetectionAdaptableIntervalFactory.java
/* Copyright 2022-2024 Romain Serra
* 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.intervals;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.util.FastMath;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.ChronologicalComparator;
import org.orekit.time.FieldTimeStamped;
import org.orekit.time.TimeStamped;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Factory for adaptable interval tuned for date(s) detection.
*
* @see org.orekit.propagation.events.DateDetector
* @see org.orekit.propagation.events.FieldDateDetector
* @author Romain Serra
* @since 13.0
*/
public class DateDetectionAdaptableIntervalFactory {
/** Default value for max check. */
public static final double DEFAULT_MAX_CHECK = 1.0e10;
/**
* Private constructor.
*/
private DateDetectionAdaptableIntervalFactory() {
// factory class
}
/**
* Return a candidate {@link AdaptableInterval} for single date detection.
* @return adaptable interval
*/
public static AdaptableInterval getSingleDateDetectionAdaptableInterval() {
return AdaptableInterval.of(DEFAULT_MAX_CHECK);
}
/**
* Return a candidate {@link AdaptableInterval} for multiple dates detection with a constant max. check.
* @param timeStampeds event dates
* @return adaptable interval
*/
public static AdaptableInterval getDatesDetectionConstantInterval(final TimeStamped... timeStampeds) {
if (timeStampeds == null || timeStampeds.length < 2) {
return getSingleDateDetectionAdaptableInterval();
}
return AdaptableInterval.of(getMinGap(timeStampeds) * 0.5);
}
/**
* Return a candidate {@link AdaptableInterval} for multiple dates detection.
* @param timeStampeds event dates
* @return adaptable interval
*/
public static AdaptableInterval getDatesDetectionInterval(final TimeStamped... timeStampeds) {
if (timeStampeds == null || timeStampeds.length < 2) {
return getSingleDateDetectionAdaptableInterval();
}
final double minGap = getMinGap(timeStampeds);
final SortedSet<TimeStamped> sortedSet = new TreeSet<>(new ChronologicalComparator());
sortedSet.addAll(Arrays.asList(timeStampeds));
return (state, isForward) -> {
final AbsoluteDate date = state.getDate();
double minDistance = Double.POSITIVE_INFINITY;
if (isForward) {
for (final TimeStamped ts : sortedSet) {
final AbsoluteDate nextDate = ts.getDate();
if (date.isBefore(nextDate)) {
minDistance = nextDate.durationFrom(date);
break;
}
}
} else {
final List<TimeStamped> inverted = new ArrayList<>(sortedSet);
Collections.reverse(inverted);
for (final TimeStamped ts : inverted) {
final AbsoluteDate nextDate = ts.getDate();
if (date.isAfter(nextDate)) {
minDistance = date.durationFrom(nextDate);
break;
}
}
}
return FastMath.abs(minDistance) + minGap / 2;
};
}
/**
* Return a candidate {@link FieldAdaptableInterval} for single date detection.
* @param <T> field type
* @return adaptable interval
*/
public static <T extends CalculusFieldElement<T>> FieldAdaptableInterval<T> getSingleDateDetectionFieldAdaptableInterval() {
return FieldAdaptableInterval.of(DEFAULT_MAX_CHECK);
}
/**
* Return a candidate {@link FieldAdaptableInterval} for multiple dates detection with a constant max. check.
* @param timeStampeds event dates
* @param <T> field type
* @return adaptable interval
*/
@SafeVarargs
public static <T extends CalculusFieldElement<T>> FieldAdaptableInterval<T> getDatesDetectionFieldConstantInterval(final FieldTimeStamped<T>... timeStampeds) {
if (timeStampeds == null || timeStampeds.length < 2) {
return getSingleDateDetectionFieldAdaptableInterval();
}
final double minGap = getMinGap(Arrays.stream(timeStampeds).map(t -> (TimeStamped) t.getDate().toAbsoluteDate())
.toArray(TimeStamped[]::new));
return FieldAdaptableInterval.of(minGap * 0.5);
}
/**
* Return a candidate {@link FieldAdaptableInterval} for multiple dates detection.
* @param timeStampeds event dates
* @param <T> field type
* @return adaptable interval
*/
@SafeVarargs
public static <T extends CalculusFieldElement<T>> FieldAdaptableInterval<T> getDatesDetectionFieldInterval(final FieldTimeStamped<T>... timeStampeds) {
if (timeStampeds == null || timeStampeds.length < 2) {
return getSingleDateDetectionFieldAdaptableInterval();
}
return FieldAdaptableInterval.of(getDatesDetectionInterval(Arrays.stream(timeStampeds)
.map(t -> (TimeStamped) t.getDate().toAbsoluteDate()).toArray(TimeStamped[]::new)));
}
/**
* Compute min. gap between dated objects if applicable. It ignores duplicates.
* @param timeStampeds time stamped objects
* @return minimum gap
*/
public static double getMinGap(final TimeStamped... timeStampeds) {
double minGap = DEFAULT_MAX_CHECK;
for (final TimeStamped timeStamped : timeStampeds) {
final Optional<Double> minDistance = Arrays.stream(timeStampeds)
.map(t -> (!t.getDate().isEqualTo(timeStamped.getDate())) ? FastMath.abs(t.durationFrom(timeStamped)) : Double.POSITIVE_INFINITY)
.min(Double::compareTo);
if (minDistance.isPresent()) {
minGap = FastMath.min(minGap, minDistance.get());
}
}
return minGap;
}
}