AttitudesSwitcher.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.attitudes;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.hipparchus.ode.events.Action;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.EventDetector;
import org.orekit.propagation.events.FieldEventDetector;
import org.orekit.time.AbsoluteDate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/** This classes manages a sequence of different attitude providers that are activated
* in turn according to switching events. Changes in attitude mode are instantaneous, so state derivatives need to be
* reset and the {@link Action} returned by the event handler is ignored.
* @author Luc Maisonobe
* @author Romain Serra
* @since 13.0
* @see AttitudesSequence
*/
public class AttitudesSwitcher extends AbstractSwitchingAttitudeProvider {
/** Switching events list. */
private final List<InstantaneousSwitch> instantaneousSwitches;
/** Constructor for an initially empty sequence.
*/
public AttitudesSwitcher() {
super();
instantaneousSwitches = new ArrayList<>();
}
/** Add a switching condition between two attitude providers.
* <p>
* The {@code past} and {@code future} attitude providers are defined with regard
* to the natural flow of time. This means that if the propagation is forward, the
* propagator will switch from {@code past} provider to {@code future} provider at
* event occurrence, but if the propagation is backward, the propagator will switch
* from {@code future} provider to {@code past} provider at event occurrence.
* </p>
* <p>
* An attitude provider may have several different switch events associated to
* it. Depending on which event is triggered, the appropriate provider is
* switched to.
* </p>
* <p>
* If the underlying detector has an event handler associated to it, this handler
* will be triggered (i.e. its {@link org.orekit.propagation.events.handlers.EventHandler#eventOccurred(SpacecraftState,
* EventDetector, boolean) eventOccurred} method will be called), <em>regardless</em>
* of the event really triggering an attitude switch or not. As an example, if an
* eclipse detector is used to switch from day to night attitude mode when entering
* eclipse, with {@code switchOnIncrease} set to {@code false} and {@code switchOnDecrease}
* set to {@code true}. Then a handler set directly at eclipse detector level would
* be triggered at both eclipse entry and eclipse exit, but attitude switch would
* occur <em>only</em> at eclipse entry.
* </p>
* @param past attitude provider applicable for times in the switch event occurrence past
* @param future attitude provider applicable for times in the switch event occurrence future
* @param switchEvent event triggering the attitude providers switch
* @param switchOnIncrease if true, switch is triggered on increasing event
* @param switchOnDecrease if true, switch is triggered on decreasing event
* @param switchHandler handler to call for notifying when switch occurs (may be null)
* @param <T> class type for the switch event
* @since 13.0
*/
public <T extends EventDetector> void addSwitchingCondition(final AttitudeProvider past,
final AttitudeProvider future,
final T switchEvent,
final boolean switchOnIncrease,
final boolean switchOnDecrease,
final AttitudeSwitchHandler switchHandler) {
// if it is the first switching condition, assume first active law is the past one
if (getActivated() == null) {
resetActiveProvider(past);
}
// add the switching condition
instantaneousSwitches.add(new InstantaneousSwitch(switchEvent, switchOnIncrease, switchOnDecrease,
past, future, switchHandler));
}
@Override
public Stream<EventDetector> getEventDetectors() {
return Stream.concat(instantaneousSwitches.stream().map(InstantaneousSwitch.class::cast), getEventDetectors(getParametersDrivers()));
}
@Override
public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(final Field<T> field) {
final Stream<FieldEventDetector<T>> switchesStream = instantaneousSwitches.stream().map(sw -> getFieldEventDetector(field, sw));
return Stream.concat(switchesStream, getFieldEventDetectors(field, getParametersDrivers()));
}
/** Switch specification. Reset derivatives due to instantaneous change of attitude. */
public class InstantaneousSwitch extends AbstractAttitudeSwitch {
/** Propagation direction. */
private boolean forward;
/**
* Simple constructor.
*
* @param event event
* @param switchOnIncrease if true, switch is triggered on increasing event
* @param switchOnDecrease if true, switch is triggered on decreasing event otherwise switch is triggered on
* decreasing event
* @param past attitude provider applicable for times in the switch event occurrence past
* @param future attitude provider applicable for times in the switch event occurrence future
* @param switchHandler handler to call for notifying when switch occurs (may be null)
*/
private InstantaneousSwitch(final EventDetector event, final boolean switchOnIncrease, final boolean switchOnDecrease,
final AttitudeProvider past, final AttitudeProvider future,
final AttitudeSwitchHandler switchHandler) {
super(event, switchOnIncrease, switchOnDecrease, past, future, switchHandler);
}
/** {@inheritDoc} */
@Override
public void init(final SpacecraftState s0, final AbsoluteDate t) {
super.init(s0, t);
// reset the transition parameters (this will be done once for each switch,
// despite doing it only once would have sufficient; it's not really a problem)
forward = t.durationFrom(s0.getDate()) >= 0.0;
if (getActivated().getSpansNumber() > 1) {
// remove switches that will be overridden during upcoming propagation (use margin to avoid erasing after creation)
if (forward) {
setActivated(getActivated().extractRange(AbsoluteDate.PAST_INFINITY, s0.getDate().shiftedBy(getDetectionSettings().getThreshold())));
} else {
setActivated(getActivated().extractRange(s0.getDate().shiftedBy(-getDetectionSettings().getThreshold()), AbsoluteDate.FUTURE_INFINITY));
}
}
}
/** {@inheritDoc} */
public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
final AbsoluteDate date = s.getDate();
if (getActivated().get(date) == (forward ? getPast() : getFuture()) &&
(increasing && isSwitchOnIncrease() || !increasing && isSwitchOnDecrease())) {
if (forward) {
// prepare future law
getActivated().addValidAfter(getFuture(), date, false);
// notify about the switch
if (getSwitchHandler() != null) {
getSwitchHandler().switchOccurred(getPast(), getFuture(), s);
}
} else {
// prepare past law
getActivated().addValidBefore(getPast(), date, false);
// notify about the switch
if (getSwitchHandler() != null) {
getSwitchHandler().switchOccurred(getFuture(), getPast(), s);
}
}
}
getDetector().getHandler().eventOccurred(s, getDetector(), increasing); // call but ignore output
return Action.RESET_DERIVATIVES;
}
}
}