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;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
22  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
23  import org.hipparchus.ode.nonstiff.AdaptiveStepsizeFieldIntegrator;
24  import org.hipparchus.ode.nonstiff.DormandPrince54FieldIntegrator;
25  import org.hipparchus.util.Binary64Field;
26  import org.hipparchus.util.FastMath;
27  import org.hipparchus.util.MathArrays;
28  import org.junit.jupiter.api.Assertions;
29  import org.junit.jupiter.api.BeforeEach;
30  import org.junit.jupiter.api.Test;
31  import org.orekit.Utils;
32  import org.orekit.attitudes.FieldAttitude;
33  import org.orekit.forces.gravity.potential.GravityFieldFactory;
34  import org.orekit.forces.gravity.potential.SHMFormatReader;
35  import org.orekit.frames.FramesFactory;
36  import org.orekit.orbits.FieldEquinoctialOrbit;
37  import org.orekit.orbits.FieldOrbit;
38  import org.orekit.orbits.OrbitType;
39  import org.orekit.propagation.analytical.BrouwerLyddanePropagator;
40  import org.orekit.propagation.analytical.FieldBrouwerLyddanePropagator;
41  import org.orekit.propagation.numerical.FieldNumericalPropagator;
42  import org.orekit.propagation.semianalytical.dsst.FieldDSSTPropagator;
43  import org.orekit.time.FieldAbsoluteDate;
44  import org.orekit.utils.FieldPVCoordinates;
45  
46  public class FieldAdditionalDataProviderTest {
47  
48      private double mu;
49      private static final double DURATION = 600.0;
50      private static final String STRING_BEFORE = "Let's go!";
51      private static final String STRING_AFTER = "Good job!";
52  
53      @BeforeEach
54      public void setUp() {
55          Utils.setDataRoot("regular-data:potential/shm-format");
56          GravityFieldFactory.addPotentialCoefficientsReader(new SHMFormatReader("^eigen_cg03c_coef$", false));
57          mu = GravityFieldFactory.getUnnormalizedProvider(0, 0).getMu();
58      }
59  
60      private <T extends CalculusFieldElement<T>> FieldSpacecraftState<T> createState(final Field<T> field) {
61          final T zero = field.getZero();
62          final FieldVector3D<T> position = new FieldVector3D<>(zero.add(7.0e6), zero.add(1.0e6), zero.add(4.0e6));
63          final FieldVector3D<T> velocity = new FieldVector3D<>(zero.add(-500.0), zero.add(8000.0), zero.add(1000.0));
64          final FieldAbsoluteDate<T> initDate = FieldAbsoluteDate.getJ2000Epoch(field);
65          final FieldOrbit<T> orbit = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(position, velocity),
66                                                   FramesFactory.getEME2000(), initDate, zero.add(mu));
67          return new FieldSpacecraftState<>(orbit);
68      }
69  
70      private <T extends CalculusFieldElement<T>> AdaptiveStepsizeFieldIntegrator<T> createIntegrator(final Field<T> field, final FieldSpacecraftState<T> state) {
71          double[][] tolerance = ToleranceProvider.getDefaultToleranceProvider(0.001).getTolerances(state.getOrbit(), OrbitType.EQUINOCTIAL);
72          AdaptiveStepsizeFieldIntegrator<T> integrator = new DormandPrince54FieldIntegrator<>(field, 0.001, 200, tolerance[0], tolerance[1]);
73          integrator.setInitialStepSize(60);
74          return integrator;
75      }
76  
77      @Test
78      public void testModifyMainState() {
79          doTestModifyMainState(Binary64Field.getInstance());
80      }
81  
82      private <T extends CalculusFieldElement<T>> void doTestModifyMainState(final Field<T> field) {
83  
84          // Create propagator
85          final FieldSpacecraftState<T> state = createState(field);
86          final AdaptiveStepsizeFieldIntegrator<T> integrator = createIntegrator(field, state);
87          final FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
88          propagator.setInitialState(state);
89  
90          // Create state modifier
91          final MainStateModifier<T> modifier = new MainStateModifier<>();
92  
93          // Add the provider to the propagator
94          propagator.addAdditionalDataProvider(modifier);
95  
96          // Propagate
97          final double dt = 600.0;
98          final FieldSpacecraftState<T> propagated = propagator.propagate(state.getDate().shiftedBy(dt));
99  
100         // Verify
101         Assertions.assertEquals(2 * SpacecraftState.DEFAULT_MASS, propagated.getMass().getReal(), 1.0e-12);
102         Assertions.assertEquals(FastMath.PI,
103                                 propagated.getAttitude().getRotation().getAngle().getReal(),
104                                 1.0e-15);
105 
106     }
107 
108     @Test
109     public void testIssue900Numerical() {
110         doTestIssue900Numerical(Binary64Field.getInstance());
111     }
112 
113     private <T extends CalculusFieldElement<T>> void doTestIssue900Numerical(final Field<T> field) {
114 
115         // Create propagator
116         final FieldSpacecraftState<T> state = createState(field);
117         final AdaptiveStepsizeFieldIntegrator<T> integrator = createIntegrator(field, state);
118         final FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
119         propagator.setInitialState(state);
120 
121         // Create additional state provider
122         final String name          = "init";
123         final TimeDifferenceProvider<T> provider = new TimeDifferenceProvider<>(name, field);
124         Assertions.assertFalse(provider.wasCalled());
125 
126         // Add the provider to the propagator
127         propagator.addAdditionalDataProvider(provider);
128 
129         // Propagate
130         final double dt = 600.0;
131         final FieldSpacecraftState<T> propagated = propagator.propagate(state.getDate().shiftedBy(dt));
132 
133         // Verify
134         Assertions.assertTrue(provider.wasCalled());
135         Assertions.assertEquals(dt, propagated.getAdditionalState(name)[0].getReal(), 0.01);
136 
137     }
138 
139     @Test
140     public void testIssue900Dsst() {
141         doTestIssue900Dsst(Binary64Field.getInstance());
142     }
143 
144     private <T extends CalculusFieldElement<T>> void doTestIssue900Dsst(final Field<T> field) {
145 
146         // Create propagator
147         final FieldSpacecraftState<T> state = createState(field);
148         final AdaptiveStepsizeFieldIntegrator<T> integrator = createIntegrator(field, state);
149         final FieldDSSTPropagator<T> propagator = new FieldDSSTPropagator<>(field, integrator);
150         propagator.setInitialState(state, PropagationType.MEAN);
151 
152         // Create additional state provider
153         final String name          = "init";
154         final TimeDifferenceProvider<T> provider = new TimeDifferenceProvider<>(name, field);
155         Assertions.assertFalse(provider.wasCalled());
156 
157         // Add the provider to the propagator
158         propagator.addAdditionalDataProvider(provider);
159 
160         // Propagate
161         final double dt = 600.0;
162         final FieldSpacecraftState<T> propagated = propagator.propagate(state.getDate().shiftedBy(dt));
163 
164         // Verify
165         Assertions.assertTrue(provider.wasCalled());
166         Assertions.assertEquals(dt, propagated.getAdditionalState(name)[0].getReal(), 0.01);
167 
168     }
169 
170     @Test
171     public void testIssue900BrouwerLyddane() {
172         doTestIssue900BrouwerLyddane(Binary64Field.getInstance());
173     }
174 
175     private <T extends CalculusFieldElement<T>> void doTestIssue900BrouwerLyddane(final Field<T> field) {
176 
177         // Create propagator
178         final FieldSpacecraftState<T> state = createState(field);
179         final FieldBrouwerLyddanePropagator<T> propagator =
180                         new FieldBrouwerLyddanePropagator<>(state.getOrbit(), GravityFieldFactory.getUnnormalizedProvider(5, 0), BrouwerLyddanePropagator.M2);
181 
182         // Create additional state provider
183         final String name          = "init";
184         final TimeDifferenceProvider<T> provider = new TimeDifferenceProvider<>(name, field);
185         Assertions.assertFalse(provider.wasCalled());
186 
187         // Add the provider to the propagator
188         propagator.addAdditionalDataProvider(provider);
189 
190         // Propagate
191         final double dt = 600.0;
192         final FieldSpacecraftState<T> propagated = propagator.propagate(state.getDate().shiftedBy(dt));
193 
194         // Verify
195         Assertions.assertTrue(provider.wasCalled());
196         Assertions.assertEquals(dt, propagated.getAdditionalState(name)[0].getReal(), 0.01);
197 
198     }
199 
200     @Test
201     void testPropagateAdditionalStringData() {
202         doTestPropagateAdditionalStringData(Binary64Field.getInstance());
203     }
204 
205     private <T extends CalculusFieldElement<T>> void doTestPropagateAdditionalStringData(Field<T> field) {
206 
207         final FieldSpacecraftState<T> state = createState(field);
208         final AdaptiveStepsizeFieldIntegrator<T> integrator = createIntegrator(field, state);
209         final FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
210         propagator.setInitialState(state);
211 
212         final MainStringDataModifier<T> modifier = new MainStringDataModifier<>();
213         propagator.addAdditionalDataProvider(modifier);
214 
215         final FieldSpacecraftState<T> propagated = propagator.propagate(state.getDate().shiftedBy(DURATION));
216         Assertions.assertEquals(STRING_AFTER, propagated.getAdditionalData(MainStringDataModifier.class.getSimpleName()));
217     }
218 
219     @Test
220     void testInterpolationAdditionalStringData() {
221         doTestInterpolationAdditionalStringData(Binary64Field.getInstance());
222     }
223 
224     private <T extends CalculusFieldElement<T>> void doTestInterpolationAdditionalStringData(Field<T> field) {
225         final FieldSpacecraftState<T> state = createState(field);
226         final AdaptiveStepsizeFieldIntegrator<T> integrator = createIntegrator(field, state);
227         final FieldNumericalPropagator<T> propagator = new FieldNumericalPropagator<>(field, integrator);
228         propagator.setInitialState(state);
229 
230         final MainStringDataModifier<T> modifier = new MainStringDataModifier<>();
231         propagator.addAdditionalDataProvider(modifier);
232         FieldEphemerisGenerator<T> generator = propagator.getEphemerisGenerator();
233         propagator.propagate(state.getDate().shiftedBy(DURATION));
234         FieldBoundedPropagator<T> ephemeris = generator.getGeneratedEphemeris();
235 
236         Assertions.assertEquals(STRING_BEFORE, getAdditionalDataAt(ephemeris, state.getDate().shiftedBy(DURATION / 2 - 0.1)));
237         Assertions.assertEquals(STRING_AFTER, getAdditionalDataAt(ephemeris, state.getDate().shiftedBy(DURATION / 2)));
238         Assertions.assertEquals(STRING_AFTER, getAdditionalDataAt(ephemeris, state.getDate().shiftedBy(DURATION / 2 + 0.1)));
239     }
240 
241     private <T extends CalculusFieldElement<T>> Object getAdditionalDataAt(FieldPropagator<T> propagator, FieldAbsoluteDate<T> date) {
242         return propagator.propagate(date).getAdditionalData(MainStringDataModifier.class.getSimpleName());
243     }
244 
245     private static class MainStringDataModifier<T extends CalculusFieldElement<T>> implements FieldAdditionalDataProvider<String, T> {
246 
247         private String value;
248 
249         @Override
250         public void init(FieldSpacecraftState<T> initialState, FieldAbsoluteDate<T> target) {
251             value = target.getDate().isBefore(initialState.getDate().shiftedBy(DURATION / 2)) ? STRING_BEFORE : STRING_AFTER;
252         }
253 
254         @Override
255         public String getAdditionalData(FieldSpacecraftState<T> state) {
256             return value;
257         }
258 
259         @Override
260         public String getName() {
261             return MainStringDataModifier.class.getSimpleName();
262         }
263     }
264 
265     private static class MainStateModifier<T extends CalculusFieldElement<T>> extends FieldAbstractStateModifier<T> {
266         /** {@inheritDoc} */
267         @Override
268         public FieldSpacecraftState<T> change(final FieldSpacecraftState<T> state) {
269             final Field<T> field = state.getDate().getField();
270             return new FieldSpacecraftState<>(state.getOrbit(),
271                     new FieldAttitude<>(state.getDate(),
272                             state.getFrame(),
273                             new FieldRotation<>(field.getZero(),
274                                     field.getZero(),
275                                     field.getZero(),
276                                     field.getOne(),
277                                     false),
278                             FieldVector3D.getZero(field),
279                             FieldVector3D.getZero(field)),
280                     field.getZero().newInstance(2 * SpacecraftState.DEFAULT_MASS));
281         }
282     }
283 
284     private static class TimeDifferenceProvider<T extends CalculusFieldElement<T>> implements FieldAdditionalDataProvider<T[], T> {
285 
286         private final String   name;
287         private boolean  called;
288         private T        dt;
289         private final Field<T> field;
290 
291         public TimeDifferenceProvider(final String name, final Field<T> field) {
292             this.name   = name;
293             this.called = false;
294             this.dt     = field.getZero();
295             this.field  = field;
296         }
297 
298         @Override
299         public void init(FieldSpacecraftState<T> initialState, FieldAbsoluteDate<T> target) {
300             this.called = true;
301             this.dt     = target.durationFrom(initialState.getDate());
302         }
303 
304         @Override
305         public String getName() {
306             return name;
307         }
308 
309         @Override
310         public T[] getAdditionalData(FieldSpacecraftState<T> state) {
311             final T[] array = MathArrays.buildArray(field, 1);
312             array[0] = dt;
313             return array;
314         }
315 
316         public boolean wasCalled() {
317             return called;
318         }
319 
320     }
321 
322 }