AttitudesSwitcher.java

  1. /* Copyright 2022-2025 Romain Serra
  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 org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.Field;
  20. import org.hipparchus.ode.events.Action;
  21. import org.orekit.propagation.SpacecraftState;
  22. import org.orekit.propagation.events.EventDetector;
  23. import org.orekit.propagation.events.FieldEventDetector;
  24. import org.orekit.time.AbsoluteDate;

  25. import java.util.ArrayList;
  26. import java.util.List;
  27. import java.util.stream.Stream;

  28. /** This classes manages a sequence of different attitude providers that are activated
  29.  * in turn according to switching events. Changes in attitude mode are instantaneous, so state derivatives need to be
  30.  * reset and the {@link Action} returned by the event handler is ignored.
  31.  * @author Luc Maisonobe
  32.  * @author Romain Serra
  33.  * @since 13.0
  34.  * @see AttitudesSequence
  35.  */
  36. public class AttitudesSwitcher extends AbstractSwitchingAttitudeProvider {

  37.     /** Switching events list. */
  38.     private final List<InstantaneousSwitch> instantaneousSwitches;

  39.     /** Constructor for an initially empty sequence.
  40.      */
  41.     public AttitudesSwitcher() {
  42.         super();
  43.         instantaneousSwitches = new ArrayList<>();
  44.     }

  45.     /** Add a switching condition between two attitude providers.
  46.      * <p>
  47.      * The {@code past} and {@code future} attitude providers are defined with regard
  48.      * to the natural flow of time. This means that if the propagation is forward, the
  49.      * propagator will switch from {@code past} provider to {@code future} provider at
  50.      * event occurrence, but if the propagation is backward, the propagator will switch
  51.      * from {@code future} provider to {@code past} provider at event occurrence.
  52.      * </p>
  53.      * <p>
  54.      * An attitude provider may have several different switch events associated to
  55.      * it. Depending on which event is triggered, the appropriate provider is
  56.      * switched to.
  57.      * </p>
  58.      * <p>
  59.      * If the underlying detector has an event handler associated to it, this handler
  60.      * will be triggered (i.e. its {@link org.orekit.propagation.events.handlers.EventHandler#eventOccurred(SpacecraftState,
  61.      * EventDetector, boolean) eventOccurred} method will be called), <em>regardless</em>
  62.      * of the event really triggering an attitude switch or not. As an example, if an
  63.      * eclipse detector is used to switch from day to night attitude mode when entering
  64.      * eclipse, with {@code switchOnIncrease} set to {@code false} and {@code switchOnDecrease}
  65.      * set to {@code true}. Then a handler set directly at eclipse detector level would
  66.      * be triggered at both eclipse entry and eclipse exit, but attitude switch would
  67.      * occur <em>only</em> at eclipse entry.
  68.      * </p>
  69.      * @param past attitude provider applicable for times in the switch event occurrence past
  70.      * @param future attitude provider applicable for times in the switch event occurrence future
  71.      * @param switchEvent event triggering the attitude providers switch
  72.      * @param switchOnIncrease if true, switch is triggered on increasing event
  73.      * @param switchOnDecrease if true, switch is triggered on decreasing event
  74.      * @param switchHandler handler to call for notifying when switch occurs (may be null)
  75.      * @param <T> class type for the switch event
  76.      * @since 13.0
  77.      */
  78.     public <T extends EventDetector> void addSwitchingCondition(final AttitudeProvider past,
  79.                                                                 final AttitudeProvider future,
  80.                                                                 final T switchEvent,
  81.                                                                 final boolean switchOnIncrease,
  82.                                                                 final boolean switchOnDecrease,
  83.                                                                 final AttitudeSwitchHandler switchHandler) {

  84.         // if it is the first switching condition, assume first active law is the past one
  85.         if (getActivated() == null) {
  86.             resetActiveProvider(past);
  87.         }

  88.         // add the switching condition
  89.         instantaneousSwitches.add(new InstantaneousSwitch(switchEvent, switchOnIncrease, switchOnDecrease,
  90.                                 past, future, switchHandler));

  91.     }

  92.     @Override
  93.     public Stream<EventDetector> getEventDetectors() {
  94.         return Stream.concat(instantaneousSwitches.stream().map(InstantaneousSwitch.class::cast), getEventDetectors(getParametersDrivers()));
  95.     }

  96.     @Override
  97.     public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(final Field<T> field) {
  98.         final Stream<FieldEventDetector<T>> switchesStream = instantaneousSwitches.stream().map(sw -> getFieldEventDetector(field, sw));
  99.         return Stream.concat(switchesStream, getFieldEventDetectors(field, getParametersDrivers()));
  100.     }

  101.     /** Switch specification. Reset derivatives due to instantaneous change of attitude. */
  102.     public class InstantaneousSwitch extends AbstractAttitudeSwitch {

  103.         /** Propagation direction. */
  104.         private boolean forward;

  105.         /**
  106.          * Simple constructor.
  107.          *
  108.          * @param event event
  109.          * @param switchOnIncrease if true, switch is triggered on increasing event
  110.          * @param switchOnDecrease if true, switch is triggered on decreasing event otherwise switch is triggered on
  111.          * decreasing event
  112.          * @param past attitude provider applicable for times in the switch event occurrence past
  113.          * @param future attitude provider applicable for times in the switch event occurrence future
  114.          * @param switchHandler handler to call for notifying when switch occurs (may be null)
  115.          */
  116.         private InstantaneousSwitch(final EventDetector event, final boolean switchOnIncrease, final boolean switchOnDecrease,
  117.                                     final AttitudeProvider past, final AttitudeProvider future,
  118.                                     final AttitudeSwitchHandler switchHandler) {
  119.             super(event, switchOnIncrease, switchOnDecrease, past, future, switchHandler);
  120.         }

  121.         /** {@inheritDoc} */
  122.         @Override
  123.         public void init(final SpacecraftState s0, final AbsoluteDate t) {
  124.             super.init(s0, t);

  125.             // reset the transition parameters (this will be done once for each switch,
  126.             //  despite doing it only once would have sufficient; it's not really a problem)
  127.             forward = t.durationFrom(s0.getDate()) >= 0.0;
  128.             if (getActivated().getSpansNumber() > 1) {
  129.                 // remove switches that will be overridden during upcoming propagation (use margin to avoid erasing after creation)
  130.                 if (forward) {
  131.                     setActivated(getActivated().extractRange(AbsoluteDate.PAST_INFINITY, s0.getDate().shiftedBy(getDetectionSettings().getThreshold())));
  132.                 } else {
  133.                     setActivated(getActivated().extractRange(s0.getDate().shiftedBy(-getDetectionSettings().getThreshold()), AbsoluteDate.FUTURE_INFINITY));
  134.                 }
  135.             }

  136.         }

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

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

  142.                 if (forward) {
  143.                     // prepare future law
  144.                     getActivated().addValidAfter(getFuture(), date, false);

  145.                     // notify about the switch
  146.                     if (getSwitchHandler() != null) {
  147.                         getSwitchHandler().switchOccurred(getPast(), getFuture(), s);
  148.                     }

  149.                 } else {
  150.                     // prepare past law
  151.                     getActivated().addValidBefore(getPast(), date, false);

  152.                     // notify about the switch
  153.                     if (getSwitchHandler() != null) {
  154.                         getSwitchHandler().switchOccurred(getFuture(), getPast(), s);
  155.                     }

  156.                 }

  157.             }
  158.             getDetector().getHandler().eventOccurred(s, getDetector(), increasing);  // call but ignore output
  159.             return Action.RESET_DERIVATIVES;
  160.         }

  161.     }

  162. }