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.orbits;
18  
19  import java.util.function.Function;
20  
21  import org.hipparchus.CalculusFieldElement;
22  import org.hipparchus.Field;
23  import org.hipparchus.analysis.differentiation.Gradient;
24  import org.hipparchus.analysis.differentiation.GradientField;
25  import org.hipparchus.complex.Complex;
26  import org.hipparchus.complex.ComplexField;
27  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
28  import org.hipparchus.geometry.euclidean.threed.Vector3D;
29  import org.hipparchus.util.Binary64;
30  import org.hipparchus.util.Binary64Field;
31  import org.hipparchus.util.MathUtils;
32  import org.junit.jupiter.api.Assertions;
33  import org.junit.jupiter.api.BeforeAll;
34  import org.junit.jupiter.api.Test;
35  import org.junit.jupiter.params.ParameterizedTest;
36  import org.junit.jupiter.params.provider.EnumSource;
37  import org.orekit.TestUtils;
38  import org.orekit.Utils;
39  import org.orekit.frames.Frame;
40  import org.orekit.frames.FramesFactory;
41  import org.orekit.frames.Predefined;
42  import org.orekit.time.AbsoluteDate;
43  import org.orekit.time.FieldAbsoluteDate;
44  import org.orekit.time.TimeScale;
45  import org.orekit.time.TimeScalesFactory;
46  import org.orekit.utils.Constants;
47  import org.orekit.utils.FieldPVCoordinates;
48  import org.orekit.utils.PVCoordinates;
49  import org.orekit.utils.TimeStampedFieldPVCoordinates;
50  
51  class FieldOrbitTest {
52  
53      @BeforeAll
54      static void setUp() {
55          // Load orekit data
56          Utils.setDataRoot("regular-data");
57      }
58  
59      @Test
60      void testGetPosition() {
61          // GIVEN
62          final TestFieldOrbit testFieldOrbit = new TestFieldOrbit(1.);
63          final FieldAbsoluteDate<Complex> date = testFieldOrbit.getDate().shiftedBy(0.);
64          final Frame frame = testFieldOrbit.getFrame();
65          // WHEN
66          final FieldVector3D<Complex> actualPosition = testFieldOrbit.getPosition(date, frame);
67          // THEN
68          final FieldVector3D<Complex> expectedPosition = testFieldOrbit.getPVCoordinates(date, frame).getPosition();
69          Assertions.assertEquals(expectedPosition, actualPosition);
70      }
71  
72      @ParameterizedTest
73      @EnumSource(value = Predefined.class, names = {"EME2000", "GCRF"})
74      void testGetVelocity(final Predefined predefined) {
75          // GIVEN
76          final Orbit orbit = TestUtils.getDefaultOrbit(AbsoluteDate.ARBITRARY_EPOCH);
77          final FieldOrbit<Complex> testFieldOrbit = new FieldCartesianOrbit<>(ComplexField.getInstance(), orbit)
78                  .inFrame(FramesFactory.getFrame(predefined));
79          final FieldAbsoluteDate<Complex> date = testFieldOrbit.getDate().shiftedBy(0.);
80          final Frame outputFrame = FramesFactory.getEME2000();
81          // WHEN
82          final FieldVector3D<Complex> actualVelocity = testFieldOrbit.getVelocity(date, outputFrame);
83          // THEN
84          final FieldVector3D<Complex> expectedVelocity = testFieldOrbit.getPVCoordinates(date, outputFrame).getVelocity();
85          Assertions.assertEquals(expectedVelocity, actualVelocity);
86      }
87  
88      @Test
89      void testHasNonKeplerianAccelerationDerivative() {
90          // GIVEN
91          final GradientField field = GradientField.getField(0);
92          final Gradient                     mu                 = field.getZero().newInstance(Constants.EGM96_EARTH_MU);
93          final FieldPVCoordinates<Gradient> fieldPVCoordinates = createFieldTPVWithKeplerianAcceleration(mu);
94          // WHEN
95          final boolean actualResult = FieldOrbit.hasNonKeplerianAcceleration(fieldPVCoordinates, mu);
96          // THEN
97          Assertions.assertFalse(actualResult);
98      }
99  
100     @Test
101     void testIssue1344() {
102         // GIVEN
103         final Binary64 mu = Binary64.ZERO.newInstance(Constants.EGM96_EARTH_MU);
104         final FieldPVCoordinates<Binary64> fieldPVCoordinates = createFieldTPVWithKeplerianAcceleration(mu);
105         // WHEN
106         final boolean actualResult = FieldOrbit.hasNonKeplerianAcceleration(fieldPVCoordinates, mu);
107         // THEN
108         Assertions.assertFalse(actualResult);
109     }
110 
111     private static <T extends CalculusFieldElement<T>> FieldPVCoordinates<T> createFieldTPVWithKeplerianAcceleration(final T mu) {
112         final Vector3D position = new Vector3D(1e6, 0, 0);
113         final Vector3D keplerianAcceleration = new Vector3D(-mu.getReal() / position.getNormSq() / position.getNorm(),
114                 position);
115         return new FieldPVCoordinates<>(mu.getField(), new PVCoordinates(position, Vector3D.ZERO, keplerianAcceleration));
116     }
117 
118     @Test
119     void testKeplerianMeanMotionAndPeriod() {
120         // GIVEN
121         final double aIn = 1.;
122         final TestFieldOrbit testOrbit = new TestFieldOrbit(aIn);
123         // WHEN
124         final Complex meanMotion = testOrbit.getKeplerianMeanMotion();
125         final Complex period = testOrbit.getKeplerianPeriod();
126         final double actualValue = period.multiply(meanMotion).getReal();
127         // THEN
128         final double expectedValue = MathUtils.TWO_PI;
129         Assertions.assertEquals(expectedValue, actualValue, 1e-10);
130     }
131 
132     @Test
133     void testIsElliptical() {
134         templateTestIsElliptical(1.);
135     }
136 
137     @Test
138     void testIsNotElliptical() {
139         templateTestIsElliptical(-1.);
140     }
141 
142     private void templateTestIsElliptical(final double aIn) {
143         // GIVEN
144         final TestFieldOrbit testOrbit = new TestFieldOrbit(aIn);
145         // WHEN
146         final boolean actualValue = testOrbit.isElliptical();
147         // THEN
148         final boolean expectedValue = aIn > 0.;
149         Assertions.assertEquals(expectedValue, actualValue);
150     }
151 
152     @Test
153     void testIssue1557() {
154         // GIVEN
155         final FieldOrbit<Binary64> fakeOrbit = TestUtils.getFakeFieldOrbit();
156 
157         // WHEN
158         final FieldVector3D<Binary64> velocity = fakeOrbit.getVelocity();
159 
160         // THEN
161         Assertions.assertEquals(fakeOrbit.getVelocity(), velocity);
162     }
163 
164     @Test
165     void testTimeShiftIsApplied() {
166         // GIVEN
167         final GradientField               field       = GradientField.getField(1);
168         final Gradient                    shift       = Gradient.variable(1, 0, 10.);
169         final FieldAbsoluteDate<Gradient> date        = new FieldAbsoluteDate<>(field, new AbsoluteDate());
170         final FieldAbsoluteDate<Gradient> shiftedDate = date.shiftedBy(shift);
171 
172         final FieldOrbit<Gradient> orbit = TestUtils.getDefaultFieldOrbit(date);
173 
174         // WHEN
175         final TimeStampedFieldPVCoordinates<Gradient>
176                         pv =
177                         orbit.getPVCoordinates(shiftedDate, FramesFactory.getEME2000());
178 
179         // THEN
180         Assertions.assertNotEquals(0, pv.getPosition().getNorm1().getGradient()[0]);
181     }
182 
183     @Test
184     void testCorrectShiftedDateWithCartesianOrbit() {
185         // GIVEN
186         final Field<Binary64> field = Binary64Field.getInstance();
187 
188         // WHEN & THEN
189         doTestCorrectShiftedDate(TestUtils::getDefaultFieldOrbit, field);
190     }
191 
192     @Test
193     void testCorrectShiftedDateWithKeplerianOrbit() {
194         // GIVEN
195         final Field<Binary64> field = Binary64Field.getInstance();
196 
197         // WHEN & THEN
198         doTestCorrectShiftedDate((date) -> new FieldKeplerianOrbit<>(TestUtils.getDefaultFieldOrbit(date)), field);
199         doTestCorrectShiftedDate((date) -> new FieldKeplerianOrbit<>(TestUtils.getDefaultFieldOrbitWithDerivatives(date)), field);
200     }
201 
202     @Test
203     void testCorrectShiftedDateWithCircularOrbit() {
204         // GIVEN
205         final Field<Binary64> field = Binary64Field.getInstance();
206 
207         // WHEN & THEN
208         doTestCorrectShiftedDate((date) -> new FieldCircularOrbit<>(TestUtils.getDefaultFieldOrbit(date)), field);
209         doTestCorrectShiftedDate((date) -> new FieldCircularOrbit<>(TestUtils.getDefaultFieldOrbitWithDerivatives(date)), field);
210     }
211 
212     @Test
213     void testCorrectShiftedDateWithEquinoctialOrbit() {
214         // GIVEN
215         final Field<Binary64> field = Binary64Field.getInstance();
216 
217         // WHEN & THEN
218         doTestCorrectShiftedDate((date) -> new FieldEquinoctialOrbit<>(TestUtils.getDefaultFieldOrbit(date)), field);
219         doTestCorrectShiftedDate((date) -> new FieldEquinoctialOrbit<>(TestUtils.getDefaultFieldOrbitWithDerivatives(date)), field);
220     }
221 
222     /**
223      * Test related to issue 1883.
224      *
225      * @see <a href="https://gitlab.orekit.org/orekit/orekit/-/issues/1883">Issue 1883</a>
226      */
227     public <T extends CalculusFieldElement<T>> void doTestCorrectShiftedDate(final Function<FieldAbsoluteDate<T>, FieldOrbit<T>> dateToOrbit,
228                                                                              final Field<T> field) {
229         // GIVEN
230         // Define dates
231         final TimeScale utc   = TimeScalesFactory.getUTC();
232         final Frame     gcrf  = FramesFactory.getGCRF();
233 
234 
235         AbsoluteDate date1 = new AbsoluteDate("2025-12-15T11:11:00.000000000000000000Z", utc);
236         AbsoluteDate date2 = new AbsoluteDate("2025-12-15T14:56:00.000000000000000000Z", utc);
237 
238         FieldAbsoluteDate<T> fieldDate1        = new FieldAbsoluteDate<>(field, date1);
239         FieldAbsoluteDate<T> fieldDate2        = new FieldAbsoluteDate<>(field, date2);
240         FieldAbsoluteDate<T> fieldDate2Shifted = fieldDate2.shiftedBy(0.123456789);
241 
242         // Define orbit
243         final FieldOrbit<T> orbitAtShiftedDate = dateToOrbit.apply(fieldDate2Shifted);
244 
245         // WHEN
246         final TimeStampedFieldPVCoordinates<T> pv =
247                 orbitAtShiftedDate.getPVCoordinates(fieldDate1, gcrf);
248         final FieldAbsoluteDate<T> actualDate = pv.getDate();
249 
250         // THEN
251         Assertions.assertEquals(0, actualDate.durationFrom(date1).getReal());
252     }
253 
254     private static class TestFieldOrbit extends FieldOrbit<Complex> {
255 
256         final Complex a;
257 
258         protected TestFieldOrbit(final double aIn)
259                 throws IllegalArgumentException {
260             super(FramesFactory.getGCRF(), FieldAbsoluteDate.getArbitraryEpoch(ComplexField.getInstance()), Complex.ONE);
261             a = new Complex(aIn, 0.);
262         }
263 
264         @Override
265         public OrbitType getType() {
266             return null;
267         }
268 
269         @Override
270         public Orbit toOrbit() {
271             return null;
272         }
273 
274         @Override
275         public Complex getA() {
276             return this.a;
277         }
278 
279         @Override
280         public Complex getADot() {
281             return null;
282         }
283 
284         @Override
285         public Complex getEquinoctialEx() {
286             return null;
287         }
288 
289         @Override
290         public Complex getEquinoctialExDot() {
291             return null;
292         }
293 
294         @Override
295         public Complex getEquinoctialEy() {
296             return null;
297         }
298 
299         @Override
300         public Complex getEquinoctialEyDot() {
301             return null;
302         }
303 
304         @Override
305         public Complex getHx() {
306             return null;
307         }
308 
309         @Override
310         public Complex getHxDot() {
311             return null;
312         }
313 
314         @Override
315         public Complex getHy() {
316             return null;
317         }
318 
319         @Override
320         public Complex getHyDot() {
321             return null;
322         }
323 
324         @Override
325         public Complex getLE() {
326             return null;
327         }
328 
329         @Override
330         public Complex getLEDot() {
331             return null;
332         }
333 
334         @Override
335         public Complex getLv() {
336             return null;
337         }
338 
339         @Override
340         public Complex getLvDot() {
341             return null;
342         }
343 
344         @Override
345         public Complex getLM() {
346             return null;
347         }
348 
349         @Override
350         public Complex getLMDot() {
351             return null;
352         }
353 
354         @Override
355         public Complex getE() {
356             return null;
357         }
358 
359         @Override
360         public Complex getEDot() {
361             return null;
362         }
363 
364         @Override
365         public Complex getI() {
366             return null;
367         }
368 
369         @Override
370         public Complex getIDot() {
371             return null;
372         }
373 
374         @Override
375         public boolean hasNonKeplerianAcceleration() {
376             return false;
377         }
378 
379         @Override
380         protected FieldVector3D<Complex> initPosition() {
381             return new FieldVector3D<>(getField(), new Vector3D(a.getReal(), 0., 0.));
382         }
383 
384         @Override
385         protected TimeStampedFieldPVCoordinates<Complex> initPVCoordinates() {
386             final FieldPVCoordinates<Complex> fieldPVCoordinates = new FieldPVCoordinates<>(initPosition(),
387                     FieldVector3D.getZero(getField()));
388             return new TimeStampedFieldPVCoordinates<>(getDate(), fieldPVCoordinates);
389         }
390 
391         @Override
392         public FieldOrbit<Complex> inFrame(Frame inertialFrame) {
393             return null;
394         }
395 
396         @Override
397         public FieldOrbit<Complex> shiftedBy(Complex dt) {
398             return shiftedBy(dt.getReal());
399         }
400 
401         @Override
402         public FieldOrbit<Complex> shiftedBy(double dt) {
403             return new TestFieldOrbit(a.getReal());
404         }
405 
406         @Override
407         protected Complex[][] computeJacobianMeanWrtCartesian() {
408             return null;
409         }
410 
411         @Override
412         protected Complex[][] computeJacobianEccentricWrtCartesian() {
413             return null;
414         }
415 
416         @Override
417         protected Complex[][] computeJacobianTrueWrtCartesian() {
418             return null;
419         }
420 
421         @Override
422         public void addKeplerContribution(PositionAngleType type, Complex gm, Complex[] pDot) {
423 
424         }
425 
426     }
427     
428 }