1   /* Copyright 2002-2024 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.models.earth.atmosphere;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
22  import org.hipparchus.geometry.euclidean.threed.Rotation;
23  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
24  import org.hipparchus.geometry.euclidean.threed.Vector3D;
25  import org.hipparchus.util.Binary64;
26  import org.hipparchus.util.Binary64Field;
27  import org.hipparchus.util.FastMath;
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.SolarInputs97to05;
32  import org.orekit.Utils;
33  import org.orekit.bodies.CelestialBody;
34  import org.orekit.bodies.CelestialBodyFactory;
35  import org.orekit.bodies.OneAxisEllipsoid;
36  import org.orekit.frames.Frame;
37  import org.orekit.frames.FramesFactory;
38  import org.orekit.frames.Transform;
39  import org.orekit.models.earth.ReferenceEllipsoid;
40  import org.orekit.time.AbsoluteDate;
41  import org.orekit.time.FieldAbsoluteDate;
42  import org.orekit.time.TimeScale;
43  import org.orekit.time.TimeScalesFactory;
44  import org.orekit.utils.Constants;
45  import org.orekit.utils.IERSConventions;
46  import org.orekit.utils.PVCoordinatesProvider;
47  import org.orekit.utils.TimeStampedFieldPVCoordinates;
48  import org.orekit.utils.TimeStampedPVCoordinates;
49  
50  import java.util.TimeZone;
51  
52  public class DTM2000Test {
53  
54      @Test
55      public void testWithOriginalTestsCases() {
56  
57          Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
58          PVCoordinatesProvider sun = CelestialBodyFactory.getSun();
59          OneAxisEllipsoid earth = new OneAxisEllipsoid(6378136.460, 1.0 / 298.257222101, itrf);
60          SolarInputs97to05 in = SolarInputs97to05.getInstance();
61          earth.setAngularThreshold(1e-10);
62          DTM2000 atm = new DTM2000(in, sun, earth);
63          double roTestCase;
64          double myRo;
65  
66          // Inputs :
67  //      alt=800.
68  //      lat=40.
69  //      day=185.
70  //      hl=16.
71  //      xlon=0.
72  //      fm(1)=150.
73  //      f(1) =fm(1)
74  //      fm(2)=0.
75  //      f(2)=0.
76  //      akp(1)=0.
77  //      akp(2)=0.
78  //      akp(3)=0.
79  //      akp(4)=0.
80  
81          // Outputs :
82          roTestCase = 1.8710001353820e-17 * 1000;
83  
84          // Computation and results
85          myRo = atm.getDensity(185, 800*1000, 0, FastMath.toRadians(40), 16*FastMath.PI/12, 150, 150, 0, 0);
86          Assertions.assertEquals(roTestCase, myRo , roTestCase * 1e-14);
87  
88  //      IDEM., day=275
89  
90          roTestCase=    2.8524195214905e-17* 1000;
91  
92          myRo = atm.getDensity(275, 800*1000, 0, FastMath.toRadians(40), 16*FastMath.PI/12, 150, 150, 0, 0);
93          Assertions.assertEquals(roTestCase, myRo , roTestCase * 1e-14);
94  
95  //      IDEM., day=355
96  
97          roTestCase=    1.7343324462212e-17* 1000;
98  
99          myRo = atm.getDensity(355, 800*1000, 0, FastMath.toRadians(40), 16*FastMath.PI/12, 150, 150, 0, 0);
100         Assertions.assertEquals(roTestCase, myRo , roTestCase * 2e-14);
101 //      IDEM., day=85
102 
103         roTestCase=    2.9983740796297e-17* 1000;
104 
105         myRo = atm.getDensity(85, 800*1000, 0, FastMath.toRadians(40), 16*FastMath.PI/12, 150, 150, 0, 0);
106         Assertions.assertEquals(roTestCase, myRo , roTestCase * 1e-14);
107 
108 
109 //      alt=500.
110 //      lat=-70.      NB: the subroutine requires latitude in rad
111 //      day=15.
112 //      hl=16.        NB: the subroutine requires local time in rad (0hr=0 rad)
113 //      xlon=0.
114 //      fm(1)=70.
115 //      f(1) =fm(1)
116 //      fm(2)=0.
117 //      f(2)=0.
118 //      akp(1)=0.
119 //      akp(2)=0.
120 //      akp(3)=0.
121 //      akp(4)=0.
122 //      ro=    1.3150282384722D-16
123 //      tz=    793.65487014559
124 //      tinf=    793.65549802348
125         // note that the values above are the ones present in the original fortran source comments
126         // however, running this original source (converted to double precision) does
127         // not yield the results in the comments, but instead gives the following results
128         // as all the other tests cases do behave correctly, we assume the comments are wrong
129         // and prefer to ensure we get the same result as the original CODE
130         // as we don't have access to any other tests cases, we can't really decide if this is
131         // the best approach. Indeed, we are able to get the same results as original fortran
132         roTestCase =    1.5699108952425600E-016* 1000;
133 
134         myRo = atm.getDensity(15, 500*1000, 0, FastMath.toRadians(-70), 16*FastMath.PI/12, 70, 70, 0, 0);
135         Assertions.assertEquals(roTestCase, myRo , roTestCase * 1e-14);
136 
137 //      IDEM., alt=800.
138 //      ro=    1.9556768571305D-18
139 //      tz=    793.65549797919
140 //      tinf=    793.65549802348
141         // note that the values above are the ones present in the original fortran source comments
142         // however, running this original source (converted to double precision) does
143         // not yield the results in the comments, but instead gives the following results
144         // as all the other tests cases do behave correctly, we assume the comments are wrong
145         // and prefer to ensure we get the same result as the original CODE
146         // as we don't have access to any other tests cases, we can't really decide if this is
147         // the best approach. Indeed, we are able to get the same results as original fortran
148         roTestCase =    2.4123751406975562E-018* 1000;
149         myRo = atm.getDensity(15, 800*1000, 0, FastMath.toRadians(-70), 16*FastMath.PI/12, 70, 70, 0, 0);
150         Assertions.assertEquals(roTestCase, myRo , roTestCase * 1e-14);
151 
152     }
153 
154     @Test
155     public void testNonEarthRotationAxisAlignedFrame() {
156         //setup
157         AbsoluteDate date = AbsoluteDate.J2000_EPOCH;
158         Frame ecef = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
159         Rotation rotation = new Rotation(Vector3D.PLUS_I, FastMath.PI / 2, RotationConvention.VECTOR_OPERATOR);
160         Frame frame = new Frame(ecef, new Transform(date, rotation), "other");
161         Vector3D pEcef = new Vector3D(6378137 + 300e3, 0, 0);
162         Vector3D pFrame = ecef.getStaticTransformTo(frame, date)
163                 .transformPosition(pEcef);
164         PVCoordinatesProvider sun = CelestialBodyFactory.getSun();
165         OneAxisEllipsoid earth = new OneAxisEllipsoid(
166                 6378136.460, 1.0 / 298.257222101, ecef);
167         SolarInputs97to05 in = SolarInputs97to05.getInstance();
168         earth.setAngularThreshold(1e-10);
169         DTM2000 atm = new DTM2000(in, sun, earth);
170 
171         //action
172         final double actual = atm.getDensity(date, pFrame, frame);
173 
174         //verify
175         Assertions.assertEquals(atm.getDensity(date, pEcef, ecef), actual, 0.0);
176     }
177 
178     @Test
179     public void testField() {
180         Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
181         PVCoordinatesProvider sun = CelestialBodyFactory.getSun();
182         OneAxisEllipsoid earth = new OneAxisEllipsoid(6378136.460, 1.0 / 298.257222101, itrf);
183         SolarInputs97to05 in = SolarInputs97to05.getInstance();
184         earth.setAngularThreshold(1e-10);
185         DTM2000 atm = new DTM2000(in, sun, earth);
186 
187         // Computation and results
188         for (double alti = 400; alti < 1000; alti += 50) {
189             for (double lon = 0; lon < 6; lon += 0.5) {
190                 for (double lat = -1.5; lat < 1.5; lat += 0.5) {
191                     for (double hl = 0; hl < 6; hl += 0.5) {
192                         double rhoD = atm.getDensity(185, alti*1000, lon, lat, hl, 50, 150, 0, 0);
193                         Binary64 rho64 = atm.getDensity(185, new Binary64(alti*1000),
194                                                          new Binary64(lon), new Binary64(lat),
195                                                          new Binary64(hl), 50, 150, 0, 0);
196                         Assertions.assertEquals(rhoD, rho64.getReal(), rhoD * 1e-14);
197                     }
198                 }
199             }
200         }
201 
202     }
203 
204     /** Test issue 1365: NaN appears during integration due to bad computation of density. */
205     @Test
206     public void testIssue1365() {
207 
208         // GIVEN
209         // -----
210 
211         // Build the input params provider
212         final DTM2000InputParameters ip = new InputParamsIssue1365();
213 
214         // Prepare field
215         final Field<Binary64> field = Binary64Field.getInstance();
216 
217         // Build the date
218         final AbsoluteDate date = new AbsoluteDate("2005-09-08T19:19:10.000", TimeScalesFactory.getUTC());
219         final FieldAbsoluteDate<Binary64> fieldDate = new FieldAbsoluteDate<>(field, date);
220 
221         // Sun position at "date" in J2000
222         final Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
223         final Vector3D sunPositionItrf = new Vector3D(-5.230565928624774E10, -1.4059879929943967E11, 1.4263029155223734E10);
224 
225         // Get Sun at date
226         final CelestialBody sunAtDate = new SunPositionIssue1365(sunPositionItrf, itrf);
227 
228         // Get Earth body shape
229 
230         final OneAxisEllipsoid earth = ReferenceEllipsoid.getWgs84(itrf);
231         // Build the model
232         final DTM2000 atm = new DTM2000(ip, sunAtDate, earth);
233 
234         // Set the position & frame
235         final Vector3D position = new Vector3D(4355380.654425714, 2296727.21938809, -4575242.76838552);
236         final FieldVector3D<Binary64> fieldPosition = new FieldVector3D<>(field, position);
237         final Frame j2000 = FramesFactory.getEME2000();
238 
239         // WHEN
240         // ----
241 
242         final double density = atm.getDensity(date, position, j2000);
243         final Binary64 fieldDensity = atm.getDensity(fieldDate, fieldPosition, j2000);
244 
245         // THEN
246         // ----
247 
248         // Check that densities are not NaN
249         Assertions.assertFalse(Double.isNaN(density));
250         Assertions.assertFalse(Double.isNaN(fieldDensity.getReal()));
251     }
252 
253     /** Test issue 539. Density computation should be independent of user's default time zone.
254      * See <a href="https://gitlab.orekit.org/orekit/orekit/issues/539"> issue 539 on Orekit forge.</a>
255      */
256     @Test
257     public void testTimeZoneIndependantIssue539() {
258 
259         // Prepare input: Choose a date in summer time for "GMT+1" time zone.
260         // So that after 22h in GMT we are in the next day in local time
261         TimeScale utc     = TimeScalesFactory.getUTC();
262         AbsoluteDate date = new AbsoluteDate("2000-04-01T22:30:00.000", utc);
263 
264         // LEO random position, in GCRF
265         Vector3D position = new Vector3D(-1038893.194, -4654348.144, 5021579.14);
266         Frame gcrf = FramesFactory.getGCRF();
267 
268         // Get ITRF and Earth ellipsoid
269         Frame itrf = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
270         PVCoordinatesProvider sun = CelestialBodyFactory.getSun();
271         OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.GRIM5C1_EARTH_EQUATORIAL_RADIUS,
272                                                       Constants.GRIM5C1_EARTH_FLATTENING, itrf);
273         SolarInputs97to05 in = SolarInputs97to05.getInstance();
274         earth.setAngularThreshold(1e-10);
275         DTM2000 atm = new DTM2000(in, sun, earth);
276 
277         // Store user time zone
278         TimeZone defaultTZ = TimeZone.getDefault();
279 
280         // Set default time zone to UTC & get density
281         TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC"));
282         double rhoUtc = atm.getDensity(date, position, gcrf);
283 
284         // Set default time zone to GMT+1 & get density
285         TimeZone.setDefault(TimeZone.getTimeZone("Europe/Paris"));
286         double rhoParis = atm.getDensity(date, position, gcrf);
287 
288         // Check that the 2 densities are equal
289         Assertions.assertEquals(0., rhoUtc - rhoParis, 0.);
290 
291         // Set back default time zone to what it was before the test, to avoid any interference with another routine
292         TimeZone.setDefault(defaultTZ);
293     }
294 
295     @BeforeEach
296     public void setUp() {
297         Utils.setDataRoot("regular-data");
298     }
299 
300     /** DTM2000 solar activity input parameters for testIssue1365. */
301     @SuppressWarnings("serial")
302     private static class InputParamsIssue1365 implements DTM2000InputParameters {
303         @Override
304         public AbsoluteDate getMinDate() { return new AbsoluteDate(2005, 9, 8, TimeScalesFactory.getUTC()); }
305         @Override
306         public AbsoluteDate getMaxDate() { return new AbsoluteDate(2005, 9, 9, TimeScalesFactory.getUTC()); }
307         @Override
308         public double getInstantFlux(AbsoluteDate date) { return 587.9532986111111; }
309         @Override
310         public double getMeanFlux(AbsoluteDate date) { return 99.5; }
311         @Override
312         public double getThreeHourlyKP(AbsoluteDate date) { return 1.7; }
313         @Override
314         public double get24HoursKp(AbsoluteDate date) { return 1.5875; }
315     }
316     
317     /** Sun position for testIssue1365. */
318     @SuppressWarnings("serial")
319     private static class SunPositionIssue1365 implements CelestialBody {
320         
321         private final Vector3D sunPositionItrf;
322         private final Frame itrf;
323 
324         /** Constructor.
325          * @param sunPositionItrf
326          * @param itrf
327          */
328         private SunPositionIssue1365(Vector3D sunPositionItrf,
329                                      Frame itrf) {
330             this.sunPositionItrf = sunPositionItrf;
331             this.itrf = itrf;
332         }
333         
334         @Override
335         public TimeStampedPVCoordinates getPVCoordinates(AbsoluteDate date, Frame frame) {
336             return new TimeStampedPVCoordinates(date, sunPositionItrf, Vector3D.ZERO);
337         }
338         @Override
339         public <T extends CalculusFieldElement<T>> TimeStampedFieldPVCoordinates<T> getPVCoordinates(FieldAbsoluteDate<T> date, Frame frame) {
340             return new TimeStampedFieldPVCoordinates<T>(date, date.getField().getOne(), getPVCoordinates(date.toAbsoluteDate(), frame));
341         }
342         @Override
343         public String getName() { return "SUN"; }
344         @Override
345         public Frame getInertiallyOrientedFrame() { return itrf; }
346         @Override
347         public double getGM() { return Constants.JPL_SSD_SUN_GM; }
348         @Override
349         public Frame getBodyOrientedFrame() { return itrf; }
350     }
351 }