StartStopEventsTrigger.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.forces.maneuvers.trigger;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.hipparchus.ode.events.Action;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.AbstractDetector;
import org.orekit.propagation.events.EventDetector;
import org.orekit.propagation.events.FieldAbstractDetector;
import org.orekit.propagation.events.intervals.FieldAdaptableInterval;
import org.orekit.propagation.events.FieldEventDetector;
import org.orekit.propagation.events.handlers.EventHandler;
import org.orekit.propagation.events.handlers.FieldEventHandler;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
/**
* Maneuver triggers based on a pair of event detectors that defines firing start and stop.
* <p>
* The thruster starts firing when the start detector becomes
* positive. The thruster stops firing when the stop detector becomes positive.
* The 2 detectors should not be positive at the same time. A date detector is
* not suited as it does not delimit an interval. They can be both negative at
* the same time.
* </p>
* @param <A> type of the start detector
* @param <O> type of the stop detector
* @see IntervalEventTrigger
* @author Luc Maisonobe
* @since 11.1
*/
public abstract class StartStopEventsTrigger<A extends AbstractDetector<A>, O extends AbstractDetector<O>> extends AbstractManeuverTriggers {
/** Start detector. */
private final A startDetector;
/** Stop detector. */
private final O stopDetector;
/** Cached field-based start detectors. */
private final transient Map<Field<? extends CalculusFieldElement<?>>, FieldEventDetector<? extends CalculusFieldElement<?>>> cachedStart;
/** Cached field-based stop detectors. */
private final transient Map<Field<? extends CalculusFieldElement<?>>, FieldEventDetector<? extends CalculusFieldElement<?>>> cachedStop;
/** Simple constructor.
* <p>
* Note that the {@code startDetector} and {@code stopDetector} passed as an argument are used only
* as a <em>prototypes</em> from which new detectors will be built using their
* {@link AbstractDetector#withHandler(EventHandler) withHandler} methods to
* set up internal handlers. The original event handlers from the prototype
* will be <em>ignored</em> and never called.
* </p>
* <p>
* If the trigger is used in a {@link org.orekit.propagation.FieldPropagator field-based propagation},
* the detector will be automatically converted to a field equivalent. Beware however that the
* {@link FieldEventHandler#eventOccurred(FieldSpacecraftState, FieldEventDetector, boolean) eventOccurred}
* of the converted propagator <em>will</em> call the method with the same name in the prototype
* detector, in order to get the correct return value.
* </p>
* @param prototypeStartDetector prototype detector for firing start
* @param prototypeStopDetector prototype detector for firing stop
*/
protected StartStopEventsTrigger(final A prototypeStartDetector, final O prototypeStopDetector) {
this.startDetector = prototypeStartDetector.withHandler(new StartHandler());
this.stopDetector = prototypeStopDetector.withHandler(new StopHandler());
this.cachedStart = new HashMap<>();
this.cachedStop = new HashMap<>();
}
/**
* Getter for the firing start detector.
* @return firing start detector
*/
public A getStartDetector() {
return startDetector;
}
/**
* Getter for the firing stop detector.
* @return firing stop detector
*/
public O getStopDetector() {
return stopDetector;
}
/** {@inheritDoc} */
@Override
public void init(final SpacecraftState initialState, final AbsoluteDate target) {
startDetector.init(initialState, target);
stopDetector.init(initialState, target);
super.init(initialState, target);
}
/** {@inheritDoc} */
@Override
protected boolean isFiringOnInitialState(final SpacecraftState initialState, final boolean isForward) {
final double startG = startDetector.g(initialState);
if (startG == 0) {
final boolean increasing = startDetector.g(initialState.shiftedBy(2 * startDetector.getThreshold())) > 0;
if (increasing) {
// we are at maneuver start
notifyResetters(initialState, true);
// if propagating forward, we start firing
return isForward;
} else {
// not a meaningful crossing
return false;
}
} else if (startG < 0) {
// we are before start
return false;
} else {
// we are after start
final double stopG = stopDetector.g(initialState);
if (stopG == 0) {
final boolean increasing = stopDetector.g(initialState.shiftedBy(2 * stopDetector.getThreshold())) > 0;
if (increasing) {
// we are at maneuver end
notifyResetters(initialState, false);
// if propagating backward, we start firing
return !isForward;
} else {
// not a meaningful crossing
return false;
}
} else if (stopG > 0) {
// we are after stop
return false;
} else {
// we are between start and stop
return true;
}
}
}
/** {@inheritDoc} */
@Override
public Stream<EventDetector> getEventDetectors() {
return Stream.of(startDetector, stopDetector);
}
/** {@inheritDoc} */
@Override
public <S extends CalculusFieldElement<S>> Stream<FieldEventDetector<S>> getFieldEventDetectors(final Field<S> field) {
// get the field version of the start detector
@SuppressWarnings("unchecked")
FieldEventDetector<S> fStart = (FieldEventDetector<S>) cachedStart.get(field);
if (fStart == null) {
fStart = convertAndSetUpStartHandler(field);
cachedStart.put(field, fStart);
}
// get the field version of the stop detector
@SuppressWarnings("unchecked")
FieldEventDetector<S> fStop = (FieldEventDetector<S>) cachedStop.get(field);
if (fStop == null) {
fStop = convertAndSetUpStopHandler(field);
cachedStop.put(field, fStop);
}
return Stream.of(fStart, fStop);
}
/** Convert a detector and set up new handler.
* <p>
* This method is not inlined in {@link #getFieldEventDetectors(Field)} because the
* parameterized types confuses the Java compiler.
* </p>
* @param field field to which the state belongs
* @param <D> type of the event detector
* @param <S> type of the field elements
* @return converted firing intervals detector
*/
private <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>> D convertAndSetUpStartHandler(final Field<S> field) {
final FieldAbstractDetector<D, S> converted = convertStartDetector(field, startDetector);
final FieldAdaptableInterval<S> maxCheck = (s, isForward) -> startDetector.getMaxCheckInterval().currentInterval(s.toSpacecraftState(), isForward);
return converted.
withMaxCheck(maxCheck).
withThreshold(field.getZero().newInstance(startDetector.getThreshold())).
withHandler(new FieldStartHandler<>());
}
/** Convert a detector and set up new handler.
* <p>
* This method is not inlined in {@link #getFieldEventDetectors(Field)} because the
* parameterized types confuses the Java compiler.
* </p>
* @param field field to which the state belongs
* @param <D> type of the event detector
* @param <S> type of the field elements
* @return converted firing intervals detector
*/
private <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>> D convertAndSetUpStopHandler(final Field<S> field) {
final FieldAbstractDetector<D, S> converted = convertStopDetector(field, stopDetector);
final FieldAdaptableInterval<S> maxCheck = (s, isForward) -> stopDetector.getMaxCheckInterval().currentInterval(s.toSpacecraftState(), isForward);
return converted.
withMaxCheck(maxCheck).
withThreshold(field.getZero().newInstance(stopDetector.getThreshold())).
withHandler(new FieldStopHandler<>());
}
/** Convert a primitive firing start detector into a field firing start detector.
* <p>
* There is not need to set up {@link FieldAbstractDetector#withMaxCheck(FieldAdaptableInterval) withMaxCheck},
* {@link FieldAbstractDetector#withThreshold(CalculusFieldElement) withThreshold}, or
* {@link FieldAbstractDetector#withHandler(org.orekit.propagation.events.handlers.FieldEventHandler) withHandler}
* in the converted detector, this will be done by caller.
* </p>
* <p>
* A skeleton implementation of this method to convert some {@code XyzDetector} into {@code FieldXyzDetector},
* considering these detectors are created from a date and a number parameter is:
* </p>
* <pre>{@code
* protected <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>>
* FieldAbstractDetector<D, S> convertStartDetector(final Field<S> field, final XyzDetector detector) {
*
* final FieldAbsoluteDate<S> date = new FieldAbsoluteDate<>(field, detector.getDate());
* final S param = field.getZero().newInstance(detector.getParam());
*
* final FieldAbstractDetector<D, S> converted = (FieldAbstractDetector<D, S>) new FieldXyzDetector<>(date, param);
* return converted;
*
* }
* }
* </pre>
* @param field field to which the state belongs
* @param detector primitive firing start detector to convert
* @param <D> type of the event detector
* @param <S> type of the field elements
* @return converted firing start detector
*/
protected abstract <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>> FieldAbstractDetector<D, S>
convertStartDetector(Field<S> field, A detector);
/** Convert a primitive firing stop detector into a field firing stop detector.
* <p>
* There is not need to set up {@link FieldAbstractDetector#withMaxCheck(FieldAdaptableInterval) withMaxCheck},
* {@link FieldAbstractDetector#withThreshold(CalculusFieldElement) withThreshold}, or
* {@link FieldAbstractDetector#withHandler(org.orekit.propagation.events.handlers.FieldEventHandler) withHandler}
* in the converted detector, this will be done by caller.
* </p>
* <p>
* A skeleton implementation of this method to convert some {@code XyzDetector} into {@code FieldXyzDetector},
* considering these detectors are created from a date and a number parameter is:
* </p>
* <pre>{@code
* protected <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>>
* FieldAbstractDetector<D, S> convertStopDetector(final Field<S> field, final XyzDetector detector) {
*
* final FieldAbsoluteDate<S> date = new FieldAbsoluteDate<>(field, detector.getDate());
* final S param = field.getZero().newInstance(detector.getParam());
*
* final FieldAbstractDetector<D, S> converted = (FieldAbstractDetector<D, S>) new FieldXyzDetector<>(date, param);
* return converted;
*
* }
* }
* </pre>
* @param field field to which the state belongs
* @param detector primitive firing stop detector to convert
* @param <D> type of the event detector
* @param <S> type of the field elements
* @return converted firing stop detector
*/
protected abstract <D extends FieldAbstractDetector<D, S>, S extends CalculusFieldElement<S>> FieldAbstractDetector<D, S>
convertStopDetector(Field<S> field, O detector);
/** Local handler for start triggers. */
private class StartHandler implements EventHandler {
/** Propagation direction. */
private boolean forward;
/** {@inheritDoc} */
@Override
public void init(final SpacecraftState initialState, final AbsoluteDate target, final EventDetector detector) {
forward = target.isAfterOrEqualTo(initialState);
initializeResetters(initialState, target);
}
/** {@inheritDoc} */
@Override
public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
if (increasing) {
// the event is meaningful for maneuver firing
if (forward) {
getFirings().addValidAfter(true, s.getDate(), false);
} else {
getFirings().addValidBefore(false, s.getDate(), false);
}
notifyResetters(s, true);
return Action.RESET_STATE;
} else {
// the event is not meaningful for maneuver firing
return Action.CONTINUE;
}
}
/** {@inheritDoc} */
@Override
public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
return applyResetters(oldState);
}
}
/** Local handler for stop triggers. */
private class StopHandler implements EventHandler {
/** Propagation direction. */
private boolean forward;
/** {@inheritDoc} */
@Override
public void init(final SpacecraftState initialState, final AbsoluteDate target, final EventDetector detector) {
forward = target.isAfterOrEqualTo(initialState);
initializeResetters(initialState, target);
}
/** {@inheritDoc} */
@Override
public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
if (increasing) {
// the event is meaningful for maneuver firing
if (forward) {
getFirings().addValidAfter(false, s.getDate(), false);
} else {
getFirings().addValidBefore(true, s.getDate(), false);
}
notifyResetters(s, false);
return Action.RESET_STATE;
} else {
// the event is not meaningful for maneuver firing
return Action.CONTINUE;
}
}
/** {@inheritDoc} */
@Override
public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
return applyResetters(oldState);
}
}
/** Local handler for start triggers.
* @param <S> type of the field elements
*/
private class FieldStartHandler<S extends CalculusFieldElement<S>> implements FieldEventHandler<S> {
/** Propagation direction. */
private boolean forward;
/** {@inheritDoc} */
@Override
public void init(final FieldSpacecraftState<S> initialState,
final FieldAbsoluteDate<S> target,
final FieldEventDetector<S> detector) {
forward = target.isAfterOrEqualTo(initialState);
initializeResetters(initialState, target);
}
/** {@inheritDoc} */
@Override
public Action eventOccurred(final FieldSpacecraftState<S> s, final FieldEventDetector<S> detector, final boolean increasing) {
if (increasing) {
// the event is meaningful for maneuver firing
if (forward) {
getFirings().addValidAfter(true, s.getDate().toAbsoluteDate(), false);
} else {
getFirings().addValidBefore(false, s.getDate().toAbsoluteDate(), false);
}
notifyResetters(s, true);
return Action.RESET_STATE;
} else {
// the event is not meaningful for maneuver firing
return Action.CONTINUE;
}
}
/** {@inheritDoc} */
@Override
public FieldSpacecraftState<S> resetState(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
return applyResetters(oldState);
}
}
/** Local handler for stop triggers.
* @param <S> type of the field elements
*/
private class FieldStopHandler<S extends CalculusFieldElement<S>> implements FieldEventHandler<S> {
/** Propagation direction. */
private boolean forward;
/** {@inheritDoc} */
@Override
public void init(final FieldSpacecraftState<S> initialState,
final FieldAbsoluteDate<S> target,
final FieldEventDetector<S> detector) {
forward = target.isAfterOrEqualTo(initialState);
initializeResetters(initialState, target);
}
/** {@inheritDoc} */
@Override
public Action eventOccurred(final FieldSpacecraftState<S> s, final FieldEventDetector<S> detector, final boolean increasing) {
if (increasing) {
// the event is meaningful for maneuver firing
if (forward) {
getFirings().addValidAfter(false, s.getDate().toAbsoluteDate(), false);
} else {
getFirings().addValidBefore(true, s.getDate().toAbsoluteDate(), false);
}
notifyResetters(s, false);
return Action.RESET_STATE;
} else {
// the event is not meaningful for maneuver firing
return Action.CONTINUE;
}
}
/** {@inheritDoc} */
@Override
public FieldSpacecraftState<S> resetState(final FieldEventDetector<S> detector, final FieldSpacecraftState<S> oldState) {
return applyResetters(oldState);
}
}
}