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      void testConstructor() {
91          // GIVEN
92          final double period = 10.;
93          final Complex threshold = Complex.ONE;
94          final FieldOrbit<Complex> mockedFieldOrbit = Mockito.mock(FieldOrbit.class);
95          Mockito.when(mockedFieldOrbit.getKeplerianPeriod()).thenReturn(new Complex(period));
96          // WHEN
97          final FieldApsideDetector<Complex> fieldApsideDetector = new FieldApsideDetector<>(threshold, mockedFieldOrbit);
98          // THEN
99          final Orbit mockedOrbit = Mockito.mock(Orbit.class);
100         Mockito.when(mockedOrbit.getKeplerianPeriod()).thenReturn(period);
101         final ApsideDetector apsideDetector = new ApsideDetector(threshold.getReal(), mockedOrbit);
102         Assertions.assertEquals(apsideDetector.getThreshold(), fieldApsideDetector.getThreshold().getReal());
103     }
104 
105     @Test
106     void testAnomalyAwareMaxCheck() {
107         final AdaptableInterval adaptableInterval = ApsideDetectionAdaptableIntervalFactory
108                 .getApsideDetectionAdaptableInterval();
109         doTestMaxcheck(Binary64Field.getInstance(), (state, isForward) -> adaptableInterval.currentInterval(state.toSpacecraftState(), true),
110                 706);
111     }
112 
113     private <T extends CalculusFieldElement<T>> void doTestMaxcheck(final Field<T> field,
114                                                                     final FieldAdaptableInterval<T> maxCheck,
115                                                                     int expectedCalls) {
116         final FieldPropagator<T> propagator = createPropagator(field);
117         CountingApsideDetector<T> detector = new CountingApsideDetector<>(propagator, maxCheck);
118         FieldEventsLogger<T> logger = new FieldEventsLogger<>();
119         propagator.addEventDetector(logger.monitorDetector(detector));
120         propagator.propagate(propagator.getInitialState().getOrbit().getDate().shiftedBy(Constants.JULIAN_DAY));
121         Assertions.assertEquals(30, logger.getLoggedEvents().size());
122         Assertions.assertEquals(expectedCalls, detector.count);
123     }
124 
125     @BeforeEach
126     public void setUp() {
127         Utils.setDataRoot("regular-data");
128     }
129 
130     private <T extends CalculusFieldElement<T>> FieldPropagator<T> createPropagator(final Field<T> field) {
131         final T zero = field.getZero();
132 
133         final TimeScale utc = TimeScalesFactory.getUTC();
134         final FieldVector3D<T> position = new FieldVector3D<>(zero.add(-6142438.668), zero.add(3492467.56), zero.add(-25767.257));
135         final FieldVector3D<T> velocity = new FieldVector3D<>(zero.add(506.0), zero.add(943.0), zero.add(7450));
136         final FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field, 2003, 9, 16, utc);
137         final FieldOrbit<T> orbit = new FieldCartesianOrbit<>(new FieldPVCoordinates<>(position,  velocity),
138                                                               FramesFactory.getEME2000(), date,
139                                                               zero.add(Constants.EIGEN5C_EARTH_MU));
140         return new FieldEcksteinHechlerPropagator<>(orbit,
141                                                     Constants.EIGEN5C_EARTH_EQUATORIAL_RADIUS,
142                                                     zero.add(Constants.EIGEN5C_EARTH_MU),
143                                                     Constants.EIGEN5C_EARTH_C20,
144                                                     Constants.EIGEN5C_EARTH_C30,
145                                                     Constants.EIGEN5C_EARTH_C40,
146                                                     Constants.EIGEN5C_EARTH_C50,
147                                                     Constants.EIGEN5C_EARTH_C60);
148     }
149 
150     private class CountingApsideDetector<T extends CalculusFieldElement<T>> implements FieldDetectorModifier<T> {
151 
152         private final FieldEventDetector<T> detector;
153         private int count;
154         
155         public CountingApsideDetector(final FieldPropagator<T> propagator, final FieldAdaptableInterval<T> maxCheck) {
156             this.detector = new FieldApsideDetector<>(propagator.getInitialState().getOrbit()).
157                     withMaxCheck(maxCheck).
158                     withThreshold(propagator.getInitialState().getDate().getField().getZero().newInstance(1.0e-12)).
159                     withHandler(new FieldContinueOnEvent<>());
160         }
161 
162         @Override
163         public FieldEventDetector<T> getDetector() {
164             return detector;
165         }
166 
167         public void init(final FieldSpacecraftState<T> s0, final FieldAbsoluteDate<T> t) {
168             FieldDetectorModifier.super.init(s0, t);
169             count = 0;
170         }
171 
172         public T g(final FieldSpacecraftState<T> s) {
173             ++count;
174             return FieldDetectorModifier.super.g(s);
175         }
176 
177     }
178 
179 }
180