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.propagation.events;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.complex.Complex;
22  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
23  import org.hipparchus.util.Binary64Field;
24  import org.hipparchus.util.FastMath;
25  import org.hipparchus.util.MathUtils;
26  import org.junit.jupiter.api.Assertions;
27  import org.junit.jupiter.api.BeforeEach;
28  import org.junit.jupiter.api.Test;
29  import org.mockito.Mockito;
30  import org.orekit.Utils;
31  import org.orekit.frames.FramesFactory;
32  import org.orekit.orbits.FieldCartesianOrbit;
33  import org.orekit.orbits.FieldKeplerianOrbit;
34  import org.orekit.orbits.FieldOrbit;
35  import org.orekit.orbits.Orbit;
36  import org.orekit.orbits.OrbitType;
37  import org.orekit.propagation.FieldPropagator;
38  import org.orekit.propagation.FieldSpacecraftState;
39  import org.orekit.propagation.analytical.FieldEcksteinHechlerPropagator;
40  import org.orekit.propagation.events.FieldEventsLogger.FieldLoggedEvent;
41  import org.orekit.propagation.events.handlers.FieldContinueOnEvent;
42  import org.orekit.propagation.events.intervals.AdaptableInterval;
43  import org.orekit.propagation.events.intervals.ApsideDetectionAdaptableIntervalFactory;
44  import org.orekit.propagation.events.intervals.FieldAdaptableInterval;
45  import org.orekit.time.FieldAbsoluteDate;
46  import org.orekit.time.TimeScale;
47  import org.orekit.time.TimeScalesFactory;
48  import org.orekit.utils.Constants;
49  import org.orekit.utils.FieldPVCoordinates;
50  
51  class FieldApsideDetectorTest {
52  
53      @Test
54      void testSimple() {
55          doTestSimple(Binary64Field.getInstance());
56      }
57  
58      private <T extends CalculusFieldElement<T>> void doTestSimple(Field<T> field) {
59  
60          final FieldPropagator<T> propagator = createPropagator(field);
61          FieldEventDetector<T> detector = new FieldApsideDetector<>(propagator.getInitialState().getOrbit()).
62                                           withMaxCheck(600.0).
63                                           withThreshold(field.getZero().newInstance(1.0e-12)).
64                                           withHandler(new FieldContinueOnEvent<T>());
65  
66          Assertions.assertEquals(600.0, detector.getMaxCheckInterval().currentInterval(null, true), 1.0e-15);
67          Assertions.assertEquals(1.0e-12, detector.getThreshold().getReal(), 1.0e-15);
68          Assertions.assertEquals(AbstractDetector.DEFAULT_MAX_ITER, detector.getMaxIterationCount());
69  
70          FieldEventsLogger<T> logger = new FieldEventsLogger<>();
71          propagator.addEventDetector(logger.monitorDetector(detector));
72  
73          propagator.propagate(propagator.getInitialState().getOrbit().getDate().shiftedBy(Constants.JULIAN_DAY));
74  
75          Assertions.assertEquals(30, logger.getLoggedEvents().size());
76          for (FieldLoggedEvent<T> e : logger.getLoggedEvents()) {
77              FieldKeplerianOrbit<T> o = (FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(e.getState().getOrbit());
78              double expected = e.isIncreasing() ? 0.0 : FastMath.PI;
79              Assertions.assertEquals(expected, MathUtils.normalizeAngle(o.getMeanAnomaly().getReal(), expected), 4.0e-14);
80          }
81  
82      }
83  
84      @Test
85      void testFixedMaxCheck() {
86          doTestMaxcheck(Binary64Field.getInstance(), FieldAdaptableInterval.of(20.), 4818);
87      }
88  
89      @Test
90      @SuppressWarnings("unchecked")
91      void testConstructor() {
92          // GIVEN
93          final double period = 10.;
94          final Complex threshold = Complex.ONE;
95          final FieldOrbit<Complex> mockedFieldOrbit = Mockito.mock(FieldOrbit.class);
96          Mockito.when(mockedFieldOrbit.getKeplerianPeriod()).thenReturn(new Complex(period));
97          // WHEN
98          final FieldApsideDetector<Complex> fieldApsideDetector = new FieldApsideDetector<>(threshold, mockedFieldOrbit);
99          // THEN
100         final Orbit mockedOrbit = Mockito.mock(Orbit.class);
101         Mockito.when(mockedOrbit.getKeplerianPeriod()).thenReturn(period);
102         final ApsideDetector apsideDetector = new ApsideDetector(threshold.getReal(), mockedOrbit);
103         Assertions.assertEquals(apsideDetector.getThreshold(), fieldApsideDetector.getThreshold().getReal());
104     }
105 
106     @Test
107     void testAnomalyAwareMaxCheck() {
108         final AdaptableInterval adaptableInterval = ApsideDetectionAdaptableIntervalFactory
109                 .getApsideDetectionAdaptableInterval();
110         doTestMaxcheck(Binary64Field.getInstance(), (state, isForward) -> adaptableInterval.currentInterval(state.toSpacecraftState(), true),
111                 706);
112     }
113 
114     private <T extends CalculusFieldElement<T>> void doTestMaxcheck(final Field<T> field,
115                                                                     final FieldAdaptableInterval<T> maxCheck,
116                                                                     int expectedCalls) {
117         final FieldPropagator<T> propagator = createPropagator(field);
118         CountingApsideDetector<T> detector = new CountingApsideDetector<>(propagator, maxCheck);
119         FieldEventsLogger<T> logger = new FieldEventsLogger<>();
120         propagator.addEventDetector(logger.monitorDetector(detector));
121         propagator.propagate(propagator.getInitialState().getOrbit().getDate().shiftedBy(Constants.JULIAN_DAY));
122         Assertions.assertEquals(30, logger.getLoggedEvents().size());
123         Assertions.assertEquals(expectedCalls, detector.count);
124     }
125 
126     @BeforeEach
127     public void setUp() {
128         Utils.setDataRoot("regular-data");
129     }
130 
131     private <T extends CalculusFieldElement<T>> FieldPropagator<T> createPropagator(final Field<T> field) {
132         final T zero = field.getZero();
133 
134         final TimeScale utc = TimeScalesFactory.getUTC();
135         final FieldVector3D<T> position = new FieldVector3D<>(zero.add(-6142438.668), zero.add(3492467.56), zero.add(-25767.257));
136         final FieldVector3D<T> velocity = new FieldVector3D<>(zero.add(506.0), zero.add(943.0), zero.add(7450));
137         final FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field, 2003, 9, 16, utc);
138         final FieldOrbit<T> orbit = new FieldCartesianOrbit<>(new FieldPVCoordinates<>(position,  velocity),
139                                                               FramesFactory.getEME2000(), date,
140                                                               zero.add(Constants.EIGEN5C_EARTH_MU));
141         return new FieldEcksteinHechlerPropagator<>(orbit,
142                                                     Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS,
143                                                     zero.add(Constants.EIGEN5C_EARTH_MU),
144                                                     Constants.EIGEN5C_EARTH_C20,
145                                                     Constants.EIGEN5C_EARTH_C30,
146                                                     Constants.EIGEN5C_EARTH_C40,
147                                                     Constants.EIGEN5C_EARTH_C50,
148                                                     Constants.EIGEN5C_EARTH_C60);
149     }
150 
151     private class CountingApsideDetector<T extends CalculusFieldElement<T>> implements FieldDetectorModifier<T> {
152 
153         private final FieldEventDetector<T> detector;
154         private int count;
155         
156         public CountingApsideDetector(final FieldPropagator<T> propagator, final FieldAdaptableInterval<T> maxCheck) {
157             this.detector = new FieldApsideDetector<>(propagator.getInitialState().getOrbit()).
158                     withMaxCheck(maxCheck).
159                     withThreshold(propagator.getInitialState().getDate().getField().getZero().newInstance(1.0e-12)).
160                     withHandler(new FieldContinueOnEvent<>());
161         }
162 
163         @Override
164         public FieldEventDetector<T> getDetector() {
165             return detector;
166         }
167 
168         public void init(final FieldSpacecraftState<T> s0, final FieldAbsoluteDate<T> t) {
169             FieldDetectorModifier.super.init(s0, t);
170             count = 0;
171         }
172 
173         public T g(final FieldSpacecraftState<T> s) {
174             ++count;
175             return FieldDetectorModifier.super.g(s);
176         }
177 
178     }
179 
180 }
181