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  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.ode.events.Action;
22  import org.orekit.propagation.SpacecraftState;
23  import org.orekit.propagation.events.EventDetector;
24  import org.orekit.propagation.events.FieldEventDetector;
25  import org.orekit.time.AbsoluteDate;
26  
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.stream.Stream;
30  
31  /** This classes manages a sequence of different attitude providers that are activated
32   * in turn according to switching events. Changes in attitude mode are instantaneous, so state derivatives need to be
33   * reset and the {@link Action} returned by the event handler is ignored.
34   * @author Luc Maisonobe
35   * @author Romain Serra
36   * @since 13.0
37   * @see AttitudesSequence
38   */
39  public class AttitudesSwitcher extends AbstractSwitchingAttitudeProvider {
40  
41      /** Switching events list. */
42      private final List<InstantaneousSwitch> instantaneousSwitches;
43  
44      /** Constructor for an initially empty sequence.
45       */
46      public AttitudesSwitcher() {
47          super();
48          instantaneousSwitches = new ArrayList<>();
49      }
50  
51      /** Add a switching condition between two attitude providers.
52       * <p>
53       * The {@code past} and {@code future} attitude providers are defined with regard
54       * to the natural flow of time. This means that if the propagation is forward, the
55       * propagator will switch from {@code past} provider to {@code future} provider at
56       * event occurrence, but if the propagation is backward, the propagator will switch
57       * from {@code future} provider to {@code past} provider at event occurrence.
58       * </p>
59       * <p>
60       * An attitude provider may have several different switch events associated to
61       * it. Depending on which event is triggered, the appropriate provider is
62       * switched to.
63       * </p>
64       * <p>
65       * If the underlying detector has an event handler associated to it, this handler
66       * will be triggered (i.e. its {@link org.orekit.propagation.events.handlers.EventHandler#eventOccurred(SpacecraftState,
67       * EventDetector, boolean) eventOccurred} method will be called), <em>regardless</em>
68       * of the event really triggering an attitude switch or not. As an example, if an
69       * eclipse detector is used to switch from day to night attitude mode when entering
70       * eclipse, with {@code switchOnIncrease} set to {@code false} and {@code switchOnDecrease}
71       * set to {@code true}. Then a handler set directly at eclipse detector level would
72       * be triggered at both eclipse entry and eclipse exit, but attitude switch would
73       * occur <em>only</em> at eclipse entry.
74       * </p>
75       * @param past attitude provider applicable for times in the switch event occurrence past
76       * @param future attitude provider applicable for times in the switch event occurrence future
77       * @param switchEvent event triggering the attitude providers switch
78       * @param switchOnIncrease if true, switch is triggered on increasing event
79       * @param switchOnDecrease if true, switch is triggered on decreasing event
80       * @param switchHandler handler to call for notifying when switch occurs (may be null)
81       * @param <T> class type for the switch event
82       * @since 13.0
83       */
84      public <T extends EventDetector> void addSwitchingCondition(final AttitudeProvider past,
85                                                                  final AttitudeProvider future,
86                                                                  final T switchEvent,
87                                                                  final boolean switchOnIncrease,
88                                                                  final boolean switchOnDecrease,
89                                                                  final AttitudeSwitchHandler switchHandler) {
90  
91          // if it is the first switching condition, assume first active law is the past one
92          if (getActivated() == null) {
93              resetActiveProvider(past);
94          }
95  
96          // add the switching condition
97          instantaneousSwitches.add(new InstantaneousSwitch(switchEvent, switchOnIncrease, switchOnDecrease,
98                                  past, future, switchHandler));
99  
100     }
101 
102     @Override
103     public Stream<EventDetector> getEventDetectors() {
104         return Stream.concat(instantaneousSwitches.stream().map(InstantaneousSwitch.class::cast), getEventDetectors(getParametersDrivers()));
105     }
106 
107     @Override
108     public <T extends CalculusFieldElement<T>> Stream<FieldEventDetector<T>> getFieldEventDetectors(final Field<T> field) {
109         final Stream<FieldEventDetector<T>> switchesStream = instantaneousSwitches.stream().map(sw -> getFieldEventDetector(field, sw));
110         return Stream.concat(switchesStream, getFieldEventDetectors(field, getParametersDrivers()));
111     }
112 
113     /** Switch specification. Reset derivatives due to instantaneous change of attitude. */
114     public class InstantaneousSwitch extends AbstractAttitudeSwitch {
115 
116         /** Propagation direction. */
117         private boolean forward;
118 
119         /**
120          * Simple constructor.
121          *
122          * @param event event
123          * @param switchOnIncrease if true, switch is triggered on increasing event
124          * @param switchOnDecrease if true, switch is triggered on decreasing event otherwise switch is triggered on
125          * decreasing event
126          * @param past attitude provider applicable for times in the switch event occurrence past
127          * @param future attitude provider applicable for times in the switch event occurrence future
128          * @param switchHandler handler to call for notifying when switch occurs (may be null)
129          */
130         private InstantaneousSwitch(final EventDetector event, final boolean switchOnIncrease, final boolean switchOnDecrease,
131                                     final AttitudeProvider past, final AttitudeProvider future,
132                                     final AttitudeSwitchHandler switchHandler) {
133             super(event, switchOnIncrease, switchOnDecrease, past, future, switchHandler);
134         }
135 
136         /** {@inheritDoc} */
137         @Override
138         public void init(final SpacecraftState s0, final AbsoluteDate t) {
139             super.init(s0, t);
140 
141             // reset the transition parameters (this will be done once for each switch,
142             //  despite doing it only once would have sufficient; it's not really a problem)
143             forward = t.durationFrom(s0.getDate()) >= 0.0;
144             if (getActivated().getSpansNumber() > 1) {
145                 // remove switches that will be overridden during upcoming propagation (use margin to avoid erasing after creation)
146                 if (forward) {
147                     setActivated(getActivated().extractRange(AbsoluteDate.PAST_INFINITY, s0.getDate().shiftedBy(getDetectionSettings().getThreshold())));
148                 } else {
149                     setActivated(getActivated().extractRange(s0.getDate().shiftedBy(-getDetectionSettings().getThreshold()), AbsoluteDate.FUTURE_INFINITY));
150                 }
151             }
152 
153         }
154 
155         /** {@inheritDoc} */
156         public Action eventOccurred(final SpacecraftState s, final EventDetector detector, final boolean increasing) {
157 
158             final AbsoluteDate date = s.getDate();
159             if (getActivated().get(date) == (forward ? getPast() : getFuture()) &&
160                 (increasing && isSwitchOnIncrease() || !increasing && isSwitchOnDecrease())) {
161 
162                 if (forward) {
163                     // prepare future law
164                     getActivated().addValidAfter(getFuture(), date, false);
165 
166                     // notify about the switch
167                     if (getSwitchHandler() != null) {
168                         getSwitchHandler().switchOccurred(getPast(), getFuture(), s);
169                     }
170 
171                 } else {
172                     // prepare past law
173                     getActivated().addValidBefore(getPast(), date, false);
174 
175                     // notify about the switch
176                     if (getSwitchHandler() != null) {
177                         getSwitchHandler().switchOccurred(getFuture(), getPast(), s);
178                     }
179 
180                 }
181 
182             }
183             getDetector().getHandler().eventOccurred(s, getDetector(), increasing);  // call but ignore output
184             return Action.RESET_DERIVATIVES;
185         }
186 
187     }
188 
189 }