1   /* Copyright 2002-2025 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.forces.maneuvers.trigger;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.geometry.euclidean.threed.Rotation;
22  import org.hipparchus.geometry.euclidean.threed.Vector3D;
23  import org.hipparchus.ode.nonstiff.AdaptiveStepsizeFieldIntegrator;
24  import org.hipparchus.ode.nonstiff.AdaptiveStepsizeIntegrator;
25  import org.hipparchus.ode.nonstiff.DormandPrince853FieldIntegrator;
26  import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
27  import org.hipparchus.util.Binary64Field;
28  import org.hipparchus.util.FastMath;
29  import org.hipparchus.util.MathUtils;
30  import org.junit.jupiter.api.Assertions;
31  import org.junit.jupiter.api.BeforeEach;
32  import org.junit.jupiter.api.Test;
33  import org.orekit.Utils;
34  import org.orekit.attitudes.AttitudeProvider;
35  import org.orekit.attitudes.FrameAlignedProvider;
36  import org.orekit.forces.maneuvers.Maneuver;
37  import org.orekit.forces.maneuvers.propulsion.BasicConstantThrustPropulsionModel;
38  import org.orekit.frames.FramesFactory;
39  import org.orekit.orbits.FieldKeplerianOrbit;
40  import org.orekit.orbits.FieldOrbit;
41  import org.orekit.orbits.KeplerianOrbit;
42  import org.orekit.orbits.Orbit;
43  import org.orekit.orbits.OrbitType;
44  import org.orekit.orbits.PositionAngleType;
45  import org.orekit.propagation.FieldSpacecraftState;
46  import org.orekit.propagation.SpacecraftState;
47  import org.orekit.propagation.ToleranceProvider;
48  import org.orekit.propagation.events.EventDetector;
49  import org.orekit.propagation.events.FieldEventDetector;
50  import org.orekit.propagation.numerical.FieldNumericalPropagator;
51  import org.orekit.propagation.numerical.NumericalPropagator;
52  import org.orekit.time.AbsoluteDate;
53  import org.orekit.time.DateComponents;
54  import org.orekit.time.FieldAbsoluteDate;
55  import org.orekit.time.TimeComponents;
56  import org.orekit.time.TimeScalesFactory;
57  import org.orekit.utils.Constants;
58  
59  import java.util.List;
60  import java.util.stream.Collectors;
61  
62  public abstract class AbstractManeuverTriggersTest<T extends AbstractManeuverTriggers> {
63  
64      /** trigger dates. */
65      private AbsoluteDate triggerStart;
66      private AbsoluteDate triggerStop;
67  
68      protected abstract T createTrigger(AbsoluteDate start, AbsoluteDate stop);
69  
70      private T configureTrigger(final AbsoluteDate start, final AbsoluteDate stop) {
71          T trigger = createTrigger(start, stop);
72          // set up separate detectors, to check lists of observers are handled properly
73          trigger.addResetter(new Resetter());
74          trigger.addResetter(new Resetter());
75          return trigger;
76      }
77  
78      private <S extends CalculusFieldElement<S>> T configureTrigger(final Field<S> field,
79                                                                     final AbsoluteDate start, final AbsoluteDate stop) {
80          T trigger = createTrigger(start, stop);
81          // set up separate detectors, to check lists of observers are handled properly
82          trigger.addResetter(field, new FieldResetter<>());
83          trigger.addResetter(field, new FieldResetter<>());
84          return trigger;
85      }
86  
87      private class Resetter implements ManeuverTriggersResetter {
88          public void maneuverTriggered(SpacecraftState state, boolean start) {
89              if (start)  {
90                  triggerStart = state.getDate();
91              } else {
92                  triggerStop  = state.getDate();
93              }
94          }
95          public SpacecraftState resetState(SpacecraftState state) {
96              return state;
97          }
98      }
99      private class FieldResetter<S extends CalculusFieldElement<S>> implements FieldManeuverTriggersResetter<S> {
100         public void maneuverTriggered(FieldSpacecraftState<S> state, boolean start) {
101             if (start)  {
102                 triggerStart = state.getDate().toAbsoluteDate();
103             } else {
104                 triggerStop = state.getDate().toAbsoluteDate();
105             }
106         }
107         public FieldSpacecraftState<S> resetState(FieldSpacecraftState<S> state) {
108             return state;
109         }
110     }
111     @BeforeEach
112     public void setUp() {
113         Utils.setDataRoot("regular-data");
114         triggerStart = null;
115         triggerStop  = null;
116     }
117 
118     @Test
119     void testRoughBehaviour() {
120 
121         final AttitudeProvider attitudeProvider = buildAttitudeProvider();
122         final SpacecraftState initialState = buildInitialState(attitudeProvider);
123 
124         final AbsoluteDate fireDate = new AbsoluteDate(new DateComponents(2004, 01, 02),
125                                                        new TimeComponents(04, 15, 34.080),
126                                                        TimeScalesFactory.getUTC());
127         final double isp = 318;
128         final double duration = 3653.99;
129         final double f = 420;
130         final Maneuver maneuver = new Maneuver(null,
131                                                configureTrigger(fireDate, fireDate.shiftedBy(duration)),
132                                                new BasicConstantThrustPropulsionModel(f, isp, Vector3D.PLUS_I, "ABM"));
133         Assertions.assertEquals(f,   ((BasicConstantThrustPropulsionModel) maneuver.getPropulsionModel()).getThrustMagnitude(), 1.0e-10);
134         Assertions.assertEquals(isp, ((BasicConstantThrustPropulsionModel) maneuver.getPropulsionModel()).getIsp(),    1.0e-10);
135 
136         final SpacecraftState finalorb = buildPropagator(attitudeProvider, initialState, maneuver).
137                                          propagate(fireDate.shiftedBy(3800));
138 
139         final double flowRate = ((BasicConstantThrustPropulsionModel) maneuver.getPropulsionModel()).getFlowRate();
140         final double massTolerance = FastMath.abs(flowRate) * maneuver.getEventDetectors().findFirst().get().getThreshold();
141         Assertions.assertEquals(2007.8824544261233, finalorb.getMass(), massTolerance);
142         Assertions.assertEquals(2.6872, FastMath.toDegrees(MathUtils.normalizeAngle(finalorb.getOrbit().getI(), FastMath.PI)), 1e-4);
143         Assertions.assertEquals(28970, finalorb.getOrbit().getA()/1000, 1);
144 
145         final List<EventDetector> list1 = maneuver.getManeuverTriggers().getEventDetectors().collect(Collectors.toList());
146         final List<EventDetector> list2 = maneuver.getManeuverTriggers().getEventDetectors().collect(Collectors.toList());
147         Assertions.assertEquals(list1.size(), list2.size());
148         for (int i = 0; i < list1.size(); ++i ) {
149             Assertions.assertSame(list1.get(i), list2.get(i));
150         }
151 
152         Assertions.assertEquals(0.0,      triggerStart.durationFrom(fireDate), 1.0e-10);
153         Assertions.assertEquals(duration, triggerStop.durationFrom(fireDate),  1.0e-10);
154 
155     }
156 
157     @Test
158     void testBackward() {
159 
160         final AttitudeProvider attitudeProvider = buildAttitudeProvider();
161         final SpacecraftState initialState = buildInitialState(attitudeProvider);
162 
163         final AbsoluteDate fireDate = new AbsoluteDate(new DateComponents(2004, 01, 02),
164                                                        new TimeComponents(04, 15, 34.080),
165                                                        TimeScalesFactory.getUTC());
166         final double isp = 318;
167         final double duration = 30;
168         final double f = 420;
169         final Maneuver maneuver = new Maneuver(null,
170                                                configureTrigger(fireDate, fireDate.shiftedBy(duration)),
171                                                new BasicConstantThrustPropulsionModel(f, isp, Vector3D.PLUS_I, "ABM"));
172         Assertions.assertEquals(f,   ((BasicConstantThrustPropulsionModel) maneuver.getPropulsionModel()).getThrustMagnitude(), 1.0e-10);
173         Assertions.assertEquals(isp, ((BasicConstantThrustPropulsionModel) maneuver.getPropulsionModel()).getIsp(),    1.0e-10);
174 
175         final SpacecraftState finalorb = buildPropagator(attitudeProvider,
176                                                          initialState.shiftedBy(initialState.getOrbit().getKeplerianPeriod()),
177                                                          maneuver).
178                                          propagate(fireDate.shiftedBy(-10));
179 
180         Assertions.assertEquals(2504.040, finalorb.getMass(), 1.0e-3);
181 
182         Assertions.assertEquals(0.0,      triggerStart.durationFrom(fireDate), 1.0e-10);
183         Assertions.assertEquals(duration, triggerStop.durationFrom(fireDate),  1.0e-10);
184 
185     }
186 
187     @Test
188     void testRoughBehaviourField() {
189         doTestRoughBehaviourField(Binary64Field.getInstance());
190     }
191 
192     private <S extends CalculusFieldElement<S>> void doTestRoughBehaviourField(Field<S> field) {
193         final AttitudeProvider attitudeProvider = buildAttitudeProvider();
194         final FieldSpacecraftState<S> initialState = buildInitialState(field, attitudeProvider);
195 
196 
197         final AbsoluteDate fireDate = new AbsoluteDate(new DateComponents(2004, 01, 02),
198                                                        new TimeComponents(04, 15, 34.080),
199                                                        TimeScalesFactory.getUTC());
200         final double isp = 318;
201         final double duration = 3653.99;
202         final double f = 420;
203         final Maneuver maneuver = new Maneuver(null,
204                                                configureTrigger(field, fireDate, fireDate.shiftedBy(duration)),
205                                                new BasicConstantThrustPropulsionModel(f, isp, Vector3D.PLUS_I, "ABM"));
206         Assertions.assertEquals(f,   ((BasicConstantThrustPropulsionModel) maneuver.getPropulsionModel()).getThrustMagnitude(), 1.0e-10);
207         Assertions.assertEquals(isp, ((BasicConstantThrustPropulsionModel) maneuver.getPropulsionModel()).getIsp(),    1.0e-10);
208 
209         final FieldSpacecraftState<S> finalorb = buildPropagator(field, attitudeProvider, initialState, maneuver).
210                                                  propagate(new FieldAbsoluteDate<>(field, fireDate).shiftedBy(3800));
211 
212         final double flowRate = ((BasicConstantThrustPropulsionModel) maneuver.getPropulsionModel()).getFlowRate();
213         final double massTolerance = FastMath.abs(flowRate) * maneuver.getEventDetectors().findFirst().get().getThreshold();
214         Assertions.assertEquals(2007.8824544261233, finalorb.getMass().getReal(), massTolerance);
215         Assertions.assertEquals(2.6872, FastMath.toDegrees(MathUtils.normalizeAngle(finalorb.getOrbit().getI(), field.getZero().newInstance(FastMath.PI))).getReal(), 1e-4);
216         Assertions.assertEquals(28970, finalorb.getOrbit().getA().divide(1000).getReal(), 1);
217 
218         final List<FieldEventDetector<?>> list1 = maneuver.getManeuverTriggers().getFieldEventDetectors(field).collect(Collectors.toList());
219         final List<FieldEventDetector<?>> list2 = maneuver.getManeuverTriggers().getFieldEventDetectors(field).collect(Collectors.toList());
220         Assertions.assertEquals(list1.size(), list2.size());
221         for (int i = 0; i < list1.size(); ++i ) {
222             Assertions.assertSame(list1.get(i), list2.get(i));
223         }
224 
225         Assertions.assertEquals(0.0,      triggerStart.durationFrom(fireDate), 1.0e-10);
226         Assertions.assertEquals(duration, triggerStop.durationFrom(fireDate),  1.0e-10);
227 
228     }
229 
230     @Test
231     void testBackwardField() {
232         doTestBackwardField(Binary64Field.getInstance());
233     }
234 
235     private <S extends CalculusFieldElement<S>> void doTestBackwardField(Field<S> field) {
236         final AttitudeProvider attitudeProvider = buildAttitudeProvider();
237         final FieldSpacecraftState<S> initialState = buildInitialState(field, attitudeProvider);
238 
239 
240         final AbsoluteDate fireDate = new AbsoluteDate(new DateComponents(2004, 01, 02),
241                                                        new TimeComponents(04, 15, 34.080),
242                                                        TimeScalesFactory.getUTC());
243         final double isp = 318;
244         final double duration = 30;
245         final double f = 420;
246         final Maneuver maneuver = new Maneuver(null,
247                                                configureTrigger(field, fireDate, fireDate.shiftedBy(duration)),
248                                                new BasicConstantThrustPropulsionModel(f, isp, Vector3D.PLUS_I, "ABM"));
249         Assertions.assertEquals(f,   ((BasicConstantThrustPropulsionModel) maneuver.getPropulsionModel()).getThrustMagnitude(), 1.0e-10);
250         Assertions.assertEquals(isp, ((BasicConstantThrustPropulsionModel) maneuver.getPropulsionModel()).getIsp(),    1.0e-10);
251 
252         final FieldSpacecraftState<S> finalorb = buildPropagator(field, attitudeProvider,
253                                                                  initialState.shiftedBy(initialState.getOrbit().getKeplerianPeriod()),
254                                                                  maneuver).
255                                                  propagate(new FieldAbsoluteDate<>(field, fireDate).shiftedBy(-10));
256 
257         Assertions.assertEquals(2504.040, finalorb.getMass().getReal(), 1.0e-3);
258 
259         Assertions.assertEquals(0.0,      triggerStart.durationFrom(fireDate), 1.0e-10);
260         Assertions.assertEquals(duration, triggerStop.durationFrom(fireDate),  1.0e-10);
261 
262     }
263 
264     private AttitudeProvider buildAttitudeProvider() {
265         final double delta = FastMath.toRadians(-7.4978);
266         final double alpha = FastMath.toRadians(351);
267         return new FrameAlignedProvider(new Rotation(new Vector3D(alpha, delta), Vector3D.PLUS_I));
268     }
269 
270     private SpacecraftState buildInitialState(final AttitudeProvider attitudeProvider) {
271         final double mass = 2500;
272         final double a = 24396159;
273         final double e = 0.72831215;
274         final double i = FastMath.toRadians(7);
275         final double omega = FastMath.toRadians(180);
276         final double OMEGA = FastMath.toRadians(261);
277         final double lv = 0;
278 
279         final AbsoluteDate initDate = new AbsoluteDate(new DateComponents(2004, 01, 01),
280                                                        new TimeComponents(23, 30, 00.000),
281                                                        TimeScalesFactory.getUTC());
282         final Orbit orbit =
283             new KeplerianOrbit(a, e, i, omega, OMEGA, lv, PositionAngleType.TRUE,
284                                FramesFactory.getEME2000(), initDate, Constants.EIGEN5C_EARTH_MU);
285         return new SpacecraftState(orbit, attitudeProvider.getAttitude(orbit, orbit.getDate(), orbit.getFrame()), mass);
286     }
287 
288     private <S extends CalculusFieldElement<S>> FieldSpacecraftState<S> buildInitialState(final Field<S> field,
289                                                                        final AttitudeProvider attitudeProvider) {
290         final S zero = field.getZero();
291         final S mass = zero.newInstance(2500);
292         final S a = zero.newInstance(24396159);
293         final S e = zero.newInstance(0.72831215);
294         final S i = FastMath.toRadians(zero.newInstance(7));
295         final S omega = FastMath.toRadians(zero.newInstance(180));
296         final S OMEGA = FastMath.toRadians(zero.newInstance(261));
297         final S lv = zero.newInstance(0);
298 
299         final FieldAbsoluteDate<S> initDate = new FieldAbsoluteDate<>(field,
300                         new DateComponents(2004, 01, 01),
301                         new TimeComponents(23, 30, 00.000),
302                         TimeScalesFactory.getUTC());
303         final FieldOrbit<S> orbit =
304                         new FieldKeplerianOrbit<>(a, e, i, omega, OMEGA, lv, PositionAngleType.TRUE,
305                                         FramesFactory.getEME2000(), initDate, zero.newInstance(Constants.EIGEN5C_EARTH_MU));
306         return new FieldSpacecraftState<>(orbit, attitudeProvider.getAttitude(orbit, orbit.getDate(), orbit.getFrame()), mass);
307     }
308 
309     private NumericalPropagator buildPropagator(final AttitudeProvider attitudeProvider, final SpacecraftState initialState,
310                                                 final Maneuver maneuver) {
311         OrbitType orbitType = OrbitType.EQUINOCTIAL;
312         double[][] tol = ToleranceProvider.getDefaultToleranceProvider(1e-3).getTolerances(initialState.getOrbit(), orbitType);
313         AdaptiveStepsizeIntegrator integrator = new DormandPrince853Integrator(0.001, 1000, tol[0], tol[1]);
314         integrator.setInitialStepSize(60);
315         final NumericalPropagator propagator = new NumericalPropagator(integrator);
316         propagator.setOrbitType(orbitType);
317         propagator.setInitialState(initialState);
318         propagator.setAttitudeProvider(attitudeProvider);
319         propagator.addForceModel(maneuver);
320         return propagator;
321     }
322 
323     private <S extends CalculusFieldElement<S>> FieldNumericalPropagator<S> buildPropagator(final Field<S> field,
324                                                                                             final AttitudeProvider attitudeProvider,
325                                                                                             final FieldSpacecraftState<S> initialState,
326                                                                                             final Maneuver maneuver) {
327         OrbitType orbitType = OrbitType.EQUINOCTIAL;
328         double[][] tol = ToleranceProvider.getDefaultToleranceProvider(1e-3).getTolerances(initialState.getOrbit(), orbitType);
329         AdaptiveStepsizeFieldIntegrator<S> integrator = new DormandPrince853FieldIntegrator<>(field, 0.001, 1000, tol[0], tol[1]);
330         integrator.setInitialStepSize(60);
331         final FieldNumericalPropagator<S> propagator = new FieldNumericalPropagator<>(field, integrator);
332         propagator.setOrbitType(orbitType);
333         propagator.setInitialState(initialState);
334         propagator.setAttitudeProvider(attitudeProvider);
335         propagator.addForceModel(maneuver);
336         return propagator;
337     }
338 
339 }