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 }