FieldEventEnablingPredicateFilter.java
/* Copyright 2002-2024 Luc Maisonobe
* 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.lang.reflect.Array;
import java.util.Arrays;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.ode.events.Action;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.propagation.events.handlers.FieldEventHandler;
import org.orekit.time.FieldAbsoluteDate;
/** Wrapper used to detect events only when enabled by an external predicated function.
*
* <p>General {@link FieldEventDetector events} are defined implicitly
* by a {@link FieldEventDetector#g(FieldSpacecraftState) g function} crossing
* zero. This implies that during an orbit propagation, events are
* triggered at all zero crossings.
* </p>
*
* <p>Sometimes, users would like to enable or disable events by themselves,
* for example to trigger them only for certain orbits, or to check elevation
* maximums only when elevation itself is positive (i.e. they want to
* discard elevation maximums below ground). In these cases, looking precisely
* for all events location and triggering events that will later be ignored
* is a waste of computing time.</p>
*
* <p>Users can wrap a regular {@link FieldEventDetector event detector} in
* an instance of this class and provide this wrapping instance to
* a {@link org.orekit.propagation.FieldPropagator}
* in order to avoid wasting time looking for uninteresting events.
* The wrapper will intercept the calls to the {@link
* FieldEventDetector#g(FieldSpacecraftState) g function} and to the {@link
* FieldEventHandler#eventOccurred(FieldSpacecraftState, FieldEventDetector, boolean)
* eventOccurred} method in order to ignore uninteresting events. The
* wrapped regular {@link FieldEventDetector event detector} will the see only
* the interesting events, i.e. either only events that occur when a
* user-provided event enabling predicate function is true, ignoring all events
* that occur when the event enabling predicate function is false. The number of
* calls to the {@link FieldEventDetector#g(FieldSpacecraftState) g function} will also be
* reduced.</p>
* @param <T> type of the field elements
* @see FieldEventSlopeFilter
* @since 12.0
*/
public class FieldEventEnablingPredicateFilter<T extends CalculusFieldElement<T>>
extends FieldAbstractDetector<FieldEventEnablingPredicateFilter<T>, T> {
/** Number of past transformers updates stored. */
private static final int HISTORY_SIZE = 100;
/** Wrapped event detector. */
private final FieldEventDetector<T> rawDetector;
/** Enabling predicate function. */
private final FieldEnablingPredicate<T> enabler;
/** Transformers of the g function. */
private final Transformer[] transformers;
/** Update time of the transformers. */
private final FieldAbsoluteDate<T>[] updates;
/** Indicator for forward integration. */
private boolean forward;
/** Extreme time encountered so far. */
private FieldAbsoluteDate<T> extremeT;
/** Detector function value at extremeT. */
private T extremeG;
/** Wrap an {@link EventDetector event detector}.
* @param rawDetector event detector to wrap
* @param enabler event enabling predicate function to use
*/
public FieldEventEnablingPredicateFilter(final FieldEventDetector<T> rawDetector,
final FieldEnablingPredicate<T> enabler) {
this(rawDetector.getMaxCheckInterval(), rawDetector.getThreshold(),
rawDetector.getMaxIterationCount(), new LocalHandler<>(),
rawDetector, enabler);
}
/** 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 maxCheck maximum checking interval
* @param threshold convergence threshold (s)
* @param maxIter maximum number of iterations in the event time search
* @param handler event handler to call at event occurrences
* @param rawDetector event detector to wrap
* @param enabler event enabling function to use
*/
@SuppressWarnings("unchecked")
protected FieldEventEnablingPredicateFilter(final FieldAdaptableInterval<T> maxCheck, final T threshold,
final int maxIter, final FieldEventHandler<T> handler,
final FieldEventDetector<T> rawDetector,
final FieldEnablingPredicate<T> enabler) {
super(new FieldEventDetectionSettings<>(maxCheck, threshold, maxIter), handler);
this.rawDetector = rawDetector;
this.enabler = enabler;
this.transformers = new Transformer[HISTORY_SIZE];
this.updates = (FieldAbsoluteDate<T>[]) Array.newInstance(FieldAbsoluteDate.class, HISTORY_SIZE);
}
/** {@inheritDoc} */
@Override
protected FieldEventEnablingPredicateFilter<T> create(final FieldAdaptableInterval<T> newMaxCheck, final T newThreshold,
final int newMaxIter,
final FieldEventHandler<T> newHandler) {
return new FieldEventEnablingPredicateFilter<>(newMaxCheck, newThreshold, newMaxIter, newHandler, rawDetector, enabler);
}
/**
* Get the wrapped raw detector.
* @return the wrapped raw detector
*/
public FieldEventDetector<T> getDetector() {
return rawDetector;
}
/** {@inheritDoc} */
public void init(final FieldSpacecraftState<T> s0,
final FieldAbsoluteDate<T> t) {
super.init(s0, t);
// delegate to raw detector
rawDetector.init(s0, t);
// initialize events triggering logic
forward = t.compareTo(s0.getDate()) >= 0;
extremeT = forward ?
FieldAbsoluteDate.getPastInfinity(t.getField()) :
FieldAbsoluteDate.getFutureInfinity(t.getField());
extremeG = t.getField().getZero().newInstance(Double.NaN);
Arrays.fill(transformers, Transformer.UNINITIALIZED);
Arrays.fill(updates, extremeT);
}
/** {@inheritDoc} */
public T g(final FieldSpacecraftState<T> s) {
final T rawG = rawDetector.g(s);
final boolean isEnabled = enabler.eventIsEnabled(s, rawDetector, rawG);
if (extremeG.isNaN()) {
extremeG = rawG;
}
// search which transformer should be applied to g
if (forward) {
final int last = transformers.length - 1;
if (extremeT.compareTo(s.getDate()) < 0) {
// we are at the forward end of the history
// check if enabled status has changed
final Transformer previous = transformers[last];
final Transformer next = selectTransformer(previous, extremeG, isEnabled);
if (next != previous) {
// there is a status change somewhere between extremeT and t.
// the new transformer is valid for t (this is how we have just computed
// it above), but it is in fact valid on both sides of the change, so
// it was already valid before t and even up to previous time. We store
// the switch at extremeT for safety, to ensure the previous transformer
// is not applied too close of the root
System.arraycopy(updates, 1, updates, 0, last);
System.arraycopy(transformers, 1, transformers, 0, last);
updates[last] = extremeT;
transformers[last] = next;
}
extremeT = s.getDate();
extremeG = rawG;
// apply the transform
return next.transformed(rawG);
} else {
// we are in the middle of the history
// select the transformer
for (int i = last; i > 0; --i) {
if (updates[i].compareTo(s.getDate()) <= 0) {
// apply the transform
return transformers[i].transformed(rawG);
}
}
return transformers[0].transformed(rawG);
}
} else {
if (s.getDate().compareTo(extremeT) < 0) {
// we are at the backward end of the history
// check if a new rough root has been crossed
final Transformer previous = transformers[0];
final Transformer next = selectTransformer(previous, extremeG, isEnabled);
if (next != previous) {
// there is a status change somewhere between extremeT and t.
// the new transformer is valid for t (this is how we have just computed
// it above), but it is in fact valid on both sides of the change, so
// it was already valid before t and even up to previous time. We store
// the switch at extremeT for safety, to ensure the previous transformer
// is not applied too close of the root
System.arraycopy(updates, 0, updates, 1, updates.length - 1);
System.arraycopy(transformers, 0, transformers, 1, transformers.length - 1);
updates[0] = extremeT;
transformers[0] = next;
}
extremeT = s.getDate();
extremeG = rawG;
// apply the transform
return next.transformed(rawG);
} else {
// we are in the middle of the history
// select the transformer
for (int i = 0; i < updates.length - 1; ++i) {
if (s.getDate().compareTo(updates[i]) <= 0) {
// apply the transform
return transformers[i].transformed(rawG);
}
}
return transformers[updates.length - 1].transformed(rawG);
}
}
}
/** Get next function transformer in the specified direction.
* @param previous transformer active on the previous point with respect
* to integration direction (may be null if no previous point is known)
* @param previousG value of the g function at the previous point
* @param isEnabled if true the event should be enabled now
* @return next transformer transformer
*/
private Transformer selectTransformer(final Transformer previous, final T previousG, final boolean isEnabled) {
if (isEnabled) {
// we need to select a transformer that can produce zero crossings,
// so it is either Transformer.PLUS or Transformer.MINUS
switch (previous) {
case UNINITIALIZED :
return Transformer.PLUS; // this initial choice is arbitrary, it could have been Transformer.MINUS
case MIN :
return previousG.getReal() >= 0 ? Transformer.MINUS : Transformer.PLUS;
case MAX :
return previousG.getReal() >= 0 ? Transformer.PLUS : Transformer.MINUS;
default :
return previous;
}
} else {
// we need to select a transformer that cannot produce any zero crossings,
// so it is either Transformer.MAX or Transformer.MIN
switch (previous) {
case UNINITIALIZED :
return Transformer.MAX; // this initial choice is arbitrary, it could have been Transformer.MIN
case PLUS :
return previousG.getReal() >= 0 ? Transformer.MAX : Transformer.MIN;
case MINUS :
return previousG.getReal() >= 0 ? Transformer.MIN : Transformer.MAX;
default :
return previous;
}
}
}
/** Local handler.
* @param <T> type of the field elements
*/
private static class LocalHandler<T extends CalculusFieldElement<T>> implements FieldEventHandler<T> {
/** {@inheritDoc} */
public Action eventOccurred(final FieldSpacecraftState<T> s, final FieldEventDetector<T> detector, final boolean increasing) {
final FieldEventEnablingPredicateFilter<T> ef = (FieldEventEnablingPredicateFilter<T>) detector;
final Transformer transformer = ef.forward ? ef.transformers[ef.transformers.length - 1] : ef.transformers[0];
return ef.rawDetector.getHandler().eventOccurred(s, ef.rawDetector, transformer == Transformer.PLUS ? increasing : !increasing);
}
/** {@inheritDoc} */
@Override
public FieldSpacecraftState<T> resetState(final FieldEventDetector<T> detector, final FieldSpacecraftState<T> oldState) {
final FieldEventEnablingPredicateFilter<T> ef = (FieldEventEnablingPredicateFilter<T>) detector;
return ef.rawDetector.getHandler().resetState(ef.rawDetector, oldState);
}
}
}