1   /* Copyright 2002-2022 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.troposphere;
18  
19  import org.hipparchus.Field;
20  import org.hipparchus.CalculusFieldElement;
21  import org.hipparchus.analysis.differentiation.DSFactory;
22  import org.hipparchus.analysis.differentiation.DerivativeStructure;
23  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
24  import org.hipparchus.geometry.euclidean.threed.Vector3D;
25  import org.hipparchus.util.Decimal64Field;
26  import org.hipparchus.util.FastMath;
27  import org.hipparchus.util.Precision;
28  import org.junit.Assert;
29  import org.junit.Before;
30  import org.junit.BeforeClass;
31  import org.junit.Test;
32  import org.orekit.Utils;
33  import org.orekit.attitudes.Attitude;
34  import org.orekit.bodies.FieldGeodeticPoint;
35  import org.orekit.bodies.GeodeticPoint;
36  import org.orekit.bodies.OneAxisEllipsoid;
37  import org.orekit.errors.OrekitException;
38  import org.orekit.estimation.measurements.GroundStation;
39  import org.orekit.frames.Frame;
40  import org.orekit.frames.FramesFactory;
41  import org.orekit.frames.TopocentricFrame;
42  import org.orekit.orbits.FieldKeplerianOrbit;
43  import org.orekit.orbits.FieldOrbit;
44  import org.orekit.orbits.Orbit;
45  import org.orekit.orbits.OrbitType;
46  import org.orekit.orbits.PositionAngle;
47  import org.orekit.propagation.FieldSpacecraftState;
48  import org.orekit.propagation.SpacecraftState;
49  import org.orekit.propagation.numerical.NumericalPropagator;
50  import org.orekit.time.AbsoluteDate;
51  import org.orekit.time.FieldAbsoluteDate;
52  import org.orekit.time.TimeScalesFactory;
53  import org.orekit.utils.Constants;
54  import org.orekit.utils.IERSConventions;
55  
56  public class FieldMendesPavlisModelTest {
57  
58      private static double epsilon = 1e-6;
59  
60      @BeforeClass
61      public static void setUpGlobal() {
62          Utils.setDataRoot("atmosphere");
63      }
64  
65      @Before
66      public void setUp() throws OrekitException {
67          Utils.setDataRoot("regular-data:potential/shm-format");
68      }
69  
70      @Test
71      public void testZenithDelay() {
72          doTestZenithDelay(Decimal64Field.getInstance());
73      }
74  
75      private <T extends CalculusFieldElement<T>> void doTestZenithDelay(final Field<T> field) {
76          final T zero = field.getZero();
77          // Site:   McDonald Observatory
78          //         latitude:  30.67166667 °
79          //         longitude: -104.0250 °
80          //         height:    2010.344 m
81          //
82          // Meteo:  pressure:            798.4188 hPa
83          //         water vapor presure: 14.322 hPa
84          //         temperature:         300.15 K
85          //         humidity:            40 %
86          //
87          // Ref:    Petit, G. and Luzum, B. (eds.), IERS Conventions (2010),
88          //         IERS Technical Note No. 36, BKG (2010)
89          
90          final double latitude     = FastMath.toRadians(30.67166667);
91          final double longitude    = FastMath.toRadians(-104.0250);
92          final double height       = 2010.344;
93          final double pressure     = 798.4188;
94          final double temperature  = 300.15;
95          final double humidity     = 0.4;
96          final double lambda       = 0.532;
97          final FieldGeodeticPoint<T> point = new FieldGeodeticPoint<>(zero.add(latitude), zero.add(longitude), zero.add(height));
98          
99          // Expected zenith hydrostatic delay: 1.932992 m (Ref)
100         final double expectedHydroDelay = 1.932992;
101         // Expected zenith wet delay: 0.223375*10-2 m (Ref)
102         final double expectedWetDelay   = 0.223375e-2;
103         // Expected total zenith delay: 1.935226 m (Ref)
104         final double expectedDelay      = 1.935226;
105         
106         final double precision = 4.0e-6;
107         
108         final FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field, 2009, 8, 12, TimeScalesFactory.getUTC());
109 
110         final MendesPavlisModel model = new MendesPavlisModel(temperature, pressure,
111                                                                humidity, lambda);
112         
113         final T[] computedDelay = model.computeZenithDelay(point, model.getParameters(field), date);
114         
115         Assert.assertEquals(expectedHydroDelay, computedDelay[0].getReal(),                    precision);
116         Assert.assertEquals(expectedWetDelay,   computedDelay[1].getReal(), precision);
117         Assert.assertEquals(expectedDelay,      computedDelay[0].getReal() + computedDelay[1].getReal(), precision);
118 
119     }
120    
121     @Test
122     public void testMappingFactors() {
123         doTestMappingFactors(Decimal64Field.getInstance());
124     }
125 
126     private <T extends CalculusFieldElement<T>> void doTestMappingFactors(final Field<T> field) {
127         final T zero = field.getZero();
128 
129         // Site:   McDonald Observatory
130         //         latitude:  30.67166667 °
131         //         longitude: -104.0250 °
132         //         height:    2075 m
133         //
134         // Meteo:  pressure:            798.4188 hPa
135         //         water vapor presure: 14.322 hPa
136         //         temperature:         300.15 K
137         //         humidity:            40 %
138         //
139         // Ref:    Petit, G. and Luzum, B. (eds.), IERS Conventions (2010),
140         //         IERS Technical Note No. 36, BKG (2010)
141 
142         final FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field, 2009, 8, 12, TimeScalesFactory.getUTC());
143         
144         final double latitude     = FastMath.toRadians(30.67166667);
145         final double longitude    = FastMath.toRadians(-104.0250);
146         final double height       = 2075;
147         final double pressure     = 798.4188;
148         final double temperature  = 300.15;
149         final double humidity     = 0.4;
150         final double lambda       = 0.532;
151         final FieldGeodeticPoint<T> point = new FieldGeodeticPoint<>(zero.add(latitude), zero.add(longitude), zero.add(height));
152 
153         final double elevation        = FastMath.toRadians(15.0);
154         // Expected mapping factor: 3.80024367 (Ref)
155         final double expectedMapping    = 3.80024367;
156         
157         // Test for the second constructor
158         final MendesPavlisModel model = new MendesPavlisModel(temperature, pressure,
159                                                               humidity, lambda);
160         
161         final T[] computedMapping = model.mappingFactors(zero.add(elevation), point, date);
162 
163         Assert.assertEquals(expectedMapping, computedMapping[0].getReal(), 5.0e-8);
164         Assert.assertEquals(expectedMapping, computedMapping[1].getReal(), 5.0e-8);
165     }
166 
167     @Test
168     public void testDelay() {
169         doTestDelay(Decimal64Field.getInstance());
170     }
171 
172     private <T extends CalculusFieldElement<T>> void doTestDelay(final Field<T> field) {
173         final T zero = field.getZero();
174         final double elevation = 10d;
175         final double height = 100d;
176         final FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field);
177         final FieldGeodeticPoint<T> point = new FieldGeodeticPoint<>(zero.add(FastMath.toRadians(45.0)), zero.add(FastMath.toRadians(45.0)), zero.add(height));
178         MendesPavlisModel model = MendesPavlisModel.getStandardModel(0.6943);
179         final T path = model.pathDelay(zero.add(FastMath.toRadians(elevation)), point, model.getParameters(field), date);
180         Assert.assertTrue(Precision.compareTo(path.getReal(), 20d, epsilon) < 0);
181         Assert.assertTrue(Precision.compareTo(path.getReal(), 0d, epsilon) > 0);
182     }
183 
184     @Test
185     public void testFixedHeight() {
186         doTestFixedHeight(Decimal64Field.getInstance());
187     }
188 
189     private <T extends CalculusFieldElement<T>> void doTestFixedHeight(final Field<T> field) {
190         final T zero = field.getZero();
191         final FieldAbsoluteDate<T> date = new FieldAbsoluteDate<>(field);
192         final FieldGeodeticPoint<T> point = new FieldGeodeticPoint<>(zero.add(FastMath.toRadians(45.0)), zero.add(FastMath.toRadians(45.0)), zero.add(350.0));
193         MendesPavlisModel model = MendesPavlisModel.getStandardModel(0.6943);
194         T lastDelay = zero.add(Double.MAX_VALUE);
195         // delay shall decline with increasing elevation angle
196         for (double elev = 10d; elev < 90d; elev += 8d) {
197             final T delay = model.pathDelay(zero.add(FastMath.toRadians(elev)), point, model.getParameters(field), date);
198             Assert.assertTrue(Precision.compareTo(delay.getReal(), lastDelay.getReal(), epsilon) < 0);
199             lastDelay = delay;
200         }
201     }
202 
203     @Test
204     public void testDelayStateDerivatives() {
205 
206         // Geodetic point
207         final double latitude     = FastMath.toRadians(45.0);
208         final double longitude    = FastMath.toRadians(45.0);
209         final double height       = 0.0;
210         final GeodeticPoint point = new GeodeticPoint(latitude, longitude, height);
211         // Body: earth
212         final OneAxisEllipsoid earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS,
213                                                             Constants.WGS84_EARTH_FLATTENING,
214                                                             FramesFactory.getITRF(IERSConventions.IERS_2010, true));
215         // Topocentric frame
216         final TopocentricFrame baseFrame = new TopocentricFrame(earth, point, "topo");
217 
218         // Station
219         final GroundStation station = new GroundStation(baseFrame);
220         
221         // Tropospheric model
222         final MendesPavlisModel model = MendesPavlisModel.getStandardModel(0.65);
223 
224         // Derivative Structure
225         final DSFactory factory = new DSFactory(6, 1);
226         final DerivativeStructure a0       = factory.variable(0, 24464560.0);
227         final DerivativeStructure e0       = factory.variable(1, 0.05);
228         final DerivativeStructure i0       = factory.variable(2, 0.122138);
229         final DerivativeStructure pa0      = factory.variable(3, 3.10686);
230         final DerivativeStructure raan0    = factory.variable(4, 1.00681);
231         final DerivativeStructure anomaly0 = factory.variable(5, 0.048363);
232         final Field<DerivativeStructure> field = a0.getField();
233         final DerivativeStructure zero = field.getZero();
234 
235         // Field Date
236         final FieldAbsoluteDate<DerivativeStructure> dsDate = new FieldAbsoluteDate<>(field);
237         // Field Orbit
238         final Frame frame = FramesFactory.getEME2000();
239         final FieldOrbit<DerivativeStructure> dsOrbit = new FieldKeplerianOrbit<>(a0, e0, i0, pa0, raan0, anomaly0,
240                                                                                   PositionAngle.MEAN, frame,
241                                                                                   dsDate, zero.add(3.9860047e14));
242         // Field State
243         final FieldSpacecraftState<DerivativeStructure> dsState = new FieldSpacecraftState<>(dsOrbit);
244 
245         // Initial satellite elevation
246         final FieldVector3D<DerivativeStructure> position = dsState.getPVCoordinates().getPosition();
247         final DerivativeStructure dsElevation = baseFrame.getElevation(position, frame, dsDate);
248 
249         // Compute Delay with state derivatives
250         final FieldGeodeticPoint<DerivativeStructure> dsPoint = new FieldGeodeticPoint<>(zero.add(latitude), zero.add(longitude), zero.add(height));
251         final DerivativeStructure delay = model.pathDelay(dsElevation, dsPoint, model.getParameters(field), dsDate);
252 
253         final double[] compDeriv = delay.getAllDerivatives();
254 
255         // Field -> non-field
256         final Orbit orbit = dsOrbit.toOrbit();
257         final SpacecraftState state = dsState.toSpacecraftState();
258 
259         // Finite differences for reference values
260         final double[][] refDeriv = new double[1][6];
261         final OrbitType orbitType = OrbitType.KEPLERIAN;
262         final PositionAngle angleType = PositionAngle.MEAN;
263         double dP = 0.001;
264         double[] steps = NumericalPropagator.tolerances(1000000 * dP, orbit, orbitType)[0];
265         for (int i = 0; i < 6; i++) {
266             SpacecraftState stateM4 = shiftState(state, orbitType, angleType, -4 * steps[i], i);
267             final Vector3D positionM4 = stateM4.getPVCoordinates().getPosition();
268             final double elevationM4  = station.getBaseFrame().getElevation(positionM4, stateM4.getFrame(), stateM4.getDate());
269             double  delayM4 = model.pathDelay(elevationM4, point, model.getParameters(), stateM4.getDate());
270             
271             SpacecraftState stateM3 = shiftState(state, orbitType, angleType, -3 * steps[i], i);
272             final Vector3D positionM3 = stateM3.getPVCoordinates().getPosition();
273             final double elevationM3  = station.getBaseFrame().getElevation(positionM3, stateM3.getFrame(), stateM3.getDate());
274             double  delayM3 = model.pathDelay(elevationM3, point, model.getParameters(), stateM3.getDate());
275             
276             SpacecraftState stateM2 = shiftState(state, orbitType, angleType, -2 * steps[i], i);
277             final Vector3D positionM2 = stateM2.getPVCoordinates().getPosition();
278             final double elevationM2  = station.getBaseFrame().getElevation(positionM2, stateM2.getFrame(), stateM2.getDate());
279             double  delayM2 = model.pathDelay(elevationM2, point, model.getParameters(), stateM2.getDate());
280  
281             SpacecraftState stateM1 = shiftState(state, orbitType, angleType, -1 * steps[i], i);
282             final Vector3D positionM1 = stateM1.getPVCoordinates().getPosition();
283             final double elevationM1  = station.getBaseFrame().getElevation(positionM1, stateM1.getFrame(), stateM1.getDate());
284             double  delayM1 = model.pathDelay(elevationM1, point, model.getParameters(), stateM1.getDate());
285            
286             SpacecraftState stateP1 = shiftState(state, orbitType, angleType, 1 * steps[i], i);
287             final Vector3D positionP1 = stateP1.getPVCoordinates().getPosition();
288             final double elevationP1  = station.getBaseFrame().getElevation(positionP1, stateP1.getFrame(), stateP1.getDate());
289             double  delayP1 = model.pathDelay(elevationP1, point, model.getParameters(), stateP1.getDate());
290             
291             SpacecraftState stateP2 = shiftState(state, orbitType, angleType, 2 * steps[i], i);
292             final Vector3D positionP2 = stateP2.getPVCoordinates().getPosition();
293             final double elevationP2  = station.getBaseFrame().getElevation(positionP2, stateP2.getFrame(), stateP2.getDate());
294             double  delayP2 = model.pathDelay(elevationP2, point, model.getParameters(), stateP2.getDate());
295             
296             SpacecraftState stateP3 = shiftState(state, orbitType, angleType, 3 * steps[i], i);
297             final Vector3D positionP3 = stateP3.getPVCoordinates().getPosition();
298             final double elevationP3  = station.getBaseFrame().getElevation(positionP3, stateP3.getFrame(), stateP3.getDate());
299             double  delayP3 = model.pathDelay(elevationP3, point, model.getParameters(), stateP3.getDate());
300             
301             SpacecraftState stateP4 = shiftState(state, orbitType, angleType, 4 * steps[i], i);
302             final Vector3D positionP4 = stateP4.getPVCoordinates().getPosition();
303             final double elevationP4  = station.getBaseFrame().getElevation(positionP4, stateP4.getFrame(), stateP4.getDate());
304             double  delayP4 = model.pathDelay(elevationP4, point, model.getParameters(), stateP4.getDate());
305             
306             fillJacobianColumn(refDeriv, i, orbitType, angleType, steps[i],
307                                delayM4, delayM3, delayM2, delayM1,
308                                delayP1, delayP2, delayP3, delayP4);
309         }
310 
311         for (int i = 0; i < 6; i++) {
312             Assert.assertEquals(compDeriv[i + 1], refDeriv[0][i], 2.0e-11);
313         }
314     }
315 
316     private void fillJacobianColumn(double[][] jacobian, int column,
317                                     OrbitType orbitType, PositionAngle angleType, double h,
318                                     double sM4h, double sM3h,
319                                     double sM2h, double sM1h,
320                                     double sP1h, double sP2h,
321                                     double sP3h, double sP4h) {
322         for (int i = 0; i < jacobian.length; ++i) {
323             jacobian[i][column] = ( -3 * (sP4h - sM4h) +
324                                     32 * (sP3h - sM3h) -
325                                    168 * (sP2h - sM2h) +
326                                    672 * (sP1h - sM1h)) / (840 * h);
327         }
328     }
329 
330     private SpacecraftState shiftState(SpacecraftState state, OrbitType orbitType, PositionAngle angleType,
331                                        double delta, int column) {
332 
333         double[][] array = stateToArray(state, orbitType, angleType, true);
334         array[0][column] += delta;
335 
336         return arrayToState(array, orbitType, angleType, state.getFrame(), state.getDate(),
337                             state.getMu(), state.getAttitude());
338 
339     }
340 
341     private double[][] stateToArray(SpacecraftState state, OrbitType orbitType, PositionAngle angleType,
342                                   boolean withMass) {
343         double[][] array = new double[2][withMass ? 7 : 6];
344         orbitType.mapOrbitToArray(state.getOrbit(), angleType, array[0], array[1]);
345         if (withMass) {
346             array[0][6] = state.getMass();
347         }
348         return array;
349     }
350 
351     private SpacecraftState arrayToState(double[][] array, OrbitType orbitType, PositionAngle angleType,
352                                          Frame frame, AbsoluteDate date, double mu,
353                                          Attitude attitude) {
354         Orbit orbit = orbitType.mapArrayToOrbit(array[0], array[1], angleType, date, mu, frame);
355         return (array.length > 6) ?
356                new SpacecraftState(orbit, attitude) :
357                new SpacecraftState(orbit, attitude, array[0][6]);
358     }
359 
360 }