AbstractSwitchingAttitudeProvider.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.geometry.euclidean.threed.FieldRotation;
import org.hipparchus.geometry.euclidean.threed.Rotation;
import org.hipparchus.ode.events.Action;
import org.orekit.frames.Frame;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.DetectorModifier;
import org.orekit.propagation.events.EventDetector;
import org.orekit.propagation.events.FieldEventDetector;
import org.orekit.propagation.events.FieldEventDetectionSettings;
import org.orekit.propagation.events.handlers.EventHandler;
import org.orekit.propagation.events.handlers.FieldEventHandler;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.utils.FieldPVCoordinatesProvider;
import org.orekit.utils.PVCoordinatesProvider;
import org.orekit.utils.TimeSpanMap;

/** This classes manages a sequence of different attitude providers that are activated
 * in turn according to switching events.
 * <p>Only one attitude provider in the sequence is in an active state. When one of
 * the switch event associated with the active provider occurs, the active provider becomes
 * the one specified with the event. A simple example is a provider for the sun lighted part
 * of the orbit and another provider for the eclipse time. When the sun lighted provider is active,
 * the eclipse entry event is checked and when it occurs the eclipse provider is activated.
 * When the eclipse provider is active, the eclipse exit event is checked and when it occurs
 * the sun lighted provider is activated again. This sequence is a simple loop.</p>
 * <p>An active attitude provider may have several switch events and next provider settings, leading
 * to different activation patterns depending on which events are triggered first. An example
 * of this feature is handling switches to safe mode if some contingency condition is met, in
 * addition to the nominal switches that correspond to proper operations. Another example
 * is handling of maneuver mode.
 * <p>
 * Note that this attitude provider is stateful, it keeps in memory the sequence of active
 * underlying providers with their switch dates and the transitions from one provider to
 * the other. This implies that this provider should <em>not</em> be shared among different
 * propagators at the same time, each propagator should use its own instance of this provider.
 * <p>
 * The sequence kept in memory is reset when {@link #resetActiveProvider(AttitudeProvider)}
 * is called, and only the specify provider is kept. The sequence is also partially
 * reset each time a propagation starts. If a new propagation is started after a first
 * propagation has been run, all the already computed switches that occur after propagation
 * start for forward propagation or before propagation start for backward propagation will
 * be erased. New switches will be computed and applied properly according to the new
 * propagation settings. The already computed switches that are not in covered are kept
 * in memory. This implies that if a propagation is interrupted and restarted in the
 * same direction, then attitude switches will remain in place, ensuring that even if the
 * interruption occurred in the middle of an attitude transition the second propagation will
 * properly complete the transition that was started by the first propagator.
 * </p>
 * @author Luc Maisonobe
 * @author Romain Serra
 * @since 13.0
 */
abstract class AbstractSwitchingAttitudeProvider implements AttitudeProvider {

    /** Providers that have been activated. */
    private TimeSpanMap<AttitudeProvider> activated;

    /** Constructor for an initially empty sequence.
     */
    protected AbstractSwitchingAttitudeProvider() {
        activated = null;
    }

    /** Reset the active provider.
     * <p>
     * Calling this method clears all already seen switch history,
     * so it should <em>not</em> be used during the propagation itself,
     * it is intended to be used only at start
     * </p>
     * @param provider provider to activate
     */
    public void resetActiveProvider(final AttitudeProvider provider) {
        activated = new TimeSpanMap<>(provider);
    }

    /**
     * Setter for map of activate attitude providers.
     * @param activated new map
     */
    protected void setActivated(final TimeSpanMap<AttitudeProvider> activated) {
        this.activated = activated;
    }

    /**
     * Getter for map of activated attitude providers.
     * @return map of providers
     */
    protected TimeSpanMap<AttitudeProvider> getActivated() {
        return activated;
    }

    /** {@inheritDoc} */
    @Override
    public Attitude getAttitude(final PVCoordinatesProvider pvProv,
                                final AbsoluteDate date, final Frame frame) {
        return activated.get(date).getAttitude(pvProv, date, frame);
    }

    /** {@inheritDoc} */
    @Override
    public <T extends CalculusFieldElement<T>> FieldAttitude<T> getAttitude(final FieldPVCoordinatesProvider<T> pvProv,
                                                                            final FieldAbsoluteDate<T> date,
                                                                            final Frame frame) {
        return activated.get(date.toAbsoluteDate()).getAttitude(pvProv, date, frame);
    }

    /** {@inheritDoc} */
    @Override
    public Rotation getAttitudeRotation(final PVCoordinatesProvider pvProv, final AbsoluteDate date, final Frame frame) {
        return activated.get(date).getAttitudeRotation(pvProv, date, frame);
    }

    @Override
    public <T extends CalculusFieldElement<T>> FieldRotation<T> getAttitudeRotation(final FieldPVCoordinatesProvider<T> pvProv,
                                                                                    final FieldAbsoluteDate<T> date,
                                                                                    final Frame frame) {
        return activated.get(date.toAbsoluteDate()).getAttitudeRotation(pvProv, date, frame);
    }

    /**
     * Method creating a Field attitude switch from a non-Field one.
     * @param field field
     * @param attitudeSwitch attitude switch
     * @return Field detector
     * @param <T> field type
     */
    protected <T extends CalculusFieldElement<T>> FieldEventDetector<T> getFieldEventDetector(final Field<T> field,
                                                                                              final AbstractAttitudeSwitch attitudeSwitch) {
        return new FieldEventDetector<T>() {

            /** {@inheritDoc} */
            @Override
            public void init(final FieldSpacecraftState<T> s0, final FieldAbsoluteDate<T> t) {
                attitudeSwitch.init(s0.toSpacecraftState(), t.toAbsoluteDate());
            }

            /** {@inheritDoc} */
            @Override
            public T g(final FieldSpacecraftState<T> s) {
                return field.getZero().newInstance(attitudeSwitch.g(s.toSpacecraftState()));
            }

            @Override
            public FieldEventDetectionSettings<T> getDetectionSettings() {
                return new FieldEventDetectionSettings<>(field, attitudeSwitch.getDetectionSettings());
            }

            /** {@inheritDoc} */
            @Override
            public FieldEventHandler<T> getHandler() {
                return new FieldEventHandler<T>() {
                    /** {@inheritDoc} */
                    @Override
                    public Action eventOccurred(final FieldSpacecraftState<T> s,
                                                final FieldEventDetector<T> detector,
                                                final boolean increasing) {
                        return attitudeSwitch.eventOccurred(s.toSpacecraftState(), attitudeSwitch, increasing);
                    }

                    /** {@inheritDoc} */
                    @Override
                    public FieldSpacecraftState<T> resetState(final FieldEventDetector<T> detector,
                                                              final FieldSpacecraftState<T> oldState) {
                        return new FieldSpacecraftState<>(field, attitudeSwitch.resetState(attitudeSwitch, oldState.toSpacecraftState()));
                    }
                };
            }

        };
    }

    /** Abstract class to manage attitude switches. */
    abstract static class AbstractAttitudeSwitch implements DetectorModifier, EventHandler {

        /**
         * Event direction triggering the switch.
         */
        private final boolean switchOnIncrease;

        /**
         * Event direction triggering the switch.
         */
        private final boolean switchOnDecrease;

        /**
         * Attitude provider applicable for times in the switch event occurrence past.
         */
        private final AttitudeProvider past;

        /**
         * Attitude provider applicable for times in the switch event occurrence future.
         */
        private final AttitudeProvider future;

        /**
         * Handler to call for notifying when switch occurs (may be null).
         */
        private final AttitudeSwitchHandler switchHandler;

        /** Wrapped event detector. */
        private final EventDetector event;

        /**
         * 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)
         */
        protected AbstractAttitudeSwitch(final EventDetector event, final boolean switchOnIncrease,
                                         final boolean switchOnDecrease, final AttitudeProvider past,
                                         final AttitudeProvider future, final AttitudeSwitchHandler switchHandler) {
            this.event = event;
            this.switchOnIncrease = switchOnIncrease;
            this.switchOnDecrease = switchOnDecrease;
            this.past = past;
            this.future = future;
            this.switchHandler = switchHandler;
        }

        /** {@inheritDoc} */
        @Override
        public EventDetector getDetector() {
            return event;
        }

        /**
         * Protected getter for switch handle.
         * @return switch handler
         */
        protected AttitudeSwitchHandler getSwitchHandler() {
            return switchHandler;
        }

        /**
         * Protected getter for future attitude provider.
         * @return future provider
         */
        protected AttitudeProvider getFuture() {
            return future;
        }

        /**
         * Protected getter for past attitude provider.
         * @return pas provider
         */
        protected AttitudeProvider getPast() {
            return past;
        }

        /**
         * Protected getter for switch-on-decrease flag.
         * @return flag
         */
        protected boolean isSwitchOnDecrease() {
            return switchOnDecrease;
        }

        /**
         * Protected getter for switch-on-increase flag.
         * @return flag
         */
        protected boolean isSwitchOnIncrease() {
            return switchOnIncrease;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public EventHandler getHandler() {
            return this;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public SpacecraftState resetState(final EventDetector detector, final SpacecraftState oldState) {
            // delegate to underlying event
            return getDetector().getHandler().resetState(getDetector(), oldState);
        }

    }

}