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.frames;
18  
19  import org.hamcrest.MatcherAssert;
20  import org.hipparchus.complex.Complex;
21  import org.hipparchus.complex.ComplexField;
22  import org.hipparchus.geometry.euclidean.threed.Rotation;
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.hipparchus.util.FastMath;
25  import org.hipparchus.util.Precision;
26  import org.junit.jupiter.api.Assertions;
27  import org.junit.jupiter.api.BeforeEach;
28  import org.junit.jupiter.api.Test;
29  import org.orekit.OrekitMatchers;
30  import org.orekit.Utils;
31  import org.orekit.data.DataContext;
32  import org.orekit.time.AbsoluteDate;
33  import org.orekit.time.DateComponents;
34  import org.orekit.time.FieldAbsoluteDate;
35  import org.orekit.time.TimeComponents;
36  import org.orekit.time.TimeScale;
37  import org.orekit.time.TimeScalesFactory;
38  import org.orekit.utils.AngularCoordinates;
39  import org.orekit.utils.IERSConventions;
40  import org.orekit.utils.PVCoordinates;
41  
42  import java.util.ArrayList;
43  import java.util.List;
44  import java.util.concurrent.Callable;
45  import java.util.concurrent.ExecutionException;
46  import java.util.concurrent.Executors;
47  import java.util.concurrent.Future;
48  
49  /**
50   * Unit tests for {@link TIRFProvider}.
51   *
52   * @author Evan Ward
53   */
54  public class TIRFProviderTest {
55  
56      @Test
57      public void testAASReferenceLEO() {
58  
59          // this reference test has been extracted from the following paper:
60          // Implementation Issues Surrounding the New IAU Reference Systems for Astrodynamics
61          // David A. Vallado, John H. Seago, P. Kenneth Seidelmann
62          // http://www.centerforspace.com/downloads/files/pubs/AAS-06-134.pdf
63          // Reference position & velocity from : "Fundamentals of Astrodynamics and Applications", Third edition, David A. Vallado
64          Utils.setLoaders(IERSConventions.IERS_2010,
65                           Utils.buildEOPList(IERSConventions.IERS_2010, ITRFVersion.ITRF_2008, new double[][] {
66                               { 53098, -0.4399619, 0.0015563, -0.140682, 0.333309, Double.NaN, Double.NaN, -0.000199, -0.000252 },
67                               { 53099, -0.4399619, 0.0015563, -0.140682, 0.333309, Double.NaN, Double.NaN, -0.000199, -0.000252 },
68                               { 53100, -0.4399619, 0.0015563, -0.140682, 0.333309, Double.NaN, Double.NaN, -0.000199, -0.000252 },
69                               { 53101, -0.4399619, 0.0015563, -0.140682, 0.333309, Double.NaN, Double.NaN, -0.000199, -0.000252 },
70                               { 53102, -0.4399619, 0.0015563, -0.140682, 0.333309, Double.NaN, Double.NaN, -0.000199, -0.000252 },
71                               { 53103, -0.4399619, 0.0015563, -0.140682, 0.333309, Double.NaN, Double.NaN, -0.000199, -0.000252 },
72                               { 53104, -0.4399619, 0.0015563, -0.140682, 0.333309, Double.NaN, Double.NaN, -0.000199, -0.000252 },
73                               { 53105, -0.4399619, 0.0015563, -0.140682, 0.333309, Double.NaN, Double.NaN, -0.000199, -0.000252 }
74                           }));
75          AbsoluteDate t0 = new AbsoluteDate(new DateComponents(2004, 4, 6),
76                                             new TimeComponents(7, 51, 28.386009),
77                                             TimeScalesFactory.getUTC());
78  
79          // Positions LEO
80          Frame itrfA = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
81          PVCoordinates pvITRF =
82              new PVCoordinates(new Vector3D(-1033479.3830, 7901295.2754, 6380356.5958),
83                                new Vector3D(-3225.636520, -2872.451450, 5531.924446));
84  
85          // Reference coordinates
86          Frame tirf = FramesFactory.getTIRF(IERSConventions.IERS_2010);
87          PVCoordinates pvTIRF =
88              new PVCoordinates(new Vector3D(-1033475.0312, 7901305.5856, 6380344.5328),
89                                new Vector3D(-3225.632747, -2872.442511, 5531.931288));
90          checkPV(pvTIRF,
91                  itrfA.getTransformTo(tirf, t0).transformPVCoordinates(pvITRF),
92                  6.379e-5, 3.78e-7);
93          checkP(pvTIRF.getPosition(),
94                  itrfA.getStaticTransformTo(tirf, t0).transformPosition(pvITRF.getPosition()),
95                  6.379e-5);
96  
97          Frame cirf = FramesFactory.getCIRF(IERSConventions.IERS_2010, true);
98          PVCoordinates pvCIRF =
99              new PVCoordinates(new Vector3D(5100018.4047, 6122786.3648, 6380344.5328),
100                               new Vector3D(-4745.380330, 790.341453, 5531.931288));
101         checkPV(pvCIRF,
102                 tirf.getTransformTo(cirf, t0).transformPVCoordinates(pvTIRF),
103                 8.59e-3, 4.65e-6);
104         checkP(pvCIRF.getPosition(),
105                 tirf.getStaticTransformTo(cirf, t0).transformPosition(pvTIRF.getPosition()),
106                 8.59e-3);
107 
108     }
109 
110     @Test
111     public void testAASReferenceGEO() {
112 
113         // this reference test has been extracted from the following paper:
114         // Implementation Issues Surrounding the New IAU Reference Systems for Astrodynamics
115         // David A. Vallado, John H. Seago, P. Kenneth Seidelmann
116         // http://www.centerforspace.com/downloads/files/pubs/AAS-06-134.pdf
117         Utils.setLoaders(IERSConventions.IERS_2010,
118                          Utils.buildEOPList(IERSConventions.IERS_2010, ITRFVersion.ITRF_2008, new double[][] {
119                              { 53153, -0.4709050,  0.0000000, -0.083853,  0.467217, Double.NaN, Double.NaN, -0.000199, -0.000252 },
120                              { 53154, -0.4709050,  0.0000000, -0.083853,  0.467217, Double.NaN, Double.NaN, -0.000199, -0.000252 },
121                              { 53155, -0.4709050,  0.0000000, -0.083853,  0.467217, Double.NaN, Double.NaN, -0.000199, -0.000252 },
122                              { 53156, -0.4709050,  0.0000000, -0.083853,  0.467217, Double.NaN, Double.NaN, -0.000199, -0.000252 },
123                              { 53157, -0.4709050,  0.0000000, -0.083853,  0.467217, Double.NaN, Double.NaN, -0.000199, -0.000252 },
124                              { 53158, -0.4709050,  0.0000000, -0.083853,  0.467217, Double.NaN, Double.NaN, -0.000199, -0.000252 },
125                              { 53159, -0.4709050,  0.0000000, -0.083853,  0.467217, Double.NaN, Double.NaN, -0.000199, -0.000252 },
126                              { 53160, -0.4709050,  0.0000000, -0.083853,  0.467217, Double.NaN, Double.NaN, -0.000199, -0.000252 }
127                          }));
128 
129         AbsoluteDate t0 = new AbsoluteDate(new DateComponents(2004, 6, 1),
130                                            TimeComponents.H00,
131                                            TimeScalesFactory.getUTC());
132 
133         //  Positions GEO
134         Frame itrfA = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
135         PVCoordinates pvITRF =
136             new PVCoordinates(new Vector3D(24796919.2915, -34115870.9234, 10226.0621),
137                               new Vector3D(-0.979178, -1.476538, -0.928776));
138 
139         Frame tirf = FramesFactory.getTIRF(IERSConventions.IERS_2010);
140         PVCoordinates pvTIRF =
141             new PVCoordinates(new Vector3D(24796919.2953, -34115870.9004, 10293.2583),
142                               new Vector3D(-0.979178, -1.476540, -0.928772));
143         checkPV(pvTIRF,
144                 itrfA.getTransformTo(tirf, t0).transformPVCoordinates(pvITRF),
145                 5.697e-5, 4.69e-7);
146         checkP(pvTIRF.getPosition(),
147                 itrfA.getStaticTransformTo(tirf, t0).transformPosition(pvITRF.getPosition()),
148                 5.697e-5);
149 
150         Frame cirf = FramesFactory.getCIRF(IERSConventions.IERS_2010, true);
151         PVCoordinates pvCIRF =
152             new PVCoordinates(new Vector3D(-40588158.1236, -11462167.0709, 10293.2583),
153                               new Vector3D(834.787843, -2958.305669, -0.928772));
154         checkPV(pvCIRF,
155                 tirf.getTransformTo(cirf, t0).transformPVCoordinates(pvTIRF),
156                 0.0505, 3.60e-6);
157         checkP(pvCIRF.getPosition(),
158                 tirf.getStaticTransformTo(cirf, t0).transformPosition(pvTIRF.getPosition()),
159                 0.0505);
160 
161     }
162 
163     /** Issue #636 */
164     @Test
165     public void testContinuousDuringLeap() {
166         // setup
167         Utils.setDataRoot("regular-data");
168         Frame tirf = FramesFactory.getTIRF(IERSConventions.IERS_2010, true);
169         Frame cirf = FramesFactory.getCIRF(IERSConventions.IERS_2010, true);
170         TimeScale utc = TimeScalesFactory.getUTC();
171         AbsoluteDate dateA = new AbsoluteDate(2005, 12, 31, 23, 59, 0.0, utc);
172         AbsoluteDate dateB = new AbsoluteDate(2006, 1, 1, 0, 1, 0.0, utc);
173         double dt = 0.5;
174         double expected = dt * 2 * FastMath.PI / (23 * 3600 + 56 * 60 + 4.1);
175         double tol = expected * 1e-6;
176         Rotation previous = cirf.getTransformTo(tirf, dateA.shiftedBy(-dt)).getRotation();
177 
178         // action + verify
179         for (AbsoluteDate d = dateA; d.compareTo(dateB) < 0; d = d.shiftedBy(dt)) {
180             Rotation actual = cirf.getTransformTo(tirf, d).getRotation();
181             Assertions.assertEquals(expected,
182                     Rotation.distance(previous, actual),
183                     tol, "At " + d.toString(utc));
184             Assertions.assertEquals(expected,
185                     Rotation.distance(
186                             previous,
187                             cirf.getStaticTransformTo(tirf, d).getRotation()),
188                     tol, "At " + d.toString(utc));
189             previous = actual;
190         }
191     }
192 
193     /**
194      * Checks that {@link TIRFProvider#getTransform(AbsoluteDate)} is thread safe.
195      */
196     @Test
197     public void testConcurrentGetTransform()
198         throws InterruptedException, ExecutionException {
199 
200         // subject under test
201         final EOPHistory eopHistory = FramesFactory.getEOPHistory(IERSConventions.IERS_2010, false);
202         final TimeScale ut1 = DataContext.getDefault().getTimeScales().getUT1(eopHistory);
203         final TIRFProvider tirf = new TIRFProvider(eopHistory, ut1);
204         // arbitrary date
205         final AbsoluteDate start = new AbsoluteDate("2009-09-19T23:59:45.000", TimeScalesFactory.getUTC());
206         // in seconds = 15min
207         final double timeStep = 300;
208         // the number of possible concurrent threads
209         final int nJobs = 24;
210         // the number of calculations per thread
211         final int nPerJob = 1000;
212         // tolerance of comparisons
213         final double absTol = Precision.EPSILON;
214         // the expected result
215         final List<Transform> expecteds = new ArrayList<>();
216         for (int j = 0; j < nPerJob; j++) {
217             final AbsoluteDate date = start.shiftedBy(timeStep * j);
218             // action
219             final Transform expected = tirf.getTransform(date);
220             // verify
221             expecteds.add(expected);
222         }
223 
224         // build jobs for concurrent execution
225         final List<Callable<Boolean>> jobs = new ArrayList<>();
226         for (int i = 0; i < nJobs; i++) {
227             jobs.add(() -> {
228                 for (int j = 0; j < nPerJob; j++) {
229                     final AbsoluteDate date = start.shiftedBy(timeStep * j);
230                     // action
231                     final Transform actual = tirf.getTransform(date);
232                     // verify
233                     assertTransformEquals(expecteds.get(j), actual, absTol);
234                 }
235                 return true;
236             });
237         }
238 
239         // run the jobs
240         runConcurrentlyAndCheck(jobs, nJobs);
241     }
242 
243     /**
244      * Runs several jobs at concurrently in the given number of threads.
245      *
246      * @param jobs
247      *            the jobs to execute
248      * @param threads
249      *            the size of the thread pool
250      */
251     private static void runConcurrentlyAndCheck(List<Callable<Boolean>> jobs, int threads)
252         throws InterruptedException, ExecutionException {
253 
254         // action
255         final List<Future<Boolean>> futures = Executors.newFixedThreadPool(threads).invokeAll(jobs);
256 
257         // verify - necessary to throw AssertionErrors from the Callable
258         for (Future<Boolean> future : futures) {
259             Assertions.assertEquals(true, future.get());
260         }
261 
262     }
263 
264     /**
265      * Check the two {@link Transform}s are the same to within an absolute tolerance.
266      */
267     private static void assertTransformEquals(Transform expected, Transform actual, double absTol) {
268         final AngularCoordinates expectedAngular = expected.getAngular();
269         final AngularCoordinates actualAngular = actual.getAngular();
270         final Rotation expectedRotation = expectedAngular.getRotation();
271         final Rotation actualRotation = actualAngular.getRotation();
272         final PVCoordinates expectedPV = expected.getCartesian();
273         final PVCoordinates actualPV = actual.getCartesian();
274 
275         // transform
276         Assertions.assertEquals(Rotation.distance(actualRotation, expectedRotation), 0, absTol);
277         Assertions.assertEquals(expectedAngular.getRotationRate(), actualAngular.getRotationRate());
278         Assertions.assertEquals(expectedPV.getPosition(), actualPV.getPosition());
279         Assertions.assertEquals(expectedPV.getVelocity(), actualPV.getVelocity());
280     }
281 
282     @Test
283     void testGetKinematicTransform() {
284         // GIVEN
285         final EOPHistory eopHistory = FramesFactory.getEOPHistory(IERSConventions.IERS_2010, false);
286         final TimeScale ut1 = DataContext.getDefault().getTimeScales().getUT1(eopHistory);
287         final TIRFProvider provider = new TIRFProvider(eopHistory, ut1);
288         final AbsoluteDate date = AbsoluteDate.ARBITRARY_EPOCH;
289         // WHEN
290         final KinematicTransform kinematicTransform = provider.getKinematicTransform(date);
291         // THEN
292         final Transform transform = provider.getTransform(date);
293         Assertions.assertEquals(date, kinematicTransform.getDate());
294         Assertions.assertEquals(transform.getCartesian().getPosition(), kinematicTransform.getTranslation());
295         Assertions.assertEquals(transform.getCartesian().getVelocity(), kinematicTransform.getVelocity());
296         Assertions.assertEquals(0., Rotation.distance(transform.getRotation(), kinematicTransform.getRotation()));
297         Assertions.assertEquals(transform.getRotationRate(), kinematicTransform.getRotationRate());
298     }
299 
300     @Test
301     void testFieldGetKinematicTransform() {
302         // GIVEN
303         final EOPHistory eopHistory = FramesFactory.getEOPHistory(IERSConventions.IERS_2010, false);
304         final TimeScale ut1 = DataContext.getDefault().getTimeScales().getUT1(eopHistory);
305         final TIRFProvider provider = new TIRFProvider(eopHistory, ut1);
306         final ComplexField field = ComplexField.getInstance();
307         final FieldAbsoluteDate<Complex> date = FieldAbsoluteDate.getArbitraryEpoch(field);
308         // WHEN
309         final FieldKinematicTransform<Complex> fieldKinematicTransform = provider.getKinematicTransform(date);
310         // THEN
311         final KinematicTransform kinematicTransform = provider.getKinematicTransform(date.toAbsoluteDate());
312         Assertions.assertEquals(kinematicTransform.getDate(), fieldKinematicTransform.getDate());
313         Assertions.assertEquals(kinematicTransform.getTranslation(), fieldKinematicTransform.getTranslation().toVector3D());
314         Assertions.assertEquals(kinematicTransform.getVelocity(), fieldKinematicTransform.getVelocity().toVector3D());
315         Assertions.assertEquals(0., Rotation.distance(kinematicTransform.getRotation(),
316                 fieldKinematicTransform.getRotation().toRotation()), 1e-15);
317         Assertions.assertEquals(kinematicTransform.getRotationRate(), fieldKinematicTransform.getRotationRate().toVector3D());
318     }
319 
320     @Test
321     void testFieldGetStaticTransform() {
322         // GIVEN
323         final EOPHistory eopHistory = FramesFactory.getEOPHistory(IERSConventions.IERS_2010, false);
324         final TimeScale ut1 = DataContext.getDefault().getTimeScales().getUT1(eopHistory);
325         final TIRFProvider provider = new TIRFProvider(eopHistory, ut1);
326         final ComplexField field = ComplexField.getInstance();
327         final FieldAbsoluteDate<Complex> date = FieldAbsoluteDate.getArbitraryEpoch(field);
328         // WHEN
329         final FieldStaticTransform<Complex> fieldStaticTransform = provider.getStaticTransform(date);
330         // THEN
331         final StaticTransform staticTransform = provider.getStaticTransform(date.toAbsoluteDate());
332         Assertions.assertEquals(staticTransform.getDate(), fieldStaticTransform.getDate());
333         Assertions.assertEquals(staticTransform.getTranslation(), fieldStaticTransform.getTranslation().toVector3D());
334         Assertions.assertEquals(0., Rotation.distance(staticTransform.getRotation(),
335                 fieldStaticTransform.getRotation().toRotation()), 1e-15);
336 
337     }
338 
339     @BeforeEach
340     public void setUp() {
341         Utils.setDataRoot("compressed-data");
342     }
343 
344     private void checkPV(PVCoordinates reference, PVCoordinates result,
345                          double expectedPositionError, double expectedVelocityError) {
346 
347         Vector3D dP = result.getPosition().subtract(reference.getPosition());
348         Vector3D dV = result.getVelocity().subtract(reference.getVelocity());
349         Assertions.assertEquals(expectedPositionError, dP.getNorm(), 0.01 * expectedPositionError);
350         Assertions.assertEquals(expectedVelocityError, dV.getNorm(), 0.01 * expectedVelocityError);
351     }
352 
353     private void checkP(Vector3D position,
354                         Vector3D transformPosition,
355                         double tol) {
356         MatcherAssert.assertThat(
357                 transformPosition,
358                 OrekitMatchers.vectorCloseTo(position, tol));
359     }
360 
361 }