1   /* Copyright 2002-2021 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  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.hipparchus.Field;
25  import org.hipparchus.CalculusFieldElement;
26  import org.hipparchus.ode.events.Action;
27  import org.orekit.errors.OrekitException;
28  import org.orekit.errors.OrekitMessages;
29  import org.orekit.frames.Frame;
30  import org.orekit.orbits.Orbit;
31  import org.orekit.propagation.FieldPropagator;
32  import org.orekit.propagation.FieldSpacecraftState;
33  import org.orekit.propagation.Propagator;
34  import org.orekit.propagation.SpacecraftState;
35  import org.orekit.propagation.events.EventDetector;
36  import org.orekit.propagation.events.FieldEventDetector;
37  import org.orekit.time.AbsoluteDate;
38  import org.orekit.time.FieldAbsoluteDate;
39  import org.orekit.utils.AngularDerivativesFilter;
40  import org.orekit.utils.FieldPVCoordinatesProvider;
41  import org.orekit.utils.PVCoordinatesProvider;
42  import org.orekit.utils.TimeSpanMap;
43  import org.orekit.utils.TimeStampedAngularCoordinates;
44  import org.orekit.utils.TimeStampedFieldAngularCoordinates;
45  
46  /** This classes manages a sequence of different attitude providers that are activated
47   * in turn according to switching events.
48   * <p>Only one attitude provider in the sequence is in an active state. When one of
49   * the switch event associated with the active provider occurs, the active provider becomes
50   * the one specified with the event. A simple example is a provider for the sun lighted part
51   * of the orbit and another provider for the eclipse time. When the sun lighted provider is active,
52   * the eclipse entry event is checked and when it occurs the eclipse provider is activated.
53   * When the eclipse provider is active, the eclipse exit event is checked and when it occurs
54   * the sun lighted provider is activated again. This sequence is a simple loop.</p>
55   * <p>An active attitude provider may have several switch events and next provider settings, leading
56   * to different activation patterns depending on which events are triggered first. An example
57   * of this feature is handling switches to safe mode if some contingency condition is met, in
58   * addition to the nominal switches that correspond to proper operations. Another example
59   * is handling of maneuver mode.
60   * <p>
61   * Note that this attitude provider is stateful, it keeps in memory the sequence of active
62   * underlying providers with their switch dates and the transitions from one provider to
63   * the other. This implies that this provider should <em>not</em> be shared among different
64   * propagators at the same time, each propagator should use its own instance of this provider.
65   * <p>
66   * The sequence kept in memory is reset when {@link #resetActiveProvider(AttitudeProvider)}
67   * is called, and only the specify provider is kept. The sequence is also partially
68   * reset each time a propagation starts. If a new propagation is started after a first
69   * propagation has been run, all the already computed switches that occur after propagation
70   * start for forward propagation or before propagation start for backward propagation will
71   * be erased. New switches will be computed and applied properly according to the new
72   * propagation settings. The already computed switches that are not in covered are kept
73   * in memory. This implies that if a propagation is interrupted and restarted in the
74   * same direction, then attitude switches will remain in place, ensuring that even if the
75   * interruption occurred in the middle of an attitude transition the second propagation will
76   * properly complete the transition that was started by the first propagator.
77   * </p>
78   * @author Luc Maisonobe
79   * @since 5.1
80   */
81  public class AttitudesSequence implements AttitudeProvider {
82  
83      /** Providers that have been activated. */
84      private transient TimeSpanMap<AttitudeProvider> activated;
85  
86      /** Switching events list. */
87      private final List<Switch<?>> switches;
88  
89      /** Constructor for an initially empty sequence.
90       */
91      public AttitudesSequence() {
92          activated = null;
93          switches  = new ArrayList<Switch<?>>();
94      }
95  
96      /** Reset the active provider.
97       * <p>
98       * Calling this method clears all already seen switch history,
99       * so it should <em>not</em> be used during the propagation itself,
100      * it is intended to be used only at start
101      * </p>
102      * @param provider provider to activate
103      */
104     public void resetActiveProvider(final AttitudeProvider provider) {
105         activated = new TimeSpanMap<AttitudeProvider>(provider);
106     }
107 
108     /** Register all wrapped switch events to the propagator.
109      * <p>
110      * This method must be called once before propagation, after the
111      * switching conditions have been set up by calls to {@link
112      * #addSwitchingCondition(AttitudeProvider, AttitudeProvider, EventDetector,
113      * boolean, boolean, double, AngularDerivativesFilter, SwitchHandler)
114      * addSwitchingCondition}.
115      * </p>
116      * @param propagator propagator that will handle the events
117      */
118     public void registerSwitchEvents(final Propagator propagator) {
119         for (final Switch<?> s : switches) {
120             propagator.addEventDetector(s);
121         }
122     }
123 
124     /** Register all wrapped switch events to the propagator.
125      * <p>
126      * This method must be called once before propagation, after the
127      * switching conditions have been set up by calls to {@link
128      * #addSwitchingCondition(AttitudeProvider, AttitudeProvider, EventDetector,
129      * boolean, boolean, double, AngularDerivativesFilter, SwitchHandler)
130      * addSwitchingCondition}.
131      * </p>
132      * @param field field to which the elements belong
133      * @param propagator propagator that will handle the events
134      * @param <T> type of the field elements
135      */
136     public <T extends CalculusFieldElement<T>> void registerSwitchEvents(final Field<T> field, final FieldPropagator<T> propagator) {
137         for (final Switch<?> sw : switches) {
138             propagator.addEventDetector(new FieldEventDetector<T>() {
139 
140                 /** {@inheritDoc} */
141                 @Override
142                 public void init(final FieldSpacecraftState<T> s0,
143                                  final FieldAbsoluteDate<T> t) {
144                     sw.init(s0.toSpacecraftState(), t.toAbsoluteDate());
145                 }
146 
147                 /** {@inheritDoc} */
148                 @Override
149                 public T g(final FieldSpacecraftState<T> s) {
150                     return field.getZero().add(sw.g(s.toSpacecraftState()));
151                 }
152 
153                 /** {@inheritDoc} */
154                 @Override
155                 public T getThreshold() {
156                     return field.getZero().add(sw.getThreshold());
157                 }
158 
159                 /** {@inheritDoc} */
160                 @Override
161                 public T getMaxCheckInterval() {
162                     return field.getZero().add(sw.getMaxCheckInterval());
163                 }
164 
165                 /** {@inheritDoc} */
166                 @Override
167                 public int getMaxIterationCount() {
168                     return sw.getMaxIterationCount();
169                 }
170 
171                 /** {@inheritDoc} */
172                 @Override
173                 public Action eventOccurred(final FieldSpacecraftState<T> s, final boolean increasing) {
174                     return sw.eventOccurred(s.toSpacecraftState(), increasing);
175                 }
176 
177                 /** {@inheritDoc} */
178                 @Override
179                 public FieldSpacecraftState<T> resetState(final FieldSpacecraftState<T> oldState) {
180                     return new FieldSpacecraftState<>(field, sw.resetState(oldState.toSpacecraftState()));
181                 }
182 
183             });
184         }
185     }
186 
187     /** Add a switching condition between two attitude providers.
188      * <p>
189      * The {@code past} and {@code future} attitude providers are defined with regard
190      * to the natural flow of time. This means that if the propagation is forward, the
191      * propagator will switch from {@code past} provider to {@code future} provider at
192      * event occurrence, but if the propagation is backward, the propagator will switch
193      * from {@code future} provider to {@code past} provider at event occurrence. The
194      * transition between the two attitude laws is not instantaneous, the switch event
195      * defines the start of the transition (i.e. when leaving the {@code past} attitude
196      * law and entering the interpolated transition law). The end of the transition
197      * (i.e. when leaving the interpolating transition law and entering the {@code future}
198      * attitude law) occurs at switch time plus {@code transitionTime}.
199      * </p>
200      * <p>
201      * An attitude provider may have several different switch events associated to
202      * it. Depending on which event is triggered, the appropriate provider is
203      * switched to.
204      * </p>
205      * <p>
206      * The switch events specified here must <em>not</em> be registered to the
207      * propagator directly. The proper way to register these events is to
208      * call {@link #registerSwitchEvents(Propagator)} once after all switching
209      * conditions have been set up. The reason for this is that the events will
210      * be wrapped before being registered.
211      * </p>
212      * <p>
213      * If the underlying detector has an event handler associated to it, this handler
214      * will be triggered (i.e. its {@link org.orekit.propagation.events.handlers.EventHandler#eventOccurred(SpacecraftState,
215      * EventDetector, boolean) eventOccurred} method will be called), <em>regardless</em>
216      * of the event really triggering an attitude switch or not. As an example, if an
217      * eclipse detector is used to switch from day to night attitude mode when entering
218      * eclipse, with {@code switchOnIncrease} set to {@code false} and {@code switchOnDecrease}
219      * set to {@code true}. Then a handler set directly at eclipse detector level would
220      * be triggered at both eclipse entry and eclipse exit, but attitude switch would
221      * occur <em>only</em> at eclipse entry. Note that for the sake of symmetry, the
222      * transition start and end dates should match for both forward and backward propagation.
223      * This implies that for backward propagation, we have to compensate for the {@code
224      * transitionTime} when looking for the event. An unfortunate consequence is that the
225      * {@link org.orekit.propagation.events.handlers.EventHandler#eventOccurred(SpacecraftState, EventDetector, boolean)
226      * eventOccurred} method may appear to be called out of sync with respect to the
227      * propagation (it will be called when propagator reaches transition end, despite it
228      * refers to transition start, as per {@code transitionTime} compensation), and if the
229      * method returns {@link Action#STOP}, it will stop at the end of the
230      * transition instead of at the start. For these reasons, it is not recommended to
231      * set up an event handler for events that are used to switch attitude. If an event
232      * handler is needed for other purposes, a second handler should be registered to
233      * the propagator rather than relying on the side effects of attitude switches.
234      * </p>
235      * <p>
236      * The smoothness of the transition between past and future attitude laws can be tuned
237      * using the {@code transitionTime} and {@code transitionFilter} parameters. The {@code
238      * transitionTime} parameter specifies how much time is spent to switch from one law to
239      * the other law. It should be larger than the event {@link EventDetector#getThreshold()
240      * convergence threshold} in order to ensure attitude continuity. The {@code
241      * transitionFilter} parameter specifies the attitude time derivatives that should match
242      * at the boundaries between past attitude law and transition law on one side, and
243      * between transition law and future law on the other side.
244      * {@link AngularDerivativesFilter#USE_R} means only the rotation should be identical,
245      * {@link AngularDerivativesFilter#USE_RR} means both rotation and rotation rate
246      * should be identical, {@link AngularDerivativesFilter#USE_RRA} means both rotation,
247      * rotation rate and rotation acceleration should be identical. During the transition,
248      * the attitude law is computed by interpolating between past attitude law at switch time
249      * and future attitude law at current intermediate time.
250      * </p>
251      * @param past attitude provider applicable for times in the switch event occurrence past
252      * @param future attitude provider applicable for times in the switch event occurrence future
253      * @param switchEvent event triggering the attitude providers switch
254      * @param switchOnIncrease if true, switch is triggered on increasing event
255      * @param switchOnDecrease if true, switch is triggered on decreasing event
256      * @param transitionTime duration of the transition between the past and future attitude laws
257      * @param transitionFilter specification of transition law time derivatives that
258      * should match past and future attitude laws
259      * @param handler handler to call for notifying when switch occurs (may be null)
260      * @param <T> class type for the switch event
261      * @since 7.1
262      */
263     public <T extends EventDetector> void addSwitchingCondition(final AttitudeProvider past,
264                                                                 final AttitudeProvider future,
265                                                                 final T switchEvent,
266                                                                 final boolean switchOnIncrease,
267                                                                 final boolean switchOnDecrease,
268                                                                 final double transitionTime,
269                                                                 final AngularDerivativesFilter transitionFilter,
270                                                                 final SwitchHandler handler) {
271 
272         // safety check, for ensuring attitude continuity
273         if (transitionTime < switchEvent.getThreshold()) {
274             throw new OrekitException(OrekitMessages.TOO_SHORT_TRANSITION_TIME_FOR_ATTITUDES_SWITCH,
275                                       transitionTime, switchEvent.getThreshold());
276         }
277 
278         // if it is the first switching condition, assume first active law is the past one
279         if (activated == null) {
280             resetActiveProvider(past);
281         }
282 
283         // add the switching condition
284         switches.add(new Switch<T>(switchEvent, switchOnIncrease, switchOnDecrease,
285                                    past, future, transitionTime, transitionFilter, handler));
286 
287     }
288 
289     /** {@inheritDoc} */
290     public Attitude getAttitude(final PVCoordinatesProvider pvProv,
291                                 final AbsoluteDate date, final Frame frame) {
292         return activated.get(date).getAttitude(pvProv, date, frame);
293     }
294 
295     /** {@inheritDoc} */
296     public <T extends CalculusFieldElement<T>> FieldAttitude<T> getAttitude(final FieldPVCoordinatesProvider<T> pvProv,
297                                                                         final FieldAbsoluteDate<T> date,
298                                                                         final Frame frame) {
299         return activated.get(date.toAbsoluteDate()).getAttitude(pvProv, date, frame);
300     }
301 
302     /** Switch specification.
303      * @param <T> class type for the generic version
304      */
305     private class Switch<T extends EventDetector> implements EventDetector {
306 
307         /** Event. */
308         private final T event;
309 
310         /** Event direction triggering the switch. */
311         private final boolean switchOnIncrease;
312 
313         /** Event direction triggering the switch. */
314         private final boolean switchOnDecrease;
315 
316         /** Attitude provider applicable for times in the switch event occurrence past. */
317         private final AttitudeProvider past;
318 
319         /** Attitude provider applicable for times in the switch event occurrence future. */
320         private final AttitudeProvider future;
321 
322         /** Duration of the transition between the past and future attitude laws. */
323         private final double transitionTime;
324 
325         /** Order at which the transition law time derivatives should match past and future attitude laws. */
326         private final AngularDerivativesFilter transitionFilter;
327 
328         /** Handler to call for notifying when switch occurs (may be null). */
329         private final SwitchHandler switchHandler;
330 
331         /** Propagation direction. */
332         private boolean forward;
333 
334         /** Simple constructor.
335          * @param event event
336          * @param switchOnIncrease if true, switch is triggered on increasing event
337          * @param switchOnDecrease if true, switch is triggered on decreasing event
338          * otherwise switch is triggered on decreasing event
339          * @param past attitude provider applicable for times in the switch event occurrence past
340          * @param future attitude provider applicable for times in the switch event occurrence future
341          * @param transitionTime duration of the transition between the past and future attitude laws
342          * @param transitionFilter order at which the transition law time derivatives
343          * should match past and future attitude laws
344          * @param switchHandler handler to call for notifying when switch occurs (may be null)
345          */
346         Switch(final T event,
347                final boolean switchOnIncrease, final boolean switchOnDecrease,
348                final AttitudeProvider past, final AttitudeProvider future,
349                final double transitionTime, final AngularDerivativesFilter transitionFilter,
350                final SwitchHandler switchHandler) {
351             this.event            = event;
352             this.switchOnIncrease = switchOnIncrease;
353             this.switchOnDecrease = switchOnDecrease;
354             this.past             = past;
355             this.future           = future;
356             this.transitionTime   = transitionTime;
357             this.transitionFilter = transitionFilter;
358             this.switchHandler    = switchHandler;
359         }
360 
361         /** {@inheritDoc} */
362         @Override
363         public double getThreshold() {
364             return event.getThreshold();
365         }
366 
367         /** {@inheritDoc} */
368         @Override
369         public double getMaxCheckInterval() {
370             return event.getMaxCheckInterval();
371         }
372 
373         /** {@inheritDoc} */
374         @Override
375         public int getMaxIterationCount() {
376             return event.getMaxIterationCount();
377         }
378 
379         /** {@inheritDoc} */
380         public void init(final SpacecraftState s0,
381                          final AbsoluteDate t) {
382 
383             // reset the transition parameters (this will be done once for each switch,
384             //  despite doing it only once would have sufficient; its not really a problem)
385             forward = t.durationFrom(s0.getDate()) >= 0.0;
386             if (activated.getTransitions().size() > 1) {
387                 // remove transitions that will be overridden during upcoming propagation
388                 if (forward) {
389                     activated = activated.extractRange(AbsoluteDate.PAST_INFINITY, s0.getDate().shiftedBy(transitionTime));
390                 } else {
391                     activated = activated.extractRange(s0.getDate().shiftedBy(-transitionTime), AbsoluteDate.FUTURE_INFINITY);
392                 }
393             }
394 
395             // initialize the underlying event
396             event.init(s0, t);
397 
398         }
399 
400         /** {@inheritDoc} */
401         public double g(final SpacecraftState s) {
402             return event.g(forward ? s : s.shiftedBy(-transitionTime));
403         }
404 
405         /** {@inheritDoc} */
406         public Action eventOccurred(final SpacecraftState s, final boolean increasing) {
407 
408             final AbsoluteDate date = s.getDate();
409             if (activated.get(date) == (forward ? past : future) &&
410                 (increasing && switchOnIncrease || !increasing && switchOnDecrease)) {
411 
412                 if (forward) {
413 
414                     // prepare transition
415                     final AbsoluteDate transitionEnd = date.shiftedBy(transitionTime);
416                     activated.addValidAfter(new TransitionProvider(s.getAttitude(), transitionEnd), date);
417 
418                     // prepare future law after transition
419                     activated.addValidAfter(future, transitionEnd);
420 
421                     // notify about the switch
422                     if (switchHandler != null) {
423                         switchHandler.switchOccurred(past, future, s);
424                     }
425 
426                     return event.eventOccurred(s, increasing);
427 
428                 } else {
429 
430                     // estimate state at transition start, according to the past attitude law
431                     final Orbit     sOrbit    = s.getOrbit().shiftedBy(-transitionTime);
432                     final Attitude  sAttitude = past.getAttitude(sOrbit, sOrbit.getDate(), sOrbit.getFrame());
433                     SpacecraftState sState    = new SpacecraftState(sOrbit, sAttitude, s.getMass());
434                     for (final Map.Entry<String, double[]> entry : s.getAdditionalStates().entrySet()) {
435                         sState = sState.addAdditionalState(entry.getKey(), entry.getValue());
436                     }
437 
438                     // prepare transition
439                     activated.addValidBefore(new TransitionProvider(sAttitude, date), date);
440 
441                     // prepare past law before transition
442                     activated.addValidBefore(past, sOrbit.getDate());
443 
444                     // notify about the switch
445                     if (switchHandler != null) {
446                         switchHandler.switchOccurred(future, past, sState);
447                     }
448 
449                     return event.eventOccurred(sState, increasing);
450 
451                 }
452 
453             } else {
454                 // trigger the underlying event despite no attitude switch occurred
455                 return event.eventOccurred(s, increasing);
456             }
457 
458         }
459 
460         /** {@inheritDoc} */
461         @Override
462         public SpacecraftState resetState(final SpacecraftState oldState) {
463             // delegate to underlying event
464             return event.resetState(oldState);
465         }
466 
467         /** Provider for transition phases.
468          * @since 9.2
469          */
470         private class TransitionProvider implements AttitudeProvider {
471 
472             /** Attitude at preceding transition. */
473             private final Attitude transitionPreceding;
474 
475             /** Date of final switch to following attitude law. */
476             private final AbsoluteDate transitionEnd;
477 
478             /** Simple constructor.
479              * @param transitionPreceding attitude at preceding transition
480              * @param transitionEnd date of final switch to following attitude law
481              */
482             TransitionProvider(final Attitude transitionPreceding, final AbsoluteDate transitionEnd) {
483                 this.transitionPreceding = transitionPreceding;
484                 this.transitionEnd       = transitionEnd;
485             }
486 
487             /** {@inheritDoc} */
488             public Attitude getAttitude(final PVCoordinatesProvider pvProv,
489                                         final AbsoluteDate date, final Frame frame) {
490 
491                 // interpolate between the two boundary attitudes
492                 final TimeStampedAngularCoordinates start =
493                                 transitionPreceding.withReferenceFrame(frame).getOrientation();
494                 final TimeStampedAngularCoordinates end =
495                                 future.getAttitude(pvProv, transitionEnd, frame).getOrientation();
496                 final TimeStampedAngularCoordinates interpolated =
497                                 TimeStampedAngularCoordinates.interpolate(date, transitionFilter,
498                                                                           Arrays.asList(start, end));
499 
500                 return new Attitude(frame, interpolated);
501 
502             }
503 
504             /** {@inheritDoc} */
505             public <S extends CalculusFieldElement<S>> FieldAttitude<S> getAttitude(final FieldPVCoordinatesProvider<S> pvProv,
506                                                                                 final FieldAbsoluteDate<S> date,
507                                                                                 final Frame frame) {
508 
509                 // interpolate between the two boundary attitudes
510                 final TimeStampedFieldAngularCoordinates<S> start =
511                                 new TimeStampedFieldAngularCoordinates<>(date.getField(),
512                                                                          transitionPreceding.withReferenceFrame(frame).getOrientation());
513                 final TimeStampedFieldAngularCoordinates<S> end =
514                                 future.getAttitude(pvProv,
515                                                    new FieldAbsoluteDate<>(date.getField(), transitionEnd),
516                                                    frame).getOrientation();
517                 final TimeStampedFieldAngularCoordinates<S> interpolated =
518                                 TimeStampedFieldAngularCoordinates.interpolate(date, transitionFilter,
519                                                                                Arrays.asList(start, end));
520 
521                 return new FieldAttitude<>(frame, interpolated);
522             }
523 
524         }
525 
526     }
527 
528     /** Interface for attitude switch notifications.
529      * <p>
530      * This interface is intended to be implemented by users who want to be
531      * notified when an attitude switch occurs.
532      * </p>
533      * @since 7.1
534      */
535     public interface SwitchHandler {
536 
537         /** Method called when attitude is switched from one law to another law.
538          * @param preceding attitude law used preceding the switch (i.e. in the past
539          * of the switch event for a forward propagation, or in the future
540          * of the switch event for a backward propagation)
541          * @param following attitude law used following the switch (i.e. in the future
542          * of the switch event for a forward propagation, or in the past
543          * of the switch event for a backward propagation)
544          * @param state state at switch time (with attitude computed using the {@code preceding} law)
545          */
546         void switchOccurred(AttitudeProvider preceding, AttitudeProvider following, SpacecraftState state);
547 
548     }
549 
550 }