AttitudesSequence.java

  1. /* Copyright 2002-2025 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.attitudes;

  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.List;
  21. import java.util.stream.Stream;

  22. import org.hipparchus.CalculusFieldElement;
  23. import org.hipparchus.Field;
  24. import org.hipparchus.ode.events.Action;
  25. import org.orekit.errors.OrekitException;
  26. import org.orekit.errors.OrekitMessages;
  27. import org.orekit.frames.Frame;
  28. import org.orekit.orbits.Orbit;
  29. import org.orekit.propagation.SpacecraftState;
  30. import org.orekit.propagation.events.EventDetector;
  31. import org.orekit.propagation.events.FieldEventDetector;
  32. import org.orekit.time.AbsoluteDate;
  33. import org.orekit.time.FieldAbsoluteDate;
  34. import org.orekit.time.FieldTimeInterpolator;
  35. import org.orekit.time.TimeInterpolator;
  36. import org.orekit.utils.AbsolutePVCoordinates;
  37. import org.orekit.utils.AngularDerivativesFilter;
  38. import org.orekit.utils.FieldPVCoordinatesProvider;
  39. import org.orekit.utils.DataDictionary;
  40. import org.orekit.utils.PVCoordinatesProvider;
  41. import org.orekit.utils.TimeStampedAngularCoordinates;
  42. import org.orekit.utils.TimeStampedAngularCoordinatesHermiteInterpolator;
  43. import org.orekit.utils.TimeStampedFieldAngularCoordinates;
  44. import org.orekit.utils.TimeStampedFieldAngularCoordinatesHermiteInterpolator;

  45. /** This classes manages a sequence of different attitude providers that are activated
  46.  * in turn according to switching events. It includes non-zero transition durations between subsequent modes.
  47.  * @author Luc Maisonobe
  48.  * @since 5.1
  49.  * @see AttitudesSwitcher
  50.  */
  51. public class AttitudesSequence extends AbstractSwitchingAttitudeProvider {

  52.     /** Switching events list. */
  53.     private final List<Switch> switches;

  54.     /** Constructor for an initially empty sequence.
  55.      */
  56.     public AttitudesSequence() {
  57.         super();
  58.         switches = new ArrayList<>();
  59.     }

  60.     /** Add a switching condition between two attitude providers.
  61.      * <p>
  62.      * The {@code past} and {@code future} attitude providers are defined with regard
  63.      * to the natural flow of time. This means that if the propagation is forward, the
  64.      * propagator will switch from {@code past} provider to {@code future} provider at
  65.      * event occurrence, but if the propagation is backward, the propagator will switch
  66.      * from {@code future} provider to {@code past} provider at event occurrence. The
  67.      * transition between the two attitude laws is not instantaneous, the switch event
  68.      * defines the start of the transition (i.e. when leaving the {@code past} attitude
  69.      * law and entering the interpolated transition law). The end of the transition
  70.      * (i.e. when leaving the interpolating transition law and entering the {@code future}
  71.      * attitude law) occurs at switch time plus {@code transitionTime}.
  72.      * </p>
  73.      * <p>
  74.      * An attitude provider may have several different switch events associated to
  75.      * it. Depending on which event is triggered, the appropriate provider is
  76.      * switched to.
  77.      * </p>
  78.      * <p>
  79.      * If the underlying detector has an event handler associated to it, this handler
  80.      * will be triggered (i.e. its {@link org.orekit.propagation.events.handlers.EventHandler#eventOccurred(SpacecraftState,
  81.      * EventDetector, boolean) eventOccurred} method will be called), <em>regardless</em>
  82.      * of the event really triggering an attitude switch or not. As an example, if an
  83.      * eclipse detector is used to switch from day to night attitude mode when entering
  84.      * eclipse, with {@code switchOnIncrease} set to {@code false} and {@code switchOnDecrease}
  85.      * set to {@code true}. Then a handler set directly at eclipse detector level would
  86.      * be triggered at both eclipse entry and eclipse exit, but attitude switch would
  87.      * occur <em>only</em> at eclipse entry. Note that for the sake of symmetry, the
  88.      * transition start and end dates should match for both forward and backward propagation.
  89.      * This implies that for backward propagation, we have to compensate for the {@code
  90.      * transitionTime} when looking for the event. An unfortunate consequence is that the
  91.      * {@link org.orekit.propagation.events.handlers.EventHandler#eventOccurred(SpacecraftState, EventDetector, boolean)
  92.      * eventOccurred} method may appear to be called out of sync with respect to the
  93.      * propagation (it will be called when propagator reaches transition end, despite it
  94.      * refers to transition start, as per {@code transitionTime} compensation), and if the
  95.      * method returns {@link Action#STOP}, it will stop at the end of the
  96.      * transition instead of at the start. For these reasons, it is not recommended to
  97.      * set up an event handler for events that are used to switch attitude. If an event
  98.      * handler is needed for other purposes, a second handler should be registered to
  99.      * the propagator rather than relying on the side effects of attitude switches.
  100.      * </p>
  101.      * <p>
  102.      * The smoothness of the transition between past and future attitude laws can be tuned
  103.      * using the {@code transitionTime} and {@code transitionFilter} parameters. The {@code
  104.      * transitionTime} parameter specifies how much time is spent to switch from one law to
  105.      * the other law. It should be larger than the event {@link EventDetector#getThreshold()
  106.      * convergence threshold} in order to ensure attitude continuity. The {@code
  107.      * transitionFilter} parameter specifies the attitude time derivatives that should match
  108.      * at the boundaries between past attitude law and transition law on one side, and
  109.      * between transition law and future law on the other side.
  110.      * {@link AngularDerivativesFilter#USE_R} means only the rotation should be identical,
  111.      * {@link AngularDerivativesFilter#USE_RR} means both rotation and rotation rate
  112.      * should be identical, {@link AngularDerivativesFilter#USE_RRA} means both rotation,
  113.      * rotation rate and rotation acceleration should be identical. During the transition,
  114.      * the attitude law is computed by interpolating between past attitude law at switch time
  115.      * and future attitude law at current intermediate time.
  116.      * </p>
  117.      * @param past attitude provider applicable for times in the switch event occurrence past
  118.      * @param future attitude provider applicable for times in the switch event occurrence future
  119.      * @param switchEvent event triggering the attitude providers switch
  120.      * @param switchOnIncrease if true, switch is triggered on increasing event
  121.      * @param switchOnDecrease if true, switch is triggered on decreasing event
  122.      * @param transitionTime duration of the transition between the past and future attitude laws
  123.      * @param transitionFilter specification of transition law time derivatives that
  124.      * should match past and future attitude laws
  125.      * @param switchHandler handler to call for notifying when switch occurs (may be null)
  126.      * @param <T> class type for the switch event
  127.      * @since 13.0
  128.      */
  129.     public <T extends EventDetector> void addSwitchingCondition(final AttitudeProvider past,
  130.                                                                 final AttitudeProvider future,
  131.                                                                 final T switchEvent,
  132.                                                                 final boolean switchOnIncrease,
  133.                                                                 final boolean switchOnDecrease,
  134.                                                                 final double transitionTime,
  135.                                                                 final AngularDerivativesFilter transitionFilter,
  136.                                                                 final AttitudeSwitchHandler switchHandler) {

  137.         // safety check, for ensuring attitude continuity
  138.         if (transitionTime < switchEvent.getThreshold()) {
  139.             throw new OrekitException(OrekitMessages.TOO_SHORT_TRANSITION_TIME_FOR_ATTITUDES_SWITCH,
  140.                                       transitionTime, switchEvent.getThreshold());
  141.         }

  142.         // if it is the first switching condition, assume first active law is the past one
  143.         if (getActivated() == null) {
  144.             resetActiveProvider(past);
  145.         }

  146.         // add the switching condition
  147.         switches.add(new Switch(switchEvent, switchOnIncrease, switchOnDecrease,
  148.                                 past, future, transitionTime, transitionFilter, switchHandler));

  149.     }

  150.     @Override
  151.     public Stream<EventDetector> getEventDetectors() {
  152.         return Stream.concat(switches.stream().map(Switch.class::cast), getEventDetectors(getParametersDrivers()));
  153.     }

  154.     @Override
  155.     public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(final Field<T> field) {
  156.         final Stream<FieldEventDetector<T>> switchesStream = switches.stream().map(sw -> getFieldEventDetector(field, sw));
  157.         return Stream.concat(switchesStream, getFieldEventDetectors(field, getParametersDrivers()));
  158.     }

  159.     /**
  160.      * Gets a deep copy of the switches stored in this instance.
  161.      *
  162.      * @return deep copy of the switches stored in this instance
  163.      */
  164.     public List<Switch> getSwitches() {
  165.         return new ArrayList<>(switches);
  166.     }

  167.     /** Switch specification. Handles the transition. */
  168.     public class Switch extends AbstractAttitudeSwitch {

  169.         /** Duration of the transition between the past and future attitude laws. */
  170.         private final double transitionTime;

  171.         /** Order at which the transition law time derivatives should match past and future attitude laws. */
  172.         private final AngularDerivativesFilter transitionFilter;

  173.         /** Propagation direction. */
  174.         private boolean forward;

  175.         /**
  176.          * Simple constructor.
  177.          *
  178.          * @param event event
  179.          * @param switchOnIncrease if true, switch is triggered on increasing event
  180.          * @param switchOnDecrease if true, switch is triggered on decreasing event otherwise switch is triggered on
  181.          * decreasing event
  182.          * @param past attitude provider applicable for times in the switch event occurrence past
  183.          * @param future attitude provider applicable for times in the switch event occurrence future
  184.          * @param transitionTime duration of the transition between the past and future attitude laws
  185.          * @param transitionFilter order at which the transition law time derivatives should match past and future attitude
  186.          * laws
  187.          * @param switchHandler handler to call for notifying when switch occurs (may be null)
  188.          */
  189.         private Switch(final EventDetector event, final boolean switchOnIncrease, final boolean switchOnDecrease,
  190.                        final AttitudeProvider past, final AttitudeProvider future, final double transitionTime,
  191.                        final AngularDerivativesFilter transitionFilter, final AttitudeSwitchHandler switchHandler) {
  192.             super(event, switchOnIncrease, switchOnDecrease, past, future, switchHandler);
  193.             this.transitionTime   = transitionTime;
  194.             this.transitionFilter = transitionFilter;
  195.         }

  196.         /** {@inheritDoc} */
  197.         @Override
  198.         public void init(final SpacecraftState s0, final AbsoluteDate t) {
  199.             super.init(s0, t);

  200.             // reset the transition parameters (this will be done once for each switch,
  201.             //  despite doing it only once would have sufficient; it's not really a problem)
  202.             forward = t.durationFrom(s0.getDate()) >= 0.0;
  203.             if (getActivated().getSpansNumber() > 1) {
  204.                 // remove transitions that will be overridden during upcoming propagation
  205.                 if (forward) {
  206.                     setActivated(getActivated().extractRange(AbsoluteDate.PAST_INFINITY, s0.getDate().shiftedBy(transitionTime)));
  207.                 } else {
  208.                     setActivated(getActivated().extractRange(s0.getDate().shiftedBy(-transitionTime), AbsoluteDate.FUTURE_INFINITY));
  209.                 }
  210.             }

  211.         }

  212.         /** {@inheritDoc} */
  213.         @Override
  214.         public double g(final SpacecraftState s) {
  215.             return getDetector().g(forward ? s : s.shiftedBy(-transitionTime));
  216.         }

  217.         /** {@inheritDoc} */
  218.         public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {

  219.             final AbsoluteDate date = s.getDate();
  220.             if (getActivated().get(date) == (forward ? getPast() : getFuture()) &&
  221.                 (increasing && isSwitchOnIncrease() || !increasing && isSwitchOnDecrease())) {

  222.                 if (forward) {

  223.                     // prepare transition
  224.                     final AbsoluteDate transitionEnd = date.shiftedBy(transitionTime);
  225.                     getActivated().addValidAfter(new TransitionProvider(s.getAttitude(), transitionEnd), date, false);

  226.                     // prepare future law after transition
  227.                     getActivated().addValidAfter(getFuture(), transitionEnd, false);

  228.                     // notify about the switch
  229.                     if (getSwitchHandler() != null) {
  230.                         getSwitchHandler().switchOccurred(getPast(), getFuture(), s);
  231.                     }

  232.                     return getDetector().getHandler().eventOccurred(s, getDetector(), increasing);

  233.                 } else {

  234.                     // estimate state at transition start, according to the past attitude law
  235.                     final double dt = -transitionTime;
  236.                     final AbsoluteDate shiftedDate = date.shiftedBy(dt);
  237.                     SpacecraftState sState;
  238.                     if (s.isOrbitDefined()) {
  239.                         final Orbit     sOrbit    = s.getOrbit().shiftedBy(dt);
  240.                         final Attitude  sAttitude = getPast().getAttitude(sOrbit, shiftedDate, s.getFrame());
  241.                         sState    = new SpacecraftState(sOrbit, sAttitude).withMass(s.getMass());
  242.                     } else {
  243.                         final AbsolutePVCoordinates sAPV    = s.getAbsPVA().shiftedBy(dt);
  244.                         final Attitude  sAttitude = getPast().getAttitude(sAPV, shiftedDate, s.getFrame());
  245.                         sState    = new SpacecraftState(sAPV, sAttitude).withMass(s.getMass());
  246.                     }
  247.                     for (final DataDictionary.Entry entry : s.getAdditionalDataValues().getData()) {
  248.                         sState = sState.addAdditionalData(entry.getKey(), entry.getValue());
  249.                     }

  250.                     // prepare transition
  251.                     getActivated().addValidBefore(new TransitionProvider(sState.getAttitude(), date), date, false);

  252.                     // prepare past law before transition
  253.                     getActivated().addValidBefore(getPast(), shiftedDate, false);

  254.                     // notify about the switch
  255.                     if (getSwitchHandler() != null) {
  256.                         getSwitchHandler().switchOccurred(getFuture(), getPast(), sState);
  257.                     }

  258.                     return getDetector().getHandler().eventOccurred(sState, getDetector(), increasing);

  259.                 }

  260.             } else {
  261.                 // trigger the underlying event despite no attitude switch occurred
  262.                 return getDetector().getHandler().eventOccurred(s, getDetector(), increasing);
  263.             }

  264.         }

  265.         /** Provider for transition phases.
  266.          * @since 9.2
  267.          */
  268.         private class TransitionProvider implements AttitudeProvider {

  269.             /** Attitude at preceding transition. */
  270.             private final Attitude transitionPreceding;

  271.             /** Date of final switch to following attitude law. */
  272.             private final AbsoluteDate transitionEnd;

  273.             /** Simple constructor.
  274.              * @param transitionPreceding attitude at preceding transition
  275.              * @param transitionEnd date of final switch to following attitude law
  276.              */
  277.             TransitionProvider(final Attitude transitionPreceding, final AbsoluteDate transitionEnd) {
  278.                 this.transitionPreceding = transitionPreceding;
  279.                 this.transitionEnd       = transitionEnd;
  280.             }

  281.             /** {@inheritDoc} */
  282.             public Attitude getAttitude(final PVCoordinatesProvider pvProv,
  283.                                         final AbsoluteDate date, final Frame frame) {

  284.                 // Create sample
  285.                 final TimeStampedAngularCoordinates start =
  286.                         transitionPreceding.withReferenceFrame(frame).getOrientation();
  287.                 final TimeStampedAngularCoordinates end =
  288.                         getFuture().getAttitude(pvProv, transitionEnd, frame).getOrientation();
  289.                 final List<TimeStampedAngularCoordinates> sample =  Arrays.asList(start, end);

  290.                 // Create interpolator
  291.                 final TimeInterpolator<TimeStampedAngularCoordinates> interpolator =
  292.                         new TimeStampedAngularCoordinatesHermiteInterpolator(sample.size(), transitionFilter);

  293.                 // interpolate between the two boundary attitudes
  294.                 final TimeStampedAngularCoordinates interpolated = interpolator.interpolate(date, sample);

  295.                 return new Attitude(frame, interpolated);

  296.             }

  297.             /** {@inheritDoc} */
  298.             public <S extends CalculusFieldElement<S>> FieldAttitude<S> getAttitude(final FieldPVCoordinatesProvider<S> pvProv,
  299.                                                                                     final FieldAbsoluteDate<S> date,
  300.                                                                                     final Frame frame) {

  301.                 // create sample
  302.                 final TimeStampedFieldAngularCoordinates<S> start =
  303.                         new TimeStampedFieldAngularCoordinates<>(date.getField(),
  304.                                                                  transitionPreceding.withReferenceFrame(frame).getOrientation());
  305.                 final TimeStampedFieldAngularCoordinates<S> end =
  306.                         getFuture().getAttitude(pvProv,
  307.                                            new FieldAbsoluteDate<>(date.getField(), transitionEnd),
  308.                                            frame).getOrientation();
  309.                 final List<TimeStampedFieldAngularCoordinates<S>> sample = Arrays.asList(start, end);

  310.                 // create interpolator
  311.                 final FieldTimeInterpolator<TimeStampedFieldAngularCoordinates<S>, S> interpolator =
  312.                         new TimeStampedFieldAngularCoordinatesHermiteInterpolator<>(sample.size(), transitionFilter);

  313.                 // interpolate between the two boundary attitudes
  314.                 final TimeStampedFieldAngularCoordinates<S> interpolated = interpolator.interpolate(date, sample);

  315.                 return new FieldAttitude<>(frame, interpolated);
  316.             }

  317.         }

  318.     }

  319. }