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.estimation.leastsquares;
18  
19  import org.hipparchus.geometry.euclidean.threed.Vector3D;
20  import org.hipparchus.linear.RealMatrix;
21  import org.hipparchus.util.FastMath;
22  import org.junit.jupiter.api.Assertions;
23  import org.junit.jupiter.api.Test;
24  import org.orekit.KeyValueFileParser;
25  import org.orekit.Utils;
26  import org.orekit.attitudes.AttitudeProvider;
27  import org.orekit.bodies.CelestialBody;
28  import org.orekit.bodies.OneAxisEllipsoid;
29  import org.orekit.errors.OrekitException;
30  import org.orekit.estimation.common.AbstractOrbitDetermination;
31  import org.orekit.estimation.common.ParameterKey;
32  import org.orekit.estimation.common.ResultBatchLeastSquares;
33  import org.orekit.forces.ForceModel;
34  import org.orekit.forces.drag.DragForce;
35  import org.orekit.forces.drag.DragSensitive;
36  import org.orekit.forces.empirical.AccelerationModel;
37  import org.orekit.forces.empirical.ParametricAcceleration;
38  import org.orekit.forces.empirical.PolynomialAccelerationModel;
39  import org.orekit.forces.gravity.HolmesFeatherstoneAttractionModel;
40  import org.orekit.forces.gravity.OceanTides;
41  import org.orekit.forces.gravity.Relativity;
42  import org.orekit.forces.gravity.SolidTides;
43  import org.orekit.forces.gravity.ThirdBodyAttraction;
44  import org.orekit.forces.gravity.potential.GravityFieldFactory;
45  import org.orekit.forces.gravity.potential.ICGEMFormatReader;
46  import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
47  import org.orekit.forces.radiation.KnockeRediffusedForceModel;
48  import org.orekit.forces.radiation.RadiationSensitive;
49  import org.orekit.forces.radiation.SolarRadiationPressure;
50  import org.orekit.models.earth.atmosphere.Atmosphere;
51  import org.orekit.orbits.Orbit;
52  import org.orekit.orbits.PositionAngleType;
53  import org.orekit.propagation.conversion.NumericalPropagatorBuilder;
54  import org.orekit.propagation.conversion.ODEIntegratorBuilder;
55  import org.orekit.time.TimeScalesFactory;
56  import org.orekit.utils.IERSConventions;
57  import org.orekit.utils.ParameterDriver;
58  import org.orekit.utils.ParameterDriversList;
59  import org.orekit.utils.ParameterDriversList.DelegatingDriver;
60  
61  import java.io.File;
62  import java.io.IOException;
63  import java.net.URISyntaxException;
64  import java.text.ParseException;
65  import java.util.ArrayList;
66  import java.util.List;
67  import java.util.NoSuchElementException;
68  
69  public class NumericalOrbitDeterminationTest extends AbstractOrbitDetermination<NumericalPropagatorBuilder> {
70  
71      /** Gravity field. */
72      private NormalizedSphericalHarmonicsProvider gravityField;
73  
74      /** {@inheritDoc} */
75      @Override
76      protected void createGravityField(final KeyValueFileParser<ParameterKey> parser)
77          throws NoSuchElementException {
78  
79          final int degree = parser.getInt(ParameterKey.CENTRAL_BODY_DEGREE);
80          final int order  = FastMath.min(degree, parser.getInt(ParameterKey.CENTRAL_BODY_ORDER));
81          gravityField = GravityFieldFactory.getNormalizedProvider(degree, order);
82      }
83  
84      /** {@inheritDoc} */
85      @Override
86      protected double getMu() {
87          return gravityField.getMu();
88      }
89  
90      /** {@inheritDoc} */
91      @Override
92      protected NumericalPropagatorBuilder createPropagatorBuilder(final Orbit referenceOrbit,
93                                                                   final ODEIntegratorBuilder builder,
94                                                                   final double positionScale) {
95          return new NumericalPropagatorBuilder(referenceOrbit, builder, PositionAngleType.MEAN,
96                                                positionScale);
97      }
98  
99      /** {@inheritDoc} */
100     @Override
101     protected void setMass(final NumericalPropagatorBuilder propagatorBuilder, final double mass) {
102         propagatorBuilder.setMass(mass);
103     }
104 
105     /** {@inheritDoc} */
106     @Override
107     protected List<ParameterDriver> setGravity(final NumericalPropagatorBuilder propagatorBuilder,
108                                                final OneAxisEllipsoid body) {
109         final ForceModel gravityModel = new HolmesFeatherstoneAttractionModel(body.getBodyFrame(), gravityField);
110         propagatorBuilder.addForceModel(gravityModel);
111         return gravityModel.getParametersDrivers();
112     }
113 
114     /** {@inheritDoc} */
115     @Override
116     protected List<ParameterDriver> setOceanTides(final NumericalPropagatorBuilder propagatorBuilder,
117                                                   final IERSConventions conventions,
118                                                   final OneAxisEllipsoid body,
119                                                   final int degree, final int order) {
120         final ForceModel tidesModel = new OceanTides(body.getBodyFrame(),
121                                                      gravityField.getAe(), gravityField.getMu(),
122                                                      degree, order, conventions,
123                                                      TimeScalesFactory.getUT1(conventions, true));
124         propagatorBuilder.addForceModel(tidesModel);
125         return tidesModel.getParametersDrivers();
126     }
127 
128     /** {@inheritDoc} */
129     @Override
130     protected List<ParameterDriver> setSolidTides(final NumericalPropagatorBuilder propagatorBuilder,
131                                                   final IERSConventions conventions,
132                                                   final OneAxisEllipsoid body,
133                                                   final CelestialBody[] solidTidesBodies) {
134         final ForceModel tidesModel = new SolidTides(body.getBodyFrame(),
135                                                      gravityField.getAe(), gravityField.getMu(),
136                                                      gravityField.getTideSystem(), conventions,
137                                                      TimeScalesFactory.getUT1(conventions, true),
138                                                      solidTidesBodies);
139         propagatorBuilder.addForceModel(tidesModel);
140         return tidesModel.getParametersDrivers();
141     }
142 
143     /** {@inheritDoc} */
144     @Override
145     protected List<ParameterDriver> setThirdBody(final NumericalPropagatorBuilder propagatorBuilder,
146                                                  final CelestialBody thirdBody) {
147         final ForceModel thirdBodyModel = new ThirdBodyAttraction(thirdBody);
148         propagatorBuilder.addForceModel(thirdBodyModel);
149         return thirdBodyModel.getParametersDrivers();
150     }
151 
152     
153     /** {@inheritDoc} */
154     @Override
155     protected List<ParameterDriver> setDrag(final NumericalPropagatorBuilder propagatorBuilder,
156                                             final Atmosphere atmosphere, final DragSensitive spacecraft) {
157         final ForceModel dragModel = new DragForce(atmosphere, spacecraft);
158         propagatorBuilder.addForceModel(dragModel);
159         return dragModel.getParametersDrivers();
160     }
161     
162     /** {@inheritDoc} */
163     @Override
164     protected List<ParameterDriver> setSolarRadiationPressure(final NumericalPropagatorBuilder propagatorBuilder, final CelestialBody sun,
165                                                               final OneAxisEllipsoid earth, final RadiationSensitive spacecraft) {
166         final ForceModel srpModel = new SolarRadiationPressure(sun, earth, spacecraft);
167         propagatorBuilder.addForceModel(srpModel);
168         return srpModel.getParametersDrivers();
169     }
170 
171     /** {@inheritDoc} */
172     @Override
173     protected List<ParameterDriver> setAlbedoInfrared(final NumericalPropagatorBuilder propagatorBuilder,
174                                                       final CelestialBody sun, final double equatorialRadius,
175                                                       final double angularResolution,
176                                                       final RadiationSensitive spacecraft) {
177         final ForceModel albedoIR = new KnockeRediffusedForceModel(sun, spacecraft, equatorialRadius, angularResolution);
178         propagatorBuilder.addForceModel(albedoIR);
179         return albedoIR.getParametersDrivers();
180     }
181 
182     /** {@inheritDoc} */
183     @Override
184     protected List<ParameterDriver> setRelativity(final NumericalPropagatorBuilder propagatorBuilder) {
185         final ForceModel relativityModel = new Relativity(gravityField.getMu());
186         propagatorBuilder.addForceModel(relativityModel);
187         return relativityModel.getParametersDrivers();
188     }
189 
190     /** {@inheritDoc} */
191     @Override
192     protected List<ParameterDriver> setPolynomialAcceleration(final NumericalPropagatorBuilder propagatorBuilder,
193                                                              final String name, final Vector3D direction, final int degree) {
194         final AccelerationModel accModel = new PolynomialAccelerationModel(name, null, degree);
195         final ForceModel polynomialModel = new ParametricAcceleration(direction, true, accModel);
196         propagatorBuilder.addForceModel(polynomialModel);
197         return polynomialModel.getParametersDrivers();
198     }
199 
200     /** {@inheritDoc} */
201     @Override
202     protected void setAttitudeProvider(final NumericalPropagatorBuilder propagatorBuilder,
203                                        final AttitudeProvider attitudeProvider) {
204         propagatorBuilder.setAttitudeProvider(attitudeProvider);
205     }
206 
207     @Test
208     // Orbit determination for Lageos2 based on SLR (range) measurements
209     public void testLageos2()
210         throws URISyntaxException, IllegalArgumentException, IOException, OrekitException {
211 
212     	// input in resources directory
213         final String inputPath = NumericalOrbitDeterminationTest.class.getClassLoader().getResource("orbit-determination/Lageos2/od_test_Lageos2.in").toURI().getPath();
214         final File input  = new File(inputPath);
215 
216         // configure Orekit data access
217         Utils.setDataRoot("orbit-determination/february-2016:potential/icgem-format");
218         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("eigen-6s-truncated", true));
219 
220         //orbit determination run.
221         ResultBatchLeastSquares odLageos2 = runBLS(input, false);
222 
223         //test
224         //definition of the accuracy for the test
225         final double distanceAccuracy = 0.62;
226         final double velocityAccuracy = 1.4e-4;
227 
228         //test on the convergence
229         final int numberOfIte  = 5;
230         final int numberOfEval = 5;
231 
232         Assertions.assertEquals(numberOfIte, odLageos2.getNumberOfIteration());
233         Assertions.assertEquals(numberOfEval, odLageos2.getNumberOfEvaluation());
234 
235         //test on the estimated position and velocity
236         final Vector3D estimatedPos = odLageos2.getEstimatedPV().getPosition();
237         final Vector3D estimatedVel = odLageos2.getEstimatedPV().getVelocity();
238         // Ref position from "lageos2_cpf_160213_5451.jax"
239         final Vector3D refPos = new Vector3D(7526994.072, -9646309.832, 1464110.239);
240         final Vector3D refVel = new Vector3D(3033.794, 1715.265, -4447.659);
241         Assertions.assertEquals(0.0, Vector3D.distance(refPos, estimatedPos), distanceAccuracy);
242         Assertions.assertEquals(0.0, Vector3D.distance(refVel, estimatedVel), velocityAccuracy);
243 
244         //test on statistic for the range residuals
245         final long nbRange = 95;
246         final double[] RefStatRange = { -0.756, 0.845, 0.0, 0.261 };
247         Assertions.assertEquals(nbRange, odLageos2.getRangeStat().getN());
248         Assertions.assertEquals(RefStatRange[0], odLageos2.getRangeStat().getMin(),               distanceAccuracy);
249         Assertions.assertEquals(RefStatRange[1], odLageos2.getRangeStat().getMax(),               distanceAccuracy);
250         Assertions.assertEquals(RefStatRange[2], odLageos2.getRangeStat().getMean(),              distanceAccuracy);
251         Assertions.assertEquals(RefStatRange[3], odLageos2.getRangeStat().getStandardDeviation(), distanceAccuracy);
252     }
253 
254     @Test
255     // Orbit determination for GNSS satellite based on range measurements
256     public void testGNSS()
257         throws URISyntaxException, IllegalArgumentException, IOException, OrekitException {
258 
259     	// input in resources directory
260         final String inputPath = NumericalOrbitDeterminationTest.class.getClassLoader().getResource("orbit-determination/GNSS/od_test_GPS07.in").toURI().getPath();
261         final File input  = new File(inputPath);
262 
263         // configure Orekit data access
264         Utils.setDataRoot("orbit-determination/february-2016:potential/icgem-format");
265         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("eigen-6s-truncated", true));
266 
267         //orbit determination run.
268         ResultBatchLeastSquares odGNSS = runBLS(input, false);
269 
270         //test
271         //definition of the accuracy for the test
272 
273         final double distanceAccuracy = 1.06;
274         final double velocityAccuracy = 2.27e-3;
275 
276         //test on the convergence
277         final int numberOfIte  = 2;
278         final int numberOfEval = 3;
279 
280         Assertions.assertEquals(numberOfIte, odGNSS.getNumberOfIteration());
281         Assertions.assertEquals(numberOfEval, odGNSS.getNumberOfEvaluation());
282 
283         //test on the estimated position and velocity (reference from IGS-MGEX file com18836.sp3)
284         final Vector3D estimatedPos = odGNSS.getEstimatedPV().getPosition();
285         final Vector3D estimatedVel = odGNSS.getEstimatedPV().getVelocity();
286         final Vector3D refPos = new Vector3D(-2747606.680868164, 22572091.30648564, 13522761.402325712);
287         final Vector3D refVel = new Vector3D(-2729.5151218788005, 1142.6629459030657, -2523.9055974487947);
288         Assertions.assertEquals(0.0, Vector3D.distance(refPos, estimatedPos), distanceAccuracy);
289         Assertions.assertEquals(0.0, Vector3D.distance(refVel, estimatedVel), velocityAccuracy);
290 
291         //test on statistic for the range residuals
292         final long nbRangeInit     = 8981;
293         final long nbRangeExcluded = 305;
294         final double[] RefStatRange = { -3.853, 8.307, 0.0, 0.873 };
295         Assertions.assertEquals(nbRangeInit - nbRangeExcluded, odGNSS.getRangeStat().getN());
296         Assertions.assertEquals(RefStatRange[0], odGNSS.getRangeStat().getMin(),               1.0e-3);
297         Assertions.assertEquals(RefStatRange[1], odGNSS.getRangeStat().getMax(),               1.0e-3);
298         Assertions.assertEquals(RefStatRange[2], odGNSS.getRangeStat().getMean(),              1.0e-3);
299         Assertions.assertEquals(RefStatRange[3], odGNSS.getRangeStat().getStandardDeviation(), 1.0e-3);
300 
301     }
302 
303     @Test
304     // Orbit determination for range, azimuth elevation measurements
305     public void testW3B()
306         throws URISyntaxException, IllegalArgumentException, IOException,
307               OrekitException, ParseException {
308 
309     	// input in resources directory
310         final String inputPath = NumericalOrbitDeterminationTest.class.getClassLoader().getResource("orbit-determination/W3B/od_test_W3.in").toURI().getPath();
311         final File input  = new File(inputPath);
312 
313         // configure Orekit data access
314         Utils.setDataRoot("orbit-determination/W3B:potential/icgem-format");
315         GravityFieldFactory.addPotentialCoefficientsReader(new ICGEMFormatReader("eigen-6s-truncated", true));
316 
317         //orbit determination run.
318         ResultBatchLeastSquares odsatW3 = runBLS(input, false);   
319         //test
320         //definition of the accuracy for the test
321         final double distanceAccuracy = 0.1;
322         final double velocityAccuracy = 1e-4;
323         final double angleAccuracy    = 1e-5;
324 
325         //test on the convergence (with some margins)
326         Assertions.assertTrue(odsatW3.getNumberOfIteration()  <  6);
327         Assertions.assertTrue(odsatW3.getNumberOfEvaluation() < 10);
328 
329         //test on the estimated position and velocity
330         final Vector3D estimatedPos = odsatW3.getEstimatedPV().getPosition();
331         final Vector3D estimatedVel = odsatW3.getEstimatedPV().getVelocity();
332         final Vector3D refPos = new Vector3D(-40541446.236, -9905357.943, 206777.082);
333         final Vector3D refVel = new Vector3D(759.0685, -1476.5156, 54.7931);
334         Assertions.assertEquals(0.0, Vector3D.distance(refPos, estimatedPos), distanceAccuracy);
335         Assertions.assertEquals(0.0, Vector3D.distance(refVel, estimatedVel), velocityAccuracy);
336 
337         //test on propagator parameters
338         final double dragCoef  = -0.2154;
339         final ParameterDriversList propagatorParameters = odsatW3.getPropagatorParameters();
340         Assertions.assertEquals(dragCoef, propagatorParameters.getDrivers().get(0).getValue(), 1e-3);
341         final Vector3D leakAcceleration0 =
342                         new Vector3D(propagatorParameters.getDrivers().get(1).getValue(),
343                                      propagatorParameters.getDrivers().get(3).getValue(),
344                                      propagatorParameters.getDrivers().get(5).getValue());
345         //Assertions.assertEquals(7.215e-6, leakAcceleration.getNorm(), 1.0e-8);
346         Assertions.assertEquals(8.005e-6, leakAcceleration0.getNorm(), 1.0e-8);
347         final Vector3D leakAcceleration1 =
348                         new Vector3D(propagatorParameters.getDrivers().get(2).getValue(),
349                                      propagatorParameters.getDrivers().get(4).getValue(),
350                                      propagatorParameters.getDrivers().get(6).getValue());
351         Assertions.assertEquals(3.060e-10, leakAcceleration1.getNorm(), 1.0e-12);
352 
353         //test on measurements parameters
354         final List<DelegatingDriver> list = new ArrayList<>(odsatW3.getMeasurementsParameters().getDrivers());
355         sortParametersChanges(list);
356 
357         //station CastleRock
358         final double[] CastleAzElBias  = { 0.062701342, -0.003613508 };
359         final double   CastleRangeBias = 11274.4195;
360         Assertions.assertEquals(CastleAzElBias[0], FastMath.toDegrees(list.get(0).getValue()), angleAccuracy);
361         Assertions.assertEquals(CastleAzElBias[1], FastMath.toDegrees(list.get(1).getValue()), angleAccuracy);
362         Assertions.assertEquals(CastleRangeBias,   list.get(2).getValue(),                     distanceAccuracy);
363 
364         //station Fucino
365         final double[] FucAzElBias  = { -0.053526137, 0.075483886 };
366         final double   FucRangeBias = 13467.8361;
367         Assertions.assertEquals(FucAzElBias[0], FastMath.toDegrees(list.get(3).getValue()), angleAccuracy);
368         Assertions.assertEquals(FucAzElBias[1], FastMath.toDegrees(list.get(4).getValue()), angleAccuracy);
369         Assertions.assertEquals(FucRangeBias,   list.get(5).getValue(),                     distanceAccuracy);
370 
371         //station Kumsan
372         final double[] KumAzElBias  = { -0.023574208, -0.054520756 };
373         final double   KumRangeBias = 13512.4898;
374         Assertions.assertEquals(KumAzElBias[0], FastMath.toDegrees(list.get(6).getValue()), angleAccuracy);
375         Assertions.assertEquals(KumAzElBias[1], FastMath.toDegrees(list.get(7).getValue()), angleAccuracy);
376         Assertions.assertEquals(KumRangeBias,   list.get(8).getValue(),                     distanceAccuracy);
377 
378         //station Pretoria
379         final double[] PreAzElBias = { 0.030201539, 0.009747877 };
380         final double PreRangeBias = 13594.1758;
381         Assertions.assertEquals(PreAzElBias[0], FastMath.toDegrees(list.get( 9).getValue()), angleAccuracy);
382         Assertions.assertEquals(PreAzElBias[1], FastMath.toDegrees(list.get(10).getValue()), angleAccuracy);
383         Assertions.assertEquals(PreRangeBias,   list.get(11).getValue(),                     distanceAccuracy);
384 
385         //station Uralla
386         final double[] UraAzElBias = { 0.167814449, -0.12305252 };
387         final double UraRangeBias = 13450.2320;
388         Assertions.assertEquals(UraAzElBias[0], FastMath.toDegrees(list.get(12).getValue()), angleAccuracy);
389         Assertions.assertEquals(UraAzElBias[1], FastMath.toDegrees(list.get(13).getValue()), angleAccuracy);
390         Assertions.assertEquals(UraRangeBias,   list.get(14).getValue(),                     distanceAccuracy);
391 
392         //test on statistic for the range residuals
393         final long nbRange = 182;
394         //statistics for the range residual (min, max, mean, std)
395         final double[] RefStatRange = { -18.39149369, 12.54165259, -4.32E-05, 4.374712716 };
396         Assertions.assertEquals(nbRange, odsatW3.getRangeStat().getN());
397         Assertions.assertEquals(RefStatRange[0], odsatW3.getRangeStat().getMin(),               distanceAccuracy);
398         Assertions.assertEquals(RefStatRange[1], odsatW3.getRangeStat().getMax(),               distanceAccuracy);
399         Assertions.assertEquals(RefStatRange[2], odsatW3.getRangeStat().getMean(),              distanceAccuracy);
400         Assertions.assertEquals(RefStatRange[3], odsatW3.getRangeStat().getStandardDeviation(), distanceAccuracy);
401 
402         //test on statistic for the azimuth residuals
403         final long nbAzi = 339;
404         //statistics for the azimuth residual (min, max, mean, std)
405         final double[] RefStatAzi = { -0.043033616, 0.025297558, -1.39E-10, 0.010063041 };
406         Assertions.assertEquals(nbAzi, odsatW3.getAzimStat().getN());
407         Assertions.assertEquals(RefStatAzi[0], odsatW3.getAzimStat().getMin(),               angleAccuracy);
408         Assertions.assertEquals(RefStatAzi[1], odsatW3.getAzimStat().getMax(),               angleAccuracy);
409         Assertions.assertEquals(RefStatAzi[2], odsatW3.getAzimStat().getMean(),              angleAccuracy);
410         Assertions.assertEquals(RefStatAzi[3], odsatW3.getAzimStat().getStandardDeviation(), angleAccuracy);
411 
412         //test on statistic for the elevation residuals
413         final long nbEle = 339;
414         final double[] RefStatEle = { -0.025061971, 0.056294405, -4.10E-11, 0.011604931 };
415         Assertions.assertEquals(nbEle, odsatW3.getElevStat().getN());
416         Assertions.assertEquals(RefStatEle[0], odsatW3.getElevStat().getMin(),               angleAccuracy);
417         Assertions.assertEquals(RefStatEle[1], odsatW3.getElevStat().getMax(),               angleAccuracy);
418         Assertions.assertEquals(RefStatEle[2], odsatW3.getElevStat().getMean(),              angleAccuracy);
419         Assertions.assertEquals(RefStatEle[3], odsatW3.getElevStat().getStandardDeviation(), angleAccuracy);
420 
421         RealMatrix covariances = odsatW3.getCovariances();
422         Assertions.assertEquals(28, covariances.getRowDimension());
423         Assertions.assertEquals(28, covariances.getColumnDimension());
424 
425         // drag coefficient variance
426         Assertions.assertEquals(0.687998, covariances.getEntry(6, 6), 1.0e-4);
427 
428         // leak-X constant term variance
429         Assertions.assertEquals(2.0540e-12, covariances.getEntry(7, 7), 1.0e-16);
430 
431         // leak-Y constant term variance
432         Assertions.assertEquals(2.4930e-11, covariances.getEntry(9, 9), 1.0e-15);
433 
434         // leak-Z constant term variance
435         Assertions.assertEquals(7.6720e-11, covariances.getEntry(11, 11), 1.0e-15);
436     }
437 
438 }