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.forces.maneuvers.trigger;
18  
19  import org.hipparchus.geometry.euclidean.threed.Vector3D;
20  import org.hipparchus.ode.nonstiff.ClassicalRungeKuttaIntegrator;
21  import org.hipparchus.util.Binary64;
22  import org.hipparchus.util.Binary64Field;
23  import org.junit.jupiter.api.Test;
24  import org.orekit.TestUtils;
25  import org.orekit.forces.maneuvers.ConstantThrustManeuver;
26  import org.orekit.forces.maneuvers.Maneuver;
27  import org.orekit.forces.maneuvers.propulsion.BasicConstantThrustPropulsionModel;
28  import org.orekit.forces.maneuvers.propulsion.ThrustPropulsionModel;
29  import org.orekit.orbits.Orbit;
30  import org.orekit.propagation.SpacecraftState;
31  import org.orekit.propagation.events.BooleanDetector;
32  import org.orekit.propagation.events.EventDetector;
33  import org.orekit.propagation.events.FieldBooleanDetector;
34  import org.orekit.propagation.events.FieldEventDetector;
35  import org.orekit.propagation.events.FieldTimeIntervalDetector;
36  import org.orekit.propagation.events.TimeIntervalDetector;
37  import org.orekit.propagation.events.handlers.ContinueOnEvent;
38  import org.orekit.propagation.numerical.NumericalPropagator;
39  import org.orekit.time.AbsoluteDate;
40  import org.orekit.time.TimeInterval;
41  import org.orekit.utils.TimeSpanMap;
42  
43  import static org.junit.jupiter.api.Assertions.*;
44  import static org.mockito.Mockito.mock;
45  import static org.mockito.Mockito.when;
46  
47  class TimeIntervalsManeuverTriggerTest {
48  
49      @Test
50      void testOfDetectors() {
51          // GIVEN
52          final TimeInterval timeInterval = TimeInterval.of(AbsoluteDate.PAST_INFINITY, AbsoluteDate.FUTURE_INFINITY);
53          final TimeIntervalDetector intervalDetector = new TimeIntervalDetector(new ContinueOnEvent(), timeInterval);
54          // WHEN
55          final TimeIntervalsManeuverTrigger trigger = TimeIntervalsManeuverTrigger.of(intervalDetector);
56          // THEN
57          final BooleanDetector booleanDetector = trigger.getFiringIntervalDetector();
58          assertEquals(intervalDetector.getThreshold(), booleanDetector.getThreshold());
59          assertEquals(intervalDetector.getMaxIterationCount(), booleanDetector.getMaxIterationCount());
60          final SpacecraftState mockedState = mock();
61          when(mockedState.getDate()).thenReturn(AbsoluteDate.ARBITRARY_EPOCH);
62          final boolean forward = true;
63          assertEquals(intervalDetector.getMaxCheckInterval().currentInterval(mockedState, forward),
64                  booleanDetector.getMaxCheckInterval().currentInterval(mockedState, forward));
65      }
66  
67      @Test
68      void testOfTimeIntervals() {
69          // GIVEN
70          final TimeInterval timeInterval = TimeInterval.of(AbsoluteDate.PAST_INFINITY, AbsoluteDate.FUTURE_INFINITY);
71          // WHEN
72          final TimeIntervalsManeuverTrigger trigger = TimeIntervalsManeuverTrigger.of(timeInterval, timeInterval);
73          // THEN
74          final BooleanDetector booleanDetector = trigger.getFiringIntervalDetector();
75          for (final EventDetector detector: booleanDetector.getDetectors()) {
76              assertInstanceOf(TimeIntervalDetector.class, detector);
77              assertEquals(timeInterval, ((TimeIntervalDetector) detector).getTimeInterval());
78          }
79      }
80  
81      @Test
82      void testPropagationFiringSpans() {
83          // GIVEN
84          final AbsoluteDate epoch = AbsoluteDate.ARBITRARY_EPOCH;
85          final AbsoluteDate startFiring = epoch.shiftedBy(100.);
86          final double duration = 10;
87          final TimeInterval firstTimeInterval = TimeInterval.of(startFiring, startFiring.shiftedBy(duration));
88          final AbsoluteDate secondFiring = firstTimeInterval.getEndDate().shiftedBy(30.);
89          final TimeInterval secondTimeInterval = TimeInterval.of(secondFiring, secondFiring.shiftedBy(duration));
90          final TimeIntervalsManeuverTrigger trigger = TimeIntervalsManeuverTrigger.of(firstTimeInterval, secondTimeInterval);
91          final Orbit initialOrbit = TestUtils.getDefaultOrbit(epoch);
92          final SpacecraftState initialState = new SpacecraftState(initialOrbit);
93          final ThrustPropulsionModel propulsionModel = new BasicConstantThrustPropulsionModel(1e-2, 1000., Vector3D.MINUS_I, "");
94          final NumericalPropagator propagator = buildPropagator(initialState, new Maneuver(null, trigger, propulsionModel));
95          // WHEN
96          propagator.propagate(secondTimeInterval.getEndDate().shiftedBy(1));
97          // THEN
98          final TimeSpanMap<Boolean> firings = trigger.getFirings();
99          assertEquals(5, firings.getSpansNumber());
100         assertEquals(firstTimeInterval.getStartDate(), firings.getFirstNonNullSpan().getEnd());
101         assertEquals(secondTimeInterval.getEndDate(), firings.getLastNonNullSpan().getStart());
102     }
103 
104     @Test
105     void testPropagationAgainstConstantThrustManeuver() {
106         // GIVEN
107         final AbsoluteDate epoch = AbsoluteDate.ARBITRARY_EPOCH;
108         final AbsoluteDate startFiring = epoch.shiftedBy(100.);
109         final double duration = 60;
110         final TimeInterval timeInterval = TimeInterval.of(startFiring, startFiring.shiftedBy(duration));
111         final TimeIntervalsManeuverTrigger trigger = TimeIntervalsManeuverTrigger.of(new TimeIntervalDetector(new ContinueOnEvent(),
112                 timeInterval));
113         final Orbit initialOrbit = TestUtils.getDefaultOrbit(epoch);
114         final SpacecraftState initialState = new SpacecraftState(initialOrbit);
115         final BasicConstantThrustPropulsionModel propulsionModel = new BasicConstantThrustPropulsionModel(1e-1, 100., Vector3D.PLUS_I, "");
116         final NumericalPropagator propagator = buildPropagator(initialState, new Maneuver(null, trigger, propulsionModel));
117         // WHEN
118         final AbsoluteDate terminalDate = timeInterval.getEndDate().shiftedBy(1);
119         final SpacecraftState actualState = propagator.propagate(terminalDate);
120         // THEN
121         final TimeSpanMap<Boolean> firings = trigger.getFirings();
122         assertEquals(3, firings.getSpansNumber());
123         final Maneuver maneuver = new ConstantThrustManeuver(startFiring, duration, null, propulsionModel);
124         final NumericalPropagator otherPropagator = buildPropagator(initialState, maneuver);
125         final SpacecraftState expectedState = otherPropagator.propagate(terminalDate);
126         assertEquals(expectedState.getPosition(), actualState.getPosition());
127     }
128 
129     private static NumericalPropagator buildPropagator(final SpacecraftState state, final Maneuver maneuver) {
130         final NumericalPropagator propagator = new NumericalPropagator(new ClassicalRungeKuttaIntegrator(20.));
131         propagator.setInitialState(state);
132         propagator.addForceModel(maneuver);
133         return propagator;
134     }
135 
136     @Test
137     void testGetParametersDrivers() {
138         // GIVEN
139         final TimeInterval timeInterval = TimeInterval.of(AbsoluteDate.PAST_INFINITY, AbsoluteDate.FUTURE_INFINITY);
140         // WHEN
141         final TimeIntervalsManeuverTrigger trigger = TimeIntervalsManeuverTrigger.of(timeInterval);
142         // THEN
143         assertTrue(trigger.getParametersDrivers().isEmpty());
144     }
145 
146     @Test
147     void testConvertIntervalDetector() {
148         // GIVEN
149         final TimeInterval timeInterval = TimeInterval.of(AbsoluteDate.PAST_INFINITY, AbsoluteDate.FUTURE_INFINITY);
150         final TimeIntervalsManeuverTrigger trigger = TimeIntervalsManeuverTrigger.of(timeInterval);
151         final Binary64Field field = Binary64Field.getInstance();
152         // WHEN
153         final FieldEventDetector<Binary64> fieldEventDetector = trigger.convertIntervalDetector(field,
154                 trigger.getFiringIntervalDetector());
155         // THEN
156         assertInstanceOf(FieldBooleanDetector.class, fieldEventDetector);
157         final FieldBooleanDetector<Binary64> fieldBooleanDetector = (FieldBooleanDetector<Binary64>) fieldEventDetector;
158         assertEquals(1, fieldBooleanDetector.getDetectors().size());
159         assertInstanceOf(FieldTimeIntervalDetector.class, fieldBooleanDetector.getDetectors().get(0));
160     }
161 }